Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da67d9ff32 | |||
| 555c2ec5bd | |||
| 886bda43a4 | |||
| 1e8bda56e0 | |||
| 83733802fc | |||
| 2c96d6b097 | |||
| c04fc6a856 | |||
| a2ea9e5bbe | |||
| 3fca7161fc | |||
| 49dcd8785b | |||
| 780b4aa8d5 | |||
| 9598147dd5 | |||
| 4d2cfa3cb3 | |||
| fc4e9016cf | |||
| 530f0ecf12 | |||
| be9ff9c2c9 | |||
| 40a0e8e303 | |||
| dceede8602 | |||
| 6ffca719d9 | |||
| 1342ab6aaf | |||
| 4fa1b3cb06 | |||
| 6381ca58cf | |||
| b87c8087f5 | |||
| d39547bdc6 | |||
| c60ef2ccde | |||
| f39ae6f0c6 | |||
| bb0fb01c00 | |||
| b5b9e53c22 | |||
| 691a740111 |
@@ -12,7 +12,8 @@ update as the project evolves:
|
||||
- `gateway_core/`: boot profile and top-level role bootstrap.
|
||||
- `dali/`: vendored ESP-IDF DALI HAL/backend reused from LuatOS, including native raw receive fan-out.
|
||||
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
|
||||
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
|
||||
- `gateway_cache/`: ESP-IDF NVS adapter for the portable `dali_cpp` cache,
|
||||
plus gateway-internal scene/group definitions.
|
||||
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
|
||||
- `openknx_idf/`: ESP-IDF port layer for the OpenKNX `gateway/knx` and `gateway/tpuart` submodules, including NVS-backed OpenKNX memory, development KNX security storage, ETS cEMI programming support, UDP multicast/unicast plumbing, and a native TP-UART interface without the Arduino framework.
|
||||
- `gateway_modbus/`: gateway-owned Modbus TCP/RTU/ASCII config, generated DALI point tables, and provisioned Modbus model override dispatch.
|
||||
@@ -21,25 +22,33 @@ update as the project evolves:
|
||||
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
|
||||
- `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, W5500 SPI Ethernet startup/teardown, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, setup AP GPIO handling, and optional Wi-Fi reset GPIO handling for the native gateway.
|
||||
- `gateway_runtime/`: persistent runtime state, command queueing, and device info services.
|
||||
- `gateway_485_control/`: optional 485 Lua control bridge for framed `0x28 0x01` commands and `0x22 ... checksum` notifications at `9600 8N1`; disabled by default because UART0 must be moved off the ESP-IDF console first.
|
||||
- `gateway_485_control/`: optional 485 control bridge at `9600 8N1`; disabled by default because UART0 must be moved off the ESP-IDF console first. C++ gateway commands on this raw multi-client bus use the session-scoped frame described below.
|
||||
- `gateway_usb_setup/`: optional USB Serial/JTAG setup bridge; disabled by default so USB remains available for debug at boot.
|
||||
- `gateway_tridonic_hid/`: optional native USB OTG HID device that emulates the Tridonic DALI USB (VID:PID `17b5:0020`) protocol for masterConfigurator.
|
||||
- `knx`: The forked OpenKNX cEMI programming support, ESP-IDF port, and KNX security storage used by the gateway. You can edit this code when necessary to support missing ETS programming features or to implement the secure-session transport path.
|
||||
- `knx_dali_gw`: The forked OpenKNX DALI-GW function-property support used by the gateway. You can edit this code when necessary to support missing DALI-GW features or to fix bugs.
|
||||
|
||||
## Gateway DALI cache
|
||||
|
||||
The gateway owns the shared DALI cache for multi-user deployments. App, BLE,
|
||||
IP, Modbus, BACnet, KNX, and local control paths should treat the gateway cache
|
||||
as the shared read surface instead of maintaining separate app-side device-state
|
||||
caches. Transparent/setup raw forwarding paths remain bypass-oriented, but raw
|
||||
DALI bus observation still feeds the passive decoder when frames are visible to
|
||||
the gateway.
|
||||
`dali_cpp::DaliGatewayCache` owns the shared DALI cache and is the authoritative
|
||||
read surface for App, BLE, IP, Modbus, BACnet, KNX, and local control paths. It
|
||||
interprets direct, group, broadcast, scene, DTR, and settings commands; owns
|
||||
presence and reconciliation state; versions persisted address snapshots; and
|
||||
emits platform-neutral `DaliGatewayStatusUpdate` callbacks.
|
||||
|
||||
`gateway_cache` stores internal scene/group data and per-short-address DALI
|
||||
state. Device settings, group masks, scene levels, known flags, and the last
|
||||
runtime status are batched to NVS using `GATEWAY_CACHE_FLUSH_INTERVAL_MS`, which
|
||||
defaults to 10000 ms. Runtime status loaded from disk is marked stale until the
|
||||
gateway observes a bus command or the background refresher verifies it again.
|
||||
The ESP-IDF `gateway_cache` component no longer contains DALI cache behavior.
|
||||
It binds the portable cache persistence callbacks to NVS and continues to own
|
||||
only gateway-internal scene/group definitions. Device settings, group masks,
|
||||
scene levels, known flags, and the last runtime status are batched through the
|
||||
portable cache using `GATEWAY_CACHE_FLUSH_INTERVAL_MS`, which defaults to
|
||||
10000 ms. Runtime status loaded from disk is marked stale until a bus command
|
||||
or the background refresher verifies it again.
|
||||
|
||||
Protocol adapters register for semantic status updates instead of decoding raw
|
||||
DALI commands independently. The current KNX adapter maps the callback target,
|
||||
status, and affected short-address list into its group-object status updates.
|
||||
Transparent/setup raw forwarding paths remain bypass-oriented, while visible
|
||||
raw DALI bus traffic still feeds the portable cache.
|
||||
|
||||
`GATEWAY_CACHE_REFRESH_INTERVAL_MS` defaults to 120000 ms. When nonzero, the
|
||||
controller maintenance loop refreshes direct short-address actual levels one
|
||||
@@ -48,13 +57,206 @@ commands, address allocation, live management reads, bridge traffic, and any raw
|
||||
DALI bus activity from another master until the bus has been idle. Group and
|
||||
broadcast targets are never queried for refresh.
|
||||
|
||||
Gateway feature opcode `0x06` advertises cache support with bit `0x40`. Gateway
|
||||
opcode `0x39` returns cache summary and target snapshots so frontend clients can
|
||||
read cached state without issuing live DALI queries on supported gateways.
|
||||
Gateway feature opcode `0x06` keeps the Lua-compatible low-byte feature bits,
|
||||
advertises cache support with bit `0x40`, advertises gateway/channel name
|
||||
support with bit `0x80`, and advertises the native C++ gateway type with bit
|
||||
`0x0100`. Bit `0x0200` advertises the generic gateway operation protocol, and
|
||||
bit `0x0400` advertises explicit raw-report leases. Gateway opcode `0x05` reads
|
||||
and writes user-facing gateway identity: operation `0x00` reads the channel
|
||||
name, `0x01` writes the channel name, `0x02` reads the physical gateway device
|
||||
name, and `0x03` writes the physical gateway device name. Empty writes reset to
|
||||
the default name. Gateway opcode `0x39` returns cache summary and target
|
||||
snapshots so frontend clients can read cached state without issuing live DALI
|
||||
queries on supported gateways.
|
||||
|
||||
Gateway opcode `0x09` with address/data `0x00/0x00` is a chip-level channel-id
|
||||
report. It returns the enabled DALI channel ids so clients do not need to probe
|
||||
every possible gateway id one by one. For native C++ gateways, channel number is
|
||||
the fixed 1-based Kconfig slot (`1` to `16`) and channel id is the persisted,
|
||||
Lua-compatible gateway id used by normal `0x28 0x01 ...` command frames.
|
||||
Physical gateway ids are reserved to `0..127`. Raw control transports reserve
|
||||
`128..254` for gateway group targets and `255` for all-gateway fan-out.
|
||||
|
||||
Gateway opcode `0x0B` is the serial-scoped channel command. The serial is the
|
||||
last three bytes of the ESP base MAC. Operation `0x00` reports serial plus
|
||||
`(channel number, channel id)` pairs. Operations `0x01` and `0x02` get and set
|
||||
the channel id for the fixed channel number in the command frame gateway byte.
|
||||
Operation `0x03` wraps an existing gateway command and dispatches it by fixed
|
||||
channel number after the serial matches, so BLE/Wi-Fi configuration and DALI
|
||||
send/query commands can target a channel even when its variable channel id is
|
||||
unknown.
|
||||
Operations `0x04` and `0x05` get and set a channel gateway group id. Group `0`
|
||||
means no group membership; group ids `128..254` can be used as raw-control
|
||||
targets. Gateway opcode `0x6A` exposes the same group setting by physical
|
||||
gateway id: operation `0x00` reads the group and operation `0x01` writes group
|
||||
`0` or `128..254`.
|
||||
|
||||
## Ethernet control
|
||||
|
||||
The network service accepts Lua-style raw gateway frames on UDP port `2020` and,
|
||||
when enabled, TCP port `2020`. It also accepts JSON control frames on the same
|
||||
ports:
|
||||
|
||||
- `{"type":"discover"}` returns `discoverResp` with device identity, UDP/TCP
|
||||
control ports, Ethernet status/config, and channel gateway/group data.
|
||||
- `{"type":"dali","data":"<hex gateway frame>"}` enqueues a raw gateway command.
|
||||
- `{"type":"macDali","mac":"<ethernet MAC or 3-byte serial>","data":"<hex gateway frame>"}`
|
||||
enqueues a raw command only on the matching gateway. The gateway broadcasts
|
||||
command responses back to the sender's UDP port, allowing local-link control
|
||||
when its IPv4 address is unknown or configured for the wrong subnet.
|
||||
- `{"type":"setconfig","data":{"ip":"...","mask":"...","gw":"...","dns":"..."}}`
|
||||
stores and applies static Ethernet IPv4 settings. `ip` empty or `0.0.0.0`
|
||||
clears static config and returns Ethernet to DHCP.
|
||||
|
||||
Raw UDP, raw TCP, and UART0 session control all expand gateway target `255` to
|
||||
all physical gateway ids and targets `128..254` to channels whose configured
|
||||
gateway group matches the target.
|
||||
|
||||
## Gateway operation protocol
|
||||
|
||||
Opcode `0x67` starts, aborts, and polls gateway-executed high-level DALI
|
||||
operations. Clients send one compact gateway command and the controller runs the
|
||||
required DTR setup, device-type select, query chain, retry, or address iteration
|
||||
on the gateway. The command frame is:
|
||||
|
||||
- Start: `28 01 <gw> 67 01 <requestId> <operationIdLo> <operationIdHi> <payloadLenLo> <payloadLenHi> <TLV...> <checksum>`.
|
||||
- Abort: `28 01 <gw> 67 02 <requestId> 00 00 00 00 <checksum>`.
|
||||
- Status: `28 01 <gw> 67 03 <requestId> 00 00 00 00 <checksum>`.
|
||||
|
||||
Multi-byte fields are little-endian. TLV payload fields use
|
||||
`{fieldId:u8, type:u8, len:u8, value...}`. Types are `0x01` u8, `0x02` u16,
|
||||
`0x03` u32, `0x04` signed i32, `0x05` bool, and `0x06` byte list. Dynamic
|
||||
snapshot metadata is returned as repeated byte-list entries using compact
|
||||
`key=value` payloads.
|
||||
|
||||
Operation events are emitted as
|
||||
`22 67 <gw> <requestId> <operationIdLo> <operationIdHi> <event> <status> <progress> <target> <count> <checksum>`.
|
||||
Event values are accepted `0x00`, progress `0x01`, item result `0x02`,
|
||||
completed `0x03`, aborted `0x04`, and error `0x05`. Status values are ok
|
||||
`0x00`, busy `0x01`, invalid `0x02`, unsupported `0x03`, no response `0x04`,
|
||||
failed `0x05`, and aborted `0x06`.
|
||||
|
||||
Large result payloads are emitted as chunks:
|
||||
`22 68 <gw> <requestId> <operationIdLo> <operationIdHi> <total> <index> <chunkLen> <TLV...> <checksum>`.
|
||||
Clients assemble chunks by gateway, request id, and operation id before
|
||||
processing result TLVs.
|
||||
|
||||
The initial C++ executor accepts the shared `BridgeOperation` numeric ids for
|
||||
raw send/query, brightness/on/off/recall, color-temperature and DT8 RGB/XY/RGBW
|
||||
setters, DT1/DT4/DT5/DT6/DT8 snapshots, group masks, scene levels/maps, address
|
||||
settings, short-address range search, and short-address allocation/reset/stop.
|
||||
Unsupported ids return an `unsupported` operation error so old clients can fall
|
||||
back to their app-side workflow. Legacy opcodes such as `0x12`, `0x13`, `0x14`,
|
||||
`0x30`, `0x32`, `0x60`-`0x65`, and `0x39` remain available for compatibility.
|
||||
|
||||
Operation id `84` performs a direct-short-address DALI identity memory batch
|
||||
read. The start TLV must include target field `0x01` as a u8 short address
|
||||
`0..63`; group and broadcast reads are rejected. Kind field `0x20` as u8
|
||||
selects compact (`0`) or extended (`nonzero`) identity coverage. Results are
|
||||
repeated field `0x30` byte-list entries, each with payload
|
||||
`[bank, location, value]`. Missing byte replies are omitted; a completed result
|
||||
with no entries reports `no response` so clients can fall back to single-byte
|
||||
`READ MEMORY LOCATION` reads.
|
||||
|
||||
Opcode `0x66` controls passive raw-report leases. Command
|
||||
`28 01 <gw> 66 01 <enabled> <ttlLo> <ttlHi> <checksum>` enables or disables a
|
||||
volatile per-gateway lease; `ttl=0` disables. The response is
|
||||
`22 66 <gw> <status> <enabled> <ttlLo> <ttlHi> <checksum>`. Passive raw DALI
|
||||
notifications over UDP, BLE raw characteristics, ESP-NOW setup UART mirroring,
|
||||
and gateway notification opcodes `0x01` and `0x65` are suppressed unless the
|
||||
lease is active. Direct command/query responses still work without a raw-report
|
||||
lease.
|
||||
|
||||
## Gateway runtime settings
|
||||
|
||||
`GET /bridge?action=gateway_settings` returns the versioned runtime-settings
|
||||
document. `POST /bridge?action=gateway_settings` accepts a partial patch inside
|
||||
`settings`. The mutable fields are the device name, BLE/cache switches, Wi-Fi
|
||||
credentials, Ethernet DHCP/static IPv4 configuration, the boot USB personality,
|
||||
and channel gateway ids/groups/names. Wi-Fi passwords are write-only: reads
|
||||
contain `wifi.passwordSet`, never the password itself. Device and channel names
|
||||
must contain at least one non-whitespace character and be at most 32 bytes.
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"settings": {
|
||||
"deviceName": "DALI Gateway",
|
||||
"bleEnabled": true,
|
||||
"cacheEnabled": true,
|
||||
"wifi": {"configured": true, "ssid": "example", "passwordSet": true},
|
||||
"ethernet": {"dhcp": true, "ip": "", "mask": "", "gateway": "", "dns": ""},
|
||||
"usb": {"mode": "debug", "channelIndex": 0, "restartRequired": false},
|
||||
"channels": [{"channelIndex": 0, "gatewayId": 3, "gatewayGroup": 0,
|
||||
"name": "Gateway_3", "phy": "native"}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
USB mode is one of `debug`, `setup`, or `tridonic`. New devices default to
|
||||
`debug`; Tridonic HID is never the runtime default. The desired USB mode and
|
||||
channel are stored in NVS, then applied only after restart because USB
|
||||
descriptors cannot be changed while attached. A response sets
|
||||
`settings.usb.restartRequired` and top-level `restartRequired` when the desired
|
||||
value differs from the personality running at boot. Channel indexes are
|
||||
zero-based. `capabilities.usb.channelIndices` is the authoritative list of
|
||||
configured DALI channel indexes, for example:
|
||||
|
||||
```json
|
||||
{
|
||||
"capabilities": {
|
||||
"usb": {
|
||||
"supported": true,
|
||||
"modes": ["debug", "setup", "tridonic"],
|
||||
"channelIndices": [0, 2]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For `setup` and `tridonic`, `settings.usb.channelIndex` must be one of those
|
||||
values. An unavailable channel is rejected before it is persisted with an
|
||||
error that points clients to `capabilities.usb.channelIndices`; it is not a
|
||||
generic `0` through `15` selector. At boot, legacy invalid persisted USB
|
||||
channels are normalized to the first configured channel when NVS is writable;
|
||||
the current boot runs in safe `debug` mode rather than starting a USB
|
||||
personality on an unintended bus. Hardware PHY/pin mappings, task sizing, and
|
||||
other Kconfig build settings are exposed only as build capabilities/defaults;
|
||||
they are not mutable runtime settings.
|
||||
|
||||
When the MQTT cloud bridge is provisioned, the same document is available on
|
||||
the existing `<topicPrefix>/<deviceID>/down` topic. Send
|
||||
`{"type":"gatewaySettingsGet","requestId":"..."}` or
|
||||
`{"type":"gatewaySettingsSet","requestId":"...","settings":{...}}`.
|
||||
The gateway publishes `type:"gatewaySettings"`, the request id, a status of
|
||||
`ok` on success or `error`, and the sanitized current
|
||||
settings/capabilities/build document on the normal `up` topic.
|
||||
|
||||
## C++ 485 session frames
|
||||
|
||||
The optional raw 485 control bridge requires a one-byte random session hash for
|
||||
C++ gateway commands so multiple unmanaged clients can distinguish responses
|
||||
and safely retry after collisions. The 485 command envelope is:
|
||||
|
||||
`28 01 <gw> 69 <hash> <innerOpcode> <innerPayload...> <checksum>`.
|
||||
|
||||
The bridge strips `69 <hash>` and dispatches the inner command through the
|
||||
normal controller. A repeated frame with the same hash and identical inner
|
||||
command is treated as the same session and is not executed again. Session
|
||||
status replies are:
|
||||
|
||||
`22 69 <gw> <hash> <status> <resultLenLo> <resultLenHi> <result...> <checksum>`.
|
||||
|
||||
Status values are failed `0x00`, queued `0x01`, in process `0x02`, and finished
|
||||
`0x03`. `result` contains the normal `0x22 ... checksum` response frame bytes
|
||||
when the command produces a response, and is empty for write-only commands.
|
||||
The session cache is volatile and keeps recent sessions long enough for clients
|
||||
to retry the same request after a bus collision. BLE, UDP/IP, USB setup, and
|
||||
LAMMIN legacy/new 485 paths continue to use their existing framing.
|
||||
|
||||
## Current status
|
||||
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, W5500 SPI Ethernet with DHCP, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, setup AP GPIO entry, and optional Wi-Fi credential reset GPIO handling, and an optional `gateway_485_control` bridge that claims UART0 for Lua-compatible framed command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE and wired Ethernet are enabled by default, W5500 initialization and startup probe failures are ignored by default for boards without populated Ethernet hardware by fully disabling Ethernet for that boot, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, W5500 SPI Ethernet with DHCP, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, setup AP GPIO entry, and optional Wi-Fi credential reset GPIO handling, and an optional `gateway_485_control` bridge that claims UART0 for C++ session-scoped command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE and wired Ethernet are enabled by default, W5500 initialization and startup probe failures are ignored by default for boards without populated Ethernet hardware by fully disabling Ethernet for that boot, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the persisted USB personality starts in debug mode unless it was explicitly changed to setup or Tridonic before reboot, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
|
||||
|
||||
## KNX Security
|
||||
|
||||
@@ -113,4 +315,4 @@ BACnet/IP is owned by `gateway/components/gateway_bacnet` and is started through
|
||||
|
||||
Provisioned BACnet models still use generic `BridgeModel` fields such as object type, object instance, property, and optional `bitIndex`. Query-style models refresh BACnet `Present_Value` from live DALI reads, and binary models with `bitIndex` expose a single packed status bit.
|
||||
|
||||
For discovered DALI short addresses, the gateway also mirrors the generated Modbus discrete diagnostics as BACnet binary-input objects. Object instances are allocated in a gateway-owned generated range using the channel index plus the generated Modbus discrete-input offset, so generated objects stay deterministic while avoiding the provisioned-object address space in normal deployments.
|
||||
For discovered DALI short addresses, the gateway also mirrors the generated Modbus discrete diagnostics as BACnet binary-input objects. Object instances are allocated in a gateway-owned generated range using the channel index plus the generated Modbus discrete-input offset, so generated objects stay deterministic while avoiding the provisioned-object address space in normal deployments.
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Mac",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"defines": [],
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++14",
|
||||
"intelliSenseMode": "${default}"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
"OPENOCD_SCRIPTS": "/Users/tonylu/.espressif/tools/openocd-esp32/v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts",
|
||||
"ESP_ROM_ELF_DIR": "/Users/tonylu/.espressif/tools/esp-rom-elfs/20240305/"
|
||||
},
|
||||
"clangd.path": "/Users/tonylu/.espressif/tools/esp-clang/esp-20.1.1_20250829/esp-clang/bin/clangd",
|
||||
"clangd.path": "/Users/tonylu/.espressif/tools/esp-clang/esp-19.1.2_20250312/esp-clang/bin/clangd",
|
||||
"clangd.arguments": [
|
||||
"--background-index",
|
||||
"--query-driver=**",
|
||||
|
||||
@@ -10,6 +10,27 @@ dependencies:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.7.19~2
|
||||
espressif/esp_tinyusb:
|
||||
component_hash:
|
||||
9a73a76a6bc17907f6e523342ff6b1e440023e8aac2c11f4691212106ec24e66
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
- name: espressif/tinyusb
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: '>=0.17.0~2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
targets:
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
- esp32p4
|
||||
- esp32h4
|
||||
- esp32s31
|
||||
version: 2.2.1
|
||||
espressif/mqtt:
|
||||
component_hash:
|
||||
ffdad5659706b4dc14bc63f8eb73ef765efa015bf7e9adf71c813d52a2dc9342
|
||||
@@ -21,14 +42,32 @@ dependencies:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.0
|
||||
espressif/tinyusb:
|
||||
component_hash:
|
||||
a72b7d67472914ab76309340fd50d578b31e310963d45ad0f81144bde3314752
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
targets:
|
||||
- esp32s2
|
||||
- esp32s3
|
||||
- esp32p4
|
||||
- esp32h4
|
||||
- esp32s31
|
||||
version: 0.21.0~1
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.5.4
|
||||
direct_dependencies:
|
||||
- espressif/cjson
|
||||
- espressif/esp_tinyusb
|
||||
- espressif/mqtt
|
||||
- idf
|
||||
manifest_hash: 9cb68c165ea4256d19cf45872db2feae2aad30a985b77b20da75d7dfa6eeab36
|
||||
manifest_hash: f0d438b2b6a63a6d50fc9bb059765c7ee2a95c05ac2541c179e9064331c07f0e
|
||||
target: esp32s3
|
||||
version: 2.0.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "app_main.cpp"
|
||||
REQUIRES gateway_core gateway_controller gateway_network gateway_bridge gateway_cache dali_domain gateway_runtime gateway_ble gateway_usb_setup gateway_485_control gateway_knx log
|
||||
REQUIRES gateway_core gateway_controller gateway_network gateway_bridge gateway_cache dali_domain gateway_runtime gateway_ble gateway_usb_setup gateway_tridonic_hid gateway_485_control gateway_knx log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
|
||||
@@ -13,7 +13,7 @@ menu "Gateway Channel 1"
|
||||
|
||||
config GATEWAY_CHANNEL1_GW_ID
|
||||
int "Gateway id"
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 3
|
||||
help
|
||||
Lua-compatible gateway id for the first DALI channel.
|
||||
@@ -121,7 +121,7 @@ menu "Gateway Channel 2"
|
||||
config GATEWAY_CHANNEL2_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 2
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 4
|
||||
help
|
||||
Lua-compatible gateway id for the second DALI channel.
|
||||
@@ -230,7 +230,7 @@ menu "Gateway Channel 3"
|
||||
config GATEWAY_CHANNEL3_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 3
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 5
|
||||
|
||||
choice GATEWAY_CHANNEL3_PHY
|
||||
@@ -319,7 +319,7 @@ menu "Gateway Channel 4"
|
||||
config GATEWAY_CHANNEL4_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 4
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 6
|
||||
|
||||
choice GATEWAY_CHANNEL4_PHY
|
||||
@@ -408,7 +408,7 @@ menu "Gateway Channel 5"
|
||||
config GATEWAY_CHANNEL5_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 5
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 7
|
||||
|
||||
choice GATEWAY_CHANNEL5_PHY
|
||||
@@ -497,7 +497,7 @@ menu "Gateway Channel 6"
|
||||
config GATEWAY_CHANNEL6_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 6
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 8
|
||||
|
||||
choice GATEWAY_CHANNEL6_PHY
|
||||
@@ -586,7 +586,7 @@ menu "Gateway Channel 7"
|
||||
config GATEWAY_CHANNEL7_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 7
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 9
|
||||
|
||||
choice GATEWAY_CHANNEL7_PHY
|
||||
@@ -675,7 +675,7 @@ menu "Gateway Channel 8"
|
||||
config GATEWAY_CHANNEL8_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 8
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 10
|
||||
|
||||
choice GATEWAY_CHANNEL8_PHY
|
||||
@@ -764,7 +764,7 @@ menu "Gateway Channel 9"
|
||||
config GATEWAY_CHANNEL9_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 9
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 11
|
||||
|
||||
choice GATEWAY_CHANNEL9_PHY
|
||||
@@ -853,7 +853,7 @@ menu "Gateway Channel 10"
|
||||
config GATEWAY_CHANNEL10_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 10
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 12
|
||||
|
||||
choice GATEWAY_CHANNEL10_PHY
|
||||
@@ -942,7 +942,7 @@ menu "Gateway Channel 11"
|
||||
config GATEWAY_CHANNEL11_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 11
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 13
|
||||
|
||||
choice GATEWAY_CHANNEL11_PHY
|
||||
@@ -1031,7 +1031,7 @@ menu "Gateway Channel 12"
|
||||
config GATEWAY_CHANNEL12_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 12
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 14
|
||||
|
||||
choice GATEWAY_CHANNEL12_PHY
|
||||
@@ -1120,7 +1120,7 @@ menu "Gateway Channel 13"
|
||||
config GATEWAY_CHANNEL13_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 13
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 15
|
||||
|
||||
choice GATEWAY_CHANNEL13_PHY
|
||||
@@ -1209,7 +1209,7 @@ menu "Gateway Channel 14"
|
||||
config GATEWAY_CHANNEL14_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 14
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 16
|
||||
|
||||
choice GATEWAY_CHANNEL14_PHY
|
||||
@@ -1298,7 +1298,7 @@ menu "Gateway Channel 15"
|
||||
config GATEWAY_CHANNEL15_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 15
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 17
|
||||
|
||||
choice GATEWAY_CHANNEL15_PHY
|
||||
@@ -1387,7 +1387,7 @@ menu "Gateway Channel 16"
|
||||
config GATEWAY_CHANNEL16_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 16
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 18
|
||||
|
||||
choice GATEWAY_CHANNEL16_PHY
|
||||
@@ -1471,6 +1471,20 @@ config GATEWAY_CHANNEL16_SERIAL_QUERY_TIMEOUT_MS
|
||||
|
||||
endmenu
|
||||
|
||||
menu "DALI Part 103 Application Controller"
|
||||
|
||||
config GATEWAY_DALI_103_APPLICATION_CONTROLLER_ENABLED
|
||||
bool "Enable the gateway as a DALI Part 103 application controller"
|
||||
default n
|
||||
help
|
||||
Makes each configured DALI channel expose the gateway as a logical
|
||||
Part 103 control device. The dali_cpp application layer answers the
|
||||
standard device queries, supports short-address commissioning, and
|
||||
emits an identity action for the gateway adapter. The ESP-IDF domain
|
||||
owns repeat-command timing and backward-frame transmission.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Gateway Cache"
|
||||
|
||||
config GATEWAY_CACHE_SUPPORTED
|
||||
@@ -1991,7 +2005,7 @@ config GATEWAY_KNX_OEM_APPLICATION_VERSION
|
||||
hex "KNX OEM application version"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 0x00 0xff
|
||||
default 0x08
|
||||
default 0x09
|
||||
help
|
||||
Application version advertised by the ETS-programmable KNX-DALI gateway
|
||||
application. Keep this in sync with MAIN_ApplicationVersion from the
|
||||
@@ -2365,10 +2379,13 @@ endmenu
|
||||
menu "USB Setup"
|
||||
|
||||
choice GATEWAY_USB_STARTUP_MODE
|
||||
prompt "USB Serial/JTAG startup mode"
|
||||
prompt "USB startup mode"
|
||||
default GATEWAY_USB_STARTUP_DEBUG_JTAG
|
||||
help
|
||||
Select whether the built-in USB Serial/JTAG interface remains available for debug or is claimed by the setup bridge.
|
||||
Select the first-boot fallback USB personality. Runtime gateway settings
|
||||
can persist debug, setup bridge, or Tridonic HID mode for the next boot.
|
||||
Debug is the default so a new gateway never emulates Tridonic until
|
||||
explicitly configured.
|
||||
|
||||
config GATEWAY_USB_STARTUP_DEBUG_JTAG
|
||||
bool "USB Serial/JTAG debug interface"
|
||||
@@ -2376,8 +2393,21 @@ config GATEWAY_USB_STARTUP_DEBUG_JTAG
|
||||
config GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
bool "USB Serial/JTAG setup bridge"
|
||||
|
||||
config GATEWAY_USB_STARTUP_TRIDONIC_HID
|
||||
bool "Tridonic DALI USB HID emulation"
|
||||
help
|
||||
Exposes the native USB OTG port as Tridonic DALI USB (17b5:0020), for
|
||||
use with Tridonic masterConfigurator. This mode cannot coexist with
|
||||
USB Serial/JTAG debug or the setup bridge.
|
||||
|
||||
endchoice
|
||||
|
||||
config GATEWAY_TRIDONIC_HID_CHANNEL_INDEX
|
||||
int "Tridonic HID DALI channel index"
|
||||
depends on GATEWAY_USB_STARTUP_TRIDONIC_HID
|
||||
range 0 15
|
||||
default 0
|
||||
|
||||
config GATEWAY_USB_SETUP_CHANNEL_INDEX
|
||||
int "USB setup DALI channel index"
|
||||
depends on GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
@@ -2502,6 +2532,32 @@ config GATEWAY_NETWORK_UDP_PORT
|
||||
range 1 65535
|
||||
default 2020
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
bool "Enable TCP raw gateway control"
|
||||
default y
|
||||
help
|
||||
Enables a TCP listener for Lua-compatible raw gateway frames and JSON
|
||||
Ethernet control frames. This mirrors the UDP control path and supports
|
||||
gateway group and all-gateway target expansion.
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_PORT
|
||||
int "TCP raw gateway control port"
|
||||
depends on GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
range 1 65535
|
||||
default 2020
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE
|
||||
int "TCP control task stack bytes"
|
||||
depends on GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
range 2048 16384
|
||||
default 4096
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY
|
||||
int "TCP control task priority"
|
||||
depends on GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
range 1 10
|
||||
default 4
|
||||
|
||||
config GATEWAY_STATUS_LED_GPIO
|
||||
int "Status LED GPIO"
|
||||
range -1 48
|
||||
@@ -2555,4 +2611,4 @@ config GATEWAY_BUTTON_TASK_STACK_SIZE
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
endmenu
|
||||
|
||||
+291
-30
@@ -8,14 +8,20 @@
|
||||
#include "gateway_runtime.hpp"
|
||||
#include "gateway_485_control.hpp"
|
||||
#include "gateway_usb_setup.hpp"
|
||||
#include "gateway_tridonic_hid.hpp"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#ifndef CONFIG_GATEWAY_CHANNEL_COUNT
|
||||
#define CONFIG_GATEWAY_CHANNEL_COUNT 2
|
||||
@@ -29,6 +35,18 @@
|
||||
#define CONFIG_GATEWAY_NETWORK_UDP_PORT 2020
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT
|
||||
#define CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT 2020
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE 4096
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY
|
||||
#define CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY 4
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_STATUS_LED_GPIO
|
||||
#define CONFIG_GATEWAY_STATUS_LED_GPIO -1
|
||||
#endif
|
||||
@@ -72,6 +90,14 @@
|
||||
#define CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS 20
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_TRIDONIC_HID_CHANNEL_INDEX
|
||||
#define CONFIG_GATEWAY_TRIDONIC_HID_CHANNEL_INDEX 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_BUS_RESET_PULSE_MS
|
||||
#define CONFIG_DALI_BUS_RESET_PULSE_MS 10
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_BAUDRATE
|
||||
#define CONFIG_GATEWAY_485_CONTROL_BAUDRATE 9600
|
||||
#endif
|
||||
@@ -361,10 +387,25 @@ constexpr bool kSmartconfigStartupEnabled = true;
|
||||
constexpr bool kSmartconfigStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
constexpr bool kUsbSetupStartupEnabled = true;
|
||||
#if defined(CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED) || defined(CONFIG_SOC_USB_OTG_SUPPORTED)
|
||||
constexpr bool kUsbSupported = true;
|
||||
#else
|
||||
constexpr bool kUsbSetupStartupEnabled = false;
|
||||
constexpr bool kUsbSupported = false;
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_GATEWAY_USB_STARTUP_TRIDONIC_HID)
|
||||
constexpr gateway::GatewayUsbMode kUsbStartupDefaultMode = gateway::GatewayUsbMode::kTridonic;
|
||||
constexpr uint8_t kUsbStartupDefaultChannelIndex =
|
||||
static_cast<uint8_t>(CONFIG_GATEWAY_TRIDONIC_HID_CHANNEL_INDEX);
|
||||
#elif defined(CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL)
|
||||
constexpr gateway::GatewayUsbMode kUsbStartupDefaultMode = gateway::GatewayUsbMode::kSetup;
|
||||
constexpr uint8_t kUsbStartupDefaultChannelIndex =
|
||||
static_cast<uint8_t>(CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX);
|
||||
#else
|
||||
// Debug is deliberately the default: a gateway only exposes the Tridonic HID
|
||||
// identity after an explicit local or remote settings change and reboot.
|
||||
constexpr gateway::GatewayUsbMode kUsbStartupDefaultMode = gateway::GatewayUsbMode::kDebug;
|
||||
constexpr uint8_t kUsbStartupDefaultChannelIndex = 0;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_BRIDGE_SUPPORTED
|
||||
@@ -470,11 +511,17 @@ constexpr bool kCacheFullStateMirrorEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST
|
||||
constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
|
||||
gateway::GatewayCachePriorityMode::kLocalGatewayFirst;
|
||||
constexpr DaliGatewayCachePriorityMode kCachePriorityMode =
|
||||
DaliGatewayCachePriorityMode::localGatewayFirst;
|
||||
#else
|
||||
constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
|
||||
gateway::GatewayCachePriorityMode::kOutsideBusFirst;
|
||||
constexpr DaliGatewayCachePriorityMode kCachePriorityMode =
|
||||
DaliGatewayCachePriorityMode::outsideBusFirst;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_DALI_103_APPLICATION_CONTROLLER_ENABLED
|
||||
constexpr bool kDali103ApplicationControllerEnabled = true;
|
||||
#else
|
||||
constexpr bool kDali103ApplicationControllerEnabled = false;
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU) || \
|
||||
@@ -519,6 +566,7 @@ std::unique_ptr<gateway::GatewayNetworkService> s_network;
|
||||
std::unique_ptr<gateway::GatewayBleBridge> s_ble_bridge;
|
||||
std::unique_ptr<gateway::Gateway485ControlBridge> s_uart0_control_bridge;
|
||||
std::unique_ptr<gateway::GatewayUsbSetupBridge> s_usb_setup_bridge;
|
||||
std::unique_ptr<gateway::GatewayTridonicHid> s_tridonic_hid;
|
||||
|
||||
[[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) {
|
||||
if (err != ESP_OK) {
|
||||
@@ -893,14 +941,27 @@ bool ValidateChannelBindings() {
|
||||
|
||||
esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||
const gateway::GatewayRuntime& runtime) {
|
||||
std::array<bool, 256> used_gateway_ids{};
|
||||
for (const auto& channel : BuildChannelBindings()) {
|
||||
if (!channel.enabled) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t gateway_id =
|
||||
runtime.gatewayIdForChannel(channel.channel_index, channel.gateway_id);
|
||||
if (gateway_id >= 128) {
|
||||
ESP_LOGE(kTag, "gateway id %u for channel %u is reserved for gateway groups",
|
||||
gateway_id, channel.channel_index + 1);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (used_gateway_ids[gateway_id]) {
|
||||
ESP_LOGE(kTag, "duplicate runtime gateway id configured: %u", gateway_id);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
used_gateway_ids[gateway_id] = true;
|
||||
if (channel.native_phy) {
|
||||
gateway::DaliHardwareBusConfig config{};
|
||||
config.channel_index = channel.channel_index;
|
||||
config.gateway_id = channel.gateway_id;
|
||||
config.gateway_id = gateway_id;
|
||||
config.bus_id = channel.native_bus_id;
|
||||
config.tx_pin = static_cast<uint8_t>(channel.native_tx_pin);
|
||||
config.rx_pin = static_cast<uint8_t>(channel.native_rx_pin);
|
||||
@@ -914,7 +975,7 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||
} else if (channel.serial_phy) {
|
||||
gateway::DaliSerialBusConfig config{};
|
||||
config.channel_index = channel.channel_index;
|
||||
config.gateway_id = channel.gateway_id;
|
||||
config.gateway_id = gateway_id;
|
||||
config.uart_port = channel.uart_port;
|
||||
config.tx_pin = channel.serial_tx_pin;
|
||||
config.rx_pin = channel.serial_rx_pin;
|
||||
@@ -934,6 +995,13 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool HasConfiguredDaliChannel(const std::vector<gateway::DaliChannelInfo>& channels,
|
||||
uint8_t channel_index) {
|
||||
return std::any_of(channels.begin(), channels.end(), [channel_index](const auto& channel) {
|
||||
return channel.channel_index == channel_index;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" void app_main(void) {
|
||||
@@ -946,7 +1014,7 @@ extern "C" void app_main(void) {
|
||||
kBleSupported,
|
||||
kEthernetSupported,
|
||||
kEspnowSetupSupported,
|
||||
kUsbSetupStartupEnabled,
|
||||
kUsbSupported,
|
||||
};
|
||||
|
||||
gateway::GatewayCore core(profile);
|
||||
@@ -955,6 +1023,17 @@ extern "C" void app_main(void) {
|
||||
ESP_ERROR_CHECK(ValidateChannelBindings() ? ESP_OK : ESP_ERR_INVALID_STATE);
|
||||
|
||||
s_dali_domain = std::make_unique<gateway::DaliDomainService>();
|
||||
s_dali_domain->setApplicationControllerEnabled(kDali103ApplicationControllerEnabled);
|
||||
s_dali_domain->setApplicationControllerIdentitySink([](uint8_t gateway_id) {
|
||||
// dali_cpp only emits a semantic identity action. This gateway adapter
|
||||
// turns it into a physical status-LED indication when the network service
|
||||
// is running; another host can bind the same action to its own hardware.
|
||||
if (s_network != nullptr) {
|
||||
s_network->identify();
|
||||
} else {
|
||||
ESP_LOGI(kTag, "Part 103 identity command executed for gateway channel=%u", gateway_id);
|
||||
}
|
||||
});
|
||||
s_runtime = std::make_unique<gateway::GatewayRuntime>(
|
||||
profile,
|
||||
gateway::GatewayRuntimeConfig{
|
||||
@@ -963,22 +1042,70 @@ extern "C" void app_main(void) {
|
||||
gateway::ReadRuntimeSerialId(),
|
||||
kBleStartupEnabled,
|
||||
kCacheSupported && kCacheStartupEnabled,
|
||||
},
|
||||
kUsbStartupDefaultMode,
|
||||
kUsbStartupDefaultChannelIndex,
|
||||
},
|
||||
s_dali_domain.get());
|
||||
ESP_ERROR_CHECK(s_runtime->start());
|
||||
s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT);
|
||||
s_dali_domain->setApplicationControllerShortAddressProvider(
|
||||
[](uint8_t channel_index, uint8_t gateway_id) {
|
||||
return s_runtime == nullptr
|
||||
? std::optional<uint8_t>(static_cast<uint8_t>(gateway_id & 0x3fU))
|
||||
: s_runtime->applicationControllerShortAddressForChannel(channel_index,
|
||||
gateway_id & 0x3fU);
|
||||
});
|
||||
s_dali_domain->setApplicationControllerShortAddressSink(
|
||||
[](uint8_t channel_index, std::optional<uint8_t> short_address) {
|
||||
if (s_runtime == nullptr ||
|
||||
!s_runtime->setApplicationControllerShortAddressForChannel(channel_index,
|
||||
short_address)) {
|
||||
ESP_LOGE(kTag, "failed to persist Part 103 short address for channel=%u",
|
||||
channel_index);
|
||||
}
|
||||
});
|
||||
ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime));
|
||||
s_runtime->setGatewayCount(s_dali_domain->channelCount());
|
||||
|
||||
// USB setup and Tridonic personalities route commands to one concrete DALI
|
||||
// channel. Repair legacy generic 0-15 NVS values before either personality
|
||||
// can start, and use debug mode as the safe fallback if persistence fails.
|
||||
const auto configured_dali_channels = s_dali_domain->channelInfo();
|
||||
const uint8_t effective_usb_default_channel_index =
|
||||
HasConfiguredDaliChannel(configured_dali_channels, kUsbStartupDefaultChannelIndex)
|
||||
? kUsbStartupDefaultChannelIndex
|
||||
: configured_dali_channels.front().channel_index;
|
||||
gateway::GatewayUsbMode active_usb_mode = s_runtime->usbMode();
|
||||
uint8_t active_usb_channel_index = s_runtime->usbChannelIndex();
|
||||
if (profile.enable_usb &&
|
||||
!HasConfiguredDaliChannel(configured_dali_channels, active_usb_channel_index)) {
|
||||
const uint8_t fallback_channel_index = configured_dali_channels.front().channel_index;
|
||||
ESP_LOGE(kTag,
|
||||
"USB %s channel %u is not configured; falling back to debug mode and channel %u",
|
||||
gateway::GatewayUsbModeToString(active_usb_mode), active_usb_channel_index,
|
||||
fallback_channel_index);
|
||||
const bool debug_persisted = active_usb_mode == gateway::GatewayUsbMode::kDebug ||
|
||||
s_runtime->setUsbMode(gateway::GatewayUsbMode::kDebug);
|
||||
if (debug_persisted && !s_runtime->setUsbChannelIndex(fallback_channel_index)) {
|
||||
ESP_LOGE(kTag, "failed to persist fallback USB channel %u", fallback_channel_index);
|
||||
}
|
||||
if (!debug_persisted) {
|
||||
ESP_LOGE(kTag, "failed to persist USB debug fallback; it remains active for this boot");
|
||||
}
|
||||
active_usb_mode = gateway::GatewayUsbMode::kDebug;
|
||||
active_usb_channel_index = fallback_channel_index;
|
||||
}
|
||||
|
||||
gateway::GatewayCacheConfig cache_config;
|
||||
cache_config.cache_enabled = kCacheSupported && kCacheStartupEnabled && s_runtime->cacheEnabled();
|
||||
cache_config.reconciliation_enabled = cache_config.cache_enabled && kCacheReconciliationEnabled;
|
||||
cache_config.full_state_mirror_enabled = cache_config.reconciliation_enabled &&
|
||||
kCacheFullStateMirrorEnabled;
|
||||
cache_config.flush_interval_ms = static_cast<uint32_t>(CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS);
|
||||
cache_config.refresh_interval_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_CACHE_REFRESH_INTERVAL_MS);
|
||||
cache_config.default_priority_mode = kCachePriorityMode;
|
||||
s_cache = std::make_unique<gateway::GatewayCache>(cache_config);
|
||||
const bool reconciliation_enabled =
|
||||
cache_config.cache_enabled && kCacheReconciliationEnabled;
|
||||
s_dali_domain->gatewayCache().configure(
|
||||
DaliGatewayCacheConfig{cache_config.cache_enabled, reconciliation_enabled,
|
||||
reconciliation_enabled && kCacheFullStateMirrorEnabled,
|
||||
kCachePriorityMode});
|
||||
s_cache = std::make_unique<gateway::GatewayCache>(s_dali_domain->gatewayCache(),
|
||||
cache_config);
|
||||
ESP_ERROR_CHECK(s_cache->start());
|
||||
|
||||
gateway::GatewayControllerConfig controller_config;
|
||||
@@ -1136,13 +1263,122 @@ extern "C" void app_main(void) {
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE);
|
||||
bridge_config.knx_task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY);
|
||||
bridge_config.gateway_device_name_provider = []() {
|
||||
return s_runtime == nullptr ? std::string() : s_runtime->deviceName();
|
||||
};
|
||||
bridge_config.gateway_settings_getter = []() {
|
||||
if (s_network == nullptr) {
|
||||
return gateway::GatewayBridgeHttpResponse{
|
||||
ESP_ERR_INVALID_STATE,
|
||||
"{\"status\":\"error\",\"error\":\"network settings service is unavailable\"}"};
|
||||
}
|
||||
return s_network->gatewaySettingsGet();
|
||||
};
|
||||
bridge_config.gateway_settings_setter = [](std::string_view body) {
|
||||
if (s_network == nullptr) {
|
||||
return gateway::GatewayBridgeHttpResponse{
|
||||
ESP_ERR_INVALID_STATE,
|
||||
"{\"status\":\"error\",\"error\":\"network settings service is unavailable\"}"};
|
||||
}
|
||||
return s_network->gatewaySettingsSet(body);
|
||||
};
|
||||
bridge_config.gateway_settings_cloud_handler = [](std::string_view request) {
|
||||
if (s_network == nullptr) {
|
||||
return std::string(
|
||||
"{\"type\":\"gatewaySettings\",\"status\":\"error\","
|
||||
"\"error\":\"network settings service is unavailable\"}");
|
||||
}
|
||||
return s_network->gatewaySettingsCloudResponse(request);
|
||||
};
|
||||
bridge_config.knx_gateway_snapshot_provider = []() {
|
||||
gateway::GatewayKnxGatewaySnapshot out;
|
||||
if (s_controller == nullptr) {
|
||||
return out;
|
||||
}
|
||||
const auto snapshot = s_controller->snapshot();
|
||||
out.channels.reserve(snapshot.channels.size());
|
||||
for (const auto& channel : snapshot.channels) {
|
||||
out.channels.push_back(
|
||||
gateway::GatewayKnxGatewayChannelSnapshot{channel.gateway_id});
|
||||
}
|
||||
return out;
|
||||
};
|
||||
bridge_config.knx_gateway_command_transactor =
|
||||
[](const std::vector<uint8_t>& frame, uint32_t timeout_ms) {
|
||||
gateway::GatewayKnxGatewayTransactionResult out;
|
||||
if (s_controller == nullptr) {
|
||||
return out;
|
||||
}
|
||||
if (!gateway::GatewayRuntime::isGatewayCommandFrame(frame) ||
|
||||
!gateway::GatewayRuntime::hasValidChecksum(frame) ||
|
||||
frame.size() < 5) {
|
||||
out.status = gateway::GatewayKnxGatewayTransactionStatus::kInvalidFrame;
|
||||
return out;
|
||||
}
|
||||
const auto is_write_only_dali_frame =
|
||||
[](const std::vector<uint8_t>& command) {
|
||||
if (command.size() < 5) {
|
||||
return false;
|
||||
}
|
||||
switch (command[3]) {
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
return command.size() >= 7;
|
||||
case 0x60:
|
||||
case 0x61:
|
||||
return command.size() >= 8;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (is_write_only_dali_frame(frame)) {
|
||||
out.status = s_controller->enqueueCommandFrame(frame)
|
||||
? gateway::GatewayKnxGatewayTransactionStatus::kOk
|
||||
: gateway::GatewayKnxGatewayTransactionStatus::kQueueRejected;
|
||||
return out;
|
||||
}
|
||||
const auto result = s_controller->transactCommandFrame(frame, timeout_ms);
|
||||
switch (result.status) {
|
||||
case gateway::GatewayCommandTransactionStatus::kOk:
|
||||
out.status = gateway::GatewayKnxGatewayTransactionStatus::kOk;
|
||||
break;
|
||||
case gateway::GatewayCommandTransactionStatus::kInvalidFrame:
|
||||
out.status = gateway::GatewayKnxGatewayTransactionStatus::kInvalidFrame;
|
||||
break;
|
||||
case gateway::GatewayCommandTransactionStatus::kQueueRejected:
|
||||
out.status = gateway::GatewayKnxGatewayTransactionStatus::kQueueRejected;
|
||||
break;
|
||||
case gateway::GatewayCommandTransactionStatus::kTimeout:
|
||||
out.status = gateway::GatewayKnxGatewayTransactionStatus::kTimeout;
|
||||
break;
|
||||
case gateway::GatewayCommandTransactionStatus::kNoResponse:
|
||||
out.status = gateway::GatewayKnxGatewayTransactionStatus::kNoResponse;
|
||||
break;
|
||||
}
|
||||
out.frames = result.frames;
|
||||
return out;
|
||||
};
|
||||
s_bridge = std::make_unique<gateway::GatewayBridgeService>(*s_dali_domain, *s_cache,
|
||||
bridge_config);
|
||||
s_controller->setBridgeService(s_bridge.get());
|
||||
s_controller->addGatewayNameSink([](uint8_t gateway_id) {
|
||||
if (s_bridge != nullptr) {
|
||||
s_bridge->handleGatewayNameChanged(gateway_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (profile.enable_wifi || profile.enable_eth) {
|
||||
gateway::GatewayNetworkServiceConfig network_config;
|
||||
network_config.wifi_supported = profile.enable_wifi;
|
||||
network_config.ethernet_supported = profile.enable_eth;
|
||||
network_config.ble_supported = profile.enable_ble;
|
||||
network_config.cache_supported = kCacheSupported;
|
||||
network_config.usb_supported = profile.enable_usb;
|
||||
network_config.usb_default_mode = kUsbStartupDefaultMode;
|
||||
network_config.usb_default_channel_index = effective_usb_default_channel_index;
|
||||
network_config.usb_active_mode = active_usb_mode;
|
||||
network_config.usb_active_channel_index = active_usb_channel_index;
|
||||
network_config.wifi_enabled = profile.enable_wifi && kWifiStartupEnabled;
|
||||
network_config.ethernet_enabled = profile.enable_eth && kEthernetStartupEnabled;
|
||||
#if CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE
|
||||
@@ -1166,9 +1402,20 @@ extern "C" void app_main(void) {
|
||||
network_config.udp_enabled = true;
|
||||
#else
|
||||
network_config.udp_enabled = false;
|
||||
#endif
|
||||
#ifdef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
network_config.tcp_control_enabled = true;
|
||||
#else
|
||||
network_config.tcp_control_enabled = false;
|
||||
#endif
|
||||
network_config.http_port = static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_HTTP_PORT);
|
||||
network_config.udp_port = static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_UDP_PORT);
|
||||
network_config.tcp_control_port =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT);
|
||||
network_config.tcp_control_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE);
|
||||
network_config.tcp_control_task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY);
|
||||
network_config.ethernet_spi_host = CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST;
|
||||
network_config.ethernet_spi_sclk_gpio = CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO;
|
||||
network_config.ethernet_spi_mosi_gpio = CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO;
|
||||
@@ -1221,16 +1468,30 @@ extern "C" void app_main(void) {
|
||||
}
|
||||
|
||||
if (profile.enable_usb) {
|
||||
gateway::GatewayUsbSetupBridgeConfig usb_config;
|
||||
usb_config.enabled = true;
|
||||
usb_config.channel_index = static_cast<uint8_t>(CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX);
|
||||
usb_config.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_USB_SETUP_RX_BUFFER);
|
||||
usb_config.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_USB_SETUP_TX_BUFFER);
|
||||
usb_config.read_timeout_ms = static_cast<uint32_t>(CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS);
|
||||
s_usb_setup_bridge = std::make_unique<gateway::GatewayUsbSetupBridge>(*s_controller,
|
||||
*s_dali_domain,
|
||||
usb_config);
|
||||
ESP_ERROR_CHECK(s_usb_setup_bridge->start());
|
||||
const auto usb_mode = active_usb_mode;
|
||||
const uint8_t usb_channel_index = active_usb_channel_index;
|
||||
if (usb_mode == gateway::GatewayUsbMode::kSetup) {
|
||||
gateway::GatewayUsbSetupBridgeConfig usb_config;
|
||||
usb_config.enabled = true;
|
||||
usb_config.channel_index = usb_channel_index;
|
||||
usb_config.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_USB_SETUP_RX_BUFFER);
|
||||
usb_config.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_USB_SETUP_TX_BUFFER);
|
||||
usb_config.read_timeout_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS);
|
||||
usb_config.reset_pulse_ms = static_cast<uint32_t>(CONFIG_DALI_BUS_RESET_PULSE_MS);
|
||||
s_usb_setup_bridge = std::make_unique<gateway::GatewayUsbSetupBridge>(
|
||||
*s_controller, *s_dali_domain, usb_config);
|
||||
ESP_ERROR_CHECK(s_usb_setup_bridge->start());
|
||||
} else if (usb_mode == gateway::GatewayUsbMode::kTridonic) {
|
||||
gateway::GatewayTridonicHidConfig hid_config;
|
||||
hid_config.enabled = true;
|
||||
hid_config.channel_index = usb_channel_index;
|
||||
s_tridonic_hid = std::make_unique<gateway::GatewayTridonicHid>(*s_dali_domain,
|
||||
hid_config);
|
||||
ESP_ERROR_CHECK(s_tridonic_hid->start());
|
||||
} else {
|
||||
ESP_LOGI(kTag, "USB debug mode selected; no USB setup personality is started");
|
||||
}
|
||||
}
|
||||
|
||||
const auto device_info = s_runtime->deviceInfo();
|
||||
@@ -1244,4 +1505,4 @@ extern "C" void app_main(void) {
|
||||
std::printf("gateway_main: runtime device type=%s serial=%s project=%s version=%s\n",
|
||||
device_info.type.c_str(), device_info.serial_id.c_str(),
|
||||
device_info.project.c_str(), device_info.version.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
+108
-8
@@ -691,6 +691,12 @@ CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
|
||||
#
|
||||
# end of Gateway Channel 16
|
||||
|
||||
#
|
||||
# DALI Part 103 Application Controller
|
||||
#
|
||||
# CONFIG_GATEWAY_DALI_103_APPLICATION_CONTROLLER_ENABLED is not set
|
||||
# end of DALI Part 103 Application Controller
|
||||
|
||||
#
|
||||
# Gateway Cache
|
||||
#
|
||||
@@ -843,6 +849,7 @@ CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
|
||||
#
|
||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||
# CONFIG_GATEWAY_USB_STARTUP_TRIDONIC_HID is not set
|
||||
# end of USB Setup
|
||||
|
||||
#
|
||||
@@ -858,6 +865,10 @@ CONFIG_GATEWAY_NETWORK_HTTP_ENABLED=y
|
||||
CONFIG_GATEWAY_NETWORK_HTTP_PORT=80
|
||||
CONFIG_GATEWAY_NETWORK_UDP_ROUTER_ENABLED=y
|
||||
CONFIG_GATEWAY_NETWORK_UDP_PORT=2020
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_ENABLED=y
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT=2020
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE=4096
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY=4
|
||||
CONFIG_GATEWAY_STATUS_LED_GPIO=-1
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
|
||||
@@ -1213,12 +1224,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11
|
||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
|
||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
|
||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
|
||||
@@ -1861,7 +1872,7 @@ CONFIG_SPIRAM_USE_MALLOC=y
|
||||
CONFIG_SPIRAM_MEMTEST=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
|
||||
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
|
||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
|
||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65535
|
||||
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
|
||||
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
|
||||
# end of SPI RAM config
|
||||
@@ -2480,9 +2491,9 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
|
||||
#
|
||||
# mbedTLS
|
||||
#
|
||||
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
|
||||
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
|
||||
@@ -2646,7 +2657,7 @@ CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y
|
||||
# CONFIG_NVS_ENCRYPTION is not set
|
||||
# CONFIG_NVS_ASSERT_ERROR_CHECK is not set
|
||||
# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set
|
||||
# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set
|
||||
CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM=y
|
||||
# end of NVS
|
||||
|
||||
#
|
||||
@@ -2919,13 +2930,15 @@ CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0
|
||||
CONFIG_DALI_TX_STOP_CONDITION_US=0
|
||||
CONFIG_DALI_RX_STOP_CONDITION_US=0
|
||||
CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=30
|
||||
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=20
|
||||
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=30
|
||||
CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=20
|
||||
CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=10
|
||||
CONFIG_DALI_FORWARD_MAX_WAIT_MS=50
|
||||
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=10
|
||||
CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500
|
||||
CONFIG_DALI_BUS_FAIL_EVENT_DELAY_MS=45
|
||||
CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS=3000
|
||||
CONFIG_DALI_BUS_RESET_PULSE_MS=10
|
||||
# CONFIG_DALI_LOG_LEVEL_NONE is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_ERROR is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_WARN is not set
|
||||
@@ -2956,6 +2969,93 @@ CONFIG_CJSON_NESTING_LIMIT=1000
|
||||
CONFIG_CJSON_CIRCULAR_LIMIT=10000
|
||||
# end of cJSON
|
||||
|
||||
#
|
||||
# TinyUSB Stack
|
||||
#
|
||||
CONFIG_TINYUSB_DEBUG_LEVEL=1
|
||||
|
||||
#
|
||||
# TinyUSB DCD
|
||||
#
|
||||
# CONFIG_TINYUSB_MODE_SLAVE is not set
|
||||
CONFIG_TINYUSB_MODE_DMA=y
|
||||
# end of TinyUSB DCD
|
||||
|
||||
#
|
||||
# TinyUSB callbacks
|
||||
#
|
||||
# CONFIG_TINYUSB_SUSPEND_CALLBACK is not set
|
||||
# CONFIG_TINYUSB_RESUME_CALLBACK is not set
|
||||
# end of TinyUSB callbacks
|
||||
|
||||
#
|
||||
# Descriptor configuration
|
||||
#
|
||||
|
||||
#
|
||||
# You can provide your custom descriptors via tinyusb_driver_install()
|
||||
#
|
||||
CONFIG_TINYUSB_DESC_USE_ESPRESSIF_VID=y
|
||||
CONFIG_TINYUSB_DESC_USE_DEFAULT_PID=y
|
||||
CONFIG_TINYUSB_DESC_BCD_DEVICE=0x0100
|
||||
CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Espressif Systems"
|
||||
CONFIG_TINYUSB_DESC_PRODUCT_STRING="Espressif Device"
|
||||
CONFIG_TINYUSB_DESC_SERIAL_STRING="123456"
|
||||
# end of Descriptor configuration
|
||||
|
||||
#
|
||||
# Mass Storage Class (MSC)
|
||||
#
|
||||
# CONFIG_TINYUSB_MSC_ENABLED is not set
|
||||
# end of Mass Storage Class (MSC)
|
||||
|
||||
#
|
||||
# Communication Device Class (CDC)
|
||||
#
|
||||
# CONFIG_TINYUSB_CDC_ENABLED is not set
|
||||
# end of Communication Device Class (CDC)
|
||||
|
||||
#
|
||||
# Musical Instrument Digital Interface (MIDI)
|
||||
#
|
||||
CONFIG_TINYUSB_MIDI_COUNT=0
|
||||
# end of Musical Instrument Digital Interface (MIDI)
|
||||
|
||||
#
|
||||
# Human Interface Device Class (HID)
|
||||
#
|
||||
CONFIG_TINYUSB_HID_COUNT=1
|
||||
# end of Human Interface Device Class (HID)
|
||||
|
||||
#
|
||||
# Device Firmware Upgrade (DFU)
|
||||
#
|
||||
# CONFIG_TINYUSB_DFU_MODE_DFU is not set
|
||||
# CONFIG_TINYUSB_DFU_MODE_DFU_RUNTIME is not set
|
||||
CONFIG_TINYUSB_DFU_MODE_NONE=y
|
||||
# end of Device Firmware Upgrade (DFU)
|
||||
|
||||
#
|
||||
# Bluetooth Host Class (BTH)
|
||||
#
|
||||
# CONFIG_TINYUSB_BTH_ENABLED is not set
|
||||
# end of Bluetooth Host Class (BTH)
|
||||
|
||||
#
|
||||
# Network driver (ECM/NCM/RNDIS)
|
||||
#
|
||||
# CONFIG_TINYUSB_NET_MODE_ECM_RNDIS is not set
|
||||
# CONFIG_TINYUSB_NET_MODE_NCM is not set
|
||||
CONFIG_TINYUSB_NET_MODE_NONE=y
|
||||
# end of Network driver (ECM/NCM/RNDIS)
|
||||
|
||||
#
|
||||
# Vendor Specific Interface
|
||||
#
|
||||
CONFIG_TINYUSB_VENDOR_COUNT=0
|
||||
# end of Vendor Specific Interface
|
||||
# end of TinyUSB Stack
|
||||
|
||||
#
|
||||
# ESP-MQTT Configurations
|
||||
#
|
||||
|
||||
@@ -19,3 +19,4 @@ CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
||||
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||
CONFIG_TINYUSB_HID_COUNT=1
|
||||
|
||||
+115
-20
@@ -600,12 +600,12 @@ CONFIG_PARTITION_TABLE_MD5=y
|
||||
#
|
||||
# DALI Settings
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL_COUNT=2
|
||||
CONFIG_GATEWAY_CHANNEL_COUNT=1
|
||||
|
||||
#
|
||||
# Gateway Channel 1
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL1_GW_ID=3
|
||||
CONFIG_GATEWAY_CHANNEL1_GW_ID=0
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set
|
||||
CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set
|
||||
@@ -619,17 +619,78 @@ CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
|
||||
#
|
||||
# Gateway Channel 2
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL2_GW_ID=4
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set
|
||||
CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE=y
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set
|
||||
CONFIG_GATEWAY_CHANNEL2_NATIVE_BUS_ID=1
|
||||
CONFIG_GATEWAY_CHANNEL2_NATIVE_TX_PIN=4
|
||||
CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN=3
|
||||
CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE=1200
|
||||
# end of Gateway Channel 2
|
||||
|
||||
#
|
||||
# Gateway Channel 3
|
||||
#
|
||||
# end of Gateway Channel 3
|
||||
|
||||
#
|
||||
# Gateway Channel 4
|
||||
#
|
||||
# end of Gateway Channel 4
|
||||
|
||||
#
|
||||
# Gateway Channel 5
|
||||
#
|
||||
# end of Gateway Channel 5
|
||||
|
||||
#
|
||||
# Gateway Channel 6
|
||||
#
|
||||
# end of Gateway Channel 6
|
||||
|
||||
#
|
||||
# Gateway Channel 7
|
||||
#
|
||||
# end of Gateway Channel 7
|
||||
|
||||
#
|
||||
# Gateway Channel 8
|
||||
#
|
||||
# end of Gateway Channel 8
|
||||
|
||||
#
|
||||
# Gateway Channel 9
|
||||
#
|
||||
# end of Gateway Channel 9
|
||||
|
||||
#
|
||||
# Gateway Channel 10
|
||||
#
|
||||
# end of Gateway Channel 10
|
||||
|
||||
#
|
||||
# Gateway Channel 11
|
||||
#
|
||||
# end of Gateway Channel 11
|
||||
|
||||
#
|
||||
# Gateway Channel 12
|
||||
#
|
||||
# end of Gateway Channel 12
|
||||
|
||||
#
|
||||
# Gateway Channel 13
|
||||
#
|
||||
# end of Gateway Channel 13
|
||||
|
||||
#
|
||||
# Gateway Channel 14
|
||||
#
|
||||
# end of Gateway Channel 14
|
||||
|
||||
#
|
||||
# Gateway Channel 15
|
||||
#
|
||||
# end of Gateway Channel 15
|
||||
|
||||
#
|
||||
# Gateway Channel 16
|
||||
#
|
||||
# end of Gateway Channel 16
|
||||
|
||||
#
|
||||
# Gateway Cache
|
||||
#
|
||||
@@ -644,10 +705,11 @@ CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
|
||||
# end of Gateway Cache
|
||||
|
||||
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
|
||||
CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=12288
|
||||
# end of DALI Settings
|
||||
|
||||
#
|
||||
# Gateway Startup Services
|
||||
# Connectivity Startup
|
||||
#
|
||||
CONFIG_GATEWAY_BLE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_BLE_ENABLED=y
|
||||
@@ -661,6 +723,7 @@ CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
||||
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
||||
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||
# end of Connectivity Startup
|
||||
|
||||
#
|
||||
# Gateway Wired Ethernet
|
||||
@@ -678,7 +741,15 @@ CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
|
||||
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=4096
|
||||
# end of Gateway Wired Ethernet
|
||||
|
||||
#
|
||||
# Bridge Runtime
|
||||
#
|
||||
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
||||
# end of Bridge Runtime
|
||||
|
||||
#
|
||||
# Modbus Settings
|
||||
#
|
||||
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
|
||||
@@ -686,8 +757,14 @@ CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
|
||||
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set
|
||||
CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
|
||||
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
|
||||
# end of Modbus Settings
|
||||
|
||||
#
|
||||
# BACnet Settings
|
||||
#
|
||||
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||
# end of BACnet Settings
|
||||
|
||||
#
|
||||
# KNX Settings
|
||||
@@ -737,6 +814,9 @@ CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
|
||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
|
||||
# end of KNX Settings
|
||||
|
||||
#
|
||||
# Cloud Settings
|
||||
#
|
||||
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX="devices"
|
||||
@@ -747,14 +827,29 @@ CONFIG_GATEWAY_CLOUD_LTE_UART_PORT=-1
|
||||
CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN=-1
|
||||
CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN=-1
|
||||
CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE=115200
|
||||
# end of Cloud Settings
|
||||
|
||||
#
|
||||
# Bridge Task Settings
|
||||
#
|
||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
|
||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4
|
||||
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
|
||||
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
|
||||
# end of Bridge Task Settings
|
||||
|
||||
#
|
||||
# USB Setup
|
||||
#
|
||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||
# end of USB Setup
|
||||
|
||||
#
|
||||
# UART0 Control
|
||||
#
|
||||
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
|
||||
# end of Gateway Startup Services
|
||||
# end of UART0 Control
|
||||
|
||||
#
|
||||
# Gateway Network Services
|
||||
@@ -1118,12 +1213,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11
|
||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
|
||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
|
||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
|
||||
@@ -1766,7 +1861,7 @@ CONFIG_SPIRAM_USE_MALLOC=y
|
||||
CONFIG_SPIRAM_MEMTEST=y
|
||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
|
||||
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
|
||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
|
||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65535
|
||||
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
|
||||
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
|
||||
# end of SPI RAM config
|
||||
@@ -2385,9 +2480,9 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
|
||||
#
|
||||
# mbedTLS
|
||||
#
|
||||
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
|
||||
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
|
||||
@@ -2551,7 +2646,7 @@ CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y
|
||||
# CONFIG_NVS_ENCRYPTION is not set
|
||||
# CONFIG_NVS_ASSERT_ERROR_CHECK is not set
|
||||
# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set
|
||||
# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set
|
||||
CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM=y
|
||||
# end of NVS
|
||||
|
||||
#
|
||||
|
||||
+20
-2
@@ -109,13 +109,31 @@ config DALI_BUS_POWER_CHECK_INTERVAL_MS
|
||||
power-down. This lets the HAL recover when the bus was already powered
|
||||
before gateway startup and no RX edge is generated.
|
||||
|
||||
config DALI_BUS_FAIL_EVENT_DELAY_MS
|
||||
int "Bus fail event delay ms"
|
||||
range 1 60000
|
||||
default 45
|
||||
help
|
||||
Continuous low-bus duration required before publishing the legacy FF FD
|
||||
bus-abnormal raw frame for the first time. Repeat reports are controlled
|
||||
separately by DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS.
|
||||
|
||||
config DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS
|
||||
int "Legacy bus abnormal report interval ms"
|
||||
range 0 60000
|
||||
default 3000
|
||||
help
|
||||
Interval for publishing the legacy two-byte FF FD bus-abnormal raw frame
|
||||
while the native DALI bus is power-down. Set to 0 to disable the report.
|
||||
Repeat interval for publishing the legacy two-byte FF FD bus-abnormal raw
|
||||
frame while the native DALI bus remains failed. Set to 0 to disable both
|
||||
the first and repeated reports.
|
||||
|
||||
config DALI_BUS_RESET_PULSE_MS
|
||||
int "Bus reset low pulse ms"
|
||||
range 1 1000
|
||||
default 10
|
||||
help
|
||||
Default duration for the native physical DALI bus reset pulse. The USB
|
||||
setup bridge uses this value for short command 01 00 00.
|
||||
|
||||
choice DALI_LOG_LEVEL_CHOICE
|
||||
prompt "DALI log level"
|
||||
|
||||
@@ -45,16 +45,21 @@ Use `menuconfig` under `DALI Component` to configure:
|
||||
TX/RX stop conditions, query response timeout, double-send delay, and TX queue
|
||||
arbitration waits.
|
||||
- TX/RX active polarity. The native gateway default is TX active low and RX active high.
|
||||
- Native bus power-down polling and legacy `FF FD` bus-abnormal raw-frame reporting intervals.
|
||||
- Native bus power-down polling, first bus-fail event delay, legacy `FF FD`
|
||||
bus-abnormal raw-frame repeat interval, and physical bus reset pulse duration.
|
||||
- Native DALI HAL log level for the `dali_hal` ESP-IDF log tag.
|
||||
- Queue sizes.
|
||||
- Task stack sizes and priorities.
|
||||
- Optional debug task.
|
||||
|
||||
The native bus monitor uses `CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS` to resample RX while
|
||||
power-down and `CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS` to publish legacy `FF FD`
|
||||
raw frames while down. The report interval defaults to 3000 ms; set it to 0 to disable the
|
||||
compatibility report.
|
||||
power-down, `CONFIG_DALI_BUS_FAIL_EVENT_DELAY_MS` to publish the first legacy `FF FD`
|
||||
raw frame after a continuous low-bus failure, and
|
||||
`CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS` for repeat reports while the bus remains
|
||||
failed. The first delay defaults to 45 ms and the repeat interval defaults to 3000 ms;
|
||||
set the repeat interval to 0 to disable the compatibility report. USB setup command
|
||||
`01 00 00` uses `CONFIG_DALI_BUS_RESET_PULSE_MS` to drive the bus low intentionally for
|
||||
a physical reset pulse, defaulting to 10 ms.
|
||||
|
||||
Native timing defaults target standard 1200 bps DALI: a 416.67 us half-bit period is
|
||||
generated by the default 3.636363 MHz timer. `CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US`
|
||||
|
||||
@@ -83,10 +83,18 @@
|
||||
#define CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS 500
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_BUS_FAIL_EVENT_DELAY_MS
|
||||
#define CONFIG_DALI_BUS_FAIL_EVENT_DELAY_MS 45
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS
|
||||
#define CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS 3000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_BUS_RESET_PULSE_MS
|
||||
#define CONFIG_DALI_BUS_RESET_PULSE_MS 10
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS
|
||||
#define CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS 20
|
||||
#endif
|
||||
@@ -109,6 +117,7 @@
|
||||
#define DALI_BAUDRATE_MIN 400U
|
||||
#define DALI_BAUDRATE_MAX 2400U
|
||||
#define DALI_BUS_POWER_CHECK_INTERVAL_US ((uint64_t)CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS * 1000ULL)
|
||||
#define DALI_BUS_FAIL_EVENT_DELAY_US ((uint64_t)CONFIG_DALI_BUS_FAIL_EVENT_DELAY_MS * 1000ULL)
|
||||
#define DALI_BUS_ABNORMAL_REPORT_INTERVAL_US ((uint64_t)CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS * 1000ULL)
|
||||
|
||||
typedef struct {
|
||||
@@ -151,7 +160,10 @@ typedef struct {
|
||||
uint8_t tx_level;
|
||||
|
||||
uint64_t bus_level_check_time;
|
||||
uint64_t bus_low_since_time;
|
||||
uint64_t bus_abnormal_report_time;
|
||||
uint64_t bus_abnormal_suppress_until;
|
||||
bool bus_fail_reported;
|
||||
|
||||
Dali_msg_t tx_data;
|
||||
bool bus_activity_seen;
|
||||
@@ -526,6 +538,48 @@ static UBaseType_t queue_waiting(QueueHandle_t queue)
|
||||
return queue ? uxQueueMessagesWaiting(queue) : 0;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR reset_rx_state_from_isr(dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
bus->rx_state = RX_STATE_IDLE;
|
||||
bus->rx_half_bit_counter = 0;
|
||||
bus->rx_data_bit_counter = 0;
|
||||
bus->rx_data.status = DALI_FRAME_UNKNOWN;
|
||||
bus->rx_data.length = 0;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR reset_tx_counters_from_isr(dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
bus->tx_half_bit_counter = 0;
|
||||
bus->tx_data_bit_counter = 0;
|
||||
}
|
||||
|
||||
static void reset_rx_state(dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
bus->rx_state = RX_STATE_IDLE;
|
||||
bus->rx_half_bit_counter = 0;
|
||||
bus->rx_data_bit_counter = 0;
|
||||
bus->rx_data.status = DALI_FRAME_UNKNOWN;
|
||||
bus->rx_data.length = 0;
|
||||
}
|
||||
|
||||
static void reset_tx_counters(dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
bus->tx_half_bit_counter = 0;
|
||||
bus->tx_data_bit_counter = 0;
|
||||
}
|
||||
|
||||
static void log_tx_message(const char *prefix, const dali_bus_ctx_t *bus,
|
||||
const Dali_msg_t *msg, uint32_t timeout_ms)
|
||||
{
|
||||
@@ -576,11 +630,8 @@ static void recover_tx_timeout(dali_bus_ctx_t *bus)
|
||||
|
||||
bus->tx_data.status = DALI_FRAME_ERROR;
|
||||
bus->tx_state = TX_STATE_IDLE;
|
||||
bus->rx_state = RX_STATE_IDLE;
|
||||
bus->tx_half_bit_counter = 0;
|
||||
bus->tx_data_bit_counter = 0;
|
||||
bus->rx_half_bit_counter = 0;
|
||||
bus->rx_data_bit_counter = 0;
|
||||
reset_rx_state(bus);
|
||||
reset_tx_counters(bus);
|
||||
if (bus->bus_state == DALI_BUS_TRANSMITTING || bus->bus_state == DALI_BUS_TIME_BREAK ||
|
||||
bus->bus_state == DALI_BUS_RECOVERY) {
|
||||
bus->tx_last_edge_time = esp_timer_get_time();
|
||||
@@ -620,22 +671,82 @@ static void IRAM_ATTR publish_bus_abnormal_from_isr(dali_bus_ctx_t *bus, BaseTyp
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR maybe_report_bus_abnormal_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
|
||||
BaseType_t *yield)
|
||||
static void IRAM_ATTR update_bus_low_tracking_from_isr(dali_bus_ctx_t *bus,
|
||||
uint64_t time_now,
|
||||
uint8_t current_level)
|
||||
{
|
||||
if (bus == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0) {
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
if (!interval_elapsed_us(time_now, bus->bus_abnormal_report_time,
|
||||
DALI_BUS_ABNORMAL_REPORT_INTERVAL_US)) {
|
||||
if (current_level == 0) {
|
||||
if (bus->bus_low_since_time == 0) {
|
||||
bus->bus_low_since_time = time_now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
publish_bus_abnormal_from_isr(bus, yield);
|
||||
bus->bus_abnormal_report_time = time_now;
|
||||
bus->bus_low_since_time = 0;
|
||||
bus->bus_fail_reported = false;
|
||||
bus->bus_abnormal_report_time = 0;
|
||||
bus->bus_abnormal_suppress_until = 0;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR poll_power_down_bus_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
|
||||
BaseType_t *yield)
|
||||
static void IRAM_ATTR maybe_report_low_bus_abnormal_from_isr(dali_bus_ctx_t *bus,
|
||||
uint64_t time_now,
|
||||
uint8_t current_level,
|
||||
BaseType_t *yield)
|
||||
{
|
||||
if (bus == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0 || current_level != 0 ||
|
||||
bus->bus_low_since_time == 0) {
|
||||
return;
|
||||
}
|
||||
if (bus->bus_abnormal_suppress_until != 0 && time_now < bus->bus_abnormal_suppress_until) {
|
||||
return;
|
||||
}
|
||||
if ((time_now - bus->bus_low_since_time) < DALI_BUS_FAIL_EVENT_DELAY_US) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bus->bus_fail_reported) {
|
||||
publish_bus_abnormal_from_isr(bus, yield);
|
||||
bus->bus_fail_reported = true;
|
||||
bus->bus_abnormal_report_time = time_now;
|
||||
return;
|
||||
}
|
||||
|
||||
if (interval_elapsed_us(time_now, bus->bus_abnormal_report_time,
|
||||
DALI_BUS_ABNORMAL_REPORT_INTERVAL_US)) {
|
||||
publish_bus_abnormal_from_isr(bus, yield);
|
||||
bus->bus_abnormal_report_time = time_now;
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR resample_stale_bus_level_from_isr(dali_bus_ctx_t *bus,
|
||||
uint64_t time_now,
|
||||
uint32_t rx_delta,
|
||||
uint8_t current_level)
|
||||
{
|
||||
if (bus == NULL || current_level == bus->rx_level) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool should_resample = bus->bus_state == DALI_BUS_POWER_DOWN ||
|
||||
bus->bus_state <= DALI_BUS_ERROR ||
|
||||
(bus->bus_state == DALI_BUS_READY &&
|
||||
(bus->rx_state != RX_STATE_IDLE || bus->rx_level == 0) &&
|
||||
rx_delta > s_timing.rx_hb_max) ||
|
||||
(bus->bus_state == DALI_BUS_RECEIVING &&
|
||||
bus->rx_state == RX_STATE_START &&
|
||||
rx_delta > s_timing.rx_hb_max);
|
||||
if (!should_resample) {
|
||||
return;
|
||||
}
|
||||
|
||||
bus->rx_level = current_level;
|
||||
bus->rx_last_edge_time = time_now;
|
||||
bus->bus_activity_seen = true;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR poll_power_down_bus_from_isr(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
{
|
||||
if (bus == NULL || bus->bus_state != DALI_BUS_POWER_DOWN) {
|
||||
return;
|
||||
@@ -650,8 +761,6 @@ static void IRAM_ATTR poll_power_down_bus_from_isr(dali_bus_ctx_t *bus, uint64_t
|
||||
bus->rx_last_edge_time = time_now;
|
||||
}
|
||||
}
|
||||
|
||||
maybe_report_bus_abnormal_from_isr(bus, time_now, yield);
|
||||
}
|
||||
|
||||
static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queue, BaseType_t *yield)
|
||||
@@ -884,9 +993,20 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
bus->tx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time
|
||||
}
|
||||
|
||||
poll_power_down_bus_from_isr(bus, time_now, &yield);
|
||||
uint8_t current_level = DALI_GET_BUS_LEVEL(bus);
|
||||
update_bus_low_tracking_from_isr(bus, time_now, current_level);
|
||||
resample_stale_bus_level_from_isr(bus, time_now, rx_delta, current_level);
|
||||
poll_power_down_bus_from_isr(bus, time_now);
|
||||
rx_delta = time_now - bus->rx_last_edge_time;
|
||||
tx_delta = time_now - bus->tx_last_edge_time;
|
||||
current_level = DALI_GET_BUS_LEVEL(bus);
|
||||
update_bus_low_tracking_from_isr(bus, time_now, current_level);
|
||||
maybe_report_low_bus_abnormal_from_isr(bus, time_now, current_level, &yield);
|
||||
|
||||
if (bus->bus_state == DALI_BUS_READY && bus->rx_state != RX_STATE_IDLE &&
|
||||
rx_delta > s_timing.rx_hb_max) {
|
||||
reset_rx_state_from_isr(bus);
|
||||
}
|
||||
|
||||
if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state == TX_STATE_STOP &&
|
||||
DALI_GET_TX_LEVEL(bus) == 1 &&
|
||||
@@ -901,18 +1021,21 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
{ // 101.8.2.4 - startup BUS after 2.4ms
|
||||
if(bus->rx_level==1 && rx_delta > s_timing.rx_stop_cond)
|
||||
{
|
||||
DALI_SET_BUS_HIGH(bus);
|
||||
reset_rx_state_from_isr(bus);
|
||||
reset_tx_counters_from_isr(bus);
|
||||
bus->bus_state = DALI_BUS_READY; // bus is ready
|
||||
}
|
||||
}
|
||||
// if bus power down - if bus is low for more then 45ms
|
||||
if(bus->rx_level==0 && rx_delta > s_timing.time_bus_down)
|
||||
if(current_level == 0 && bus->bus_low_since_time != 0 &&
|
||||
(time_now - bus->bus_low_since_time) > s_timing.time_bus_down)
|
||||
{ // power lost
|
||||
bool entered_power_down = bus->bus_state != DALI_BUS_POWER_DOWN;
|
||||
bus->bus_state = DALI_BUS_POWER_DOWN; // bus is power down - recovery see previous if
|
||||
DALI_SET_BUS_HIGH(bus); // make sure TX is high
|
||||
if (entered_power_down) {
|
||||
bus->bus_level_check_time = time_now;
|
||||
bus->bus_abnormal_report_time = time_now;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -940,6 +1063,8 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
}
|
||||
else if(bus->bus_state == DALI_BUS_RECOVERY && rx_delta > s_timing.time_recovery_min)
|
||||
{
|
||||
reset_rx_state_from_isr(bus);
|
||||
reset_tx_counters_from_isr(bus);
|
||||
bus->bus_state = DALI_BUS_READY; // bus is ready
|
||||
// immediately start transmitting if we have data
|
||||
}
|
||||
@@ -1025,6 +1150,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
|
||||
}
|
||||
bus->tx_state = TX_STATE_IDLE; // clear state
|
||||
reset_tx_counters_from_isr(bus);
|
||||
}
|
||||
|
||||
// recover receiving
|
||||
@@ -1280,7 +1406,10 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
|
||||
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin
|
||||
bus->tx_last_edge_time = bus->rx_last_edge_time;
|
||||
bus->bus_level_check_time = bus->rx_last_edge_time;
|
||||
bus->bus_low_since_time = bus->rx_level == 0 ? bus->rx_last_edge_time : 0;
|
||||
bus->bus_abnormal_report_time = 0;
|
||||
bus->bus_abnormal_suppress_until = 0;
|
||||
bus->bus_fail_reported = false;
|
||||
bus->bus_activity_seen = false;
|
||||
bus->rx_last_frame_time = 0;
|
||||
bus->rx_last_frame_length = 0;
|
||||
@@ -1513,6 +1642,104 @@ esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t dali_hal_pulse_bus_low(uint8_t bus_id, uint32_t duration_ms)
|
||||
{
|
||||
if (bus_id >= DALI_PHY_COUNT) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (duration_ms == 0) {
|
||||
duration_ms = CONFIG_DALI_BUS_RESET_PULSE_MS;
|
||||
}
|
||||
|
||||
dali_hal_lock();
|
||||
esp_err_t err = ensure_timing_ready_locked();
|
||||
if (err != ESP_OK) {
|
||||
dali_hal_unlock();
|
||||
return err;
|
||||
}
|
||||
dali_bus_ctx_t *bus = &s_bus[bus_id];
|
||||
if (!bus->inited) {
|
||||
dali_hal_unlock();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
const uint64_t start_time = esp_timer_get_time();
|
||||
const uint64_t duration_us = (uint64_t)duration_ms * 1000ULL;
|
||||
const bool had_tx = bus->tx_state > TX_STATE_IDLE || queue_waiting(bus->tx_queue) > 0;
|
||||
Dali_msg_t failed_tx = bus->tx_data;
|
||||
failed_tx.status = DALI_FRAME_ERROR;
|
||||
|
||||
err = gpio_intr_disable(bus->rx_pin);
|
||||
if (err != ESP_OK) {
|
||||
dali_hal_unlock();
|
||||
return err;
|
||||
}
|
||||
|
||||
if (bus->tx_queue) {
|
||||
xQueueReset(bus->tx_queue);
|
||||
}
|
||||
if (bus->tx_reply_queue) {
|
||||
xQueueReset(bus->tx_reply_queue);
|
||||
if (had_tx && !is_backward_tx_msg(&failed_tx)) {
|
||||
xQueueSendToBack(bus->tx_reply_queue, &failed_tx, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bus->force_backward_tx = false;
|
||||
bus->tx_state = TX_STATE_IDLE;
|
||||
bus->tx_data.status = DALI_FRAME_ERROR;
|
||||
reset_rx_state(bus);
|
||||
reset_tx_counters(bus);
|
||||
bus->bus_activity_seen = true;
|
||||
bus->bus_state = DALI_BUS_POWER_DOWN;
|
||||
bus->bus_level_check_time = start_time;
|
||||
bus->bus_abnormal_report_time = 0;
|
||||
bus->bus_fail_reported = false;
|
||||
bus->bus_abnormal_suppress_until =
|
||||
start_time + duration_us + DALI_BUS_FAIL_EVENT_DELAY_US + s_timing.rx_stop_cond;
|
||||
|
||||
DALI_SET_BUS_LOW(bus);
|
||||
bus->rx_level = DALI_GET_BUS_LEVEL(bus);
|
||||
bus->rx_last_edge_time = start_time;
|
||||
bus->tx_last_edge_time = start_time;
|
||||
bus->bus_low_since_time = bus->rx_level == 0 ? start_time : 0;
|
||||
dali_hal_unlock();
|
||||
|
||||
TickType_t delay_ticks = pdMS_TO_TICKS(duration_ms);
|
||||
if (delay_ticks == 0) {
|
||||
delay_ticks = 1;
|
||||
}
|
||||
vTaskDelay(delay_ticks);
|
||||
|
||||
dali_hal_lock();
|
||||
const uint64_t end_time = esp_timer_get_time();
|
||||
DALI_SET_BUS_HIGH(bus);
|
||||
bus->rx_level = DALI_GET_BUS_LEVEL(bus);
|
||||
bus->rx_last_edge_time = end_time;
|
||||
bus->tx_last_edge_time = end_time;
|
||||
bus->bus_level_check_time = end_time;
|
||||
bus->bus_low_since_time = bus->rx_level == 0 ? end_time : 0;
|
||||
bus->bus_abnormal_report_time = 0;
|
||||
bus->bus_fail_reported = false;
|
||||
if (bus->rx_level == 1) {
|
||||
bus->bus_abnormal_suppress_until = 0;
|
||||
bus->bus_state = DALI_BUS_READY;
|
||||
} else {
|
||||
bus->bus_abnormal_suppress_until =
|
||||
end_time + DALI_BUS_FAIL_EVENT_DELAY_US + s_timing.rx_stop_cond;
|
||||
bus->bus_state = DALI_BUS_POWER_DOWN;
|
||||
}
|
||||
bus->tx_state = TX_STATE_IDLE;
|
||||
reset_rx_state(bus);
|
||||
reset_tx_counters(bus);
|
||||
gpio_intr_enable(bus->rx_pin);
|
||||
dali_hal_unlock();
|
||||
|
||||
ESP_LOGI(TAG, "bus=%u physical reset pulse complete durationMs=%lu", bus_id,
|
||||
(unsigned long)duration_ms);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
QueueHandle_t dali_hal_raw_receive_queue(void)
|
||||
{
|
||||
ensure_common_queues();
|
||||
|
||||
@@ -22,6 +22,7 @@ Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd
|
||||
|
||||
// create standard DALI message: 16,24,32 bits
|
||||
Dali_msg_t dali_msg_new(uint8_t address, uint8_t cmd1);
|
||||
Dali_msg_t dali_msg_new_1B(uint8_t data);
|
||||
Dali_msg_t dali_msg_new_3B(uint8_t address, uint8_t cmd1, uint8_t cmd2);
|
||||
Dali_msg_t dali_msg_new_4B(uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3);
|
||||
|
||||
@@ -86,4 +87,3 @@ void dali_change_short_address(int addr1, int addr2);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -238,10 +238,10 @@ esp_err_t dali_hal_set_baudrate(uint32_t baudrate);
|
||||
uint32_t dali_hal_get_baudrate(void);
|
||||
size_t dali_hal_get_inited_buses(uint8_t *ids, size_t max_ids);
|
||||
esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info);
|
||||
esp_err_t dali_hal_pulse_bus_low(uint8_t bus_id, uint32_t duration_ms);
|
||||
QueueHandle_t dali_hal_raw_receive_queue(void);
|
||||
void dali_task(void *pvParameters);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "dali_gateway.hpp"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
@@ -76,12 +78,15 @@ struct DaliRawFrame {
|
||||
uint8_t gateway_id{0};
|
||||
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
|
||||
std::vector<uint8_t> data;
|
||||
bool double_send_confirmed{false};
|
||||
};
|
||||
|
||||
enum class DaliDt8SceneColorMode {
|
||||
kDisabled,
|
||||
kColorTemperature,
|
||||
kRgb,
|
||||
kRgbw,
|
||||
kRgbcw,
|
||||
};
|
||||
|
||||
struct DaliDomainSnapshot {
|
||||
@@ -112,6 +117,11 @@ struct DaliAddressSettingsSnapshot {
|
||||
|
||||
class DaliDomainService {
|
||||
public:
|
||||
using ApplicationControllerIdentitySink = std::function<void(uint8_t gateway_id)>;
|
||||
using ApplicationControllerShortAddressProvider =
|
||||
std::function<std::optional<uint8_t>(uint8_t channel_index, uint8_t gateway_id)>;
|
||||
using ApplicationControllerShortAddressSink =
|
||||
std::function<void(uint8_t channel_index, std::optional<uint8_t> short_address)>;
|
||||
DaliDomainService();
|
||||
~DaliDomainService();
|
||||
|
||||
@@ -124,8 +134,15 @@ class DaliDomainService {
|
||||
size_t channelCount() const;
|
||||
std::vector<DaliChannelInfo> channelInfo() const;
|
||||
void addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink);
|
||||
void setApplicationControllerEnabled(bool enabled);
|
||||
void setApplicationControllerIdentitySink(ApplicationControllerIdentitySink sink);
|
||||
void setApplicationControllerShortAddressProvider(ApplicationControllerShortAddressProvider provider);
|
||||
void setApplicationControllerShortAddressSink(ApplicationControllerShortAddressSink sink);
|
||||
DaliGatewayCache& gatewayCache();
|
||||
const DaliGatewayCache& gatewayCache() const;
|
||||
|
||||
bool resetBus(uint8_t gateway_id) const;
|
||||
bool pulseBusLow(uint8_t gateway_id, uint32_t duration_ms) const;
|
||||
bool isBusIdle(uint8_t gateway_id, uint32_t quiet_ms) const;
|
||||
void markHostActivity(uint8_t gateway_id) const;
|
||||
void markHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
@@ -138,9 +155,16 @@ class DaliDomainService {
|
||||
bool sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
bool sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
std::optional<uint8_t> queryRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
bool sendControlDeviceRaw(uint8_t gateway_id, uint8_t byte0, uint8_t byte1, uint8_t byte2,
|
||||
bool send_twice = false) const;
|
||||
std::optional<uint8_t> queryControlDeviceRaw(uint8_t gateway_id, uint8_t byte0,
|
||||
uint8_t byte1, uint8_t byte2) const;
|
||||
bool sendBackwardFrame(uint8_t gateway_id, uint8_t value) const;
|
||||
std::optional<DaliDomainSnapshot> discoverDeviceTypes(
|
||||
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types = {},
|
||||
int max_next_types = 16) const;
|
||||
std::optional<uint8_t> readMemoryLocation(uint8_t gateway_id, int short_address, uint8_t bank,
|
||||
uint8_t location) const;
|
||||
std::optional<DaliDomainSnapshot> baseStatusSnapshot(uint8_t gateway_id,
|
||||
int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt1Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
@@ -158,14 +182,21 @@ class DaliDomainService {
|
||||
bool storeDt8SceneSnapshot(uint8_t gateway_id, int short_address, int scene, int brightness,
|
||||
DaliDt8SceneColorMode color_mode = DaliDt8SceneColorMode::kDisabled,
|
||||
int color_temperature = 0, int red = 0, int green = 0,
|
||||
int blue = 0) const;
|
||||
int blue = 0, int white = 0, int amber = 0,
|
||||
int freecolour = 255, int rgbwaf_control = -1) const;
|
||||
bool storeDt8PowerOnLevelSnapshot(uint8_t gateway_id, int short_address, int level) const;
|
||||
bool storeDt8SystemFailureLevelSnapshot(uint8_t gateway_id, int short_address, int level) const;
|
||||
bool setBright(uint8_t gateway_id, int short_address, int brightness) const;
|
||||
bool setBright(uint8_t gateway_id, int short_address, int brightness,
|
||||
bool logarithmic_curve = false) const;
|
||||
bool setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const;
|
||||
bool setColTemp(uint8_t gateway_id, int short_address, int kelvin) const;
|
||||
bool setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const;
|
||||
bool setColourRGB(uint8_t gateway_id, int short_address, int r, int g, int b) const;
|
||||
bool setColourRGBW(uint8_t gateway_id, int short_address, int r, int g, int b, int w) const;
|
||||
bool setColourRGBCW(uint8_t gateway_id, int short_address, int r, int g, int b,
|
||||
int cool_white, int warm_white) const;
|
||||
bool setColourRGBWAF(uint8_t gateway_id, int short_address, int r, int g, int b, int w,
|
||||
int amber, int freecolour, int control = -1) const;
|
||||
bool on(uint8_t gateway_id, int short_address) const;
|
||||
bool off(uint8_t gateway_id, int short_address) const;
|
||||
bool off(int short_address) const;
|
||||
@@ -179,6 +210,8 @@ class DaliDomainService {
|
||||
std::optional<uint8_t> level) const;
|
||||
bool applyAddressSettings(uint8_t gateway_id, int short_address,
|
||||
const DaliAddressSettingsSnapshot& settings) const;
|
||||
std::optional<uint8_t> gatewayIdForChannelIndex(uint8_t channel_index) const;
|
||||
bool updateChannelGatewayId(uint8_t channel_index, uint8_t gateway_id);
|
||||
bool updateChannelName(uint8_t gateway_id, std::string_view name);
|
||||
bool allocateAllAddr(uint8_t gateway_id, int start_address = 0) const;
|
||||
void stopAllocAddr(uint8_t gateway_id) const;
|
||||
@@ -195,18 +228,28 @@ class DaliDomainService {
|
||||
TickType_t tick{0};
|
||||
bool valid{false};
|
||||
};
|
||||
struct RecentForwardFrame {
|
||||
std::array<uint8_t, 3> data{};
|
||||
TickType_t tick{0};
|
||||
bool valid{false};
|
||||
};
|
||||
|
||||
DaliChannel* findChannelByGateway(uint8_t gateway_id);
|
||||
const DaliChannel* findChannelByGateway(uint8_t gateway_id) const;
|
||||
DaliChannel* findChannelByIndex(uint8_t channel_index);
|
||||
const DaliChannel* findChannelByIndex(uint8_t channel_index) const;
|
||||
const DaliChannel* findChannelByHardwareBus(uint8_t bus_id) const;
|
||||
bool hasSerialPort(int uart_port) const;
|
||||
esp_err_t startSerialRxTask(DaliChannel& channel);
|
||||
static void SerialRxTaskEntry(void* arg);
|
||||
void serialRxTaskLoop(DaliChannel* channel);
|
||||
esp_err_t startRawFrameDispatchTask();
|
||||
static void RawFrameDispatchTaskEntry(void* arg);
|
||||
void rawFrameDispatchTaskLoop();
|
||||
esp_err_t startRawFrameTask();
|
||||
static void RawFrameTaskEntry(void* arg);
|
||||
void rawFrameTaskLoop();
|
||||
void queueRawFrame(const DaliChannel& channel, const uint8_t* data, size_t len);
|
||||
void notifyRawFrameSinks(const DaliRawFrame& frame);
|
||||
void markBusActivity(uint8_t gateway_id) const;
|
||||
|
||||
@@ -218,7 +261,15 @@ class DaliDomainService {
|
||||
mutable SemaphoreHandle_t host_activity_lock_{nullptr};
|
||||
mutable std::map<uint8_t, TickType_t> last_host_activity_ticks_;
|
||||
mutable std::map<uint8_t, RecentHostCommandFrame> recent_host_command_frames_;
|
||||
std::map<uint8_t, RecentForwardFrame> recent_forward_frames_;
|
||||
bool application_controller_enabled_{false};
|
||||
ApplicationControllerIdentitySink application_controller_identity_sink_;
|
||||
ApplicationControllerShortAddressProvider application_controller_short_address_provider_;
|
||||
ApplicationControllerShortAddressSink application_controller_short_address_sink_;
|
||||
DaliGatewayCache gateway_cache_;
|
||||
QueueHandle_t raw_frame_dispatch_queue_{nullptr};
|
||||
TaskHandle_t raw_frame_dispatch_task_handle_{nullptr};
|
||||
TaskHandle_t raw_frame_task_handle_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "dali.h"
|
||||
#include "dali_hal.h"
|
||||
#include "dali.hpp"
|
||||
#include "dali_gateway.hpp"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
@@ -16,6 +17,18 @@
|
||||
#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 25
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_DOMAIN_RAW_RX_TASK_STACK_SIZE
|
||||
#define CONFIG_DALI_DOMAIN_RAW_RX_TASK_STACK_SIZE 4096
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_DOMAIN_RAW_DISPATCH_TASK_STACK_SIZE
|
||||
#define CONFIG_DALI_DOMAIN_RAW_DISPATCH_TASK_STACK_SIZE 12288
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_DOMAIN_RAW_DISPATCH_QUEUE_LEN
|
||||
#define CONFIG_DALI_DOMAIN_RAW_DISPATCH_QUEUE_LEN 16
|
||||
#endif
|
||||
|
||||
static const char *TAG = "dali_domain";
|
||||
|
||||
namespace gateway {
|
||||
@@ -24,7 +37,16 @@ namespace {
|
||||
|
||||
constexpr size_t kSerialRxPacketMaxBytes = 8;
|
||||
constexpr UBaseType_t kSerialRxQueueDepth = 8;
|
||||
constexpr size_t kRawFrameMaxBytes = DALI_MAX_BYTES;
|
||||
constexpr UBaseType_t kRawFrameDispatchQueueDepth = CONFIG_DALI_DOMAIN_RAW_DISPATCH_QUEUE_LEN;
|
||||
constexpr uint32_t kRawFrameRxTaskStackSize = CONFIG_DALI_DOMAIN_RAW_RX_TASK_STACK_SIZE;
|
||||
constexpr uint32_t kRawFrameDispatchTaskStackSize =
|
||||
CONFIG_DALI_DOMAIN_RAW_DISPATCH_TASK_STACK_SIZE;
|
||||
constexpr uint32_t kHardwareQueryRawPostSuppressMs = 10;
|
||||
constexpr uint32_t kDaliDoubleSendWindowMs = 100;
|
||||
constexpr uint8_t kControlDeviceSendOpcode = 0x60;
|
||||
constexpr uint8_t kControlDeviceSendTwiceOpcode = 0x61;
|
||||
constexpr uint8_t kControlDeviceQueryOpcode = 0x62;
|
||||
|
||||
portMUX_TYPE s_query_raw_suppress_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
uint8_t s_query_raw_suppress_inflight[DALI_PHY_COUNT] = {};
|
||||
@@ -107,12 +129,30 @@ void PutOptionalNumber(DaliDomainSnapshot& snapshot, const char* name,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Report>
|
||||
void PutRgbwafReport(DaliDomainSnapshot& snapshot, const Report& report) {
|
||||
if (!report.hasRgbwaf()) {
|
||||
return;
|
||||
}
|
||||
snapshot.int_arrays["rgbwaf"] = report.rgbwaf;
|
||||
snapshot.int_arrays["rgbw"] = {report.rgbwaf[0], report.rgbwaf[1], report.rgbwaf[2], report.rgbwaf[3]};
|
||||
snapshot.int_arrays["rgbcw"] = {report.rgbwaf[0], report.rgbwaf[1], report.rgbwaf[2],
|
||||
report.rgbwaf[3], report.rgbwaf[4]};
|
||||
if (report.rgbwafControl.has_value()) {
|
||||
snapshot.ints["rgbwafControl"] = report.rgbwafControl.value();
|
||||
}
|
||||
}
|
||||
|
||||
Dt8SceneStoreColorMode ToDaliCppColorMode(DaliDt8SceneColorMode color_mode) {
|
||||
switch (color_mode) {
|
||||
case DaliDt8SceneColorMode::kColorTemperature:
|
||||
return Dt8SceneStoreColorMode::colorTemperature;
|
||||
case DaliDt8SceneColorMode::kRgb:
|
||||
return Dt8SceneStoreColorMode::rgb;
|
||||
case DaliDt8SceneColorMode::kRgbw:
|
||||
return Dt8SceneStoreColorMode::rgbw;
|
||||
case DaliDt8SceneColorMode::kRgbcw:
|
||||
return Dt8SceneStoreColorMode::rgbcw;
|
||||
case DaliDt8SceneColorMode::kDisabled:
|
||||
default:
|
||||
return Dt8SceneStoreColorMode::disabled;
|
||||
@@ -124,10 +164,25 @@ struct SerialRxPacket {
|
||||
uint8_t data[kSerialRxPacketMaxBytes]{};
|
||||
};
|
||||
|
||||
struct RawFrameEvent {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
|
||||
size_t len{0};
|
||||
uint8_t data[kRawFrameMaxBytes]{};
|
||||
};
|
||||
|
||||
std::vector<uint8_t> LegacyQueryResponse(uint8_t status, uint8_t value = 0x00) {
|
||||
return {status, value};
|
||||
}
|
||||
|
||||
std::optional<uint8_t> ParseLegacyQueryValue(const std::vector<uint8_t>& packet) {
|
||||
if (packet.size() >= 2 && packet[0] == 0xFF) {
|
||||
return packet[1];
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void LogQueryRxPacket(const char* transport, int id, const std::vector<uint8_t>& packet,
|
||||
const char* note = nullptr) {
|
||||
const unsigned first = packet.size() > 0 ? packet[0] : 0;
|
||||
@@ -138,11 +193,80 @@ void LogQueryRxPacket(const char* transport, int id, const std::vector<uint8_t>&
|
||||
note == nullptr ? "" : " note=", note == nullptr ? "" : note);
|
||||
}
|
||||
|
||||
bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
|
||||
bool SendHardwareControlDeviceFrame(uint8_t bus_id, const uint8_t* data, size_t len,
|
||||
bool send_twice) {
|
||||
if (data == nullptr || len != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Dali_msg_t tx = dali_msg_new_3B(data[0], data[1], data[2]);
|
||||
tx.id = bus_id;
|
||||
ESP_LOGD(TAG, "sending hardware control-device frame for bus=%u data=%02x %02x %02x",
|
||||
bus_id, data[0], data[1], data[2]);
|
||||
if (send_twice) {
|
||||
dali_send_double(&tx);
|
||||
} else {
|
||||
dali_send(&tx);
|
||||
}
|
||||
return tx.status == DALI_FRAME_OK;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> TransactHardwareControlDeviceFrame(uint8_t bus_id, const uint8_t* data,
|
||||
size_t len) {
|
||||
if (data == nullptr || len != 3) {
|
||||
const auto packet = LegacyQueryResponse(0xFD);
|
||||
LogQueryRxPacket("hardware-cd", bus_id, packet, "invalid-query-len");
|
||||
return packet;
|
||||
}
|
||||
|
||||
Dali_msg_t tx = dali_msg_new_3B(data[0], data[1], data[2]);
|
||||
tx.id = bus_id;
|
||||
Dali_msg_t rx = {};
|
||||
ESP_LOGD(TAG, "received hardware control-device query for bus=%u data=%02x %02x %02x",
|
||||
bus_id, data[0], data[1], data[2]);
|
||||
BeginHardwareQueryRawSuppress(bus_id);
|
||||
if (dali_query(&tx, &rx) == pdTRUE) {
|
||||
ClearHardwareQueryRawSuppress(bus_id);
|
||||
if (rx.status != DALI_FRAME_OK || rx.length != 8) {
|
||||
ESP_LOGW(TAG, "hardware control-device query response for bus=%u has invalid status or length",
|
||||
bus_id);
|
||||
const auto packet = LegacyQueryResponse(0xFD);
|
||||
LogQueryRxPacket("hardware-cd", bus_id, packet, "invalid-status-or-length");
|
||||
return packet;
|
||||
}
|
||||
const std::vector<uint8_t> packet{0xFF, rx.data[0]};
|
||||
LogQueryRxPacket("hardware-cd", bus_id, packet, "ok");
|
||||
return packet;
|
||||
}
|
||||
ClearHardwareQueryRawSuppress(bus_id);
|
||||
const auto packet = LegacyQueryResponse(0xFE);
|
||||
LogQueryRxPacket("hardware-cd", bus_id, packet, "no-response");
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool SendHardwareBackwardFrame(uint8_t bus_id, uint8_t value) {
|
||||
Dali_msg_t tx = dali_msg_new_1B(value);
|
||||
tx.id = bus_id;
|
||||
ESP_LOGD(TAG, "sending hardware backward frame for bus=%u data=%02x", bus_id, value);
|
||||
dali_send(&tx);
|
||||
return tx.status == DALI_FRAME_OK;
|
||||
}
|
||||
|
||||
bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (len == 4 && (data[0] == kControlDeviceSendOpcode ||
|
||||
data[0] == kControlDeviceSendTwiceOpcode)) {
|
||||
return SendHardwareControlDeviceFrame(bus_id, data + 1, 3,
|
||||
data[0] == kControlDeviceSendTwiceOpcode);
|
||||
}
|
||||
|
||||
if (len != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data[0] == 0x00) {
|
||||
return true;
|
||||
}
|
||||
@@ -168,6 +292,9 @@ std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data,
|
||||
if (data == nullptr) {
|
||||
return {};
|
||||
}
|
||||
if (len == 4 && data[0] == kControlDeviceQueryOpcode) {
|
||||
return TransactHardwareControlDeviceFrame(bus_id, data + 1, 3);
|
||||
}
|
||||
if (len != 3) {
|
||||
if (len > 0 && data[0] == 0x12) {
|
||||
const auto packet = LegacyQueryResponse(0xFD);
|
||||
@@ -180,6 +307,7 @@ std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data,
|
||||
switch (data[0]) {
|
||||
case 0x00:
|
||||
ESP_LOGD(TAG, "received reset for bus=%u", bus_id);
|
||||
return {1};
|
||||
case 0x01:
|
||||
return {1};
|
||||
case 0x10:
|
||||
@@ -258,12 +386,14 @@ std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
|
||||
LogQueryRxPacket("serial", uart_port, packet, "empty-query");
|
||||
return packet;
|
||||
}
|
||||
if (data[0] == 0x12 && len != 3) {
|
||||
const bool query_frame = data[0] == 0x12 || data[0] == kControlDeviceQueryOpcode;
|
||||
const size_t expected_query_len = data[0] == kControlDeviceQueryOpcode ? 4 : 3;
|
||||
if (query_frame && len != expected_query_len) {
|
||||
const auto packet = LegacyQueryResponse(0xFD);
|
||||
LogQueryRxPacket("serial", uart_port, packet, "invalid-query-len");
|
||||
return packet;
|
||||
}
|
||||
if (data != nullptr && len > 0 && data[0] == 0x12) {
|
||||
if (query_frame) {
|
||||
DrainSerialQueue(queue);
|
||||
}
|
||||
if (!WriteSerialFrame(uart_port, data, len)) {
|
||||
@@ -271,7 +401,7 @@ std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
|
||||
LogQueryRxPacket("serial", uart_port, packet, "write-failed");
|
||||
return packet;
|
||||
}
|
||||
if (data[0] != 0x12) {
|
||||
if (!query_frame) {
|
||||
return {0xFF};
|
||||
}
|
||||
|
||||
@@ -311,8 +441,24 @@ struct DaliDomainService::DaliChannel {
|
||||
std::optional<DaliSerialBusConfig> serial_bus;
|
||||
QueueHandle_t serial_rx_queue{nullptr};
|
||||
TaskHandle_t serial_rx_task_handle{nullptr};
|
||||
std::unique_ptr<DaliApplicationController> application_controller;
|
||||
};
|
||||
|
||||
std::optional<uint8_t> ApplicationControllerShortAddress(
|
||||
const DaliDomainService::ApplicationControllerShortAddressProvider& provider,
|
||||
const DaliChannelConfig& channel) {
|
||||
if (provider) {
|
||||
const auto short_address = provider(channel.channel_index, channel.gateway_id);
|
||||
if (short_address.has_value() && short_address.value() <= 63) {
|
||||
return short_address;
|
||||
}
|
||||
if (!short_address.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return static_cast<uint8_t>(channel.gateway_id & 0x3fU);
|
||||
}
|
||||
|
||||
DaliDomainService::DaliDomainService()
|
||||
: raw_frame_sink_lock_(xSemaphoreCreateMutex()),
|
||||
bus_activity_lock_(xSemaphoreCreateMutex()),
|
||||
@@ -335,6 +481,14 @@ DaliDomainService::~DaliDomainService() {
|
||||
}
|
||||
}
|
||||
|
||||
DaliGatewayCache& DaliDomainService::gatewayCache() {
|
||||
return gateway_cache_;
|
||||
}
|
||||
|
||||
const DaliGatewayCache& DaliDomainService::gatewayCache() const {
|
||||
return gateway_cache_;
|
||||
}
|
||||
|
||||
bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) {
|
||||
if (!hooks.send) {
|
||||
return false;
|
||||
@@ -347,6 +501,13 @@ bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTrans
|
||||
channel->comm = std::make_unique<DaliComm>(channel->hooks.send, channel->hooks.read,
|
||||
channel->hooks.transact, channel->hooks.delay);
|
||||
channel->dali = std::make_unique<Dali>(*channel->comm, config.gateway_id, config.name);
|
||||
if (application_controller_enabled_) {
|
||||
DaliApplicationControllerConfig application_config;
|
||||
application_config.shortAddress =
|
||||
ApplicationControllerShortAddress(application_controller_short_address_provider_, config);
|
||||
channel->application_controller =
|
||||
std::make_unique<DaliApplicationController>(application_config);
|
||||
}
|
||||
|
||||
auto* existing = findChannelByIndex(config.channel_index);
|
||||
if (existing != nullptr) {
|
||||
@@ -523,8 +684,11 @@ std::vector<DaliChannelInfo> DaliDomainService::channelInfo() const {
|
||||
std::vector<DaliChannelInfo> info;
|
||||
info.reserve(channels_.size());
|
||||
for (const auto& channel : channels_) {
|
||||
DaliChannelInfo item{channel->config.channel_index, channel->config.gateway_id,
|
||||
channel->phy_kind, channel->config.name};
|
||||
DaliChannelInfo item;
|
||||
item.channel_index = channel->config.channel_index;
|
||||
item.gateway_id = channel->config.gateway_id;
|
||||
item.phy_kind = channel->phy_kind;
|
||||
item.name = channel->config.name;
|
||||
if (channel->hardware_bus.has_value()) {
|
||||
item.native_bus_id = channel->hardware_bus->bus_id;
|
||||
}
|
||||
@@ -533,6 +697,31 @@ std::vector<DaliChannelInfo> DaliDomainService::channelInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DaliDomainService::gatewayIdForChannelIndex(uint8_t channel_index) const {
|
||||
const auto* channel = findChannelByIndex(channel_index);
|
||||
if (channel == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return channel->config.gateway_id;
|
||||
}
|
||||
|
||||
bool DaliDomainService::updateChannelGatewayId(uint8_t channel_index, uint8_t gateway_id) {
|
||||
auto* channel = findChannelByIndex(channel_index);
|
||||
if (channel == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& other : channels_) {
|
||||
if (other.get() != channel && other->config.gateway_id == gateway_id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
channel->config.gateway_id = gateway_id;
|
||||
if (channel->dali != nullptr) {
|
||||
channel->dali = std::make_unique<Dali>(*channel->comm, gateway_id, channel->config.name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DaliDomainService::addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink) {
|
||||
if (!sink) {
|
||||
return;
|
||||
@@ -546,6 +735,37 @@ void DaliDomainService::addRawFrameSink(std::function<void(const DaliRawFrame& f
|
||||
}
|
||||
}
|
||||
|
||||
void DaliDomainService::setApplicationControllerEnabled(bool enabled) {
|
||||
application_controller_enabled_ = enabled;
|
||||
for (auto& channel : channels_) {
|
||||
if (!enabled) {
|
||||
channel->application_controller.reset();
|
||||
continue;
|
||||
}
|
||||
if (channel->application_controller != nullptr) {
|
||||
continue;
|
||||
}
|
||||
DaliApplicationControllerConfig config;
|
||||
config.shortAddress = ApplicationControllerShortAddress(
|
||||
application_controller_short_address_provider_, channel->config);
|
||||
channel->application_controller = std::make_unique<DaliApplicationController>(config);
|
||||
}
|
||||
}
|
||||
|
||||
void DaliDomainService::setApplicationControllerIdentitySink(ApplicationControllerIdentitySink sink) {
|
||||
application_controller_identity_sink_ = std::move(sink);
|
||||
}
|
||||
|
||||
void DaliDomainService::setApplicationControllerShortAddressProvider(
|
||||
ApplicationControllerShortAddressProvider provider) {
|
||||
application_controller_short_address_provider_ = std::move(provider);
|
||||
}
|
||||
|
||||
void DaliDomainService::setApplicationControllerShortAddressSink(
|
||||
ApplicationControllerShortAddressSink sink) {
|
||||
application_controller_short_address_sink_ = std::move(sink);
|
||||
}
|
||||
|
||||
bool DaliDomainService::resetBus(uint8_t gateway_id) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
@@ -555,6 +775,15 @@ bool DaliDomainService::resetBus(uint8_t gateway_id) const {
|
||||
return channel->comm->resetBus();
|
||||
}
|
||||
|
||||
bool DaliDomainService::pulseBusLow(uint8_t gateway_id, uint32_t duration_ms) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || !channel->hardware_bus.has_value()) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return dali_hal_pulse_bus_low(channel->hardware_bus->bus_id, duration_ms) == ESP_OK;
|
||||
}
|
||||
|
||||
bool DaliDomainService::isBusIdle(uint8_t gateway_id, uint32_t quiet_ms) const {
|
||||
TickType_t last_activity = 0;
|
||||
if (bus_activity_lock_ != nullptr) {
|
||||
@@ -703,6 +932,70 @@ std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t r
|
||||
return channel->comm->queryRawNew(raw_addr, command);
|
||||
}
|
||||
|
||||
bool DaliDomainService::sendControlDeviceRaw(uint8_t gateway_id, uint8_t byte0, uint8_t byte1,
|
||||
uint8_t byte2, bool send_twice) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t frame[3] = {byte0, byte1, byte2};
|
||||
markHostActivity(gateway_id);
|
||||
markBusActivity(gateway_id);
|
||||
if (channel->hardware_bus.has_value()) {
|
||||
return SendHardwareControlDeviceFrame(channel->hardware_bus->bus_id, frame, sizeof(frame),
|
||||
send_twice);
|
||||
}
|
||||
if (!channel->hooks.send) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t bridge_frame[4] = {
|
||||
static_cast<uint8_t>(send_twice ? kControlDeviceSendTwiceOpcode : kControlDeviceSendOpcode),
|
||||
byte0,
|
||||
byte1,
|
||||
byte2,
|
||||
};
|
||||
return channel->hooks.send(bridge_frame, sizeof(bridge_frame));
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DaliDomainService::queryControlDeviceRaw(uint8_t gateway_id, uint8_t byte0,
|
||||
uint8_t byte1,
|
||||
uint8_t byte2) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const uint8_t frame[3] = {byte0, byte1, byte2};
|
||||
markHostActivity(gateway_id);
|
||||
markBusActivity(gateway_id);
|
||||
if (channel->hardware_bus.has_value()) {
|
||||
return ParseLegacyQueryValue(
|
||||
TransactHardwareControlDeviceFrame(channel->hardware_bus->bus_id, frame, sizeof(frame)));
|
||||
}
|
||||
if (!channel->hooks.transact) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint8_t bridge_frame[4] = {kControlDeviceQueryOpcode, byte0, byte1, byte2};
|
||||
return ParseLegacyQueryValue(channel->hooks.transact(bridge_frame, sizeof(bridge_frame)));
|
||||
}
|
||||
|
||||
bool DaliDomainService::sendBackwardFrame(uint8_t gateway_id, uint8_t value) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
markBusActivity(gateway_id);
|
||||
if (channel->hardware_bus.has_value()) {
|
||||
return SendHardwareBackwardFrame(channel->hardware_bus->bus_id, value);
|
||||
}
|
||||
if (!channel->hooks.send) {
|
||||
return false;
|
||||
}
|
||||
return channel->hooks.send(&value, 1);
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
||||
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types,
|
||||
int max_next_types) const {
|
||||
@@ -726,6 +1019,25 @@ std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DaliDomainService::readMemoryLocation(uint8_t gateway_id,
|
||||
int short_address,
|
||||
uint8_t bank,
|
||||
uint8_t location) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (short_address < 0 || short_address > 63) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const auto value = channel->dali->base.readMemoryLocation(short_address, bank, location);
|
||||
if (!value.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<uint8_t>(value.value() & 0xFF);
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::baseStatusSnapshot(
|
||||
uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
@@ -1099,6 +1411,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
|
||||
if (report->hasXy()) {
|
||||
snapshot.number_arrays["xy"] = report->xy;
|
||||
}
|
||||
PutRgbwafReport(snapshot, report.value());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -1122,6 +1435,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8PowerOnLevelColorReport(
|
||||
if (report->hasXy()) {
|
||||
snapshot.number_arrays["xy"] = report->xy;
|
||||
}
|
||||
PutRgbwafReport(snapshot, report.value());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -1145,6 +1459,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8SystemFailureLevelColorR
|
||||
if (report->hasXy()) {
|
||||
snapshot.number_arrays["xy"] = report->xy;
|
||||
}
|
||||
PutRgbwafReport(snapshot, report.value());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@@ -1152,7 +1467,8 @@ bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_addr
|
||||
int brightness,
|
||||
DaliDt8SceneColorMode color_mode,
|
||||
int color_temperature, int red, int green,
|
||||
int blue) const {
|
||||
int blue, int white, int amber, int freecolour,
|
||||
int rgbwaf_control) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
@@ -1160,7 +1476,8 @@ bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_addr
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.storeSceneSnapshot(short_address, scene, brightness,
|
||||
ToDaliCppColorMode(color_mode), color_temperature,
|
||||
red, green, blue);
|
||||
red, green, blue, white, amber, freecolour,
|
||||
rgbwaf_control);
|
||||
}
|
||||
|
||||
bool DaliDomainService::storeDt8PowerOnLevelSnapshot(uint8_t gateway_id, int short_address,
|
||||
@@ -1183,13 +1500,14 @@ bool DaliDomainService::storeDt8SystemFailureLevelSnapshot(uint8_t gateway_id,
|
||||
return channel->dali->dt8.storeSystemFailureLevelSnapshot(short_address, level);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setBright(uint8_t gateway_id, int short_address, int brightness) const {
|
||||
bool DaliDomainService::setBright(uint8_t gateway_id, int short_address, int brightness,
|
||||
bool logarithmic_curve) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->base.setBright(short_address, brightness);
|
||||
return channel->dali->base.setBright(short_address, brightness, logarithmic_curve);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const {
|
||||
@@ -1229,6 +1547,38 @@ bool DaliDomainService::setColourRGB(uint8_t gateway_id, int short_address, int
|
||||
return channel->dali->dt8.setColourRGB(short_address, r, g, b);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColourRGBW(uint8_t gateway_id, int short_address, int r, int g,
|
||||
int b, int w) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.setColourRGBW(short_address, r, g, b, w);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColourRGBCW(uint8_t gateway_id, int short_address, int r, int g,
|
||||
int b, int cool_white, int warm_white) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.setColourRGBCW(short_address, r, g, b, cool_white, warm_white);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColourRGBWAF(uint8_t gateway_id, int short_address, int r, int g,
|
||||
int b, int w, int amber, int freecolour,
|
||||
int control) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.setColourRGBWAF(short_address, r, g, b, w, amber, freecolour,
|
||||
control);
|
||||
}
|
||||
|
||||
bool DaliDomainService::on(uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
@@ -1471,6 +1821,15 @@ DaliDomainService::DaliChannel* DaliDomainService::findChannelByIndex(uint8_t ch
|
||||
return it == channels_.end() ? nullptr : it->get();
|
||||
}
|
||||
|
||||
const DaliDomainService::DaliChannel* DaliDomainService::findChannelByIndex(
|
||||
uint8_t channel_index) const {
|
||||
const auto it = std::find_if(channels_.begin(), channels_.end(),
|
||||
[channel_index](const auto& channel) {
|
||||
return channel->config.channel_index == channel_index;
|
||||
});
|
||||
return it == channels_.end() ? nullptr : it->get();
|
||||
}
|
||||
|
||||
const DaliDomainService::DaliChannel* DaliDomainService::findChannelByHardwareBus(
|
||||
uint8_t bus_id) const {
|
||||
const auto it = std::find_if(channels_.begin(), channels_.end(), [bus_id](const auto& channel) {
|
||||
@@ -1486,6 +1845,10 @@ esp_err_t DaliDomainService::startSerialRxTask(DaliChannel& channel) {
|
||||
if (!channel.serial_bus.has_value() || channel.serial_rx_queue == nullptr) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
const esp_err_t dispatch_err = startRawFrameDispatchTask();
|
||||
if (dispatch_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "serial raw frame dispatch disabled: %s", esp_err_to_name(dispatch_err));
|
||||
}
|
||||
const BaseType_t created = xTaskCreate(&DaliDomainService::SerialRxTaskEntry,
|
||||
"dali_uart_rx", 4096, &channel, 4,
|
||||
&channel.serial_rx_task_handle);
|
||||
@@ -1528,11 +1891,83 @@ void DaliDomainService::serialRxTaskLoop(DaliChannel* channel) {
|
||||
if (packet.len != 2 && packet.len != 3) {
|
||||
continue;
|
||||
}
|
||||
queueRawFrame(*channel, packet.data, packet.len);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t DaliDomainService::startRawFrameDispatchTask() {
|
||||
if (raw_frame_dispatch_task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (raw_frame_dispatch_queue_ == nullptr) {
|
||||
raw_frame_dispatch_queue_ =
|
||||
xQueueCreate(kRawFrameDispatchQueueDepth, sizeof(RawFrameEvent));
|
||||
if (raw_frame_dispatch_queue_ == nullptr) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
|
||||
const BaseType_t created =
|
||||
xTaskCreate(&DaliDomainService::RawFrameDispatchTaskEntry, "dali_raw_dispatch",
|
||||
kRawFrameDispatchTaskStackSize, this, 4,
|
||||
&raw_frame_dispatch_task_handle_);
|
||||
if (created != pdPASS) {
|
||||
raw_frame_dispatch_task_handle_ = nullptr;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void DaliDomainService::RawFrameDispatchTaskEntry(void* arg) {
|
||||
static_cast<DaliDomainService*>(arg)->rawFrameDispatchTaskLoop();
|
||||
}
|
||||
|
||||
void DaliDomainService::rawFrameDispatchTaskLoop() {
|
||||
RawFrameEvent event;
|
||||
while (true) {
|
||||
if (raw_frame_dispatch_queue_ == nullptr) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
continue;
|
||||
}
|
||||
if (xQueueReceive(raw_frame_dispatch_queue_, &event, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
DaliRawFrame frame;
|
||||
frame.channel_index = channel->config.channel_index;
|
||||
frame.gateway_id = channel->config.gateway_id;
|
||||
frame.phy_kind = channel->phy_kind;
|
||||
frame.data.assign(packet.data, packet.data + packet.len);
|
||||
frame.channel_index = event.channel_index;
|
||||
frame.gateway_id = event.gateway_id;
|
||||
frame.phy_kind = event.phy_kind;
|
||||
frame.data.assign(event.data, event.data + event.len);
|
||||
if (event.len == 3) {
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
auto& recent = recent_forward_frames_[event.gateway_id];
|
||||
const bool same = recent.valid && recent.data[0] == event.data[0] &&
|
||||
recent.data[1] == event.data[1] && recent.data[2] == event.data[2];
|
||||
frame.double_send_confirmed =
|
||||
same && (now - recent.tick) <= pdMS_TO_TICKS(kDaliDoubleSendWindowMs);
|
||||
recent.data = {event.data[0], event.data[1], event.data[2]};
|
||||
recent.tick = now;
|
||||
recent.valid = true;
|
||||
}
|
||||
if (auto* channel = findChannelByGateway(frame.gateway_id);
|
||||
channel != nullptr && channel->application_controller != nullptr && frame.data.size() == 3) {
|
||||
const std::array<uint8_t, 3> forward{frame.data[0], frame.data[1], frame.data[2]};
|
||||
const auto result = channel->application_controller->handleForwardFrame(
|
||||
forward, frame.double_send_confirmed);
|
||||
if (result.backwardFrame.has_value()) {
|
||||
sendBackwardFrame(frame.gateway_id, result.backwardFrame.value());
|
||||
}
|
||||
if (result.identifyRequested && application_controller_identity_sink_) {
|
||||
application_controller_identity_sink_(frame.gateway_id);
|
||||
}
|
||||
if (result.shortAddressChanged) {
|
||||
ESP_LOGI(TAG, "Part 103 application controller gateway=%u short-address=%d",
|
||||
frame.gateway_id, result.shortAddress.has_value() ? result.shortAddress.value() : -1);
|
||||
if (application_controller_short_address_sink_) {
|
||||
application_controller_short_address_sink_(channel->config.channel_index,
|
||||
result.shortAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyRawFrameSinks(frame);
|
||||
}
|
||||
}
|
||||
@@ -1541,12 +1976,16 @@ esp_err_t DaliDomainService::startRawFrameTask() {
|
||||
if (raw_frame_task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
esp_err_t err = startRawFrameDispatchTask();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
QueueHandle_t queue = dali_hal_raw_receive_queue();
|
||||
if (queue == nullptr) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
const BaseType_t created = xTaskCreate(&DaliDomainService::RawFrameTaskEntry,
|
||||
"dali_raw_rx", 4096, this, 4,
|
||||
"dali_raw_rx", kRawFrameRxTaskStackSize, this, 4,
|
||||
&raw_frame_task_handle_);
|
||||
if (created != pdPASS) {
|
||||
raw_frame_task_handle_ = nullptr;
|
||||
@@ -1588,15 +2027,41 @@ void DaliDomainService::rawFrameTaskLoop() {
|
||||
if (byte_count != 1 && byte_count != 2 && byte_count != 3) {
|
||||
continue;
|
||||
}
|
||||
DaliRawFrame frame;
|
||||
frame.channel_index = channel->config.channel_index;
|
||||
frame.gateway_id = channel->config.gateway_id;
|
||||
frame.phy_kind = channel->phy_kind;
|
||||
frame.data.assign(message.data, message.data + byte_count);
|
||||
notifyRawFrameSinks(frame);
|
||||
queueRawFrame(*channel, message.data, byte_count);
|
||||
}
|
||||
}
|
||||
|
||||
void DaliDomainService::queueRawFrame(const DaliChannel& channel, const uint8_t* data,
|
||||
size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
return;
|
||||
}
|
||||
if (len > kRawFrameMaxBytes) {
|
||||
len = kRawFrameMaxBytes;
|
||||
}
|
||||
|
||||
markBusActivity(channel.config.gateway_id);
|
||||
|
||||
if (raw_frame_dispatch_queue_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RawFrameEvent event;
|
||||
event.channel_index = channel.config.channel_index;
|
||||
event.gateway_id = channel.config.gateway_id;
|
||||
event.phy_kind = channel.phy_kind;
|
||||
event.len = len;
|
||||
std::copy(data, data + len, event.data);
|
||||
|
||||
if (xQueueSendToBack(raw_frame_dispatch_queue_, &event, 0) == pdTRUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
RawFrameEvent dropped;
|
||||
xQueueReceive(raw_frame_dispatch_queue_, &dropped, 0);
|
||||
xQueueSendToBack(raw_frame_dispatch_queue_, &event, 0);
|
||||
}
|
||||
|
||||
void DaliDomainService::notifyRawFrameSinks(const DaliRawFrame& frame) {
|
||||
markBusActivity(frame.gateway_id);
|
||||
if (raw_frame_sink_lock_ != nullptr) {
|
||||
@@ -1627,4 +2092,4 @@ bool DaliDomainService::hasSerialPort(int uart_port) const {
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_485_control.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_driver_uart freertos gateway_controller log
|
||||
REQUIRES esp_driver_uart freertos gateway_controller gateway_runtime log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "gateway_controller.hpp"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class GatewayController;
|
||||
|
||||
struct Gateway485ControlBridgeConfig {
|
||||
bool enabled{false};
|
||||
int tx_pin{-1};
|
||||
@@ -33,15 +35,51 @@ class Gateway485ControlBridge {
|
||||
esp_err_t start();
|
||||
|
||||
private:
|
||||
enum class SessionStatus : uint8_t {
|
||||
kFailed = 0,
|
||||
kQueued = 1,
|
||||
kInProcess = 2,
|
||||
kFinished = 3,
|
||||
};
|
||||
|
||||
struct SessionEntry {
|
||||
uint8_t hash{0};
|
||||
uint8_t gateway_id{0};
|
||||
uint8_t opcode{0};
|
||||
std::vector<uint8_t> command;
|
||||
SessionStatus status{SessionStatus::kQueued};
|
||||
std::vector<uint8_t> result;
|
||||
TickType_t updated_at{0};
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void handleBytes(const uint8_t* data, size_t len);
|
||||
bool handleSessionBytes(const uint8_t* data, size_t len);
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
std::optional<SessionEntry> findSession(uint8_t hash,
|
||||
const std::vector<uint8_t>& command);
|
||||
void rememberSession(const SessionEntry& entry);
|
||||
void updateSession(uint8_t hash,
|
||||
const std::vector<uint8_t>& command,
|
||||
SessionStatus status,
|
||||
const std::vector<uint8_t>& result = {});
|
||||
void writeSessionStatus(uint8_t gateway_id,
|
||||
uint8_t hash,
|
||||
SessionStatus status,
|
||||
const std::vector<uint8_t>& result);
|
||||
GatewayCommandTransactionResult transactRoutedCommand(const std::vector<uint8_t>& command);
|
||||
bool shouldSuppressNotification(const std::vector<uint8_t>& frame);
|
||||
bool responseMatchesActiveSession(const std::vector<uint8_t>& frame) const;
|
||||
bool activeSessionTargetMatches(uint8_t response_gateway) const;
|
||||
|
||||
GatewayController& controller_;
|
||||
Gateway485ControlBridgeConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
SemaphoreHandle_t session_lock_{nullptr};
|
||||
std::deque<SessionEntry> session_cache_;
|
||||
std::optional<SessionEntry> active_session_;
|
||||
bool started_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#include "gateway_485_control.hpp"
|
||||
|
||||
#include "gateway_controller.hpp"
|
||||
#include "gateway_runtime.hpp"
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
@@ -13,15 +15,62 @@ namespace {
|
||||
constexpr const char* kTag = "gateway_485";
|
||||
constexpr uart_port_t kControlUart = UART_NUM_0;
|
||||
constexpr size_t kCommandFrameMinLen = 7;
|
||||
constexpr uint8_t kSessionOpcode = 0x69;
|
||||
constexpr size_t kSessionFrameMinLen = 7;
|
||||
constexpr size_t kSessionCacheSize = 16;
|
||||
constexpr uint32_t kSessionTtlMs = 30000;
|
||||
constexpr uint32_t kSessionTransactionTimeoutMs = 700;
|
||||
constexpr uint32_t kSessionTransactionIdleMs = 30;
|
||||
constexpr size_t kSessionMaxResponseBytes = 2048;
|
||||
constexpr uint8_t kDali103QueryOpcode = 0x62;
|
||||
constexpr uint8_t kDali103QueryResponseOpcode = 0x63;
|
||||
constexpr uint8_t kDali103NoResponseOpcode = 0x64;
|
||||
constexpr uint8_t kRawReportLeaseOpcode = 0x66;
|
||||
constexpr uint8_t kGatewayOperationOpcode = 0x67;
|
||||
constexpr uint8_t kGatewayOperationResultOpcode = 0x68;
|
||||
constexpr uint8_t kGatewayGroupOpcode = 0x6A;
|
||||
constexpr uint8_t kGatewayCacheOpcode = 0x39;
|
||||
constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0;
|
||||
constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1;
|
||||
|
||||
int EffectivePin(int pin) {
|
||||
return pin >= 0 ? pin : UART_PIN_NO_CHANGE;
|
||||
}
|
||||
|
||||
bool HasExactChecksum(const std::vector<uint8_t>& frame) {
|
||||
if (frame.empty()) {
|
||||
return false;
|
||||
}
|
||||
uint32_t sum = 0;
|
||||
for (size_t i = 0; i + 1 < frame.size(); ++i) {
|
||||
sum += frame[i];
|
||||
}
|
||||
return static_cast<uint8_t>(sum & 0xFF) == frame.back();
|
||||
}
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
|
||||
if (lock_ != nullptr) {
|
||||
xSemaphoreTakeRecursive(lock_, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
~LockGuard() {
|
||||
if (lock_ != nullptr) {
|
||||
xSemaphoreGiveRecursive(lock_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SemaphoreHandle_t lock_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Gateway485ControlBridge::Gateway485ControlBridge(GatewayController& controller,
|
||||
Gateway485ControlBridgeConfig config)
|
||||
: controller_(controller), config_(config) {}
|
||||
: controller_(controller), config_(config),
|
||||
session_lock_(xSemaphoreCreateRecursiveMutex()) {}
|
||||
|
||||
esp_err_t Gateway485ControlBridge::start() {
|
||||
if (started_) {
|
||||
@@ -113,13 +162,85 @@ void Gateway485ControlBridge::handleBytes(const uint8_t* data, size_t len) {
|
||||
return;
|
||||
}
|
||||
|
||||
controller_.enqueueCommandFrame(std::vector<uint8_t>(data, data + len));
|
||||
if (handleSessionBytes(data, len)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(kTag, "ignored unscoped UART0 gateway command len=%u",
|
||||
static_cast<unsigned>(len));
|
||||
}
|
||||
|
||||
bool Gateway485ControlBridge::handleSessionBytes(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len < kSessionFrameMinLen || data[3] != kSessionOpcode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> session_frame(data, data + len);
|
||||
if (!HasExactChecksum(session_frame)) {
|
||||
ESP_LOGW(kTag, "dropped invalid session command checksum len=%u",
|
||||
static_cast<unsigned>(len));
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t gateway_id = data[2];
|
||||
const uint8_t session_hash = data[4];
|
||||
std::vector<uint8_t> command{0x28, 0x01, gateway_id};
|
||||
command.insert(command.end(), data + 5, data + len - 1);
|
||||
command = GatewayRuntime::checksum(std::move(command));
|
||||
if (command.size() < kCommandFrameMinLen) {
|
||||
writeSessionStatus(gateway_id, session_hash, SessionStatus::kFailed, {});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto cached = findSession(session_hash, command)) {
|
||||
writeSessionStatus(cached->gateway_id, cached->hash, cached->status, cached->result);
|
||||
return true;
|
||||
}
|
||||
|
||||
SessionEntry entry;
|
||||
entry.hash = session_hash;
|
||||
entry.gateway_id = gateway_id;
|
||||
entry.opcode = command[3];
|
||||
entry.command = command;
|
||||
entry.status = SessionStatus::kQueued;
|
||||
entry.updated_at = xTaskGetTickCount();
|
||||
rememberSession(entry);
|
||||
writeSessionStatus(gateway_id, session_hash, SessionStatus::kQueued, {});
|
||||
|
||||
updateSession(session_hash, command, SessionStatus::kInProcess);
|
||||
writeSessionStatus(gateway_id, session_hash, SessionStatus::kInProcess, {});
|
||||
|
||||
{
|
||||
LockGuard guard(session_lock_);
|
||||
active_session_ = entry;
|
||||
active_session_->status = SessionStatus::kInProcess;
|
||||
}
|
||||
|
||||
const auto result = transactRoutedCommand(command);
|
||||
|
||||
{
|
||||
LockGuard guard(session_lock_);
|
||||
if (active_session_.has_value() && active_session_->hash == session_hash &&
|
||||
active_session_->command == command) {
|
||||
active_session_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
const auto status = result.status == GatewayCommandTransactionStatus::kOk
|
||||
? SessionStatus::kFinished
|
||||
: SessionStatus::kFailed;
|
||||
updateSession(session_hash, command, status, result.frames);
|
||||
writeSessionStatus(gateway_id, session_hash, status, result.frames);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
||||
if (!started_ || frame.empty()) {
|
||||
return;
|
||||
}
|
||||
if (shouldSuppressNotification(frame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int written = uart_write_bytes(kControlUart, frame.data(), frame.size());
|
||||
if (written < 0 || static_cast<size_t>(written) != frame.size()) {
|
||||
@@ -131,4 +252,175 @@ void Gateway485ControlBridge::handleGatewayNotification(const std::vector<uint8_
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
std::optional<Gateway485ControlBridge::SessionEntry> Gateway485ControlBridge::findSession(
|
||||
uint8_t hash,
|
||||
const std::vector<uint8_t>& command) {
|
||||
LockGuard guard(session_lock_);
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
const TickType_t ttl_ticks = pdMS_TO_TICKS(kSessionTtlMs);
|
||||
session_cache_.erase(std::remove_if(session_cache_.begin(), session_cache_.end(),
|
||||
[now, ttl_ticks](const SessionEntry& entry) {
|
||||
return ttl_ticks > 0 &&
|
||||
now - entry.updated_at > ttl_ticks;
|
||||
}),
|
||||
session_cache_.end());
|
||||
const auto it = std::find_if(session_cache_.begin(), session_cache_.end(),
|
||||
[hash, &command](const SessionEntry& entry) {
|
||||
return entry.hash == hash && entry.command == command;
|
||||
});
|
||||
if (it == session_cache_.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::rememberSession(const SessionEntry& entry) {
|
||||
LockGuard guard(session_lock_);
|
||||
session_cache_.push_back(entry);
|
||||
while (session_cache_.size() > kSessionCacheSize) {
|
||||
session_cache_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::updateSession(uint8_t hash,
|
||||
const std::vector<uint8_t>& command,
|
||||
SessionStatus status,
|
||||
const std::vector<uint8_t>& result) {
|
||||
LockGuard guard(session_lock_);
|
||||
const auto it = std::find_if(session_cache_.begin(), session_cache_.end(),
|
||||
[hash, &command](const SessionEntry& entry) {
|
||||
return entry.hash == hash && entry.command == command;
|
||||
});
|
||||
if (it == session_cache_.end()) {
|
||||
return;
|
||||
}
|
||||
it->status = status;
|
||||
it->result = result;
|
||||
it->updated_at = xTaskGetTickCount();
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::writeSessionStatus(uint8_t gateway_id,
|
||||
uint8_t hash,
|
||||
SessionStatus status,
|
||||
const std::vector<uint8_t>& result) {
|
||||
const size_t result_len = std::min<size_t>(result.size(), 65535);
|
||||
std::vector<uint8_t> payload{kSessionOpcode,
|
||||
gateway_id,
|
||||
hash,
|
||||
static_cast<uint8_t>(status),
|
||||
static_cast<uint8_t>(result_len & 0xFF),
|
||||
static_cast<uint8_t>((result_len >> 8) & 0xFF)};
|
||||
payload.insert(payload.end(), result.begin(), result.begin() + result_len);
|
||||
const auto frame = GatewayRuntime::buildNotificationFrame(payload);
|
||||
const int written = uart_write_bytes(kControlUart, frame.data(), frame.size());
|
||||
if (written < 0 || static_cast<size_t>(written) != frame.size()) {
|
||||
ESP_LOGW(kTag, "failed to write UART0 session status len=%u",
|
||||
static_cast<unsigned>(frame.size()));
|
||||
return;
|
||||
}
|
||||
if (uart_wait_tx_done(kControlUart, pdMS_TO_TICKS(config_.write_timeout_ms)) != ESP_OK) {
|
||||
ESP_LOGW(kTag, "timed out flushing UART0 session status len=%u",
|
||||
static_cast<unsigned>(frame.size()));
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCommandTransactionResult Gateway485ControlBridge::transactRoutedCommand(
|
||||
const std::vector<uint8_t>& command) {
|
||||
GatewayCommandTransactionResult aggregate;
|
||||
if (command.size() < kCommandFrameMinLen) {
|
||||
aggregate.status = GatewayCommandTransactionStatus::kInvalidFrame;
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
const auto targets = controller_.gatewayIdsForControlTarget(command[2]);
|
||||
if (targets.empty() || (targets.size() == 1 && targets.front() == command[2])) {
|
||||
return controller_.transactCommandFrame(command, kSessionTransactionTimeoutMs,
|
||||
kSessionTransactionIdleMs,
|
||||
kSessionMaxResponseBytes, true);
|
||||
}
|
||||
|
||||
aggregate.status = GatewayCommandTransactionStatus::kNoResponse;
|
||||
for (const auto gateway_id : targets) {
|
||||
std::vector<uint8_t> routed(command.begin(), command.end() - 1);
|
||||
routed[2] = gateway_id;
|
||||
const auto result = controller_.transactCommandFrame(
|
||||
GatewayRuntime::checksum(std::move(routed)), kSessionTransactionTimeoutMs,
|
||||
kSessionTransactionIdleMs, kSessionMaxResponseBytes, true);
|
||||
if (result.status == GatewayCommandTransactionStatus::kOk) {
|
||||
aggregate.status = GatewayCommandTransactionStatus::kOk;
|
||||
} else if (aggregate.status != GatewayCommandTransactionStatus::kOk) {
|
||||
aggregate.status = result.status;
|
||||
}
|
||||
if (!result.frames.empty()) {
|
||||
const size_t remaining =
|
||||
kSessionMaxResponseBytes > aggregate.frames.size()
|
||||
? kSessionMaxResponseBytes - aggregate.frames.size()
|
||||
: 0;
|
||||
aggregate.frames.insert(aggregate.frames.end(), result.frames.begin(),
|
||||
result.frames.begin() +
|
||||
std::min(remaining, result.frames.size()));
|
||||
}
|
||||
}
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
bool Gateway485ControlBridge::shouldSuppressNotification(const std::vector<uint8_t>& frame) {
|
||||
LockGuard guard(session_lock_);
|
||||
return responseMatchesActiveSession(frame);
|
||||
}
|
||||
|
||||
bool Gateway485ControlBridge::activeSessionTargetMatches(uint8_t response_gateway) const {
|
||||
if (!active_session_.has_value()) {
|
||||
return false;
|
||||
}
|
||||
const auto targets = controller_.gatewayIdsForControlTarget(active_session_->gateway_id);
|
||||
if (targets.empty()) {
|
||||
return response_gateway == active_session_->gateway_id ||
|
||||
active_session_->gateway_id == 0 || response_gateway == 0xff;
|
||||
}
|
||||
return std::find(targets.begin(), targets.end(), response_gateway) != targets.end();
|
||||
}
|
||||
|
||||
bool Gateway485ControlBridge::responseMatchesActiveSession(
|
||||
const std::vector<uint8_t>& frame) const {
|
||||
if (!active_session_.has_value() || frame.size() < 3 || frame[0] != 0x22) {
|
||||
return false;
|
||||
}
|
||||
const auto& session = *active_session_;
|
||||
const uint8_t response_opcode = frame[1];
|
||||
const uint8_t response_gateway = frame[2];
|
||||
const bool gateway_matches = activeSessionTargetMatches(response_gateway);
|
||||
switch (session.opcode) {
|
||||
case 0x06:
|
||||
return response_opcode == 0x03 && gateway_matches;
|
||||
case 0x09:
|
||||
return (response_opcode == 0x09 && session.gateway_id == 0) ||
|
||||
((response_opcode == 0x03 || response_opcode == 0x04) && gateway_matches);
|
||||
case 0x14:
|
||||
return (response_opcode == 0x03 || response_opcode == 0x04) && gateway_matches;
|
||||
case kDali103QueryOpcode:
|
||||
return (response_opcode == kDali103QueryResponseOpcode ||
|
||||
response_opcode == kDali103NoResponseOpcode) &&
|
||||
gateway_matches;
|
||||
case 0x05:
|
||||
case 0x0A:
|
||||
case 0x30:
|
||||
case kRawReportLeaseOpcode:
|
||||
case kGatewayCacheOpcode:
|
||||
case 0xA0:
|
||||
case 0xA2:
|
||||
return response_opcode == session.opcode && gateway_matches;
|
||||
case kGatewayOperationOpcode:
|
||||
return (response_opcode == kGatewayOperationOpcode ||
|
||||
response_opcode == kGatewayOperationResultOpcode) &&
|
||||
gateway_matches;
|
||||
case kGatewayGroupOpcode:
|
||||
return response_opcode == kGatewayGroupOpcode && gateway_matches;
|
||||
case kBridgeTransportRequestOpcode:
|
||||
return response_opcode == kBridgeTransportResponseOpcode && gateway_matches;
|
||||
default:
|
||||
return response_opcode == session.opcode && gateway_matches;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace gateway {
|
||||
struct GatewayBacnetServerConfig {
|
||||
uint32_t device_instance{4194303};
|
||||
std::string device_name{"DALI Gateway"};
|
||||
std::string channel_name;
|
||||
std::string local_address;
|
||||
uint16_t udp_port{47808};
|
||||
uint32_t task_stack_size{8192};
|
||||
@@ -91,4 +92,4 @@ class GatewayBacnetServer {
|
||||
bool started_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -48,6 +48,8 @@ bool gateway_bacnet_stack_start(
|
||||
|
||||
void gateway_bacnet_stack_cleanup(void);
|
||||
|
||||
bool gateway_bacnet_stack_set_device_name(const char* device_name);
|
||||
|
||||
bool gateway_bacnet_stack_upsert_object(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
@@ -74,4 +76,4 @@ void gateway_bacnet_stack_poll(uint16_t elapsed_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -251,6 +251,11 @@ esp_err_t GatewayBacnetServer::registerChannel(
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
GatewayBacnetServerConfig effective_config = config;
|
||||
if (effective_config.device_name.empty()) {
|
||||
effective_config.device_name = "DALI Gateway";
|
||||
}
|
||||
|
||||
bindings.erase(std::remove_if(bindings.begin(), bindings.end(), [](const auto& binding) {
|
||||
return !IsSupportedObjectType(binding.object_type) ||
|
||||
binding.object_instance > kMaxBacnetInstance;
|
||||
@@ -258,7 +263,7 @@ esp_err_t GatewayBacnetServer::registerChannel(
|
||||
bindings.end());
|
||||
|
||||
LockGuard guard(lock_);
|
||||
if (started_ && !configCompatible(config)) {
|
||||
if (started_ && !configCompatible(effective_config)) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
@@ -268,7 +273,15 @@ esp_err_t GatewayBacnetServer::registerChannel(
|
||||
if (bindings.empty() && !started_ && channel == channels_.end()) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
ChannelRegistration registration{gateway_id, config, std::move(bindings),
|
||||
if (started_ && active_config_.device_name != effective_config.device_name) {
|
||||
if (!gateway_bacnet_stack_set_device_name(effective_config.device_name.c_str())) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
active_config_.device_name = effective_config.device_name;
|
||||
gateway_bacnet_stack_send_i_am();
|
||||
}
|
||||
|
||||
ChannelRegistration registration{gateway_id, effective_config, std::move(bindings),
|
||||
std::move(write_callback), std::move(read_callback)};
|
||||
if (channel == channels_.end()) {
|
||||
channels_.push_back(std::move(registration));
|
||||
@@ -276,7 +289,7 @@ esp_err_t GatewayBacnetServer::registerChannel(
|
||||
*channel = std::move(registration);
|
||||
}
|
||||
|
||||
esp_err_t err = startStackLocked(config);
|
||||
esp_err_t err = startStackLocked(effective_config);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
@@ -341,9 +354,16 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
|
||||
used_objects.insert(key);
|
||||
|
||||
const std::string name = ObjectName(binding);
|
||||
std::string description = channel.config.channel_name;
|
||||
if (!binding.model_id.empty()) {
|
||||
if (!description.empty()) {
|
||||
description += " / ";
|
||||
}
|
||||
description += binding.model_id;
|
||||
}
|
||||
if (!gateway_bacnet_stack_upsert_object(ToBacnetKind(binding.object_type),
|
||||
binding.object_instance, name.c_str(),
|
||||
binding.model_id.c_str(),
|
||||
description.c_str(),
|
||||
binding.out_of_service,
|
||||
binding.reliability)) {
|
||||
return ESP_FAIL;
|
||||
@@ -465,4 +485,4 @@ void GatewayBacnetServer::taskLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -410,6 +410,14 @@ void gateway_bacnet_stack_cleanup(void)
|
||||
Write_Callback_Context = NULL;
|
||||
}
|
||||
|
||||
bool gateway_bacnet_stack_set_device_name(const char* device_name)
|
||||
{
|
||||
if (!device_name || device_name[0] == '\0') {
|
||||
device_name = "DALI Gateway";
|
||||
}
|
||||
return Device_Object_Name_ANSI_Init(device_name);
|
||||
}
|
||||
|
||||
bool gateway_bacnet_stack_upsert_object(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
@@ -636,4 +644,4 @@ void gateway_bacnet_stack_poll(uint16_t elapsed_ms)
|
||||
tsm_timer_milliseconds(elapsed_ms);
|
||||
Device_Timer(elapsed_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +405,13 @@ void GatewayBleBridge::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
if (!enabled_ || conn_handle_ == kInvalidConnectionHandle || frame.data.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!controller_.rawReportingEnabled(frame.gateway_id)) {
|
||||
return;
|
||||
}
|
||||
// Forward/control events are already published on FFF3 as structured 0x22 frames.
|
||||
if (notify_enabled_[kGatewayCharacteristicIndex] && frame.data.size() != 1) {
|
||||
return;
|
||||
}
|
||||
notifyCharacteristic(frame.channel_index, LegacyRawPayload(frame.data));
|
||||
}
|
||||
|
||||
@@ -552,7 +559,8 @@ int GatewayBleBridge::handleAccess(uint16_t, uint16_t attr_handle,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (index == static_cast<int>(kGatewayCharacteristicIndex)) {
|
||||
if (GatewayRuntime::isGatewayCommandFrame(payload) ||
|
||||
index == static_cast<int>(kGatewayCharacteristicIndex)) {
|
||||
handleGatewayWrite(payload);
|
||||
} else {
|
||||
handleRawWrite(static_cast<size_t>(index), payload);
|
||||
@@ -565,4 +573,4 @@ int GatewayBleBridge::handleAccess(uint16_t, uint16_t attr_handle,
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "dali_gateway.hpp"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -17,9 +20,13 @@
|
||||
namespace gateway {
|
||||
|
||||
class DaliDomainService;
|
||||
struct DaliRawFrame;
|
||||
class GatewayCache;
|
||||
|
||||
struct GatewayBridgeHttpResponse {
|
||||
esp_err_t err{ESP_OK};
|
||||
std::string body;
|
||||
};
|
||||
|
||||
struct GatewayBridgeServiceConfig {
|
||||
bool bridge_enabled{true};
|
||||
bool modbus_enabled{true};
|
||||
@@ -41,11 +48,12 @@ struct GatewayBridgeServiceConfig {
|
||||
uint32_t knx_task_stack_size{12288};
|
||||
UBaseType_t knx_task_priority{5};
|
||||
std::optional<GatewayKnxConfig> default_knx_config;
|
||||
};
|
||||
|
||||
struct GatewayBridgeHttpResponse {
|
||||
esp_err_t err{ESP_OK};
|
||||
std::string body;
|
||||
std::function<std::string()> gateway_device_name_provider;
|
||||
std::function<GatewayBridgeHttpResponse()> gateway_settings_getter;
|
||||
std::function<GatewayBridgeHttpResponse(std::string_view)> gateway_settings_setter;
|
||||
std::function<std::string(std::string_view)> gateway_settings_cloud_handler;
|
||||
GatewayKnxGatewaySnapshotProvider knx_gateway_snapshot_provider;
|
||||
GatewayKnxGatewayCommandTransactor knx_gateway_command_transactor;
|
||||
};
|
||||
|
||||
class GatewayBridgeService {
|
||||
@@ -62,6 +70,7 @@ class GatewayBridgeService {
|
||||
GatewayBridgeHttpResponse handlePost(const std::string& action, int gateway_id,
|
||||
const std::string& body);
|
||||
std::string handleTransportRequest(uint8_t gateway_id, std::string_view request);
|
||||
void handleGatewayNameChanged(uint8_t gateway_id);
|
||||
|
||||
private:
|
||||
struct ChannelRuntime;
|
||||
@@ -77,7 +86,7 @@ class GatewayBridgeService {
|
||||
size_t len);
|
||||
DaliBridgeResult routeKnxGroupObjectWrite(uint16_t group_object_number,
|
||||
const uint8_t* data, size_t len);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
void handleDaliStatusUpdate(const DaliGatewayStatusUpdate& update);
|
||||
void collectUsedRuntimeResources(uint8_t except_gateway_id,
|
||||
std::set<uint16_t>* modbus_tcp_ports,
|
||||
std::set<uint16_t>* knx_udp_ports,
|
||||
|
||||
@@ -70,6 +70,12 @@ constexpr uint8_t kDaliGroupRawMax = 0x9F;
|
||||
#define CONFIG_GATEWAY_KNX_INSTANCE_COUNT 1
|
||||
#endif
|
||||
constexpr uint32_t kKnxEnabledInstanceCount = CONFIG_GATEWAY_KNX_INSTANCE_COUNT;
|
||||
constexpr uint32_t kKnxOamInstanceId = 16;
|
||||
#if defined(CONFIG_GATEWAY_KNX_OAM_ROUTER_SUPPORTED)
|
||||
constexpr bool kKnxOamInstanceSupported = true;
|
||||
#else
|
||||
constexpr bool kKnxOamInstanceSupported = false;
|
||||
#endif
|
||||
|
||||
void ConfigureDaliCppLogging() {
|
||||
static bool configured = false;
|
||||
@@ -97,11 +103,6 @@ struct BridgeDiscoveryEntry {
|
||||
DaliDomainSnapshot discovery;
|
||||
};
|
||||
|
||||
struct DaliKnxStatusUpdate {
|
||||
GatewayKnxDaliTarget target;
|
||||
uint8_t actual_level{0};
|
||||
};
|
||||
|
||||
using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>;
|
||||
|
||||
extern "C" uint8_t knx_platform_copy_security_failures(uint8_t* counters, size_t countersLen,
|
||||
@@ -400,21 +401,49 @@ std::optional<int> QueryInt(std::string_view query, std::string_view primary,
|
||||
}
|
||||
|
||||
std::optional<uint32_t> ValidKnxInstanceId(int value) {
|
||||
if (value < 0 || static_cast<uint32_t>(value) >= kKnxEnabledInstanceCount) {
|
||||
return std::nullopt;
|
||||
if (value >= 0) {
|
||||
const auto instance_id = static_cast<uint32_t>(value);
|
||||
if (instance_id < kKnxEnabledInstanceCount ||
|
||||
(kKnxOamInstanceSupported && instance_id == kKnxOamInstanceId)) {
|
||||
return instance_id;
|
||||
}
|
||||
}
|
||||
return static_cast<uint32_t>(value);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string KnxInstanceIdRangeMessage() {
|
||||
return "KNX instanceId must be in range 0.." +
|
||||
std::to_string(kKnxEnabledInstanceCount - 1);
|
||||
std::string message = "KNX instanceId must be in range 0.." +
|
||||
std::to_string(kKnxEnabledInstanceCount - 1);
|
||||
if (kKnxOamInstanceSupported) {
|
||||
message += " or " + std::to_string(kKnxOamInstanceId) + " for OAM";
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> QueryKnxInstanceId(std::string_view query) {
|
||||
return ValidKnxInstanceId(QueryInt(query, "instanceId", "instance").value_or(0));
|
||||
}
|
||||
|
||||
bool IsKnxOamInstanceId(uint32_t instance_id) {
|
||||
return kKnxOamInstanceSupported && instance_id == kKnxOamInstanceId;
|
||||
}
|
||||
|
||||
cJSON* KnxAvailableInstanceIdsCjson(bool oam_enabled) {
|
||||
cJSON* instances = cJSON_CreateArray();
|
||||
if (instances == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
for (uint32_t instance_id = 0; instance_id < kKnxEnabledInstanceCount; ++instance_id) {
|
||||
cJSON_AddItemToArray(instances,
|
||||
cJSON_CreateNumber(static_cast<double>(instance_id)));
|
||||
}
|
||||
if (kKnxOamInstanceSupported && oam_enabled) {
|
||||
cJSON_AddItemToArray(instances,
|
||||
cJSON_CreateNumber(static_cast<double>(kKnxOamInstanceId)));
|
||||
}
|
||||
return instances;
|
||||
}
|
||||
|
||||
bool EraseOpenKnxRuntimeData(const std::string& nvs_namespace) {
|
||||
if (nvs_namespace.empty()) {
|
||||
return false;
|
||||
@@ -438,54 +467,6 @@ bool ValidDaliAddress(int address) {
|
||||
return address >= 0 && address <= 127;
|
||||
}
|
||||
|
||||
std::optional<GatewayKnxDaliTarget> DecodeKnxDaliTarget(uint8_t raw_addr) {
|
||||
if (raw_addr <= 0x7F) {
|
||||
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
|
||||
static_cast<int>(raw_addr >> 1)};
|
||||
}
|
||||
if (raw_addr >= kDaliGroupRawMin && raw_addr <= kDaliGroupRawMax) {
|
||||
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
|
||||
static_cast<int>((raw_addr - kDaliGroupRawMin) >> 1)};
|
||||
}
|
||||
if (raw_addr == 0xFE || raw_addr == 0xFF) {
|
||||
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kBroadcast, 127};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DaliKnxStatusUpdate> DecodeDaliKnxStatusUpdate(const DaliRawFrame& frame) {
|
||||
if (frame.data.size() != 2 && frame.data.size() != 3) {
|
||||
return std::nullopt;
|
||||
}
|
||||
uint8_t raw_addr = 0;
|
||||
uint8_t command = 0;
|
||||
if (frame.data.size() == 2) {
|
||||
raw_addr = frame.data[0];
|
||||
command = frame.data[1];
|
||||
if (raw_addr == 0xBE) {
|
||||
return std::nullopt;
|
||||
}
|
||||
} else {
|
||||
raw_addr = frame.data[1];
|
||||
command = frame.data[2];
|
||||
}
|
||||
auto target = DecodeKnxDaliTarget(raw_addr);
|
||||
if (!target.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if ((raw_addr & 0x01U) == 0) {
|
||||
if (command > 254) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DaliKnxStatusUpdate{*target, command};
|
||||
}
|
||||
if (command == kDaliCmdOff || command == kDaliCmdRecallMax) {
|
||||
return DaliKnxStatusUpdate{*target,
|
||||
static_cast<uint8_t>(command == kDaliCmdOff ? 0 : 254)};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool ValidShortAddress(int address) {
|
||||
return address >= 0 && address <= kMaxDaliShortAddress;
|
||||
}
|
||||
@@ -599,6 +580,7 @@ bool OperationRequiresDt1(BridgeOperation operation) {
|
||||
case BridgeOperation::dt1GetRatedDuration:
|
||||
case BridgeOperation::dt1GetExtendedVersion:
|
||||
case BridgeOperation::dt1GetEmergencyDeviceType:
|
||||
case BridgeOperation::readMemoryIdentity:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -986,6 +968,12 @@ DaliDt8SceneColorMode JsonColorMode(const cJSON* root) {
|
||||
if (item->valueint == 2) {
|
||||
return DaliDt8SceneColorMode::kRgb;
|
||||
}
|
||||
if (item->valueint == 3) {
|
||||
return DaliDt8SceneColorMode::kRgbw;
|
||||
}
|
||||
if (item->valueint == 4) {
|
||||
return DaliDt8SceneColorMode::kRgbcw;
|
||||
}
|
||||
return DaliDt8SceneColorMode::kDisabled;
|
||||
}
|
||||
if (!cJSON_IsString(item) || item->valuestring == nullptr) {
|
||||
@@ -998,6 +986,12 @@ DaliDt8SceneColorMode JsonColorMode(const cJSON* root) {
|
||||
if (mode == "rgb") {
|
||||
return DaliDt8SceneColorMode::kRgb;
|
||||
}
|
||||
if (mode == "rgbw") {
|
||||
return DaliDt8SceneColorMode::kRgbw;
|
||||
}
|
||||
if (mode == "rgbcw" || mode == "rgbWwCw" || mode == "rgb_cw") {
|
||||
return DaliDt8SceneColorMode::kRgbcw;
|
||||
}
|
||||
return DaliDt8SceneColorMode::kDisabled;
|
||||
}
|
||||
|
||||
@@ -1015,6 +1009,15 @@ GatewayBridgeHttpResponse StoreDt8SceneSnapshot(DaliDomainService& domain, uint8
|
||||
const int red = JsonIntAny(root, "red", "r").value_or(0);
|
||||
const int green = JsonIntAny(root, "green", "g").value_or(0);
|
||||
const int blue = JsonIntAny(root, "blue", "b").value_or(0);
|
||||
const int white = JsonIntAny(root, "white", "w")
|
||||
.value_or(JsonIntAny(root, "coolWhite", "cool_white")
|
||||
.value_or(JsonInt(root, "cw").value_or(0)));
|
||||
const int amber = JsonIntAny(root, "amber", "a")
|
||||
.value_or(JsonIntAny(root, "warmWhite", "warm_white")
|
||||
.value_or(JsonInt(root, "ww").value_or(0)));
|
||||
const int freecolour = JsonIntAny(root, "freecolour", "freeColor").value_or(255);
|
||||
const int rgbwaf_control = JsonIntAny(root, "rgbwafControl", "rgbwaf_control")
|
||||
.value_or(JsonInt(root, "control").value_or(-1));
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (!address.has_value() || !scene.has_value() || !brightness.has_value() ||
|
||||
@@ -1023,7 +1026,7 @@ GatewayBridgeHttpResponse StoreDt8SceneSnapshot(DaliDomainService& domain, uint8
|
||||
}
|
||||
if (!domain.storeDt8SceneSnapshot(gateway_id, address.value(), scene.value(),
|
||||
brightness.value(), color_mode, color_temperature, red,
|
||||
green, blue)) {
|
||||
green, blue, white, amber, freecolour, rgbwaf_control)) {
|
||||
return ErrorResponse(ESP_FAIL, "failed to store DT8 scene snapshot");
|
||||
}
|
||||
return StoredSnapshotResponse(domain.dt8SceneColorReport(gateway_id, address.value(),
|
||||
@@ -1527,6 +1530,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
GatewayBridgeServiceConfig service_config)
|
||||
: service(service),
|
||||
domain(domain),
|
||||
dali_cache(domain.gatewayCache()),
|
||||
cache(cache),
|
||||
channel(std::move(channel)),
|
||||
service_config(service_config),
|
||||
@@ -1547,6 +1551,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
|
||||
GatewayBridgeService& service;
|
||||
DaliDomainService& domain;
|
||||
DaliGatewayCache& dali_cache;
|
||||
GatewayCache& cache;
|
||||
DaliChannelInfo channel;
|
||||
GatewayBridgeServiceConfig service_config;
|
||||
@@ -1660,6 +1665,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
|
||||
knx = std::make_unique<GatewayKnxBridge>(*engine);
|
||||
knx->setGatewayControllerCallbacks(service_config.knx_gateway_snapshot_provider,
|
||||
service_config.knx_gateway_command_transactor);
|
||||
knx_router = std::make_unique<GatewayKnxTpIpRouter>(*knx, openKnxNamespace());
|
||||
knx_router->setGroupWriteHandler(
|
||||
[this](uint16_t group_address, const uint8_t* data, size_t len) {
|
||||
@@ -1863,6 +1870,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
for (const auto& model : bridge_config.models) {
|
||||
cloud->bridge().upsertModel(model);
|
||||
}
|
||||
cloud->setGatewaySettingsHandler(service_config.gateway_settings_cloud_handler);
|
||||
wireCloudCemiProxyLocked();
|
||||
}
|
||||
|
||||
@@ -2045,8 +2053,17 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
GatewayBacnetServerConfig bacnetServerConfigLocked() const {
|
||||
GatewayBacnetServerConfig config;
|
||||
config.device_name = channel.name.empty() ? "DALI Gateway " + std::to_string(channel.gateway_id)
|
||||
: channel.name;
|
||||
config.channel_name = channel.name.empty()
|
||||
? "Gateway " + std::to_string(channel.gateway_id)
|
||||
: channel.name;
|
||||
if (service_config.gateway_device_name_provider) {
|
||||
config.device_name = service_config.gateway_device_name_provider();
|
||||
}
|
||||
if (config.device_name.empty()) {
|
||||
config.device_name = channel.name.empty()
|
||||
? "DALI Gateway " + std::to_string(channel.gateway_id)
|
||||
: channel.name;
|
||||
}
|
||||
config.task_stack_size = service_config.bacnet_task_stack_size;
|
||||
config.task_priority = service_config.bacnet_task_priority;
|
||||
if (bacnet_server_config.has_value()) {
|
||||
@@ -2265,6 +2282,21 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
#endif
|
||||
|
||||
void updateChannelInfo(DaliChannelInfo next_channel) {
|
||||
LockGuard guard(lock);
|
||||
channel = std::move(next_channel);
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
if (bacnet_started) {
|
||||
const esp_err_t err = syncBacnetServerLocked();
|
||||
if (err != ESP_OK && err != ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGW(kTag, "gateway=%u BACnet name refresh failed: %s", channel.gateway_id,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
bacnet_started = err == ESP_OK;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t startKnx(std::set<uint16_t>* used_ports = nullptr,
|
||||
std::set<int>* used_uarts = nullptr) {
|
||||
LockGuard guard(lock);
|
||||
@@ -2401,9 +2433,19 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
const auto effective_knx =
|
||||
knx_config.has_value() ? knx_config : service_config.default_knx_config;
|
||||
const bool oam_instance_enabled =
|
||||
effective_knx.has_value() && effective_knx->oam_router.enabled;
|
||||
cJSON_AddNumberToObject(knx_json, "instanceId", static_cast<double>(instance_id));
|
||||
cJSON_AddNumberToObject(knx_json, "enabledInstanceCount",
|
||||
static_cast<double>(kKnxEnabledInstanceCount));
|
||||
cJSON* instance_ids_json = KnxAvailableInstanceIdsCjson(oam_instance_enabled);
|
||||
if (instance_ids_json != nullptr) {
|
||||
cJSON_AddItemToObject(knx_json, "availableInstanceIds", instance_ids_json);
|
||||
}
|
||||
if (kKnxOamInstanceSupported) {
|
||||
cJSON_AddNumberToObject(knx_json, "oamInstanceId",
|
||||
static_cast<double>(kKnxOamInstanceId));
|
||||
}
|
||||
cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "started", knx_started);
|
||||
@@ -2457,14 +2499,19 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddStringToObject(security_json, "storage", "none");
|
||||
#endif
|
||||
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||
const auto fdsk_info = openknx::LoadFactoryFdskInfoForInstance(instance_id);
|
||||
const bool oam_instance = IsKnxOamInstanceId(instance_id);
|
||||
const auto fdsk_info = oam_instance
|
||||
? openknx::LoadOamFactoryFdskInfo()
|
||||
: openknx::LoadFactoryFdskInfoForInstance(instance_id);
|
||||
cJSON* fdsk_json = FactoryFdskInfoToCjson(fdsk_info, true);
|
||||
if (fdsk_json != nullptr) {
|
||||
cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json);
|
||||
}
|
||||
cJSON* certificate_json =
|
||||
FactoryCertificateToCjson(
|
||||
openknx::BuildFactoryCertificatePayloadForInstance(instance_id), false);
|
||||
oam_instance ? openknx::BuildOamFactoryCertificatePayload()
|
||||
: openknx::BuildFactoryCertificatePayloadForInstance(instance_id),
|
||||
false);
|
||||
if (certificate_json != nullptr) {
|
||||
cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json);
|
||||
}
|
||||
@@ -3043,7 +3090,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
const auto state = cache.daliAddressState(channel.gateway_id,
|
||||
const auto state = dali_cache.addressState(channel.gateway_id,
|
||||
static_cast<uint8_t>(point.short_address));
|
||||
switch (point.generated_kind) {
|
||||
case GatewayModbusGeneratedKind::kShortDiscovered:
|
||||
@@ -3061,11 +3108,11 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
case GatewayModbusGeneratedKind::kShortSupportsDt8:
|
||||
return discovery != nullptr && SnapshotHasDeviceType(discovery->discovery, 8);
|
||||
case GatewayModbusGeneratedKind::kShortGroupMaskKnown:
|
||||
return state.group_mask_known;
|
||||
return state.groupMaskKnown;
|
||||
case GatewayModbusGeneratedKind::kShortActualLevelKnown:
|
||||
return state.status.actual_level.has_value();
|
||||
return state.status.actualLevel.has_value();
|
||||
case GatewayModbusGeneratedKind::kShortSceneKnown:
|
||||
return state.status.scene_id.has_value();
|
||||
return state.status.sceneID.has_value();
|
||||
case GatewayModbusGeneratedKind::kShortSettingsKnown:
|
||||
return state.settings.anyKnown();
|
||||
case GatewayModbusGeneratedKind::kShortControlGearPresent:
|
||||
@@ -3097,7 +3144,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
|
||||
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
|
||||
const auto state = cache.daliAddressState(channel.gateway_id,
|
||||
const auto state = dali_cache.addressState(channel.gateway_id,
|
||||
static_cast<uint8_t>(point.short_address));
|
||||
switch (point.generated_kind) {
|
||||
case GatewayModbusGeneratedKind::kShortInventoryState:
|
||||
@@ -3118,12 +3165,12 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return discovery == nullptr ? kModbusUnknownRegister : DeviceTypeMask(discovery->discovery);
|
||||
case GatewayModbusGeneratedKind::kShortBrightness:
|
||||
case GatewayModbusGeneratedKind::kShortActualLevel:
|
||||
return state.status.actual_level.has_value()
|
||||
? static_cast<uint16_t>(state.status.actual_level.value())
|
||||
return state.status.actualLevel.has_value()
|
||||
? static_cast<uint16_t>(state.status.actualLevel.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortSceneId:
|
||||
return state.status.scene_id.has_value()
|
||||
? static_cast<uint16_t>(state.status.scene_id.value())
|
||||
return state.status.sceneID.has_value()
|
||||
? static_cast<uint16_t>(state.status.sceneID.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortRawStatus: {
|
||||
const auto snapshot = diagnosticSnapshotLocked(point.short_address, "base_status");
|
||||
@@ -3135,30 +3182,30 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return kModbusUnknownRegister;
|
||||
}
|
||||
case GatewayModbusGeneratedKind::kShortGroupMask:
|
||||
return state.group_mask_known ? state.group_mask : kModbusUnknownRegister;
|
||||
return state.groupMaskKnown ? state.groupMask : kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
|
||||
return state.settings.power_on_level.has_value()
|
||||
? static_cast<uint16_t>(state.settings.power_on_level.value())
|
||||
return state.settings.powerOnLevel.has_value()
|
||||
? static_cast<uint16_t>(state.settings.powerOnLevel.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortSystemFailureLevel:
|
||||
return state.settings.system_failure_level.has_value()
|
||||
? static_cast<uint16_t>(state.settings.system_failure_level.value())
|
||||
return state.settings.systemFailureLevel.has_value()
|
||||
? static_cast<uint16_t>(state.settings.systemFailureLevel.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortMinLevel:
|
||||
return state.settings.min_level.has_value()
|
||||
? static_cast<uint16_t>(state.settings.min_level.value())
|
||||
return state.settings.minLevel.has_value()
|
||||
? static_cast<uint16_t>(state.settings.minLevel.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortMaxLevel:
|
||||
return state.settings.max_level.has_value()
|
||||
? static_cast<uint16_t>(state.settings.max_level.value())
|
||||
return state.settings.maxLevel.has_value()
|
||||
? static_cast<uint16_t>(state.settings.maxLevel.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortFadeTime:
|
||||
return state.settings.fade_time.has_value()
|
||||
? static_cast<uint16_t>(state.settings.fade_time.value())
|
||||
return state.settings.fadeTime.has_value()
|
||||
? static_cast<uint16_t>(state.settings.fadeTime.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortFadeRate:
|
||||
return state.settings.fade_rate.has_value()
|
||||
? static_cast<uint16_t>(state.settings.fade_rate.value())
|
||||
return state.settings.fadeRate.has_value()
|
||||
? static_cast<uint16_t>(state.settings.fadeRate.value())
|
||||
: kModbusUnknownRegister;
|
||||
case GatewayModbusGeneratedKind::kShortColorTemperature:
|
||||
return kModbusUnknownRegister;
|
||||
@@ -3199,7 +3246,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return false;
|
||||
}
|
||||
if (sent) {
|
||||
cache.mirrorDaliCommand(channel.gateway_id, raw_command_address, mirrored_command);
|
||||
dali_cache.mirrorForwardFrame(channel.gateway_id, raw_command_address, mirrored_command);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
@@ -3217,7 +3264,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
domain.markHostCommandFrame(channel.gateway_id, RawArcAddressFromDec(point.short_address),
|
||||
static_cast<uint8_t>(value));
|
||||
if (domain.setBright(channel.gateway_id, point.short_address, value)) {
|
||||
cache.mirrorDaliCommand(channel.gateway_id, RawArcAddressFromDec(point.short_address),
|
||||
dali_cache.mirrorForwardFrame(channel.gateway_id, RawArcAddressFromDec(point.short_address),
|
||||
static_cast<uint8_t>(value));
|
||||
return true;
|
||||
}
|
||||
@@ -3228,7 +3275,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
case GatewayModbusGeneratedKind::kShortGroupMask:
|
||||
domain.markHostActivity(channel.gateway_id);
|
||||
if (domain.applyGroupMask(channel.gateway_id, point.short_address, value)) {
|
||||
cache.setDaliGroupMask(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
||||
dali_cache.setGroupMask(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
||||
value);
|
||||
return true;
|
||||
}
|
||||
@@ -3242,40 +3289,40 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (value > 255) {
|
||||
return false;
|
||||
}
|
||||
auto current = cache.daliAddressState(channel.gateway_id,
|
||||
auto current = dali_cache.addressState(channel.gateway_id,
|
||||
static_cast<uint8_t>(point.short_address)).settings;
|
||||
switch (point.generated_kind) {
|
||||
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
|
||||
current.power_on_level = static_cast<uint8_t>(value);
|
||||
current.powerOnLevel = static_cast<uint8_t>(value);
|
||||
break;
|
||||
case GatewayModbusGeneratedKind::kShortSystemFailureLevel:
|
||||
current.system_failure_level = static_cast<uint8_t>(value);
|
||||
current.systemFailureLevel = static_cast<uint8_t>(value);
|
||||
break;
|
||||
case GatewayModbusGeneratedKind::kShortMinLevel:
|
||||
current.min_level = static_cast<uint8_t>(value);
|
||||
current.minLevel = static_cast<uint8_t>(value);
|
||||
break;
|
||||
case GatewayModbusGeneratedKind::kShortMaxLevel:
|
||||
current.max_level = static_cast<uint8_t>(value);
|
||||
current.maxLevel = static_cast<uint8_t>(value);
|
||||
break;
|
||||
case GatewayModbusGeneratedKind::kShortFadeTime:
|
||||
current.fade_time = static_cast<uint8_t>(value);
|
||||
current.fadeTime = static_cast<uint8_t>(value);
|
||||
break;
|
||||
case GatewayModbusGeneratedKind::kShortFadeRate:
|
||||
current.fade_rate = static_cast<uint8_t>(value);
|
||||
current.fadeRate = static_cast<uint8_t>(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
DaliAddressSettingsSnapshot domain_settings;
|
||||
domain_settings.power_on_level = current.power_on_level;
|
||||
domain_settings.system_failure_level = current.system_failure_level;
|
||||
domain_settings.min_level = current.min_level;
|
||||
domain_settings.max_level = current.max_level;
|
||||
domain_settings.fade_time = current.fade_time;
|
||||
domain_settings.fade_rate = current.fade_rate;
|
||||
domain_settings.power_on_level = current.powerOnLevel;
|
||||
domain_settings.system_failure_level = current.systemFailureLevel;
|
||||
domain_settings.min_level = current.minLevel;
|
||||
domain_settings.max_level = current.maxLevel;
|
||||
domain_settings.fade_time = current.fadeTime;
|
||||
domain_settings.fade_rate = current.fadeRate;
|
||||
domain.markHostActivity(channel.gateway_id);
|
||||
if (domain.applyAddressSettings(channel.gateway_id, point.short_address, domain_settings)) {
|
||||
cache.setDaliSettings(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
||||
dali_cache.setSettings(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
||||
current);
|
||||
return true;
|
||||
}
|
||||
@@ -4220,7 +4267,9 @@ GatewayBridgeService::GatewayBridgeService(DaliDomainService& dali_domain,
|
||||
GatewayBridgeServiceConfig config)
|
||||
: dali_domain_(dali_domain), cache_(cache), config_(config) {}
|
||||
|
||||
GatewayBridgeService::~GatewayBridgeService() = default;
|
||||
GatewayBridgeService::~GatewayBridgeService() {
|
||||
dali_domain_.gatewayCache().setStatusUpdateCallback({});
|
||||
}
|
||||
|
||||
esp_err_t GatewayBridgeService::start() {
|
||||
ConfigureDaliCppLogging();
|
||||
@@ -4245,8 +4294,8 @@ esp_err_t GatewayBridgeService::start() {
|
||||
runtimes_.push_back(std::move(runtime));
|
||||
}
|
||||
|
||||
dali_domain_.addRawFrameSink(
|
||||
[this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
|
||||
dali_domain_.gatewayCache().setStatusUpdateCallback(
|
||||
[this](const DaliGatewayStatusUpdate& update) { handleDaliStatusUpdate(update); });
|
||||
|
||||
std::set<int> used_serial_uarts;
|
||||
if (config_.modbus_enabled && config_.modbus_startup_enabled) {
|
||||
@@ -4309,6 +4358,24 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GatewayBridgeService::handleGatewayNameChanged(uint8_t gateway_id) {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
for (const auto& runtime : runtimes_) {
|
||||
if (gateway_id != 0xff && runtime->channel.gateway_id != gateway_id) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t runtime_gateway = runtime->channel.gateway_id;
|
||||
const auto channel =
|
||||
std::find_if(channels.begin(), channels.end(), [runtime_gateway](
|
||||
const DaliChannelInfo& item) {
|
||||
return item.gateway_id == runtime_gateway;
|
||||
});
|
||||
if (channel != channels.end()) {
|
||||
runtime->updateChannelInfo(*channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GatewayBridgeService::ChannelRuntime* GatewayBridgeService::selectKnxEndpointRuntime() {
|
||||
auto eligible = [](ChannelRuntime* runtime) {
|
||||
if (runtime == nullptr) {
|
||||
@@ -4467,14 +4534,13 @@ DaliBridgeResult GatewayBridgeService::routeKnxGroupObjectWrite(uint16_t group_o
|
||||
return runtime->knx->handleGroupObjectWrite(group_object_number, data, len);
|
||||
}
|
||||
|
||||
void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
const auto update = DecodeDaliKnxStatusUpdate(frame);
|
||||
if (!update.has_value()) {
|
||||
void GatewayBridgeService::handleDaliStatusUpdate(const DaliGatewayStatusUpdate& update) {
|
||||
if (!update.status.actualLevel.has_value()) {
|
||||
return;
|
||||
}
|
||||
auto* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
|
||||
: selectKnxEndpointRuntime();
|
||||
if (owner == nullptr || owner->channel.gateway_id != frame.gateway_id) {
|
||||
if (owner == nullptr || owner->channel.gateway_id != update.channel) {
|
||||
return;
|
||||
}
|
||||
LockGuard guard(owner->lock);
|
||||
@@ -4484,32 +4550,29 @@ void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
|
||||
auto publish_target = [&](GatewayKnxDaliTargetKind kind, int address) {
|
||||
owner->knx_router->publishDaliStatus(GatewayKnxDaliTarget{kind, address},
|
||||
update->actual_level);
|
||||
*update.status.actualLevel);
|
||||
};
|
||||
|
||||
publish_target(update->target.kind, update->target.address);
|
||||
GatewayKnxDaliTargetKind target_kind = GatewayKnxDaliTargetKind::kShortAddress;
|
||||
int target_address = update.target.value;
|
||||
if (update.target.kind == DaliGatewayTargetKind::group) {
|
||||
target_kind = GatewayKnxDaliTargetKind::kGroup;
|
||||
} else if (update.target.kind == DaliGatewayTargetKind::broadcast) {
|
||||
target_kind = GatewayKnxDaliTargetKind::kBroadcast;
|
||||
target_address = 127;
|
||||
}
|
||||
publish_target(target_kind, target_address);
|
||||
|
||||
if (update->target.kind == GatewayKnxDaliTargetKind::kGroup &&
|
||||
update->target.address >= 0 && update->target.address < 16) {
|
||||
const uint16_t group_bit = static_cast<uint16_t>(1U << update->target.address);
|
||||
for (int short_address = 0; short_address <= kMaxDaliShortAddress; ++short_address) {
|
||||
const auto state = owner->cache.daliAddressState(frame.gateway_id,
|
||||
static_cast<uint8_t>(short_address));
|
||||
if (!state.group_mask_known || (state.group_mask & group_bit) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (update.target.kind != DaliGatewayTargetKind::shortAddress) {
|
||||
for (const uint8_t short_address : update.affectedShortAddresses) {
|
||||
publish_target(GatewayKnxDaliTargetKind::kShortAddress, short_address);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (update->target.kind == GatewayKnxDaliTargetKind::kBroadcast) {
|
||||
if (update.target.kind == DaliGatewayTargetKind::broadcast) {
|
||||
for (int group = 0; group < 16; ++group) {
|
||||
publish_target(GatewayKnxDaliTargetKind::kGroup, group);
|
||||
}
|
||||
for (int short_address = 0; short_address <= kMaxDaliShortAddress; ++short_address) {
|
||||
publish_target(GatewayKnxDaliTargetKind::kShortAddress, short_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4567,6 +4630,13 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
|
||||
action = "status";
|
||||
}
|
||||
|
||||
if (action == "gateway_settings") {
|
||||
if (!config_.gateway_settings_getter) {
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED, "gateway settings are not available");
|
||||
}
|
||||
return config_.gateway_settings_getter();
|
||||
}
|
||||
|
||||
if (action == "status" && !gateway_id.has_value()) {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "enabled", true);
|
||||
@@ -4738,6 +4808,12 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
if (action.empty()) {
|
||||
action = "execute";
|
||||
}
|
||||
if (action == "gateway_settings") {
|
||||
if (!config_.gateway_settings_setter) {
|
||||
return ErrorResponse(ESP_ERR_NOT_SUPPORTED, "gateway settings are not available");
|
||||
}
|
||||
return config_.gateway_settings_setter(body);
|
||||
}
|
||||
if (!gateway_id.has_value()) {
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, "gateway id is required");
|
||||
}
|
||||
@@ -4963,6 +5039,7 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
return ErrorResponse(ESP_ERR_INVALID_ARG, message.c_str());
|
||||
}
|
||||
|
||||
const bool reset_oam = IsKnxOamInstanceId(instance_id.value());
|
||||
const bool reset_runtime = instance_id.value() == 0;
|
||||
const bool restart_router = reset_runtime &&
|
||||
(runtime->knx_started ||
|
||||
@@ -4975,7 +5052,9 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
}
|
||||
|
||||
std::string nvs_namespace = runtime->openKnxNamespace();
|
||||
if (instance_id.value() != 0) {
|
||||
if (reset_oam) {
|
||||
nvs_namespace = kGatewayKnxOamOpenKnxNamespace;
|
||||
} else if (instance_id.value() != 0) {
|
||||
nvs_namespace += "_" + std::to_string(instance_id.value());
|
||||
}
|
||||
if (!EraseOpenKnxRuntimeData(nvs_namespace)) {
|
||||
@@ -4983,7 +5062,11 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
}
|
||||
|
||||
openknx::FactoryFdskInfo info;
|
||||
if (!openknx::ResetFactorySecurityForInstance(instance_id.value(), &info)) {
|
||||
const bool security_reset = reset_oam
|
||||
? openknx::ResetOamFactoryFdskCache(&info)
|
||||
: openknx::ResetFactorySecurityForInstance(instance_id.value(),
|
||||
&info);
|
||||
if (!security_reset) {
|
||||
return ErrorResponse(ESP_FAIL, "failed to restore KNX factory security key");
|
||||
}
|
||||
|
||||
|
||||
@@ -489,6 +489,16 @@ bool LoadFactoryFdsk(uint8_t* data, size_t len) {
|
||||
return LoadFactoryFdskForContext(kReg1Context, data, len);
|
||||
}
|
||||
|
||||
extern "C" bool knx_platform_get_fdsk_for_namespace(const char* nvs_namespace,
|
||||
uint8_t* data,
|
||||
size_t len) {
|
||||
const std::string namespace_name = nvs_namespace == nullptr ? std::string() : nvs_namespace;
|
||||
if (namespace_name.size() >= 4 && namespace_name.compare(namespace_name.size() - 4, 4, "_oam") == 0) {
|
||||
return LoadFactoryFdskForContext(kOamContext, data, len);
|
||||
}
|
||||
return LoadFactoryFdsk(data, len);
|
||||
}
|
||||
|
||||
FactoryFdskInfo LoadFactoryFdskInfo() {
|
||||
return LoadFactoryFdskInfoForContext(kReg1Context);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_cache.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES freertos log nvs_flash
|
||||
REQUIRES dali_cpp freertos log nvs_flash
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
|
||||
@@ -13,86 +13,16 @@
|
||||
#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
|
||||
namespace gateway {
|
||||
class DaliGatewayCache;
|
||||
|
||||
enum class GatewayCachePriorityMode : uint8_t {
|
||||
kOutsideBusFirst = 0,
|
||||
kLocalGatewayFirst = 1,
|
||||
};
|
||||
namespace gateway {
|
||||
|
||||
struct GatewayCacheConfig {
|
||||
std::string storage_namespace{"gateway_rt"};
|
||||
bool cache_enabled{true};
|
||||
bool reconciliation_enabled{true};
|
||||
bool full_state_mirror_enabled{false};
|
||||
uint32_t flush_interval_ms{10000};
|
||||
uint32_t refresh_interval_ms{120000};
|
||||
uint32_t task_stack_size{4096};
|
||||
UBaseType_t task_priority{3};
|
||||
GatewayCachePriorityMode default_priority_mode{GatewayCachePriorityMode::kOutsideBusFirst};
|
||||
};
|
||||
|
||||
enum class GatewayCacheRawFrameOrigin : uint8_t {
|
||||
kLocalGateway = 0,
|
||||
kOutsideBus = 1,
|
||||
};
|
||||
|
||||
enum class GatewayCacheDaliTargetKind : uint8_t {
|
||||
kShortAddress = 0,
|
||||
kGroup = 1,
|
||||
kBroadcast = 2,
|
||||
};
|
||||
|
||||
enum class GatewayCacheDaliPresence : uint8_t {
|
||||
kUnknown = 0,
|
||||
kOnline = 1,
|
||||
kOffline = 2,
|
||||
};
|
||||
|
||||
struct GatewayCacheDaliTarget {
|
||||
GatewayCacheDaliTargetKind kind{GatewayCacheDaliTargetKind::kShortAddress};
|
||||
uint8_t value{0};
|
||||
};
|
||||
|
||||
struct GatewayCacheChannelFlags {
|
||||
bool need_update_group{false};
|
||||
bool need_update_scene{false};
|
||||
bool need_update_settings{false};
|
||||
};
|
||||
|
||||
struct GatewayCacheDaliSettingsSnapshot {
|
||||
std::optional<uint8_t> power_on_level;
|
||||
std::optional<uint8_t> system_failure_level;
|
||||
std::optional<uint8_t> min_level;
|
||||
std::optional<uint8_t> max_level;
|
||||
std::optional<uint8_t> fade_time;
|
||||
std::optional<uint8_t> fade_rate;
|
||||
|
||||
bool anyKnown() const {
|
||||
return power_on_level.has_value() || system_failure_level.has_value() ||
|
||||
min_level.has_value() || max_level.has_value() || fade_time.has_value() ||
|
||||
fade_rate.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
struct GatewayCacheDaliRuntimeStatus {
|
||||
std::optional<uint8_t> actual_level;
|
||||
std::optional<uint8_t> scene_id;
|
||||
bool use_min_level{false};
|
||||
bool stale{false};
|
||||
uint32_t revision{0};
|
||||
|
||||
bool anyKnown() const {
|
||||
return actual_level.has_value() || scene_id.has_value() || use_min_level;
|
||||
}
|
||||
};
|
||||
|
||||
struct GatewayCacheDaliAddressState {
|
||||
bool group_mask_known{false};
|
||||
uint16_t group_mask{0};
|
||||
std::array<std::optional<uint8_t>, 16> scene_levels{};
|
||||
GatewayCacheDaliSettingsSnapshot settings;
|
||||
GatewayCacheDaliRuntimeStatus status;
|
||||
};
|
||||
|
||||
class GatewayCache {
|
||||
@@ -117,11 +47,10 @@ class GatewayCache {
|
||||
using SceneStore = std::array<SceneEntry, 16>;
|
||||
using GroupStore = std::array<GroupEntry, 16>;
|
||||
|
||||
explicit GatewayCache(GatewayCacheConfig config = {});
|
||||
GatewayCache(DaliGatewayCache& dali_cache, GatewayCacheConfig config = {});
|
||||
~GatewayCache();
|
||||
|
||||
esp_err_t start();
|
||||
void preloadChannel(uint8_t gateway_id);
|
||||
|
||||
SceneStore scenes(uint8_t gateway_id);
|
||||
GroupStore groups(uint8_t gateway_id);
|
||||
@@ -142,47 +71,10 @@ class GatewayCache {
|
||||
bool deleteGroup(uint8_t gateway_id, uint8_t group_id);
|
||||
std::pair<uint8_t, uint8_t> groupMask(uint8_t gateway_id);
|
||||
|
||||
GatewayCacheChannelFlags channelFlags(uint8_t gateway_id);
|
||||
GatewayCacheChannelFlags pendingChannelFlags(uint8_t gateway_id);
|
||||
GatewayCacheDaliAddressState daliAddressState(uint8_t gateway_id, uint8_t short_address);
|
||||
GatewayCacheDaliPresence daliAddressPresence(uint8_t gateway_id, uint8_t short_address);
|
||||
void markDaliAddressPresence(uint8_t gateway_id, uint8_t short_address,
|
||||
GatewayCacheDaliPresence presence);
|
||||
std::optional<GatewayCacheDaliTarget> decodeDaliTarget(uint8_t raw_addr);
|
||||
std::vector<uint8_t> reconciliationAddresses(
|
||||
uint8_t gateway_id, std::optional<GatewayCacheDaliTarget> target);
|
||||
GatewayCacheDaliRuntimeStatus daliGroupStatus(uint8_t gateway_id, uint8_t group_id);
|
||||
GatewayCacheDaliRuntimeStatus daliBroadcastStatus(uint8_t gateway_id);
|
||||
bool setDaliGroupMask(uint8_t gateway_id, uint8_t short_address,
|
||||
std::optional<uint16_t> group_mask);
|
||||
bool setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id,
|
||||
std::optional<uint8_t> level);
|
||||
bool setDaliSettings(uint8_t gateway_id, uint8_t short_address,
|
||||
std::optional<GatewayCacheDaliSettingsSnapshot> settings);
|
||||
bool setDaliActualLevel(uint8_t gateway_id, uint8_t short_address,
|
||||
std::optional<uint8_t> level);
|
||||
bool clearChannelFlagsIfMatched(uint8_t gateway_id, const GatewayCacheChannelFlags& flags);
|
||||
void markGroupUpdateNeeded(uint8_t gateway_id, bool needed = true);
|
||||
void markSceneUpdateNeeded(uint8_t gateway_id, bool needed = true);
|
||||
void markSettingsUpdateNeeded(uint8_t gateway_id, bool needed = true);
|
||||
|
||||
bool cacheEnabled() const;
|
||||
bool reconciliationEnabled() const;
|
||||
bool fullStateMirrorEnabled() const;
|
||||
bool mirrorDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
|
||||
bool observeDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command,
|
||||
GatewayCacheRawFrameOrigin origin);
|
||||
|
||||
GatewayCachePriorityMode priorityMode();
|
||||
void setPriorityMode(GatewayCachePriorityMode mode);
|
||||
bool setCacheEnabled(bool enabled);
|
||||
|
||||
private:
|
||||
struct DtrState {
|
||||
std::optional<uint8_t> dtr0;
|
||||
std::optional<uint8_t> dtr1;
|
||||
std::optional<uint8_t> dtr2;
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
bool flushDirty();
|
||||
@@ -191,62 +83,23 @@ class GatewayCache {
|
||||
void closeStorageLocked();
|
||||
bool persistSceneLocked(uint8_t gateway_id, uint8_t scene_id, const SceneEntry& scene);
|
||||
bool persistGroupLocked(uint8_t gateway_id, uint8_t group_id, const GroupEntry& group);
|
||||
bool persistDaliAddressStateLocked(uint8_t gateway_id, uint8_t short_address,
|
||||
const GatewayCacheDaliAddressState& state);
|
||||
bool commitStorageLocked();
|
||||
bool shouldTrackUpdateFlagsLocked() const;
|
||||
uint32_t nextDaliRuntimeRevisionLocked();
|
||||
void markDaliAddressPresenceLocked(uint8_t gateway_id, uint8_t short_address,
|
||||
GatewayCacheDaliPresence presence);
|
||||
bool mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
|
||||
void clearDaliTargetStateLocked(uint8_t gateway_id, const GatewayCacheDaliTarget& target,
|
||||
uint32_t revision);
|
||||
void applyDaliTargetRuntimeStatusLocked(uint8_t gateway_id,
|
||||
const GatewayCacheDaliTarget& target,
|
||||
const GatewayCacheDaliRuntimeStatus& status);
|
||||
void applyDaliRuntimeStatusToAddressLocked(GatewayCacheDaliAddressState& state,
|
||||
const GatewayCacheDaliRuntimeStatus& status);
|
||||
void applyDaliTargetGroupMutationLocked(uint8_t gateway_id,
|
||||
const GatewayCacheDaliTarget& target, uint8_t group_id,
|
||||
bool add_to_group);
|
||||
void applyDaliTargetSceneLevelLocked(uint8_t gateway_id,
|
||||
const GatewayCacheDaliTarget& target, uint8_t scene_id,
|
||||
std::optional<uint8_t> level);
|
||||
void applyDaliTargetSettingsLocked(uint8_t gateway_id,
|
||||
const GatewayCacheDaliTarget& target, uint8_t command,
|
||||
uint8_t value);
|
||||
void refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id,
|
||||
GatewayCacheDaliAddressState& state);
|
||||
GatewayCacheDaliAddressState& ensureDaliAddressStateLocked(uint8_t gateway_id,
|
||||
uint8_t short_address);
|
||||
GatewayCacheDaliRuntimeStatus& ensureDaliGroupStatusLocked(uint8_t gateway_id,
|
||||
uint8_t group_id);
|
||||
GatewayCacheDaliRuntimeStatus& ensureDaliBroadcastStatusLocked(uint8_t gateway_id);
|
||||
SceneStore& ensureSceneStoreLocked(uint8_t gateway_id);
|
||||
GroupStore& ensureGroupStoreLocked(uint8_t gateway_id);
|
||||
void loadSceneStoreLocked(uint8_t gateway_id, SceneStore& scenes);
|
||||
void loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups);
|
||||
void loadDaliStateStoreLocked(uint8_t gateway_id,
|
||||
std::array<GatewayCacheDaliAddressState, 64>& states);
|
||||
std::string readStringLocked(std::string_view key);
|
||||
bool writeStringLocked(std::string_view key, std::string_view value);
|
||||
bool eraseKeyLocked(std::string_view key);
|
||||
|
||||
DaliGatewayCache& dali_cache_;
|
||||
GatewayCacheConfig config_;
|
||||
GatewayCachePriorityMode priority_mode_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
SemaphoreHandle_t lock_{nullptr};
|
||||
nvs_handle_t storage_{0};
|
||||
std::map<uint8_t, SceneStore> scenes_;
|
||||
std::map<uint8_t, GroupStore> groups_;
|
||||
std::map<uint8_t, std::array<GatewayCacheDaliAddressState, 64>> dali_states_;
|
||||
std::map<uint8_t, std::array<GatewayCacheDaliPresence, 64>> dali_presence_;
|
||||
std::map<uint8_t, std::array<GatewayCacheDaliRuntimeStatus, 16>> dali_group_status_;
|
||||
std::map<uint8_t, GatewayCacheDaliRuntimeStatus> dali_broadcast_status_;
|
||||
std::map<uint8_t, DtrState> dtr_states_;
|
||||
std::map<uint8_t, GatewayCacheChannelFlags> channel_flags_;
|
||||
uint32_t dali_runtime_revision_{0};
|
||||
bool dirty_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "dali_gateway.hpp"
|
||||
#include "gateway_cache.hpp"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@@ -44,6 +45,7 @@ struct GatewayControllerConfig {
|
||||
struct GatewayChannelSnapshot {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
uint8_t gateway_group{0};
|
||||
std::string name;
|
||||
std::string phy;
|
||||
uint8_t scene_mask_low{0};
|
||||
@@ -52,6 +54,13 @@ struct GatewayChannelSnapshot {
|
||||
uint8_t group_mask_high{0};
|
||||
bool allocating{false};
|
||||
int last_alloc_addr{0};
|
||||
bool operation_active{false};
|
||||
uint8_t operation_request_id{0};
|
||||
uint16_t operation_id{0};
|
||||
uint8_t operation_status{0};
|
||||
uint8_t operation_progress{0};
|
||||
uint8_t operation_target{0};
|
||||
uint8_t operation_count{0};
|
||||
};
|
||||
|
||||
struct GatewayControllerSnapshot {
|
||||
@@ -65,6 +74,19 @@ struct GatewayControllerSnapshot {
|
||||
std::vector<GatewayChannelSnapshot> channels;
|
||||
};
|
||||
|
||||
enum class GatewayCommandTransactionStatus : uint8_t {
|
||||
kOk = 0,
|
||||
kInvalidFrame = 1,
|
||||
kQueueRejected = 2,
|
||||
kTimeout = 3,
|
||||
kNoResponse = 4,
|
||||
};
|
||||
|
||||
struct GatewayCommandTransactionResult {
|
||||
GatewayCommandTransactionStatus status{GatewayCommandTransactionStatus::kNoResponse};
|
||||
std::vector<uint8_t> frames;
|
||||
};
|
||||
|
||||
class GatewayController {
|
||||
public:
|
||||
using NotificationSink = std::function<void(const std::vector<uint8_t>& frame)>;
|
||||
@@ -79,6 +101,12 @@ class GatewayController {
|
||||
|
||||
esp_err_t start();
|
||||
bool enqueueCommandFrame(const std::vector<uint8_t>& frame);
|
||||
GatewayCommandTransactionResult transactCommandFrame(
|
||||
const std::vector<uint8_t>& frame,
|
||||
uint32_t timeout_ms = 700,
|
||||
uint32_t idle_ms = 30,
|
||||
size_t max_response_bytes = 2048,
|
||||
bool wait_for_completion = false);
|
||||
void addNotificationSink(NotificationSink sink);
|
||||
void addBleStateSink(BleStateSink sink);
|
||||
void addWifiStateSink(WifiStateSink sink);
|
||||
@@ -89,9 +117,28 @@ class GatewayController {
|
||||
bool wirelessSetupMode() const;
|
||||
void setWirelessSetupMode(bool enabled);
|
||||
bool bleEnabled() const;
|
||||
bool setBleEnabled(bool enabled);
|
||||
bool cacheEnabled() const;
|
||||
bool setCacheEnabled(bool enabled);
|
||||
bool wifiEnabled() const;
|
||||
bool ipRouterEnabled() const;
|
||||
bool rawReportingEnabled(uint8_t gateway_id) const;
|
||||
bool setDeviceName(std::string_view name);
|
||||
GatewayControllerSnapshot snapshot();
|
||||
std::vector<uint8_t> gatewayIdsForControlTarget(uint8_t target) const;
|
||||
|
||||
/// Runtime configuration helpers used by the local/cloud gateway-settings
|
||||
/// surface. They persist the same values as the compatible raw commands.
|
||||
bool setChannelGatewayId(uint8_t channel_index, uint8_t gateway_id);
|
||||
bool setChannelGatewayGroup(uint8_t channel_index, uint8_t gateway_group);
|
||||
bool setChannelName(uint8_t channel_index, std::string_view name);
|
||||
|
||||
struct ParsedTlv {
|
||||
uint8_t type{0};
|
||||
std::vector<uint8_t> value;
|
||||
};
|
||||
|
||||
using ParsedTlvMap = std::map<uint8_t, ParsedTlv>;
|
||||
|
||||
private:
|
||||
struct ReconciliationJob {
|
||||
@@ -102,9 +149,9 @@ class GatewayController {
|
||||
kSettings = 3,
|
||||
};
|
||||
|
||||
GatewayCacheChannelFlags flags{};
|
||||
DaliGatewayChannelFlags flags{};
|
||||
Phase phase{Phase::kReloadFlags};
|
||||
std::optional<GatewayCacheDaliTarget> target;
|
||||
std::optional<DaliGatewayTarget> target;
|
||||
std::vector<uint8_t> addresses;
|
||||
size_t address_index{0};
|
||||
uint8_t scene_id{0};
|
||||
@@ -122,11 +169,49 @@ class GatewayController {
|
||||
uint8_t short_address{0};
|
||||
};
|
||||
|
||||
struct GatewayOperationRuntimeState {
|
||||
bool active{false};
|
||||
bool cancel_requested{false};
|
||||
uint8_t request_id{0};
|
||||
uint16_t operation_id{0};
|
||||
uint8_t status{0};
|
||||
uint8_t progress{0};
|
||||
uint8_t target{0};
|
||||
uint8_t count{0};
|
||||
};
|
||||
|
||||
struct GatewayRawReportLease {
|
||||
bool enabled{false};
|
||||
TickType_t expires_at{0};
|
||||
};
|
||||
|
||||
struct GatewayOperationTaskContext {
|
||||
GatewayController* controller{nullptr};
|
||||
uint8_t gateway_id{0};
|
||||
uint8_t request_id{0};
|
||||
uint16_t operation_id{0};
|
||||
ParsedTlvMap fields;
|
||||
};
|
||||
|
||||
struct TransactionWaiter {
|
||||
uint8_t gateway_id{0};
|
||||
uint8_t opcode{0};
|
||||
SemaphoreHandle_t signal{nullptr};
|
||||
std::vector<uint8_t> command;
|
||||
std::vector<uint8_t> frames;
|
||||
size_t max_response_bytes{0};
|
||||
bool wait_for_completion{false};
|
||||
bool completed{false};
|
||||
bool overflow{false};
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
static void OperationTaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void runOperationTask(GatewayOperationTaskContext* context);
|
||||
void dispatchCommand(const std::vector<uint8_t>& command);
|
||||
void scheduleReconciliation(uint8_t gateway_id,
|
||||
std::optional<GatewayCacheDaliTarget> target = std::nullopt);
|
||||
std::optional<DaliGatewayTarget> target = std::nullopt);
|
||||
bool hasPendingReconciliation() const;
|
||||
bool cacheRefreshEnabled() const;
|
||||
bool cacheMaintenanceSnoozed(uint8_t gateway_id) const;
|
||||
@@ -139,10 +224,24 @@ class GatewayController {
|
||||
|
||||
bool hasGateway(uint8_t gateway_id) const;
|
||||
std::vector<uint8_t> gatewayIds() const;
|
||||
std::optional<uint8_t> gatewayIdForChannelNumber(uint8_t channel_number) const;
|
||||
std::optional<uint8_t> channelNumberForGatewayId(uint8_t gateway_id) const;
|
||||
uint8_t gatewayGroupForGatewayId(uint8_t gateway_id) const;
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
void refreshRuntimeGatewayNames();
|
||||
void publishPayload(uint8_t gateway_id, const std::vector<uint8_t>& payload);
|
||||
void publishFrame(const std::vector<uint8_t>& frame);
|
||||
void publishRawReportLeaseResponse(uint8_t gateway_id, uint8_t status);
|
||||
void publishOperationEvent(uint8_t gateway_id, uint8_t request_id, uint16_t operation_id,
|
||||
uint8_t event, uint8_t status, uint8_t progress,
|
||||
uint8_t target, uint8_t count);
|
||||
void publishOperationResultChunks(uint8_t gateway_id, uint8_t request_id,
|
||||
uint16_t operation_id,
|
||||
const std::vector<uint8_t>& tlv_payload);
|
||||
bool transactionFrameMatches(const TransactionWaiter& waiter,
|
||||
const std::vector<uint8_t>& frame) const;
|
||||
void captureTransactionFrame(const std::vector<uint8_t>& frame);
|
||||
void captureTransactionCompletion(const std::vector<uint8_t>& command);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
|
||||
bool sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
|
||||
@@ -178,8 +277,24 @@ class GatewayController {
|
||||
bool executeGroup(uint8_t gateway_id, uint8_t group_id);
|
||||
|
||||
void handleGatewayNameCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleGatewaySerialCommand(uint8_t channel_number, const std::vector<uint8_t>& command);
|
||||
void handleGatewayGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void publishGatewaySerialReport();
|
||||
void publishGatewaySerialResponse(uint8_t status, uint8_t op,
|
||||
const std::vector<uint8_t>& data);
|
||||
bool gatewaySerialMatches(const std::vector<uint8_t>& command) const;
|
||||
void handleGatewayIdentityCommand(uint8_t gateway_id, uint8_t op);
|
||||
void handleAllocationCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleRawReportLeaseCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleGatewayOperationCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
bool operationActive(uint8_t gateway_id) const;
|
||||
bool operationCancelRequested(uint8_t gateway_id, uint8_t request_id) const;
|
||||
bool startOperation(uint8_t gateway_id, uint8_t request_id, uint16_t operation_id,
|
||||
ParsedTlvMap fields);
|
||||
void abortOperation(uint8_t gateway_id, uint8_t request_id);
|
||||
void finishOperation(uint8_t gateway_id, uint8_t request_id, uint8_t status,
|
||||
uint8_t progress, uint8_t target, uint8_t count);
|
||||
ParsedTlvMap parseOperationTlvs(const uint8_t* data, size_t len, bool* ok) const;
|
||||
void handleInternalSceneCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleInternalGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleGatewayCacheCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
@@ -189,18 +304,24 @@ class GatewayController {
|
||||
|
||||
GatewayRuntime& runtime_;
|
||||
DaliDomainService& dali_domain_;
|
||||
DaliGatewayCache& dali_cache_;
|
||||
GatewayCache& cache_;
|
||||
GatewayBridgeService* bridge_service_{nullptr};
|
||||
GatewayControllerConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
SemaphoreHandle_t maintenance_lock_{nullptr};
|
||||
SemaphoreHandle_t transaction_lock_{nullptr};
|
||||
SemaphoreHandle_t operation_lock_{nullptr};
|
||||
std::vector<NotificationSink> notification_sinks_;
|
||||
std::vector<BleStateSink> ble_state_sinks_;
|
||||
std::vector<WifiStateSink> wifi_state_sinks_;
|
||||
std::vector<GatewayNameSink> gateway_name_sinks_;
|
||||
std::map<uint16_t, BridgeTransportRequestState> bridge_transport_requests_;
|
||||
std::vector<TransactionWaiter*> transaction_waiters_;
|
||||
std::map<uint8_t, ReconciliationJob> reconciliation_jobs_;
|
||||
std::map<uint8_t, CacheRefreshJob> cache_refresh_jobs_;
|
||||
std::map<uint8_t, GatewayOperationRuntimeState> operation_states_;
|
||||
std::map<uint8_t, GatewayRawReportLease> raw_report_leases_;
|
||||
std::atomic<int> maintenance_activity_gateway_{-1};
|
||||
bool setup_mode_{false};
|
||||
bool wireless_setup_mode_{false};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ class EtsDeviceRuntime {
|
||||
public:
|
||||
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
||||
using CemiFrameReceiver = std::function<bool(const uint8_t* data, size_t len)>;
|
||||
using TpAckHandler = std::function<TPAckType(uint16_t destination, bool is_group_address)>;
|
||||
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
|
||||
size_t len)>;
|
||||
using GroupObjectWriteHandler = std::function<void(uint16_t group_object_number,
|
||||
@@ -64,6 +65,7 @@ class EtsDeviceRuntime {
|
||||
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
|
||||
void setBusFrameSender(CemiFrameSender sender);
|
||||
void setTpFrameReceiver(CemiFrameReceiver receiver);
|
||||
void setTpAckHandler(TpAckHandler handler);
|
||||
void setNetworkInterface(esp_netif_t* netif);
|
||||
bool hasTpUart() const;
|
||||
bool enableTpUart(bool enabled = true);
|
||||
@@ -124,6 +126,7 @@ class EtsDeviceRuntime {
|
||||
CemiFrameSender sender_;
|
||||
CemiFrameSender bus_frame_sender_;
|
||||
CemiFrameReceiver tp_frame_receiver_;
|
||||
TpAckHandler tp_ack_handler_;
|
||||
GroupWriteHandler group_write_handler_;
|
||||
GroupObjectWriteHandler group_object_write_handler_;
|
||||
FunctionPropertyHandler command_handler_;
|
||||
|
||||
@@ -33,6 +33,7 @@ class TpuartUartInterface;
|
||||
|
||||
constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671;
|
||||
constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12";
|
||||
constexpr const char* kGatewayKnxOamOpenKnxNamespace = "openknx_oam";
|
||||
constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200;
|
||||
constexpr uint32_t kGatewayKnxDefaultTpStartupTimeoutMs = 2000;
|
||||
|
||||
@@ -168,6 +169,32 @@ struct GatewayKnxReg1ScanOptions {
|
||||
bool assign{false};
|
||||
};
|
||||
|
||||
struct GatewayKnxGatewayChannelSnapshot {
|
||||
uint8_t gateway_id{0};
|
||||
};
|
||||
|
||||
struct GatewayKnxGatewaySnapshot {
|
||||
std::vector<GatewayKnxGatewayChannelSnapshot> channels;
|
||||
};
|
||||
|
||||
enum class GatewayKnxGatewayTransactionStatus : uint8_t {
|
||||
kOk = 0,
|
||||
kInvalidFrame = 1,
|
||||
kQueueRejected = 2,
|
||||
kTimeout = 3,
|
||||
kNoResponse = 4,
|
||||
};
|
||||
|
||||
struct GatewayKnxGatewayTransactionResult {
|
||||
GatewayKnxGatewayTransactionStatus status{GatewayKnxGatewayTransactionStatus::kNoResponse};
|
||||
std::vector<uint8_t> frames;
|
||||
};
|
||||
|
||||
using GatewayKnxGatewaySnapshotProvider = std::function<GatewayKnxGatewaySnapshot()>;
|
||||
using GatewayKnxGatewayCommandTransactor =
|
||||
std::function<GatewayKnxGatewayTransactionResult(const std::vector<uint8_t>& frame,
|
||||
uint32_t timeout_ms)>;
|
||||
|
||||
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
||||
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
||||
std::optional<GatewayKnxOamRouterConfig> GatewayKnxOamRouterConfigFromValue(
|
||||
@@ -193,6 +220,8 @@ class GatewayKnxBridge {
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
void setRuntimeContext(const openknx::EtsDeviceRuntime* runtime);
|
||||
void setGatewayControllerCallbacks(GatewayKnxGatewaySnapshotProvider snapshot_provider,
|
||||
GatewayKnxGatewayCommandTransactor command_transactor);
|
||||
const GatewayKnxConfig& config() const;
|
||||
size_t etsBindingCount() const;
|
||||
|
||||
@@ -210,6 +239,13 @@ class GatewayKnxBridge {
|
||||
std::vector<uint8_t>* response);
|
||||
|
||||
private:
|
||||
struct GatewayTransferState {
|
||||
uint16_t total_length{0};
|
||||
uint8_t total_chunks{0};
|
||||
std::map<uint8_t, std::vector<uint8_t>> request_chunks;
|
||||
std::vector<uint8_t> response_bytes;
|
||||
};
|
||||
|
||||
DaliBridgeResult executeForDecodedWrite(uint16_t group_address,
|
||||
GatewayKnxDaliDataType data_type,
|
||||
GatewayKnxDaliTarget target,
|
||||
@@ -243,13 +279,29 @@ class GatewayKnxBridge {
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1FoundEvgsState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleDalimasterDiscoveryState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleDalimasterTransferCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleDalimasterTransferState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool executeDalimasterTransfer(uint16_t transaction_id, uint32_t timeout_ms,
|
||||
std::vector<uint8_t>* response);
|
||||
bool appendDalimasterTransferResponse(uint8_t transfer_status,
|
||||
uint16_t transaction_id,
|
||||
const std::vector<uint8_t>& response_bytes,
|
||||
uint8_t chunk_index,
|
||||
std::vector<uint8_t>* response) const;
|
||||
static void CommissioningScanTaskEntry(void* arg);
|
||||
void runCommissioningScanTask();
|
||||
|
||||
DaliBridgeEngine& engine_;
|
||||
GatewayKnxGatewaySnapshotProvider gateway_snapshot_provider_;
|
||||
GatewayKnxGatewayCommandTransactor gateway_command_transactor_;
|
||||
GatewayKnxConfig config_;
|
||||
const openknx::EtsDeviceRuntime* runtime_{nullptr};
|
||||
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
|
||||
std::map<uint16_t, GatewayTransferState> gateway_transfer_states_;
|
||||
SemaphoreHandle_t commissioning_lock_{nullptr};
|
||||
TaskHandle_t commissioning_scan_task_{nullptr};
|
||||
std::atomic_bool commissioning_scan_cancel_requested_{false};
|
||||
@@ -535,4 +587,4 @@ class GatewayKnxTpIpRouter {
|
||||
std::string last_error_;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -78,7 +78,7 @@ inline constexpr uint32_t kOamRouterSerialMacIncrement = kDaliMaxKnxInstanceCoun
|
||||
inline constexpr uint16_t kOamRouterDeviceDescriptor = 0x091A;
|
||||
inline constexpr uint16_t kOamRouterManufacturerId =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID);
|
||||
inline constexpr uint16_t kOamRouterHardwareId =
|
||||
inline constexpr uint16_t kOamRouterLegacyHardwareId =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID);
|
||||
inline constexpr uint16_t kOamRouterApplicationNumber =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER);
|
||||
@@ -87,8 +87,15 @@ inline constexpr uint8_t kOamRouterApplicationVersion =
|
||||
inline constexpr uint8_t kOamRouterHardwareType[6] = {
|
||||
0x00,
|
||||
0x00,
|
||||
static_cast<uint8_t>((kOamRouterHardwareId >> 8) & 0xff),
|
||||
static_cast<uint8_t>(kOamRouterHardwareId & 0xff),
|
||||
static_cast<uint8_t>((kOamRouterApplicationNumber >> 8) & 0xff),
|
||||
static_cast<uint8_t>(kOamRouterApplicationNumber & 0xff),
|
||||
kOamRouterApplicationVersion,
|
||||
0x00};
|
||||
inline constexpr uint8_t kOamRouterLegacyHardwareType[6] = {
|
||||
0x00,
|
||||
0x00,
|
||||
static_cast<uint8_t>((kOamRouterLegacyHardwareId >> 8) & 0xff),
|
||||
static_cast<uint8_t>(kOamRouterLegacyHardwareId & 0xff),
|
||||
kOamRouterApplicationVersion,
|
||||
0x00};
|
||||
inline constexpr uint8_t kOamRouterOrderNumber[10] = {
|
||||
|
||||
@@ -38,6 +38,8 @@ class OamRouterRuntime {
|
||||
bool programmingMode() const;
|
||||
void setProgrammingMode(bool enabled);
|
||||
void toggleProgrammingMode();
|
||||
bool matchesSecureSyncSerial(CemiFrame& frame) const;
|
||||
bool matchesRecentSecureToolAccess(CemiFrame& frame) const;
|
||||
EtsMemorySnapshot snapshot() const;
|
||||
|
||||
DeviceObject* deviceObject();
|
||||
@@ -60,6 +62,8 @@ class OamRouterRuntime {
|
||||
std::string nvs_namespace_;
|
||||
CemiFrameSender sender_;
|
||||
CemiFrameSender bus_frame_sender_;
|
||||
mutable uint16_t recent_secure_tool_source_{0xffff};
|
||||
mutable int64_t recent_secure_tool_sync_us_{0};
|
||||
#if defined(ENABLE_BAU091A_PERSONA)
|
||||
EspIdfPlatform platform_;
|
||||
Bau091A device_;
|
||||
|
||||
@@ -102,9 +102,6 @@
|
||||
<TranslationElement RefId="%AID%_UP-1100052"><Translation AttributeName="Text" Text="LAN mode" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_UP-1100061"><Translation AttributeName="Text" Text="Time server" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_M-1100001"><Translation AttributeName="Text" Text="Must be no more than 24 characters and consist only of letters, numbers, and hyphens." /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_CH-BASE"><Translation AttributeName="Text" Text="OpenKNX" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_CH-NET"><Translation AttributeName="Text" Text="Network" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_MD-11000100_B-1100000"><Translation AttributeName="Text" Text="Transfer" /></TranslationElement>
|
||||
</TranslationUnit>
|
||||
</Language>
|
||||
<Language Identifier="zh-CN">
|
||||
@@ -158,8 +155,6 @@
|
||||
<TranslationElement RefId="%AID%_UP-1100052"><Translation AttributeName="Text" Text="LAN 模式" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_UP-1100061"><Translation AttributeName="Text" Text="时间服务器" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_M-1100001"><Translation AttributeName="Text" Text="最多 24 个字符,且只能包含字母、数字和连字符。" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_CH-NET"><Translation AttributeName="Text" Text="网络" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_MD-11000100_B-1100000"><Translation AttributeName="Text" Text="传输" /></TranslationElement>
|
||||
</TranslationUnit>
|
||||
</Language>
|
||||
<Language Identifier="de-DE">
|
||||
@@ -215,8 +210,6 @@
|
||||
<TranslationElement RefId="%AID%_UP-1100052"><Translation AttributeName="Text" Text="LAN モード" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_UP-1100061"><Translation AttributeName="Text" Text="時刻サーバー" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_M-1100001"><Translation AttributeName="Text" Text="24 文字以内で、文字、数字、ハイフンのみを使用してください。" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_CH-NET"><Translation AttributeName="Text" Text="ネットワーク" /></TranslationElement>
|
||||
<TranslationElement RefId="%AID%_MD-11000100_B-1100000"><Translation AttributeName="Text" Text="転送" /></TranslationElement>
|
||||
</TranslationUnit>
|
||||
</Language>
|
||||
</Languages>
|
||||
|
||||
@@ -160,10 +160,6 @@
|
||||
<Languages>
|
||||
<Language Identifier="zh-CN">
|
||||
<TranslationUnit RefId="%AID%">
|
||||
<TranslationElement RefId="%AID%_CH-%PREFIX%-Routing">
|
||||
<Translation AttributeName="Name" Text="OAM IP 路由器" />
|
||||
<Translation AttributeName="Text" Text="OAM IP 路由器" />
|
||||
</TranslationElement>
|
||||
<TranslationElement RefId="%AID%_PT-Bool_EN-0">
|
||||
<Translation AttributeName="Text" Text="禁用" />
|
||||
</TranslationElement>
|
||||
@@ -252,10 +248,6 @@
|
||||
</Language>
|
||||
<Language Identifier="de-DE">
|
||||
<TranslationUnit RefId="%AID%">
|
||||
<TranslationElement RefId="%AID%_CH-%PREFIX%-Routing">
|
||||
<Translation AttributeName="Name" Text="OAM IP-Router" />
|
||||
<Translation AttributeName="Text" Text="OAM IP-Router" />
|
||||
</TranslationElement>
|
||||
<TranslationElement RefId="%AID%_PT-Bool_EN-0">
|
||||
<Translation AttributeName="Text" Text="Deaktiviert" />
|
||||
</TranslationElement>
|
||||
@@ -344,10 +336,6 @@
|
||||
</Language>
|
||||
<Language Identifier="ja-JP">
|
||||
<TranslationUnit RefId="%AID%">
|
||||
<TranslationElement RefId="%AID%_CH-%PREFIX%-Routing">
|
||||
<Translation AttributeName="Name" Text="OAM IP ルーター" />
|
||||
<Translation AttributeName="Text" Text="OAM IP ルーター" />
|
||||
</TranslationElement>
|
||||
<TranslationElement RefId="%AID%_PT-Bool_EN-0">
|
||||
<Translation AttributeName="Text" Text="無効" />
|
||||
</TranslationElement>
|
||||
|
||||
@@ -51,6 +51,8 @@ bool IsBroadcastManagementRequest(CemiFrame& frame) {
|
||||
case IndividualAddressRead:
|
||||
case IndividualAddressSerialNumberRead:
|
||||
case IndividualAddressSerialNumberWrite:
|
||||
case FunctionPropertyCommand:
|
||||
case FunctionPropertyState:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -301,6 +303,16 @@ void EtsDeviceRuntime::setTpFrameReceiver(CemiFrameReceiver receiver) {
|
||||
}
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setTpAckHandler(TpAckHandler handler) {
|
||||
tp_ack_handler_ = std::move(handler);
|
||||
if (auto* data_link_layer = device_.getDataLinkLayer()) {
|
||||
data_link_layer->acknowledgeHandler([this](uint16_t destination, bool is_group_address) {
|
||||
return tp_ack_handler_ ? tp_ack_handler_(destination, is_group_address)
|
||||
: TPAckType::AckReqNone;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setNetworkInterface(esp_netif_t* netif) {
|
||||
platform_.networkInterface(netif);
|
||||
}
|
||||
|
||||
@@ -418,6 +418,13 @@ void GatewayKnxBridge::setRuntimeContext(const openknx::EtsDeviceRuntime* runtim
|
||||
runtime_ = runtime;
|
||||
}
|
||||
|
||||
void GatewayKnxBridge::setGatewayControllerCallbacks(
|
||||
GatewayKnxGatewaySnapshotProvider snapshot_provider,
|
||||
GatewayKnxGatewayCommandTransactor command_transactor) {
|
||||
gateway_snapshot_provider_ = std::move(snapshot_provider);
|
||||
gateway_command_transactor_ = std::move(command_transactor);
|
||||
}
|
||||
|
||||
const GatewayKnxConfig& GatewayKnxBridge::config() const { return config_; }
|
||||
|
||||
size_t GatewayKnxBridge::etsBindingCount() const {
|
||||
@@ -615,8 +622,14 @@ DaliBridgeResult GatewayKnxBridge::handleGroupObjectWrite(uint16_t group_object_
|
||||
bool GatewayKnxBridge::handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (object_index != kReg1DaliFunctionObjectIndex || property_id != kReg1DaliFunctionPropertyId ||
|
||||
data == nullptr || len == 0 || response == nullptr) {
|
||||
if (object_index != kReg1DaliFunctionObjectIndex || data == nullptr || len == 0 ||
|
||||
response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (property_id == kDalimasterTransferFunctionPropertyId) {
|
||||
return handleDalimasterTransferCommand(data, len, response);
|
||||
}
|
||||
if (property_id != kReg1DaliFunctionPropertyId) {
|
||||
return false;
|
||||
}
|
||||
switch (data[0]) {
|
||||
@@ -644,8 +657,16 @@ bool GatewayKnxBridge::handleFunctionPropertyCommand(uint8_t object_index, uint8
|
||||
bool GatewayKnxBridge::handleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (object_index != kReg1DaliFunctionObjectIndex || property_id != kReg1DaliFunctionPropertyId ||
|
||||
data == nullptr || len == 0 || response == nullptr) {
|
||||
if (object_index != kReg1DaliFunctionObjectIndex || response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (property_id == kDalimasterDiscoveryFunctionPropertyId) {
|
||||
return handleDalimasterDiscoveryState(data, len, response);
|
||||
}
|
||||
if (property_id == kDalimasterTransferFunctionPropertyId) {
|
||||
return handleDalimasterTransferState(data, len, response);
|
||||
}
|
||||
if (property_id != kReg1DaliFunctionPropertyId || data == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
switch (data[0]) {
|
||||
@@ -661,6 +682,238 @@ bool GatewayKnxBridge::handleFunctionPropertyState(uint8_t object_index, uint8_t
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleDalimasterDiscoveryState(const uint8_t*, size_t,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
response->clear();
|
||||
response->reserve(32);
|
||||
response->push_back(0x00);
|
||||
response->push_back('D');
|
||||
response->push_back('M');
|
||||
response->push_back('K');
|
||||
response->push_back('G');
|
||||
response->push_back(0x01);
|
||||
response->push_back(kDalimasterTransferChunkBytes);
|
||||
uint16_t feature_flags = 0x0100 | 0x0080 | 0x0200;
|
||||
if (config_.tunnel_enabled) {
|
||||
feature_flags |= 0x0001;
|
||||
}
|
||||
if (config_.multicast_enabled) {
|
||||
feature_flags |= 0x0002;
|
||||
}
|
||||
if (config_.oam_router.secure_tunnel_enabled) {
|
||||
feature_flags |= 0x0004;
|
||||
}
|
||||
if (config_.oam_router.secure_routing_enabled) {
|
||||
feature_flags |= 0x0008;
|
||||
}
|
||||
response->push_back(static_cast<uint8_t>(feature_flags & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((feature_flags >> 8) & 0xff));
|
||||
response->push_back(kReg1DaliFunctionObjectIndex);
|
||||
response->push_back(kDalimasterDiscoveryFunctionPropertyId);
|
||||
response->push_back(kDalimasterTransferFunctionPropertyId);
|
||||
const uint16_t individual_address =
|
||||
runtime_ != nullptr ? runtime_->individualAddress() : config_.individual_address;
|
||||
const uint16_t tunnel_address =
|
||||
runtime_ != nullptr ? runtime_->tunnelClientAddress() : config_.ip_interface_individual_address;
|
||||
response->push_back(static_cast<uint8_t>((individual_address >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(individual_address & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((tunnel_address >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(tunnel_address & 0xff));
|
||||
|
||||
const auto snapshot = gateway_snapshot_provider_ ? gateway_snapshot_provider_()
|
||||
: GatewayKnxGatewaySnapshot{};
|
||||
const auto channel_count = std::min<size_t>(snapshot.channels.size(), 32);
|
||||
response->push_back(static_cast<uint8_t>(channel_count));
|
||||
for (size_t index = 0; index < channel_count; ++index) {
|
||||
response->push_back(snapshot.channels[index].gateway_id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleDalimasterTransferCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (data == nullptr || len == 0 || response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto read_le16 = [](const uint8_t* value) -> uint16_t {
|
||||
return static_cast<uint16_t>(value[0] | (static_cast<uint16_t>(value[1]) << 8));
|
||||
};
|
||||
const uint8_t op = data[0];
|
||||
if (op == kDalimasterTransferOpSingle) {
|
||||
if (len < 6) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
const uint16_t transaction_id = read_le16(data + 1);
|
||||
const uint32_t timeout_ms = read_le16(data + 3);
|
||||
gateway_transfer_states_[transaction_id] = GatewayTransferState{};
|
||||
gateway_transfer_states_[transaction_id].request_chunks[0] =
|
||||
std::vector<uint8_t>(data + 5, data + len);
|
||||
gateway_transfer_states_[transaction_id].total_length =
|
||||
static_cast<uint16_t>(len - 5);
|
||||
gateway_transfer_states_[transaction_id].total_chunks = 1;
|
||||
return executeDalimasterTransfer(transaction_id, timeout_ms, response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpBegin) {
|
||||
if (len < 6) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
const uint16_t transaction_id = read_le16(data + 1);
|
||||
GatewayTransferState state;
|
||||
state.total_length = read_le16(data + 3);
|
||||
state.total_chunks = data[5];
|
||||
gateway_transfer_states_[transaction_id] = std::move(state);
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id, {}, 0,
|
||||
response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpChunk) {
|
||||
if (len < 6) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
const uint16_t transaction_id = read_le16(data + 1);
|
||||
const uint8_t chunk_index = data[3];
|
||||
const uint8_t chunk_len = data[4];
|
||||
if (len < static_cast<size_t>(5 + chunk_len) ||
|
||||
chunk_len > kDalimasterTransferChunkBytes ||
|
||||
gateway_transfer_states_.find(transaction_id) == gateway_transfer_states_.end()) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, chunk_index, response);
|
||||
}
|
||||
gateway_transfer_states_[transaction_id].request_chunks[chunk_index] =
|
||||
std::vector<uint8_t>(data + 5, data + 5 + chunk_len);
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id, {},
|
||||
chunk_index, response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpExecute) {
|
||||
if (len < 5) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
return executeDalimasterTransfer(read_le16(data + 1), read_le16(data + 3), response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpReadResponse) {
|
||||
return handleDalimasterTransferState(data, len, response);
|
||||
}
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleDalimasterTransferState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (data == nullptr || len < 4 || response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const uint16_t transaction_id =
|
||||
static_cast<uint16_t>(data[1] | (static_cast<uint16_t>(data[2]) << 8));
|
||||
const uint8_t chunk_index = data[3];
|
||||
const auto it = gateway_transfer_states_.find(transaction_id);
|
||||
if (it == gateway_transfer_states_.end()) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, chunk_index, response);
|
||||
}
|
||||
const auto response_bytes = it->second.response_bytes;
|
||||
const bool ok = appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id,
|
||||
response_bytes, chunk_index, response);
|
||||
const size_t total_chunks =
|
||||
std::max<size_t>(1, (response_bytes.size() + kDalimasterTransferChunkBytes - 1) /
|
||||
kDalimasterTransferChunkBytes);
|
||||
if (chunk_index + 1 >= total_chunks) {
|
||||
gateway_transfer_states_.erase(it);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::executeDalimasterTransfer(uint16_t transaction_id, uint32_t timeout_ms,
|
||||
std::vector<uint8_t>* response) {
|
||||
const auto it = gateway_transfer_states_.find(transaction_id);
|
||||
if (it == gateway_transfer_states_.end() || response == nullptr) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
std::vector<uint8_t> frame;
|
||||
frame.reserve(it->second.total_length);
|
||||
for (uint8_t index = 0; index < it->second.total_chunks; ++index) {
|
||||
const auto chunk = it->second.request_chunks.find(index);
|
||||
if (chunk == it->second.request_chunks.end()) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
frame.insert(frame.end(), chunk->second.begin(), chunk->second.end());
|
||||
}
|
||||
if (it->second.total_length != frame.size() || frame.size() < 4 ||
|
||||
frame[0] != 0x28 || frame[1] != 0x01) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
if (!gateway_command_transactor_) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusNoController,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
const auto transaction =
|
||||
gateway_command_transactor_(frame, timeout_ms == 0 ? 700 : timeout_ms);
|
||||
uint8_t status = kDalimasterTransferStatusOk;
|
||||
switch (transaction.status) {
|
||||
case GatewayKnxGatewayTransactionStatus::kOk:
|
||||
status = kDalimasterTransferStatusOk;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kInvalidFrame:
|
||||
status = kDalimasterTransferStatusInvalidFrame;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kQueueRejected:
|
||||
status = kDalimasterTransferStatusQueueRejected;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kTimeout:
|
||||
status = kDalimasterTransferStatusTimeout;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kNoResponse:
|
||||
status = kDalimasterTransferStatusNoResponse;
|
||||
break;
|
||||
}
|
||||
it->second.response_bytes = transaction.frames;
|
||||
return appendDalimasterTransferResponse(status, transaction_id, it->second.response_bytes, 0,
|
||||
response);
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::appendDalimasterTransferResponse(
|
||||
uint8_t transfer_status, uint16_t transaction_id, const std::vector<uint8_t>& response_bytes,
|
||||
uint8_t chunk_index, std::vector<uint8_t>* response) const {
|
||||
if (response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const size_t total_chunks =
|
||||
std::max<size_t>(1, (response_bytes.size() + kDalimasterTransferChunkBytes - 1) /
|
||||
kDalimasterTransferChunkBytes);
|
||||
if (chunk_index >= total_chunks) {
|
||||
response->assign({0x00, kDalimasterTransferStatusInvalidFrame,
|
||||
static_cast<uint8_t>(transaction_id & 0xff),
|
||||
static_cast<uint8_t>((transaction_id >> 8) & 0xff), 0x00, 0x00,
|
||||
static_cast<uint8_t>(total_chunks & 0xff), chunk_index, 0x00});
|
||||
return true;
|
||||
}
|
||||
const size_t start = static_cast<size_t>(chunk_index) * kDalimasterTransferChunkBytes;
|
||||
const size_t chunk_len =
|
||||
std::min<size_t>(kDalimasterTransferChunkBytes, response_bytes.size() - start);
|
||||
response->clear();
|
||||
response->reserve(9 + chunk_len);
|
||||
response->push_back(0x00);
|
||||
response->push_back(transfer_status);
|
||||
response->push_back(static_cast<uint8_t>(transaction_id & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((transaction_id >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(response_bytes.size() & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((response_bytes.size() >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(total_chunks & 0xff));
|
||||
response->push_back(chunk_index);
|
||||
response->push_back(static_cast<uint8_t>(chunk_len & 0xff));
|
||||
response->insert(response->end(), response_bytes.begin() + start,
|
||||
response_bytes.begin() + start + chunk_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleReg1TypeCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (len < 2 || response == nullptr) {
|
||||
|
||||
@@ -138,6 +138,20 @@ constexpr uint8_t kDaliCmdOnStepUp = 0x08;
|
||||
constexpr uint8_t kDaliCmdStopFade = 0xff;
|
||||
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
|
||||
constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
|
||||
constexpr uint8_t kDalimasterDiscoveryFunctionPropertyId = 210;
|
||||
constexpr uint8_t kDalimasterTransferFunctionPropertyId = 211;
|
||||
constexpr uint8_t kDalimasterTransferChunkBytes = 192;
|
||||
constexpr uint8_t kDalimasterTransferOpSingle = 0x01;
|
||||
constexpr uint8_t kDalimasterTransferOpBegin = 0x02;
|
||||
constexpr uint8_t kDalimasterTransferOpChunk = 0x03;
|
||||
constexpr uint8_t kDalimasterTransferOpExecute = 0x04;
|
||||
constexpr uint8_t kDalimasterTransferOpReadResponse = 0x05;
|
||||
constexpr uint8_t kDalimasterTransferStatusOk = 0x00;
|
||||
constexpr uint8_t kDalimasterTransferStatusInvalidFrame = 0x01;
|
||||
constexpr uint8_t kDalimasterTransferStatusNoController = 0x02;
|
||||
constexpr uint8_t kDalimasterTransferStatusQueueRejected = 0x03;
|
||||
constexpr uint8_t kDalimasterTransferStatusTimeout = 0x04;
|
||||
constexpr uint8_t kDalimasterTransferStatusNoResponse = 0x05;
|
||||
constexpr uint8_t kReg1FunctionType = 2;
|
||||
constexpr uint8_t kReg1FunctionScan = 3;
|
||||
constexpr uint8_t kReg1FunctionAssign = 4;
|
||||
@@ -834,6 +848,8 @@ bool IsKnxBroadcastManagementRequest(CemiFrame& frame) {
|
||||
case IndividualAddressRead:
|
||||
case IndividualAddressSerialNumberRead:
|
||||
case IndividualAddressSerialNumberWrite:
|
||||
case FunctionPropertyCommand:
|
||||
case FunctionPropertyState:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -256,7 +256,7 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
||||
ets_device_->deviceObject(), ets_device_->platform());
|
||||
if (config_.oam_router.enabled) {
|
||||
oam_router_ = std::make_unique<openknx::OamRouterRuntime>(
|
||||
openknx_namespace_ + "_oam", config_.oam_router.individual_address,
|
||||
kGatewayKnxOamOpenKnxNamespace, config_.oam_router.individual_address,
|
||||
config_.oam_router.tunnel_address_base);
|
||||
if (oam_router_->available()) {
|
||||
oam_router_->setProgrammingMode(oam_programming_mode_);
|
||||
@@ -276,8 +276,8 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
||||
ets_device_->tunnelClientAddress(), commissioning_only_);
|
||||
if (oam_router_ != nullptr) {
|
||||
ESP_LOGI(kTag,
|
||||
"OAM router persona namespace=%s_oam configured=%d device=0x%04x tunnelClient=0x%04x secureTunnel=%d secureRouting=%d",
|
||||
openknx_namespace_.c_str(), oam_router_->configured(),
|
||||
"OAM router persona namespace=%s configured=%d device=0x%04x tunnelClient=0x%04x secureTunnel=%d secureRouting=%d",
|
||||
kGatewayKnxOamOpenKnxNamespace, oam_router_->configured(),
|
||||
oam_router_->individualAddress(), oam_router_->tunnelClientAddress(),
|
||||
config_.oam_router.secure_tunnel_enabled,
|
||||
config_.oam_router.secure_routing_enabled);
|
||||
@@ -354,6 +354,17 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
||||
ets_device_->setTpFrameReceiver([this](const uint8_t* data, size_t len) {
|
||||
return handleOpenKnxTpIngressFrame(data, len);
|
||||
});
|
||||
ets_device_->setTpAckHandler([this](uint16_t destination, bool is_group_address) {
|
||||
if (is_group_address || oam_router_ == nullptr) {
|
||||
return TPAckType::AckReqNone;
|
||||
}
|
||||
const bool commissioning = !oam_router_->configured() || oam_router_->programmingMode();
|
||||
return destination == oam_router_->individualAddress() ||
|
||||
destination == oam_router_->tunnelClientAddress() ||
|
||||
(commissioning && destination == 0xffff)
|
||||
? TPAckType::AckReqAck
|
||||
: TPAckType::AckReqNone;
|
||||
});
|
||||
if (oam_router_ != nullptr) {
|
||||
oam_router_->setBusFrameSender([this](const uint8_t* data, size_t len) {
|
||||
publishCloudCemiFrame(data, len);
|
||||
|
||||
@@ -34,7 +34,9 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
||||
if (frame.valid()) {
|
||||
route_to_all_internal_instances = IsKnxBroadcastManagementRequest(frame);
|
||||
if (!route_to_oam && oam_router_ != nullptr &&
|
||||
MatchesOamRouterLocalIndividualAddress(frame, *oam_router_)) {
|
||||
(MatchesOamRouterLocalIndividualAddress(frame, *oam_router_) ||
|
||||
oam_router_->matchesSecureSyncSerial(frame) ||
|
||||
oam_router_->matchesRecentSecureToolAccess(frame))) {
|
||||
route_to_oam = true;
|
||||
}
|
||||
}
|
||||
@@ -326,7 +328,10 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTpIngressFrame(const uint8_t* data, size
|
||||
|
||||
const bool broadcast_management = IsKnxBroadcastManagementRequest(frame);
|
||||
const bool addressed_to_oam =
|
||||
oam_router_ != nullptr && MatchesOamRouterLocalIndividualAddress(frame, *oam_router_);
|
||||
oam_router_ != nullptr &&
|
||||
(MatchesOamRouterLocalIndividualAddress(frame, *oam_router_) ||
|
||||
oam_router_->matchesSecureSyncSerial(frame) ||
|
||||
oam_router_->matchesRecentSecureToolAccess(frame));
|
||||
if (!broadcast_management && !addressed_to_oam) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,10 @@ void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* packet_data, s
|
||||
const size_t cemi_len = frame.dataLength();
|
||||
bool consumed_by_local_application = false;
|
||||
const bool addressed_to_oam =
|
||||
oam_router_ != nullptr && MatchesOamRouterLocalIndividualAddress(frame, *oam_router_);
|
||||
oam_router_ != nullptr &&
|
||||
(MatchesOamRouterLocalIndividualAddress(frame, *oam_router_) ||
|
||||
oam_router_->matchesSecureSyncSerial(frame) ||
|
||||
oam_router_->matchesRecentSecureToolAccess(frame));
|
||||
const bool addressed_to_reg1 =
|
||||
ets_device_ != nullptr && MatchesOpenKnxLocalIndividualAddress(frame, *ets_device_);
|
||||
std::vector<uint8_t> local_tunnel_frame;
|
||||
|
||||
@@ -4,19 +4,52 @@
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_timer.h"
|
||||
#include "knx/cemi_server.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
extern "C" bool knx_platform_get_fdsk_for_namespace(const char* nvs_namespace,
|
||||
uint8_t* data,
|
||||
size_t len) __attribute__((weak));
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t kInvalidIndividualAddress = 0xffff;
|
||||
constexpr uint16_t kKnxUnconfiguredBroadcastAddress = 0xffff;
|
||||
constexpr uint8_t kSecureDataPdu = 0;
|
||||
constexpr uint8_t kSecureSyncRequest = 2;
|
||||
constexpr uint8_t kSecureToolAccessFlag = 0x80;
|
||||
constexpr size_t kKnxSerialLength = 6;
|
||||
constexpr size_t kSecureApduScfOffset = 1;
|
||||
constexpr size_t kSecureApduSerialOffset = 8;
|
||||
constexpr size_t kSecureApduMinimumSyncRequestLength =
|
||||
kSecureApduSerialOffset + kKnxSerialLength;
|
||||
constexpr int64_t kSecureToolAccessRouteWindowUs = 120LL * 1000LL * 1000LL;
|
||||
constexpr uint16_t kOamDeviceObjectVersion = 3;
|
||||
|
||||
bool HardwareTypeEquals(const uint8_t* actual, const uint8_t* expected) {
|
||||
return actual != nullptr && expected != nullptr &&
|
||||
std::memcmp(actual, expected, sizeof(knx_internal::kOamRouterHardwareType)) == 0;
|
||||
}
|
||||
|
||||
VersionCheckResult OamRouterVersionCheck(uint16_t manufacturer_id,
|
||||
uint8_t* hardware_type,
|
||||
uint16_t version) {
|
||||
if (manufacturer_id != knx_internal::kOamRouterManufacturerId ||
|
||||
(!HardwareTypeEquals(hardware_type, knx_internal::kOamRouterHardwareType) &&
|
||||
!HardwareTypeEquals(hardware_type, knx_internal::kOamRouterLegacyHardwareType))) {
|
||||
return FlashAllInvalid;
|
||||
}
|
||||
return version == kOamDeviceObjectVersion ? FlashValid : FlashTablesInvalid;
|
||||
}
|
||||
|
||||
bool IsUsableIndividualAddress(uint16_t address) {
|
||||
return address != 0 && address != kInvalidIndividualAddress;
|
||||
@@ -31,12 +64,29 @@ bool IsBroadcastManagementRequest(CemiFrame& frame) {
|
||||
case IndividualAddressRead:
|
||||
case IndividualAddressSerialNumberRead:
|
||||
case IndividualAddressSerialNumberWrite:
|
||||
case FunctionPropertyCommand:
|
||||
case FunctionPropertyState:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsGroupBroadcastSecureToolAccess(CemiFrame& frame, uint8_t* service) {
|
||||
if (frame.addressType() != GroupAddress || frame.destinationAddress() != 0x0000 ||
|
||||
frame.apdu().type() != SecureService || frame.apdu().length() <= kSecureApduScfOffset) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t scf = frame.apdu().data()[kSecureApduScfOffset];
|
||||
if ((scf & kSecureToolAccessFlag) == 0) {
|
||||
return false;
|
||||
}
|
||||
if (service != nullptr) {
|
||||
*service = scf & 0x07;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t OamBauNumberFromBaseMac() {
|
||||
uint8_t mac[6]{};
|
||||
if (esp_efuse_mac_get_default(mac) != ESP_OK && esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||
@@ -59,6 +109,22 @@ void ApplyOamRouterIdentity(Bau091A& device) {
|
||||
property->write(knx_internal::kOamRouterProgramVersion);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyOamFactoryFdsk(Bau091A& device, const std::string& nvs_namespace) {
|
||||
#if defined(USE_DATASECURE)
|
||||
if (knx_platform_get_fdsk_for_namespace == nullptr) {
|
||||
return;
|
||||
}
|
||||
std::array<uint8_t, 16> fdsk{};
|
||||
if (knx_platform_get_fdsk_for_namespace(nvs_namespace.c_str(), fdsk.data(), fdsk.size())) {
|
||||
device.factoryFdsk(fdsk.data(), fdsk.size());
|
||||
}
|
||||
std::fill(fdsk.begin(), fdsk.end(), 0);
|
||||
#else
|
||||
(void)device;
|
||||
(void)nvs_namespace;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
@@ -75,12 +141,15 @@ OamRouterRuntime::OamRouterRuntime(std::string nvs_namespace,
|
||||
{
|
||||
#if defined(ENABLE_BAU091A_PERSONA)
|
||||
platform_.outboundCemiFrameCallback(&OamRouterRuntime::HandleOutboundCemiFrame, this);
|
||||
ApplyOamFactoryFdsk(device_, nvs_namespace_);
|
||||
ApplyOamRouterIdentity(device_);
|
||||
device_.versionCheckCallback(&OamRouterVersionCheck);
|
||||
if (IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
ESP_LOGI("gateway_knx", "OAM OpenKNX loading memory namespace=%s", nvs_namespace_.c_str());
|
||||
device_.readMemory();
|
||||
ApplyOamFactoryFdsk(device_, nvs_namespace_);
|
||||
ApplyOamRouterIdentity(device_);
|
||||
if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
|
||||
IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
@@ -174,6 +243,55 @@ void OamRouterRuntime::setProgrammingMode(bool enabled) {
|
||||
|
||||
void OamRouterRuntime::toggleProgrammingMode() { setProgrammingMode(!programmingMode()); }
|
||||
|
||||
bool OamRouterRuntime::matchesSecureSyncSerial(CemiFrame& frame) const {
|
||||
#if defined(ENABLE_BAU091A_PERSONA) && defined(USE_DATASECURE)
|
||||
uint8_t service = 0;
|
||||
if (!IsGroupBroadcastSecureToolAccess(frame, &service) ||
|
||||
frame.apdu().length() < kSecureApduMinimumSyncRequestLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service != kSecureSyncRequest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t* apdu_data = frame.apdu().data();
|
||||
const uint8_t* serial = apdu_data + kSecureApduSerialOffset;
|
||||
const uint8_t* local_serial =
|
||||
const_cast<Bau091A&>(device_).deviceObject().propertyData(PID_SERIAL_NUMBER);
|
||||
const bool matches = local_serial != nullptr &&
|
||||
std::memcmp(serial, local_serial, kKnxSerialLength) == 0;
|
||||
if (matches) {
|
||||
recent_secure_tool_source_ = frame.sourceAddress();
|
||||
recent_secure_tool_sync_us_ = esp_timer_get_time();
|
||||
}
|
||||
return matches;
|
||||
#else
|
||||
(void)frame;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OamRouterRuntime::matchesRecentSecureToolAccess(CemiFrame& frame) const {
|
||||
#if defined(ENABLE_BAU091A_PERSONA) && defined(USE_DATASECURE)
|
||||
uint8_t service = 0;
|
||||
if (!IsGroupBroadcastSecureToolAccess(frame, &service) || service != kSecureDataPdu ||
|
||||
recent_secure_tool_sync_us_ <= 0 || frame.sourceAddress() != recent_secure_tool_source_) {
|
||||
return false;
|
||||
}
|
||||
const int64_t now = esp_timer_get_time();
|
||||
if (now < recent_secure_tool_sync_us_ ||
|
||||
now - recent_secure_tool_sync_us_ > kSecureToolAccessRouteWindowUs) {
|
||||
return false;
|
||||
}
|
||||
recent_secure_tool_sync_us_ = now;
|
||||
return true;
|
||||
#else
|
||||
(void)frame;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
EtsMemorySnapshot OamRouterRuntime::snapshot() const {
|
||||
EtsMemorySnapshot out;
|
||||
#if defined(ENABLE_BAU091A_PERSONA)
|
||||
@@ -300,11 +418,18 @@ void OamRouterRuntime::loop() {
|
||||
|
||||
bool OamRouterRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) {
|
||||
auto* self = static_cast<OamRouterRuntime*>(context);
|
||||
if (self == nullptr || !self->sender_) {
|
||||
if (self == nullptr) {
|
||||
return false;
|
||||
}
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
return true;
|
||||
if (self->sender_) {
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
return true;
|
||||
}
|
||||
if (self->bus_frame_sender_) {
|
||||
self->bus_frame_sender_(frame.data(), frame.dataLength());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void OamRouterRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
||||
@@ -348,6 +473,12 @@ bool OamRouterRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
return dest == individualAddress() || dest == tunnelClientAddress() ||
|
||||
(commissioning && dest == kKnxUnconfiguredBroadcastAddress);
|
||||
}
|
||||
if (matchesSecureSyncSerial(frame)) {
|
||||
return true;
|
||||
}
|
||||
if (matchesRecentSecureToolAccess(frame)) {
|
||||
return true;
|
||||
}
|
||||
if (IsBroadcastManagementRequest(frame)) {
|
||||
return true;
|
||||
}
|
||||
@@ -365,6 +496,12 @@ bool OamRouterRuntime::shouldConsumeBusFrame(CemiFrame& frame) const {
|
||||
if (IsBroadcastManagementRequest(frame)) {
|
||||
return true;
|
||||
}
|
||||
if (matchesSecureSyncSerial(frame)) {
|
||||
return true;
|
||||
}
|
||||
if (matchesRecentSecureToolAccess(frame)) {
|
||||
return true;
|
||||
}
|
||||
if (frame.addressType() != IndividualAddress) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_eth.h"
|
||||
@@ -26,9 +28,19 @@ namespace gateway {
|
||||
class GatewayController;
|
||||
class DaliDomainService;
|
||||
class GatewayBridgeService;
|
||||
struct GatewayBridgeHttpResponse;
|
||||
struct DaliRawFrame;
|
||||
|
||||
struct GatewayNetworkServiceConfig {
|
||||
bool wifi_supported{false};
|
||||
bool ethernet_supported{false};
|
||||
bool ble_supported{false};
|
||||
bool cache_supported{false};
|
||||
bool usb_supported{false};
|
||||
GatewayUsbMode usb_default_mode{GatewayUsbMode::kDebug};
|
||||
uint8_t usb_default_channel_index{0};
|
||||
GatewayUsbMode usb_active_mode{GatewayUsbMode::kDebug};
|
||||
uint8_t usb_active_channel_index{0};
|
||||
bool wifi_enabled{true};
|
||||
bool ethernet_enabled{false};
|
||||
bool ethernet_ignore_init_failure{false};
|
||||
@@ -39,8 +51,10 @@ struct GatewayNetworkServiceConfig {
|
||||
uint8_t smartconfig_timeout_sec{60};
|
||||
bool http_enabled{true};
|
||||
bool udp_enabled{true};
|
||||
bool tcp_control_enabled{true};
|
||||
uint16_t http_port{80};
|
||||
uint16_t udp_port{2020};
|
||||
uint16_t tcp_control_port{2020};
|
||||
int ethernet_spi_host{1};
|
||||
int ethernet_spi_sclk_gpio{14};
|
||||
int ethernet_spi_mosi_gpio{13};
|
||||
@@ -63,6 +77,8 @@ struct GatewayNetworkServiceConfig {
|
||||
UBaseType_t boot_button_task_priority{2};
|
||||
uint32_t udp_task_stack_size{4096};
|
||||
UBaseType_t udp_task_priority{4};
|
||||
uint32_t tcp_control_task_stack_size{4096};
|
||||
UBaseType_t tcp_control_task_priority{4};
|
||||
};
|
||||
|
||||
class GatewayNetworkService {
|
||||
@@ -73,10 +89,21 @@ class GatewayNetworkService {
|
||||
GatewayBridgeService* bridge_service = nullptr);
|
||||
|
||||
esp_err_t start();
|
||||
// Physical adapter for the Part 103 IDENTIFY DEVICE action. The DALI core
|
||||
// remains hardware-independent and invokes this only through the app wiring.
|
||||
void identify();
|
||||
|
||||
/// Stable runtime settings surface shared by local HTTP, the bridge API, and
|
||||
/// cloud provisioning. The payload is a schema-versioned JSON document.
|
||||
GatewayBridgeHttpResponse gatewaySettingsGet() const;
|
||||
GatewayBridgeHttpResponse gatewaySettingsSet(std::string_view body);
|
||||
std::string gatewaySettingsCloudResponse(std::string_view request);
|
||||
|
||||
private:
|
||||
static void UdpTaskEntry(void* arg);
|
||||
static void TcpControlTaskEntry(void* arg);
|
||||
static void BootButtonTaskEntry(void* arg);
|
||||
static void IdentifyTaskEntry(void* arg);
|
||||
static esp_err_t HandleInfoGet(httpd_req_t* req);
|
||||
static esp_err_t HandleCommandGet(httpd_req_t* req);
|
||||
static esp_err_t HandleCommandPost(httpd_req_t* req);
|
||||
@@ -107,10 +134,22 @@ class GatewayNetworkService {
|
||||
esp_err_t configureStatusLed();
|
||||
esp_err_t startHttpServer();
|
||||
esp_err_t startUdpTask();
|
||||
esp_err_t startTcpControlTask();
|
||||
esp_err_t configureBootButton();
|
||||
esp_err_t startBootButtonTask();
|
||||
void udpTaskLoop();
|
||||
void tcpControlTaskLoop();
|
||||
void bootButtonTaskLoop();
|
||||
void identifyTaskLoop();
|
||||
void handleNetworkControlBytes(const uint8_t* data, size_t len);
|
||||
std::optional<std::string> handleJsonControlFrame(const uint8_t* data, size_t len);
|
||||
bool enqueueControlFrameForTargets(const std::vector<uint8_t>& frame);
|
||||
std::string discoveryResponseJson() const;
|
||||
std::string setEthernetConfigFromJson(cJSON* data);
|
||||
std::string gatewaySettingsJson() const;
|
||||
GatewayBridgeHttpResponse gatewaySettingsError(std::string_view message) const;
|
||||
bool applyGatewaySettings(cJSON* settings, std::string* error);
|
||||
esp_err_t applyEthernetConfig();
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
void handleWifiControl(uint8_t mode);
|
||||
void handleEthernetEvent(esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
@@ -150,12 +189,17 @@ class GatewayNetworkService {
|
||||
bool espnow_connected_{false};
|
||||
std::array<uint8_t, 6> espnow_peer_{};
|
||||
TaskHandle_t boot_button_task_handle_{nullptr};
|
||||
TaskHandle_t identify_task_handle_{nullptr};
|
||||
TaskHandle_t udp_task_handle_{nullptr};
|
||||
TaskHandle_t tcp_control_task_handle_{nullptr};
|
||||
int udp_socket_{-1};
|
||||
int tcp_control_socket_{-1};
|
||||
int tcp_control_client_socket_{-1};
|
||||
SemaphoreHandle_t udp_lock_{nullptr};
|
||||
SemaphoreHandle_t tcp_control_lock_{nullptr};
|
||||
bool has_udp_remote_{false};
|
||||
sockaddr_storage udp_remote_addr_{};
|
||||
socklen_t udp_remote_addr_len_{0};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,12 +35,33 @@ struct EthernetInfo {
|
||||
std::string ip;
|
||||
};
|
||||
|
||||
struct EthernetConfig {
|
||||
std::string ip;
|
||||
std::string mask;
|
||||
std::string gateway;
|
||||
std::string dns;
|
||||
};
|
||||
|
||||
/// Boot-only USB personalities. The selected value is persisted in NVS so a
|
||||
/// remote configuration survives reboot, but changing it cannot re-enumerate
|
||||
/// an already-running USB device.
|
||||
enum class GatewayUsbMode : uint8_t {
|
||||
kDebug = 0,
|
||||
kSetup = 1,
|
||||
kTridonic = 2,
|
||||
};
|
||||
|
||||
const char* GatewayUsbModeToString(GatewayUsbMode mode);
|
||||
std::optional<GatewayUsbMode> GatewayUsbModeFromString(std::string_view value);
|
||||
|
||||
struct GatewayRuntimeConfig {
|
||||
std::string_view project_name;
|
||||
std::string_view version;
|
||||
std::string serial_id;
|
||||
bool default_ble_enabled{true};
|
||||
bool default_cache_enabled{true};
|
||||
GatewayUsbMode default_usb_mode{GatewayUsbMode::kDebug};
|
||||
uint8_t default_usb_channel_index{0};
|
||||
size_t command_queue_capacity{16};
|
||||
};
|
||||
|
||||
@@ -73,14 +94,35 @@ class GatewaySettingsStore {
|
||||
std::optional<std::string> getWifiPassword() const;
|
||||
bool setWifiCredentials(std::string_view ssid, std::string_view password);
|
||||
bool clearWifiCredentials();
|
||||
std::optional<EthernetConfig> getEthernetConfig() const;
|
||||
bool setEthernetConfig(const EthernetConfig& config);
|
||||
bool clearEthernetConfig();
|
||||
|
||||
GatewayUsbMode getUsbMode(GatewayUsbMode default_value) const;
|
||||
bool setUsbMode(GatewayUsbMode mode);
|
||||
uint8_t getUsbChannelIndex(uint8_t default_value) const;
|
||||
bool setUsbChannelIndex(uint8_t channel_index);
|
||||
|
||||
std::string getDeviceName(std::string_view fallback) const;
|
||||
bool setDeviceName(std::string_view name);
|
||||
std::string getGatewayName(uint8_t gateway_id, std::string_view fallback) const;
|
||||
bool setGatewayName(uint8_t gateway_id, std::string_view name);
|
||||
uint8_t getChannelGatewayId(uint8_t channel_index, uint8_t fallback) const;
|
||||
bool setChannelGatewayId(uint8_t channel_index, uint8_t gateway_id);
|
||||
uint8_t getChannelGatewayGroup(uint8_t channel_index, uint8_t fallback) const;
|
||||
bool setChannelGatewayGroup(uint8_t channel_index, uint8_t gateway_group);
|
||||
std::optional<uint8_t> getApplicationControllerShortAddress(uint8_t channel_index,
|
||||
uint8_t fallback) const;
|
||||
bool setApplicationControllerShortAddress(uint8_t channel_index,
|
||||
std::optional<uint8_t> short_address);
|
||||
|
||||
private:
|
||||
std::optional<std::string> readString(std::string_view key) const;
|
||||
bool writeString(std::string_view key, std::string_view value);
|
||||
std::string makeGatewayNameKey(uint8_t gateway_id) const;
|
||||
std::string makeChannelGatewayIdKey(uint8_t channel_index) const;
|
||||
std::string makeChannelGatewayGroupKey(uint8_t channel_index) const;
|
||||
std::string makeApplicationControllerShortAddressKey(uint8_t channel_index) const;
|
||||
|
||||
mutable nvs_handle_t handle_{0};
|
||||
};
|
||||
@@ -128,6 +170,13 @@ class GatewayRuntime {
|
||||
void setEthernetInfo(EthernetInfo info);
|
||||
void clearEthernetInfo();
|
||||
void clearEthernetIp();
|
||||
std::optional<EthernetConfig> ethernetConfig() const;
|
||||
bool setEthernetConfig(const EthernetConfig& config);
|
||||
bool clearEthernetConfig();
|
||||
GatewayUsbMode usbMode() const;
|
||||
bool setUsbMode(GatewayUsbMode mode);
|
||||
uint8_t usbChannelIndex() const;
|
||||
bool setUsbChannelIndex(uint8_t channel_index);
|
||||
void setCommandAddressResolver(std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver);
|
||||
|
||||
GatewayDeviceInfo deviceInfo() const;
|
||||
@@ -135,8 +184,19 @@ class GatewayRuntime {
|
||||
bool setBleEnabled(bool enabled);
|
||||
bool cacheEnabled() const;
|
||||
bool setCacheEnabled(bool enabled);
|
||||
uint8_t gatewayIdForChannel(uint8_t channel_index, uint8_t fallback) const;
|
||||
bool setGatewayIdForChannel(uint8_t channel_index, uint8_t gateway_id);
|
||||
uint8_t gatewayGroupForChannel(uint8_t channel_index, uint8_t fallback = 0) const;
|
||||
bool setGatewayGroupForChannel(uint8_t channel_index, uint8_t gateway_group);
|
||||
std::optional<uint8_t> applicationControllerShortAddressForChannel(uint8_t channel_index,
|
||||
uint8_t fallback) const;
|
||||
bool setApplicationControllerShortAddressForChannel(
|
||||
uint8_t channel_index, std::optional<uint8_t> short_address);
|
||||
std::string deviceName() const;
|
||||
bool setDeviceName(std::string_view name);
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
bool setGatewayName(uint8_t gateway_id, std::string_view name);
|
||||
std::vector<uint8_t> serialNumberBytes() const;
|
||||
std::string gatewaySerialHex(uint8_t gateway_id) const;
|
||||
std::string bleMacHex() const;
|
||||
std::string bleGatewayName(uint8_t gateway_id, std::string_view gateway_name) const;
|
||||
@@ -148,6 +208,7 @@ class GatewayRuntime {
|
||||
std::deque<std::vector<uint8_t>>& queueForPriorityLocked(CommandPriority priority);
|
||||
const std::deque<std::vector<uint8_t>>& queueForPriorityLocked(CommandPriority priority) const;
|
||||
std::optional<std::string> queryCommandKey(const std::vector<uint8_t>& command) const;
|
||||
std::string defaultDeviceName() const;
|
||||
std::string defaultGatewayName(uint8_t gateway_id) const;
|
||||
std::vector<uint8_t> serialBytes() const;
|
||||
static std::string toHex(const std::vector<uint8_t>& bytes);
|
||||
@@ -161,10 +222,16 @@ class GatewayRuntime {
|
||||
std::deque<std::vector<uint8_t>> control_commands_;
|
||||
std::deque<std::vector<uint8_t>> normal_commands_;
|
||||
std::deque<std::vector<uint8_t>> maintenance_commands_;
|
||||
mutable std::optional<std::string> device_name_;
|
||||
mutable std::map<uint8_t, std::string> gateway_names_;
|
||||
mutable std::map<uint8_t, uint8_t> channel_gateway_ids_;
|
||||
mutable std::map<uint8_t, uint8_t> channel_gateway_groups_;
|
||||
mutable std::map<uint8_t, std::optional<uint8_t>> application_controller_short_addresses_;
|
||||
size_t gateway_count_{0};
|
||||
bool ble_enabled_{false};
|
||||
bool cache_enabled_{true};
|
||||
GatewayUsbMode usb_mode_{GatewayUsbMode::kDebug};
|
||||
uint8_t usb_channel_index_{0};
|
||||
CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone};
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> command_address_resolver_;
|
||||
std::optional<WirelessInfo> wireless_info_;
|
||||
@@ -172,4 +239,4 @@ class GatewayRuntime {
|
||||
SemaphoreHandle_t command_lock_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -19,8 +19,15 @@ constexpr const char* kTag = "gateway_runtime";
|
||||
constexpr const char* kNamespace = "gateway_rt";
|
||||
constexpr const char* kBleEnabledKey = "ble_enabled";
|
||||
constexpr const char* kCacheEnabledKey = "cache_enabled";
|
||||
constexpr const char* kDeviceNameKey = "device_name";
|
||||
constexpr const char* kWifiSsidKey = "wifi_ssid";
|
||||
constexpr const char* kWifiPasswordKey = "wifi_passwd";
|
||||
constexpr const char* kEthIpKey = "eth_ip";
|
||||
constexpr const char* kEthMaskKey = "eth_mask";
|
||||
constexpr const char* kEthGatewayKey = "eth_gw";
|
||||
constexpr const char* kEthDnsKey = "eth_dns";
|
||||
constexpr const char* kUsbModeKey = "usb_mode";
|
||||
constexpr const char* kUsbChannelKey = "usb_channel";
|
||||
constexpr size_t kMaxGatewayNameBytes = 32;
|
||||
constexpr uint8_t kCommandFramePrefix0 = 0x28;
|
||||
constexpr uint8_t kCommandFramePrefix1 = 0x01;
|
||||
@@ -57,7 +64,8 @@ esp_err_t InitializeRuntimeNvs() {
|
||||
|
||||
std::string ReadRuntimeSerialId() {
|
||||
uint8_t mac[6] = {0};
|
||||
if (esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||
if (esp_read_mac(mac, ESP_MAC_BASE) != ESP_OK &&
|
||||
esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||
return "DALIGW";
|
||||
}
|
||||
|
||||
@@ -67,6 +75,31 @@ std::string ReadRuntimeSerialId() {
|
||||
return std::string(serial);
|
||||
}
|
||||
|
||||
const char* GatewayUsbModeToString(GatewayUsbMode mode) {
|
||||
switch (mode) {
|
||||
case GatewayUsbMode::kDebug:
|
||||
return "debug";
|
||||
case GatewayUsbMode::kSetup:
|
||||
return "setup";
|
||||
case GatewayUsbMode::kTridonic:
|
||||
return "tridonic";
|
||||
}
|
||||
return "debug";
|
||||
}
|
||||
|
||||
std::optional<GatewayUsbMode> GatewayUsbModeFromString(std::string_view value) {
|
||||
if (value == "debug") {
|
||||
return GatewayUsbMode::kDebug;
|
||||
}
|
||||
if (value == "setup") {
|
||||
return GatewayUsbMode::kSetup;
|
||||
}
|
||||
if (value == "tridonic") {
|
||||
return GatewayUsbMode::kTridonic;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GatewaySettingsStore::GatewaySettingsStore() = default;
|
||||
|
||||
GatewaySettingsStore::~GatewaySettingsStore() {
|
||||
@@ -173,6 +206,107 @@ bool GatewaySettingsStore::clearWifiCredentials() {
|
||||
return ssid_err == ESP_OK && password_err == ESP_OK && nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<EthernetConfig> GatewaySettingsStore::getEthernetConfig() const {
|
||||
EthernetConfig config;
|
||||
if (const auto value = readString(kEthIpKey)) {
|
||||
config.ip = *value;
|
||||
}
|
||||
if (const auto value = readString(kEthMaskKey)) {
|
||||
config.mask = *value;
|
||||
}
|
||||
if (const auto value = readString(kEthGatewayKey)) {
|
||||
config.gateway = *value;
|
||||
}
|
||||
if (const auto value = readString(kEthDnsKey)) {
|
||||
config.dns = *value;
|
||||
}
|
||||
if (config.ip.empty() && config.mask.empty() && config.gateway.empty() &&
|
||||
config.dns.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setEthernetConfig(const EthernetConfig& config) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const esp_err_t ip_err = nvs_set_str(handle_, kEthIpKey, config.ip.c_str());
|
||||
const esp_err_t mask_err = nvs_set_str(handle_, kEthMaskKey, config.mask.c_str());
|
||||
const esp_err_t gw_err = nvs_set_str(handle_, kEthGatewayKey, config.gateway.c_str());
|
||||
const esp_err_t dns_err = nvs_set_str(handle_, kEthDnsKey, config.dns.c_str());
|
||||
return ip_err == ESP_OK && mask_err == ESP_OK && gw_err == ESP_OK && dns_err == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::clearEthernetConfig() {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto erase = [this](const char* key) {
|
||||
const esp_err_t err = nvs_erase_key(handle_, key);
|
||||
return err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND;
|
||||
};
|
||||
const bool ok = erase(kEthIpKey) && erase(kEthMaskKey) && erase(kEthGatewayKey) &&
|
||||
erase(kEthDnsKey);
|
||||
return ok && nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
GatewayUsbMode GatewaySettingsStore::getUsbMode(GatewayUsbMode default_value) const {
|
||||
if (handle_ == 0) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
uint8_t raw_mode = static_cast<uint8_t>(default_value);
|
||||
if (nvs_get_u8(handle_, kUsbModeKey, &raw_mode) != ESP_OK ||
|
||||
raw_mode > static_cast<uint8_t>(GatewayUsbMode::kTridonic)) {
|
||||
return default_value;
|
||||
}
|
||||
return static_cast<GatewayUsbMode>(raw_mode);
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setUsbMode(GatewayUsbMode mode) {
|
||||
if (handle_ == 0 || mode > GatewayUsbMode::kTridonic) {
|
||||
return false;
|
||||
}
|
||||
return nvs_set_u8(handle_, kUsbModeKey, static_cast<uint8_t>(mode)) == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t GatewaySettingsStore::getUsbChannelIndex(uint8_t default_value) const {
|
||||
if (handle_ == 0) {
|
||||
return default_value;
|
||||
}
|
||||
uint8_t channel_index = default_value;
|
||||
if (nvs_get_u8(handle_, kUsbChannelKey, &channel_index) != ESP_OK) {
|
||||
return default_value;
|
||||
}
|
||||
return channel_index;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setUsbChannelIndex(uint8_t channel_index) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
return nvs_set_u8(handle_, kUsbChannelKey, channel_index) == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::getDeviceName(std::string_view fallback) const {
|
||||
const auto value = readString(kDeviceNameKey);
|
||||
if (!value.has_value() || value->empty()) {
|
||||
return std::string(fallback);
|
||||
}
|
||||
|
||||
return *value;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setDeviceName(std::string_view name) {
|
||||
return writeString(kDeviceNameKey, name);
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::getGatewayName(uint8_t gateway_id,
|
||||
std::string_view fallback) const {
|
||||
const auto value = readString(makeGatewayNameKey(gateway_id));
|
||||
@@ -188,6 +322,81 @@ bool GatewaySettingsStore::setGatewayName(uint8_t gateway_id,
|
||||
return writeString(makeGatewayNameKey(gateway_id), name);
|
||||
}
|
||||
|
||||
uint8_t GatewaySettingsStore::getChannelGatewayId(uint8_t channel_index,
|
||||
uint8_t fallback) const {
|
||||
if (handle_ == 0) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
uint8_t gateway_id = fallback;
|
||||
if (nvs_get_u8(handle_, makeChannelGatewayIdKey(channel_index).c_str(), &gateway_id) !=
|
||||
ESP_OK) {
|
||||
return fallback;
|
||||
}
|
||||
return gateway_id;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setChannelGatewayId(uint8_t channel_index,
|
||||
uint8_t gateway_id) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nvs_set_u8(handle_, makeChannelGatewayIdKey(channel_index).c_str(), gateway_id) ==
|
||||
ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t GatewaySettingsStore::getChannelGatewayGroup(uint8_t channel_index,
|
||||
uint8_t fallback) const {
|
||||
if (handle_ == 0) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
uint8_t gateway_group = fallback;
|
||||
if (nvs_get_u8(handle_, makeChannelGatewayGroupKey(channel_index).c_str(),
|
||||
&gateway_group) != ESP_OK) {
|
||||
return fallback;
|
||||
}
|
||||
return gateway_group;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setChannelGatewayGroup(uint8_t channel_index,
|
||||
uint8_t gateway_group) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nvs_set_u8(handle_, makeChannelGatewayGroupKey(channel_index).c_str(),
|
||||
gateway_group) == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> GatewaySettingsStore::getApplicationControllerShortAddress(
|
||||
uint8_t channel_index, uint8_t fallback) const {
|
||||
if (handle_ == 0) {
|
||||
return fallback & 0x3fU;
|
||||
}
|
||||
|
||||
uint8_t short_address = fallback & 0x3fU;
|
||||
if (nvs_get_u8(handle_, makeApplicationControllerShortAddressKey(channel_index).c_str(),
|
||||
&short_address) != ESP_OK) {
|
||||
return fallback & 0x3fU;
|
||||
}
|
||||
return short_address <= 63 ? std::optional<uint8_t>(short_address) : std::nullopt;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setApplicationControllerShortAddress(
|
||||
uint8_t channel_index, std::optional<uint8_t> short_address) {
|
||||
if (handle_ == 0 || (short_address.has_value() && short_address.value() > 63)) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t stored = short_address.value_or(0xffU);
|
||||
return nvs_set_u8(handle_, makeApplicationControllerShortAddressKey(channel_index).c_str(),
|
||||
stored) == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<std::string> GatewaySettingsStore::readString(std::string_view key) const {
|
||||
if (handle_ == 0) {
|
||||
return std::nullopt;
|
||||
@@ -222,6 +431,25 @@ std::string GatewaySettingsStore::makeGatewayNameKey(uint8_t gateway_id) const {
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::makeChannelGatewayIdKey(uint8_t channel_index) const {
|
||||
char key[24] = {0};
|
||||
std::snprintf(key, sizeof(key), "dali_ch_id_%u", channel_index);
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::makeChannelGatewayGroupKey(uint8_t channel_index) const {
|
||||
char key[24] = {0};
|
||||
std::snprintf(key, sizeof(key), "dali_ch_grp_%u", channel_index);
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::makeApplicationControllerShortAddressKey(
|
||||
uint8_t channel_index) const {
|
||||
char key[24] = {0};
|
||||
std::snprintf(key, sizeof(key), "dali_cd_sa_%u", channel_index);
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
GatewayRuntime::GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
|
||||
DaliDomainService* dali_domain)
|
||||
: profile_(profile),
|
||||
@@ -246,6 +474,13 @@ esp_err_t GatewayRuntime::start() {
|
||||
|
||||
ble_enabled_ = settings_.getBleEnabled(config_.default_ble_enabled);
|
||||
cache_enabled_ = settings_.getCacheEnabled(config_.default_cache_enabled);
|
||||
usb_mode_ = settings_.getUsbMode(config_.default_usb_mode);
|
||||
usb_channel_index_ = settings_.getUsbChannelIndex(config_.default_usb_channel_index);
|
||||
if (usb_channel_index_ > 15) {
|
||||
usb_channel_index_ = config_.default_usb_channel_index > 15
|
||||
? 0
|
||||
: config_.default_usb_channel_index;
|
||||
}
|
||||
|
||||
if (!wireless_info_.has_value()) {
|
||||
WirelessInfo info;
|
||||
@@ -263,10 +498,11 @@ esp_err_t GatewayRuntime::start() {
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag,
|
||||
"runtime project=%.*s version=%.*s serial=%s ble=%d cache=%d dali_bound=%d",
|
||||
"runtime project=%.*s version=%.*s serial=%s ble=%d cache=%d usb=%s/%u dali_bound=%d",
|
||||
static_cast<int>(config_.project_name.size()), config_.project_name.data(),
|
||||
static_cast<int>(config_.version.size()), config_.version.data(),
|
||||
config_.serial_id.c_str(), ble_enabled_, cache_enabled_,
|
||||
GatewayUsbModeToString(usb_mode_), usb_channel_index_,
|
||||
dali_domain_ != nullptr && dali_domain_->isBound());
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -323,7 +559,9 @@ GatewayRuntime::CommandPriority GatewayRuntime::classifyCommandPriority(
|
||||
}
|
||||
if (opcode == 0x00 || opcode == 0x01 || opcode == 0x03 || opcode == 0x04 || opcode == 0x07 ||
|
||||
opcode == 0x08 || opcode == 0x10 || opcode == 0x11 || opcode == 0x12 || opcode == 0x13 ||
|
||||
opcode == 0x17 || opcode == 0x18 || opcode == 0x37 || opcode == 0x38 ||
|
||||
opcode == 0x0B || opcode == 0x17 || opcode == 0x18 || opcode == 0x37 || opcode == 0x38 ||
|
||||
opcode == 0x60 || opcode == 0x61 || opcode == 0x62 || opcode == 0x66 || opcode == 0x67 ||
|
||||
opcode == 0x6A ||
|
||||
(opcode == 0x30 && addr == 0)) {
|
||||
return CommandPriority::kControl;
|
||||
}
|
||||
@@ -467,6 +705,74 @@ void GatewayRuntime::clearEthernetIp() {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<EthernetConfig> GatewayRuntime::ethernetConfig() const {
|
||||
return settings_.getEthernetConfig();
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setEthernetConfig(const EthernetConfig& config) {
|
||||
return settings_.setEthernetConfig(config);
|
||||
}
|
||||
|
||||
bool GatewayRuntime::clearEthernetConfig() {
|
||||
return settings_.clearEthernetConfig();
|
||||
}
|
||||
|
||||
GatewayUsbMode GatewayRuntime::usbMode() const {
|
||||
LockGuard guard(command_lock_);
|
||||
return usb_mode_;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setUsbMode(GatewayUsbMode mode) {
|
||||
if (mode > GatewayUsbMode::kTridonic) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
LockGuard guard(command_lock_);
|
||||
if (usb_mode_ == mode) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!settings_.setUsbMode(mode)) {
|
||||
return false;
|
||||
}
|
||||
LockGuard guard(command_lock_);
|
||||
usb_mode_ = mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t GatewayRuntime::usbChannelIndex() const {
|
||||
LockGuard guard(command_lock_);
|
||||
return usb_channel_index_;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setUsbChannelIndex(uint8_t channel_index) {
|
||||
if (channel_index > 15) {
|
||||
return false;
|
||||
}
|
||||
if (dali_domain_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto channels = dali_domain_->channelInfo();
|
||||
if (!std::any_of(channels.begin(), channels.end(),
|
||||
[channel_index](const DaliChannelInfo& channel) {
|
||||
return channel.channel_index == channel_index;
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
LockGuard guard(command_lock_);
|
||||
if (usb_channel_index_ == channel_index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!settings_.setUsbChannelIndex(channel_index)) {
|
||||
return false;
|
||||
}
|
||||
LockGuard guard(command_lock_);
|
||||
usb_channel_index_ = channel_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GatewayRuntime::setCommandAddressResolver(
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
|
||||
LockGuard guard(command_lock_);
|
||||
@@ -531,6 +837,110 @@ bool GatewayRuntime::setCacheEnabled(bool enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t GatewayRuntime::gatewayIdForChannel(uint8_t channel_index, uint8_t fallback) const {
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = channel_gateway_ids_.find(channel_index);
|
||||
if (cached != channel_gateway_ids_.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
const uint8_t gateway_id = settings_.getChannelGatewayId(channel_index, fallback);
|
||||
channel_gateway_ids_[channel_index] = gateway_id;
|
||||
return gateway_id;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setGatewayIdForChannel(uint8_t channel_index, uint8_t gateway_id) {
|
||||
if (!settings_.setChannelGatewayId(channel_index, gateway_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockGuard guard(command_lock_);
|
||||
channel_gateway_ids_[channel_index] = gateway_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t GatewayRuntime::gatewayGroupForChannel(uint8_t channel_index, uint8_t fallback) const {
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = channel_gateway_groups_.find(channel_index);
|
||||
if (cached != channel_gateway_groups_.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
const uint8_t gateway_group = settings_.getChannelGatewayGroup(channel_index, fallback);
|
||||
channel_gateway_groups_[channel_index] = gateway_group;
|
||||
return gateway_group;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setGatewayGroupForChannel(uint8_t channel_index, uint8_t gateway_group) {
|
||||
if (!settings_.setChannelGatewayGroup(channel_index, gateway_group)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockGuard guard(command_lock_);
|
||||
channel_gateway_groups_[channel_index] = gateway_group;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> GatewayRuntime::applicationControllerShortAddressForChannel(
|
||||
uint8_t channel_index, uint8_t fallback) const {
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = application_controller_short_addresses_.find(channel_index);
|
||||
if (cached != application_controller_short_addresses_.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
const auto short_address =
|
||||
settings_.getApplicationControllerShortAddress(channel_index, fallback);
|
||||
application_controller_short_addresses_[channel_index] = short_address;
|
||||
return short_address;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setApplicationControllerShortAddressForChannel(
|
||||
uint8_t channel_index, std::optional<uint8_t> short_address) {
|
||||
if (short_address.has_value() && short_address.value() > 63) {
|
||||
return false;
|
||||
}
|
||||
if (!settings_.setApplicationControllerShortAddress(channel_index, short_address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockGuard guard(command_lock_);
|
||||
application_controller_short_addresses_[channel_index] = short_address;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::deviceName() const {
|
||||
LockGuard guard(command_lock_);
|
||||
if (device_name_.has_value()) {
|
||||
return device_name_.value();
|
||||
}
|
||||
auto name = settings_.getDeviceName(defaultDeviceName());
|
||||
if (name.size() > kMaxGatewayNameBytes) {
|
||||
name.resize(kMaxGatewayNameBytes);
|
||||
}
|
||||
device_name_ = name;
|
||||
return name;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setDeviceName(std::string_view name) {
|
||||
std::string normalized(name);
|
||||
if (normalized.size() > kMaxGatewayNameBytes) {
|
||||
normalized.resize(kMaxGatewayNameBytes);
|
||||
}
|
||||
if (normalized.empty()) {
|
||||
normalized = defaultDeviceName();
|
||||
}
|
||||
|
||||
if (deviceName() == normalized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!settings_.setDeviceName(normalized)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockGuard guard(command_lock_);
|
||||
device_name_ = normalized;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const {
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = gateway_names_.find(gateway_id);
|
||||
@@ -577,16 +987,23 @@ std::string GatewayRuntime::gatewaySerialHex(uint8_t gateway_id) const {
|
||||
return toHex(serial);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GatewayRuntime::serialNumberBytes() const {
|
||||
const auto bytes = serialBytes();
|
||||
return {bytes[3], bytes[4], bytes[5]};
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::bleMacHex() const {
|
||||
return toHex(serialBytes());
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::bleGatewayName(uint8_t gateway_id, std::string_view gateway_name) const {
|
||||
std::string normalized(gateway_name);
|
||||
std::string normalized(deviceName());
|
||||
if (normalized.size() > kMaxGatewayNameBytes) {
|
||||
normalized.resize(kMaxGatewayNameBytes);
|
||||
}
|
||||
if (!normalized.empty() && normalized != defaultGatewayName(gateway_id)) {
|
||||
(void)gateway_id;
|
||||
(void)gateway_name;
|
||||
if (!normalized.empty() && normalized != defaultDeviceName()) {
|
||||
return normalized;
|
||||
}
|
||||
return defaultBleGatewayName();
|
||||
@@ -597,8 +1014,8 @@ std::string GatewayRuntime::defaultBleGatewayName() const {
|
||||
}
|
||||
|
||||
bool GatewayRuntime::isQueryCommand(const std::vector<uint8_t>& command) const {
|
||||
return command.size() >= 6 && isGatewayCommandFrame(command) && command[3] >= 0x14 &&
|
||||
command[3] <= 0x16;
|
||||
return command.size() >= 6 && isGatewayCommandFrame(command) &&
|
||||
((command[3] >= 0x14 && command[3] <= 0x16) || command[3] == 0x62);
|
||||
}
|
||||
|
||||
size_t GatewayRuntime::pendingCommandCountLocked() const {
|
||||
@@ -639,6 +1056,15 @@ std::optional<std::string> GatewayRuntime::queryCommandKey(
|
||||
|
||||
const auto gw = command[2];
|
||||
const auto cmd = command[3];
|
||||
if (cmd == 0x62) {
|
||||
if (command.size() < 8) {
|
||||
return std::nullopt;
|
||||
}
|
||||
char key[40] = {0};
|
||||
std::snprintf(key, sizeof(key), "%u:%u:%u:%u:%u", gw, cmd, command[4], command[5],
|
||||
command[6]);
|
||||
return std::string(key);
|
||||
}
|
||||
if (cmd == 0x16) {
|
||||
char key[16] = {0};
|
||||
std::snprintf(key, sizeof(key), "%u:%u", gw, cmd);
|
||||
@@ -651,7 +1077,7 @@ std::optional<std::string> GatewayRuntime::queryCommandKey(
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::defaultGatewayName(uint8_t) const {
|
||||
std::string GatewayRuntime::defaultDeviceName() const {
|
||||
const std::string serial_hex = bleMacHex();
|
||||
if (serial_hex.size() <= 6) {
|
||||
return "DALIGW_" + serial_hex;
|
||||
@@ -659,6 +1085,10 @@ std::string GatewayRuntime::defaultGatewayName(uint8_t) const {
|
||||
return "DALIGW_" + serial_hex.substr(serial_hex.size() - 6);
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::defaultGatewayName(uint8_t gateway_id) const {
|
||||
return "Channel " + std::to_string(gateway_id);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GatewayRuntime::serialBytes() const {
|
||||
std::vector<uint8_t> bytes;
|
||||
const std::string& serial = config_.serial_id;
|
||||
@@ -688,4 +1118,4 @@ std::string GatewayRuntime::toHex(const std::vector<uint8_t>& bytes) {
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_tridonic_hid.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_domain esp_tinyusb freertos log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
espressif/esp_tinyusb: "^2.0.1"
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "dali_domain.hpp"
|
||||
#include "esp_err.h"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
struct GatewayTridonicHidConfig {
|
||||
bool enabled{false};
|
||||
uint8_t channel_index{0};
|
||||
};
|
||||
|
||||
/// USB device implementation of the Tridonic 17b5:0020 DALI USB HID protocol.
|
||||
class GatewayTridonicHid {
|
||||
public:
|
||||
GatewayTridonicHid(DaliDomainService& dali_domain, GatewayTridonicHidConfig config = {});
|
||||
esp_err_t start();
|
||||
/// Called by TinyUSB when the host submits one fixed-size output report.
|
||||
void handleOutputReport(const uint8_t* report, uint16_t len);
|
||||
|
||||
private:
|
||||
uint8_t gatewayId() const;
|
||||
void handleRawFrame(const DaliRawFrame& frame);
|
||||
void sendResult(uint8_t sequence, uint8_t type, uint8_t address, uint8_t command);
|
||||
void sendDaliFrame(uint8_t type, uint8_t ecommand, uint8_t address, uint8_t command);
|
||||
|
||||
DaliDomainService& dali_domain_;
|
||||
GatewayTridonicHidConfig config_;
|
||||
bool started_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "gateway_tridonic_hid.hpp"
|
||||
|
||||
#include "class/hid/hid_device.h"
|
||||
#include "esp_log.h"
|
||||
#include "tinyusb.h"
|
||||
#include "tinyusb_default_config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
namespace gateway {
|
||||
namespace {
|
||||
constexpr const char* kTag = "tridonic_hid";
|
||||
constexpr uint8_t kDirectionUsb = 0x12;
|
||||
constexpr uint8_t kDirectionDali = 0x11;
|
||||
constexpr uint8_t kType16Bit = 0x03;
|
||||
constexpr uint8_t kTypeNoResponse = 0x71;
|
||||
constexpr uint8_t kTypeResponse = 0x72;
|
||||
constexpr uint8_t kTypeComplete = 0x73;
|
||||
constexpr uint16_t kReportLength = 64;
|
||||
|
||||
GatewayTridonicHid* s_instance = nullptr;
|
||||
|
||||
// Vendor-defined HID reports, matching the fixed 64-byte reports used by the
|
||||
// Tridonic USB interface. The VID/PID identity is intentionally supplied by
|
||||
// the device descriptor below for masterConfigurator compatibility.
|
||||
const uint8_t kHidReportDescriptor[] = {
|
||||
0x06, 0x00, 0xff, 0x09, 0x01, 0xa1, 0x01,
|
||||
0x09, 0x02, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 0x91, 0x02,
|
||||
0x09, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 0x81, 0x02,
|
||||
0xc0,
|
||||
};
|
||||
|
||||
const tusb_desc_device_t kDeviceDescriptor = {
|
||||
.bLength = sizeof(tusb_desc_device_t), .bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200, .bDeviceClass = 0, .bDeviceSubClass = 0, .bDeviceProtocol = 0,
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
.idVendor = 0x17b5, .idProduct = 0x0020, .bcdDevice = 0x0100,
|
||||
.iManufacturer = 1, .iProduct = 2, .iSerialNumber = 3, .bNumConfigurations = 1,
|
||||
};
|
||||
|
||||
const uint8_t kConfigurationDescriptor[] = {
|
||||
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
||||
TUD_HID_DESCRIPTOR(0, 4, false, sizeof(kHidReportDescriptor), 0x81, 64, 1),
|
||||
};
|
||||
|
||||
const char* kStrings[] = {nullptr, "Tridonic", "DALI USB", "DaliMaster"};
|
||||
} // namespace
|
||||
|
||||
extern "C" uint8_t const* tud_hid_descriptor_report_cb(uint8_t) { return kHidReportDescriptor; }
|
||||
extern "C" uint16_t tud_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t*, uint16_t) { return 0; }
|
||||
extern "C" void tud_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const* buffer, uint16_t len) {
|
||||
if (s_instance != nullptr) s_instance->handleOutputReport(buffer, len);
|
||||
}
|
||||
|
||||
GatewayTridonicHid::GatewayTridonicHid(DaliDomainService& dali_domain, GatewayTridonicHidConfig config)
|
||||
: dali_domain_(dali_domain), config_(config) {}
|
||||
|
||||
esp_err_t GatewayTridonicHid::start() {
|
||||
if (started_ || !config_.enabled) return ESP_OK;
|
||||
const auto configured_channels = dali_domain_.channelInfo();
|
||||
const auto channel = std::find_if(
|
||||
configured_channels.begin(), configured_channels.end(), [this](const auto& value) {
|
||||
return value.channel_index == config_.channel_index;
|
||||
});
|
||||
if (channel == configured_channels.end()) {
|
||||
ESP_LOGE(kTag, "Tridonic HID channel %u is not configured; refusing to start",
|
||||
config_.channel_index);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
tinyusb_config_t usb_config = TINYUSB_DEFAULT_CONFIG();
|
||||
usb_config.descriptor.device = &kDeviceDescriptor;
|
||||
usb_config.descriptor.full_speed_config = kConfigurationDescriptor;
|
||||
usb_config.descriptor.string = kStrings;
|
||||
usb_config.descriptor.string_count = sizeof(kStrings) / sizeof(kStrings[0]);
|
||||
const esp_err_t err = tinyusb_driver_install(&usb_config);
|
||||
if (err != ESP_OK) return err;
|
||||
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleRawFrame(frame); });
|
||||
s_instance = this;
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "Tridonic HID emulation started on channel=%u", config_.channel_index);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t GatewayTridonicHid::gatewayId() const {
|
||||
for (const auto& channel : dali_domain_.channelInfo()) if (channel.channel_index == config_.channel_index) return channel.gateway_id;
|
||||
return config_.channel_index;
|
||||
}
|
||||
|
||||
void GatewayTridonicHid::handleOutputReport(const uint8_t* report, uint16_t len) {
|
||||
if (report == nullptr || len < 8 || report[0] != kDirectionUsb) return;
|
||||
const uint8_t sequence = report[1];
|
||||
const uint8_t frame_type = report[3];
|
||||
const uint8_t address = report[6];
|
||||
const uint8_t command = report[7];
|
||||
if (frame_type == 0x04) {
|
||||
// The 24-bit packet is [direction, sequence, 0, 4, 0, ecommand,
|
||||
// address, command]. This is the format recorded by daliserver, although
|
||||
// its issue #7 confirms it never implemented the transport itself.
|
||||
const uint8_t ecommand = report[5];
|
||||
const bool is_query = command >= 0x90;
|
||||
if (is_query) {
|
||||
const auto response = dali_domain_.queryControlDeviceRaw(gatewayId(), ecommand, address,
|
||||
command);
|
||||
if (response.has_value()) {
|
||||
sendResult(sequence, kTypeResponse, address, *response);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
dali_domain_.sendControlDeviceRaw(gatewayId(), ecommand, address, command);
|
||||
}
|
||||
sendResult(sequence, kTypeNoResponse, address, command);
|
||||
return;
|
||||
}
|
||||
if (frame_type != kType16Bit) return;
|
||||
// Direct arc-power commands use an even first byte. Standard gear queries
|
||||
// are special commands (odd first byte) with an opcode in the query range.
|
||||
const bool is_query = (address & 0x01U) != 0 && command >= 0x90;
|
||||
if (is_query) {
|
||||
const auto response = dali_domain_.queryRaw(gatewayId(), address, command);
|
||||
if (response.has_value()) {
|
||||
sendResult(sequence, kTypeResponse, address, *response);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
dali_domain_.sendRaw(gatewayId(), address, command);
|
||||
}
|
||||
// A write has no backward frame; this is also the protocol's timeout form
|
||||
// for a query that received no DALI response.
|
||||
sendResult(sequence, kTypeNoResponse, address, command);
|
||||
}
|
||||
|
||||
void GatewayTridonicHid::handleRawFrame(const DaliRawFrame& frame) {
|
||||
if (!started_ || frame.channel_index != config_.channel_index || frame.data.size() < 2) return;
|
||||
if (frame.data.size() == 2) {
|
||||
sendDaliFrame(kTypeComplete, 0, frame.data[0], frame.data[1]);
|
||||
} else if (frame.data.size() == 3) {
|
||||
sendDaliFrame(0x74, frame.data[0], frame.data[1], frame.data[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayTridonicHid::sendResult(uint8_t sequence, uint8_t type, uint8_t address, uint8_t command) {
|
||||
if (!tud_hid_ready()) return;
|
||||
std::array<uint8_t, kReportLength> report{};
|
||||
report[0] = kDirectionUsb;
|
||||
report[1] = type;
|
||||
report[4] = address;
|
||||
report[5] = command;
|
||||
report[8] = sequence;
|
||||
tud_hid_report(0, report.data(), report.size());
|
||||
}
|
||||
|
||||
void GatewayTridonicHid::sendDaliFrame(uint8_t type, uint8_t ecommand, uint8_t address,
|
||||
uint8_t command) {
|
||||
if (!tud_hid_ready()) return;
|
||||
std::array<uint8_t, kReportLength> report{};
|
||||
report[0] = kDirectionDali;
|
||||
report[1] = type;
|
||||
report[3] = ecommand;
|
||||
report[4] = address;
|
||||
report[5] = command;
|
||||
tud_hid_report(0, report.data(), report.size());
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
@@ -20,6 +20,7 @@ struct GatewayUsbSetupBridgeConfig {
|
||||
size_t tx_buffer_size{256};
|
||||
uint32_t read_timeout_ms{20};
|
||||
uint32_t write_timeout_ms{20};
|
||||
uint32_t reset_pulse_ms{10};
|
||||
uint32_t task_stack_size{4096};
|
||||
UBaseType_t task_priority{4};
|
||||
};
|
||||
@@ -45,4 +46,4 @@ class GatewayUsbSetupBridge {
|
||||
bool started_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -34,6 +34,16 @@ esp_err_t GatewayUsbSetupBridge::start() {
|
||||
ESP_LOGI(kTag, "USB Serial/JTAG setup bridge disabled; USB remains available for debug");
|
||||
return ESP_OK;
|
||||
}
|
||||
const auto configured_channels = dali_domain_.channelInfo();
|
||||
const auto channel = std::find_if(
|
||||
configured_channels.begin(), configured_channels.end(), [this](const auto& value) {
|
||||
return value.channel_index == config_.channel_index;
|
||||
});
|
||||
if (channel == configured_channels.end()) {
|
||||
ESP_LOGE(kTag, "USB setup channel %u is not configured; refusing to start",
|
||||
config_.channel_index);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!usb_serial_jtag_is_driver_installed()) {
|
||||
usb_serial_jtag_driver_config_t driver_config = {};
|
||||
@@ -89,6 +99,22 @@ void GatewayUsbSetupBridge::handleBytes(const uint8_t* data, size_t len) {
|
||||
}
|
||||
|
||||
const uint8_t gateway_id = setupGatewayId();
|
||||
if (len == 3 && data[0] == 0x01 && data[1] == 0x00 && data[2] == 0x00) {
|
||||
const bool ok = dali_domain_.pulseBusLow(gateway_id, config_.reset_pulse_ms);
|
||||
const uint8_t response[2] = {static_cast<uint8_t>(ok ? 0x01 : 0xFD), 0x00};
|
||||
const int written = usb_serial_jtag_write_bytes(response, sizeof(response),
|
||||
pdMS_TO_TICKS(config_.write_timeout_ms));
|
||||
if (written < 0 || static_cast<size_t>(written) != sizeof(response)) {
|
||||
ESP_LOGW(kTag, "failed to write USB bus reset response channel=%u ok=%u",
|
||||
config_.channel_index, ok ? 1 : 0);
|
||||
}
|
||||
if (!ok) {
|
||||
ESP_LOGW(kTag, "failed to pulse DALI bus low for USB reset channel=%u gateway=%u",
|
||||
config_.channel_index, gateway_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data[0] == 0x12) {
|
||||
const auto response = dali_domain_.transactBridgeFrame(gateway_id, data, len);
|
||||
if (!response.empty()) {
|
||||
@@ -131,4 +157,4 @@ uint8_t GatewayUsbSetupBridge::setupGatewayId() const {
|
||||
return config_.channel_index;
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -76,6 +76,11 @@ class DaliGatewayBridge {
|
||||
|
||||
bool setColourTemperature(int short_address, int kelvin) const;
|
||||
bool setColourRGB(int short_address, uint8_t r, uint8_t g, uint8_t b) const;
|
||||
bool setColourRGBW(int short_address, uint8_t r, uint8_t g, uint8_t b, uint8_t w) const;
|
||||
bool setColourRGBCW(int short_address, uint8_t r, uint8_t g, uint8_t b,
|
||||
uint8_t cool_white, uint8_t warm_white) const;
|
||||
bool setColourRGBWAF(int short_address, uint8_t r, uint8_t g, uint8_t b, uint8_t w,
|
||||
uint8_t amber, uint8_t freecolour, uint8_t control) const;
|
||||
std::optional<DaliDomainSnapshot> dt8StatusSnapshot(int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt8SceneColorReport(int short_address, int scene) const;
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if __has_include("sdkconfig.h")
|
||||
#include "sdkconfig.h"
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID
|
||||
#define CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID 0x00A4
|
||||
@@ -11,12 +15,12 @@
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION
|
||||
#define CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION 0x08
|
||||
#define CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION 0x09
|
||||
#endif
|
||||
|
||||
// Minimal stub for knxprod.h — generated KNX product definitions.
|
||||
// The full file (1796 bytes of parameters, 1439 group objects) will be
|
||||
// adapted in Phase 3 to use the gateway/knx API directly.
|
||||
// replaced by the OpenKNXproducer-generated header.
|
||||
|
||||
// Product identity
|
||||
#define MAIN_OpenKnxId (CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID & 0xff)
|
||||
@@ -45,6 +49,8 @@ enum PT_ColorType : uint8_t {
|
||||
PT_colorType_RGB = 1,
|
||||
PT_colorType_TW = 2,
|
||||
PT_colorType_XYY = 3,
|
||||
PT_colorType_RGBW = 4,
|
||||
PT_colorType_RGBCW = 5,
|
||||
};
|
||||
|
||||
enum PT_ColorSpace : uint8_t {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram>
|
||||
<Static>
|
||||
<Parameters>
|
||||
<Parameter Id="%AID%_P-%TT%%CC%001" Name="colorType%C%" ParameterType="%AID%_PT-ColorType" Text="Address %C% colour type" Value="2">
|
||||
<Property ObjectType="160" PropertyId="201" Offset="%C-1%" BitOffset="0" />
|
||||
</Parameter>
|
||||
</Parameters>
|
||||
<ParameterRefs>
|
||||
<ParameterRef Id="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" RefId="%AID%_P-%TT%%CC%001" />
|
||||
</ParameterRefs>
|
||||
<ComObjectTable>
|
||||
<ComObject Id="%AID%_O-%TT%%CC%001" Number="%K0%" Name="Address %C% switch" Text="Switch" FunctionText="Switch" DatapointType="DPST-1-1" ObjectSize="1 Bit" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%002" Number="%K1%" Name="Address %C% brightness" Text="Brightness" FunctionText="Brightness" DatapointType="DPST-5-1" ObjectSize="1 Byte" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%003" Number="%K2%" Name="Address %C% RGB colour" Text="RGB colour" FunctionText="RGB colour" DatapointType="DPST-232-600" ObjectSize="3 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%004" Number="%K3%" Name="Address %C% RGBW colour" Text="RGBW colour" FunctionText="RGBW colour" DatapointType="DPST-251-600" ObjectSize="6 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%005" Number="%K4%" Name="Address %C% RGBCW colour" Text="RGBCW colour" FunctionText="RGBCW colour" DatapointType="DPST-251-600" ObjectSize="6 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%006" Number="%K5%" Name="Address %C% colour temperature" Text="Colour temperature" FunctionText="Colour temperature" DatapointType="DPST-7-600" ObjectSize="2 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
</ComObjectTable>
|
||||
<ComObjectRefs>
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%001_R-%TT%%CC%00101" RefId="%AID%_O-%TT%%CC%001" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%002_R-%TT%%CC%00201" RefId="%AID%_O-%TT%%CC%002" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%003_R-%TT%%CC%00301" RefId="%AID%_O-%TT%%CC%003" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%004_R-%TT%%CC%00401" RefId="%AID%_O-%TT%%CC%004" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%005_R-%TT%%CC%00501" RefId="%AID%_O-%TT%%CC%005" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%006_R-%TT%%CC%00601" RefId="%AID%_O-%TT%%CC%006" />
|
||||
</ComObjectRefs>
|
||||
</Static>
|
||||
<Dynamic>
|
||||
<ChannelIndependentBlock>
|
||||
<ParameterBlock Id="%AID%_PB-%TT%%CC%001" Name="Address%C%" Text="DALI address %C%" Inline="true" Layout="Grid">
|
||||
<Rows>
|
||||
<Row Id="%AID%_PB-%TT%%CC%001_R-1" />
|
||||
</Rows>
|
||||
<Columns>
|
||||
<Column Id="%AID%_PB-%TT%%CC%001_C-1" Width="30%" />
|
||||
<Column Id="%AID%_PB-%TT%%CC%001_C-2" Width="70%" />
|
||||
</Columns>
|
||||
<ParameterSeparator Id="%AID%_PS-%TT%%CC%001" Text="DALI address %C%" Cell="1,1" />
|
||||
<ParameterRefRef RefId="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" Cell="1,2" />
|
||||
</ParameterBlock>
|
||||
</ChannelIndependentBlock>
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram>
|
||||
<Static>
|
||||
<ParameterTypes>
|
||||
<ParameterType Id="%AID%_PT-ColorType" Name="ColorType">
|
||||
<TypeRestriction Base="Value" SizeInBit="%DALI_ColorTypeBits%">
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-0" Text="HSV" Value="0" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-1" Text="RGB" Value="1" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-2" Text="Tunable white" Value="2" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-3" Text="xyY" Value="3" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-4" Text="RGBW" Value="4" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-5" Text="RGBCW" Value="5" />
|
||||
</TypeRestriction>
|
||||
</ParameterType>
|
||||
</ParameterTypes>
|
||||
</Static>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram>
|
||||
<Static>
|
||||
<Parameters>
|
||||
<Parameter Id="%AID%_P-%TT%%CC%001" Name="colorType%C%" ParameterType="%AID%_PT-ColorType" Text="Group %C% colour type" Value="2">
|
||||
<Property ObjectType="161" PropertyId="201" Offset="%C-1%" BitOffset="0" />
|
||||
</Parameter>
|
||||
</Parameters>
|
||||
<ParameterRefs>
|
||||
<ParameterRef Id="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" RefId="%AID%_P-%TT%%CC%001" />
|
||||
</ParameterRefs>
|
||||
<ComObjectTable>
|
||||
<ComObject Id="%AID%_O-%TT%%CC%001" Number="%K0%" Name="Group %C% switch" Text="Switch" FunctionText="Switch" DatapointType="DPST-1-1" ObjectSize="1 Bit" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%002" Number="%K1%" Name="Group %C% brightness" Text="Brightness" FunctionText="Brightness" DatapointType="DPST-5-1" ObjectSize="1 Byte" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%003" Number="%K2%" Name="Group %C% RGB colour" Text="RGB colour" FunctionText="RGB colour" DatapointType="DPST-232-600" ObjectSize="3 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%004" Number="%K3%" Name="Group %C% RGBW colour" Text="RGBW colour" FunctionText="RGBW colour" DatapointType="DPST-251-600" ObjectSize="6 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%005" Number="%K4%" Name="Group %C% RGBCW colour" Text="RGBCW colour" FunctionText="RGBCW colour" DatapointType="DPST-251-600" ObjectSize="6 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
<ComObject Id="%AID%_O-%TT%%CC%006" Number="%K5%" Name="Group %C% colour temperature" Text="Colour temperature" FunctionText="Colour temperature" DatapointType="DPST-7-600" ObjectSize="2 Bytes" Priority="Low" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" />
|
||||
</ComObjectTable>
|
||||
<ComObjectRefs>
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%001_R-%TT%%CC%00101" RefId="%AID%_O-%TT%%CC%001" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%002_R-%TT%%CC%00201" RefId="%AID%_O-%TT%%CC%002" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%003_R-%TT%%CC%00301" RefId="%AID%_O-%TT%%CC%003" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%004_R-%TT%%CC%00401" RefId="%AID%_O-%TT%%CC%004" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%005_R-%TT%%CC%00501" RefId="%AID%_O-%TT%%CC%005" />
|
||||
<ComObjectRef Id="%AID%_O-%TT%%CC%006_R-%TT%%CC%00601" RefId="%AID%_O-%TT%%CC%006" />
|
||||
</ComObjectRefs>
|
||||
</Static>
|
||||
<Dynamic>
|
||||
<ChannelIndependentBlock>
|
||||
<ParameterBlock Id="%AID%_PB-%TT%%CC%001" Name="Group%C%" Text="DALI group %C%" Inline="true" Layout="Grid">
|
||||
<Rows>
|
||||
<Row Id="%AID%_PB-%TT%%CC%001_R-1" />
|
||||
</Rows>
|
||||
<Columns>
|
||||
<Column Id="%AID%_PB-%TT%%CC%001_C-1" Width="30%" />
|
||||
<Column Id="%AID%_PB-%TT%%CC%001_C-2" Width="70%" />
|
||||
</Columns>
|
||||
<ParameterSeparator Id="%AID%_PS-%TT%%CC%001" Text="DALI group %C%" Cell="1,1" />
|
||||
<ParameterRefRef RefId="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" Cell="1,2" />
|
||||
</ParameterBlock>
|
||||
</ChannelIndependentBlock>
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,302 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<KNX xmlns="http://knx.org/xml/project/23">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram Id="%AID%">
|
||||
<ModuleDefs>
|
||||
<ModuleDef Name="group" Id="%AID%_MD-2000001">
|
||||
<Arguments>
|
||||
<Argument Id="%AID%_MD-2000001_A-1" Name="argParas" Allocates="16" />
|
||||
<Argument Id="%AID%_MD-2000001_A-2" Name="argComs" Allocates="17" />
|
||||
<Argument Id="%AID%_MD-2000001_A-3" Name="argChan" Allocates="1" />
|
||||
</Arguments>
|
||||
<Static>
|
||||
<Parameters>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000002" Name="m2_deviceType" ParameterType="%AID%_PT-groupType" Text="Device type" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000001" Name="m2_name" ParameterType="%AID%_PT-name30" Text="Name" Value="" />
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000003" Name="m2_lock" ParameterType="%AID%_PT-noyes" Text="Show lock object" Value="0" />
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000004" Name="m2_type" ParameterType="%AID%_PT-runType" Text="Operating mode" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="2" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000005" Name="m2_nachtriggern" ParameterType="%AID%_PT-noyes" Text="Allow retriggering" Value="1">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="3" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000006" Name="m2_stairtime" ParameterType="%AID%_PT-time" Text="Run-on time" Value="60">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="1" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000007" Name="m2_manuoff" ParameterType="%AID%_PT-noyes" Text="Manual switch-off" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="4" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000008" Name="m2_lockbehave" ParameterType="%AID%_PT-lock" Text="Behavior while locked" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="5" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000009" Name="m2_lockvalue" ParameterType="%AID%_PT-percent" Text="" Value="100">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="3" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000010" Name="m2_unlockbehave" ParameterType="%AID%_PT-lock" Text="Behavior when unlocked" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="4" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000011" Name="m2_unlockvalue" ParameterType="%AID%_PT-percent" Text="" Value="100">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="5" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000012" Name="m2_onDay" ParameterType="%AID%_PT-percent" Text="Day switch-on value" SuffixText="%" Value="100">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="6" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000013" Name="m2_onNight" ParameterType="%AID%_PT-percent" Text="Night switch-on value" SuffixText="%" Value="10">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="7" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000018" Name="m2_locknegate" ParameterType="%AID%_PT-lockNegate" Text="Lock when" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="7" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000019" Name="m2_colorType" ParameterType="%AID%_PT-colorType" Text="Control color via" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="4" BitOffset="2" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000020" Name="m2_colorSpace" ParameterType="%AID%_PT-colorSpace" Text="Transfer color via" Value="1">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="3" BitOffset="7" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000021" Name="m2_tempMin" ParameterType="%AID%_PT-number16b" Text="Min color temperature" SuffixText="K" Value="1500">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="8" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000022" Name="m2_tempMax" ParameterType="%AID%_PT-number16b" Text="Max color temperature" SuffixText="K" Value="10000">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="10" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000023" Name="m2_hcl" ParameterType="%AID%_PT-noyes" Text="Enable HCL" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="4" BitOffset="4" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000024" Name="m2_xyIgnore" ParameterType="%AID%_PT-noyes" Text="Ignore brightness? (use xy only)" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="4" BitOffset="5" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000025" Name="m2_dimmStateInterval" ParameterType="%AID%_PT-interval" Text="Dimming status interval for relative" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="12" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000026" Name="m2_queryTime" ParameterType="%AID%_PT-timeSpan" Text="Query dimming value (0 = disabled)" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="13" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000027" Name="m2_dimmRelDuration" ParameterType="%AID%_PT-durationms" Text="Dimming time from 0-100% for relative" Value="50">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="15" BitOffset="0" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000028" Name="m2_hclCurve" ParameterType="%AID%_PT-hclCurves" Text="Use" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="4" BitOffset="6" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-2000001_P-2000029" Name="m2_dimmLock" ParameterType="%AID%_PT-dimmLock" Text="Switch on with relative" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="12" BitOffset="4" BaseOffset="%AID%_MD-2000001_A-1" />
|
||||
</Parameter>
|
||||
</Parameters>
|
||||
<ParameterRefs>
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000001" Id="%AID%_MD-2000001_P-2000001_R-200000101" Name="name" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000002" Id="%AID%_MD-2000001_P-2000002_R-200000202" Name="deviceType" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000003" Id="%AID%_MD-2000001_P-2000003_R-200000303" Name="lock" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000004" Id="%AID%_MD-2000001_P-2000004_R-200000404" Name="type" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000005" Id="%AID%_MD-2000001_P-2000005_R-200000505" Name="nachtriggern" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000006" Id="%AID%_MD-2000001_P-2000006_R-200000606" Name="stairtime" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000007" Id="%AID%_MD-2000001_P-2000007_R-200000707" Name="manuoff" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000008" Id="%AID%_MD-2000001_P-2000008_R-200000808" Name="lockbehave" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000009" Id="%AID%_MD-2000001_P-2000009_R-200000909" Name="lockvalue" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000010" Id="%AID%_MD-2000001_P-2000010_R-200001010" Name="unlockbehave" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000011" Id="%AID%_MD-2000001_P-2000011_R-200001111" Name="unlockvalue" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000012" Id="%AID%_MD-2000001_P-2000012_R-200001212" Name="onDay" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000013" Id="%AID%_MD-2000001_P-2000013_R-200001313" Name="onNight" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000018" Id="%AID%_MD-2000001_P-2000018_R-200001818" Name="locknegate" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000019" Id="%AID%_MD-2000001_P-2000019_R-200001919" Name="colorType" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000020" Id="%AID%_MD-2000001_P-2000020_R-200002020" Name="colorSpace" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000023" Id="%AID%_MD-2000001_P-2000023_R-200002321" Name="hcl" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000021" Id="%AID%_MD-2000001_P-2000021_R-200002122" Name="tempMin" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000022" Id="%AID%_MD-2000001_P-2000022_R-200002223" Name="tempMax" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000024" Id="%AID%_MD-2000001_P-2000024_R-200002424" Name="xyIgnore" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000025" Id="%AID%_MD-2000001_P-2000025_R-200002525" Name="dimmStateInterval" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000026" Id="%AID%_MD-2000001_P-2000026_R-200002626" Name="queryTime" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000027" Id="%AID%_MD-2000001_P-2000027_R-200002727" Name="dimmRelDuration" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000028" Id="%AID%_MD-2000001_P-2000028_R-200002828" Name="hclCurve" />
|
||||
<ParameterRef RefId="%AID%_MD-2000001_P-2000029" Id="%AID%_MD-2000001_P-2000029_R-200002929" Name="dimmLock" />
|
||||
</ParameterRefs>
|
||||
<ComObjects>
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000000" Name="m2_switch" Text="G{{argChan}} {{0}}" Number="24" FunctionText="Switch" ObjectSize="1 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-1-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000001" Name="m2_switch_state" Text="G{{argChan}} {{0}}" Number="25" FunctionText="Switching status" ObjectSize="1 Bit" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-1-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000002" Name="m2_dimm_relative" Text="G{{argChan}} {{0}}" Number="26" FunctionText="Relative dimming" ObjectSize="4 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-3-7" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000003" Name="m2_dimm_absolute" Text="G{{argChan}} {{0}}" Number="27" FunctionText="Absolute dimming" ObjectSize="1 Byte" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000004" Name="m2_dimm_state" Text="G{{argChan}} {{0}}" Number="28" FunctionText="Dimming status" ObjectSize="1 Byte" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000005" Name="m2_lock" Text="G{{argChan}} {{0}}" Number="29" FunctionText="Lock" ObjectSize="1 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-1-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000006" Name="m2_color" Text="G{{argChan}} {{0}}" Number="30" FunctionText="RGB color" ObjectSize="6 Bytes" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-242-600" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000007" Name="m2_color_state" Text="G{{argChan}} {{0}}" Number="31" FunctionText="RGB color status" ObjectSize="6 Bytes" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-242-600" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000008" Name="m2_color_red_rel" Text="G{{argChan}} {{0}}" Number="32" FunctionText="Red relative" ObjectSize="4 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-3-7" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000009" Name="m2_color_red_abs" Text="G{{argChan}} {{0}}" Number="33" FunctionText="Red absolute" ObjectSize="1 Byte" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000010" Name="m2_color_red_state" Text="G{{argChan}} {{0}}" Number="34" FunctionText="Red status" ObjectSize="1 Byte" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000011" Name="m2_color_green_rel" Text="G{{argChan}} {{0}}" Number="35" FunctionText="Green relative" ObjectSize="4 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-3-7" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000012" Name="m2_color_green_abs" Text="G{{argChan}} {{0}}" Number="36" FunctionText="Green absolute" ObjectSize="1 Byte" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000013" Name="m2_color_green_state" Text="G{{argChan}} {{0}}" Number="37" FunctionText="Green status" ObjectSize="1 Byte" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000014" Name="m2_color_blue_rel" Text="G{{argChan}} {{0}}" Number="38" FunctionText="Blue relative" ObjectSize="4 Bit" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-3-7" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000015" Name="m2_color_blue_abs" Text="G{{argChan}} {{0}}" Number="39" FunctionText="Blue absolute" ObjectSize="1 Byte" ReadFlag="Disabled" WriteFlag="Enabled" CommunicationFlag="Enabled" TransmitFlag="Disabled" UpdateFlag="Enabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
<ComObject Id="%AID%_MD-2000001_O-2000016" Name="m2_color_blue_state" Text="G{{argChan}} {{0}}" Number="40" FunctionText="Blue status" ObjectSize="1 Byte" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-5-1" BaseNumber="%AID%_MD-2000001_A-2" />
|
||||
</ComObjects>
|
||||
<ComObjectRefs>
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000000" Id="%AID%_MD-2000001_O-2000000_R-200000000" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000001" Id="%AID%_MD-2000001_O-2000001_R-200000101" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000002" Id="%AID%_MD-2000001_O-2000002_R-200000202" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000003" Id="%AID%_MD-2000001_O-2000003_R-200000303" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000004" Id="%AID%_MD-2000001_O-2000004_R-200000404" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000005" Id="%AID%_MD-2000001_O-2000005_R-200000505" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000006" Id="%AID%_MD-2000001_O-2000006_R-200000606" DatapointType="DPST-232-600" ObjectSize="3 Bytes" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000007" Id="%AID%_MD-2000001_O-2000007_R-200000707" DatapointType="DPST-232-600" ObjectSize="3 Bytes" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000008" Id="%AID%_MD-2000001_O-2000008_R-200000808" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000009" Id="%AID%_MD-2000001_O-2000009_R-200000909" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000010" Id="%AID%_MD-2000001_O-2000010_R-200001010" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000011" Id="%AID%_MD-2000001_O-2000011_R-200001111" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000012" Id="%AID%_MD-2000001_O-2000012_R-200001212" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000013" Id="%AID%_MD-2000001_O-2000013_R-200001313" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000014" Id="%AID%_MD-2000001_O-2000014_R-200001414" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000015" Id="%AID%_MD-2000001_O-2000015_R-200001515" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000016" Id="%AID%_MD-2000001_O-2000016_R-200001616" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000006" Id="%AID%_MD-2000001_O-2000006_R-200000617" FunctionText="HSV color" DatapointType="DPST-232-600" ObjectSize="3 Bytes" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000007" Id="%AID%_MD-2000001_O-2000007_R-200000718" FunctionText="HSV color status" DatapointType="DPST-232-600" ObjectSize="3 Bytes" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000008" Id="%AID%_MD-2000001_O-2000008_R-200000819" FunctionText="Color relative" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000009" Id="%AID%_MD-2000001_O-2000009_R-200000920" FunctionText="Color absolute" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000010" Id="%AID%_MD-2000001_O-2000010_R-200001021" FunctionText="Color status" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000011" Id="%AID%_MD-2000001_O-2000011_R-200001122" FunctionText="Saturation relative" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000012" Id="%AID%_MD-2000001_O-2000012_R-200001223" FunctionText="Saturation absolute" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000013" Id="%AID%_MD-2000001_O-2000013_R-200001324" FunctionText="Saturation status" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000006" Id="%AID%_MD-2000001_O-2000006_R-200000625" FunctionText="Color temperature" DatapointType="DPST-7-600" ObjectSize="2 Bytes" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000007" Id="%AID%_MD-2000001_O-2000007_R-200000726" FunctionText="Color temperature status" DatapointType="DPST-7-600" ObjectSize="2 Bytes" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000006" Id="%AID%_MD-2000001_O-2000006_R-200000627" FunctionText="Color xyY / xy" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ComObjectRef RefId="%AID%_MD-2000001_O-2000007" Id="%AID%_MD-2000001_O-2000007_R-200000728" FunctionText="Color xyY / xy status" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
</ComObjectRefs>
|
||||
</Static>
|
||||
<Dynamic>
|
||||
<ParameterBlock Id="%AID%_MD-2000001_PB-7" Text="G{{argChan}}: {{0:---}}" TextParameterRefId="%AID%_MD-2000001_P-2000001_R-200000101" ShowInComObjectTree="true">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000001_R-200000101" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000002_R-200000202" />
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-67" Text="" />
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000002_R-200000202">
|
||||
<when test="!=0">
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000000_R-200000000" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000001_R-200000101" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000026_R-200002626" />
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000002_R-200000202">
|
||||
<when test=">1">
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000002_R-200000202" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000003_R-200000303" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000004_R-200000404" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000027_R-200002727" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000025_R-200002525" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000012_R-200001212" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000013_R-200001313" />
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-68" Text="0 = Switch on with last value" UIHint="Information" />
|
||||
</when>
|
||||
</choose>
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000004_R-200000404" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000003_R-200000303" />
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000004_R-200000404">
|
||||
<when test="1">
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-69" Text="Staircase light" UIHint="Headline" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000006_R-200000606" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000007_R-200000707" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000005_R-200000505" />
|
||||
</when>
|
||||
</choose>
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000003_R-200000303">
|
||||
<when test="1">
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000005_R-200000505" />
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-70" Text="Lock" UIHint="Headline" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000018_R-200001818" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000008_R-200000808" />
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000008_R-200000808">
|
||||
<when test="3">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000009_R-200000909" />
|
||||
</when>
|
||||
</choose>
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000010_R-200001010" />
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000010_R-200001010">
|
||||
<when test="3">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000011_R-200001111" />
|
||||
</when>
|
||||
</choose>
|
||||
</when>
|
||||
</choose>
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000002_R-200000202">
|
||||
<when test="=3">
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-71" Text="Color" UIHint="Headline" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000019_R-200001919" />
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000019_R-200001919">
|
||||
<when test="=0">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000020_R-200002020" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000006_R-200000617" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000007_R-200000718" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000008_R-200000819" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000009_R-200000920" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000010_R-200001021" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000011_R-200001122" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000012_R-200001223" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000013_R-200001324" />
|
||||
</when>
|
||||
<when test="=1">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000020_R-200002020" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000006_R-200000606" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000007_R-200000707" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000008_R-200000808" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000009_R-200000909" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000010_R-200001010" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000011_R-200001111" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000012_R-200001212" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000013_R-200001313" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000014_R-200001414" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000015_R-200001515" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000016_R-200001616" />
|
||||
</when>
|
||||
<when test="=2">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000021_R-200002122" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000022_R-200002223" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000023_R-200002321" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000006_R-200000625" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000007_R-200000726" />
|
||||
</when>
|
||||
<when test="=3">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000024_R-200002424" />
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000020_R-200002020" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000006_R-200000627" />
|
||||
<ComObjectRefRef RefId="%AID%_MD-2000001_O-2000007_R-200000728" />
|
||||
</when>
|
||||
</choose>
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000020_R-200002020">
|
||||
<when test="!=0">
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000024_R-200002424">
|
||||
<when test="1">
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-72" Text="This configuration is not permitted. If brightness should be ignored for xyY, control MUST be via XY." UIHint="Error" />
|
||||
</when>
|
||||
</choose>
|
||||
</when>
|
||||
</choose>
|
||||
</when>
|
||||
</choose>
|
||||
<choose ParamRefId="%AID%_MD-2000001_P-2000023_R-200002321">
|
||||
<when test="=1">
|
||||
<ParameterBlock Id="%AID%_MD-2000001_PB-8" Text="HCL settings">
|
||||
<ParameterRefRef RefId="%AID%_MD-2000001_P-2000028_R-200002828" />
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-73" Text="Abort conditions" UIHint="Headline" />
|
||||
<ParameterSeparator Id="%AID%_MD-2000001_PS-74" Text="Settings for returning from manual mode to automatic mode." />
|
||||
</ParameterBlock>
|
||||
</when>
|
||||
</choose>
|
||||
</when>
|
||||
</choose>
|
||||
</ParameterBlock>
|
||||
</Dynamic>
|
||||
</ModuleDef>
|
||||
</ModuleDefs>
|
||||
<Dynamic>
|
||||
<Channel Text="Groups" Number="3" Id="%AID%_CH-3" Name="Gruppen">
|
||||
<Repeat Id="%AID%_X-4" Name="" Count="16">
|
||||
<Module Id="%AID%_MD-2000001_M-2000004" RefId="%AID%_MD-2000001">
|
||||
<NumericArg RefId="%AID%_MD-2000001_A-1" AllocatorRefId="%AID%_L-4" />
|
||||
<NumericArg RefId="%AID%_MD-2000001_A-2" AllocatorRefId="%AID%_L-5" />
|
||||
<NumericArg RefId="%AID%_MD-2000001_A-3" AllocatorRefId="%AID%_L-6" />
|
||||
</Module>
|
||||
</Repeat>
|
||||
</Channel>
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram>
|
||||
<Static>
|
||||
<ParameterTypes>
|
||||
<ParameterType Id="%AID%_PT-Bool" Name="Bool">
|
||||
<TypeRestriction Base="Value" SizeInBit="1">
|
||||
<Enumeration Id="%AID%_PT-Bool_EN-0" Text="Disabled" Value="0" />
|
||||
<Enumeration Id="%AID%_PT-Bool_EN-1" Text="Enabled" Value="1" />
|
||||
</TypeRestriction>
|
||||
</ParameterType>
|
||||
</ParameterTypes>
|
||||
</Static>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram>
|
||||
<Static>
|
||||
<Parameters>
|
||||
<Parameter Id="%AID%_P-%TT%%CC%001" Name="curveEnabled%C%" ParameterType="%AID%_PT-Bool" Text="HCL curve %C% enabled" Value="0">
|
||||
<Property ObjectType="162" PropertyId="201" Offset="%C-1%" BitOffset="0" />
|
||||
</Parameter>
|
||||
</Parameters>
|
||||
<ParameterRefs>
|
||||
<ParameterRef Id="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" RefId="%AID%_P-%TT%%CC%001" />
|
||||
</ParameterRefs>
|
||||
</Static>
|
||||
<Dynamic>
|
||||
<ChannelIndependentBlock>
|
||||
<ParameterBlock Id="%AID%_PB-%TT%%CC%001" Name="HCL%C%" Text="HCL curve %C%" Inline="true" Layout="Grid">
|
||||
<Rows>
|
||||
<Row Id="%AID%_PB-%TT%%CC%001_R-1" />
|
||||
</Rows>
|
||||
<Columns>
|
||||
<Column Id="%AID%_PB-%TT%%CC%001_C-1" Width="30%" />
|
||||
<Column Id="%AID%_PB-%TT%%CC%001_C-2" Width="70%" />
|
||||
</Columns>
|
||||
<ParameterSeparator Id="%AID%_PS-%TT%%CC%001" Text="HCL curve %C%" Cell="1,1" />
|
||||
<ParameterRefRef RefId="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" Cell="1,2" />
|
||||
</ParameterBlock>
|
||||
</ChannelIndependentBlock>
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,105 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<KNX xmlns="http://knx.org/xml/project/23">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram Id="%AID%">
|
||||
<ModuleDefs>
|
||||
<ModuleDef Name="hcl" Id="%AID%_MD-4000001">
|
||||
<Arguments>
|
||||
<Argument Id="%AID%_MD-4000001_A-1" Name="argParas" Allocates="7" />
|
||||
<Argument Id="%AID%_MD-4000001_A-2" Name="argComs" Allocates="1" />
|
||||
<Argument Id="%AID%_MD-4000001_A-3" Name="argChan" Allocates="1" />
|
||||
</Arguments>
|
||||
<Static>
|
||||
<Parameters>
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000001" Name="m4_type" ParameterType="%AID%_PT-hclType" Text="Control via" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="0" BaseOffset="%AID%_MD-4000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000002" Name="m4_name" ParameterType="%AID%_PT-name14" Text="Name" Value="" />
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000003" Name="m4_min" ParameterType="%AID%_PT-temperature" Text="Min color temperature" Value="2700">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="1" BitOffset="0" BaseOffset="%AID%_MD-4000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000004" Name="m4_max" ParameterType="%AID%_PT-temperature" Text="Max color temperature" Value="5000">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="3" BitOffset="0" BaseOffset="%AID%_MD-4000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000005" Name="m4_picHclSonne" ParameterType="%AID%_PT-picHclSonne" Text="" Value="" />
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000006" Name="m4_offsetRiseType" ParameterType="%AID%_PT-offset" Text="Shift sunrise" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="2" BaseOffset="%AID%_MD-4000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000007" Name="m4_offsetRiseMin" ParameterType="%AID%_PT-durationMin" Text="" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="5" BitOffset="0" BaseOffset="%AID%_MD-4000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000008" Name="m4_offsetSetType" ParameterType="%AID%_PT-offset" Text="Shift sunset" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="4" BaseOffset="%AID%_MD-4000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-4000001_P-4000009" Name="m4_offsetSetMin" ParameterType="%AID%_PT-durationMin" Text="" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="6" BitOffset="0" BaseOffset="%AID%_MD-4000001_A-1" />
|
||||
</Parameter>
|
||||
</Parameters>
|
||||
<ParameterRefs>
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000001" Id="%AID%_MD-4000001_P-4000001_R-400000101" Name="type" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000002" Id="%AID%_MD-4000001_P-4000002_R-400000202" Name="name" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000003" Id="%AID%_MD-4000001_P-4000003_R-400000303" Name="min" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000004" Id="%AID%_MD-4000001_P-4000004_R-400000404" Name="max" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000005" Id="%AID%_MD-4000001_P-4000005_R-400000505" Name="picHclSonne" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000006" Id="%AID%_MD-4000001_P-4000006_R-400000606" Name="offsetRiseType" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000007" Id="%AID%_MD-4000001_P-4000007_R-400000707" Name="offsetRiseMin" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000008" Id="%AID%_MD-4000001_P-4000008_R-400000808" Name="offsetSetType" />
|
||||
<ParameterRef RefId="%AID%_MD-4000001_P-4000009" Id="%AID%_MD-4000001_P-4000009_R-400000909" Name="offsetSetMin" />
|
||||
</ParameterRefs>
|
||||
<ComObjects>
|
||||
<ComObject Id="%AID%_MD-4000001_O-4000000" Name="m4_hcl_state" Text="HCL curve {{argChan}}" Number="41" FunctionText="Color temperature" ObjectSize="2 Bytes" ReadFlag="Enabled" WriteFlag="Disabled" CommunicationFlag="Enabled" TransmitFlag="Enabled" UpdateFlag="Disabled" ReadOnInitFlag="Disabled" DatapointType="DPST-7-600" BaseNumber="%AID%_MD-4000001_A-2" />
|
||||
</ComObjects>
|
||||
<ComObjectRefs>
|
||||
<ComObjectRef RefId="%AID%_MD-4000001_O-4000000" Id="%AID%_MD-4000001_O-4000000_R-400000000" />
|
||||
</ComObjectRefs>
|
||||
</Static>
|
||||
<Dynamic>
|
||||
<ParameterBlock Id="%AID%_MD-4000001_PB-10" Text="Curve {{argChan}}: {{0:---}}" TextParameterRefId="%AID%_MD-4000001_P-4000002_R-400000202">
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000002_R-400000202" />
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000001_R-400000101" />
|
||||
<choose ParamRefId="%AID%_MD-4000001_P-4000001_R-400000101">
|
||||
<when test="1">
|
||||
<ComObjectRefRef RefId="%AID%_MD-4000001_O-4000000_R-400000000" />
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000003_R-400000303" />
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000004_R-400000404" />
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000006_R-400000606" />
|
||||
<choose ParamRefId="%AID%_MD-4000001_P-4000006_R-400000606">
|
||||
<when test="!=0">
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000007_R-400000707" />
|
||||
</when>
|
||||
</choose>
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000008_R-400000808" />
|
||||
<choose ParamRefId="%AID%_MD-4000001_P-4000008_R-400000808">
|
||||
<when test="!=0">
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000009_R-400000909" />
|
||||
</when>
|
||||
</choose>
|
||||
<ParameterRefRef RefId="%AID%_MD-4000001_P-4000005_R-400000505" />
|
||||
<ParameterSeparator Id="%AID%_MD-4000001_PS-75" Text="The current color temperature is calculated after sunrise/sunset. " UIHint="Information" />
|
||||
</when>
|
||||
<when test="2">
|
||||
<ParameterSeparator Id="%AID%_MD-4000001_PS-76" Text="Coming later" />
|
||||
</when>
|
||||
</choose>
|
||||
</ParameterBlock>
|
||||
</Dynamic>
|
||||
</ModuleDef>
|
||||
</ModuleDefs>
|
||||
<Dynamic>
|
||||
<Channel Text="HCL curves" Number="4" Id="%AID%_CH-4" Name="">
|
||||
<Repeat Id="%AID%_X-5" Name="" Count="3">
|
||||
<Module Id="%AID%_MD-4000001_M-4000005" RefId="%AID%_MD-4000001">
|
||||
<NumericArg RefId="%AID%_MD-4000001_A-1" AllocatorRefId="%AID%_L-10" />
|
||||
<NumericArg RefId="%AID%_MD-4000001_A-2" AllocatorRefId="%AID%_L-11" />
|
||||
<NumericArg RefId="%AID%_MD-4000001_A-3" AllocatorRefId="%AID%_L-12" />
|
||||
</Module>
|
||||
</Repeat>
|
||||
</Channel>
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<KNX xmlns="http://knx.org/xml/project/23">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram Id="%AID%">
|
||||
<ModuleDefs>
|
||||
<ModuleDef Name="scene" Id="%AID%_MD-3000001">
|
||||
<Arguments>
|
||||
<Argument Id="%AID%_MD-3000001_A-1" Name="argParas" Allocates="4" />
|
||||
<Argument Id="%AID%_MD-3000001_A-2" Name="argComs" Allocates="0" />
|
||||
<Argument Id="%AID%_MD-3000001_A-3" Name="argChan" Allocates="1" />
|
||||
</Arguments>
|
||||
<Static>
|
||||
<Parameters>
|
||||
<Parameter Id="%AID%_MD-3000001_P-3000001" Name="m3_name" ParameterType="%AID%_PT-name30" Text="Name" Value="" />
|
||||
<Parameter Id="%AID%_MD-3000001_P-3000002" Name="m3_type" ParameterType="%AID%_PT-scenetype" Text="Send to" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="0" BaseOffset="%AID%_MD-3000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-3000001_P-3000003" Name="m3_save" ParameterType="%AID%_PT-noyes" Text="Allow saving" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="2" BaseOffset="%AID%_MD-3000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-3000001_P-3000004" Name="m3_numberKnx" ParameterType="%AID%_PT-number64" Text="KNX scene number" Value="1">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="1" BitOffset="0" BaseOffset="%AID%_MD-3000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-3000001_P-3000005" Name="m3_numberDali" ParameterType="%AID%_PT-number15" Text="DALI scene number" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="0" BitOffset="3" BaseOffset="%AID%_MD-3000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-3000001_P-3000006" Name="m3_address" ParameterType="%AID%_PT-number63" Text="DALI address" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="2" BitOffset="0" BaseOffset="%AID%_MD-3000001_A-1" />
|
||||
</Parameter>
|
||||
<Parameter Id="%AID%_MD-3000001_P-3000007" Name="m3_group" ParameterType="%AID%_PT-number15" Text="DALI group" Value="0">
|
||||
<Memory CodeSegment="%AID%_RS-04-00000" Offset="3" BitOffset="0" BaseOffset="%AID%_MD-3000001_A-1" />
|
||||
</Parameter>
|
||||
</Parameters>
|
||||
<ParameterRefs>
|
||||
<ParameterRef RefId="%AID%_MD-3000001_P-3000001" Id="%AID%_MD-3000001_P-3000001_R-300000101" Name="name" />
|
||||
<ParameterRef RefId="%AID%_MD-3000001_P-3000002" Id="%AID%_MD-3000001_P-3000002_R-300000202" Name="type" />
|
||||
<ParameterRef RefId="%AID%_MD-3000001_P-3000003" Id="%AID%_MD-3000001_P-3000003_R-300000303" Name="save" />
|
||||
<ParameterRef RefId="%AID%_MD-3000001_P-3000004" Id="%AID%_MD-3000001_P-3000004_R-300000404" Name="numberKnx" />
|
||||
<ParameterRef RefId="%AID%_MD-3000001_P-3000005" Id="%AID%_MD-3000001_P-3000005_R-300000505" Name="numberDali" />
|
||||
<ParameterRef RefId="%AID%_MD-3000001_P-3000006" Id="%AID%_MD-3000001_P-3000006_R-300000606" Name="address" />
|
||||
<ParameterRef RefId="%AID%_MD-3000001_P-3000007" Id="%AID%_MD-3000001_P-3000007_R-300000707" Name="group" />
|
||||
</ParameterRefs>
|
||||
<ComObjects />
|
||||
</Static>
|
||||
<Dynamic>
|
||||
<ParameterBlock Id="%AID%_MD-3000001_PB-9" Text="Assignment {{argChan}} {{0}}" TextParameterRefId="%AID%_MD-3000001_P-3000001_R-300000101" ShowInComObjectTree="true">
|
||||
<ParameterRefRef RefId="%AID%_MD-3000001_P-3000001_R-300000101" />
|
||||
<ParameterRefRef RefId="%AID%_MD-3000001_P-3000002_R-300000202" />
|
||||
<choose ParamRefId="%AID%_MD-3000001_P-3000002_R-300000202">
|
||||
<when test="!=0">
|
||||
<choose ParamRefId="%AID%_MD-3000001_P-3000002_R-300000202">
|
||||
<when test="1">
|
||||
<ParameterRefRef RefId="%AID%_MD-3000001_P-3000006_R-300000606" />
|
||||
</when>
|
||||
<when test="2">
|
||||
<ParameterRefRef RefId="%AID%_MD-3000001_P-3000007_R-300000707" />
|
||||
</when>
|
||||
</choose>
|
||||
<ParameterRefRef RefId="%AID%_MD-3000001_P-3000003_R-300000303" />
|
||||
<ParameterRefRef RefId="%AID%_MD-3000001_P-3000004_R-300000404" />
|
||||
<ParameterRefRef RefId="%AID%_MD-3000001_P-3000005_R-300000505" />
|
||||
</when>
|
||||
</choose>
|
||||
</ParameterBlock>
|
||||
</Dynamic>
|
||||
</ModuleDef>
|
||||
</ModuleDefs>
|
||||
<Dynamic>
|
||||
<Channel Text="Scenes" Number="1" Id="%AID%_CH-1" Name="Szenen">
|
||||
<Repeat Id="%AID%_X-2" Name="" Count="64">
|
||||
<Module Id="%AID%_MD-3000001_M-3000002" RefId="%AID%_MD-3000001">
|
||||
<NumericArg RefId="%AID%_MD-3000001_A-1" AllocatorRefId="%AID%_L-7" />
|
||||
<NumericArg RefId="%AID%_MD-3000001_A-2" AllocatorRefId="%AID%_L-8" />
|
||||
<NumericArg RefId="%AID%_MD-3000001_A-3" AllocatorRefId="%AID%_L-9" />
|
||||
</Module>
|
||||
</Repeat>
|
||||
</Channel>
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<Baggages>
|
||||
<Baggage TargetPath="openknxapp" Name="hcl_sonne.png" Id="M-00FA_BG-openknxapp-hcl.5Fsonne.2Epng">
|
||||
<FileInfo TimeInfo="%DATETIME%" />
|
||||
</Baggage>
|
||||
<Baggage TargetPath="root" Name="ets.png" Id="M-00FA_BG-root-ets.2Epng">
|
||||
<FileInfo TimeInfo="%DATETIME%" />
|
||||
</Baggage>
|
||||
</Baggages>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<Catalog>
|
||||
<CatalogSection Id="M-00FA_CS-OpenKNX" Name="OpenKNX" Number="OpenKNX" DefaultLanguage="en-US">
|
||||
<CatalogItem Id="%CatalogItemId%" Name="OpenKNX: %ProductName%%BuildSuffixText%" Number="1" VisibleDescription="Control your DALI lights via KNX" ProductRefId="%ProductId%" Hardware2ProgramRefId="%Hardware2ProgramId%" DefaultLanguage="en-US" />
|
||||
</CatalogSection>
|
||||
</Catalog>
|
||||
<ApplicationPrograms>
|
||||
<op:version OpenKnxId="%OpenKnxId%" ApplicationNumber="%ApplicationNumber%" ApplicationVersion="%ApplicationVersion%" ReplacesVersions="%ReplacesVersions%" ApplicationRevision="%ApplicationRevision%" FirmwareRevision="%FirmwareRevision%" />
|
||||
<op:include href="DALI-GW.model.xml" xpath="//ApplicationProgram" />
|
||||
</ApplicationPrograms>
|
||||
<Baggages>
|
||||
<op:include href="DALI-GW.baggages.xml" xpath="//Baggages/*" prefix="BAG" />
|
||||
</Baggages>
|
||||
<Hardware>
|
||||
<Hardware Id="%HardwareId%" Name="OpenKNX-%HardwareName%%BuildSuffix%" SerialNumber="%SerialNumber%" VersionNumber="1" BusCurrent="%BusCurrent%" HasIndividualAddress="true" HasApplicationProgram="true">
|
||||
<Products>
|
||||
<Product Id="%ProductId%" Text="OpenKNX: %ProductName%%BuildSuffixText%" OrderNumber="%OrderNumber%" IsRailMounted="%IsRailMounted%" WidthInMillimeter="72" DefaultLanguage="en-US">
|
||||
<RegistrationInfo RegistrationStatus="Registered" />
|
||||
</Product>
|
||||
</Products>
|
||||
<Hardware2Programs>
|
||||
<Hardware2Program Id="%Hardware2ProgramId%" MediumTypes="%MediumTypes%">
|
||||
<ApplicationProgramRef RefId="%AID%" />
|
||||
<RegistrationInfo RegistrationStatus="Registered" RegistrationNumber="0001/%HardwareVersionEncoded%1" />
|
||||
</Hardware2Program>
|
||||
</Hardware2Programs>
|
||||
</Hardware>
|
||||
</Hardware>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<KNX xmlns="http://knx.org/xml/project/23" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram Id="%AID%" ApplicationNumber="0" ApplicationVersion="0" ProgramType="ApplicationProgram" MaskVersion="MV-07B0" Name="OpenKNX DALI gateway" DefaultLanguage="en-US" LoadProcedureStyle="MergedProcedure" PeiType="0" DynamicTableManagement="false" Linkable="false" IsSecureEnabled="%DALI_IsSecureEnabled%" MaxSecurityIndividualAddressEntries="%DALI_MaxSecurityIndividualAddressEntries%" MaxSecurityGroupKeyTableEntries="%DALI_MaxSecurityGroupKeyTableEntries%" ReplacesVersions="0 1 2 3" MinEtsVersion="6.0">
|
||||
<Static>
|
||||
<op:include href="DALI-GW.common.xml" xpath="//ApplicationProgram/Static/*" />
|
||||
</Static>
|
||||
<ModuleDefs>
|
||||
<op:include href="DALI-GW-address.xml" xpath="//ApplicationProgram/ModuleDefs/ModuleDef" />
|
||||
<op:include href="DALI-GW-group.xml" xpath="//ApplicationProgram/ModuleDefs/ModuleDef" />
|
||||
<op:include href="DALI-GW-scene.xml" xpath="//ApplicationProgram/ModuleDefs/ModuleDef" />
|
||||
<op:include href="DALI-GW-hcl.xml" xpath="//ApplicationProgram/ModuleDefs/ModuleDef" />
|
||||
<op:include href="DALI-GW.common.xml" xpath="//ApplicationProgram/ModuleDefs/ModuleDef" />
|
||||
</ModuleDefs>
|
||||
<Dynamic>
|
||||
<op:include href="DALI-GW.common.xml" xpath="//ApplicationProgram/Dynamic/*" />
|
||||
<op:include href="DALI-GW-scene.xml" xpath="//ApplicationProgram/Dynamic/*" />
|
||||
<op:include href="DALI-GW-address.xml" xpath="//ApplicationProgram/Dynamic/*" />
|
||||
<op:include href="DALI-GW-group.xml" xpath="//ApplicationProgram/Dynamic/*" />
|
||||
<op:include href="DALI-GW-hcl.xml" xpath="//ApplicationProgram/Dynamic/*" />
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<KNX xmlns="http://knx.org/xml/project/23" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<op:config name="%DALI_IsSecureEnabled%" value="true" />
|
||||
<op:config name="%DALI_MaxSecurityIndividualAddressEntries%" value="65535" />
|
||||
<op:config name="%DALI_MaxSecurityGroupKeyTableEntries%" value="65535" />
|
||||
</KNX>
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
<ManufacturerData>
|
||||
<Manufacturer RefId="M-00FA">
|
||||
<ApplicationPrograms>
|
||||
<ApplicationProgram>
|
||||
<Static>
|
||||
<ParameterTypes>
|
||||
<ParameterType Id="%AID%_PT-Bool" Name="Bool">
|
||||
<TypeRestriction Base="Value" SizeInBit="1">
|
||||
<Enumeration Id="%AID%_PT-Bool_EN-0" Text="Disabled" Value="0" />
|
||||
<Enumeration Id="%AID%_PT-Bool_EN-1" Text="Enabled" Value="1" />
|
||||
</TypeRestriction>
|
||||
</ParameterType>
|
||||
<ParameterType Id="%AID%_PT-ColorType" Name="ColorType">
|
||||
<TypeRestriction Base="Value" SizeInBit="%DALI_ColorTypeBits%">
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-0" Text="HSV" Value="0" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-1" Text="RGB" Value="1" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-2" Text="Tunable white" Value="2" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-3" Text="xyY" Value="3" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-4" Text="RGBW" Value="4" />
|
||||
<Enumeration Id="%AID%_PT-ColorType_EN-5" Text="RGBCW" Value="5" />
|
||||
</TypeRestriction>
|
||||
</ParameterType>
|
||||
<ParameterType Id="%AID%_PT-Level" Name="Level">
|
||||
<TypeRestriction Base="Value" SizeInBit="8" />
|
||||
</ParameterType>
|
||||
<ParameterType Id="%AID%_PT-Kelvin" Name="Kelvin">
|
||||
<TypeNumber SizeInBit="16" Type="unsignedInt" minInclusive="1000" maxInclusive="20000" />
|
||||
</ParameterType>
|
||||
</ParameterTypes>
|
||||
<ComObjectTable />
|
||||
<ComObjectRefs />
|
||||
<ParameterRefs />
|
||||
</Static>
|
||||
<Dynamic>
|
||||
<ChannelIndependentBlock>
|
||||
<ParameterBlock Id="%AID%_PB-000001" Name="Overview" Text="DALI Gateway" Inline="true" Layout="Grid">
|
||||
<Rows>
|
||||
<Row Id="%AID%_PB-000001_R-1" />
|
||||
</Rows>
|
||||
<Columns>
|
||||
<Column Id="%AID%_PB-000001_C-1" Width="100%" />
|
||||
</Columns>
|
||||
<ParameterSeparator Id="%AID%_PS-000001" Text="DALI Gateway" Cell="1,1" />
|
||||
</ParameterBlock>
|
||||
</ChannelIndependentBlock>
|
||||
</Dynamic>
|
||||
</ApplicationProgram>
|
||||
</ApplicationPrograms>
|
||||
</Manufacturer>
|
||||
</ManufacturerData>
|
||||
</KNX>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
|
||||
|
||||
<op:config href="DALI-GW.security.xml" />
|
||||
|
||||
<op:ETS OpenKnxId="0xA4"
|
||||
ApplicationNumber="1"
|
||||
ApplicationVersion="0.8"
|
||||
ReplacesVersions="0.3 0.2 0.1 0"
|
||||
ApplicationRevision="0"
|
||||
ProductName="DALI gateway"
|
||||
SerialNumber="0xA401"
|
||||
OrderNumber="REG1-Dali"
|
||||
ApplicationName="OpenKNX DALI gateway"
|
||||
IsRailMounted="true"
|
||||
BusCurrent="10"
|
||||
MaskVersion="MV-07B0" />
|
||||
|
||||
<generate base="DALI-GW.base.xml" />
|
||||
|
||||
</KNX>
|
||||
@@ -0,0 +1,49 @@
|
||||
# DALI-GW KNX Product XML
|
||||
|
||||
This folder contains the gateway-owned KNX product source for the REG1-Dali KNX-DALI gateway persona.
|
||||
|
||||
The source is organized as independent OpenKNXproducer XML fragments. `DALI-GW.base.xml` owns catalog, baggage, and hardware metadata; `DALI-GW.model.xml` owns the application shell and include order; `DALI-GW.common.xml` owns shared static types, scripts, messages, general UI, and the common module; `DALI-GW-address.xml`, `DALI-GW-group.xml`, `DALI-GW-scene.xml`, and `DALI-GW-hcl.xml` own their functional modules and dynamic channels; `DALI-GW.security.xml` owns the KNX Secure configuration values.
|
||||
|
||||
The source is self-contained in this folder and does not include XML from external OpenKNX module repositories.
|
||||
|
||||
Current migration targets:
|
||||
|
||||
- Manufacturer/OpenKNX id: `0xA4`
|
||||
- Application number: `0x01`
|
||||
- Application version: `0x08`
|
||||
- Mask: `MV-07B0`
|
||||
- Order number: `REG1-Dali`
|
||||
- KNX secure: enabled
|
||||
- Address channels: `64`
|
||||
- Group channels: `16`
|
||||
- HCL curves: `3`
|
||||
|
||||
The existing color type values stay stable for compatibility:
|
||||
|
||||
- `HSV = 0`
|
||||
- `RGB = 1`
|
||||
- `TW = 2`
|
||||
- `XYY = 3`
|
||||
- `RGBW = 4`
|
||||
- `RGBCW = 5`
|
||||
|
||||
RGBW is exposed with standard DPT `251.600` (`DPT_Colour_RGBW`). RGBCW uses the same RGBW payload shape and maps the white and amber DT8 RGBWAF channels as cool-white and warm-white levels at runtime because the local KNX master database does not define a standard RGBCW datapoint subtype.
|
||||
|
||||
## Validation
|
||||
|
||||
Run a well-formed XML check from the repository root:
|
||||
|
||||
```sh
|
||||
xmllint --noout gateway/components/knx_dali_gw/knxprod/DALI-GW*.xml
|
||||
```
|
||||
|
||||
Generate the product database artifact with the local non-ETS producer build:
|
||||
|
||||
```sh
|
||||
cd gateway/components/knx_dali_gw/knxprod
|
||||
/Users/tonylu/Downloads/OpenKNXproducer-osx-arm64/OpenKNXproducer create -o test.knxprod DALI-GW.xml
|
||||
```
|
||||
|
||||
The source is expected to pass the producer's standard `create` sanity checks directly. Generated files such as `test.knxprod`, `DALI-GW.baggages/`, and debug XML files should not be committed.
|
||||
|
||||
The runtime identity must stay synchronized with `gateway/apps/gateway/main/Kconfig.projbuild` and `gateway/components/knx_dali_gw/src/knx_dali_gw.cpp`.
|
||||
@@ -207,6 +207,25 @@ bool DaliGatewayBridge::setColourRGB(int short_address, uint8_t r, uint8_t g,
|
||||
return dali_.setColourRGB(gateway_id_, short_address, r, g, b);
|
||||
}
|
||||
|
||||
bool DaliGatewayBridge::setColourRGBW(int short_address, uint8_t r, uint8_t g,
|
||||
uint8_t b, uint8_t w) const {
|
||||
return dali_.setColourRGBW(gateway_id_, short_address, r, g, b, w);
|
||||
}
|
||||
|
||||
bool DaliGatewayBridge::setColourRGBCW(int short_address, uint8_t r, uint8_t g,
|
||||
uint8_t b, uint8_t cool_white,
|
||||
uint8_t warm_white) const {
|
||||
return dali_.setColourRGBCW(gateway_id_, short_address, r, g, b, cool_white,
|
||||
warm_white);
|
||||
}
|
||||
|
||||
bool DaliGatewayBridge::setColourRGBWAF(int short_address, uint8_t r, uint8_t g,
|
||||
uint8_t b, uint8_t w, uint8_t amber,
|
||||
uint8_t freecolour, uint8_t control) const {
|
||||
return dali_.setColourRGBWAF(gateway_id_, short_address, r, g, b, w, amber,
|
||||
freecolour, control);
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliGatewayBridge::dt8StatusSnapshot(
|
||||
int short_address) const {
|
||||
return dali_.dt8StatusSnapshot(gateway_id_, short_address);
|
||||
|
||||
@@ -132,8 +132,25 @@ void KnxDaliChannel::setDimmState(uint8_t value, bool, bool) {
|
||||
|
||||
void KnxDaliChannel::sendColor() {
|
||||
if (dali_ == nullptr) return;
|
||||
dali_->setColourRGB(static_cast<int>(index_), current_color_[0],
|
||||
current_color_[1], current_color_[2]);
|
||||
const auto color_type = is_group_ ? ParamGRP_colorType(index_) : ParamADR_colorType(index_);
|
||||
switch (color_type) {
|
||||
case PT_ColorType::PT_colorType_RGBW:
|
||||
dali_->setColourRGBW(static_cast<int>(index_), current_color_[0], current_color_[1],
|
||||
current_color_[2], current_color_[3]);
|
||||
break;
|
||||
case PT_ColorType::PT_colorType_RGBCW:
|
||||
dali_->setColourRGBCW(static_cast<int>(index_), current_color_[0], current_color_[1],
|
||||
current_color_[2], current_color_[3], current_color_[4]);
|
||||
break;
|
||||
case PT_ColorType::PT_colorType_RGB:
|
||||
case PT_ColorType::PT_colorType_HSV:
|
||||
case PT_ColorType::PT_colorType_XYY:
|
||||
case PT_ColorType::PT_colorType_TW:
|
||||
default:
|
||||
dali_->setColourRGB(static_cast<int>(index_), current_color_[0], current_color_[1],
|
||||
current_color_[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- KO Handlers ----
|
||||
@@ -172,12 +189,21 @@ void KnxDaliChannel::koHandleLock(GroupObject& ko) {
|
||||
}
|
||||
|
||||
void KnxDaliChannel::koHandleColor(GroupObject& ko) {
|
||||
KNXValue val = ko.value();
|
||||
if (true) {
|
||||
// RGB packed in float or raw bytes
|
||||
// Simplified: store and send
|
||||
sendColor();
|
||||
const auto color_type = is_group_ ? ParamGRP_colorType(index_) : ParamADR_colorType(index_);
|
||||
uint8_t* data = ko.valueRef();
|
||||
if (data != nullptr) {
|
||||
current_color_[0] = data[0];
|
||||
current_color_[1] = data[1];
|
||||
current_color_[2] = data[2];
|
||||
if (color_type == PT_ColorType::PT_colorType_RGBW ||
|
||||
color_type == PT_ColorType::PT_colorType_RGBCW) {
|
||||
current_color_[3] = data[3];
|
||||
}
|
||||
if (color_type == PT_ColorType::PT_colorType_RGBCW) {
|
||||
current_color_[4] = data[4];
|
||||
}
|
||||
}
|
||||
sendColor();
|
||||
}
|
||||
|
||||
} // namespace knx_dali_gw
|
||||
|
||||
@@ -77,7 +77,7 @@ class KnxDaliChannel {
|
||||
bool current_state_{false};
|
||||
uint8_t current_step_{0};
|
||||
bool current_is_locked_{false};
|
||||
uint8_t current_color_[4]{};
|
||||
uint8_t current_color_[6]{};
|
||||
|
||||
// HCL
|
||||
uint8_t hcl_curve_{255};
|
||||
|
||||
@@ -21,21 +21,27 @@ constexpr const char* kTag = "knx_dali_gw";
|
||||
#define CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER 0x0001
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID
|
||||
#define CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID 0xA401
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION
|
||||
#define CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION 0x08
|
||||
#define CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION 0x09
|
||||
#endif
|
||||
|
||||
constexpr uint16_t kKnxOemManufacturerId =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID);
|
||||
constexpr uint16_t kKnxOemApplicationNumber =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER);
|
||||
constexpr uint16_t kKnxOemHardwareId =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID);
|
||||
constexpr uint8_t kKnxOemApplicationVersion =
|
||||
static_cast<uint8_t>(CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION);
|
||||
constexpr uint8_t kKnxOemHardwareType[6] = {
|
||||
0x00,
|
||||
0x00,
|
||||
static_cast<uint8_t>(kKnxOemManufacturerId & 0xff),
|
||||
static_cast<uint8_t>(kKnxOemApplicationNumber & 0xff),
|
||||
static_cast<uint8_t>((kKnxOemHardwareId >> 8) & 0xff),
|
||||
static_cast<uint8_t>(kKnxOemHardwareId & 0xff),
|
||||
kKnxOemApplicationVersion,
|
||||
0x00};
|
||||
constexpr uint8_t kKnxOemProgramVersion[5] = {
|
||||
|
||||
+1
-1
Submodule knx updated: 29fa7318f9...cc476dd71d
Reference in New Issue
Block a user