Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70ae1ae6cf | |||
| f2ffb45ca6 | |||
| 0b2d00472e | |||
| 4553ed32e7 | |||
| 39ef630608 | |||
| b74367e5a0 | |||
| df1dd472cc | |||
| 888d021343 | |||
| de0edd5ad9 | |||
| e58115d303 | |||
| 626f86ec4e | |||
| 36d10702da | |||
| d231460612 | |||
| 70367f53ca | |||
| 1b8753636f | |||
| e94945fc0f | |||
| bf23cf0b79 | |||
| 1a8ee06ec1 | |||
| 029785ff1d | |||
| 34d2d9caa0 | |||
| 640e78f688 | |||
| ee1246c942 | |||
| 7424b43bdd | |||
| 694217eb2c | |||
| 8aa5a451a4 | |||
| 30a96c5125 | |||
| fa2eae87cf | |||
| 639fdd860e |
@@ -1,3 +1,4 @@
|
||||
**/build/
|
||||
build/
|
||||
**/managed_components/
|
||||
.DS_Store
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
[submodule "bacnet_stack"]
|
||||
path = bacnet_stack
|
||||
url = https://git.tonycloud.org/dali/bacnet_stack.git
|
||||
[submodule "knx"]
|
||||
path = knx
|
||||
url = https://git.tonycloud.org/knx/knx.git
|
||||
branch = v1
|
||||
[submodule "knx_dali_gw"]
|
||||
path = knx_dali_gw
|
||||
url = https://git.tonycloud.org/knx/GW-REG1-Dali.git
|
||||
branch = tonycloud-dev
|
||||
[submodule "tpuart"]
|
||||
path = tpuart
|
||||
url = https://git.tonycloud.org/knx/tpuart.git
|
||||
branch = main
|
||||
@@ -0,0 +1,361 @@
|
||||
# Gateway Modbus
|
||||
|
||||
The native gateway exposes each DALI channel as a Modbus server. Supported transports are:
|
||||
|
||||
- `tcp-server`: Modbus TCP server, default TCP port `1502`.
|
||||
- `rtu-server`: Modbus RTU slave/server on an ESP-IDF UART.
|
||||
- `ascii-server`: Modbus ASCII slave/server on an ESP-IDF UART.
|
||||
|
||||
The generated Modbus map is identical for TCP, RTU, and ASCII. Only the frame transport changes.
|
||||
|
||||
## Configuration
|
||||
|
||||
Compile-time defaults live in `apps/gateway/main/Kconfig.projbuild`:
|
||||
|
||||
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP`
|
||||
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU`
|
||||
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII`
|
||||
- `GATEWAY_MODBUS_TCP_PORT`
|
||||
- `GATEWAY_MODBUS_UNIT_ID`
|
||||
- `GATEWAY_MODBUS_SERIAL_UART_PORT`
|
||||
- `GATEWAY_MODBUS_SERIAL_TX_PIN`
|
||||
- `GATEWAY_MODBUS_SERIAL_RX_PIN`
|
||||
- `GATEWAY_MODBUS_SERIAL_BAUDRATE`
|
||||
- `GATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MS`
|
||||
- `GATEWAY_MODBUS_SERIAL_RS485_ENABLED`
|
||||
- `GATEWAY_MODBUS_SERIAL_RS485_DE_PIN`
|
||||
- `GATEWAY_MODBUS_ALLOW_UART0`
|
||||
|
||||
UART0 is reserved for the ESP-IDF console by default. Select `GATEWAY_MODBUS_ALLOW_UART0` only when the console has been moved away from UART0 or the deployment intentionally repurposes it. The app validates the Kconfig default UART against DALI serial PHY assignments at boot.
|
||||
|
||||
Runtime bridge config keeps the existing top-level `modbus` object. The serial fields are nested under `serial`:
|
||||
|
||||
```json
|
||||
{
|
||||
"modbus": {
|
||||
"transport": "rtu-server",
|
||||
"unitID": 1,
|
||||
"port": 1502,
|
||||
"serial": {
|
||||
"uartPort": 1,
|
||||
"txPin": 17,
|
||||
"rxPin": 18,
|
||||
"baudrate": 9600,
|
||||
"dataBits": 8,
|
||||
"parity": "none",
|
||||
"stopBits": 1,
|
||||
"rxBufferBytes": 512,
|
||||
"txBufferBytes": 512,
|
||||
"responseTimeoutMs": 20,
|
||||
"interFrameGapUs": 4000,
|
||||
"rs485": {
|
||||
"enabled": true,
|
||||
"dePin": 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`unitID` accepts aliases `unitId` and `unit_id`. A configured `unitID` of `0` keeps the gateway compatibility behavior where incoming unit ids are accepted as a wildcard. Incoming serial requests addressed to unit id `0` are treated as Modbus broadcast and do not generate a response.
|
||||
|
||||
## UART Management Commands
|
||||
|
||||
When a Modbus serial transport is active, the UART also accepts management lines before Modbus frame parsing. A management line starts with `@DALIGW `, contains JSON, and ends with newline.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
@DALIGW {"action":"modbus_status","gw":3}
|
||||
@DALIGW {"action":"modbus_stop","gw":3}
|
||||
@DALIGW {"action":"modbus_config","gw":3,"modbus":{"transport":"ascii-server","unitID":1,"serial":{"uartPort":1,"txPin":17,"rxPin":18,"baudrate":9600,"rs485":{"enabled":true,"dePin":16}}}}
|
||||
```
|
||||
|
||||
Responses are newline-terminated `@DALIGW` JSON lines. A successful `modbus_config` command is saved to NVS and restarts the Modbus transport after the response is written.
|
||||
|
||||
HTTP still supports the bridge routes:
|
||||
|
||||
- `GET /bridge?action=status&gw=N`
|
||||
- `GET /bridge?action=config&gw=N`
|
||||
- `GET /bridge?action=modbus&gw=N`
|
||||
- `POST /bridge?action=config&gw=N`
|
||||
- `POST /bridge?action=modbus_start&gw=N`
|
||||
- `POST /bridge?action=modbus_stop&gw=N`
|
||||
|
||||
## Function Codes
|
||||
|
||||
Supported function codes are:
|
||||
|
||||
| Code | Name | Map space |
|
||||
| --- | --- | --- |
|
||||
| `0x01` | Read coils | Coils |
|
||||
| `0x02` | Read discrete inputs | Discrete inputs |
|
||||
| `0x03` | Read holding registers | Holding registers |
|
||||
| `0x04` | Read input registers | Input registers |
|
||||
| `0x05` | Write single coil | Coils |
|
||||
| `0x06` | Write single holding register | Holding registers |
|
||||
| `0x0F` | Write multiple coils | Coils |
|
||||
| `0x10` | Write multiple holding registers | Holding registers |
|
||||
|
||||
Limits are `2000` read bits, `125` read registers, `1968` write bits, `123` write registers, and `252` PDU bytes.
|
||||
|
||||
Exception codes used by the gateway:
|
||||
|
||||
| Code | Meaning |
|
||||
| --- | --- |
|
||||
| `0x01` | Unsupported function |
|
||||
| `0x02` | Unmapped address or read failure |
|
||||
| `0x03` | Invalid quantity, value, or request shape |
|
||||
| `0x04` | DALI execution failure |
|
||||
| `0x0B` | Unit id mismatch |
|
||||
|
||||
## Address Calculation
|
||||
|
||||
Modbus protocol frames use zero-based wire offsets. Most tools show human addresses with traditional bases:
|
||||
|
||||
| Space | Human base | Function codes |
|
||||
| --- | ---: | --- |
|
||||
| Coil | `1` | `0x01`, `0x05`, `0x0F` |
|
||||
| Discrete input | `10001` | `0x02` |
|
||||
| Input register | `30001` | `0x04` |
|
||||
| Holding register | `40001` | `0x03`, `0x06`, `0x10` |
|
||||
|
||||
For the main generated slice, every DALI short address `0-63` gets a stride of `32` points in each space:
|
||||
|
||||
```text
|
||||
human = base + shortAddress * 32 + offset
|
||||
wire = human - base
|
||||
```
|
||||
|
||||
Example: short address `5` brightness is holding-register offset `0`:
|
||||
|
||||
```text
|
||||
human = 40001 + 5 * 32 + 0 = 40161
|
||||
wire = 40161 - 40001 = 160
|
||||
```
|
||||
|
||||
The diagnostic discrete-input extension starts after the first `64 * 32` discrete inputs:
|
||||
|
||||
```text
|
||||
diagnosticBase = 10001 + 64 * 32 = 12049
|
||||
human = 12049 + shortAddress * 128 + diagnosticOffset
|
||||
wire = human - 10001
|
||||
```
|
||||
|
||||
Example: short address `5`, diagnostic offset `105` (`DT8 xy out of range`):
|
||||
|
||||
```text
|
||||
human = 12049 + 5 * 128 + 105 = 12794
|
||||
wire = 12794 - 10001 = 2793
|
||||
```
|
||||
|
||||
## Main Generated Map
|
||||
|
||||
Generated points exist for all short addresses `0-63`, even when no device has been discovered. Unknown numeric values read as `0xFFFF`; unknown booleans read as false unless inventory or cache state proves otherwise. Generated reads prefer gateway cache state and do not poll the DALI bus for every Modbus read.
|
||||
|
||||
### Coils
|
||||
|
||||
| Offset | Address formula | Access | Function |
|
||||
| ---: | --- | --- | --- |
|
||||
| `0` | `1 + short * 32 + 0` | Write | On / recall max |
|
||||
| `1` | `1 + short * 32 + 1` | Write | Off |
|
||||
| `2` | `1 + short * 32 + 2` | Write | Recall max |
|
||||
| `3` | `1 + short * 32 + 3` | Write | Recall min |
|
||||
|
||||
### Discrete Inputs
|
||||
|
||||
| Offset | Address formula | Function |
|
||||
| ---: | --- | --- |
|
||||
| `0` | `10001 + short * 32 + 0` | Discovered |
|
||||
| `1` | `10001 + short * 32 + 1` | Online |
|
||||
| `2` | `10001 + short * 32 + 2` | Supports DT1 |
|
||||
| `3` | `10001 + short * 32 + 3` | Supports DT4 |
|
||||
| `4` | `10001 + short * 32 + 4` | Supports DT5 |
|
||||
| `5` | `10001 + short * 32 + 5` | Supports DT6 |
|
||||
| `6` | `10001 + short * 32 + 6` | Supports DT8 |
|
||||
| `7` | `10001 + short * 32 + 7` | Group mask known |
|
||||
| `8` | `10001 + short * 32 + 8` | Actual level known |
|
||||
| `9` | `10001 + short * 32 + 9` | Scene known |
|
||||
| `10` | `10001 + short * 32 + 10` | Settings known |
|
||||
| `16` | `10001 + short * 32 + 16` | Control gear present |
|
||||
| `17` | `10001 + short * 32 + 17` | Lamp failure |
|
||||
| `18` | `10001 + short * 32 + 18` | Lamp power on |
|
||||
| `19` | `10001 + short * 32 + 19` | Limit error |
|
||||
| `20` | `10001 + short * 32 + 20` | Fading completed |
|
||||
| `21` | `10001 + short * 32 + 21` | Reset state |
|
||||
| `22` | `10001 + short * 32 + 22` | Missing short address |
|
||||
| `23` | `10001 + short * 32 + 23` | Power supply fault |
|
||||
|
||||
### Holding Registers
|
||||
|
||||
| Offset | Address formula | Access | Function |
|
||||
| ---: | --- | --- | --- |
|
||||
| `0` | `40001 + short * 32 + 0` | Read/write | Brightness |
|
||||
| `1` | `40001 + short * 32 + 1` | Write, cache-read if known | Color temperature |
|
||||
| `2` | `40001 + short * 32 + 2` | Read/write | Group mask |
|
||||
| `3` | `40001 + short * 32 + 3` | Read/write | Power-on level |
|
||||
| `4` | `40001 + short * 32 + 4` | Read/write | System-failure level |
|
||||
| `5` | `40001 + short * 32 + 5` | Read/write | Minimum level |
|
||||
| `6` | `40001 + short * 32 + 6` | Read/write | Maximum level |
|
||||
| `7` | `40001 + short * 32 + 7` | Read/write | Fade time |
|
||||
| `8` | `40001 + short * 32 + 8` | Read/write | Fade rate |
|
||||
|
||||
### Input Registers
|
||||
|
||||
| Offset | Address formula | Function |
|
||||
| ---: | --- | --- |
|
||||
| `0` | `30001 + short * 32 + 0` | Inventory state: `0` never seen, `1` offline, `2` online |
|
||||
| `1` | `30001 + short * 32 + 1` | Primary DALI device type |
|
||||
| `2` | `30001 + short * 32 + 2` | Device type mask |
|
||||
| `3` | `30001 + short * 32 + 3` | Actual level |
|
||||
| `4` | `30001 + short * 32 + 4` | Scene id |
|
||||
| `5` | `30001 + short * 32 + 5` | Raw status |
|
||||
| `6` | `30001 + short * 32 + 6` | Group mask |
|
||||
| `7` | `30001 + short * 32 + 7` | Power-on level |
|
||||
| `8` | `30001 + short * 32 + 8` | System-failure level |
|
||||
| `9` | `30001 + short * 32 + 9` | Minimum level |
|
||||
| `10` | `30001 + short * 32 + 10` | Maximum level |
|
||||
| `11` | `30001 + short * 32 + 11` | Fade time |
|
||||
| `12` | `30001 + short * 32 + 12` | Fade rate |
|
||||
|
||||
## Diagnostic Discrete Inputs
|
||||
|
||||
Diagnostic addresses use `12049 + short * 128 + offset`.
|
||||
|
||||
| Offset | Function |
|
||||
| ---: | --- |
|
||||
| `0` | DT1 circuit failure |
|
||||
| `1` | DT1 battery duration failure |
|
||||
| `2` | DT1 battery failure |
|
||||
| `3` | DT1 emergency lamp failure |
|
||||
| `4` | DT1 function test max delay exceeded |
|
||||
| `5` | DT1 duration test max delay exceeded |
|
||||
| `6` | DT1 function test failed |
|
||||
| `7` | DT1 duration test failed |
|
||||
| `8` | DT1 inhibit mode |
|
||||
| `9` | DT1 function test result valid |
|
||||
| `10` | DT1 duration test result valid |
|
||||
| `11` | DT1 battery fully charged |
|
||||
| `12` | DT1 function test request pending |
|
||||
| `13` | DT1 duration test request pending |
|
||||
| `14` | DT1 identification active |
|
||||
| `15` | DT1 physically selected |
|
||||
| `16` | DT1 rest mode active |
|
||||
| `17` | DT1 normal mode active |
|
||||
| `18` | DT1 emergency mode active |
|
||||
| `19` | DT1 extended emergency mode active |
|
||||
| `20` | DT1 function test in progress |
|
||||
| `21` | DT1 duration test in progress |
|
||||
| `22` | DT1 hardwired inhibit active |
|
||||
| `23` | DT1 hardwired switch on |
|
||||
| `24` | DT1 integral emergency gear |
|
||||
| `25` | DT1 maintained gear |
|
||||
| `26` | DT1 switched maintained gear |
|
||||
| `27` | DT1 auto test capability |
|
||||
| `28` | DT1 adjustable emergency level |
|
||||
| `29` | DT1 hardwired inhibit supported |
|
||||
| `30` | DT1 physical selection supported |
|
||||
| `31` | DT1 relight in rest mode supported |
|
||||
| `32` | DT4 leading edge running |
|
||||
| `33` | DT4 trailing edge running |
|
||||
| `34` | DT4 reference measurement running |
|
||||
| `35` | DT4 non-log curve active |
|
||||
| `36` | DT4 can query load over-current shutdown |
|
||||
| `37` | DT4 can query open circuit |
|
||||
| `38` | DT4 can query load decrease |
|
||||
| `39` | DT4 can query load increase |
|
||||
| `40` | DT4 can query thermal shutdown |
|
||||
| `41` | DT4 can query thermal overload |
|
||||
| `42` | DT4 physical selection supported |
|
||||
| `43` | DT4 can query temperature |
|
||||
| `44` | DT4 can query supply voltage |
|
||||
| `45` | DT4 can query supply frequency |
|
||||
| `46` | DT4 can query load voltage |
|
||||
| `47` | DT4 can query load current |
|
||||
| `48` | DT4 can query real load power |
|
||||
| `49` | DT4 can query load rating |
|
||||
| `50` | DT4 can query current overload |
|
||||
| `51` | DT4 can select non-log curve |
|
||||
| `52` | DT4 can query unsuitable load |
|
||||
| `53` | DT4 load over-current shutdown |
|
||||
| `54` | DT4 open circuit detected |
|
||||
| `55` | DT4 load decrease detected |
|
||||
| `56` | DT4 load increase detected |
|
||||
| `57` | DT4 thermal shutdown |
|
||||
| `58` | DT4 thermal overload reduction |
|
||||
| `59` | DT4 reference failed |
|
||||
| `60` | DT4 unsuitable load |
|
||||
| `61` | DT4 supply voltage out of limits |
|
||||
| `62` | DT4 supply frequency out of limits |
|
||||
| `63` | DT4 load voltage out of limits |
|
||||
| `64` | DT4 load current overload |
|
||||
| `65` | DT5 output range selectable |
|
||||
| `66` | DT5 pull-up selectable |
|
||||
| `67` | DT5 fault detection selectable |
|
||||
| `68` | DT5 mains relay |
|
||||
| `69` | DT5 output level queryable |
|
||||
| `70` | DT5 non-log curve supported |
|
||||
| `71` | DT5 output-loss selection supported |
|
||||
| `72` | DT5 selection switch supported |
|
||||
| `73` | DT5 output fault detected |
|
||||
| `74` | DT5 0-10V operation |
|
||||
| `75` | DT5 pull-up on |
|
||||
| `76` | DT5 non-log curve active |
|
||||
| `77` | DT6 power supply integrated |
|
||||
| `78` | DT6 LED module integrated |
|
||||
| `79` | DT6 AC supply possible |
|
||||
| `80` | DT6 DC supply possible |
|
||||
| `81` | DT6 PWM possible |
|
||||
| `82` | DT6 AM possible |
|
||||
| `83` | DT6 current control possible |
|
||||
| `84` | DT6 high current possible |
|
||||
| `85` | DT6 can query short circuit |
|
||||
| `86` | DT6 can query open circuit |
|
||||
| `87` | DT6 can query load decrease |
|
||||
| `88` | DT6 can query load increase |
|
||||
| `89` | DT6 can query current protector |
|
||||
| `90` | DT6 can query thermal shutdown |
|
||||
| `91` | DT6 can query thermal overload |
|
||||
| `92` | DT6 short circuit |
|
||||
| `93` | DT6 open circuit |
|
||||
| `94` | DT6 load decrease |
|
||||
| `95` | DT6 load increase |
|
||||
| `96` | DT6 current protector active |
|
||||
| `97` | DT6 thermal shutdown |
|
||||
| `98` | DT6 thermal overload |
|
||||
| `99` | DT6 reference failed |
|
||||
| `100` | DT6 PWM active |
|
||||
| `101` | DT6 AM active |
|
||||
| `102` | DT6 current controlled output |
|
||||
| `103` | DT6 high current active |
|
||||
| `104` | DT6 non-log curve active |
|
||||
| `105` | DT8 xy out of range |
|
||||
| `106` | DT8 color-temperature out of range |
|
||||
| `107` | DT8 auto calibration active |
|
||||
| `108` | DT8 auto calibration success |
|
||||
| `109` | DT8 xy active |
|
||||
| `110` | DT8 color-temperature active |
|
||||
| `111` | DT8 primary-N active |
|
||||
| `112` | DT8 RGBWAF active |
|
||||
| `113` | DT8 xy capable |
|
||||
| `114` | DT8 color-temperature capable |
|
||||
| `115` | DT8 primary-N capable |
|
||||
| `116` | DT8 RGBWAF capable |
|
||||
| `117` | DT6 physical selection supported |
|
||||
| `118` | DT6 current protector enabled |
|
||||
| `119` | DT1 control gear failure |
|
||||
|
||||
## Provisioned Overrides
|
||||
|
||||
Provisioned bridge models are applied after the generated map. If a Modbus model uses the same space and human address as a generated point, the model replaces that generated point.
|
||||
|
||||
For Modbus models:
|
||||
|
||||
- `external.objectType` must be `coil`, `discrete_input`, `input_register`, or `holding_register`.
|
||||
- `external.registerAddress` is the human address, not the zero-based wire offset.
|
||||
- `external.bitIndex` exposes one bit from a numeric read result for boolean objects.
|
||||
- DALI targets use short addresses `0-63`, groups as `64 + group`, and broadcast as `127`.
|
||||
- DALI query/read operations must target short addresses only.
|
||||
- `valueTransform` scaling, offset, rounding, and clamps are applied by the bridge engine.
|
||||
|
||||
Use `GET /bridge?action=modbus&gw=N` to inspect the effective generated and provisioned bindings served by a running gateway.
|
||||
@@ -4,18 +4,79 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
||||
|
||||
## Layout
|
||||
|
||||
This is the list of top-level directories and their purposes,
|
||||
update as the project evolves:
|
||||
- `apps/`: standard ESP-IDF applications for each firmware role.
|
||||
- `apps/gateway/main/Kconfig.projbuild`: project-visible gateway-role settings such as per-channel native/serial PHY selection, gateway ids, pin mapping, and startup transport policy.
|
||||
- `components/`: reusable components shared by all gateway applications.
|
||||
- `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_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.
|
||||
- `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack, including the gateway-owned BACnet bridge model adapter.
|
||||
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
|
||||
- `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, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway.
|
||||
- `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_usb_setup/`: optional USB Serial/JTAG setup bridge; disabled by default so USB remains available for debug at boot.
|
||||
- `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.
|
||||
|
||||
## 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, and 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`, 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, and BOOT-button Wi-Fi credential clearing. Startup behavior is configured in `main/Kconfig.projbuild`: BLE is enabled by default, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, and the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected. 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 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.
|
||||
|
||||
## KNX Security
|
||||
|
||||
KNX Data Secure and KNXnet/IP Secure support are controlled by `GATEWAY_KNX_DATA_SECURE_SUPPORTED` and `GATEWAY_KNX_IP_SECURE_SUPPORTED`. The current KNXnet/IP Secure flag reserves and reports secure service capability, while runtime secure-session transport is still reported as not implemented until that path is wired. The gateway derives its KNX serial identity from the ESP base MAC, and the development factory setup key is deterministically derived from that KNX serial so the same board keeps the same FDSK across NVS erases.
|
||||
|
||||
The KNXnet/IP tunnel can start from the built-in default configuration before any ETS download. KNX TP-UART is enabled only when `GATEWAY_KNX_TP_UART_PORT` is `0`, `1`, or `2`; set that UART port to `-1` for IP-only operation. UART TX/RX GPIO values of `-1` mean use the ESP-IDF target default pins for that UART, not disabled. `GATEWAY_KNX_TP_UART_9BIT_MODE` enables the NCN5120/OpenKNX-style 9-bit host frame on the wire, represented on ESP-IDF as 8 data bits plus even parity. Non-UART GPIO options use `-1` as disabled, including the KNX programming button, KNX programming LED, setup AP button, Wi-Fi reset button, and status LED.
|
||||
|
||||
When no KNX bridge config or ETS application data has been downloaded, the KNXnet/IP router starts in commissioning mode: OpenKNX receives tunnel programming traffic from ETS, while DALI group routing and REG1-Dali function-property actions stay inactive until ETS reports a configured application.
|
||||
|
||||
The bridge service exposes one shared KNXnet/IP endpoint per physical gateway on the configured UDP port. Per-channel DALI/KNX bridge runtimes keep their own group-address mappings behind that endpoint, and incoming group writes are dispatched to the matching channel instead of starting one UDP socket per DALI channel.
|
||||
|
||||
KNX programming mode can be controlled locally with `GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO`, and `GATEWAY_KNX_PROGRAMMING_LED_GPIO` mirrors the current programming-mode state. The setup AP entry button is configured separately with `GATEWAY_SETUP_AP_BUTTON_GPIO`; Wi-Fi credential reset remains a separate long-press function on `GATEWAY_BOOT_BUTTON_GPIO` when enabled.
|
||||
|
||||
When `GATEWAY_KNX_SECURITY_DEV_ENDPOINTS` is enabled, the bridge HTTP action surface exposes development-only operations for reading, writing, generating, and resetting the factory setup key, exporting the factory certificate payload, and clearing local KNX security failure diagnostics. These endpoints require explicit confirmation fields in the JSON body and should stay disabled in production builds. The default development storage mode is plain NVS via `GATEWAY_KNX_SECURITY_PLAIN_NVS`; production builds should replace that with encrypted NVS, flash encryption, and secure boot before handling real commissioning keys.
|
||||
|
||||
The normal bridge status response includes a `knx.security` object with compile-time capability flags, storage mode, factory setup key metadata, factory certificate metadata, and security failure counters/log entries. Secret FDSK strings are returned only by the explicit development actions, not by passive status polling.
|
||||
|
||||
## Modbus
|
||||
|
||||
Modbus TCP, RTU, and ASCII are owned by `gateway/components/gateway_modbus` and started through the per-channel bridge service. The gateway keeps the existing bridge config JSON shape with a top-level `modbus` object containing `transport`, `host`, `port`, and `unitID`, and now adds nested serial UART settings for RTU/ASCII. Parsing and runtime behavior live in the gateway project rather than in `dali_cpp`.
|
||||
|
||||
See `MODBUS.md` for transport setup, UART0 policy, RS485 wiring, runtime `@DALIGW` management commands, supported function codes, and the full generated address map with address formulas.
|
||||
|
||||
The first generated map slice creates stable points for every DALI short address `0-63` whether the device is online, offline, or never seen. Per short address, the generated map reserves a 32-point stride in each Modbus space:
|
||||
|
||||
- Coils: command triggers such as on, off, recall max, and recall min.
|
||||
- Discrete inputs: inventory, online, supported device-type, cache-known, and base status bit positions.
|
||||
- Holding registers: writable brightness, color temperature, group mask, power-on level, system-failure level, min/max level, and fade time.
|
||||
- Input registers: read-only inventory state, primary type, type mask, cached actual level, scene id, raw status placeholder, group mask, and cached settings.
|
||||
|
||||
Unknown numeric values read as `0xFFFF`; booleans read as false unless inventory or cache state proves otherwise. Provisioned Modbus models still work as overrides at their configured Modbus point, and normal generated reads prefer gateway cache state to avoid DALI bus polling.
|
||||
|
||||
An extension discrete-input range starts immediately after the legacy `0-63` short-address block. It publishes decoded DALI status, failure, and feature bits as individual booleans for base status, DT1 emergency, DT4/5/6 control-gear feature/failure status, and DT8 color status/features. This keeps existing Modbus addresses stable while making bit-level diagnostics readable without consumers masking packed status registers.
|
||||
|
||||
## BACnet/IP
|
||||
|
||||
BACnet/IP is owned by `gateway/components/gateway_bacnet` and is started through the per-channel bridge service. Runtime BACnet server settings live under top-level `bacnetServer` in bridge config:
|
||||
|
||||
```json
|
||||
{
|
||||
"bacnetServer": {
|
||||
"deviceInstance": 4194303,
|
||||
"localAddress": "",
|
||||
"udpPort": 47808
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
@@ -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 log
|
||||
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
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -71,18 +71,20 @@ config GATEWAY_CHANNEL1_NATIVE_BAUDRATE
|
||||
config GATEWAY_CHANNEL1_SERIAL_TX_PIN
|
||||
int "Serial PHY TX pin"
|
||||
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
|
||||
range 0 48
|
||||
range -1 48
|
||||
default 1
|
||||
help
|
||||
ESP32-S3 GPIO used by the channel 1 serial PHY transmit pin.
|
||||
ESP32-S3 GPIO used by the channel 1 serial PHY transmit pin. Set to -1
|
||||
to keep the UART driver's default TX routing.
|
||||
|
||||
config GATEWAY_CHANNEL1_SERIAL_RX_PIN
|
||||
int "Serial PHY RX pin"
|
||||
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
|
||||
range 0 48
|
||||
range -1 48
|
||||
default 2
|
||||
help
|
||||
ESP32-S3 GPIO used by the channel 1 serial PHY receive pin.
|
||||
ESP32-S3 GPIO used by the channel 1 serial PHY receive pin. Set to -1
|
||||
to keep the UART driver's default RX routing.
|
||||
|
||||
config GATEWAY_CHANNEL1_SERIAL_BAUDRATE
|
||||
int "Serial PHY baudrate"
|
||||
@@ -178,18 +180,20 @@ config GATEWAY_CHANNEL2_NATIVE_BAUDRATE
|
||||
config GATEWAY_CHANNEL2_SERIAL_TX_PIN
|
||||
int "Serial PHY TX pin"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
|
||||
range 0 48
|
||||
range -1 48
|
||||
default 6
|
||||
help
|
||||
ESP32-S3 GPIO used by the channel 2 serial PHY transmit pin.
|
||||
ESP32-S3 GPIO used by the channel 2 serial PHY transmit pin. Set to -1
|
||||
to keep the UART driver's default TX routing.
|
||||
|
||||
config GATEWAY_CHANNEL2_SERIAL_RX_PIN
|
||||
int "Serial PHY RX pin"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
|
||||
range 0 48
|
||||
range -1 48
|
||||
default 7
|
||||
help
|
||||
ESP32-S3 GPIO used by the channel 2 serial PHY receive pin.
|
||||
ESP32-S3 GPIO used by the channel 2 serial PHY receive pin. Set to -1
|
||||
to keep the UART driver's default RX routing.
|
||||
|
||||
config GATEWAY_CHANNEL2_SERIAL_BAUDRATE
|
||||
int "Serial PHY baudrate"
|
||||
@@ -221,8 +225,39 @@ endmenu
|
||||
|
||||
menu "Gateway Cache"
|
||||
|
||||
config GATEWAY_CACHE_SUPPORTED
|
||||
bool "Gateway cache support"
|
||||
default y
|
||||
help
|
||||
Enables the gateway cache facade. When disabled, internal scene and group
|
||||
commands still persist through direct NVS writes.
|
||||
|
||||
config GATEWAY_CACHE_START_ENABLED
|
||||
bool "Start gateway cache in deferred mode"
|
||||
depends on GATEWAY_CACHE_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Starts the deferred RAM cache and background flush task at boot. Disable
|
||||
this to keep scene and group persistence in direct-NVS mode.
|
||||
|
||||
config GATEWAY_CACHE_RECONCILIATION_ENABLED
|
||||
bool "Enable cache reconciliation tracking"
|
||||
depends on GATEWAY_CACHE_SUPPORTED && GATEWAY_CACHE_START_ENABLED
|
||||
default y
|
||||
help
|
||||
Tracks outside DALI bus mutations as update-needed flags for resumable
|
||||
reconciliation work.
|
||||
|
||||
config GATEWAY_CACHE_FULL_STATE_MIRROR
|
||||
bool "Enable full DALI state mirror"
|
||||
depends on GATEWAY_CACHE_RECONCILIATION_ENABLED
|
||||
default n
|
||||
help
|
||||
Enables the heavier full bus-state mirror path for future reconciliation.
|
||||
|
||||
config GATEWAY_CACHE_FLUSH_INTERVAL_MS
|
||||
int "Cache flush interval ms"
|
||||
depends on GATEWAY_CACHE_SUPPORTED && GATEWAY_CACHE_START_ENABLED
|
||||
range 100 600000
|
||||
default 5000
|
||||
help
|
||||
@@ -230,6 +265,7 @@ config GATEWAY_CACHE_FLUSH_INTERVAL_MS
|
||||
|
||||
choice GATEWAY_CACHE_CONFLICT_PRIORITY
|
||||
prompt "Cache conflict priority default"
|
||||
depends on GATEWAY_CACHE_RECONCILIATION_ENABLED
|
||||
default GATEWAY_CACHE_OUTSIDE_BUS_FIRST
|
||||
help
|
||||
Default source of truth to use when future cache reconciliation detects
|
||||
@@ -348,6 +384,109 @@ config GATEWAY_SMARTCONFIG_TIMEOUT_SEC
|
||||
help
|
||||
Timeout passed to ESP-IDF smartconfig before provisioning restarts internally.
|
||||
|
||||
config GATEWAY_ETHERNET_SUPPORTED
|
||||
bool "Wired Ethernet gateway transport is supported"
|
||||
default y
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_W5500
|
||||
help
|
||||
Enables the ESP-IDF Ethernet driver path for wired gateway networking. The
|
||||
native gateway currently provisions a W5500 SPI Ethernet controller and
|
||||
exposes the same HTTP, UDP, KNXnet/IP, BACnet/IP, Modbus TCP, and cloud
|
||||
services over the wired netif.
|
||||
|
||||
config GATEWAY_START_ETHERNET_ENABLED
|
||||
bool "Start wired Ethernet at startup"
|
||||
depends on GATEWAY_ETHERNET_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Starts the configured W5500 Ethernet netif during boot and uses DHCP for
|
||||
address assignment. Disable this when the board is built without Ethernet
|
||||
hardware even though the firmware keeps Ethernet support compiled in.
|
||||
|
||||
config GATEWAY_ETHERNET_IGNORE_INIT_FAILURE
|
||||
bool "Ignore wired Ethernet init failures"
|
||||
depends on GATEWAY_START_ETHERNET_ENABLED
|
||||
default y
|
||||
help
|
||||
Continues booting if the W5500 Ethernet controller is missing, held in
|
||||
reset, miswired, or otherwise fails ESP-IDF Ethernet driver startup.
|
||||
Disable this for strict hardware bring-up where Ethernet failure should
|
||||
abort application startup.
|
||||
|
||||
menu "Gateway Wired Ethernet"
|
||||
depends on GATEWAY_ETHERNET_SUPPORTED
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_SPI_HOST
|
||||
int "W5500 SPI host number"
|
||||
range 1 2
|
||||
default 1
|
||||
help
|
||||
SPI host used for the W5500 Ethernet controller. On ESP32-S3, host 1 maps
|
||||
to SPI2 and host 2 maps to SPI3; do not use host 0 because it is reserved
|
||||
by flash/PSRAM.
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_SCLK_GPIO
|
||||
int "W5500 SPI SCLK GPIO"
|
||||
range 0 48
|
||||
default 14
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_MOSI_GPIO
|
||||
int "W5500 SPI MOSI GPIO"
|
||||
range 0 48
|
||||
default 13
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_MISO_GPIO
|
||||
int "W5500 SPI MISO GPIO"
|
||||
range 0 48
|
||||
default 12
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_CS_GPIO
|
||||
int "W5500 SPI CS GPIO"
|
||||
range 0 48
|
||||
default 15
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_INT_GPIO
|
||||
int "W5500 interrupt GPIO"
|
||||
range -1 48
|
||||
default 4
|
||||
help
|
||||
W5500 interrupt pin. Set to -1 to disable interrupt mode and poll RX
|
||||
status periodically.
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS
|
||||
int "W5500 polling period ms"
|
||||
range 0 1000
|
||||
default 0
|
||||
help
|
||||
Polling interval used when the W5500 interrupt pin is disabled. A value of
|
||||
0 keeps interrupt mode when an interrupt GPIO is configured; if the
|
||||
interrupt GPIO is -1, the gateway falls back to 100 ms polling.
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_CLOCK_MHZ
|
||||
int "W5500 SPI clock MHz"
|
||||
range 5 80
|
||||
default 36
|
||||
|
||||
config GATEWAY_ETHERNET_PHY_RESET_GPIO
|
||||
int "Ethernet PHY reset GPIO"
|
||||
range -1 48
|
||||
default 5
|
||||
help
|
||||
GPIO used to reset the W5500 PHY. Set to -1 to disable hardware reset.
|
||||
|
||||
config GATEWAY_ETHERNET_PHY_ADDR
|
||||
int "Ethernet PHY address"
|
||||
range 0 31
|
||||
default 1
|
||||
|
||||
config GATEWAY_ETHERNET_RX_TASK_STACK_SIZE
|
||||
int "Ethernet RX task stack bytes"
|
||||
range 2048 8192
|
||||
default 3072
|
||||
|
||||
endmenu
|
||||
|
||||
config GATEWAY_BRIDGE_SUPPORTED
|
||||
bool "dali_cpp bridge runtime is supported"
|
||||
default y
|
||||
@@ -355,22 +494,111 @@ config GATEWAY_BRIDGE_SUPPORTED
|
||||
Enables per-channel dali_cpp bridge model provisioning, execution, and protocol adapter state.
|
||||
|
||||
config GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
bool "Modbus TCP bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
bool "Modbus bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Enables the per-channel Modbus TCP adapter backed by DaliModbusBridge. Runtime startup still requires persisted bridge config with Modbus settings.
|
||||
Enables the gateway-owned per-channel Modbus TCP, RTU, or ASCII server,
|
||||
generated DALI point map, and provisioned model overrides.
|
||||
|
||||
config GATEWAY_START_MODBUS_BRIDGE_ENABLED
|
||||
bool "Start Modbus TCP bridge at startup"
|
||||
bool "Start Modbus bridge at startup"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Starts configured Modbus TCP listeners at boot. Disabled by default so ports are opened only after provisioning or explicit runtime start.
|
||||
Starts the configured or Kconfig-default Modbus listener at boot. Disabled
|
||||
by default so ports are opened only after provisioning or explicit runtime start.
|
||||
|
||||
choice GATEWAY_MODBUS_DEFAULT_TRANSPORT
|
||||
prompt "Default Modbus transport"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
default GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP
|
||||
help
|
||||
Selects the default Modbus transport used before runtime bridge config is saved.
|
||||
|
||||
config GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP
|
||||
bool "TCP server"
|
||||
depends on GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED
|
||||
|
||||
config GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU
|
||||
bool "RTU server on UART"
|
||||
|
||||
config GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII
|
||||
bool "ASCII server on UART"
|
||||
|
||||
endchoice
|
||||
|
||||
config GATEWAY_MODBUS_TCP_PORT
|
||||
int "Default Modbus TCP port"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP
|
||||
range 1 65535
|
||||
default 1502
|
||||
|
||||
config GATEWAY_MODBUS_UNIT_ID
|
||||
int "Default Modbus unit id"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED
|
||||
range 0 247
|
||||
default 1
|
||||
help
|
||||
Unit id used by the default Modbus server. A value of 0 keeps the existing
|
||||
gateway wildcard behavior for incoming requests.
|
||||
|
||||
config GATEWAY_MODBUS_SERIAL_UART_PORT
|
||||
int "Default Modbus serial UART port"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
range -1 2
|
||||
default 1
|
||||
help
|
||||
UART used by the default Modbus serial server. Set to -1 to disable the
|
||||
default serial UART runtime for this function.
|
||||
|
||||
config GATEWAY_MODBUS_ALLOW_UART0
|
||||
bool "Allow Modbus/setup to claim UART0"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
default n
|
||||
help
|
||||
UART0 is normally reserved for the ESP-IDF console. Enable only when the
|
||||
console has been moved away from UART0 or the deployment intentionally
|
||||
repurposes it.
|
||||
|
||||
config GATEWAY_MODBUS_SERIAL_TX_PIN
|
||||
int "Default Modbus serial TX pin"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
range -1 48
|
||||
default -1
|
||||
|
||||
config GATEWAY_MODBUS_SERIAL_RX_PIN
|
||||
int "Default Modbus serial RX pin"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
range -1 48
|
||||
default -1
|
||||
|
||||
config GATEWAY_MODBUS_SERIAL_BAUDRATE
|
||||
int "Default Modbus serial baudrate"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
range 1200 921600
|
||||
default 9600
|
||||
|
||||
config GATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MS
|
||||
int "Default Modbus serial response timeout ms"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
range 1 1000
|
||||
default 20
|
||||
|
||||
config GATEWAY_MODBUS_SERIAL_RS485_ENABLED
|
||||
bool "Enable Modbus RS485 half-duplex mode"
|
||||
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
default n
|
||||
|
||||
config GATEWAY_MODBUS_SERIAL_RS485_DE_PIN
|
||||
int "Default Modbus RS485 DE/RTS pin"
|
||||
depends on GATEWAY_MODBUS_SERIAL_RS485_ENABLED
|
||||
range -1 48
|
||||
default -1
|
||||
|
||||
config GATEWAY_BACNET_BRIDGE_SUPPORTED
|
||||
bool "BACnet/IP bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||
default n
|
||||
help
|
||||
Enables BACnet bridge configuration, binding discovery, and the bacnet-stack BACnet/IP server adapter.
|
||||
@@ -383,9 +611,192 @@ config GATEWAY_START_BACNET_BRIDGE_ENABLED
|
||||
help
|
||||
Starts configured BACnet/IP object bindings at boot. Disabled by default so the UDP BACnet/IP port is opened only after provisioning or explicit runtime start.
|
||||
|
||||
config GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
bool "KNX to DALI bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||
default n
|
||||
help
|
||||
Enables the gateway-owned KNX group-address router and KNXnet/IP TP/IP
|
||||
router. Group addresses use the configured main group, middle groups as
|
||||
DALI data types, and subgroups matching DALI short address structure.
|
||||
|
||||
config GATEWAY_START_KNX_BRIDGE_ENABLED
|
||||
bool "Start KNX/IP bridge at startup"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Starts the KNXnet/IP tunneling/multicast listener at boot. Disabled by
|
||||
default so UDP port 3671 is opened only after provisioning or explicit start.
|
||||
|
||||
config GATEWAY_KNX_DATA_SECURE_SUPPORTED
|
||||
bool "Enable KNX Data Secure support"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Compiles the OpenKNX SecurityInterfaceObject and SecureApplicationLayer
|
||||
into the ETS runtime. This is the application-layer security path used
|
||||
for secure KNX group-object and ETS tool traffic.
|
||||
|
||||
config GATEWAY_KNX_IP_SECURE_SUPPORTED
|
||||
bool "Enable KNXnet/IP Secure support"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Builds gateway support for KNXnet/IP Secure tunneling and routing. The
|
||||
secure session transport is implemented by the gateway-owned KNX/IP
|
||||
router and is separate from KNX Data Secure APDU handling.
|
||||
|
||||
config GATEWAY_KNX_SECURITY_DEV_ENDPOINTS
|
||||
bool "Enable KNX security development HTTP endpoints"
|
||||
depends on GATEWAY_KNX_DATA_SECURE_SUPPORTED || GATEWAY_KNX_IP_SECURE_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Exposes development-only HTTP actions for reading, writing, generating,
|
||||
and resetting KNX security material. Disable this for production builds.
|
||||
|
||||
config GATEWAY_KNX_SECURITY_PLAIN_NVS
|
||||
bool "Store KNX security material in plain NVS"
|
||||
depends on GATEWAY_KNX_DATA_SECURE_SUPPORTED || GATEWAY_KNX_IP_SECURE_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Stores development KNX security material in normal NVS. This is useful
|
||||
during bring-up, but production builds should replace it with encrypted
|
||||
NVS, flash encryption, and secure boot before exposing real keys.
|
||||
|
||||
config GATEWAY_KNX_MAIN_GROUP
|
||||
int "KNX DALI main group"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 0 31
|
||||
default 0
|
||||
help
|
||||
Main group used by the built-in KNX to DALI router. Middle groups select
|
||||
the data type and subgroups select broadcast, short-address, or group targets.
|
||||
|
||||
config GATEWAY_KNX_TUNNEL_ENABLED
|
||||
bool "Enable KNXnet/IP tunneling mode"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
default y
|
||||
|
||||
config GATEWAY_KNX_MULTICAST_ENABLED
|
||||
bool "Enable KNXnet/IP multicast routing mode"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
default y
|
||||
|
||||
config GATEWAY_KNX_UDP_PORT
|
||||
int "KNXnet/IP UDP port"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 1 65535
|
||||
default 3671
|
||||
|
||||
config GATEWAY_KNX_MULTICAST_ADDRESS
|
||||
string "KNXnet/IP multicast address"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_MULTICAST_ENABLED
|
||||
default "224.0.23.12"
|
||||
|
||||
config GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS
|
||||
int "KNXnet/IP interface individual address raw value"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 0 65535
|
||||
default 65281
|
||||
help
|
||||
Raw 16-bit individual address advertised by the KNXnet/IP interface.
|
||||
The default 65281 is 15.15.1.
|
||||
|
||||
config GATEWAY_KNX_INDIVIDUAL_ADDRESS
|
||||
int "Logical KNX-DALI gateway individual address raw value"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 0 65535
|
||||
default 65534
|
||||
help
|
||||
Raw 16-bit individual address used by the ETS-programmable KNX-DALI gateway device.
|
||||
The default 65534 is 15.15.254, used as the unprogrammed logical device address.
|
||||
|
||||
config GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO
|
||||
int "KNX programming button GPIO"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range -1 48
|
||||
default -1
|
||||
help
|
||||
GPIO used to toggle KNX programming mode. Set to -1 to disable the local
|
||||
KNX programming button.
|
||||
|
||||
config GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW
|
||||
bool "KNX programming button is active low"
|
||||
depends on GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO >= 0
|
||||
default y
|
||||
|
||||
config GATEWAY_KNX_PROGRAMMING_LED_GPIO
|
||||
int "KNX programming LED GPIO"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range -1 48
|
||||
default -1
|
||||
help
|
||||
GPIO used to show KNX programming mode. Set to -1 to disable the local
|
||||
KNX programming LED.
|
||||
|
||||
config GATEWAY_KNX_PROGRAMMING_LED_ACTIVE_HIGH
|
||||
bool "KNX programming LED is active high"
|
||||
depends on GATEWAY_KNX_PROGRAMMING_LED_GPIO >= 0
|
||||
default y
|
||||
|
||||
config GATEWAY_KNX_TP_UART_PORT
|
||||
int "KNX TP UART port"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range -1 2
|
||||
default -1
|
||||
help
|
||||
UART used by the KNX TP-UART interface. Set to -1 to disable TP-UART
|
||||
while keeping KNXnet/IP tunnelling and routing available.
|
||||
|
||||
config GATEWAY_KNX_TP_TX_PIN
|
||||
int "KNX TP UART TX pin"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range -1 48
|
||||
default -1
|
||||
help
|
||||
GPIO used by the KNX TP-UART TX pin. Set to -1 to keep the UART driver's
|
||||
default TX routing.
|
||||
|
||||
config GATEWAY_KNX_TP_RX_PIN
|
||||
int "KNX TP UART RX pin"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range -1 48
|
||||
default -1
|
||||
help
|
||||
GPIO used by the KNX TP-UART RX pin. Set to -1 to keep the UART driver's
|
||||
default RX routing.
|
||||
|
||||
config GATEWAY_KNX_TP_BAUDRATE
|
||||
int "KNX TP UART baudrate"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 1200 921600
|
||||
default 19200
|
||||
|
||||
config GATEWAY_KNX_TP_UART_9BIT_MODE
|
||||
bool "KNX TP UART 9-bit mode"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Enable the NCN5120/OpenKNX-style 9-bit UART frame on the wire. ESP-IDF
|
||||
exposes this as 8 data bits plus even parity, matching the TP-UART host
|
||||
mode commonly described as 19200 baud 9-bit UART. Disable only for
|
||||
hardware wired for 8N1 host UART mode.
|
||||
|
||||
config GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE
|
||||
int "KNX/IP bridge task stack bytes"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 6144 24576
|
||||
default 12288
|
||||
|
||||
config GATEWAY_BRIDGE_KNX_TASK_PRIORITY
|
||||
int "KNX/IP bridge task priority"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 1 10
|
||||
default 5
|
||||
|
||||
config GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
||||
bool "MQTT cloud bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||
default y
|
||||
help
|
||||
Enables per-channel DaliCloudBridge provisioning and MQTT downlink execution.
|
||||
@@ -461,6 +872,72 @@ config GATEWAY_USB_SETUP_READ_TIMEOUT_MS
|
||||
range 1 1000
|
||||
default 20
|
||||
|
||||
config GATEWAY_485_CONTROL_ENABLED
|
||||
bool "Enable UART0 Lua control bridge"
|
||||
default n
|
||||
help
|
||||
Claims UART0 for the Lua-compatible framed gateway control channel at boot.
|
||||
This requires moving the ESP-IDF console away from UART0 and prevents Modbus
|
||||
serial from using UART0 at runtime.
|
||||
|
||||
config GATEWAY_485_CONTROL_BAUDRATE
|
||||
int "UART0 control baudrate"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range 1200 921600
|
||||
default 9600
|
||||
|
||||
config GATEWAY_485_CONTROL_TX_PIN
|
||||
int "UART0 control TX pin"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range -1 48
|
||||
default -1
|
||||
help
|
||||
Leave at -1 to keep the current UART0 TX routing.
|
||||
|
||||
config GATEWAY_485_CONTROL_RX_PIN
|
||||
int "UART0 control RX pin"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range -1 48
|
||||
default -1
|
||||
help
|
||||
Leave at -1 to keep the current UART0 RX routing.
|
||||
|
||||
config GATEWAY_485_CONTROL_RX_BUFFER
|
||||
int "UART0 control RX buffer bytes"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range 64 4096
|
||||
default 256
|
||||
|
||||
config GATEWAY_485_CONTROL_TX_BUFFER
|
||||
int "UART0 control TX buffer bytes"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range 64 4096
|
||||
default 256
|
||||
|
||||
config GATEWAY_485_CONTROL_READ_TIMEOUT_MS
|
||||
int "UART0 control read timeout ms"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range 1 1000
|
||||
default 20
|
||||
|
||||
config GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS
|
||||
int "UART0 control write timeout ms"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range 1 1000
|
||||
default 20
|
||||
|
||||
config GATEWAY_485_CONTROL_TASK_STACK_SIZE
|
||||
int "UART0 control task stack bytes"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range 2048 16384
|
||||
default 4096
|
||||
|
||||
config GATEWAY_485_CONTROL_TASK_PRIORITY
|
||||
int "UART0 control task priority"
|
||||
depends on GATEWAY_485_CONTROL_ENABLED
|
||||
range 1 10
|
||||
default 4
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Gateway Network Services"
|
||||
@@ -502,23 +979,44 @@ config GATEWAY_STATUS_LED_ACTIVE_HIGH
|
||||
default y
|
||||
|
||||
config GATEWAY_BOOT_BUTTON_GPIO
|
||||
int "BOOT button GPIO"
|
||||
int "Wi-Fi reset button GPIO"
|
||||
range -1 48
|
||||
default 0
|
||||
default -1
|
||||
help
|
||||
GPIO used for Lua-compatible setup entry and Wi-Fi credential clearing. Set to -1 to disable.
|
||||
GPIO used for long-press Wi-Fi credential clearing. Set to -1 to disable.
|
||||
|
||||
config GATEWAY_BOOT_BUTTON_ACTIVE_LOW
|
||||
bool "BOOT button is active low"
|
||||
bool "Wi-Fi reset button is active low"
|
||||
depends on GATEWAY_BOOT_BUTTON_GPIO >= 0
|
||||
default y
|
||||
|
||||
config GATEWAY_BOOT_BUTTON_LONG_PRESS_MS
|
||||
int "BOOT button long press ms"
|
||||
int "Wi-Fi reset button long press ms"
|
||||
depends on GATEWAY_BOOT_BUTTON_GPIO >= 0
|
||||
range 500 10000
|
||||
default 3000
|
||||
|
||||
config GATEWAY_SETUP_AP_BUTTON_GPIO
|
||||
int "Setup AP button GPIO"
|
||||
range -1 48
|
||||
default 0
|
||||
help
|
||||
GPIO used for entering setup AP mode. Set to -1 to disable local setup
|
||||
AP entry by GPIO.
|
||||
|
||||
config GATEWAY_SETUP_AP_BUTTON_ACTIVE_LOW
|
||||
bool "Setup AP button is active low"
|
||||
depends on GATEWAY_SETUP_AP_BUTTON_GPIO >= 0
|
||||
default y
|
||||
|
||||
config GATEWAY_BUTTON_TASK_STACK_SIZE
|
||||
int "Gateway button task stack bytes"
|
||||
depends on GATEWAY_BOOT_BUTTON_GPIO >= 0 || GATEWAY_SETUP_AP_BUTTON_GPIO >= 0
|
||||
range 3072 12288
|
||||
default 8192
|
||||
help
|
||||
Stack used by the GPIO button task and one-shot setup AP task.
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
+487
-23
@@ -6,6 +6,7 @@
|
||||
#include "gateway_core.hpp"
|
||||
#include "gateway_network.hpp"
|
||||
#include "gateway_runtime.hpp"
|
||||
#include "gateway_485_control.hpp"
|
||||
#include "gateway_usb_setup.hpp"
|
||||
|
||||
#include "esp_log.h"
|
||||
@@ -39,6 +40,21 @@
|
||||
#define CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS 3000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO
|
||||
#ifdef CONFIG_GATEWAY_BOOT_BUTTON_GPIO
|
||||
#define CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO CONFIG_GATEWAY_BOOT_BUTTON_GPIO
|
||||
#ifdef CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW
|
||||
#define CONFIG_GATEWAY_SETUP_AP_BUTTON_ACTIVE_LOW 1
|
||||
#endif
|
||||
#else
|
||||
#define CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO -1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BUTTON_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BUTTON_TASK_STACK_SIZE 8192
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX
|
||||
#define CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX 0
|
||||
#endif
|
||||
@@ -55,10 +71,94 @@
|
||||
#define CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS 20
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_BAUDRATE
|
||||
#define CONFIG_GATEWAY_485_CONTROL_BAUDRATE 9600
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_TX_PIN
|
||||
#define CONFIG_GATEWAY_485_CONTROL_TX_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_RX_PIN
|
||||
#define CONFIG_GATEWAY_485_CONTROL_RX_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_RX_BUFFER
|
||||
#define CONFIG_GATEWAY_485_CONTROL_RX_BUFFER 256
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_TX_BUFFER
|
||||
#define CONFIG_GATEWAY_485_CONTROL_TX_BUFFER 256
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS
|
||||
#define CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS 20
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS
|
||||
#define CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS 20
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE 4096
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY
|
||||
#define CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY 4
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC
|
||||
#define CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC 60
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE
|
||||
#define CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO 14
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO 13
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO 12
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO 15
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO 4
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ 36
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO 5
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_PHY_ADDR
|
||||
#define CONFIG_GATEWAY_ETHERNET_PHY_ADDR 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE 3072
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE 6144
|
||||
#endif
|
||||
@@ -67,6 +167,38 @@
|
||||
#define CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY 4
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_TCP_PORT
|
||||
#define CONFIG_GATEWAY_MODBUS_TCP_PORT 1502
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_UNIT_ID
|
||||
#define CONFIG_GATEWAY_MODBUS_UNIT_ID 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT
|
||||
#define CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_SERIAL_TX_PIN
|
||||
#define CONFIG_GATEWAY_MODBUS_SERIAL_TX_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_SERIAL_RX_PIN
|
||||
#define CONFIG_GATEWAY_MODBUS_SERIAL_RX_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_SERIAL_BAUDRATE
|
||||
#define CONFIG_GATEWAY_MODBUS_SERIAL_BAUDRATE 9600
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MS
|
||||
#define CONFIG_GATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MS 20
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_MODBUS_SERIAL_RS485_DE_PIN
|
||||
#define CONFIG_GATEWAY_MODBUS_SERIAL_RS485_DE_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE 8192
|
||||
#endif
|
||||
@@ -75,6 +207,58 @@
|
||||
#define CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY 5
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE 12288
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY
|
||||
#define CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY 5
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_MAIN_GROUP
|
||||
#define CONFIG_GATEWAY_KNX_MAIN_GROUP 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_UDP_PORT
|
||||
#define CONFIG_GATEWAY_KNX_UDP_PORT 3671
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS
|
||||
#define CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS "224.0.23.12"
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS
|
||||
#define CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS 65534
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS
|
||||
#define CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS 65281
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO
|
||||
#define CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO
|
||||
#define CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_TP_UART_PORT
|
||||
#define CONFIG_GATEWAY_KNX_TP_UART_PORT -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_TP_TX_PIN
|
||||
#define CONFIG_GATEWAY_KNX_TP_TX_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_TP_RX_PIN
|
||||
#define CONFIG_GATEWAY_KNX_TP_RX_PIN -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_TP_BAUDRATE
|
||||
#define CONFIG_GATEWAY_KNX_TP_BAUDRATE 19200
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS
|
||||
#define CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS 5000
|
||||
#endif
|
||||
@@ -90,12 +274,24 @@ constexpr bool kWifiSupported = true;
|
||||
constexpr bool kWifiSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_ETHERNET_SUPPORTED
|
||||
constexpr bool kEthernetSupported = true;
|
||||
#else
|
||||
constexpr bool kEthernetSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_WIFI_STA_ENABLED
|
||||
constexpr bool kWifiStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kWifiStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_ETHERNET_ENABLED
|
||||
constexpr bool kEthernetStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kEthernetStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_BLE_SUPPORTED
|
||||
constexpr bool kBleSupported = true;
|
||||
#else
|
||||
@@ -168,6 +364,30 @@ constexpr bool kBacnetBridgeStartupEnabled = true;
|
||||
constexpr bool kBacnetBridgeStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
constexpr bool kKnxBridgeSupported = true;
|
||||
#else
|
||||
constexpr bool kKnxBridgeSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED
|
||||
constexpr bool kKnxBridgeStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kKnxBridgeStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_KNX_TUNNEL_ENABLED
|
||||
constexpr bool kKnxTunnelEnabled = true;
|
||||
#else
|
||||
constexpr bool kKnxTunnelEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_KNX_MULTICAST_ENABLED
|
||||
constexpr bool kKnxMulticastEnabled = true;
|
||||
#else
|
||||
constexpr bool kKnxMulticastEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
||||
constexpr bool kCloudBridgeSupported = true;
|
||||
#else
|
||||
@@ -180,6 +400,30 @@ constexpr bool kCloudBridgeStartupEnabled = true;
|
||||
constexpr bool kCloudBridgeStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CACHE_SUPPORTED
|
||||
constexpr bool kCacheSupported = true;
|
||||
#else
|
||||
constexpr bool kCacheSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CACHE_START_ENABLED
|
||||
constexpr bool kCacheStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kCacheStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED
|
||||
constexpr bool kCacheReconciliationEnabled = true;
|
||||
#else
|
||||
constexpr bool kCacheReconciliationEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR
|
||||
constexpr bool kCacheFullStateMirrorEnabled = true;
|
||||
#else
|
||||
constexpr bool kCacheFullStateMirrorEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST
|
||||
constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
|
||||
gateway::GatewayCachePriorityMode::kLocalGatewayFirst;
|
||||
@@ -188,6 +432,39 @@ constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
|
||||
gateway::GatewayCachePriorityMode::kOutsideBusFirst;
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU) || \
|
||||
defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
constexpr bool kModbusDefaultSerialTransport = true;
|
||||
#else
|
||||
constexpr bool kModbusDefaultSerialTransport = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_MODBUS_ALLOW_UART0
|
||||
constexpr bool kModbusAllowUart0 = true;
|
||||
#else
|
||||
constexpr bool kModbusAllowUart0 = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_485_CONTROL_ENABLED
|
||||
constexpr bool k485ControlEnabled = true;
|
||||
#else
|
||||
constexpr bool k485ControlEnabled = false;
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_ESP_CONSOLE_UART) && defined(CONFIG_ESP_CONSOLE_UART_NUM) && CONFIG_ESP_CONSOLE_UART_NUM == 0
|
||||
constexpr bool kConsoleOnUart0 = true;
|
||||
#elif defined(CONFIG_CONSOLE_UART) && defined(CONFIG_CONSOLE_UART_NUM) && CONFIG_CONSOLE_UART_NUM == 0
|
||||
constexpr bool kConsoleOnUart0 = true;
|
||||
#else
|
||||
constexpr bool kConsoleOnUart0 = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_MODBUS_SERIAL_RS485_ENABLED
|
||||
constexpr bool kModbusSerialRs485Enabled = true;
|
||||
#else
|
||||
constexpr bool kModbusSerialRs485Enabled = false;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<gateway::DaliDomainService> s_dali_domain;
|
||||
std::unique_ptr<gateway::GatewayRuntime> s_runtime;
|
||||
std::unique_ptr<gateway::GatewayCache> s_cache;
|
||||
@@ -195,6 +472,7 @@ std::unique_ptr<gateway::GatewayController> s_controller;
|
||||
std::unique_ptr<gateway::GatewayBridgeService> s_bridge;
|
||||
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;
|
||||
|
||||
[[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) {
|
||||
@@ -214,6 +492,11 @@ struct ChannelBindingConfig {
|
||||
};
|
||||
|
||||
bool ValidateChannelBindings() {
|
||||
if (k485ControlEnabled && kConsoleOnUart0) {
|
||||
ESP_LOGE(kTag, "485 control bridge requires moving the ESP-IDF console off UART0");
|
||||
return false;
|
||||
}
|
||||
|
||||
ChannelBindingConfig channels[CONFIG_GATEWAY_CHANNEL_COUNT] = {};
|
||||
|
||||
#if CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE
|
||||
@@ -298,6 +581,54 @@ bool ValidateChannelBindings() {
|
||||
}
|
||||
}
|
||||
|
||||
if (kModbusBridgeSupported && kModbusDefaultSerialTransport) {
|
||||
const int modbus_uart = CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT;
|
||||
if (k485ControlEnabled && modbus_uart == 0) {
|
||||
ESP_LOGE(kTag, "Modbus serial UART0 conflicts with the UART0 control bridge");
|
||||
return false;
|
||||
}
|
||||
if (modbus_uart == 0 && !kModbusAllowUart0) {
|
||||
ESP_LOGE(kTag, "Modbus serial is configured on UART0, but UART0 is reserved for console");
|
||||
return false;
|
||||
}
|
||||
if (modbus_uart == 0 && kConsoleOnUart0) {
|
||||
ESP_LOGE(kTag, "Modbus serial UART0 requires moving the ESP-IDF console off UART0");
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < CONFIG_GATEWAY_CHANNEL_COUNT; ++i) {
|
||||
if (channels[i].enabled && channels[i].serial_phy && channels[i].uart_port == modbus_uart) {
|
||||
ESP_LOGE(kTag, "Modbus serial UART%d conflicts with DALI channel %d serial PHY",
|
||||
modbus_uart, i + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (kKnxBridgeSupported) {
|
||||
const int knx_uart = CONFIG_GATEWAY_KNX_TP_UART_PORT;
|
||||
if (knx_uart >= 0 && k485ControlEnabled && knx_uart == 0) {
|
||||
ESP_LOGE(kTag, "KNX TP UART0 conflicts with the UART0 control bridge");
|
||||
return false;
|
||||
}
|
||||
if (knx_uart >= 0 && knx_uart == 0 && kConsoleOnUart0) {
|
||||
ESP_LOGE(kTag, "KNX TP-UART on UART0 requires moving the ESP-IDF console off UART0");
|
||||
return false;
|
||||
}
|
||||
if (knx_uart >= 0 && kModbusBridgeSupported && kModbusDefaultSerialTransport &&
|
||||
knx_uart == CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT) {
|
||||
ESP_LOGE(kTag, "KNX TP UART%d conflicts with default Modbus serial UART", knx_uart);
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < CONFIG_GATEWAY_CHANNEL_COUNT; ++i) {
|
||||
if (knx_uart >= 0 && channels[i].enabled && channels[i].serial_phy &&
|
||||
channels[i].uart_port == knx_uart) {
|
||||
ESP_LOGE(kTag, "KNX TP UART%d conflicts with DALI channel %d serial PHY", knx_uart,
|
||||
i + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!any_enabled) {
|
||||
ESP_LOGE(kTag, "no DALI PHY is configured; enable at least one native or serial channel");
|
||||
return false;
|
||||
@@ -339,10 +670,10 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||
channel1.query_timeout_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS);
|
||||
channel1.name = runtime.gatewayName(channel1.gateway_id);
|
||||
esp_err_t err = dali_domain.bindSerialBus(channel1);
|
||||
LogBindError("channel1 serial DALI", err);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
esp_err_t err1 = dali_domain.bindSerialBus(channel1);
|
||||
LogBindError("channel1 serial DALI", err1);
|
||||
if (err1 != ESP_OK) {
|
||||
return err1;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -356,10 +687,10 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||
channel2.rx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN);
|
||||
channel2.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE);
|
||||
channel2.name = runtime.gatewayName(channel2.gateway_id);
|
||||
esp_err_t err = dali_domain.bindHardwareBus(channel2);
|
||||
LogBindError("channel2 native DALI", err);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
esp_err_t err2 = dali_domain.bindHardwareBus(channel2);
|
||||
LogBindError("channel2 native DALI", err2);
|
||||
if (err2 != ESP_OK) {
|
||||
return err2;
|
||||
}
|
||||
#elif CONFIG_GATEWAY_CHANNEL2_PHY_UART1 || CONFIG_GATEWAY_CHANNEL2_PHY_UART2
|
||||
gateway::DaliSerialBusConfig channel2{};
|
||||
@@ -378,10 +709,10 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||
channel2.query_timeout_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS);
|
||||
channel2.name = runtime.gatewayName(channel2.gateway_id);
|
||||
esp_err_t err = dali_domain.bindSerialBus(channel2);
|
||||
LogBindError("channel2 serial DALI", err);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
esp_err_t err2 = dali_domain.bindSerialBus(channel2);
|
||||
LogBindError("channel2 serial DALI", err2);
|
||||
if (err2 != ESP_OK) {
|
||||
return err2;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -399,7 +730,7 @@ extern "C" void app_main(void) {
|
||||
"gateway",
|
||||
kWifiSupported,
|
||||
kBleSupported,
|
||||
true,
|
||||
kEthernetSupported,
|
||||
kEspnowSetupSupported,
|
||||
kUsbSetupStartupEnabled,
|
||||
};
|
||||
@@ -417,6 +748,7 @@ extern "C" void app_main(void) {
|
||||
kProjectVersion,
|
||||
gateway::ReadRuntimeSerialId(),
|
||||
kBleStartupEnabled,
|
||||
kCacheSupported && kCacheStartupEnabled,
|
||||
},
|
||||
s_dali_domain.get());
|
||||
ESP_ERROR_CHECK(s_runtime->start());
|
||||
@@ -424,50 +756,160 @@ extern "C" void app_main(void) {
|
||||
ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime));
|
||||
|
||||
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.default_priority_mode = kCachePriorityMode;
|
||||
s_cache = std::make_unique<gateway::GatewayCache>(cache_config);
|
||||
ESP_ERROR_CHECK(s_cache->start());
|
||||
|
||||
gateway::GatewayControllerConfig controller_config;
|
||||
const bool network_transport_supported = profile.enable_wifi || profile.enable_eth;
|
||||
controller_config.setup_supported = true;
|
||||
controller_config.ble_supported = profile.enable_ble;
|
||||
controller_config.wifi_supported = profile.enable_wifi;
|
||||
controller_config.ip_router_supported = profile.enable_wifi || profile.enable_eth;
|
||||
controller_config.ip_router_supported = network_transport_supported;
|
||||
controller_config.internal_scene_supported = true;
|
||||
controller_config.internal_group_supported = true;
|
||||
|
||||
s_controller = std::make_unique<gateway::GatewayController>(*s_runtime, *s_dali_domain,
|
||||
*s_cache,
|
||||
controller_config);
|
||||
ESP_ERROR_CHECK(s_controller->start());
|
||||
if (k485ControlEnabled) {
|
||||
gateway::Gateway485ControlBridgeConfig gateway485_config;
|
||||
gateway485_config.enabled = true;
|
||||
gateway485_config.tx_pin = CONFIG_GATEWAY_485_CONTROL_TX_PIN;
|
||||
gateway485_config.rx_pin = CONFIG_GATEWAY_485_CONTROL_RX_PIN;
|
||||
gateway485_config.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_485_CONTROL_BAUDRATE);
|
||||
gateway485_config.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_485_CONTROL_RX_BUFFER);
|
||||
gateway485_config.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_485_CONTROL_TX_BUFFER);
|
||||
gateway485_config.read_timeout_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS);
|
||||
gateway485_config.write_timeout_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS);
|
||||
gateway485_config.task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE);
|
||||
gateway485_config.task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY);
|
||||
s_uart0_control_bridge = std::make_unique<gateway::Gateway485ControlBridge>(*s_controller,
|
||||
gateway485_config);
|
||||
ESP_ERROR_CHECK(s_uart0_control_bridge->start());
|
||||
}
|
||||
ESP_ERROR_CHECK(s_controller->start());
|
||||
|
||||
if (kBridgeSupported) {
|
||||
gateway::GatewayBridgeServiceConfig bridge_config;
|
||||
bridge_config.bridge_enabled = true;
|
||||
bridge_config.modbus_enabled = profile.enable_wifi && kModbusBridgeSupported;
|
||||
bridge_config.modbus_startup_enabled = profile.enable_wifi && kModbusBridgeSupported &&
|
||||
kModbusBridgeStartupEnabled;
|
||||
bridge_config.bacnet_enabled = profile.enable_wifi && kBacnetBridgeSupported;
|
||||
bridge_config.bacnet_startup_enabled = profile.enable_wifi && kBacnetBridgeSupported &&
|
||||
bridge_config.modbus_enabled = kModbusBridgeSupported;
|
||||
bridge_config.modbus_startup_enabled = kModbusBridgeSupported && kModbusBridgeStartupEnabled;
|
||||
bridge_config.bacnet_enabled = network_transport_supported && kBacnetBridgeSupported;
|
||||
bridge_config.bacnet_startup_enabled = network_transport_supported && kBacnetBridgeSupported &&
|
||||
kBacnetBridgeStartupEnabled;
|
||||
bridge_config.cloud_enabled = profile.enable_wifi && kCloudBridgeSupported;
|
||||
bridge_config.cloud_startup_enabled = profile.enable_wifi && kCloudBridgeSupported &&
|
||||
bridge_config.knx_enabled = network_transport_supported && kKnxBridgeSupported;
|
||||
bridge_config.knx_startup_enabled = network_transport_supported && kKnxBridgeSupported &&
|
||||
kKnxBridgeStartupEnabled;
|
||||
bridge_config.cloud_enabled = network_transport_supported && kCloudBridgeSupported;
|
||||
bridge_config.cloud_startup_enabled = network_transport_supported && kCloudBridgeSupported &&
|
||||
kCloudBridgeStartupEnabled;
|
||||
bridge_config.modbus_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE);
|
||||
bridge_config.modbus_task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY);
|
||||
bridge_config.allow_modbus_uart0 = kModbusAllowUart0 && !kConsoleOnUart0 && !k485ControlEnabled;
|
||||
bridge_config.allow_knx_uart0 = !kConsoleOnUart0 && !k485ControlEnabled;
|
||||
#if CONFIG_GATEWAY_CHANNEL1_PHY_UART1
|
||||
bridge_config.reserved_uart_ports.push_back(1);
|
||||
#elif CONFIG_GATEWAY_CHANNEL1_PHY_UART2
|
||||
bridge_config.reserved_uart_ports.push_back(2);
|
||||
#endif
|
||||
#if CONFIG_GATEWAY_CHANNEL_COUNT >= 2
|
||||
#if CONFIG_GATEWAY_CHANNEL2_PHY_UART1
|
||||
bridge_config.reserved_uart_ports.push_back(1);
|
||||
#elif CONFIG_GATEWAY_CHANNEL2_PHY_UART2
|
||||
bridge_config.reserved_uart_ports.push_back(2);
|
||||
#endif
|
||||
#endif
|
||||
if (kModbusBridgeSupported) {
|
||||
gateway::GatewayModbusConfig default_modbus;
|
||||
#if defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU)
|
||||
default_modbus.transport = "rtu-server";
|
||||
#elif defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
|
||||
default_modbus.transport = "ascii-server";
|
||||
#else
|
||||
default_modbus.transport = "tcp-server";
|
||||
#endif
|
||||
default_modbus.port = static_cast<uint16_t>(CONFIG_GATEWAY_MODBUS_TCP_PORT);
|
||||
default_modbus.unit_id = static_cast<uint8_t>(CONFIG_GATEWAY_MODBUS_UNIT_ID);
|
||||
default_modbus.serial.uart_port = CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT;
|
||||
default_modbus.serial.tx_pin = CONFIG_GATEWAY_MODBUS_SERIAL_TX_PIN;
|
||||
default_modbus.serial.rx_pin = CONFIG_GATEWAY_MODBUS_SERIAL_RX_PIN;
|
||||
default_modbus.serial.baudrate =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_MODBUS_SERIAL_BAUDRATE);
|
||||
default_modbus.serial.response_timeout_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MS);
|
||||
default_modbus.serial.rs485.enabled = kModbusSerialRs485Enabled;
|
||||
default_modbus.serial.rs485.de_pin = CONFIG_GATEWAY_MODBUS_SERIAL_RS485_DE_PIN;
|
||||
bridge_config.default_modbus_config = default_modbus;
|
||||
}
|
||||
bridge_config.bacnet_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE);
|
||||
bridge_config.bacnet_task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY);
|
||||
s_bridge = std::make_unique<gateway::GatewayBridgeService>(*s_dali_domain, bridge_config);
|
||||
if (kKnxBridgeSupported) {
|
||||
gateway::GatewayKnxConfig default_knx;
|
||||
default_knx.dali_router_enabled = true;
|
||||
default_knx.ip_router_enabled = true;
|
||||
default_knx.tunnel_enabled = kKnxTunnelEnabled;
|
||||
default_knx.multicast_enabled = kKnxMulticastEnabled;
|
||||
default_knx.main_group = static_cast<uint8_t>(CONFIG_GATEWAY_KNX_MAIN_GROUP);
|
||||
default_knx.udp_port = static_cast<uint16_t>(CONFIG_GATEWAY_KNX_UDP_PORT);
|
||||
default_knx.multicast_address = CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS;
|
||||
default_knx.ip_interface_individual_address =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS);
|
||||
default_knx.individual_address =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS);
|
||||
default_knx.programming_button_gpio = CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO;
|
||||
default_knx.programming_led_gpio = CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO;
|
||||
#ifdef CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW
|
||||
default_knx.programming_button_active_low = true;
|
||||
#else
|
||||
default_knx.programming_button_active_low = false;
|
||||
#endif
|
||||
#ifdef CONFIG_GATEWAY_KNX_PROGRAMMING_LED_ACTIVE_HIGH
|
||||
default_knx.programming_led_active_high = true;
|
||||
#else
|
||||
default_knx.programming_led_active_high = false;
|
||||
#endif
|
||||
default_knx.tp_uart.uart_port = CONFIG_GATEWAY_KNX_TP_UART_PORT;
|
||||
default_knx.tp_uart.tx_pin = CONFIG_GATEWAY_KNX_TP_TX_PIN;
|
||||
default_knx.tp_uart.rx_pin = CONFIG_GATEWAY_KNX_TP_RX_PIN;
|
||||
default_knx.tp_uart.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_KNX_TP_BAUDRATE);
|
||||
#ifdef CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE
|
||||
default_knx.tp_uart.nine_bit_mode = true;
|
||||
#else
|
||||
default_knx.tp_uart.nine_bit_mode = false;
|
||||
#endif
|
||||
bridge_config.default_knx_config = default_knx;
|
||||
}
|
||||
bridge_config.knx_task_stack_size =
|
||||
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);
|
||||
s_bridge = std::make_unique<gateway::GatewayBridgeService>(*s_dali_domain, *s_cache,
|
||||
bridge_config);
|
||||
}
|
||||
|
||||
if (profile.enable_wifi || profile.enable_eth) {
|
||||
gateway::GatewayNetworkServiceConfig network_config;
|
||||
network_config.wifi_enabled = profile.enable_wifi && kWifiStartupEnabled;
|
||||
network_config.ethernet_enabled = profile.enable_eth && kEthernetStartupEnabled;
|
||||
#if CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE
|
||||
network_config.ethernet_ignore_init_failure = true;
|
||||
#else
|
||||
network_config.ethernet_ignore_init_failure = false;
|
||||
#endif
|
||||
network_config.espnow_setup_enabled = profile.enable_espnow;
|
||||
network_config.espnow_setup_startup_enabled =
|
||||
profile.enable_espnow && kEspnowSetupStartupEnabled;
|
||||
@@ -487,9 +929,26 @@ extern "C" void app_main(void) {
|
||||
#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.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;
|
||||
network_config.ethernet_spi_miso_gpio = CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO;
|
||||
network_config.ethernet_spi_cs_gpio = CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO;
|
||||
network_config.ethernet_spi_int_gpio = CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO;
|
||||
network_config.ethernet_poll_period_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS);
|
||||
network_config.ethernet_spi_clock_mhz =
|
||||
static_cast<uint8_t>(CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ);
|
||||
network_config.ethernet_phy_reset_gpio = CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO;
|
||||
network_config.ethernet_phy_addr = CONFIG_GATEWAY_ETHERNET_PHY_ADDR;
|
||||
network_config.ethernet_rx_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE);
|
||||
network_config.status_led_gpio = CONFIG_GATEWAY_STATUS_LED_GPIO;
|
||||
network_config.boot_button_gpio = CONFIG_GATEWAY_BOOT_BUTTON_GPIO;
|
||||
network_config.setup_ap_button_gpio = CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO;
|
||||
network_config.boot_button_long_press_ms = CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS;
|
||||
network_config.boot_button_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_BUTTON_TASK_STACK_SIZE);
|
||||
#ifdef CONFIG_GATEWAY_STATUS_LED_ACTIVE_HIGH
|
||||
network_config.status_led_active_high = true;
|
||||
#else
|
||||
@@ -499,6 +958,11 @@ extern "C" void app_main(void) {
|
||||
network_config.boot_button_active_low = true;
|
||||
#else
|
||||
network_config.boot_button_active_low = false;
|
||||
#endif
|
||||
#ifdef CONFIG_GATEWAY_SETUP_AP_BUTTON_ACTIVE_LOW
|
||||
network_config.setup_ap_button_active_low = true;
|
||||
#else
|
||||
network_config.setup_ap_button_active_low = false;
|
||||
#endif
|
||||
s_network = std::make_unique<gateway::GatewayNetworkService>(*s_controller, *s_runtime,
|
||||
*s_dali_domain, network_config,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
otadata, data, ota, 0xf000, 0x2000,
|
||||
phy_init, data, phy, 0x11000, 0x1000,
|
||||
factory, app, factory, 0x20000, 0x280000,
|
||||
storage, data, spiffs, 0x300000, 0x100000,
|
||||
|
+162
-43
@@ -562,13 +562,13 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
|
||||
CONFIG_ESPTOOLPY_BEFORE_RESET=y
|
||||
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
|
||||
@@ -587,8 +587,8 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
|
||||
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
|
||||
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4M-single.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions-4M-single.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
CONFIG_PARTITION_TABLE_MD5=y
|
||||
# end of Partition Table
|
||||
@@ -596,32 +596,35 @@ CONFIG_PARTITION_TABLE_MD5=y
|
||||
#
|
||||
# Gateway App
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL_COUNT=2
|
||||
CONFIG_GATEWAY_CHANNEL_COUNT=1
|
||||
|
||||
#
|
||||
# Gateway Channel 1
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL1_GW_ID=3
|
||||
CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED=y
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE is not set
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set
|
||||
CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID=0
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_TX_PIN=2
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN=1
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
|
||||
# end of Gateway Channel 1
|
||||
|
||||
#
|
||||
# Gateway Channel 2
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL2_GW_ID=4
|
||||
CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED=y
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE is not set
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set
|
||||
# end of Gateway Channel 2
|
||||
|
||||
#
|
||||
# Gateway Cache
|
||||
#
|
||||
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=5000
|
||||
CONFIG_GATEWAY_CACHE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_CACHE_START_ENABLED=y
|
||||
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
|
||||
# CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set
|
||||
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=60000
|
||||
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
|
||||
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
|
||||
# end of Gateway Cache
|
||||
@@ -640,11 +643,60 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
|
||||
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
|
||||
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
||||
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
||||
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||
|
||||
#
|
||||
# Gateway Wired Ethernet
|
||||
#
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=2
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=48
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=47
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=33
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=34
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=36
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=40
|
||||
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=-1
|
||||
CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
|
||||
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=4096
|
||||
# end of Gateway Wired Ethernet
|
||||
|
||||
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
|
||||
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU is not set
|
||||
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set
|
||||
CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
|
||||
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
|
||||
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set
|
||||
# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set
|
||||
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
|
||||
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
|
||||
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_UDP_PORT=3671
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
|
||||
CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS=65281
|
||||
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=65534
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW=y
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO=10
|
||||
# CONFIG_GATEWAY_KNX_PROGRAMMING_LED_ACTIVE_HIGH is not set
|
||||
CONFIG_GATEWAY_KNX_TP_UART_PORT=0
|
||||
CONFIG_GATEWAY_KNX_TP_TX_PIN=-1
|
||||
CONFIG_GATEWAY_KNX_TP_RX_PIN=-1
|
||||
CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200
|
||||
CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y
|
||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
|
||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
|
||||
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
|
||||
@@ -653,6 +705,7 @@ CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
|
||||
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_485_CONTROL_ENABLED is not set
|
||||
# end of Gateway Startup Services
|
||||
|
||||
#
|
||||
@@ -666,6 +719,8 @@ CONFIG_GATEWAY_STATUS_LED_GPIO=-1
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS=3000
|
||||
CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO=-1
|
||||
CONFIG_GATEWAY_BUTTON_TASK_STACK_SIZE=8192
|
||||
# end of Gateway Network Services
|
||||
# end of Gateway App
|
||||
|
||||
@@ -713,6 +768,7 @@ CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y
|
||||
#
|
||||
# CONFIG_APPTRACE_DEST_JTAG is not set
|
||||
CONFIG_APPTRACE_DEST_NONE=y
|
||||
# CONFIG_APPTRACE_DEST_UART0 is not set
|
||||
# CONFIG_APPTRACE_DEST_UART1 is not set
|
||||
# CONFIG_APPTRACE_DEST_UART2 is not set
|
||||
# CONFIG_APPTRACE_DEST_USB_CDC is not set
|
||||
@@ -739,6 +795,7 @@ CONFIG_BT_CONTROLLER_ENABLED=y
|
||||
# General
|
||||
#
|
||||
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
|
||||
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set
|
||||
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
|
||||
CONFIG_BT_NIMBLE_PINNED_TO_CORE=0
|
||||
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y
|
||||
@@ -1013,12 +1070,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=y
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
|
||||
# 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 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
|
||||
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
|
||||
@@ -1491,8 +1548,8 @@ CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4
|
||||
#
|
||||
# Sleep Config
|
||||
#
|
||||
# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set
|
||||
CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y
|
||||
CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y
|
||||
CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y
|
||||
CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y
|
||||
CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y
|
||||
@@ -1631,7 +1688,40 @@ CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y
|
||||
#
|
||||
# ESP PSRAM
|
||||
#
|
||||
# CONFIG_SPIRAM is not set
|
||||
CONFIG_SPIRAM=y
|
||||
|
||||
#
|
||||
# SPI RAM config
|
||||
#
|
||||
CONFIG_SPIRAM_MODE_QUAD=y
|
||||
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||
CONFIG_SPIRAM_CLK_IO=30
|
||||
CONFIG_SPIRAM_CS_IO=26
|
||||
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||
# CONFIG_SPIRAM_RODATA is not set
|
||||
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
# CONFIG_SPIRAM_SPEED_40M is not set
|
||||
CONFIG_SPIRAM_SPEED=80
|
||||
CONFIG_SPIRAM_BOOT_HW_INIT=y
|
||||
CONFIG_SPIRAM_BOOT_INIT=y
|
||||
CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y
|
||||
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
|
||||
# CONFIG_SPIRAM_USE_MEMMAP is not set
|
||||
# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set
|
||||
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_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
|
||||
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
|
||||
# end of SPI RAM config
|
||||
# end of ESP PSRAM
|
||||
|
||||
#
|
||||
@@ -1716,24 +1806,21 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
|
||||
|
||||
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
|
||||
CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
|
||||
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048
|
||||
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
|
||||
# CONFIG_ESP_CONSOLE_UART_DEFAULT is not set
|
||||
# CONFIG_ESP_CONSOLE_USB_CDC is not set
|
||||
# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||
# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
|
||||
# CONFIG_ESP_CONSOLE_NONE is not set
|
||||
# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y
|
||||
CONFIG_ESP_CONSOLE_UART=y
|
||||
CONFIG_ESP_CONSOLE_UART_NUM=0
|
||||
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0
|
||||
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
|
||||
CONFIG_ESP_CONSOLE_UART_NUM=-1
|
||||
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=4
|
||||
CONFIG_ESP_INT_WDT=y
|
||||
CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
|
||||
CONFIG_ESP_INT_WDT_CHECK_CPU1=y
|
||||
@@ -1886,6 +1973,7 @@ CONFIG_FATFS_CODEPAGE=437
|
||||
CONFIG_FATFS_FS_LOCK=0
|
||||
CONFIG_FATFS_TIMEOUT_MS=10000
|
||||
CONFIG_FATFS_PER_FILE_CACHE=y
|
||||
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
|
||||
# CONFIG_FATFS_USE_FASTSEEK is not set
|
||||
CONFIG_FATFS_USE_STRFUNC_NONE=y
|
||||
# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set
|
||||
@@ -1963,6 +2051,7 @@ CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
|
||||
#
|
||||
# Extra
|
||||
#
|
||||
CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y
|
||||
# end of Extra
|
||||
|
||||
CONFIG_FREERTOS_PORT=y
|
||||
@@ -2090,8 +2179,7 @@ CONFIG_LWIP_GARP_TMR_INTERVAL=60
|
||||
CONFIG_LWIP_ESP_MLDV6_REPORT=y
|
||||
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
|
||||
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
|
||||
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
|
||||
CONFIG_LWIP_DHCP_DOES_ACD_CHECK=y
|
||||
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
|
||||
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
|
||||
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
|
||||
@@ -2110,13 +2198,16 @@ CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
|
||||
CONFIG_LWIP_DHCPS_ADD_DNS=y
|
||||
# end of DHCP server
|
||||
|
||||
# CONFIG_LWIP_AUTOIP is not set
|
||||
CONFIG_LWIP_AUTOIP=y
|
||||
CONFIG_LWIP_AUTOIP_TRIES=2
|
||||
CONFIG_LWIP_AUTOIP_MAX_CONFLICTS=9
|
||||
CONFIG_LWIP_AUTOIP_RATE_LIMIT_INTERVAL=20
|
||||
CONFIG_LWIP_IPV4=y
|
||||
CONFIG_LWIP_IPV6=y
|
||||
# CONFIG_LWIP_IPV6_AUTOCONFIG is not set
|
||||
CONFIG_LWIP_IPV6_NUM_ADDRESSES=3
|
||||
# CONFIG_LWIP_IPV6_FORWARD is not set
|
||||
# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set
|
||||
CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
|
||||
CONFIG_LWIP_NETIF_LOOPBACK=y
|
||||
CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
|
||||
|
||||
@@ -2247,6 +2338,7 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
|
||||
# mbedTLS
|
||||
#
|
||||
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
|
||||
@@ -2403,12 +2495,15 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y
|
||||
CONFIG_LIBC_ASSERT_BUFFER_SIZE=200
|
||||
# end of LibC
|
||||
|
||||
CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y
|
||||
|
||||
#
|
||||
# NVS
|
||||
#
|
||||
# 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
|
||||
# end of NVS
|
||||
|
||||
#
|
||||
@@ -2676,13 +2771,36 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
|
||||
#
|
||||
CONFIG_DALI_PHY_COUNT=16
|
||||
CONFIG_DALI_DEFAULT_BAUDRATE=1200
|
||||
CONFIG_DALI_API_QUEUE_LEN=10
|
||||
CONFIG_DALI_TX_QUEUE_LEN=1
|
||||
CONFIG_DALI_TIMER_RESOLUTION_HZ=3000000
|
||||
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=20
|
||||
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=12
|
||||
CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=25
|
||||
CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=5
|
||||
CONFIG_DALI_FORWARD_MAX_WAIT_MS=50
|
||||
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=9
|
||||
CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500
|
||||
CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS=3000
|
||||
# CONFIG_DALI_LOG_LEVEL_NONE is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_ERROR is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_WARN is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_INFO is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_DEBUG is not set
|
||||
CONFIG_DALI_LOG_LEVEL_VERBOSE=y
|
||||
CONFIG_DALI_LOG_LEVEL=5
|
||||
CONFIG_DALI_TX_ACTIVE_LOW=y
|
||||
# CONFIG_DALI_TX_ACTIVE_HIGH is not set
|
||||
CONFIG_DALI_RX_ACTIVE_LOW=y
|
||||
# CONFIG_DALI_RX_ACTIVE_HIGH is not set
|
||||
CONFIG_DALI_API_QUEUE_LEN=64
|
||||
CONFIG_DALI_TX_QUEUE_LEN=4
|
||||
CONFIG_DALI_TX_REPLY_QUEUE_LEN=4
|
||||
CONFIG_DALI_RX_QUEUE_LEN=50
|
||||
CONFIG_DALI_DEBUG_QUEUE_LEN=100
|
||||
# CONFIG_DALI_ENABLE_DEBUG_TASK is not set
|
||||
CONFIG_DALI_DALI_TASK_STACK_SIZE=2048
|
||||
CONFIG_DALI_ENABLE_DEBUG_TASK=y
|
||||
CONFIG_DALI_DALI_TASK_STACK_SIZE=8192
|
||||
CONFIG_DALI_DALI_TASK_PRIORITY=2
|
||||
CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048
|
||||
CONFIG_DALI_DEBUG_TASK_PRIORITY=1
|
||||
@@ -2752,6 +2870,7 @@ CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
|
||||
# CONFIG_BLUEDROID_ENABLED is not set
|
||||
CONFIG_NIMBLE_ENABLED=y
|
||||
CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
|
||||
# CONFIG_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set
|
||||
# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
|
||||
CONFIG_NIMBLE_PINNED_TO_CORE=0
|
||||
CONFIG_NIMBLE_PINNED_TO_CORE_0=y
|
||||
@@ -2800,7 +2919,6 @@ CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
|
||||
CONFIG_GDBSTUB_SUPPORT_TASKS=y
|
||||
CONFIG_GDBSTUB_MAX_TASKS=32
|
||||
# CONFIG_OTA_ALLOW_HTTP is not set
|
||||
# CONFIG_ESP_SYSTEM_PD_FLASH is not set
|
||||
CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000
|
||||
CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000
|
||||
CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y
|
||||
@@ -2836,21 +2954,21 @@ CONFIG_ESP32_PHY_MAX_TX_POWER=20
|
||||
# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set
|
||||
CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y
|
||||
CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y
|
||||
# CONFIG_ESP32S3_SPIRAM_SUPPORT is not set
|
||||
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
|
||||
CONFIG_DEFAULT_PSRAM_CLK_IO=30
|
||||
CONFIG_DEFAULT_PSRAM_CS_IO=26
|
||||
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y
|
||||
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160
|
||||
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
|
||||
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=3584
|
||||
CONFIG_CONSOLE_UART_DEFAULT=y
|
||||
CONFIG_MAIN_TASK_STACK_SIZE=8192
|
||||
# CONFIG_CONSOLE_UART_DEFAULT is not set
|
||||
# CONFIG_CONSOLE_UART_CUSTOM is not set
|
||||
# CONFIG_CONSOLE_UART_NONE is not set
|
||||
# CONFIG_ESP_CONSOLE_UART_NONE is not set
|
||||
CONFIG_CONSOLE_UART=y
|
||||
CONFIG_CONSOLE_UART_NUM=0
|
||||
CONFIG_CONSOLE_UART_BAUDRATE=115200
|
||||
CONFIG_CONSOLE_UART_NUM=-1
|
||||
CONFIG_INT_WDT=y
|
||||
CONFIG_INT_WDT_TIMEOUT_MS=300
|
||||
CONFIG_INT_WDT_CHECK_CPU1=y
|
||||
@@ -2903,6 +3021,7 @@ CONFIG_TIMER_TASK_PRIORITY=1
|
||||
CONFIG_TIMER_TASK_STACK_DEPTH=2048
|
||||
CONFIG_TIMER_QUEUE_LENGTH=10
|
||||
# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
|
||||
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
||||
# CONFIG_HAL_ASSERTION_SILIENT is not set
|
||||
# CONFIG_L2_TO_L3_COPY is not set
|
||||
CONFIG_ESP_GRATUITOUS_ARP=y
|
||||
|
||||
@@ -7,9 +7,15 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
CONFIG_PARTITION_TABLE_MD5=y
|
||||
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
CONFIG_ETH_ENABLED=y
|
||||
CONFIG_ETH_USE_SPI_ETHERNET=y
|
||||
CONFIG_ETH_SPI_ETHERNET_W5500=y
|
||||
CONFIG_ETH_SPI_ETHERNET_W5500=y
|
||||
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+215
-27
@@ -562,13 +562,13 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
|
||||
CONFIG_ESPTOOLPY_BEFORE_RESET=y
|
||||
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
|
||||
@@ -587,8 +587,8 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
|
||||
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
|
||||
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4M-single.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions-4M-single.csv"
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
CONFIG_PARTITION_TABLE_MD5=y
|
||||
# end of Partition Table
|
||||
@@ -596,7 +596,132 @@ CONFIG_PARTITION_TABLE_MD5=y
|
||||
#
|
||||
# Gateway App
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL_COUNT=1
|
||||
|
||||
#
|
||||
# Gateway Channel 1
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL1_GW_ID=3
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set
|
||||
CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set
|
||||
# CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID=0
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_TX_PIN=2
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN=1
|
||||
CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
|
||||
# end of Gateway Channel 1
|
||||
|
||||
#
|
||||
# Gateway Channel 2
|
||||
#
|
||||
# end of Gateway Channel 2
|
||||
|
||||
#
|
||||
# Gateway Cache
|
||||
#
|
||||
CONFIG_GATEWAY_CACHE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_CACHE_START_ENABLED=y
|
||||
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
|
||||
# CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set
|
||||
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=60000
|
||||
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
|
||||
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
|
||||
# end of Gateway Cache
|
||||
|
||||
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
|
||||
|
||||
#
|
||||
# Gateway Startup Services
|
||||
#
|
||||
CONFIG_GATEWAY_BLE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_BLE_ENABLED=y
|
||||
CONFIG_GATEWAY_WIFI_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_WIFI_STA_ENABLED is not set
|
||||
CONFIG_GATEWAY_ESPNOW_SETUP_SUPPORTED=y
|
||||
CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
|
||||
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
|
||||
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
||||
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
||||
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||
|
||||
#
|
||||
# Gateway Wired Ethernet
|
||||
#
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=2
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=48
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=47
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=33
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=34
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=36
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=40
|
||||
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=-1
|
||||
CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
|
||||
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=4096
|
||||
# end of Gateway Wired Ethernet
|
||||
|
||||
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
|
||||
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU is not set
|
||||
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set
|
||||
CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
|
||||
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
|
||||
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set
|
||||
# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set
|
||||
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
|
||||
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
|
||||
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_UDP_PORT=3671
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
|
||||
CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS=65281
|
||||
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=65534
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW=y
|
||||
CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO=10
|
||||
# CONFIG_GATEWAY_KNX_PROGRAMMING_LED_ACTIVE_HIGH is not set
|
||||
CONFIG_GATEWAY_KNX_TP_UART_PORT=0
|
||||
CONFIG_GATEWAY_KNX_TP_TX_PIN=-1
|
||||
CONFIG_GATEWAY_KNX_TP_RX_PIN=-1
|
||||
CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200
|
||||
CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y
|
||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
|
||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
|
||||
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
||||
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
|
||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
|
||||
# end of Gateway Startup Services
|
||||
|
||||
#
|
||||
# Gateway Network Services
|
||||
#
|
||||
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_STATUS_LED_GPIO=-1
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS=3000
|
||||
CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO=-1
|
||||
CONFIG_GATEWAY_BUTTON_TASK_STACK_SIZE=8192
|
||||
# end of Gateway Network Services
|
||||
# end of Gateway App
|
||||
|
||||
#
|
||||
@@ -643,6 +768,7 @@ CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y
|
||||
#
|
||||
# CONFIG_APPTRACE_DEST_JTAG is not set
|
||||
CONFIG_APPTRACE_DEST_NONE=y
|
||||
# CONFIG_APPTRACE_DEST_UART0 is not set
|
||||
# CONFIG_APPTRACE_DEST_UART1 is not set
|
||||
# CONFIG_APPTRACE_DEST_UART2 is not set
|
||||
# CONFIG_APPTRACE_DEST_USB_CDC is not set
|
||||
@@ -669,6 +795,7 @@ CONFIG_BT_CONTROLLER_ENABLED=y
|
||||
# General
|
||||
#
|
||||
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
|
||||
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set
|
||||
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
|
||||
CONFIG_BT_NIMBLE_PINNED_TO_CORE=0
|
||||
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y
|
||||
@@ -943,12 +1070,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=y
|
||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
|
||||
# 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 is not set
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
|
||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
|
||||
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
|
||||
@@ -1421,8 +1548,8 @@ CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4
|
||||
#
|
||||
# Sleep Config
|
||||
#
|
||||
# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set
|
||||
CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y
|
||||
CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y
|
||||
CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y
|
||||
CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y
|
||||
CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y
|
||||
@@ -1561,7 +1688,40 @@ CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y
|
||||
#
|
||||
# ESP PSRAM
|
||||
#
|
||||
# CONFIG_SPIRAM is not set
|
||||
CONFIG_SPIRAM=y
|
||||
|
||||
#
|
||||
# SPI RAM config
|
||||
#
|
||||
CONFIG_SPIRAM_MODE_QUAD=y
|
||||
# CONFIG_SPIRAM_MODE_OCT is not set
|
||||
CONFIG_SPIRAM_TYPE_AUTO=y
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
|
||||
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
|
||||
CONFIG_SPIRAM_CLK_IO=30
|
||||
CONFIG_SPIRAM_CS_IO=26
|
||||
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
|
||||
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
|
||||
# CONFIG_SPIRAM_RODATA is not set
|
||||
# CONFIG_SPIRAM_SPEED_120M is not set
|
||||
CONFIG_SPIRAM_SPEED_80M=y
|
||||
# CONFIG_SPIRAM_SPEED_40M is not set
|
||||
CONFIG_SPIRAM_SPEED=80
|
||||
CONFIG_SPIRAM_BOOT_HW_INIT=y
|
||||
CONFIG_SPIRAM_BOOT_INIT=y
|
||||
CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y
|
||||
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
|
||||
# CONFIG_SPIRAM_USE_MEMMAP is not set
|
||||
# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set
|
||||
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_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
|
||||
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
|
||||
# end of SPI RAM config
|
||||
# end of ESP PSRAM
|
||||
|
||||
#
|
||||
@@ -1646,24 +1806,21 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
|
||||
|
||||
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
|
||||
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
|
||||
CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
|
||||
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048
|
||||
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
|
||||
# CONFIG_ESP_CONSOLE_UART_DEFAULT is not set
|
||||
# CONFIG_ESP_CONSOLE_USB_CDC is not set
|
||||
# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||
# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
|
||||
# CONFIG_ESP_CONSOLE_NONE is not set
|
||||
# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y
|
||||
CONFIG_ESP_CONSOLE_UART=y
|
||||
CONFIG_ESP_CONSOLE_UART_NUM=0
|
||||
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0
|
||||
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
|
||||
CONFIG_ESP_CONSOLE_UART_NUM=-1
|
||||
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=4
|
||||
CONFIG_ESP_INT_WDT=y
|
||||
CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
|
||||
CONFIG_ESP_INT_WDT_CHECK_CPU1=y
|
||||
@@ -1816,6 +1973,7 @@ CONFIG_FATFS_CODEPAGE=437
|
||||
CONFIG_FATFS_FS_LOCK=0
|
||||
CONFIG_FATFS_TIMEOUT_MS=10000
|
||||
CONFIG_FATFS_PER_FILE_CACHE=y
|
||||
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
|
||||
# CONFIG_FATFS_USE_FASTSEEK is not set
|
||||
CONFIG_FATFS_USE_STRFUNC_NONE=y
|
||||
# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set
|
||||
@@ -1893,6 +2051,7 @@ CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
|
||||
#
|
||||
# Extra
|
||||
#
|
||||
CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y
|
||||
# end of Extra
|
||||
|
||||
CONFIG_FREERTOS_PORT=y
|
||||
@@ -2020,8 +2179,7 @@ CONFIG_LWIP_GARP_TMR_INTERVAL=60
|
||||
CONFIG_LWIP_ESP_MLDV6_REPORT=y
|
||||
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
|
||||
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
|
||||
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
|
||||
CONFIG_LWIP_DHCP_DOES_ACD_CHECK=y
|
||||
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
|
||||
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
|
||||
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
|
||||
@@ -2040,13 +2198,16 @@ CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
|
||||
CONFIG_LWIP_DHCPS_ADD_DNS=y
|
||||
# end of DHCP server
|
||||
|
||||
# CONFIG_LWIP_AUTOIP is not set
|
||||
CONFIG_LWIP_AUTOIP=y
|
||||
CONFIG_LWIP_AUTOIP_TRIES=2
|
||||
CONFIG_LWIP_AUTOIP_MAX_CONFLICTS=9
|
||||
CONFIG_LWIP_AUTOIP_RATE_LIMIT_INTERVAL=20
|
||||
CONFIG_LWIP_IPV4=y
|
||||
CONFIG_LWIP_IPV6=y
|
||||
# CONFIG_LWIP_IPV6_AUTOCONFIG is not set
|
||||
CONFIG_LWIP_IPV6_NUM_ADDRESSES=3
|
||||
# CONFIG_LWIP_IPV6_FORWARD is not set
|
||||
# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set
|
||||
CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
|
||||
CONFIG_LWIP_NETIF_LOOPBACK=y
|
||||
CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
|
||||
|
||||
@@ -2177,6 +2338,7 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
|
||||
# mbedTLS
|
||||
#
|
||||
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
|
||||
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
|
||||
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
|
||||
@@ -2333,12 +2495,15 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y
|
||||
CONFIG_LIBC_ASSERT_BUFFER_SIZE=200
|
||||
# end of LibC
|
||||
|
||||
CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y
|
||||
|
||||
#
|
||||
# NVS
|
||||
#
|
||||
# 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
|
||||
# end of NVS
|
||||
|
||||
#
|
||||
@@ -2606,13 +2771,36 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
|
||||
#
|
||||
CONFIG_DALI_PHY_COUNT=16
|
||||
CONFIG_DALI_DEFAULT_BAUDRATE=1200
|
||||
CONFIG_DALI_API_QUEUE_LEN=10
|
||||
CONFIG_DALI_TX_QUEUE_LEN=1
|
||||
CONFIG_DALI_TIMER_RESOLUTION_HZ=3000000
|
||||
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=12
|
||||
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=12
|
||||
CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=25
|
||||
CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=5
|
||||
CONFIG_DALI_FORWARD_MAX_WAIT_MS=50
|
||||
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=9
|
||||
CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500
|
||||
CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS=3000
|
||||
# CONFIG_DALI_LOG_LEVEL_NONE is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_ERROR is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_WARN is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_INFO is not set
|
||||
# CONFIG_DALI_LOG_LEVEL_DEBUG is not set
|
||||
CONFIG_DALI_LOG_LEVEL_VERBOSE=y
|
||||
CONFIG_DALI_LOG_LEVEL=5
|
||||
CONFIG_DALI_TX_ACTIVE_LOW=y
|
||||
# CONFIG_DALI_TX_ACTIVE_HIGH is not set
|
||||
CONFIG_DALI_RX_ACTIVE_LOW=y
|
||||
# CONFIG_DALI_RX_ACTIVE_HIGH is not set
|
||||
CONFIG_DALI_API_QUEUE_LEN=64
|
||||
CONFIG_DALI_TX_QUEUE_LEN=4
|
||||
CONFIG_DALI_TX_REPLY_QUEUE_LEN=4
|
||||
CONFIG_DALI_RX_QUEUE_LEN=50
|
||||
CONFIG_DALI_DEBUG_QUEUE_LEN=100
|
||||
# CONFIG_DALI_ENABLE_DEBUG_TASK is not set
|
||||
CONFIG_DALI_DALI_TASK_STACK_SIZE=2048
|
||||
CONFIG_DALI_ENABLE_DEBUG_TASK=y
|
||||
CONFIG_DALI_DALI_TASK_STACK_SIZE=8192
|
||||
CONFIG_DALI_DALI_TASK_PRIORITY=2
|
||||
CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048
|
||||
CONFIG_DALI_DEBUG_TASK_PRIORITY=1
|
||||
|
||||
Submodule
+1
Submodule bacnet_stack added at b1f4389f31
+169
-1
@@ -14,6 +14,174 @@ config DALI_DEFAULT_BAUDRATE
|
||||
help
|
||||
Default baudrate used during initialization.
|
||||
|
||||
config DALI_TIMER_RESOLUTION_HZ
|
||||
int "Native DALI timer resolution Hz"
|
||||
range 1000000 8000000
|
||||
default 3000000
|
||||
help
|
||||
GPTimer resolution for native DALI Manchester timing. The default 3 MHz
|
||||
allows a 1200 bps half-bit period of 416.67 us to be represented as
|
||||
1250 timer ticks.
|
||||
|
||||
config DALI_CUSTOM_HALF_BIT_TIME_X100_US
|
||||
int "Custom native DALI half-bit time x100 us"
|
||||
range 0 500000
|
||||
default 0
|
||||
help
|
||||
Development override for the native DALI half-bit period, expressed in
|
||||
1/100 us. Set to 0 to derive the value from the configured baudrate.
|
||||
Standard 1200 bps DALI is 41667, meaning 416.67 us.
|
||||
|
||||
config DALI_TX_STOP_CONDITION_US
|
||||
int "Custom TX stop condition us"
|
||||
range 0 10000
|
||||
default 0
|
||||
help
|
||||
Development override for the native TX stop-condition wait. Set to 0 to
|
||||
use the scaled standard timing.
|
||||
|
||||
config DALI_RX_STOP_CONDITION_US
|
||||
int "Custom RX stop condition us"
|
||||
range 0 10000
|
||||
default 0
|
||||
help
|
||||
Development override for the native RX stop-condition wait. Set to 0 to
|
||||
use the scaled standard timing.
|
||||
|
||||
config DALI_QUERY_RESPONSE_TIMEOUT_MS
|
||||
int "DALI query response timeout ms"
|
||||
range 10 100
|
||||
default 25
|
||||
help
|
||||
Time to wait for a complete backward frame after a forward query has
|
||||
finished transmitting. DALI backward frames start 5.5-10.5 ms after the
|
||||
forward frame and last about 9.95 ms, so 25 ms leaves margin without the
|
||||
legacy 50 ms no-response delay.
|
||||
|
||||
config DALI_DOUBLE_SEND_DELAY_MS
|
||||
int "Double-send delay ms"
|
||||
range 0 100
|
||||
default 10
|
||||
help
|
||||
Delay between the two frames sent by dali_send_double(), measured after
|
||||
the first frame has completed. Exposed for development tuning.
|
||||
|
||||
config DALI_FORWARD_ACTIVITY_WAIT_MS
|
||||
int "Forward-frame wait after bus activity ms"
|
||||
range 0 1000
|
||||
default 25
|
||||
help
|
||||
Minimum delay before sending a 2- to 4-byte forward frame after normal
|
||||
bus activity. This is the native queue anti-collision wait.
|
||||
|
||||
config DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
|
||||
int "Forward-frame wait after backward frame ms"
|
||||
range 0 1000
|
||||
default 5
|
||||
help
|
||||
Minimum delay before sending a 2- to 4-byte forward frame after the last
|
||||
valid 1-byte backward frame received by the native bus.
|
||||
|
||||
config DALI_FORWARD_MAX_WAIT_MS
|
||||
int "Forward-frame maximum queue wait ms"
|
||||
range 0 5000
|
||||
default 50
|
||||
help
|
||||
Maximum time the native queue waits for a forward-frame send window. If
|
||||
no valid send window is reached before this timeout, the frame is
|
||||
dropped with an error status.
|
||||
|
||||
config DALI_BACKWARD_IDLE_TIMEOUT_MS
|
||||
int "Backward-frame idle wait timeout ms"
|
||||
range 0 1000
|
||||
default 9
|
||||
help
|
||||
Time a 1-byte backward frame waits for an idle bus before being sent
|
||||
anyway. Backward frame sends are not echo-verified because collisions
|
||||
during addressing responses are valid DALI behavior.
|
||||
|
||||
config DALI_BUS_POWER_CHECK_INTERVAL_MS
|
||||
int "Bus power-down check interval ms"
|
||||
range 10 5000
|
||||
default 100
|
||||
help
|
||||
Interval used to resample the RX pin while the native DALI bus is marked
|
||||
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_ABNORMAL_REPORT_INTERVAL_MS
|
||||
int "Legacy bus abnormal report interval ms"
|
||||
range 0 60000
|
||||
default 1000
|
||||
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.
|
||||
|
||||
choice DALI_LOG_LEVEL_CHOICE
|
||||
prompt "DALI log level"
|
||||
default DALI_LOG_LEVEL_WARN
|
||||
help
|
||||
Runtime log level applied to the native DALI HAL ESP-IDF log tag.
|
||||
|
||||
config DALI_LOG_LEVEL_NONE
|
||||
bool "No output"
|
||||
|
||||
config DALI_LOG_LEVEL_ERROR
|
||||
bool "Error"
|
||||
|
||||
config DALI_LOG_LEVEL_WARN
|
||||
bool "Warning"
|
||||
|
||||
config DALI_LOG_LEVEL_INFO
|
||||
bool "Info"
|
||||
|
||||
config DALI_LOG_LEVEL_DEBUG
|
||||
bool "Debug"
|
||||
|
||||
config DALI_LOG_LEVEL_VERBOSE
|
||||
bool "Verbose"
|
||||
|
||||
endchoice
|
||||
|
||||
config DALI_LOG_LEVEL
|
||||
int
|
||||
default 0 if DALI_LOG_LEVEL_NONE
|
||||
default 1 if DALI_LOG_LEVEL_ERROR
|
||||
default 2 if DALI_LOG_LEVEL_WARN
|
||||
default 3 if DALI_LOG_LEVEL_INFO
|
||||
default 4 if DALI_LOG_LEVEL_DEBUG
|
||||
default 5 if DALI_LOG_LEVEL_VERBOSE
|
||||
|
||||
choice DALI_TX_ACTIVE_LEVEL
|
||||
prompt "DALI TX pin active level"
|
||||
default DALI_TX_ACTIVE_LOW
|
||||
help
|
||||
Select the physical GPIO level that drives the DALI bus active. The
|
||||
native gateway default is TX active low.
|
||||
|
||||
config DALI_TX_ACTIVE_LOW
|
||||
bool "Active low"
|
||||
|
||||
config DALI_TX_ACTIVE_HIGH
|
||||
bool "Active high"
|
||||
|
||||
endchoice
|
||||
|
||||
choice DALI_RX_ACTIVE_LEVEL
|
||||
prompt "DALI RX pin active level"
|
||||
default DALI_RX_ACTIVE_HIGH
|
||||
help
|
||||
Select the physical GPIO level read when the DALI bus is active. The
|
||||
native gateway default is RX active high.
|
||||
|
||||
config DALI_RX_ACTIVE_LOW
|
||||
bool "Active low"
|
||||
|
||||
config DALI_RX_ACTIVE_HIGH
|
||||
bool "Active high"
|
||||
|
||||
endchoice
|
||||
|
||||
config DALI_API_QUEUE_LEN
|
||||
int "Global API queue length"
|
||||
range 1 64
|
||||
@@ -48,7 +216,7 @@ config DALI_ENABLE_DEBUG_TASK
|
||||
config DALI_DALI_TASK_STACK_SIZE
|
||||
int "DALI task stack size"
|
||||
range 1024 8192
|
||||
default 2048
|
||||
default 4096
|
||||
|
||||
config DALI_DALI_TASK_PRIORITY
|
||||
int "DALI task priority"
|
||||
|
||||
@@ -41,10 +41,34 @@ void app_main(void) {
|
||||
Use `menuconfig` under `DALI Component` to configure:
|
||||
|
||||
- Bus count and default baudrate.
|
||||
- Native timing values for development, including timer resolution, half-bit period,
|
||||
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 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 1000 ms; set it to 0 to disable the
|
||||
compatibility report.
|
||||
|
||||
Native timing defaults target standard 1200 bps DALI: a 416.67 us half-bit period is
|
||||
generated by the default 3 MHz timer as 1250 ticks. `CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US`
|
||||
can override the half-bit period for development; keep it at 0 for baudrate-derived timing.
|
||||
Query no-response timeout defaults to 25 ms, which covers the 5.5-10.5 ms backward-frame
|
||||
start window plus the approximately 9.95 ms backward frame duration.
|
||||
|
||||
Native TX queue arbitration uses frame length as the frame type signal. Two- to four-byte
|
||||
forward frames wait up to `CONFIG_DALI_FORWARD_MAX_WAIT_MS` for a valid send window,
|
||||
using `CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS` after normal bus activity and
|
||||
`CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS` after a valid backward frame. One-byte
|
||||
backward frames wait up to `CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS` for idle, then are
|
||||
sent without echo verification because addressing-phase collisions are valid.
|
||||
|
||||
## API Note
|
||||
|
||||
The global TX response queue symbol was renamed:
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
#include <memory.h> // for memset
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#ifndef CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS
|
||||
#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 25
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_DOUBLE_SEND_DELAY_MS
|
||||
#define CONFIG_DALI_DOUBLE_SEND_DELAY_MS 10
|
||||
#endif
|
||||
|
||||
static SemaphoreHandle_t s_dali_core_lock;
|
||||
|
||||
static SemaphoreHandle_t dali_core_mutex(void)
|
||||
@@ -31,6 +39,20 @@ static inline void dali_core_unlock(void)
|
||||
}
|
||||
}
|
||||
|
||||
static UBaseType_t drain_rx_queue(QueueHandle_t rx_q)
|
||||
{
|
||||
if (rx_q == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Dali_msg_t stale = {0};
|
||||
UBaseType_t drained = 0;
|
||||
while (xQueueReceive(rx_q, &stale, 0) == pdTRUE) {
|
||||
drained++;
|
||||
}
|
||||
return drained;
|
||||
}
|
||||
|
||||
Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) {
|
||||
Dali_msg_t dali_msg;
|
||||
dali_msg.id = 0;
|
||||
@@ -90,7 +112,7 @@ void dali_send_double(Dali_msg_t *dali_msg) {
|
||||
dali_send_locked(dali_msg);
|
||||
// TODO check status
|
||||
dali_msg->id++; // increment message ID
|
||||
dali_delay_ms(10); // delay 13ms 101.8.1.2: 13.5 - 75ms
|
||||
dali_delay_ms(CONFIG_DALI_DOUBLE_SEND_DELAY_MS);
|
||||
dali_send_locked(dali_msg);
|
||||
// TODO check status
|
||||
dali_core_unlock();
|
||||
@@ -110,17 +132,16 @@ int dali_query(Dali_msg_t *tx_msg, Dali_msg_t *rx_msg) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// TODO check empty queue
|
||||
if(xQueueReceive(rx_q, rx_msg, 0) == pdTRUE) {
|
||||
printf("Queue not empty\n");
|
||||
return -1;
|
||||
dali_core_lock();
|
||||
UBaseType_t drained = drain_rx_queue(rx_q);
|
||||
if (drained > 0) {
|
||||
printf("dali_query: dropped %u stale RX frame(s)\n", (unsigned)drained);
|
||||
}
|
||||
// printf("check A tx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0]);
|
||||
|
||||
dali_core_lock();
|
||||
dali_send_locked(tx_msg);
|
||||
// receive message from DALI task
|
||||
ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(50));
|
||||
ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS));
|
||||
dali_core_unlock();
|
||||
// printf("B rx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, rx_msg->type, rx_msg->status, rx_msg->length, rx_msg->data[0]);
|
||||
return ret;
|
||||
|
||||
@@ -4,6 +4,15 @@
|
||||
//ESP-IDF HAL
|
||||
#ifdef CONFIG_IDF_TARGET
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifndef CONFIG_DALI_LOG_LEVEL
|
||||
#define CONFIG_DALI_LOG_LEVEL 2
|
||||
#endif
|
||||
|
||||
#ifndef LOG_LOCAL_LEVEL
|
||||
#define LOG_LOCAL_LEVEL CONFIG_DALI_LOG_LEVEL
|
||||
#endif
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
@@ -39,7 +48,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_DALI_TASK_STACK_SIZE
|
||||
#define CONFIG_DALI_DALI_TASK_STACK_SIZE 2048
|
||||
#define CONFIG_DALI_DALI_TASK_STACK_SIZE 4096
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_DALI_TASK_PRIORITY
|
||||
@@ -54,14 +63,57 @@
|
||||
#define CONFIG_DALI_DEBUG_TASK_PRIORITY 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_TIMER_RESOLUTION_HZ
|
||||
#define CONFIG_DALI_TIMER_RESOLUTION_HZ 3000000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US
|
||||
#define CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_TX_STOP_CONDITION_US
|
||||
#define CONFIG_DALI_TX_STOP_CONDITION_US 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_RX_STOP_CONDITION_US
|
||||
#define CONFIG_DALI_RX_STOP_CONDITION_US 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS
|
||||
#define CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS 100
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS
|
||||
#define CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS 1000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS
|
||||
#define CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS 25
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
|
||||
#define CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS 5
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_FORWARD_MAX_WAIT_MS
|
||||
#define CONFIG_DALI_FORWARD_MAX_WAIT_MS 50
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS
|
||||
#define CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS 9
|
||||
#endif
|
||||
|
||||
#define WITHIN_RANGE(x, min, max) ((x) > (min) && (x) < (max))
|
||||
#define MAX_DELTA_RELOAD_TIME 600000000 // 600s - max u32: 4,294,967,295~4,294s
|
||||
|
||||
#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_ABNORMAL_REPORT_INTERVAL_US ((uint64_t)CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS * 1000ULL)
|
||||
|
||||
typedef struct {
|
||||
uint32_t hb;
|
||||
uint32_t timer_alarm_ticks;
|
||||
uint32_t rx_hb_min;
|
||||
uint32_t rx_hb_max;
|
||||
uint32_t rx_2hb_min;
|
||||
@@ -76,6 +128,11 @@ typedef struct {
|
||||
|
||||
static const char *TAG = "dali_hal";
|
||||
|
||||
static inline void apply_dali_log_level(void)
|
||||
{
|
||||
esp_log_level_set(TAG, (esp_log_level_t)CONFIG_DALI_LOG_LEVEL);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t bus_id;
|
||||
uint8_t tx_pin;
|
||||
@@ -91,8 +148,17 @@ typedef struct {
|
||||
uint32_t rx_tx_delta;
|
||||
uint32_t rx_pulse_width;
|
||||
uint8_t rx_level;
|
||||
uint8_t tx_level;
|
||||
|
||||
uint64_t bus_level_check_time;
|
||||
uint64_t bus_abnormal_report_time;
|
||||
|
||||
Dali_msg_t tx_data;
|
||||
bool bus_activity_seen;
|
||||
uint64_t rx_last_frame_time;
|
||||
uint8_t rx_last_frame_length;
|
||||
uint8_t rx_last_frame_status;
|
||||
volatile bool force_backward_tx;
|
||||
Dali_msg_t rx_data;
|
||||
|
||||
uint8_t tx_half_bit_counter;
|
||||
@@ -137,17 +203,31 @@ static inline void dali_hal_unlock(void)
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t dali_half_bit_from_baud(uint32_t baudrate)
|
||||
static uint32_t dali_half_bit_x100_from_baud(uint32_t baudrate)
|
||||
{
|
||||
if (baudrate < DALI_BAUDRATE_MIN || baudrate > DALI_BAUDRATE_MAX) {
|
||||
return 0;
|
||||
}
|
||||
uint64_t hb = 500000ULL + (baudrate / 2U); // round to nearest
|
||||
hb /= baudrate;
|
||||
if (hb == 0 || hb > 2000000ULL) { // should never happen with checked bounds
|
||||
uint64_t hb_x100 = 50000000ULL + (baudrate / 2U); // round to nearest 0.01 us
|
||||
hb_x100 /= baudrate;
|
||||
if (hb_x100 == 0 || hb_x100 > 200000000ULL) { // should never happen with checked bounds
|
||||
return 0;
|
||||
}
|
||||
return (uint32_t)hb;
|
||||
return (uint32_t)hb_x100;
|
||||
}
|
||||
|
||||
static uint32_t half_bit_us_from_x100(uint32_t hb_x100)
|
||||
{
|
||||
return (hb_x100 + 50U) / 100U;
|
||||
}
|
||||
|
||||
static uint32_t timer_alarm_ticks_from_x100(uint32_t hb_x100)
|
||||
{
|
||||
uint64_t ticks = ((uint64_t)CONFIG_DALI_TIMER_RESOLUTION_HZ * hb_x100 + 50000000ULL) / 100000000ULL;
|
||||
if (ticks == 0 || ticks > UINT32_MAX) {
|
||||
return 0;
|
||||
}
|
||||
return (uint32_t)ticks;
|
||||
}
|
||||
|
||||
static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
|
||||
@@ -155,25 +235,40 @@ static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
|
||||
return (uint32_t)(((uint64_t)base_us * hb_us + (DALI_TIME_HB / 2U)) / DALI_TIME_HB);
|
||||
}
|
||||
|
||||
static inline uint32_t configured_or_scaled_time(uint32_t configured_us, uint32_t base_us,
|
||||
uint32_t hb_us)
|
||||
{
|
||||
return configured_us > 0 ? configured_us : scale_time_by_hb(base_us, hb_us);
|
||||
}
|
||||
|
||||
static esp_err_t update_timing_locked(uint32_t baudrate)
|
||||
{
|
||||
uint32_t hb = dali_half_bit_from_baud(baudrate);
|
||||
if (hb == 0) {
|
||||
ESP_LOGE(TAG, "invalid baudrate: %u", baudrate);
|
||||
uint32_t hb_x100 = CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US;
|
||||
if (hb_x100 == 0) {
|
||||
hb_x100 = dali_half_bit_x100_from_baud(baudrate);
|
||||
}
|
||||
uint32_t hb = half_bit_us_from_x100(hb_x100);
|
||||
uint32_t timer_alarm_ticks = timer_alarm_ticks_from_x100(hb_x100);
|
||||
if (hb == 0 || timer_alarm_ticks == 0) {
|
||||
ESP_LOGE(TAG, "invalid native timing: baudrate=%u hbX100Us=%u timerHz=%u",
|
||||
baudrate, hb_x100, CONFIG_DALI_TIMER_RESOLUTION_HZ);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
dali_timing_t new_timing = {
|
||||
.hb = hb,
|
||||
.timer_alarm_ticks = timer_alarm_ticks,
|
||||
.rx_hb_min = scale_time_by_hb(DALI_RX_HB_MIN, hb),
|
||||
.rx_hb_max = scale_time_by_hb(DALI_RX_HB_MAX, hb),
|
||||
.rx_2hb_min = scale_time_by_hb(DALI_RX_2HB_MIN, hb),
|
||||
.rx_2hb_max = scale_time_by_hb(DALI_RX_2HB_MAX, hb),
|
||||
.rx_stop_cond = scale_time_by_hb(DALI_RX_STOP_COND, hb),
|
||||
.rx_stop_cond = configured_or_scaled_time(CONFIG_DALI_RX_STOP_CONDITION_US,
|
||||
DALI_RX_STOP_COND, hb),
|
||||
.time_bus_down = scale_time_by_hb(DALI_TIME_BUS_DOWN, hb),
|
||||
.time_break_min = scale_time_by_hb(DALI_TIME_BREAK_MIN, hb),
|
||||
.time_recovery_min = scale_time_by_hb(DALI_TIME_RECOVERY_MIN, hb),
|
||||
.tx_stop_cond = scale_time_by_hb(DALI_TX_STOP_COND, hb),
|
||||
.tx_stop_cond = configured_or_scaled_time(CONFIG_DALI_TX_STOP_CONDITION_US,
|
||||
DALI_TX_STOP_COND, hb),
|
||||
.collision_txrx_delta = scale_time_by_hb(DALI_COLLISION_TXRX_DELTA, hb),
|
||||
};
|
||||
|
||||
@@ -199,7 +294,7 @@ static esp_err_t apply_timer_alarm_locked(void)
|
||||
return ESP_OK;
|
||||
}
|
||||
gptimer_alarm_config_t timer_alarm_config = {
|
||||
.alarm_count = s_timing.hb,
|
||||
.alarm_count = s_timing.timer_alarm_ticks,
|
||||
.reload_count = 0,
|
||||
.flags = {
|
||||
.auto_reload_on_alarm = true,
|
||||
@@ -235,22 +330,330 @@ static bool s_debug_task_created = false;
|
||||
#endif
|
||||
static bool s_dali_task_created = false;
|
||||
|
||||
#define DALI_SET_BUS_HIGH(bus) gpio_set_level((bus)->tx_pin, DALI_TX_HIGH) // set bus level
|
||||
#define DALI_SET_BUS_LOW(bus) gpio_set_level((bus)->tx_pin, DALI_TX_LOW) // set bus level
|
||||
#define DALI_SET_BUS_LEVEL(bus, x) gpio_set_level((bus)->tx_pin, ((x)==DALI_TX_HIGH)) // set bus level
|
||||
#define DALI_SET_BUS_HIGH(bus) do { gpio_set_level((bus)->tx_pin, DALI_TX_HIGH); (bus)->tx_level = 1; } while (0)
|
||||
#define DALI_SET_BUS_LOW(bus) do { gpio_set_level((bus)->tx_pin, DALI_TX_LOW); (bus)->tx_level = 0; } while (0)
|
||||
#define DALI_SET_BUS_LEVEL(bus, x) do { \
|
||||
uint8_t level = (x) ? 1 : 0; \
|
||||
gpio_set_level((bus)->tx_pin, level ? DALI_TX_HIGH : DALI_TX_LOW); \
|
||||
(bus)->tx_level = level; \
|
||||
} while (0)
|
||||
|
||||
// !!! read from RX pin, we need real bus level, not logic level of TX pin
|
||||
// return: 0 - bus level low, active state
|
||||
// 1 - bus level high, idle state
|
||||
#define DALI_GET_BUS_LEVEL(bus) (gpio_get_level((bus)->rx_pin) == (DALI_RX_HIGH)) // get bus level
|
||||
// return: 0 - tx pin drive bus low, active state
|
||||
// 1 - tx pin drive bus high, idle state
|
||||
#define DALI_GET_TX_LEVEL(bus) (gpio_get_level((bus)->tx_pin) == (DALI_TX_HIGH)) // get TX pin level
|
||||
// return: 0 - TX drives bus low, active state
|
||||
// 1 - TX releases/drives bus high, idle state
|
||||
#define DALI_GET_TX_LEVEL(bus) ((bus)->tx_level)
|
||||
|
||||
static inline bool bus_valid(uint8_t bus_id) {
|
||||
return bus_id < DALI_PHY_COUNT && s_bus[bus_id].inited;
|
||||
}
|
||||
|
||||
static const char *bus_state_name(dali_bus_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case DALI_BUS_UNKNOWN: return "unknown";
|
||||
case DALI_BUS_POWER_DOWN: return "power_down";
|
||||
case DALI_BUS_ERROR: return "error";
|
||||
case DALI_BUS_READY: return "ready";
|
||||
case DALI_BUS_TRANSMITTING: return "transmitting";
|
||||
case DALI_BUS_RECEIVING: return "receiving";
|
||||
case DALI_BUS_TIME_BREAK: return "time_break";
|
||||
case DALI_BUS_RECOVERY: return "recovery";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *tx_state_name(dali_tx_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case TX_STATE_ERROR: return "error";
|
||||
case TX_STATE_COLLISION: return "collision";
|
||||
case TX_STATE_IDLE: return "idle";
|
||||
case TX_STATE_START: return "start";
|
||||
case TX_STATE_DATA: return "data";
|
||||
case TX_STATE_STOP: return "stop";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *rx_state_name(dali_rx_state_t state)
|
||||
{
|
||||
switch (state) {
|
||||
case RX_STATE_ERROR: return "error";
|
||||
case RX_STATE_IDLE: return "idle";
|
||||
case RX_STATE_START: return "start";
|
||||
case RX_STATE_DATA: return "data";
|
||||
case RX_STATE_STOP: return "stop";
|
||||
case RX_STATE_END: return "end";
|
||||
default: return "invalid";
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t tx_completion_timeout_ms(const Dali_msg_t *msg)
|
||||
{
|
||||
uint32_t hb = s_timing_ready ? s_timing.hb : DALI_TIME_HB;
|
||||
uint32_t stop_us = s_timing_ready ? s_timing.tx_stop_cond : DALI_TX_STOP_COND;
|
||||
uint32_t bits = msg ? msg->length : DALI_MAX_BITS;
|
||||
if (bits == 0 || bits > DALI_MAX_BITS) {
|
||||
bits = DALI_MAX_BITS;
|
||||
}
|
||||
|
||||
uint64_t frame_us = ((uint64_t)(2U + bits * 2U) * hb) + stop_us;
|
||||
uint32_t frame_ms = (uint32_t)((frame_us + 999U) / 1000U);
|
||||
uint32_t timeout_ms = frame_ms + 30U;
|
||||
if (timeout_ms < 30U) {
|
||||
timeout_ms = 30U;
|
||||
}
|
||||
if (timeout_ms > 500U) {
|
||||
timeout_ms = 500U;
|
||||
}
|
||||
return timeout_ms;
|
||||
}
|
||||
|
||||
static inline bool IRAM_ATTR is_backward_tx_msg(const Dali_msg_t *msg)
|
||||
{
|
||||
return msg != NULL && msg->length == 8;
|
||||
}
|
||||
|
||||
static bool is_forward_tx_msg(const Dali_msg_t *msg)
|
||||
{
|
||||
return msg != NULL && msg->length >= 16 && msg->length <= 32 && (msg->length % 8) == 0;
|
||||
}
|
||||
|
||||
static UBaseType_t queue_waiting(QueueHandle_t queue);
|
||||
|
||||
static uint64_t bus_last_activity_time_us(const dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL || !bus->bus_activity_seen) {
|
||||
return 0;
|
||||
}
|
||||
return bus->rx_last_edge_time > bus->tx_last_edge_time ? bus->rx_last_edge_time
|
||||
: bus->tx_last_edge_time;
|
||||
}
|
||||
|
||||
static bool bus_idle_for_tx(const dali_bus_ctx_t *bus)
|
||||
{
|
||||
return bus != NULL && bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE &&
|
||||
bus->rx_state == RX_STATE_IDLE && bus->rx_level == 1 && queue_waiting(bus->tx_queue) == 0;
|
||||
}
|
||||
|
||||
static bool last_activity_was_backward_frame(const dali_bus_ctx_t *bus, uint64_t last_activity)
|
||||
{
|
||||
return bus != NULL && bus->rx_last_frame_status == DALI_FRAME_OK &&
|
||||
bus->rx_last_frame_length == 8 && bus->rx_last_frame_time != 0 &&
|
||||
bus->rx_last_frame_time >= last_activity;
|
||||
}
|
||||
|
||||
static TickType_t tx_wait_poll_ticks(void)
|
||||
{
|
||||
TickType_t ticks = pdMS_TO_TICKS(1);
|
||||
return ticks == 0 ? 1 : ticks;
|
||||
}
|
||||
|
||||
static bool wait_for_forward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg)
|
||||
{
|
||||
if (bus == NULL || !is_forward_tx_msg(msg)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint64_t started = esp_timer_get_time();
|
||||
const uint64_t max_wait_us = (uint64_t)CONFIG_DALI_FORWARD_MAX_WAIT_MS * 1000ULL;
|
||||
const TickType_t poll_ticks = tx_wait_poll_ticks();
|
||||
|
||||
while (true) {
|
||||
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t now = esp_timer_get_time();
|
||||
if (bus_idle_for_tx(bus)) {
|
||||
const uint64_t last_activity = bus_last_activity_time_us(bus);
|
||||
if (last_activity == 0) {
|
||||
return true;
|
||||
}
|
||||
const uint32_t wait_ms = last_activity_was_backward_frame(bus, last_activity)
|
||||
? CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
|
||||
: CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS;
|
||||
if (wait_ms == 0 || (now - last_activity) >= ((uint64_t)wait_ms * 1000ULL)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (max_wait_us == 0 || (now - started) >= max_wait_us) {
|
||||
return false;
|
||||
}
|
||||
vTaskDelay(poll_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
static bool wait_for_backward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg,
|
||||
bool *force_start)
|
||||
{
|
||||
if (force_start != NULL) {
|
||||
*force_start = false;
|
||||
}
|
||||
if (bus == NULL || !is_backward_tx_msg(msg)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint64_t started = esp_timer_get_time();
|
||||
const uint64_t timeout_us = (uint64_t)CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS * 1000ULL;
|
||||
const TickType_t poll_ticks = tx_wait_poll_ticks();
|
||||
|
||||
while (true) {
|
||||
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
|
||||
return false;
|
||||
}
|
||||
if (bus_idle_for_tx(bus)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint64_t now = esp_timer_get_time();
|
||||
if (timeout_us == 0 || (now - started) >= timeout_us) {
|
||||
if (force_start != NULL) {
|
||||
*force_start = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
vTaskDelay(poll_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
static UBaseType_t queue_waiting(QueueHandle_t queue)
|
||||
{
|
||||
return queue ? uxQueueMessagesWaiting(queue) : 0;
|
||||
}
|
||||
|
||||
static void log_tx_message(const char *prefix, const dali_bus_ctx_t *bus,
|
||||
const Dali_msg_t *msg, uint32_t timeout_ms)
|
||||
{
|
||||
if (bus == NULL || msg == NULL) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGW(TAG,
|
||||
"%s bus=%u timeoutMs=%lu busState=%s txState=%s rxState=%s "
|
||||
"txQ=%u replyQ=%u status=%u len=%u data=%02x %02x %02x %02x",
|
||||
prefix, bus->bus_id, (unsigned long)timeout_ms,
|
||||
bus_state_name(bus->bus_state), tx_state_name(bus->tx_state),
|
||||
rx_state_name(bus->rx_state), (unsigned)queue_waiting(bus->tx_queue),
|
||||
(unsigned)queue_waiting(bus->tx_reply_queue), msg->status, msg->length,
|
||||
msg->data[0], msg->data[1], msg->data[2], msg->data[3]);
|
||||
}
|
||||
|
||||
static void drain_tx_reply_queue(dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL || bus->tx_reply_queue == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dali_msg_t stale = {0};
|
||||
UBaseType_t drained = 0;
|
||||
while (xQueueReceive(bus->tx_reply_queue, &stale, 0) == pdTRUE) {
|
||||
drained++;
|
||||
}
|
||||
if (drained > 0) {
|
||||
ESP_LOGW(TAG,
|
||||
"bus=%u dropped %u stale TX completion(s) status=%u len=%u data=%02x %02x %02x %02x",
|
||||
bus->bus_id, (unsigned)drained, stale.status, stale.length, stale.data[0],
|
||||
stale.data[1], stale.data[2], stale.data[3]);
|
||||
}
|
||||
}
|
||||
|
||||
static void recover_tx_timeout(dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bus->tx_queue) {
|
||||
xQueueReset(bus->tx_queue);
|
||||
}
|
||||
if (bus->tx_reply_queue) {
|
||||
xQueueReset(bus->tx_reply_queue);
|
||||
}
|
||||
|
||||
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;
|
||||
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();
|
||||
DALI_SET_BUS_HIGH(bus);
|
||||
bus->bus_state = DALI_BUS_READY;
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "bus=%u native TX queue recovered busState=%s txState=%s rxState=%s",
|
||||
bus->bus_id, bus_state_name(bus->bus_state), tx_state_name(bus->tx_state),
|
||||
rx_state_name(bus->rx_state));
|
||||
}
|
||||
|
||||
static inline bool IRAM_ATTR interval_elapsed_us(uint64_t time_now, uint64_t last_time,
|
||||
uint64_t interval_us)
|
||||
{
|
||||
return interval_us > 0 && (time_now - last_time) >= interval_us;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR publish_bus_abnormal_from_isr(dali_bus_ctx_t *bus, BaseType_t *yield)
|
||||
{
|
||||
if (bus == NULL || dali_raw_receive_queue == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dali_msg_t abnormal = {0};
|
||||
abnormal.id = bus->bus_id;
|
||||
abnormal.type = DALI_MSG_FORWARD;
|
||||
abnormal.status = DALI_FRAME_OK;
|
||||
abnormal.length = 16;
|
||||
abnormal.data[0] = 0xFF;
|
||||
abnormal.data[1] = 0xFD;
|
||||
|
||||
if (xQueueSendToBackFromISR(dali_raw_receive_queue, &abnormal, yield) != pdTRUE) {
|
||||
Dali_msg_t dropped = {0};
|
||||
xQueueReceiveFromISR(dali_raw_receive_queue, &dropped, yield);
|
||||
xQueueSendToBackFromISR(dali_raw_receive_queue, &abnormal, yield);
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR maybe_report_bus_abnormal_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
|
||||
BaseType_t *yield)
|
||||
{
|
||||
if (bus == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0) {
|
||||
return;
|
||||
}
|
||||
if (!interval_elapsed_us(time_now, bus->bus_abnormal_report_time,
|
||||
DALI_BUS_ABNORMAL_REPORT_INTERVAL_US)) {
|
||||
return;
|
||||
}
|
||||
publish_bus_abnormal_from_isr(bus, yield);
|
||||
bus->bus_abnormal_report_time = time_now;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR poll_power_down_bus_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
|
||||
BaseType_t *yield)
|
||||
{
|
||||
if (bus == NULL || bus->bus_state != DALI_BUS_POWER_DOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interval_elapsed_us(time_now, bus->bus_level_check_time,
|
||||
DALI_BUS_POWER_CHECK_INTERVAL_US)) {
|
||||
const uint8_t current_level = DALI_GET_BUS_LEVEL(bus);
|
||||
bus->bus_level_check_time = time_now;
|
||||
if (current_level != bus->rx_level) {
|
||||
bus->rx_level = current_level;
|
||||
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)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
@@ -264,6 +667,48 @@ static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queu
|
||||
}
|
||||
}
|
||||
|
||||
static inline void IRAM_ATTR note_rx_frame_from_isr(dali_bus_ctx_t *bus)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
bus->rx_last_frame_time = bus->rx_last_edge_time;
|
||||
bus->rx_last_frame_length = bus->rx_data.length;
|
||||
bus->rx_last_frame_status = bus->rx_data.status;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR complete_tx_from_isr(dali_bus_ctx_t *bus, BaseType_t *yield)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
bus->tx_data.status = DALI_FRAME_OK;
|
||||
if (!is_backward_tx_msg(&bus->tx_data) && bus->tx_reply_queue) {
|
||||
if (xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, yield) != pdTRUE) {
|
||||
Dali_msg_t dropped = {0};
|
||||
xQueueReceiveFromISR(bus->tx_reply_queue, &dropped, yield);
|
||||
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, yield);
|
||||
}
|
||||
}
|
||||
bus->tx_state = TX_STATE_IDLE;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR start_tx_collision_recovery_from_isr(dali_bus_ctx_t *bus,
|
||||
uint64_t time_now)
|
||||
{
|
||||
if (bus == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
bus->rx_last_edge_time = time_now;
|
||||
bus->tx_last_edge_time = time_now;
|
||||
DALI_SET_BUS_LOW(bus);
|
||||
|
||||
bus->bus_state = DALI_BUS_TIME_BREAK;
|
||||
bus->tx_data.status = DALI_FRAME_COLLISION;
|
||||
}
|
||||
|
||||
// GPIO ISR handler
|
||||
// define rx_gpio_isr_handler on any edge
|
||||
static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
|
||||
@@ -273,6 +718,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
|
||||
return;
|
||||
}
|
||||
|
||||
BaseType_t yield = false;
|
||||
uint64_t rx_current_edge_time = esp_timer_get_time(); // get time in us
|
||||
uint8_t rx_previous_level = bus->rx_level;
|
||||
|
||||
@@ -280,6 +726,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
|
||||
|
||||
// rx_level = 1 if and only if DALI bus is really high, idle
|
||||
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin - not depend on hw: 0 - low, 1 - high
|
||||
bus->bus_activity_seen = true;
|
||||
|
||||
bus->rx_pulse_width = rx_current_edge_time - bus->rx_last_edge_time; // time from last edge
|
||||
bus->rx_tx_delta = rx_current_edge_time - bus->tx_last_edge_time; // time from last edge
|
||||
@@ -287,8 +734,16 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
|
||||
// always save time of last edge
|
||||
bus->rx_last_edge_time = rx_current_edge_time; // get time in us
|
||||
|
||||
if(bus->bus_state == DALI_BUS_READY && bus->rx_level == 0) // found start bit
|
||||
bool tx_stop_released = bus->bus_state == DALI_BUS_TRANSMITTING &&
|
||||
bus->tx_state == TX_STATE_STOP &&
|
||||
DALI_GET_TX_LEVEL(bus) == 1;
|
||||
|
||||
if((bus->bus_state == DALI_BUS_READY || tx_stop_released) && bus->rx_level == 0) // found start bit
|
||||
{
|
||||
if (tx_stop_released) {
|
||||
complete_tx_from_isr(bus, &yield);
|
||||
}
|
||||
|
||||
// within range for backward frame
|
||||
uint32_t time_ms = bus->rx_pulse_width / 1000; // 1ms = 1000us
|
||||
if(time_ms>255) bus->rx_data.type = 255;
|
||||
@@ -378,12 +833,14 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
|
||||
|
||||
|
||||
// if collision detected: we are too late after bit was transmitted
|
||||
else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->rx_tx_delta > s_timing.collision_txrx_delta)
|
||||
else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state != TX_STATE_STOP &&
|
||||
!is_backward_tx_msg(&bus->tx_data) &&
|
||||
bus->rx_tx_delta > s_timing.collision_txrx_delta)
|
||||
{
|
||||
// we need now to start collision recovery with time break: 101.9.2.4
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
DALI_SET_BUS_LOW(bus); // force TX low - active state, inform about collision, this also generate new GPIO ISR
|
||||
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
bus->bus_state = DALI_BUS_TIME_BREAK; // we are in time break state
|
||||
bus->tx_data.status = DALI_FRAME_COLLISION; // collision detected
|
||||
}
|
||||
@@ -399,7 +856,11 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
|
||||
dbg.rx_pulse_width = bus->rx_pulse_width;
|
||||
dbg.rx_tx_delta = bus->rx_tx_delta;
|
||||
dbg.bus_id = bus->bus_id;
|
||||
xQueueSendToBackFromISR(rx_dbg_queue, &dbg, NULL); // send data to queue
|
||||
xQueueSendToBackFromISR(rx_dbg_queue, &dbg, &yield); // send data to queue
|
||||
}
|
||||
|
||||
if (yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,6 +884,18 @@ 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);
|
||||
rx_delta = time_now - bus->rx_last_edge_time;
|
||||
tx_delta = time_now - bus->tx_last_edge_time;
|
||||
|
||||
if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state == TX_STATE_STOP &&
|
||||
DALI_GET_TX_LEVEL(bus) == 1 &&
|
||||
(bus->rx_state == RX_STATE_START || bus->rx_state == RX_STATE_DATA ||
|
||||
bus->rx_state == RX_STATE_STOP)) {
|
||||
complete_tx_from_isr(bus, &yield);
|
||||
bus->bus_state = DALI_BUS_RECEIVING;
|
||||
}
|
||||
|
||||
// recovery from different error states: UNKNOWN, ERROR, POWER_DOWN
|
||||
if(bus->bus_state <= DALI_BUS_ERROR)
|
||||
{ // 101.8.2.4 - startup BUS after 2.4ms
|
||||
@@ -434,8 +907,13 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
// if bus power down - if bus is low for more then 45ms
|
||||
if(bus->rx_level==0 && rx_delta > 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;
|
||||
}
|
||||
|
||||
}
|
||||
// recovery from collision detection
|
||||
@@ -444,9 +922,9 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
// BUS: ACTIVE, LOW - BUS is busy, let caller to restart transmission
|
||||
if(bus->bus_state == DALI_BUS_TIME_BREAK && rx_delta > s_timing.time_break_min)
|
||||
{
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
DALI_SET_BUS_HIGH(bus); // TX high - idle state - generate ISR on RX pin
|
||||
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
// read bus state
|
||||
if(DALI_GET_BUS_LEVEL(bus) == 0) // other device is keeping bus low
|
||||
{
|
||||
@@ -467,33 +945,58 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
}
|
||||
|
||||
// start transmitting
|
||||
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE && bus->tx_queue && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, NULL) == pdTRUE)
|
||||
else if(bus->tx_state == TX_STATE_IDLE && bus->tx_queue)
|
||||
{
|
||||
bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success
|
||||
bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting
|
||||
bus->tx_state = TX_STATE_START; // start transmitting
|
||||
bus->tx_half_bit_counter = 0;
|
||||
bus->tx_data_bit_counter = 0; // actually sent bits count
|
||||
DALI_SET_BUS_LOW(bus); // start bit first half
|
||||
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
Dali_msg_t pending = {0};
|
||||
bool can_start = bus->bus_state == DALI_BUS_READY;
|
||||
bool force_backward = false;
|
||||
if (!can_start && bus->force_backward_tx &&
|
||||
xQueuePeekFromISR(bus->tx_queue, &pending) == pdTRUE) {
|
||||
if (is_backward_tx_msg(&pending) && bus->bus_state != DALI_BUS_POWER_DOWN) {
|
||||
can_start = true;
|
||||
force_backward = true;
|
||||
} else {
|
||||
bus->force_backward_tx = false;
|
||||
}
|
||||
}
|
||||
if (can_start && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, &yield) == pdTRUE) {
|
||||
bus->force_backward_tx = false;
|
||||
if (force_backward) {
|
||||
bus->rx_state = RX_STATE_IDLE;
|
||||
}
|
||||
bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success
|
||||
bus->bus_activity_seen = true;
|
||||
bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting
|
||||
bus->tx_state = TX_STATE_START; // start transmitting
|
||||
bus->tx_half_bit_counter = 0;
|
||||
bus->tx_data_bit_counter = 0; // actually sent bits count
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
DALI_SET_BUS_LOW(bus); // start bit first half
|
||||
}
|
||||
}
|
||||
else if(bus->bus_state == DALI_BUS_TRANSMITTING)
|
||||
{
|
||||
uint8_t bus_level = DALI_GET_BUS_LEVEL(bus);
|
||||
if(bus->tx_state != TX_STATE_STOP && !is_backward_tx_msg(&bus->tx_data) &&
|
||||
tx_delta > s_timing.collision_txrx_delta &&
|
||||
bus_level != DALI_GET_TX_LEVEL(bus)) {
|
||||
bus->rx_level = bus_level;
|
||||
bus->rx_tx_delta = tx_delta;
|
||||
start_tx_collision_recovery_from_isr(bus, time_now);
|
||||
}
|
||||
// transmit data
|
||||
if(bus->tx_state == TX_STATE_START) {
|
||||
else if(bus->tx_state == TX_STATE_START) {
|
||||
bus->tx_state = TX_STATE_DATA; // start transmitting data
|
||||
bus->tx_half_bit_counter++;
|
||||
DALI_SET_BUS_HIGH(bus); // start bit second half
|
||||
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
DALI_SET_BUS_HIGH(bus); // start bit second half
|
||||
}
|
||||
else if(bus->tx_state == TX_STATE_DATA) {
|
||||
bool value = (bus->tx_data.data[bus->tx_data_bit_counter/8] >> ( 7 - (bus->tx_data_bit_counter % 8) )) & 0x01;
|
||||
value ^= bus->tx_half_bit_counter & 0x01; // xor=invert value for odd half bit 1:0->1 and 0:1->0
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
DALI_SET_BUS_LEVEL(bus, value);
|
||||
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
bus->tx_half_bit_counter++; // increment half bit counter before next test
|
||||
if(bus->tx_half_bit_counter & 0x01) { // next bit
|
||||
bus->tx_data_bit_counter++;
|
||||
@@ -506,15 +1009,11 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
// here we check TX (NOT RX) bit state
|
||||
if(DALI_GET_TX_LEVEL(bus) == 0) // really ok - otherwise we will keep bus low forever
|
||||
{
|
||||
DALI_SET_BUS_HIGH(bus);
|
||||
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
|
||||
DALI_SET_BUS_HIGH(bus);
|
||||
}
|
||||
else if(tx_delta > s_timing.tx_stop_cond) {
|
||||
bus->tx_data.status = DALI_FRAME_OK; // frame is OK
|
||||
if (bus->tx_reply_queue) {
|
||||
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
|
||||
}
|
||||
bus->tx_state = TX_STATE_IDLE; // final state with transmitted data
|
||||
complete_tx_from_isr(bus, &yield);
|
||||
bus->bus_state = DALI_BUS_READY; // bus is ready
|
||||
}
|
||||
}
|
||||
@@ -522,7 +1021,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state > TX_STATE_IDLE)
|
||||
{
|
||||
// we are not transmitting but we have data - reply to queue and let error state in tx_data.status
|
||||
if (bus->tx_reply_queue) {
|
||||
if (bus->tx_reply_queue && !is_backward_tx_msg(&bus->tx_data)) {
|
||||
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
|
||||
}
|
||||
bus->tx_state = TX_STATE_IDLE; // clear state
|
||||
@@ -538,6 +1037,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
// rx_data.status = DALI_FRAME_ERROR; // should be set inside ISR
|
||||
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
|
||||
// rx_data.data[0] = 0xAA; // debug
|
||||
note_rx_frame_from_isr(bus);
|
||||
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
|
||||
}
|
||||
}
|
||||
@@ -549,9 +1049,14 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
bus->rx_data.status = DALI_FRAME_OK; // frame is OK
|
||||
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
|
||||
// rx_data.data[0] = 0xBB; // debug
|
||||
note_rx_frame_from_isr(bus);
|
||||
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
|
||||
}
|
||||
}
|
||||
else if(bus->rx_state == RX_STATE_START && rx_delta > s_timing.rx_hb_max) {
|
||||
bus->rx_state = RX_STATE_ERROR;
|
||||
bus->rx_data.status = DALI_FRAME_TIME_VIOLATION;
|
||||
}
|
||||
}
|
||||
return yield;
|
||||
}
|
||||
@@ -588,7 +1093,7 @@ static void debug_task(void *pvParameters)
|
||||
else v = '0' + dbg.level;
|
||||
}
|
||||
else v=' ';
|
||||
printf("bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]",
|
||||
ESP_LOGD(TAG, "bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]",
|
||||
dbg.bus_id, i++, dbg.rx_pulse_width, dbg.level, dbg.rx_tx_delta, v);
|
||||
if(dbg.rx_pulse_width < 1000) {
|
||||
if(dbg.rx_pulse_width > 550) HB+=2;
|
||||
@@ -659,7 +1164,7 @@ static esp_err_t ensure_timer_started(void)
|
||||
.flags = {
|
||||
.intr_shared = true,
|
||||
},
|
||||
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
|
||||
.resolution_hz = CONFIG_DALI_TIMER_RESOLUTION_HZ,
|
||||
};
|
||||
err = gptimer_new_timer(&timer_config, &gptimer);
|
||||
if (err != ESP_OK) {
|
||||
@@ -726,6 +1231,9 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
|
||||
bus->bus_id = bus_id;
|
||||
bus->tx_pin = tx_pin;
|
||||
bus->rx_pin = rx_pin;
|
||||
bus->tx_level = 1;
|
||||
|
||||
gpio_set_level(bus->tx_pin, DALI_TX_HIGH);
|
||||
|
||||
gpio_config_t io_conf;
|
||||
|
||||
@@ -771,6 +1279,13 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
|
||||
bus->rx_last_edge_time = esp_timer_get_time(); // get time in us - startup time
|
||||
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_abnormal_report_time = 0;
|
||||
bus->bus_activity_seen = false;
|
||||
bus->rx_last_frame_time = 0;
|
||||
bus->rx_last_frame_length = 0;
|
||||
bus->rx_last_frame_status = DALI_FRAME_UNKNOWN;
|
||||
bus->force_backward_tx = false;
|
||||
|
||||
err = ensure_isr_service();
|
||||
if (err != ESP_OK) {
|
||||
@@ -805,17 +1320,69 @@ static int dali_tx_bus(dali_bus_ctx_t *bus, Dali_msg_t *dali_msg)
|
||||
if (bus == NULL || !bus->inited) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if(xQueueSendToBack(bus->tx_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
|
||||
xQueueReset(bus->tx_queue); // clear queue
|
||||
printf("dali_tx: Queue full\n");
|
||||
if (bus->tx_queue == NULL || bus->tx_reply_queue == NULL || dali_msg == NULL) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if(xQueueReceive(bus->tx_reply_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
|
||||
xQueueReset(bus->tx_reply_queue); // clear queue
|
||||
printf("dali_tx: No reply\n");
|
||||
|
||||
const uint32_t timeout_ms = tx_completion_timeout_ms(dali_msg);
|
||||
const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
|
||||
drain_tx_reply_queue(bus);
|
||||
|
||||
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
|
||||
dali_msg->status = DALI_FRAME_ERROR;
|
||||
log_tx_message("native TX skipped, bus power down", bus, dali_msg, timeout_ms);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
ESP_LOGD(TAG, "bus=%u queue TX len=%u data=%02x %02x %02x %02x timeoutMs=%lu",
|
||||
bus->bus_id, dali_msg->length, dali_msg->data[0], dali_msg->data[1],
|
||||
dali_msg->data[2], dali_msg->data[3], (unsigned long)timeout_ms);
|
||||
|
||||
if (is_forward_tx_msg(dali_msg) && !wait_for_forward_tx_window(bus, dali_msg)) {
|
||||
dali_msg->status = DALI_FRAME_ERROR;
|
||||
log_tx_message("native TX wait timeout", bus, dali_msg,
|
||||
CONFIG_DALI_FORWARD_MAX_WAIT_MS);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
bool force_backward = false;
|
||||
if (is_backward_tx_msg(dali_msg) &&
|
||||
!wait_for_backward_tx_window(bus, dali_msg, &force_backward)) {
|
||||
dali_msg->status = DALI_FRAME_ERROR;
|
||||
log_tx_message("native backward TX wait timeout", bus, dali_msg,
|
||||
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
bus->force_backward_tx = force_backward;
|
||||
if(xQueueSendToBack(bus->tx_queue, dali_msg, 0) == pdFALSE) {
|
||||
bus->force_backward_tx = false;
|
||||
log_tx_message("native TX queue full", bus, dali_msg, timeout_ms);
|
||||
if (bus->tx_state == TX_STATE_IDLE) {
|
||||
xQueueReset(bus->tx_queue);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (is_backward_tx_msg(dali_msg)) {
|
||||
dali_msg->status = DALI_FRAME_OK;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if(xQueueReceive(bus->tx_reply_queue, dali_msg, timeout_ticks) == pdFALSE) {
|
||||
if (bus->tx_state == TX_STATE_IDLE && queue_waiting(bus->tx_queue) == 0) {
|
||||
*dali_msg = bus->tx_data;
|
||||
log_tx_message("native TX completion queue missed", bus, dali_msg, timeout_ms);
|
||||
return dali_msg->status == DALI_FRAME_OK ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
log_tx_message("native TX completion timeout", bus, dali_msg, timeout_ms);
|
||||
recover_tx_timeout(bus);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ESP_LOGD(TAG, "bus=%u TX complete status=%u len=%u data=%02x %02x %02x %02x",
|
||||
bus->bus_id, dali_msg->status, dali_msg->length, dali_msg->data[0],
|
||||
dali_msg->data[1], dali_msg->data[2], dali_msg->data[3]);
|
||||
return dali_msg->status == DALI_FRAME_OK ? ESP_OK : ESP_FAIL;
|
||||
}
|
||||
|
||||
// dali_task - should run at highest priority
|
||||
@@ -843,6 +1410,7 @@ void dali_task(void *pvParameters)
|
||||
esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
|
||||
{
|
||||
dali_hal_lock();
|
||||
apply_dali_log_level();
|
||||
|
||||
esp_err_t err = ensure_timing_ready_locked();
|
||||
if (err == ESP_OK) {
|
||||
@@ -873,6 +1441,7 @@ esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
|
||||
esp_err_t dali_hal_set_baudrate(uint32_t baudrate)
|
||||
{
|
||||
dali_hal_lock();
|
||||
apply_dali_log_level();
|
||||
|
||||
bool resume_timer = s_timer_started && gptimer;
|
||||
if (resume_timer) {
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#include <stdint.h>
|
||||
#include "dali_hal.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
Addressing: 102.7.2.1
|
||||
0AAA AAAx - short address AAAAAA 0-63
|
||||
@@ -77,5 +81,9 @@ void dali_set_search_addr24(uint32_t addr24);
|
||||
uint32_t dali_binary_search();
|
||||
void dali_change_short_address(int addr1, int addr2);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DALI_PHY_COUNT
|
||||
#define DALI_PHY_COUNT CONFIG_DALI_PHY_COUNT
|
||||
#else
|
||||
@@ -182,13 +186,22 @@ typedef struct Dali_msg Dali_msg_t;
|
||||
|
||||
// define HW DALI for gpio functions
|
||||
#ifndef DALI_HW_PINS
|
||||
// define HW DALI for gpio functions
|
||||
// - what we should write to pin to get HIGH/LOW
|
||||
#define DALI_TX_HIGH 1 // idle state
|
||||
#define DALI_TX_LOW 0 // active state
|
||||
// - what we should read from pin to get HIGH/LOW
|
||||
#define DALI_RX_HIGH 1 // idle state
|
||||
#define DALI_RX_LOW 0 // active state
|
||||
// Physical GPIO levels for logical DALI idle/high and active/low bus states.
|
||||
#ifdef CONFIG_DALI_TX_ACTIVE_HIGH
|
||||
#define DALI_TX_HIGH 0 // idle state
|
||||
#define DALI_TX_LOW 1 // active state
|
||||
#else
|
||||
#define DALI_TX_HIGH 1 // idle state
|
||||
#define DALI_TX_LOW 0 // active state
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DALI_RX_ACTIVE_LOW
|
||||
#define DALI_RX_HIGH 1 // idle state
|
||||
#define DALI_RX_LOW 0 // active state
|
||||
#else
|
||||
#define DALI_RX_HIGH 0 // idle state
|
||||
#define DALI_RX_LOW 1 // active state
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// LED onboard - debug
|
||||
@@ -228,3 +241,7 @@ esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info);
|
||||
QueueHandle_t dali_hal_raw_receive_queue(void);
|
||||
void dali_task(void *pvParameters);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -93,6 +94,21 @@ struct DaliDomainSnapshot {
|
||||
std::map<std::string, std::vector<double>> number_arrays;
|
||||
};
|
||||
|
||||
struct DaliAddressSettingsSnapshot {
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
class DaliDomainService {
|
||||
public:
|
||||
DaliDomainService();
|
||||
@@ -118,9 +134,14 @@ class DaliDomainService {
|
||||
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<DaliDomainSnapshot> baseStatusSnapshot(uint8_t gateway_id,
|
||||
int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt1Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt4Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt5Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt6Snapshot(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt8StatusSnapshot(uint8_t gateway_id,
|
||||
int short_address) const;
|
||||
std::optional<DaliDomainSnapshot> dt8SceneColorReport(uint8_t gateway_id, int short_address,
|
||||
int scene) const;
|
||||
std::optional<DaliDomainSnapshot> dt8PowerOnLevelColorReport(uint8_t gateway_id,
|
||||
@@ -141,6 +162,15 @@ class DaliDomainService {
|
||||
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;
|
||||
std::optional<uint16_t> queryGroupMask(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<uint8_t> querySceneLevel(uint8_t gateway_id, int short_address, int scene) const;
|
||||
std::optional<DaliAddressSettingsSnapshot> queryAddressSettings(uint8_t gateway_id,
|
||||
int short_address) const;
|
||||
bool applyGroupMask(uint8_t gateway_id, int short_address, uint16_t group_mask) const;
|
||||
bool applySceneLevel(uint8_t gateway_id, int short_address, int scene,
|
||||
std::optional<uint8_t> level) const;
|
||||
bool applyAddressSettings(uint8_t gateway_id, int short_address,
|
||||
const DaliAddressSettingsSnapshot& settings) const;
|
||||
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;
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#ifndef CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS
|
||||
#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 25
|
||||
#endif
|
||||
|
||||
namespace gateway {
|
||||
|
||||
namespace {
|
||||
@@ -17,6 +21,48 @@ namespace {
|
||||
constexpr const char* kTag = "dali_domain";
|
||||
constexpr size_t kSerialRxPacketMaxBytes = 8;
|
||||
constexpr UBaseType_t kSerialRxQueueDepth = 8;
|
||||
constexpr uint32_t kHardwareQueryRawSuppressMs = CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS + 10;
|
||||
|
||||
portMUX_TYPE s_query_raw_suppress_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
TickType_t s_query_raw_suppress_until[DALI_PHY_COUNT] = {};
|
||||
|
||||
void BeginHardwareQueryRawSuppress(uint8_t bus_id) {
|
||||
if (bus_id >= DALI_PHY_COUNT) {
|
||||
return;
|
||||
}
|
||||
const TickType_t until = xTaskGetTickCount() + pdMS_TO_TICKS(kHardwareQueryRawSuppressMs);
|
||||
portENTER_CRITICAL(&s_query_raw_suppress_lock);
|
||||
s_query_raw_suppress_until[bus_id] = until;
|
||||
portEXIT_CRITICAL(&s_query_raw_suppress_lock);
|
||||
}
|
||||
|
||||
bool TakeHardwareQueryRawSuppress(uint8_t bus_id) {
|
||||
if (bus_id >= DALI_PHY_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool suppress = false;
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
portENTER_CRITICAL(&s_query_raw_suppress_lock);
|
||||
const TickType_t until = s_query_raw_suppress_until[bus_id];
|
||||
if (until != 0 && now <= until) {
|
||||
suppress = true;
|
||||
s_query_raw_suppress_until[bus_id] = 0;
|
||||
} else if (until != 0) {
|
||||
s_query_raw_suppress_until[bus_id] = 0;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_query_raw_suppress_lock);
|
||||
return suppress;
|
||||
}
|
||||
|
||||
void ClearHardwareQueryRawSuppress(uint8_t bus_id) {
|
||||
if (bus_id >= DALI_PHY_COUNT) {
|
||||
return;
|
||||
}
|
||||
portENTER_CRITICAL(&s_query_raw_suppress_lock);
|
||||
s_query_raw_suppress_until[bus_id] = 0;
|
||||
portEXIT_CRITICAL(&s_query_raw_suppress_lock);
|
||||
}
|
||||
|
||||
DaliDomainSnapshot MakeSnapshot(uint8_t gateway_id, int address, const char* kind) {
|
||||
DaliDomainSnapshot snapshot;
|
||||
@@ -64,6 +110,10 @@ struct SerialRxPacket {
|
||||
uint8_t data[kSerialRxPacketMaxBytes]{};
|
||||
};
|
||||
|
||||
std::vector<uint8_t> LegacyQueryResponse(uint8_t status, uint8_t value = 0x00) {
|
||||
return {status, value};
|
||||
}
|
||||
|
||||
bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len != 3) {
|
||||
return false;
|
||||
@@ -89,9 +139,12 @@ bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
|
||||
}
|
||||
|
||||
std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len != 3) {
|
||||
if (data == nullptr) {
|
||||
return {};
|
||||
}
|
||||
if (len != 3) {
|
||||
return len > 0 && data[0] == 0x12 ? LegacyQueryResponse(0xFD) : std::vector<uint8_t>{};
|
||||
}
|
||||
|
||||
switch (data[0]) {
|
||||
case 0x00:
|
||||
@@ -106,10 +159,16 @@ std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data,
|
||||
Dali_msg_t tx = dali_msg_new(data[1], data[2]);
|
||||
tx.id = bus_id;
|
||||
Dali_msg_t rx = {};
|
||||
BeginHardwareQueryRawSuppress(bus_id);
|
||||
if (dali_query(&tx, &rx) == pdTRUE) {
|
||||
if (rx.status != DALI_FRAME_OK || rx.length != 8) {
|
||||
ClearHardwareQueryRawSuppress(bus_id);
|
||||
return LegacyQueryResponse(0xFD);
|
||||
}
|
||||
return {0xFF, rx.data[0]};
|
||||
}
|
||||
return {0xFE};
|
||||
ClearHardwareQueryRawSuppress(bus_id);
|
||||
return LegacyQueryResponse(0xFE);
|
||||
}
|
||||
default:
|
||||
return {};
|
||||
@@ -151,13 +210,19 @@ std::vector<uint8_t> ReadSerialFrame(QueueHandle_t queue, size_t len, uint32_t t
|
||||
std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
|
||||
uint32_t query_timeout_ms, const uint8_t* data,
|
||||
size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
return LegacyQueryResponse(0xFD);
|
||||
}
|
||||
if (data[0] == 0x12 && len != 3) {
|
||||
return LegacyQueryResponse(0xFD);
|
||||
}
|
||||
if (data != nullptr && len > 0 && data[0] == 0x12) {
|
||||
DrainSerialQueue(queue);
|
||||
}
|
||||
if (!WriteSerialFrame(uart_port, data, len)) {
|
||||
return {0xFD};
|
||||
return LegacyQueryResponse(0xFD);
|
||||
}
|
||||
if (data == nullptr || len == 0 || data[0] != 0x12) {
|
||||
if (data[0] != 0x12) {
|
||||
return {0xFF};
|
||||
}
|
||||
|
||||
@@ -173,10 +238,10 @@ std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
|
||||
auto response = PacketToVector(packet, 2);
|
||||
if (!response.empty() &&
|
||||
(response[0] == 0xFF || response[0] == 0xFE || response[0] == 0xFD)) {
|
||||
return response;
|
||||
return LegacyQueryResponse(response[0], response.size() > 1 ? response[1] : 0x00);
|
||||
}
|
||||
}
|
||||
return {0xFE};
|
||||
return LegacyQueryResponse(0xFE);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -290,8 +355,9 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
|
||||
ESP_LOGE(kTag, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = uart_set_pin(uart, config.tx_pin, config.rx_pin, UART_PIN_NO_CHANGE,
|
||||
UART_PIN_NO_CHANGE);
|
||||
err = uart_set_pin(uart, config.tx_pin < 0 ? UART_PIN_NO_CHANGE : config.tx_pin,
|
||||
config.rx_pin < 0 ? UART_PIN_NO_CHANGE : config.rx_pin,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to set uart%d pins tx=%d rx=%d: %s", config.uart_port,
|
||||
config.tx_pin, config.rx_pin, esp_err_to_name(err));
|
||||
@@ -451,7 +517,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const std::vector<int> fallback = fallback_types.empty() ? std::vector<int>{4, 5, 6, 8}
|
||||
const std::vector<int> fallback = fallback_types.empty() ? std::vector<int>{1, 4, 5, 6, 8}
|
||||
: fallback_types;
|
||||
auto discovery = channel->dali->base.discoverDeviceTypes(short_address, fallback,
|
||||
max_next_types);
|
||||
@@ -466,6 +532,113 @@ std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::baseStatusSnapshot(
|
||||
uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto raw_status = channel->dali->base.getStatus(short_address);
|
||||
if (!raw_status.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const DaliStatus status = DaliStatus::fromByte(static_cast<uint8_t>(raw_status.value()));
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "base_status");
|
||||
snapshot.ints["rawStatus"] = raw_status.value() & 0xFF;
|
||||
snapshot.bools["controlGearPresent"] = status.controlGearPresent;
|
||||
snapshot.bools["lampFailure"] = status.lampFailure;
|
||||
snapshot.bools["lampPowerOn"] = status.lampPowerOn;
|
||||
snapshot.bools["limitError"] = status.limitError;
|
||||
snapshot.bools["fadingCompleted"] = status.fadingCompleted;
|
||||
snapshot.bools["resetState"] = status.resetState;
|
||||
snapshot.bools["missingShortAddress"] = status.missingShortAddress;
|
||||
snapshot.bools["powerSupplyFault"] = status.psFault;
|
||||
snapshot.bools["psFault"] = status.psFault;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt1Snapshot(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto detailed = channel->dali->dt1.getDT1TestStatusDetailed(short_address);
|
||||
if (!detailed.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt1");
|
||||
PutOptionalInt(snapshot, "failureStatusRaw", detailed->failureStatus);
|
||||
PutOptionalInt(snapshot, "emergencyStatusRaw", detailed->emergencyStatus);
|
||||
PutOptionalInt(snapshot, "emergencyModeRaw", detailed->emergencyMode);
|
||||
PutOptionalInt(snapshot, "featuresRaw", detailed->feature);
|
||||
PutOptionalInt(snapshot, "deviceStatusRaw", detailed->deviceStatus);
|
||||
|
||||
snapshot.bools["testInProgress"] = detailed->testInProgress;
|
||||
snapshot.bools["lampFailure"] = detailed->lampFailure;
|
||||
snapshot.bools["batteryFailure"] = detailed->batteryFailure;
|
||||
snapshot.bools["functionTestActive"] = detailed->functionTestActive;
|
||||
snapshot.bools["durationTestActive"] = detailed->durationTestActive;
|
||||
snapshot.bools["testDone"] = detailed->testDone;
|
||||
snapshot.bools["identifyActive"] = detailed->identifyActive;
|
||||
snapshot.bools["physicalSelectionActive"] = detailed->physicalSelectionActive;
|
||||
snapshot.bools["circuitFailure"] = detailed->circuitFailure;
|
||||
snapshot.bools["batteryDurationFailure"] = detailed->batteryDurationFailure;
|
||||
snapshot.bools["emergencyLampFailure"] = detailed->emergencyLampFailure;
|
||||
snapshot.bools["functionTestMaxDelayExceeded"] = detailed->functionTestMaxDelayExceeded;
|
||||
snapshot.bools["durationTestMaxDelayExceeded"] = detailed->durationTestMaxDelayExceeded;
|
||||
snapshot.bools["functionTestFailed"] = detailed->functionTestFailed;
|
||||
snapshot.bools["durationTestFailed"] = detailed->durationTestFailed;
|
||||
snapshot.bools["functionTestResultValid"] = detailed->functionTestResultValid;
|
||||
snapshot.bools["durationTestResultValid"] = detailed->durationTestResultValid;
|
||||
snapshot.bools["batteryFullyCharged"] = detailed->batteryFullyCharged;
|
||||
snapshot.bools["functionTestPending"] = detailed->functionTestPending;
|
||||
snapshot.bools["durationTestPending"] = detailed->durationTestPending;
|
||||
snapshot.bools["restModeActive"] = detailed->restModeActive;
|
||||
snapshot.bools["normalModeActive"] = detailed->normalModeActive;
|
||||
snapshot.bools["emergencyModeActive"] = detailed->emergencyModeActive;
|
||||
snapshot.bools["extendedEmergencyModeActive"] = detailed->extendedEmergencyModeActive;
|
||||
snapshot.bools["hardwiredInhibitActive"] = detailed->hardwiredInhibitActive;
|
||||
snapshot.bools["hardwiredSwitchOn"] = detailed->hardwiredSwitchOn;
|
||||
snapshot.bools["supportsAutoTest"] = detailed->supportsAutoTest;
|
||||
snapshot.bools["supportsAdjustableEmergencyLevel"] = detailed->supportsAdjustableEmergencyLevel;
|
||||
|
||||
if (detailed->emergencyStatus.has_value()) {
|
||||
const DaliDT1EmergencyStatus status(detailed->emergencyStatus.value());
|
||||
snapshot.bools["inhibitMode"] = status.inhibitMode();
|
||||
snapshot.bools["functionTestRequestPending"] = status.functionTestRequestPending();
|
||||
snapshot.bools["durationTestRequestPending"] = status.durationTestRequestPending();
|
||||
snapshot.bools["identificationActive"] = status.identificationActive();
|
||||
snapshot.bools["physicallySelected"] = status.physicallySelected();
|
||||
}
|
||||
if (detailed->emergencyMode.has_value()) {
|
||||
const DaliDT1EmergencyMode mode(detailed->emergencyMode.value());
|
||||
snapshot.bools["functionTestInProgress"] = mode.functionTestInProgress();
|
||||
snapshot.bools["durationTestInProgress"] = mode.durationTestInProgress();
|
||||
}
|
||||
if (detailed->feature.has_value()) {
|
||||
const DaliDT1Features features(detailed->feature.value());
|
||||
snapshot.bools["integralEmergencyControlGear"] = features.integralEmergencyControlGear();
|
||||
snapshot.bools["maintainedControlGear"] = features.maintainedControlGear();
|
||||
snapshot.bools["switchedMaintainedControlGear"] = features.switchedMaintainedControlGear();
|
||||
snapshot.bools["autoTestCapability"] = features.autoTestCapability();
|
||||
snapshot.bools["adjustableEmergencyLevel"] = features.adjustableEmergencyLevel();
|
||||
snapshot.bools["hardwiredInhibitSupported"] = features.hardwiredInhibitSupported();
|
||||
snapshot.bools["physicalSelectionSupported"] = features.physicalSelectionSupported();
|
||||
snapshot.bools["relightInRestModeSupported"] = features.relightInRestModeSupported();
|
||||
}
|
||||
if (detailed->deviceStatus.has_value()) {
|
||||
const DaliDT1DeviceStatus status(detailed->deviceStatus.value());
|
||||
snapshot.bools["controlGearFailure"] = status.controlGearFailure();
|
||||
snapshot.bools["controlGearOk"] = status.controlGearOk();
|
||||
snapshot.bools["lampPoweredByEmergencyGear"] = status.lampPoweredByEmergencyGear();
|
||||
snapshot.bools["deviceResetState"] = status.resetState();
|
||||
snapshot.bools["deviceMissingShortAddress"] = status.missingShortAddress();
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
@@ -506,15 +679,27 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway
|
||||
snapshot.ints["featuresRaw2"] = features->raw2();
|
||||
snapshot.ints["featuresRaw3"] = features->raw3();
|
||||
snapshot.ints["dimmingMethodCode"] = features->dimmingMethodCode();
|
||||
snapshot.bools["canQueryLoadOverCurrentShutdown"] =
|
||||
features->canQueryLoadOverCurrentShutdown();
|
||||
snapshot.bools["canQueryOpenCircuitDetection"] = features->canQueryOpenCircuitDetection();
|
||||
snapshot.bools["canQueryLoadDecrease"] = features->canQueryLoadDecrease();
|
||||
snapshot.bools["canQueryLoadIncrease"] = features->canQueryLoadIncrease();
|
||||
snapshot.bools["canQueryThermalShutdown"] = features->canQueryThermalShutdown();
|
||||
snapshot.bools["canQueryThermalOverloadReduction"] =
|
||||
features->canQueryThermalOverloadReduction();
|
||||
snapshot.bools["canQueryTemperature"] = features->canQueryTemperature();
|
||||
snapshot.bools["canQuerySupplyVoltage"] = features->canQuerySupplyVoltage();
|
||||
snapshot.bools["canQuerySupplyFrequency"] = features->canQuerySupplyFrequency();
|
||||
snapshot.bools["canQueryLoadVoltage"] = features->canQueryLoadVoltage();
|
||||
snapshot.bools["canQueryLoadCurrent"] = features->canQueryLoadCurrent();
|
||||
snapshot.bools["canQueryRealLoadPower"] = features->canQueryRealLoadPower();
|
||||
snapshot.bools["canQueryLoadRating"] = features->canQueryLoadRating();
|
||||
snapshot.bools["canQueryCurrentOverloadReduction"] =
|
||||
features->canQueryCurrentOverloadReduction();
|
||||
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
|
||||
snapshot.bools["canSelectNonLogarithmicDimmingCurve"] =
|
||||
features->canSelectNonLogarithmicDimmingCurve();
|
||||
snapshot.bools["canQueryUnsuitableLoad"] = features->canQueryUnsuitableLoad();
|
||||
}
|
||||
if (const auto failure = dt4.getFailureStatus(short_address)) {
|
||||
snapshot.ints["failureRaw1"] = failure->raw1();
|
||||
@@ -526,7 +711,10 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway
|
||||
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
|
||||
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
|
||||
snapshot.bools["referenceMeasurementFailedStatus"] = failure->referenceMeasurementFailed();
|
||||
snapshot.bools["loadUnsuitableForSelectedMethod"] =
|
||||
failure->loadUnsuitableForSelectedMethod();
|
||||
snapshot.bools["supplyVoltageOutOfLimits"] = failure->supplyVoltageOutOfLimits();
|
||||
snapshot.bools["supplyFrequencyOutOfLimits"] = failure->supplyFrequencyOutOfLimits();
|
||||
snapshot.bools["loadVoltageOutOfLimits"] = failure->loadVoltageOutOfLimits();
|
||||
snapshot.bools["loadCurrentOverloadReduction"] = failure->loadCurrentOverloadReduction();
|
||||
}
|
||||
@@ -637,6 +825,40 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt6Snapshot(uint8_t gateway
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt8StatusSnapshot(
|
||||
uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_status");
|
||||
bool has_data = false;
|
||||
if (const auto status = channel->dali->dt8.getColorStatus(short_address)) {
|
||||
has_data = true;
|
||||
snapshot.ints["colorStatusRaw"] = status->raw();
|
||||
snapshot.bools["xyOutOfRange"] = status->xyOutOfRange();
|
||||
snapshot.bools["ctOutOfRange"] = status->ctOutOfRange();
|
||||
snapshot.bools["autoCalibrationActive"] = status->autoCalibrationActive();
|
||||
snapshot.bools["autoCalibrationSuccess"] = status->autoCalibrationSuccess();
|
||||
snapshot.bools["xyActive"] = status->xyActive();
|
||||
snapshot.bools["ctActive"] = status->ctActive();
|
||||
snapshot.bools["primaryNActive"] = status->primaryNActive();
|
||||
snapshot.bools["rgbwafActive"] = status->rgbwafActive();
|
||||
}
|
||||
if (const auto features = channel->dali->dt8.getColorTypeFeature(short_address)) {
|
||||
has_data = true;
|
||||
snapshot.ints["colorTypeFeaturesRaw"] = features->features();
|
||||
snapshot.ints["primaryCount"] = features->primaryCount();
|
||||
snapshot.ints["rgbwafChannels"] = features->rgbwafChannels();
|
||||
snapshot.bools["xyCapable"] = features->xyCapable();
|
||||
snapshot.bools["ctCapable"] = features->ctCapable();
|
||||
snapshot.bools["primaryNCapable"] = features->primaryNCapable();
|
||||
snapshot.bools["rgbwafCapable"] = features->rgbwafCapable();
|
||||
}
|
||||
return has_data ? std::optional<DaliDomainSnapshot>(std::move(snapshot)) : std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
|
||||
uint8_t gateway_id, int short_address, int scene) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
@@ -779,6 +1001,125 @@ bool DaliDomainService::off(int short_address) const {
|
||||
return off(channels_.front()->config.gateway_id, short_address);
|
||||
}
|
||||
|
||||
std::optional<uint16_t> DaliDomainService::queryGroupMask(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto group_mask = channel->dali->base.getGroup(short_address);
|
||||
if (!group_mask.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<uint16_t>(*group_mask);
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DaliDomainService::querySceneLevel(uint8_t gateway_id, int short_address,
|
||||
int scene) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto level = channel->dali->base.getScene(short_address, scene);
|
||||
if (!level.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return static_cast<uint8_t>(*level);
|
||||
}
|
||||
|
||||
std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettings(
|
||||
uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DaliAddressSettingsSnapshot settings{};
|
||||
|
||||
if (const auto value = channel->dali->base.getPowerOnLevel(short_address); value.has_value()) {
|
||||
settings.power_on_level = static_cast<uint8_t>(*value);
|
||||
}
|
||||
if (const auto value = channel->dali->base.getSystemFailureLevel(short_address);
|
||||
value.has_value()) {
|
||||
settings.system_failure_level = static_cast<uint8_t>(*value);
|
||||
}
|
||||
if (const auto value = channel->dali->base.getMinLevel(short_address); value.has_value()) {
|
||||
settings.min_level = static_cast<uint8_t>(*value);
|
||||
}
|
||||
if (const auto value = channel->dali->base.getMaxLevel(short_address); value.has_value()) {
|
||||
settings.max_level = static_cast<uint8_t>(*value);
|
||||
}
|
||||
if (const auto value = channel->dali->base.getFadeTime(short_address); value.has_value()) {
|
||||
settings.fade_time = static_cast<uint8_t>(*value);
|
||||
}
|
||||
if (const auto value = channel->dali->base.getFadeRate(short_address); value.has_value()) {
|
||||
settings.fade_rate = static_cast<uint8_t>(*value);
|
||||
}
|
||||
|
||||
if (!settings.anyKnown()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
bool DaliDomainService::applyGroupMask(uint8_t gateway_id, int short_address,
|
||||
uint16_t group_mask) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->base.setGroup(short_address, group_mask);
|
||||
}
|
||||
|
||||
bool DaliDomainService::applySceneLevel(uint8_t gateway_id, int short_address, int scene,
|
||||
std::optional<uint8_t> level) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr || !level.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*level == 255U) {
|
||||
return channel->dali->base.removeScene(short_address, scene);
|
||||
}
|
||||
|
||||
return channel->dali->base.setDTR(*level) &&
|
||||
channel->dali->base.storeDTRAsSceneBright(short_address, scene);
|
||||
}
|
||||
|
||||
bool DaliDomainService::applyAddressSettings(uint8_t gateway_id, int short_address,
|
||||
const DaliAddressSettingsSnapshot& settings) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
if (settings.power_on_level.has_value()) {
|
||||
ok = ok && channel->dali->base.setPowerOnLevel(short_address, *settings.power_on_level);
|
||||
}
|
||||
if (settings.system_failure_level.has_value()) {
|
||||
ok = ok &&
|
||||
channel->dali->base.setSystemFailureLevel(short_address, *settings.system_failure_level);
|
||||
}
|
||||
if (settings.min_level.has_value()) {
|
||||
ok = ok && channel->dali->base.setMinLevel(short_address, *settings.min_level);
|
||||
}
|
||||
if (settings.max_level.has_value()) {
|
||||
ok = ok && channel->dali->base.setMaxLevel(short_address, *settings.max_level);
|
||||
}
|
||||
if (settings.fade_time.has_value()) {
|
||||
ok = ok && channel->dali->base.setFadeTime(short_address, *settings.fade_time);
|
||||
}
|
||||
if (settings.fade_rate.has_value()) {
|
||||
ok = ok && channel->dali->base.setFadeRate(short_address, *settings.fade_rate);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool DaliDomainService::updateChannelName(uint8_t gateway_id, std::string_view name) {
|
||||
auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr) {
|
||||
@@ -958,6 +1299,12 @@ void DaliDomainService::rawFrameTaskLoop() {
|
||||
if (byte_count > DALI_MAX_BYTES) {
|
||||
byte_count = DALI_MAX_BYTES;
|
||||
}
|
||||
if (byte_count == 1 && TakeHardwareQueryRawSuppress(message.id)) {
|
||||
continue;
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_485_control.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_driver_uart freertos gateway_controller log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class GatewayController;
|
||||
|
||||
struct Gateway485ControlBridgeConfig {
|
||||
bool enabled{false};
|
||||
int tx_pin{-1};
|
||||
int rx_pin{-1};
|
||||
uint32_t baudrate{9600};
|
||||
size_t rx_buffer_size{256};
|
||||
size_t tx_buffer_size{256};
|
||||
uint32_t read_timeout_ms{20};
|
||||
uint32_t write_timeout_ms{20};
|
||||
uint32_t task_stack_size{4096};
|
||||
UBaseType_t task_priority{4};
|
||||
};
|
||||
|
||||
class Gateway485ControlBridge {
|
||||
public:
|
||||
Gateway485ControlBridge(GatewayController& controller,
|
||||
Gateway485ControlBridgeConfig config = {});
|
||||
|
||||
esp_err_t start();
|
||||
|
||||
private:
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void handleBytes(const uint8_t* data, size_t len);
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
|
||||
GatewayController& controller_;
|
||||
Gateway485ControlBridgeConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
bool started_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "gateway_485_control.hpp"
|
||||
|
||||
#include "gateway_controller.hpp"
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
namespace {
|
||||
constexpr const char* kTag = "gateway_485";
|
||||
constexpr uart_port_t kControlUart = UART_NUM_0;
|
||||
constexpr size_t kCommandFrameMinLen = 7;
|
||||
|
||||
int EffectivePin(int pin) {
|
||||
return pin >= 0 ? pin : UART_PIN_NO_CHANGE;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Gateway485ControlBridge::Gateway485ControlBridge(GatewayController& controller,
|
||||
Gateway485ControlBridgeConfig config)
|
||||
: controller_(controller), config_(config) {}
|
||||
|
||||
esp_err_t Gateway485ControlBridge::start() {
|
||||
if (started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (!config_.enabled) {
|
||||
ESP_LOGI(kTag, "UART0 control bridge disabled");
|
||||
return ESP_OK;
|
||||
}
|
||||
if (uart_is_driver_installed(kControlUart)) {
|
||||
ESP_LOGE(kTag, "UART0 driver already installed; move console or other users off UART0");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uart_config_t uart_config{};
|
||||
uart_config.baud_rate = static_cast<int>(config_.baudrate);
|
||||
uart_config.data_bits = UART_DATA_8_BITS;
|
||||
uart_config.parity = UART_PARITY_DISABLE;
|
||||
uart_config.stop_bits = UART_STOP_BITS_1;
|
||||
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
uart_config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
esp_err_t err = uart_param_config(kControlUart, &uart_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure UART0: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = uart_set_pin(kControlUart, EffectivePin(config_.tx_pin), EffectivePin(config_.rx_pin),
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to set UART0 pins: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = uart_driver_install(kControlUart, static_cast<int>(config_.rx_buffer_size),
|
||||
static_cast<int>(config_.tx_buffer_size), 0, nullptr, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to install UART0 driver: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
controller_.addNotificationSink(
|
||||
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
|
||||
|
||||
const BaseType_t created = xTaskCreate(&Gateway485ControlBridge::TaskEntry,
|
||||
"gateway_485_ctrl",
|
||||
static_cast<uint32_t>(config_.task_stack_size), this,
|
||||
config_.task_priority, &task_handle_);
|
||||
if (created != pdPASS) {
|
||||
uart_driver_delete(kControlUart);
|
||||
task_handle_ = nullptr;
|
||||
ESP_LOGE(kTag, "failed to create 485 control task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "485 control bridge started baud=%lu", static_cast<unsigned long>(config_.baudrate));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::TaskEntry(void* arg) {
|
||||
static_cast<Gateway485ControlBridge*>(arg)->taskLoop();
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::taskLoop() {
|
||||
std::vector<uint8_t> read_buffer(std::max<size_t>(config_.rx_buffer_size, 64));
|
||||
std::vector<uint8_t> pending;
|
||||
pending.reserve(std::max<size_t>(config_.rx_buffer_size, 64));
|
||||
const TickType_t timeout = pdMS_TO_TICKS(config_.read_timeout_ms);
|
||||
|
||||
while (true) {
|
||||
const int read_len = uart_read_bytes(kControlUart, read_buffer.data(), read_buffer.size(), timeout);
|
||||
if (read_len > 0) {
|
||||
pending.insert(pending.end(), read_buffer.begin(), read_buffer.begin() + read_len);
|
||||
continue;
|
||||
}
|
||||
if (!pending.empty()) {
|
||||
handleBytes(pending.data(), pending.size());
|
||||
pending.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::handleBytes(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len < kCommandFrameMinLen) {
|
||||
return;
|
||||
}
|
||||
if (data[0] != 0x28 || data[1] != 0x01) {
|
||||
ESP_LOGD(kTag, "ignored non-gateway UART0 burst len=%u", static_cast<unsigned>(len));
|
||||
return;
|
||||
}
|
||||
|
||||
controller_.enqueueCommandFrame(std::vector<uint8_t>(data, data + len));
|
||||
}
|
||||
|
||||
void Gateway485ControlBridge::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
||||
if (!started_ || frame.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 notification 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 notification len=%u", static_cast<unsigned>(frame.size()));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
@@ -6,7 +6,7 @@ if(NOT CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(BACNET_STACK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../bacnet-stack")
|
||||
set(BACNET_STACK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../bacnet_stack")
|
||||
set(BACNET_SRC_ROOT "${BACNET_STACK_ROOT}/src")
|
||||
set(BACNET_ESP32_PORT "${BACNET_STACK_ROOT}/ports/esp32/src")
|
||||
idf_build_get_property(IDF_PATH IDF_PATH)
|
||||
@@ -49,11 +49,15 @@ set(BACNET_BASIC_SRCS
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/binding/address.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/npdu/h_npdu.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/npdu/s_router.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/ai.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/ao.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/av.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/bi.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/bo.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/bv.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/device.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/ms-input.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/mso.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/object/msv.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_apdu.c"
|
||||
"${BACNET_SRC_ROOT}/bacnet/basic/service/h_cov.c"
|
||||
@@ -88,6 +92,7 @@ set(BACNET_PORT_SRCS
|
||||
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"src/gateway_bacnet_bridge.cpp"
|
||||
"src/gateway_bacnet.cpp"
|
||||
"src/gateway_bacnet_stack_port.c"
|
||||
"src/bip_socket_lwip.cpp"
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -30,6 +31,9 @@ struct GatewayBacnetObjectBinding {
|
||||
BridgeObjectType object_type{BridgeObjectType::unknown};
|
||||
uint32_t object_instance{0};
|
||||
std::string property{"presentValue"};
|
||||
bool out_of_service{false};
|
||||
uint32_t reliability{0};
|
||||
bool readable{false};
|
||||
};
|
||||
|
||||
struct GatewayBacnetServerStatus {
|
||||
@@ -44,13 +48,19 @@ using GatewayBacnetWriteCallback =
|
||||
std::function<bool(BridgeObjectType object_type, uint32_t object_instance,
|
||||
const std::string& property, const DaliValue& value)>;
|
||||
|
||||
using GatewayBacnetReadCallback =
|
||||
std::function<std::optional<DaliValue>(BridgeObjectType object_type,
|
||||
uint32_t object_instance,
|
||||
const std::string& property)>;
|
||||
|
||||
class GatewayBacnetServer {
|
||||
public:
|
||||
static GatewayBacnetServer& instance();
|
||||
|
||||
esp_err_t registerChannel(uint8_t gateway_id, const GatewayBacnetServerConfig& config,
|
||||
std::vector<GatewayBacnetObjectBinding> bindings,
|
||||
GatewayBacnetWriteCallback write_callback);
|
||||
GatewayBacnetWriteCallback write_callback,
|
||||
GatewayBacnetReadCallback read_callback = nullptr);
|
||||
GatewayBacnetServerStatus status() const;
|
||||
bool configCompatible(const GatewayBacnetServerConfig& config) const;
|
||||
bool handleWrite(BridgeObjectType object_type, uint32_t object_instance,
|
||||
@@ -68,6 +78,7 @@ class GatewayBacnetServer {
|
||||
|
||||
esp_err_t startStackLocked(const GatewayBacnetServerConfig& config);
|
||||
esp_err_t rebuildObjectsLocked();
|
||||
void refreshPresentValues();
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "bridge.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
struct GatewayBacnetBridgeConfig {
|
||||
uint32_t deviceInstance{4194303};
|
||||
std::string localAddress;
|
||||
uint16_t udpPort{47808};
|
||||
};
|
||||
|
||||
struct GatewayBacnetModelBinding {
|
||||
std::string modelID;
|
||||
BridgeObjectType objectType{BridgeObjectType::unknown};
|
||||
int objectInstance{-1};
|
||||
std::string property;
|
||||
BridgeOperation operation{BridgeOperation::unknown};
|
||||
BridgeDaliTarget target;
|
||||
std::optional<int> bitIndex;
|
||||
};
|
||||
|
||||
class GatewayBacnetBridgeAdapter {
|
||||
public:
|
||||
explicit GatewayBacnetBridgeAdapter(DaliBridgeEngine& engine);
|
||||
|
||||
void setConfig(const GatewayBacnetBridgeConfig& config);
|
||||
const GatewayBacnetBridgeConfig& config() const;
|
||||
|
||||
DaliBridgeResult handlePropertyWrite(BridgeObjectType object_type,
|
||||
int object_instance,
|
||||
const std::string& property,
|
||||
const DaliValue& value) const;
|
||||
DaliBridgeResult readProperty(BridgeObjectType object_type,
|
||||
int object_instance,
|
||||
const std::string& property) const;
|
||||
std::optional<GatewayBacnetModelBinding> findObject(BridgeObjectType object_type,
|
||||
int object_instance,
|
||||
const std::string& property) const;
|
||||
std::vector<GatewayBacnetModelBinding> describeObjects() const;
|
||||
|
||||
private:
|
||||
DaliBridgeResult executeBinding(const GatewayBacnetModelBinding& binding,
|
||||
const std::string& sequence,
|
||||
const DaliValue* value) const;
|
||||
|
||||
DaliBridgeEngine& engine_;
|
||||
GatewayBacnetBridgeConfig config_;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -9,11 +9,15 @@ extern "C" {
|
||||
|
||||
typedef enum gateway_bacnet_object_kind {
|
||||
GW_BACNET_OBJECT_UNKNOWN = 0,
|
||||
GW_BACNET_OBJECT_ANALOG_INPUT,
|
||||
GW_BACNET_OBJECT_ANALOG_VALUE,
|
||||
GW_BACNET_OBJECT_ANALOG_OUTPUT,
|
||||
GW_BACNET_OBJECT_BINARY_INPUT,
|
||||
GW_BACNET_OBJECT_BINARY_VALUE,
|
||||
GW_BACNET_OBJECT_BINARY_OUTPUT,
|
||||
GW_BACNET_OBJECT_MULTI_STATE_INPUT,
|
||||
GW_BACNET_OBJECT_MULTI_STATE_VALUE,
|
||||
GW_BACNET_OBJECT_MULTI_STATE_OUTPUT,
|
||||
} gateway_bacnet_object_kind_t;
|
||||
|
||||
typedef enum gateway_bacnet_write_value_kind {
|
||||
@@ -48,7 +52,22 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
const char* object_name,
|
||||
const char* description);
|
||||
const char* description,
|
||||
bool out_of_service,
|
||||
uint32_t reliability);
|
||||
|
||||
bool gateway_bacnet_stack_set_object_state(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
bool out_of_service,
|
||||
uint32_t reliability);
|
||||
|
||||
bool gateway_bacnet_stack_set_present_value(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
const gateway_bacnet_write_value_t* value);
|
||||
|
||||
bool gateway_bacnet_stack_clear_objects(void);
|
||||
|
||||
void gateway_bacnet_stack_send_i_am(void);
|
||||
void gateway_bacnet_stack_poll(uint16_t elapsed_ms);
|
||||
|
||||
@@ -18,6 +18,8 @@ namespace {
|
||||
|
||||
constexpr const char* kTag = "gateway_bacnet";
|
||||
constexpr TickType_t kPollDelayTicks = pdMS_TO_TICKS(10);
|
||||
constexpr TickType_t kValueRefreshTicks = pdMS_TO_TICKS(2000);
|
||||
constexpr uint32_t kReliabilityCommunicationFailure = 12;
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
@@ -42,16 +44,32 @@ GatewayBacnetServer* g_server = nullptr;
|
||||
|
||||
gateway_bacnet_object_kind_t ToBacnetKind(BridgeObjectType type) {
|
||||
switch (type) {
|
||||
case BridgeObjectType::analogInput:
|
||||
return GW_BACNET_OBJECT_ANALOG_INPUT;
|
||||
case BridgeObjectType::analogValue:
|
||||
return GW_BACNET_OBJECT_ANALOG_VALUE;
|
||||
case BridgeObjectType::analogOutput:
|
||||
return GW_BACNET_OBJECT_ANALOG_OUTPUT;
|
||||
case BridgeObjectType::binaryInput:
|
||||
return GW_BACNET_OBJECT_BINARY_INPUT;
|
||||
case BridgeObjectType::binaryValue:
|
||||
return GW_BACNET_OBJECT_BINARY_VALUE;
|
||||
case BridgeObjectType::binaryOutput:
|
||||
return GW_BACNET_OBJECT_BINARY_OUTPUT;
|
||||
case BridgeObjectType::multiStateInput:
|
||||
return GW_BACNET_OBJECT_MULTI_STATE_INPUT;
|
||||
case BridgeObjectType::multiStateValue:
|
||||
return GW_BACNET_OBJECT_MULTI_STATE_VALUE;
|
||||
case BridgeObjectType::multiStateOutput:
|
||||
return GW_BACNET_OBJECT_MULTI_STATE_OUTPUT;
|
||||
case BridgeObjectType::holdingRegister:
|
||||
return GW_BACNET_OBJECT_ANALOG_VALUE;
|
||||
case BridgeObjectType::inputRegister:
|
||||
return GW_BACNET_OBJECT_ANALOG_INPUT;
|
||||
case BridgeObjectType::coil:
|
||||
return GW_BACNET_OBJECT_BINARY_OUTPUT;
|
||||
case BridgeObjectType::discreteInput:
|
||||
return GW_BACNET_OBJECT_BINARY_INPUT;
|
||||
default:
|
||||
return GW_BACNET_OBJECT_UNKNOWN;
|
||||
}
|
||||
@@ -59,16 +77,24 @@ gateway_bacnet_object_kind_t ToBacnetKind(BridgeObjectType type) {
|
||||
|
||||
BridgeObjectType FromBacnetKind(gateway_bacnet_object_kind_t kind) {
|
||||
switch (kind) {
|
||||
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||
return BridgeObjectType::analogInput;
|
||||
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||
return BridgeObjectType::analogValue;
|
||||
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
|
||||
return BridgeObjectType::analogOutput;
|
||||
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||
return BridgeObjectType::binaryInput;
|
||||
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||
return BridgeObjectType::binaryValue;
|
||||
case GW_BACNET_OBJECT_BINARY_OUTPUT:
|
||||
return BridgeObjectType::binaryOutput;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||
return BridgeObjectType::multiStateInput;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||
return BridgeObjectType::multiStateValue;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
|
||||
return BridgeObjectType::multiStateOutput;
|
||||
default:
|
||||
return BridgeObjectType::unknown;
|
||||
}
|
||||
@@ -101,6 +127,59 @@ DaliValue StackWriteValueToDali(const gateway_bacnet_write_value_t& value) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DaliValueToStackPresentValue(BridgeObjectType object_type, const DaliValue& value,
|
||||
gateway_bacnet_write_value_t* out) {
|
||||
if (out == nullptr) {
|
||||
return false;
|
||||
}
|
||||
switch (ToBacnetKind(object_type)) {
|
||||
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||
case GW_BACNET_OBJECT_ANALOG_OUTPUT: {
|
||||
const auto parsed = value.asDouble();
|
||||
if (!parsed.has_value()) {
|
||||
return false;
|
||||
}
|
||||
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_REAL,
|
||||
parsed.value(),
|
||||
false,
|
||||
0};
|
||||
return true;
|
||||
}
|
||||
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||
case GW_BACNET_OBJECT_BINARY_OUTPUT: {
|
||||
const auto parsed = value.asBool();
|
||||
if (!parsed.has_value()) {
|
||||
return false;
|
||||
}
|
||||
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_BOOLEAN,
|
||||
0.0,
|
||||
parsed.value(),
|
||||
0};
|
||||
return true;
|
||||
}
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT: {
|
||||
const auto parsed = value.asInt();
|
||||
if (!parsed.has_value()) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t unsigned_value = parsed.value() <= 0
|
||||
? 1
|
||||
: static_cast<uint32_t>(parsed.value());
|
||||
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_UNSIGNED,
|
||||
0.0,
|
||||
false,
|
||||
unsigned_value};
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void HandleStackWrite(gateway_bacnet_object_kind_t object_kind, uint32_t object_instance,
|
||||
const gateway_bacnet_write_value_t* value, void*) {
|
||||
if (g_server == nullptr || value == nullptr) {
|
||||
@@ -116,6 +195,7 @@ struct GatewayBacnetServer::ChannelRegistration {
|
||||
GatewayBacnetServerConfig config;
|
||||
std::vector<GatewayBacnetObjectBinding> bindings;
|
||||
GatewayBacnetWriteCallback write_callback;
|
||||
GatewayBacnetReadCallback read_callback;
|
||||
};
|
||||
|
||||
struct GatewayBacnetServer::RuntimeBinding {
|
||||
@@ -124,7 +204,11 @@ struct GatewayBacnetServer::RuntimeBinding {
|
||||
uint32_t object_instance{0};
|
||||
std::string model_id;
|
||||
std::string property{"presentValue"};
|
||||
bool out_of_service{false};
|
||||
uint32_t reliability{0};
|
||||
bool readable{false};
|
||||
GatewayBacnetWriteCallback write_callback;
|
||||
GatewayBacnetReadCallback read_callback;
|
||||
};
|
||||
|
||||
GatewayBacnetServer& GatewayBacnetServer::instance() {
|
||||
@@ -161,19 +245,17 @@ GatewayBacnetServerStatus GatewayBacnetServer::status() const {
|
||||
esp_err_t GatewayBacnetServer::registerChannel(
|
||||
uint8_t gateway_id, const GatewayBacnetServerConfig& config,
|
||||
std::vector<GatewayBacnetObjectBinding> bindings,
|
||||
GatewayBacnetWriteCallback write_callback) {
|
||||
GatewayBacnetWriteCallback write_callback,
|
||||
GatewayBacnetReadCallback read_callback) {
|
||||
if (write_callback == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
bindings.erase(std::remove_if(bindings.begin(), bindings.end(), [](const auto& binding) {
|
||||
return !IsSupportedObjectType(binding.object_type) ||
|
||||
binding.object_instance > kMaxBacnetInstance;
|
||||
binding.object_instance > kMaxBacnetInstance;
|
||||
}),
|
||||
bindings.end());
|
||||
if (bindings.empty()) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
LockGuard guard(lock_);
|
||||
if (started_ && !configCompatible(config)) {
|
||||
@@ -183,8 +265,11 @@ esp_err_t GatewayBacnetServer::registerChannel(
|
||||
auto channel = std::find_if(channels_.begin(), channels_.end(), [gateway_id](const auto& item) {
|
||||
return item.gateway_id == gateway_id;
|
||||
});
|
||||
if (bindings.empty() && !started_ && channel == channels_.end()) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
ChannelRegistration registration{gateway_id, config, std::move(bindings),
|
||||
std::move(write_callback)};
|
||||
std::move(write_callback), std::move(read_callback)};
|
||||
if (channel == channels_.end()) {
|
||||
channels_.push_back(std::move(registration));
|
||||
} else {
|
||||
@@ -240,6 +325,10 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
|
||||
runtime_bindings_.clear();
|
||||
std::set<std::pair<BridgeObjectType, uint32_t>> used_objects;
|
||||
|
||||
if (!gateway_bacnet_stack_clear_objects()) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
for (const auto& channel : channels_) {
|
||||
for (const auto& binding : channel.bindings) {
|
||||
const auto key = std::make_pair(binding.object_type, binding.object_instance);
|
||||
@@ -254,7 +343,9 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
|
||||
const std::string name = ObjectName(binding);
|
||||
if (!gateway_bacnet_stack_upsert_object(ToBacnetKind(binding.object_type),
|
||||
binding.object_instance, name.c_str(),
|
||||
binding.model_id.c_str())) {
|
||||
binding.model_id.c_str(),
|
||||
binding.out_of_service,
|
||||
binding.reliability)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
runtime_bindings_.push_back(RuntimeBinding{channel.gateway_id,
|
||||
@@ -263,7 +354,11 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
|
||||
binding.model_id,
|
||||
binding.property.empty() ? "presentValue"
|
||||
: binding.property,
|
||||
channel.write_callback});
|
||||
binding.out_of_service,
|
||||
binding.reliability,
|
||||
binding.readable,
|
||||
channel.write_callback,
|
||||
channel.read_callback});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,12 +399,47 @@ bool GatewayBacnetServer::handleWrite(BridgeObjectType object_type, uint32_t obj
|
||||
return ok;
|
||||
}
|
||||
|
||||
void GatewayBacnetServer::refreshPresentValues() {
|
||||
std::vector<RuntimeBinding> bindings;
|
||||
{
|
||||
LockGuard guard(lock_);
|
||||
bindings = runtime_bindings_;
|
||||
}
|
||||
|
||||
for (const auto& binding : bindings) {
|
||||
const auto object_kind = ToBacnetKind(binding.object_type);
|
||||
if (object_kind == GW_BACNET_OBJECT_UNKNOWN) {
|
||||
continue;
|
||||
}
|
||||
if (!binding.readable || binding.read_callback == nullptr) {
|
||||
LockGuard guard(lock_);
|
||||
gateway_bacnet_stack_set_object_state(object_kind, binding.object_instance,
|
||||
binding.out_of_service, binding.reliability);
|
||||
continue;
|
||||
}
|
||||
|
||||
gateway_bacnet_write_value_t stack_value = {};
|
||||
const auto value = binding.read_callback(binding.object_type, binding.object_instance,
|
||||
binding.property);
|
||||
const bool converted = value.has_value() &&
|
||||
DaliValueToStackPresentValue(binding.object_type, value.value(),
|
||||
&stack_value);
|
||||
LockGuard guard(lock_);
|
||||
const bool ok = converted && gateway_bacnet_stack_set_present_value(
|
||||
object_kind, binding.object_instance, &stack_value);
|
||||
gateway_bacnet_stack_set_object_state(
|
||||
object_kind, binding.object_instance, binding.out_of_service || !ok,
|
||||
ok ? binding.reliability : kReliabilityCommunicationFailure);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBacnetServer::TaskEntry(void* arg) {
|
||||
static_cast<GatewayBacnetServer*>(arg)->taskLoop();
|
||||
}
|
||||
|
||||
void GatewayBacnetServer::taskLoop() {
|
||||
TickType_t last_timer = xTaskGetTickCount();
|
||||
TickType_t last_refresh = last_timer;
|
||||
|
||||
while (true) {
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
@@ -319,9 +449,17 @@ void GatewayBacnetServer::taskLoop() {
|
||||
elapsed_ms = static_cast<uint16_t>(elapsed * portTICK_PERIOD_MS);
|
||||
last_timer = now;
|
||||
}
|
||||
bool refresh_due = false;
|
||||
{
|
||||
LockGuard guard(lock_);
|
||||
gateway_bacnet_stack_poll(elapsed_ms);
|
||||
if ((now - last_refresh) >= kValueRefreshTicks) {
|
||||
refresh_due = true;
|
||||
}
|
||||
}
|
||||
if (refresh_due) {
|
||||
refreshPresentValues();
|
||||
last_refresh = now;
|
||||
}
|
||||
vTaskDelay(kPollDelayTicks);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#include "gateway_bacnet_bridge.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
GatewayBacnetBridgeAdapter::GatewayBacnetBridgeAdapter(DaliBridgeEngine& engine)
|
||||
: engine_(engine) {}
|
||||
|
||||
void GatewayBacnetBridgeAdapter::setConfig(const GatewayBacnetBridgeConfig& config) {
|
||||
config_ = config;
|
||||
}
|
||||
|
||||
const GatewayBacnetBridgeConfig& GatewayBacnetBridgeAdapter::config() const { return config_; }
|
||||
|
||||
DaliBridgeResult GatewayBacnetBridgeAdapter::handlePropertyWrite(
|
||||
BridgeObjectType object_type, int object_instance, const std::string& property,
|
||||
const DaliValue& value) const {
|
||||
const auto binding = findObject(object_type, object_instance, property);
|
||||
if (!binding.has_value()) {
|
||||
DaliBridgeResult result;
|
||||
result.sequence = "bacnet-" + std::to_string(object_instance);
|
||||
result.error = "unmapped bacnet object";
|
||||
return result;
|
||||
}
|
||||
const std::string sequence = "bacnet-" + std::to_string(object_instance);
|
||||
return executeBinding(binding.value(), sequence, &value);
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayBacnetBridgeAdapter::readProperty(BridgeObjectType object_type,
|
||||
int object_instance,
|
||||
const std::string& property) const {
|
||||
const auto binding = findObject(object_type, object_instance, property);
|
||||
if (!binding.has_value()) {
|
||||
DaliBridgeResult result;
|
||||
result.sequence = "bacnet-read-" + std::to_string(object_instance);
|
||||
result.error = "unmapped bacnet object";
|
||||
return result;
|
||||
}
|
||||
const std::string sequence = "bacnet-read-" + std::to_string(object_instance);
|
||||
return executeBinding(binding.value(), sequence, nullptr);
|
||||
}
|
||||
|
||||
std::optional<GatewayBacnetModelBinding> GatewayBacnetBridgeAdapter::findObject(
|
||||
BridgeObjectType object_type, int object_instance, const std::string& property) const {
|
||||
for (const auto& model : engine_.listModels()) {
|
||||
if (model.protocol != BridgeProtocolKind::bacnet) {
|
||||
continue;
|
||||
}
|
||||
if (model.external.objectType != object_type) {
|
||||
continue;
|
||||
}
|
||||
if (model.external.objectInstance.value_or(-1) != object_instance) {
|
||||
continue;
|
||||
}
|
||||
if (!model.external.property.empty() && model.external.property != property) {
|
||||
continue;
|
||||
}
|
||||
const std::string binding_property = model.external.property.empty() ? property
|
||||
: model.external.property;
|
||||
return GatewayBacnetModelBinding{model.id,
|
||||
object_type,
|
||||
object_instance,
|
||||
binding_property,
|
||||
model.operation,
|
||||
model.dali,
|
||||
model.external.bitIndex};
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<GatewayBacnetModelBinding> GatewayBacnetBridgeAdapter::describeObjects() const {
|
||||
std::vector<GatewayBacnetModelBinding> bindings;
|
||||
for (const auto& model : engine_.listModels()) {
|
||||
if (model.protocol != BridgeProtocolKind::bacnet || !model.external.objectInstance.has_value()) {
|
||||
continue;
|
||||
}
|
||||
bindings.push_back(GatewayBacnetModelBinding{model.id,
|
||||
model.external.objectType,
|
||||
model.external.objectInstance.value(),
|
||||
model.external.property,
|
||||
model.operation,
|
||||
model.dali,
|
||||
model.external.bitIndex});
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayBacnetBridgeAdapter::executeBinding(
|
||||
const GatewayBacnetModelBinding& binding, const std::string& sequence,
|
||||
const DaliValue* value) const {
|
||||
DaliBridgeRequest request;
|
||||
request.sequence = sequence;
|
||||
request.modelID = binding.modelID;
|
||||
if (value != nullptr) {
|
||||
request.value = *value;
|
||||
}
|
||||
return engine_.execute(request);
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
@@ -5,11 +5,15 @@
|
||||
|
||||
#include "bacnet/apdu.h"
|
||||
#include "bacnet/basic/binding/address.h"
|
||||
#include "bacnet/basic/object/ai.h"
|
||||
#include "bacnet/basic/object/ao.h"
|
||||
#include "bacnet/basic/object/av.h"
|
||||
#include "bacnet/basic/object/bi.h"
|
||||
#include "bacnet/basic/object/bo.h"
|
||||
#include "bacnet/basic/object/bv.h"
|
||||
#include "bacnet/basic/object/device.h"
|
||||
#include "bacnet/basic/object/ms-input.h"
|
||||
#include "bacnet/basic/object/mso.h"
|
||||
#include "bacnet/basic/object/msv.h"
|
||||
#include "bacnet/basic/service/h_apdu.h"
|
||||
#include "bacnet/basic/services.h"
|
||||
@@ -39,6 +43,159 @@ static const char Multistate_Value_States[] =
|
||||
"State 15\0"
|
||||
"State 16\0";
|
||||
|
||||
static bool clear_analog_value_objects(void)
|
||||
{
|
||||
unsigned count = Analog_Value_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Analog_Value_Delete(Analog_Value_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_analog_output_objects(void)
|
||||
{
|
||||
unsigned count = Analog_Output_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Analog_Output_Delete(Analog_Output_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_binary_value_objects(void)
|
||||
{
|
||||
unsigned count = Binary_Value_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Binary_Value_Delete(Binary_Value_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_binary_output_objects(void)
|
||||
{
|
||||
unsigned count = Binary_Output_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Binary_Output_Delete(Binary_Output_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_multistate_value_objects(void)
|
||||
{
|
||||
unsigned count = Multistate_Value_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Multistate_Value_Delete(Multistate_Value_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_analog_input_objects(void)
|
||||
{
|
||||
unsigned count = Analog_Input_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Analog_Input_Delete(Analog_Input_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_binary_input_objects(void)
|
||||
{
|
||||
unsigned count = Binary_Input_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Binary_Input_Delete(Binary_Input_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_multistate_input_objects(void)
|
||||
{
|
||||
unsigned count = Multistate_Input_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Multistate_Input_Delete(Multistate_Input_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool clear_multistate_output_objects(void)
|
||||
{
|
||||
unsigned count = Multistate_Output_Count();
|
||||
while (count > 0) {
|
||||
count--;
|
||||
Multistate_Output_Delete(Multistate_Output_Index_To_Instance(count));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_analog_value_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Analog_Value_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Analog_Value_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_analog_output_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Analog_Output_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Analog_Output_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_binary_value_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Binary_Value_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Binary_Value_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_binary_output_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Binary_Output_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Binary_Output_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_multistate_value_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Multistate_Value_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Multistate_Value_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_analog_input_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Analog_Input_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Analog_Input_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_binary_input_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Binary_Input_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Binary_Input_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_multistate_input_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Multistate_Input_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Multistate_Input_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void set_multistate_output_state(
|
||||
uint32_t object_instance, bool out_of_service, BACNET_RELIABILITY reliability)
|
||||
{
|
||||
Multistate_Output_Out_Of_Service_Set(object_instance, out_of_service);
|
||||
Multistate_Output_Reliability_Set(object_instance, reliability);
|
||||
}
|
||||
|
||||
static void notify_write_real(
|
||||
gateway_bacnet_object_kind_t object_kind, uint32_t object_instance, double value)
|
||||
{
|
||||
@@ -113,6 +270,25 @@ static void multistate_value_write(uint32_t object_instance, uint32_t old_value,
|
||||
notify_write_unsigned(GW_BACNET_OBJECT_MULTI_STATE_VALUE, object_instance, value);
|
||||
}
|
||||
|
||||
static void binary_input_write(
|
||||
uint32_t object_instance, BACNET_BINARY_PV old_value, BACNET_BINARY_PV value)
|
||||
{
|
||||
(void)old_value;
|
||||
notify_write_boolean(GW_BACNET_OBJECT_BINARY_INPUT, object_instance, value == BINARY_ACTIVE);
|
||||
}
|
||||
|
||||
static void multistate_input_write(uint32_t object_instance, uint32_t old_value, uint32_t value)
|
||||
{
|
||||
(void)old_value;
|
||||
notify_write_unsigned(GW_BACNET_OBJECT_MULTI_STATE_INPUT, object_instance, value);
|
||||
}
|
||||
|
||||
static void multistate_output_write(uint32_t object_instance, uint32_t old_value, uint32_t value)
|
||||
{
|
||||
(void)old_value;
|
||||
notify_write_unsigned(GW_BACNET_OBJECT_MULTI_STATE_OUTPUT, object_instance, value);
|
||||
}
|
||||
|
||||
static object_functions_t Object_Table[] = {
|
||||
{ OBJECT_DEVICE, NULL, Device_Count, Device_Index_To_Instance,
|
||||
Device_Valid_Object_Instance_Number, Device_Object_Name, Device_Read_Property_Local,
|
||||
@@ -149,6 +325,32 @@ static object_functions_t Object_Table[] = {
|
||||
Multistate_Value_Encode_Value_List, Multistate_Value_Change_Of_Value,
|
||||
Multistate_Value_Change_Of_Value_Clear, NULL, NULL, NULL, Multistate_Value_Create,
|
||||
Multistate_Value_Delete, NULL, Multistate_Value_Writable_Property_List },
|
||||
{ OBJECT_ANALOG_INPUT, Analog_Input_Init, Analog_Input_Count,
|
||||
Analog_Input_Index_To_Instance, Analog_Input_Valid_Instance, Analog_Input_Object_Name,
|
||||
Analog_Input_Read_Property, Analog_Input_Write_Property, Analog_Input_Property_Lists,
|
||||
NULL, NULL, Analog_Input_Encode_Value_List, Analog_Input_Change_Of_Value,
|
||||
Analog_Input_Change_Of_Value_Clear, NULL, NULL, NULL, Analog_Input_Create,
|
||||
Analog_Input_Delete, NULL, Analog_Input_Writable_Property_List },
|
||||
{ OBJECT_BINARY_INPUT, Binary_Input_Init, Binary_Input_Count,
|
||||
Binary_Input_Index_To_Instance, Binary_Input_Valid_Instance, Binary_Input_Object_Name,
|
||||
Binary_Input_Read_Property, Binary_Input_Write_Property, Binary_Input_Property_Lists,
|
||||
NULL, NULL, Binary_Input_Encode_Value_List, Binary_Input_Change_Of_Value,
|
||||
Binary_Input_Change_Of_Value_Clear, NULL, NULL, NULL, Binary_Input_Create,
|
||||
Binary_Input_Delete, NULL, Binary_Input_Writable_Property_List },
|
||||
{ OBJECT_MULTI_STATE_INPUT, Multistate_Input_Init, Multistate_Input_Count,
|
||||
Multistate_Input_Index_To_Instance, Multistate_Input_Valid_Instance,
|
||||
Multistate_Input_Object_Name, Multistate_Input_Read_Property,
|
||||
Multistate_Input_Write_Property, Multistate_Input_Property_Lists, NULL, NULL,
|
||||
Multistate_Input_Encode_Value_List, Multistate_Input_Change_Of_Value,
|
||||
Multistate_Input_Change_Of_Value_Clear, NULL, NULL, NULL, Multistate_Input_Create,
|
||||
Multistate_Input_Delete, NULL, Multistate_Input_Writable_Property_List },
|
||||
{ OBJECT_MULTI_STATE_OUTPUT, Multistate_Output_Init, Multistate_Output_Count,
|
||||
Multistate_Output_Index_To_Instance, Multistate_Output_Valid_Instance,
|
||||
Multistate_Output_Object_Name, Multistate_Output_Read_Property,
|
||||
Multistate_Output_Write_Property, Multistate_Output_Property_Lists, NULL, NULL,
|
||||
Multistate_Output_Encode_Value_List, Multistate_Output_Change_Of_Value,
|
||||
Multistate_Output_Change_Of_Value_Clear, NULL, NULL, NULL, Multistate_Output_Create,
|
||||
Multistate_Output_Delete, NULL, Multistate_Output_Writable_Property_List },
|
||||
{ MAX_BACNET_OBJECT_TYPE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL },
|
||||
};
|
||||
@@ -184,6 +386,9 @@ bool gateway_bacnet_stack_start(
|
||||
Binary_Value_Write_Present_Value_Callback_Set(binary_value_write);
|
||||
Binary_Output_Write_Present_Value_Callback_Set(binary_output_write);
|
||||
Multistate_Value_Write_Present_Value_Callback_Set(multistate_value_write);
|
||||
Binary_Input_Write_Present_Value_Callback_Set(binary_input_write);
|
||||
Multistate_Input_Write_Present_Value_Callback_Set(multistate_input_write);
|
||||
Multistate_Output_Write_Present_Value_Callback_Set(multistate_output_write);
|
||||
|
||||
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
|
||||
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
|
||||
@@ -209,7 +414,9 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
const char* object_name,
|
||||
const char* description)
|
||||
const char* description,
|
||||
bool out_of_service,
|
||||
uint32_t reliability)
|
||||
{
|
||||
if (!object_name || object_name[0] == '\0') {
|
||||
object_name = "DALI BACnet Object";
|
||||
@@ -218,6 +425,8 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
description = "";
|
||||
}
|
||||
|
||||
BACNET_RELIABILITY object_reliability = (BACNET_RELIABILITY)reliability;
|
||||
|
||||
switch (object_kind) {
|
||||
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||
if (!Analog_Value_Valid_Instance(object_instance)) {
|
||||
@@ -227,6 +436,7 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
Analog_Value_Description_Set(object_instance, description);
|
||||
Analog_Value_Units_Set(object_instance, UNITS_PERCENT);
|
||||
Analog_Value_Present_Value_Set(object_instance, 0.0f, BACNET_NO_PRIORITY);
|
||||
set_analog_value_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
|
||||
if (!Analog_Output_Valid_Instance(object_instance)) {
|
||||
@@ -236,6 +446,7 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
Analog_Output_Description_Set(object_instance, description);
|
||||
Analog_Output_Units_Set(object_instance, UNITS_PERCENT);
|
||||
Analog_Output_Present_Value_Set(object_instance, 0.0f, BACNET_MAX_PRIORITY);
|
||||
set_analog_output_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||
if (!Binary_Value_Valid_Instance(object_instance)) {
|
||||
@@ -245,6 +456,7 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
Binary_Value_Description_Set(object_instance, description);
|
||||
Binary_Value_Write_Enable(object_instance);
|
||||
Binary_Value_Present_Value_Set(object_instance, BINARY_INACTIVE);
|
||||
set_binary_value_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_BINARY_OUTPUT:
|
||||
if (!Binary_Output_Valid_Instance(object_instance)) {
|
||||
@@ -253,6 +465,7 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
Binary_Output_Name_Set(object_instance, object_name);
|
||||
Binary_Output_Description_Set(object_instance, description);
|
||||
Binary_Output_Present_Value_Set(object_instance, BINARY_INACTIVE, BACNET_MAX_PRIORITY);
|
||||
set_binary_output_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||
if (!Multistate_Value_Valid_Instance(object_instance)) {
|
||||
@@ -263,12 +476,150 @@ bool gateway_bacnet_stack_upsert_object(
|
||||
Multistate_Value_State_Text_List_Set(object_instance, Multistate_Value_States);
|
||||
Multistate_Value_Write_Enable(object_instance);
|
||||
Multistate_Value_Present_Value_Set(object_instance, 1);
|
||||
set_multistate_value_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||
if (!Analog_Input_Valid_Instance(object_instance)) {
|
||||
Analog_Input_Create(object_instance);
|
||||
}
|
||||
Analog_Input_Name_Set(object_instance, object_name);
|
||||
Analog_Input_Description_Set(object_instance, description);
|
||||
Analog_Input_Units_Set(object_instance, UNITS_PERCENT);
|
||||
Analog_Input_Present_Value_Set(object_instance, 0.0f);
|
||||
set_analog_input_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||
if (!Binary_Input_Valid_Instance(object_instance)) {
|
||||
Binary_Input_Create(object_instance);
|
||||
}
|
||||
Binary_Input_Name_Set(object_instance, object_name);
|
||||
Binary_Input_Description_Set(object_instance, description);
|
||||
Binary_Input_Present_Value_Set(object_instance, BINARY_INACTIVE);
|
||||
set_binary_input_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||
if (!Multistate_Input_Valid_Instance(object_instance)) {
|
||||
Multistate_Input_Create(object_instance);
|
||||
}
|
||||
Multistate_Input_Name_Set(object_instance, object_name);
|
||||
Multistate_Input_Description_Set(object_instance, description);
|
||||
Multistate_Input_State_Text_List_Set(object_instance, Multistate_Value_States);
|
||||
Multistate_Input_Present_Value_Set(object_instance, 1);
|
||||
set_multistate_input_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
|
||||
if (!Multistate_Output_Valid_Instance(object_instance)) {
|
||||
Multistate_Output_Create(object_instance);
|
||||
}
|
||||
Multistate_Output_Name_Set(object_instance, object_name);
|
||||
Multistate_Output_Description_Set(object_instance, description);
|
||||
Multistate_Output_State_Text_List_Set(object_instance, Multistate_Value_States);
|
||||
Multistate_Output_Present_Value_Set(object_instance, 1, BACNET_MAX_PRIORITY);
|
||||
set_multistate_output_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool gateway_bacnet_stack_set_object_state(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
bool out_of_service,
|
||||
uint32_t reliability)
|
||||
{
|
||||
BACNET_RELIABILITY object_reliability = (BACNET_RELIABILITY)reliability;
|
||||
|
||||
switch (object_kind) {
|
||||
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||
set_analog_value_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
|
||||
set_analog_output_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||
set_binary_value_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_BINARY_OUTPUT:
|
||||
set_binary_output_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||
set_multistate_value_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||
set_analog_input_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||
set_binary_input_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||
set_multistate_input_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
|
||||
set_multistate_output_state(object_instance, out_of_service, object_reliability);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool gateway_bacnet_stack_set_present_value(
|
||||
gateway_bacnet_object_kind_t object_kind,
|
||||
uint32_t object_instance,
|
||||
const gateway_bacnet_write_value_t* value)
|
||||
{
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (object_kind) {
|
||||
case GW_BACNET_OBJECT_ANALOG_VALUE:
|
||||
return Analog_Value_Present_Value_Set(
|
||||
object_instance, (float)value->real_value, BACNET_NO_PRIORITY);
|
||||
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
|
||||
return Analog_Output_Present_Value_Set(
|
||||
object_instance, (float)value->real_value, BACNET_MAX_PRIORITY);
|
||||
case GW_BACNET_OBJECT_ANALOG_INPUT:
|
||||
Analog_Input_Present_Value_Set(object_instance, (float)value->real_value);
|
||||
return true;
|
||||
case GW_BACNET_OBJECT_BINARY_VALUE:
|
||||
return Binary_Value_Present_Value_Set(
|
||||
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE);
|
||||
case GW_BACNET_OBJECT_BINARY_OUTPUT:
|
||||
return Binary_Output_Present_Value_Set(
|
||||
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE,
|
||||
BACNET_MAX_PRIORITY);
|
||||
case GW_BACNET_OBJECT_BINARY_INPUT:
|
||||
return Binary_Input_Present_Value_Set(
|
||||
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE);
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
|
||||
return Multistate_Value_Present_Value_Set(
|
||||
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value);
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
|
||||
return Multistate_Output_Present_Value_Set(
|
||||
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value,
|
||||
BACNET_MAX_PRIORITY);
|
||||
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
|
||||
return Multistate_Input_Present_Value_Set(
|
||||
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool gateway_bacnet_stack_clear_objects(void)
|
||||
{
|
||||
return clear_analog_value_objects() &&
|
||||
clear_analog_output_objects() &&
|
||||
clear_analog_input_objects() &&
|
||||
clear_binary_value_objects() &&
|
||||
clear_binary_output_objects() &&
|
||||
clear_binary_input_objects() &&
|
||||
clear_multistate_value_objects() &&
|
||||
clear_multistate_input_objects() &&
|
||||
clear_multistate_output_objects();
|
||||
}
|
||||
|
||||
void gateway_bacnet_stack_send_i_am(void)
|
||||
{
|
||||
Send_I_Am(&Handler_Transmit_Buffer[0]);
|
||||
|
||||
@@ -141,6 +141,13 @@ void RegisterGatt(struct ble_gatt_register_ctxt* ctxt, void* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> LegacyRawPayload(const std::vector<uint8_t>& data) {
|
||||
if (data.size() == 1) {
|
||||
return {0xBE, data[0]};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const struct ble_gatt_svc_def kGattServices[] = {
|
||||
{
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
@@ -377,7 +384,7 @@ void GatewayBleBridge::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
if (!enabled_ || conn_handle_ == kInvalidConnectionHandle || frame.data.empty()) {
|
||||
return;
|
||||
}
|
||||
notifyCharacteristic(frame.channel_index, frame.data);
|
||||
notifyCharacteristic(frame.channel_index, LegacyRawPayload(frame.data));
|
||||
}
|
||||
|
||||
void GatewayBleBridge::handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload) {
|
||||
|
||||
@@ -2,10 +2,15 @@ set(GATEWAY_BRIDGE_REQUIRES
|
||||
dali_domain
|
||||
dali_cpp
|
||||
espressif__cjson
|
||||
esp_driver_uart
|
||||
freertos
|
||||
gateway_cache
|
||||
gateway_knx
|
||||
gateway_modbus
|
||||
log
|
||||
lwip
|
||||
nvs_flash
|
||||
openknx_idf
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "gateway_knx.hpp"
|
||||
#include "gateway_modbus.hpp"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class DaliDomainService;
|
||||
struct DaliRawFrame;
|
||||
class GatewayCache;
|
||||
|
||||
struct GatewayBridgeServiceConfig {
|
||||
bool bridge_enabled{true};
|
||||
@@ -19,12 +26,21 @@ struct GatewayBridgeServiceConfig {
|
||||
bool modbus_startup_enabled{false};
|
||||
bool bacnet_enabled{false};
|
||||
bool bacnet_startup_enabled{false};
|
||||
bool knx_enabled{false};
|
||||
bool knx_startup_enabled{false};
|
||||
bool cloud_enabled{true};
|
||||
bool cloud_startup_enabled{false};
|
||||
uint32_t modbus_task_stack_size{6144};
|
||||
UBaseType_t modbus_task_priority{4};
|
||||
std::optional<GatewayModbusConfig> default_modbus_config;
|
||||
bool allow_modbus_uart0{false};
|
||||
bool allow_knx_uart0{false};
|
||||
std::vector<int> reserved_uart_ports;
|
||||
uint32_t bacnet_task_stack_size{8192};
|
||||
UBaseType_t bacnet_task_priority{5};
|
||||
uint32_t knx_task_stack_size{12288};
|
||||
UBaseType_t knx_task_priority{5};
|
||||
std::optional<GatewayKnxConfig> default_knx_config;
|
||||
};
|
||||
|
||||
struct GatewayBridgeHttpResponse {
|
||||
@@ -35,13 +51,14 @@ struct GatewayBridgeHttpResponse {
|
||||
class GatewayBridgeService {
|
||||
public:
|
||||
GatewayBridgeService(DaliDomainService& dali_domain,
|
||||
GatewayCache& cache,
|
||||
GatewayBridgeServiceConfig config = {});
|
||||
~GatewayBridgeService();
|
||||
|
||||
esp_err_t start();
|
||||
|
||||
GatewayBridgeHttpResponse handleGet(const std::string& action, int gateway_id = -1,
|
||||
const std::string& query = {}) const;
|
||||
const std::string& query = {});
|
||||
GatewayBridgeHttpResponse handlePost(const std::string& action, int gateway_id,
|
||||
const std::string& body);
|
||||
|
||||
@@ -50,10 +67,25 @@ class GatewayBridgeService {
|
||||
|
||||
ChannelRuntime* findRuntime(uint8_t gateway_id);
|
||||
const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
|
||||
ChannelRuntime* selectKnxEndpointRuntime();
|
||||
bool isKnxEndpointRuntime(const ChannelRuntime* runtime) const;
|
||||
esp_err_t startKnxEndpoint(ChannelRuntime* requested_runtime,
|
||||
std::set<int>* used_uarts = nullptr);
|
||||
esp_err_t stopKnxEndpoint(ChannelRuntime* requested_runtime);
|
||||
DaliBridgeResult routeKnxCemiFrame(const uint8_t* data, size_t len);
|
||||
DaliBridgeResult routeKnxGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
size_t len);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
void collectUsedRuntimeResources(uint8_t except_gateway_id,
|
||||
std::set<uint16_t>* modbus_tcp_ports,
|
||||
std::set<uint16_t>* knx_udp_ports,
|
||||
std::set<int>* serial_uarts) const;
|
||||
|
||||
DaliDomainService& dali_domain_;
|
||||
GatewayCache& cache_;
|
||||
GatewayBridgeServiceConfig config_;
|
||||
std::vector<std::unique_ptr<ChannelRuntime>> runtimes_;
|
||||
ChannelRuntime* knx_endpoint_runtime_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "esp_err.h"
|
||||
@@ -20,18 +21,71 @@ enum class GatewayCachePriorityMode : uint8_t {
|
||||
|
||||
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{5000};
|
||||
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,
|
||||
};
|
||||
|
||||
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};
|
||||
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 {
|
||||
public:
|
||||
struct SceneEntry {
|
||||
@@ -80,20 +134,73 @@ class GatewayCache {
|
||||
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);
|
||||
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 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);
|
||||
|
||||
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();
|
||||
|
||||
bool openStorageLocked();
|
||||
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 commitStorageLocked();
|
||||
bool shouldTrackUpdateFlagsLocked() const;
|
||||
uint32_t nextDaliRuntimeRevisionLocked();
|
||||
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);
|
||||
@@ -109,7 +216,12 @@ class GatewayCache {
|
||||
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<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};
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -10,6 +12,7 @@
|
||||
#include "gateway_cache.hpp"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
namespace gateway {
|
||||
@@ -83,9 +86,30 @@ class GatewayController {
|
||||
GatewayControllerSnapshot snapshot();
|
||||
|
||||
private:
|
||||
struct ReconciliationJob {
|
||||
enum class Phase : uint8_t {
|
||||
kReloadFlags = 0,
|
||||
kGroups = 1,
|
||||
kScenes = 2,
|
||||
kSettings = 3,
|
||||
};
|
||||
|
||||
GatewayCacheChannelFlags flags{};
|
||||
Phase phase{Phase::kReloadFlags};
|
||||
uint8_t short_address{0};
|
||||
uint8_t scene_id{0};
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void dispatchCommand(const std::vector<uint8_t>& command);
|
||||
void scheduleReconciliation(uint8_t gateway_id);
|
||||
bool hasPendingReconciliation() const;
|
||||
bool runMaintenanceStep();
|
||||
bool runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job);
|
||||
void reconcileGroupStep(uint8_t gateway_id, uint8_t short_address);
|
||||
void reconcileSceneStep(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id);
|
||||
void reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address);
|
||||
|
||||
bool hasGateway(uint8_t gateway_id) const;
|
||||
std::vector<uint8_t> gatewayIds() const;
|
||||
@@ -95,12 +119,19 @@ class GatewayController {
|
||||
void publishFrame(const std::vector<uint8_t>& frame);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
|
||||
bool sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
|
||||
bool sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
|
||||
bool setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level);
|
||||
bool offAndMirror(uint8_t gateway_id, int dec_address);
|
||||
bool onAndMirror(uint8_t gateway_id, int dec_address);
|
||||
uint8_t resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr);
|
||||
static uint8_t normalizeGroupTargetType(uint8_t target_type);
|
||||
static uint8_t normalizeGroupTargetValue(uint8_t target_type, uint8_t target_value);
|
||||
static uint8_t internalGroupRawTargetAddress(uint8_t target_type, uint8_t target_value,
|
||||
uint8_t raw_addr);
|
||||
static int internalGroupDecTargetAddress(uint8_t target_type, uint8_t target_value);
|
||||
static uint8_t rawArcAddressFromDec(int dec_address);
|
||||
static uint8_t rawCommandAddressFromDec(int dec_address);
|
||||
static int shortAddressFromRaw(uint8_t raw_addr);
|
||||
static int reverseInRange(int value, int min_value, int max_value);
|
||||
|
||||
@@ -131,10 +162,13 @@ class GatewayController {
|
||||
GatewayCache& cache_;
|
||||
GatewayControllerConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
SemaphoreHandle_t maintenance_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<uint8_t, ReconciliationJob> reconciliation_jobs_;
|
||||
std::atomic<int> maintenance_activity_gateway_{-1};
|
||||
bool setup_mode_{false};
|
||||
bool wireless_setup_mode_{false};
|
||||
bool ble_enabled_{false};
|
||||
|
||||
@@ -16,6 +16,33 @@ namespace {
|
||||
|
||||
constexpr const char* kTag = "gateway_controller";
|
||||
constexpr size_t kMaxNameBytes = 32;
|
||||
constexpr uint8_t kDaliShortAddressCount = 64;
|
||||
constexpr uint8_t kDaliSceneCount = 16;
|
||||
constexpr uint8_t kDaliCmdOff = 0x00;
|
||||
constexpr uint8_t kDaliCmdRecallMax = 0x05;
|
||||
constexpr TickType_t kMaintenancePollTicks = pdMS_TO_TICKS(20);
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
|
||||
if (lock_ != nullptr) {
|
||||
xSemaphoreTake(lock_, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
~LockGuard() {
|
||||
if (lock_ != nullptr) {
|
||||
xSemaphoreGive(lock_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
SemaphoreHandle_t lock_;
|
||||
};
|
||||
|
||||
bool AnyFlagSet(const GatewayCacheChannelFlags& flags) {
|
||||
return flags.need_update_group || flags.need_update_scene || flags.need_update_settings;
|
||||
}
|
||||
|
||||
std::string NormalizeName(std::string_view name) {
|
||||
std::string normalized(name);
|
||||
@@ -71,9 +98,18 @@ const char* PhyKindToString(DaliPhyKind phy_kind) {
|
||||
|
||||
GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain,
|
||||
GatewayCache& cache, GatewayControllerConfig config)
|
||||
: runtime_(runtime), dali_domain_(dali_domain), cache_(cache), config_(config) {}
|
||||
: runtime_(runtime),
|
||||
dali_domain_(dali_domain),
|
||||
cache_(cache),
|
||||
config_(config),
|
||||
maintenance_lock_(xSemaphoreCreateMutex()) {}
|
||||
|
||||
GatewayController::~GatewayController() = default;
|
||||
GatewayController::~GatewayController() {
|
||||
if (maintenance_lock_ != nullptr) {
|
||||
vSemaphoreDelete(maintenance_lock_);
|
||||
maintenance_lock_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t GatewayController::start() {
|
||||
const auto device_info = runtime_.deviceInfo();
|
||||
@@ -112,7 +148,7 @@ bool GatewayController::enqueueCommandFrame(const std::vector<uint8_t>& frame) {
|
||||
ESP_LOGW(kTag, "dropped invalid command frame len=%u", static_cast<unsigned>(frame.size()));
|
||||
return false;
|
||||
}
|
||||
if (!runtime_.enqueueCommand(frame)) {
|
||||
if (!runtime_.enqueueCommand(frame, GatewayRuntime::classifyCommandPriority(frame))) {
|
||||
if (runtime_.lastEnqueueDropReason() != GatewayRuntime::CommandDropReason::kDuplicate) {
|
||||
ESP_LOGW(kTag, "dropped command frame reason=%d",
|
||||
static_cast<int>(runtime_.lastEnqueueDropReason()));
|
||||
@@ -210,15 +246,223 @@ void GatewayController::TaskEntry(void* arg) {
|
||||
|
||||
void GatewayController::taskLoop() {
|
||||
while (true) {
|
||||
bool drained = false;
|
||||
while (auto command = runtime_.popNextCommand()) {
|
||||
drained = true;
|
||||
bool worked = false;
|
||||
if (auto command = runtime_.popNextCommand()) {
|
||||
worked = true;
|
||||
dispatchCommand(*command);
|
||||
runtime_.completeCurrentCommand();
|
||||
}
|
||||
if (!drained) {
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
if (!worked) {
|
||||
worked = runMaintenanceStep();
|
||||
}
|
||||
if (!worked) {
|
||||
ulTaskNotifyTake(pdTRUE, hasPendingReconciliation() ? kMaintenancePollTicks : portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::scheduleReconciliation(uint8_t gateway_id) {
|
||||
if (!cache_.reconciliationEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto flags = cache_.pendingChannelFlags(gateway_id);
|
||||
if (cache_.fullStateMirrorEnabled() && AnyFlagSet(flags)) {
|
||||
flags = {true, true, true};
|
||||
}
|
||||
if (!AnyFlagSet(flags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
reconciliation_jobs_.try_emplace(gateway_id);
|
||||
}
|
||||
|
||||
if (task_handle_ != nullptr) {
|
||||
xTaskNotifyGive(task_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayController::hasPendingReconciliation() const {
|
||||
LockGuard guard(maintenance_lock_);
|
||||
return !reconciliation_jobs_.empty();
|
||||
}
|
||||
|
||||
bool GatewayController::runMaintenanceStep() {
|
||||
if (!cache_.reconciliationEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t gateway_id = 0;
|
||||
ReconciliationJob job;
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
if (reconciliation_jobs_.empty()) {
|
||||
return false;
|
||||
}
|
||||
const auto it = reconciliation_jobs_.begin();
|
||||
gateway_id = it->first;
|
||||
job = it->second;
|
||||
}
|
||||
|
||||
if (runtime_.shouldYieldMaintenance(gateway_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool keep_job = runReconciliationStep(gateway_id, job);
|
||||
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
auto it = reconciliation_jobs_.find(gateway_id);
|
||||
if (it == reconciliation_jobs_.end()) {
|
||||
return true;
|
||||
}
|
||||
if (keep_job) {
|
||||
it->second = job;
|
||||
} else {
|
||||
reconciliation_jobs_.erase(it);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayController::runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job) {
|
||||
if (job.phase == ReconciliationJob::Phase::kReloadFlags) {
|
||||
job.flags = cache_.pendingChannelFlags(gateway_id);
|
||||
if (cache_.fullStateMirrorEnabled() && AnyFlagSet(job.flags)) {
|
||||
job.flags = {true, true, true};
|
||||
}
|
||||
if (!AnyFlagSet(job.flags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
job.short_address = 0;
|
||||
job.scene_id = 0;
|
||||
if (job.flags.need_update_group) {
|
||||
job.phase = ReconciliationJob::Phase::kGroups;
|
||||
} else if (job.flags.need_update_scene) {
|
||||
job.phase = ReconciliationJob::Phase::kScenes;
|
||||
} else {
|
||||
job.phase = ReconciliationJob::Phase::kSettings;
|
||||
}
|
||||
}
|
||||
|
||||
switch (job.phase) {
|
||||
case ReconciliationJob::Phase::kGroups:
|
||||
reconcileGroupStep(gateway_id, job.short_address++);
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
if (job.flags.need_update_scene) {
|
||||
job.phase = ReconciliationJob::Phase::kScenes;
|
||||
} else if (job.flags.need_update_settings) {
|
||||
job.phase = ReconciliationJob::Phase::kSettings;
|
||||
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
|
||||
job.phase = ReconciliationJob::Phase::kReloadFlags;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case ReconciliationJob::Phase::kScenes:
|
||||
reconcileSceneStep(gateway_id, job.short_address, job.scene_id);
|
||||
++job.scene_id;
|
||||
if (job.scene_id >= kDaliSceneCount) {
|
||||
job.scene_id = 0;
|
||||
++job.short_address;
|
||||
}
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
if (job.flags.need_update_settings) {
|
||||
job.phase = ReconciliationJob::Phase::kSettings;
|
||||
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
|
||||
job.phase = ReconciliationJob::Phase::kReloadFlags;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case ReconciliationJob::Phase::kSettings:
|
||||
reconcileSettingsStep(gateway_id, job.short_address++);
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
|
||||
job.phase = ReconciliationJob::Phase::kReloadFlags;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case ReconciliationJob::Phase::kReloadFlags:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_address) {
|
||||
const auto policy = cache_.priorityMode();
|
||||
const auto state = cache_.daliAddressState(gateway_id, short_address);
|
||||
|
||||
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst && state.group_mask_known) {
|
||||
maintenance_activity_gateway_.store(gateway_id);
|
||||
const bool applied = dali_domain_.applyGroupMask(gateway_id, short_address, state.group_mask);
|
||||
maintenance_activity_gateway_.store(-1);
|
||||
const auto verified_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
|
||||
cache_.setDaliGroupMask(gateway_id, short_address, verified_mask);
|
||||
if (!applied && verified_mask.has_value()) {
|
||||
ESP_LOGW(kTag, "group reconcile fallback gateway=%u short=%u", gateway_id, short_address);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cache_.setDaliGroupMask(gateway_id, short_address,
|
||||
dali_domain_.queryGroupMask(gateway_id, short_address));
|
||||
}
|
||||
|
||||
void GatewayController::reconcileSceneStep(uint8_t gateway_id, uint8_t short_address,
|
||||
uint8_t scene_id) {
|
||||
const auto policy = cache_.priorityMode();
|
||||
const auto state = cache_.daliAddressState(gateway_id, short_address);
|
||||
|
||||
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst &&
|
||||
state.scene_levels[scene_id].has_value()) {
|
||||
maintenance_activity_gateway_.store(gateway_id);
|
||||
dali_domain_.applySceneLevel(gateway_id, short_address, scene_id, state.scene_levels[scene_id]);
|
||||
maintenance_activity_gateway_.store(-1);
|
||||
}
|
||||
|
||||
cache_.setDaliSceneLevel(gateway_id, short_address, scene_id,
|
||||
dali_domain_.querySceneLevel(gateway_id, short_address, scene_id));
|
||||
}
|
||||
|
||||
void GatewayController::reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address) {
|
||||
const auto policy = cache_.priorityMode();
|
||||
const auto state = cache_.daliAddressState(gateway_id, short_address);
|
||||
|
||||
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst && state.settings.anyKnown()) {
|
||||
maintenance_activity_gateway_.store(gateway_id);
|
||||
dali_domain_.applyAddressSettings(gateway_id, short_address, {
|
||||
state.settings.power_on_level,
|
||||
state.settings.system_failure_level,
|
||||
state.settings.min_level,
|
||||
state.settings.max_level,
|
||||
state.settings.fade_time,
|
||||
state.settings.fade_rate,
|
||||
});
|
||||
maintenance_activity_gateway_.store(-1);
|
||||
}
|
||||
|
||||
const auto settings = dali_domain_.queryAddressSettings(gateway_id, short_address);
|
||||
if (settings.has_value()) {
|
||||
cache_.setDaliSettings(gateway_id, short_address,
|
||||
GatewayCacheDaliSettingsSnapshot{settings->power_on_level,
|
||||
settings->system_failure_level,
|
||||
settings->min_level,
|
||||
settings->max_level,
|
||||
settings->fade_time,
|
||||
settings->fade_rate});
|
||||
} else {
|
||||
cache_.setDaliSettings(gateway_id, short_address, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +560,7 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
}
|
||||
case 0x07:
|
||||
case 0x08:
|
||||
dali_domain_.sendRaw(gateway_id, addr, data);
|
||||
sendRawAndMirror(gateway_id, addr, data);
|
||||
break;
|
||||
case 0x09: {
|
||||
const auto ids = gatewayIds();
|
||||
@@ -333,20 +577,20 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
break;
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
dali_domain_.sendRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
|
||||
sendRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
|
||||
break;
|
||||
case 0x12:
|
||||
if (addr == 0xff && data >= 0x10 && data <= 0x1f) {
|
||||
const uint8_t scene_id = static_cast<uint8_t>(data - 0x10);
|
||||
if (!executeScene(gateway_id, shortAddressFromRaw(addr), scene_id)) {
|
||||
dali_domain_.sendRaw(gateway_id, addr, data);
|
||||
sendRawAndMirror(gateway_id, addr, data);
|
||||
}
|
||||
} else {
|
||||
dali_domain_.sendRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
|
||||
sendRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
|
||||
}
|
||||
break;
|
||||
case 0x13:
|
||||
dali_domain_.sendExtRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
|
||||
sendExtRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
|
||||
break;
|
||||
case 0x14: {
|
||||
const auto result = dali_domain_.queryRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
|
||||
@@ -403,7 +647,7 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
const uint8_t b = command[7];
|
||||
const int target = shortAddressFromRaw(resolveInternalGroupRawAddress(gateway_id, addr));
|
||||
if (r == 0 && g == 0 && b == 0) {
|
||||
dali_domain_.off(gateway_id, target);
|
||||
offAndMirror(gateway_id, target);
|
||||
} else {
|
||||
dali_domain_.setColourRGB(gateway_id, target, r, g, b);
|
||||
}
|
||||
@@ -480,10 +724,6 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
if (frame.data.size() != 2 && frame.data.size() != 3) {
|
||||
return;
|
||||
}
|
||||
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) ||
|
||||
runtime_.hasActiveQueryCommand(frame.gateway_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t addr = 0;
|
||||
uint8_t data = 0;
|
||||
@@ -498,9 +738,67 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
data = frame.data[2];
|
||||
}
|
||||
|
||||
const bool maintenance_activity = maintenance_activity_gateway_.load() == frame.gateway_id;
|
||||
const bool local_activity = maintenance_activity || runtime_.hasActiveCommand(frame.gateway_id) ||
|
||||
dali_domain_.isAllocAddr(frame.gateway_id);
|
||||
const bool flagged = cache_.observeDaliCommand(frame.gateway_id, addr, data,
|
||||
local_activity
|
||||
? GatewayCacheRawFrameOrigin::kLocalGateway
|
||||
: GatewayCacheRawFrameOrigin::kOutsideBus);
|
||||
if (flagged) {
|
||||
scheduleReconciliation(frame.gateway_id);
|
||||
}
|
||||
|
||||
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) || maintenance_activity ||
|
||||
runtime_.hasActiveQueryCommand(frame.gateway_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
publishPayload(frame.gateway_id, {0x01, frame.gateway_id, addr, data});
|
||||
}
|
||||
|
||||
bool GatewayController::sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) {
|
||||
const bool sent = dali_domain_.sendRaw(gateway_id, raw_addr, command);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, raw_addr, command);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool GatewayController::sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr,
|
||||
uint8_t command) {
|
||||
const bool sent = dali_domain_.sendExtRaw(gateway_id, raw_addr, command);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, raw_addr, command);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool GatewayController::setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level) {
|
||||
const bool sent = dali_domain_.setBright(gateway_id, dec_address, level);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, rawArcAddressFromDec(dec_address), level);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool GatewayController::offAndMirror(uint8_t gateway_id, int dec_address) {
|
||||
const bool sent = dali_domain_.off(gateway_id, dec_address);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address), kDaliCmdOff);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool GatewayController::onAndMirror(uint8_t gateway_id, int dec_address) {
|
||||
const bool sent = dali_domain_.on(gateway_id, dec_address);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address),
|
||||
kDaliCmdRecallMax);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
uint8_t GatewayController::resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr) {
|
||||
if (raw_addr < 0x80 || raw_addr > 0x9f) {
|
||||
return raw_addr;
|
||||
@@ -554,6 +852,26 @@ int GatewayController::internalGroupDecTargetAddress(uint8_t target_type, uint8_
|
||||
return 127;
|
||||
}
|
||||
|
||||
uint8_t GatewayController::rawArcAddressFromDec(int dec_address) {
|
||||
if (dec_address >= 0 && dec_address < 64) {
|
||||
return static_cast<uint8_t>(dec_address * 2);
|
||||
}
|
||||
if (dec_address >= 64 && dec_address < 80) {
|
||||
return static_cast<uint8_t>(0x80 + (dec_address - 64) * 2);
|
||||
}
|
||||
return 0xfe;
|
||||
}
|
||||
|
||||
uint8_t GatewayController::rawCommandAddressFromDec(int dec_address) {
|
||||
if (dec_address >= 0 && dec_address < 64) {
|
||||
return static_cast<uint8_t>(dec_address * 2 + 1);
|
||||
}
|
||||
if (dec_address >= 64 && dec_address < 80) {
|
||||
return static_cast<uint8_t>(0x80 + (dec_address - 64) * 2 + 1);
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
int GatewayController::shortAddressFromRaw(uint8_t raw_addr) {
|
||||
return raw_addr / 2;
|
||||
}
|
||||
@@ -605,9 +923,9 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint
|
||||
return false;
|
||||
}
|
||||
if (scene_data.brightness <= 0) {
|
||||
dali_domain_.off(gateway_id, short_address);
|
||||
offAndMirror(gateway_id, short_address);
|
||||
} else {
|
||||
dali_domain_.setBright(gateway_id, short_address, scene_data.brightness);
|
||||
setBrightAndMirror(gateway_id, short_address, scene_data.brightness);
|
||||
}
|
||||
if (scene_data.color_mode == 0) {
|
||||
int kelvin = scene_data.data1 * 256 + scene_data.data2;
|
||||
@@ -623,7 +941,7 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint
|
||||
dali_domain_.setColourRGB(gateway_id, short_address, scene_data.data1, scene_data.data2,
|
||||
scene_data.data3);
|
||||
} else if (scene_data.brightness <= 0) {
|
||||
dali_domain_.off(gateway_id, short_address);
|
||||
offAndMirror(gateway_id, short_address);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -658,9 +976,9 @@ bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) {
|
||||
if (!group_data.enabled) {
|
||||
return false;
|
||||
}
|
||||
return dali_domain_.on(gateway_id,
|
||||
internalGroupDecTargetAddress(group_data.target_type,
|
||||
group_data.target_value));
|
||||
return onAndMirror(gateway_id,
|
||||
internalGroupDecTargetAddress(group_data.target_type,
|
||||
group_data.target_value));
|
||||
}
|
||||
|
||||
void GatewayController::handleGatewayNameCommand(uint8_t gateway_id,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_knx.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_cpp esp_driver_gpio esp_driver_uart esp_hw_support esp_netif freertos log lwip openknx_idf
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,372 @@
|
||||
#pragma once
|
||||
|
||||
#include "bridge.hpp"
|
||||
#include "model_value.hpp"
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
namespace openknx {
|
||||
class EtsDeviceRuntime;
|
||||
}
|
||||
|
||||
constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671;
|
||||
constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12";
|
||||
constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200;
|
||||
|
||||
struct GatewayKnxTpUartConfig {
|
||||
int uart_port{1};
|
||||
int tx_pin{-1};
|
||||
int rx_pin{-1};
|
||||
uint32_t baudrate{kGatewayKnxDefaultTpBaudrate};
|
||||
size_t rx_buffer_size{1024};
|
||||
size_t tx_buffer_size{1024};
|
||||
uint32_t read_timeout_ms{20};
|
||||
bool nine_bit_mode{true};
|
||||
};
|
||||
|
||||
enum class GatewayKnxMappingMode : uint8_t {
|
||||
kFormula = 0,
|
||||
kGwReg1Direct = 1,
|
||||
kManual = 2,
|
||||
kEtsDatabase = 3,
|
||||
};
|
||||
|
||||
struct GatewayKnxEtsAssociation {
|
||||
uint16_t group_address{0};
|
||||
uint16_t group_object_number{0};
|
||||
};
|
||||
|
||||
struct GatewayKnxConfig {
|
||||
bool dali_router_enabled{true};
|
||||
bool ip_router_enabled{false};
|
||||
bool tunnel_enabled{true};
|
||||
bool multicast_enabled{true};
|
||||
bool ets_database_enabled{true};
|
||||
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
|
||||
uint8_t main_group{0};
|
||||
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
||||
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
||||
uint16_t ip_interface_individual_address{0xff01};
|
||||
uint16_t individual_address{0xfffe};
|
||||
int programming_button_gpio{-1};
|
||||
bool programming_button_active_low{true};
|
||||
int programming_led_gpio{-1};
|
||||
bool programming_led_active_high{true};
|
||||
std::vector<GatewayKnxEtsAssociation> ets_associations;
|
||||
GatewayKnxTpUartConfig tp_uart;
|
||||
};
|
||||
|
||||
enum class GatewayKnxDaliDataType : uint8_t {
|
||||
kUnknown = 0,
|
||||
kSwitch = 1,
|
||||
kBrightness = 2,
|
||||
kColorTemperature = 3,
|
||||
kRgb = 4,
|
||||
};
|
||||
|
||||
enum class GatewayKnxDaliTargetKind : uint8_t {
|
||||
kNone = 0,
|
||||
kBroadcast = 1,
|
||||
kShortAddress = 2,
|
||||
kGroup = 3,
|
||||
};
|
||||
|
||||
struct GatewayKnxDaliTarget {
|
||||
GatewayKnxDaliTargetKind kind{GatewayKnxDaliTargetKind::kNone};
|
||||
int address{-1};
|
||||
};
|
||||
|
||||
struct GatewayKnxDaliBinding {
|
||||
uint16_t group_address{0};
|
||||
uint8_t main_group{0};
|
||||
uint8_t middle_group{0};
|
||||
uint8_t sub_group{0};
|
||||
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
|
||||
int group_object_number{-1};
|
||||
int channel_index{-1};
|
||||
std::string address;
|
||||
std::string name;
|
||||
std::string object_role;
|
||||
std::string datapoint_type;
|
||||
GatewayKnxDaliDataType data_type{GatewayKnxDaliDataType::kUnknown};
|
||||
GatewayKnxDaliTarget target;
|
||||
};
|
||||
|
||||
struct GatewayKnxCommissioningBallast {
|
||||
uint8_t high{0};
|
||||
uint8_t middle{0};
|
||||
uint8_t low{0};
|
||||
uint8_t short_address{0xff};
|
||||
};
|
||||
|
||||
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
||||
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
||||
bool GatewayKnxConfigUsesTpUart(const GatewayKnxConfig& config);
|
||||
|
||||
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode);
|
||||
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value);
|
||||
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type);
|
||||
const char* GatewayKnxTargetKindToString(GatewayKnxDaliTargetKind kind);
|
||||
std::optional<GatewayKnxDaliDataType> GatewayKnxDaliDataTypeForMiddleGroup(
|
||||
uint8_t middle_group);
|
||||
std::optional<GatewayKnxDaliTarget> GatewayKnxDaliTargetForSubgroup(uint8_t sub_group);
|
||||
uint16_t GatewayKnxGroupAddress(uint8_t main_group, uint8_t middle_group,
|
||||
uint8_t sub_group);
|
||||
std::string GatewayKnxGroupAddressString(uint16_t group_address);
|
||||
|
||||
class GatewayKnxBridge {
|
||||
public:
|
||||
explicit GatewayKnxBridge(DaliBridgeEngine& engine);
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
const GatewayKnxConfig& config() const;
|
||||
size_t etsBindingCount() const;
|
||||
|
||||
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
|
||||
bool matchesCemiFrame(const uint8_t* data, size_t len) const;
|
||||
bool matchesGroupAddress(uint16_t group_address) const;
|
||||
DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len);
|
||||
DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
size_t len);
|
||||
bool handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
|
||||
private:
|
||||
DaliBridgeResult executeForDecodedWrite(uint16_t group_address,
|
||||
GatewayKnxDaliDataType data_type,
|
||||
GatewayKnxDaliTarget target,
|
||||
const uint8_t* data, size_t len);
|
||||
DaliBridgeResult executeEtsBindings(uint16_t group_address,
|
||||
const std::vector<GatewayKnxDaliBinding>& bindings,
|
||||
const uint8_t* data, size_t len);
|
||||
void rebuildEtsBindings();
|
||||
|
||||
bool handleReg1TypeCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1ScanCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1AssignCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1EvgWriteCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1EvgReadCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1SetSceneCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1GetSceneCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1IdentifyCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1ScanState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1AssignState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1FoundEvgsState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
|
||||
DaliBridgeEngine& engine_;
|
||||
GatewayKnxConfig config_;
|
||||
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
|
||||
bool commissioning_scan_done_{true};
|
||||
bool commissioning_assign_done_{true};
|
||||
std::vector<GatewayKnxCommissioningBallast> commissioning_found_ballasts_;
|
||||
};
|
||||
|
||||
class GatewayKnxTpIpRouter {
|
||||
public:
|
||||
using CemiFrameHandler = std::function<DaliBridgeResult(const uint8_t* data, size_t len)>;
|
||||
using GroupWriteHandler = std::function<DaliBridgeResult(uint16_t group_address,
|
||||
const uint8_t* data,
|
||||
size_t len)>;
|
||||
|
||||
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler,
|
||||
std::string openknx_namespace = "openknx");
|
||||
~GatewayKnxTpIpRouter();
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
void setCommissioningOnly(bool enabled);
|
||||
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||
const GatewayKnxConfig& config() const;
|
||||
bool tpUartOnline() const;
|
||||
bool programmingMode();
|
||||
esp_err_t setProgrammingMode(bool enabled);
|
||||
esp_err_t toggleProgrammingMode();
|
||||
|
||||
esp_err_t start(uint32_t task_stack_size, UBaseType_t task_priority);
|
||||
esp_err_t stop();
|
||||
bool started() const;
|
||||
const std::string& lastError() const;
|
||||
bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level);
|
||||
|
||||
private:
|
||||
static constexpr size_t kMaxTunnelClients = 16;
|
||||
static constexpr size_t kMaxTcpClients = 4;
|
||||
|
||||
struct TcpClient {
|
||||
int sock{-1};
|
||||
::sockaddr_in remote{};
|
||||
std::vector<uint8_t> rx_buffer;
|
||||
TickType_t last_activity_tick{0};
|
||||
};
|
||||
|
||||
struct TunnelClient {
|
||||
bool connected{false};
|
||||
uint8_t channel_id{0};
|
||||
uint8_t connection_type{0};
|
||||
uint8_t received_sequence{255};
|
||||
uint8_t send_sequence{0};
|
||||
uint16_t individual_address{0};
|
||||
int tcp_sock{-1};
|
||||
TickType_t last_activity_tick{0};
|
||||
::sockaddr_in control_remote{};
|
||||
::sockaddr_in data_remote{};
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
|
||||
esp_err_t initializeRuntime();
|
||||
void taskLoop();
|
||||
void finishTask();
|
||||
void closeSockets();
|
||||
bool configureSocket();
|
||||
void handleTcpAccept();
|
||||
void handleTcpClient(TcpClient& client);
|
||||
void closeTcpClient(TcpClient& client);
|
||||
bool configureTpUart();
|
||||
bool initializeTpUart();
|
||||
bool configureProgrammingGpio();
|
||||
void refreshNetworkInterfaces(bool force_log = false);
|
||||
void handleUdpDatagram(const uint8_t* data, size_t len, const ::sockaddr_in& remote);
|
||||
void handleSearchRequest(uint16_t service, const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void handleDescriptionRequest(const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void handleRoutingIndication(const uint8_t* body, size_t len);
|
||||
void handleTunnellingRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
|
||||
void handleDeviceConfigurationRequest(const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void handleConnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
|
||||
void handleConnectionStateRequest(const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void handleDisconnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
|
||||
void handleSecureService(uint16_t service, const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendDeviceConfigurationAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendConnectionHeaderAck(uint16_t service, uint8_t channel_id, uint8_t sequence,
|
||||
uint8_t status, const ::sockaddr_in& remote);
|
||||
void sendSecureSessionStatus(uint8_t status, const ::sockaddr_in& remote);
|
||||
void sendTunnelIndication(const uint8_t* data, size_t len);
|
||||
void sendTunnelIndicationToClient(TunnelClient& client, const uint8_t* data, size_t len);
|
||||
void sendConnectionStateResponse(uint8_t channel_id, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendDisconnectResponse(uint8_t channel_id, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendConnectResponse(uint8_t channel_id, uint8_t status,
|
||||
const ::sockaddr_in& remote, uint8_t connection_type,
|
||||
uint16_t tunnel_address);
|
||||
void sendRoutingIndication(const uint8_t* data, size_t len);
|
||||
void sendSearchResponse(uint16_t service, const ::sockaddr_in& remote,
|
||||
const std::set<uint8_t>& requested_dibs = {});
|
||||
void sendDescriptionResponse(const ::sockaddr_in& remote);
|
||||
bool sendPacket(const std::vector<uint8_t>& packet, const ::sockaddr_in& remote) const;
|
||||
bool sendPacketToTunnelClient(const TunnelClient& client,
|
||||
const std::vector<uint8_t>& packet) const;
|
||||
bool currentTransportAllowsTcpHpai() const;
|
||||
std::optional<std::array<uint8_t, 8>> localHpaiForRemote(const ::sockaddr_in& remote,
|
||||
bool tcp = false) const;
|
||||
std::vector<uint8_t> buildDeviceInfoDib(const ::sockaddr_in& remote) const;
|
||||
std::vector<uint8_t> buildSupportedServiceDib() const;
|
||||
std::vector<uint8_t> buildExtendedDeviceInfoDib() const;
|
||||
std::vector<uint8_t> buildIpConfigDib(const ::sockaddr_in& remote, bool current) const;
|
||||
std::vector<uint8_t> buildKnxAddressesDib() const;
|
||||
std::vector<uint8_t> buildTunnelingInfoDib() const;
|
||||
TunnelClient* findTunnelClient(uint8_t channel_id);
|
||||
const TunnelClient* findTunnelClient(uint8_t channel_id) const;
|
||||
TunnelClient* allocateTunnelClient(const ::sockaddr_in& control_remote,
|
||||
const ::sockaddr_in& data_remote,
|
||||
uint8_t connection_type);
|
||||
void resetTunnelClient(TunnelClient& client);
|
||||
uint8_t nextTunnelChannelId() const;
|
||||
uint16_t effectiveTunnelAddressForSlot(size_t slot) const;
|
||||
void pruneStaleTunnelClients();
|
||||
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len,
|
||||
TunnelClient* response_client);
|
||||
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
|
||||
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
|
||||
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
|
||||
bool shouldRouteDaliApplicationFrames() const;
|
||||
uint8_t advertisedMedium() const;
|
||||
void syncOpenKnxConfigFromDevice();
|
||||
uint16_t effectiveIpInterfaceIndividualAddress() const;
|
||||
uint16_t effectiveKnxDeviceIndividualAddress() const;
|
||||
uint16_t effectiveTunnelAddress() const;
|
||||
void pollTpUart();
|
||||
void pollProgrammingButton();
|
||||
void updateProgrammingLed();
|
||||
void setProgrammingLed(bool on);
|
||||
void handleTpUartControlByte(uint8_t byte);
|
||||
void handleTpTelegram(const uint8_t* data, size_t len);
|
||||
void forwardCemiToTp(const uint8_t* data, size_t len);
|
||||
|
||||
GatewayKnxBridge& bridge_;
|
||||
CemiFrameHandler handler_;
|
||||
GroupWriteHandler group_write_handler_;
|
||||
std::string openknx_namespace_;
|
||||
GatewayKnxConfig config_;
|
||||
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
SemaphoreHandle_t openknx_lock_{nullptr};
|
||||
SemaphoreHandle_t startup_semaphore_{nullptr};
|
||||
esp_err_t startup_result_{ESP_OK};
|
||||
std::atomic_bool stop_requested_{false};
|
||||
std::atomic_bool started_{false};
|
||||
int udp_sock_{-1};
|
||||
int tcp_sock_{-1};
|
||||
int active_tcp_sock_{-1};
|
||||
int tp_uart_port_{-1};
|
||||
int tp_uart_tx_pin_{-1};
|
||||
int tp_uart_rx_pin_{-1};
|
||||
std::vector<uint32_t> multicast_joined_interfaces_;
|
||||
TickType_t network_refresh_tick_{0};
|
||||
std::array<TcpClient, kMaxTcpClients> tcp_clients_{};
|
||||
std::array<TunnelClient, kMaxTunnelClients> tunnel_clients_{};
|
||||
uint8_t last_tunnel_channel_id_{0};
|
||||
std::vector<uint8_t> tp_rx_frame_;
|
||||
std::vector<uint8_t> tp_last_sent_telegram_;
|
||||
TickType_t tp_uart_last_byte_tick_{0};
|
||||
bool tp_uart_extended_frame_{false};
|
||||
bool tp_uart_online_{false};
|
||||
bool commissioning_only_{false};
|
||||
std::atomic_bool openknx_configured_{false};
|
||||
bool programming_button_last_pressed_{false};
|
||||
bool programming_led_state_{false};
|
||||
TickType_t programming_button_last_toggle_tick_{0};
|
||||
std::string last_error_;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_modbus.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_cpp
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,190 @@
|
||||
#pragma once
|
||||
|
||||
#include "bridge.hpp"
|
||||
#include "bridge_model.hpp"
|
||||
#include "model_value.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
constexpr uint16_t kGatewayModbusDefaultTcpPort = 1502;
|
||||
constexpr size_t kGatewayModbusMaxPduBytes = 252;
|
||||
constexpr uint16_t kGatewayModbusMaxReadBits = 2000;
|
||||
constexpr uint16_t kGatewayModbusMaxReadRegisters = 125;
|
||||
constexpr uint16_t kGatewayModbusMaxWriteBits = 1968;
|
||||
constexpr uint16_t kGatewayModbusMaxWriteRegisters = 123;
|
||||
constexpr uint32_t kGatewayModbusDefaultSerialBaudrate = 9600;
|
||||
constexpr uint32_t kGatewayModbusDefaultSerialResponseTimeoutMs = 20;
|
||||
constexpr uint32_t kGatewayModbusDefaultSerialInterFrameGapUs = 4000;
|
||||
|
||||
struct GatewayModbusRs485Config {
|
||||
bool enabled{false};
|
||||
int de_pin{-1};
|
||||
};
|
||||
|
||||
struct GatewayModbusSerialConfig {
|
||||
int uart_port{1};
|
||||
int tx_pin{-1};
|
||||
int rx_pin{-1};
|
||||
uint32_t baudrate{kGatewayModbusDefaultSerialBaudrate};
|
||||
int data_bits{8};
|
||||
std::string parity{"none"};
|
||||
int stop_bits{1};
|
||||
size_t rx_buffer_size{512};
|
||||
size_t tx_buffer_size{512};
|
||||
uint32_t response_timeout_ms{kGatewayModbusDefaultSerialResponseTimeoutMs};
|
||||
uint32_t inter_frame_gap_us{kGatewayModbusDefaultSerialInterFrameGapUs};
|
||||
GatewayModbusRs485Config rs485;
|
||||
};
|
||||
|
||||
struct GatewayModbusConfig {
|
||||
std::string transport{"tcp-server"};
|
||||
std::string host;
|
||||
uint16_t port{kGatewayModbusDefaultTcpPort};
|
||||
uint8_t unit_id{1};
|
||||
GatewayModbusSerialConfig serial;
|
||||
};
|
||||
|
||||
bool GatewayModbusTransportIsTcp(const std::string& transport);
|
||||
bool GatewayModbusTransportIsRtu(const std::string& transport);
|
||||
bool GatewayModbusTransportIsAscii(const std::string& transport);
|
||||
bool GatewayModbusTransportIsSerial(const std::string& transport);
|
||||
|
||||
enum class GatewayModbusSpace : uint8_t {
|
||||
kCoil = 1,
|
||||
kDiscreteInput = 2,
|
||||
kHoldingRegister = 3,
|
||||
kInputRegister = 4,
|
||||
};
|
||||
|
||||
enum class GatewayModbusAccess : uint8_t {
|
||||
kReadOnly = 0,
|
||||
kWriteOnly = 1,
|
||||
kReadWrite = 2,
|
||||
};
|
||||
|
||||
enum class GatewayModbusGeneratedKind : uint8_t {
|
||||
kNone = 0,
|
||||
kShortOn,
|
||||
kShortOff,
|
||||
kShortRecallMax,
|
||||
kShortRecallMin,
|
||||
kShortDiscovered,
|
||||
kShortOnline,
|
||||
kShortSupportsDt1,
|
||||
kShortSupportsDt4,
|
||||
kShortSupportsDt5,
|
||||
kShortSupportsDt6,
|
||||
kShortSupportsDt8,
|
||||
kShortGroupMaskKnown,
|
||||
kShortActualLevelKnown,
|
||||
kShortSceneKnown,
|
||||
kShortSettingsKnown,
|
||||
kShortControlGearPresent,
|
||||
kShortLampFailure,
|
||||
kShortLampPowerOn,
|
||||
kShortLimitError,
|
||||
kShortFadingCompleted,
|
||||
kShortResetState,
|
||||
kShortMissingShortAddress,
|
||||
kShortPowerSupplyFault,
|
||||
kShortBrightness,
|
||||
kShortColorTemperature,
|
||||
kShortGroupMask,
|
||||
kShortPowerOnLevel,
|
||||
kShortSystemFailureLevel,
|
||||
kShortMinLevel,
|
||||
kShortMaxLevel,
|
||||
kShortFadeTime,
|
||||
kShortFadeRate,
|
||||
kShortInventoryState,
|
||||
kShortPrimaryType,
|
||||
kShortTypeMask,
|
||||
kShortActualLevel,
|
||||
kShortSceneId,
|
||||
kShortRawStatus,
|
||||
kShortDiagnosticBit,
|
||||
};
|
||||
|
||||
struct GatewayModbusPoint {
|
||||
GatewayModbusSpace space{GatewayModbusSpace::kHoldingRegister};
|
||||
GatewayModbusAccess access{GatewayModbusAccess::kReadWrite};
|
||||
uint16_t address{0};
|
||||
std::string id;
|
||||
std::string name;
|
||||
bool generated{false};
|
||||
GatewayModbusGeneratedKind generated_kind{GatewayModbusGeneratedKind::kNone};
|
||||
const char* generated_suffix{"point"};
|
||||
const char* generated_name{"point"};
|
||||
int short_address{-1};
|
||||
std::string model_id;
|
||||
BridgeOperation operation{BridgeOperation::unknown};
|
||||
std::optional<int> bit_index;
|
||||
const char* diagnostic_snapshot{""};
|
||||
const char* diagnostic_bool{""};
|
||||
int diagnostic_device_type{-1};
|
||||
};
|
||||
|
||||
struct GatewayModbusPointBinding {
|
||||
std::string model_id;
|
||||
GatewayModbusSpace space{GatewayModbusSpace::kHoldingRegister};
|
||||
uint16_t address{0};
|
||||
std::string id;
|
||||
std::string name;
|
||||
bool generated{false};
|
||||
GatewayModbusGeneratedKind generated_kind{GatewayModbusGeneratedKind::kNone};
|
||||
int short_address{-1};
|
||||
GatewayModbusAccess access{GatewayModbusAccess::kReadWrite};
|
||||
std::optional<int> bit_index;
|
||||
std::string diagnostic_snapshot;
|
||||
std::string diagnostic_bool;
|
||||
int diagnostic_device_type{-1};
|
||||
};
|
||||
|
||||
std::optional<GatewayModbusConfig> GatewayModbusConfigFromValue(const DaliValue* value);
|
||||
DaliValue GatewayModbusConfigToValue(const GatewayModbusConfig& config);
|
||||
|
||||
const char* GatewayModbusSpaceToString(GatewayModbusSpace space);
|
||||
const char* GatewayModbusAccessToString(GatewayModbusAccess access);
|
||||
const char* GatewayModbusGeneratedKindToString(GatewayModbusGeneratedKind kind);
|
||||
|
||||
int GatewayModbusHumanAddressFromWire(GatewayModbusSpace space, uint16_t zero_based_address);
|
||||
std::optional<GatewayModbusSpace> GatewayModbusReadSpaceForFunction(uint8_t function_code);
|
||||
std::optional<GatewayModbusSpace> GatewayModbusWriteSpaceForFunction(uint8_t function_code);
|
||||
|
||||
class GatewayModbusBridge {
|
||||
public:
|
||||
explicit GatewayModbusBridge(DaliBridgeEngine& engine);
|
||||
|
||||
void setConfig(const GatewayModbusConfig& config);
|
||||
const GatewayModbusConfig& config() const;
|
||||
|
||||
void rebuildMap();
|
||||
std::optional<GatewayModbusPoint> findPoint(GatewayModbusSpace space,
|
||||
uint16_t address) const;
|
||||
GatewayModbusPointBinding describePoint(const GatewayModbusPoint& point) const;
|
||||
void appendGeneratedPointsForShortAddress(uint8_t short_address,
|
||||
std::vector<GatewayModbusPoint>* points) const;
|
||||
std::vector<GatewayModbusPointBinding> describePoints() const;
|
||||
std::vector<GatewayModbusPointBinding> describeHoldingRegisters() const;
|
||||
const std::vector<GatewayModbusPoint>& points() const;
|
||||
|
||||
DaliBridgeResult readModelPoint(const GatewayModbusPoint& point) const;
|
||||
DaliBridgeResult writeRegisterPoint(const GatewayModbusPoint& point, uint16_t value) const;
|
||||
DaliBridgeResult writeCoilPoint(const GatewayModbusPoint& point, bool value) const;
|
||||
|
||||
private:
|
||||
DaliBridgeResult executeModelPoint(const GatewayModbusPoint& point,
|
||||
std::optional<int> value) const;
|
||||
|
||||
DaliBridgeEngine& engine_;
|
||||
GatewayModbusConfig config_;
|
||||
std::vector<GatewayModbusPoint> points_;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,941 @@
|
||||
#include "gateway_modbus.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
namespace gateway {
|
||||
namespace {
|
||||
|
||||
constexpr uint16_t kCoilBase = 1;
|
||||
constexpr uint16_t kDiscreteInputBase = 10001;
|
||||
constexpr uint16_t kInputRegisterBase = 30001;
|
||||
constexpr uint16_t kHoldingRegisterBase = 40001;
|
||||
constexpr uint16_t kShortAddressCount = 64;
|
||||
constexpr uint16_t kShortStride = 32;
|
||||
constexpr uint16_t kDiagnosticDiscreteInputBase = kDiscreteInputBase +
|
||||
kShortAddressCount * kShortStride;
|
||||
constexpr uint16_t kDiagnosticStride = 128;
|
||||
|
||||
struct PointKey {
|
||||
GatewayModbusSpace space{GatewayModbusSpace::kHoldingRegister};
|
||||
uint16_t address{0};
|
||||
|
||||
bool operator<(const PointKey& other) const {
|
||||
if (space != other.space) {
|
||||
return static_cast<uint8_t>(space) < static_cast<uint8_t>(other.space);
|
||||
}
|
||||
return address < other.address;
|
||||
}
|
||||
};
|
||||
|
||||
struct GeneratedPointSpec {
|
||||
uint16_t offset;
|
||||
GatewayModbusSpace space;
|
||||
GatewayModbusAccess access;
|
||||
GatewayModbusGeneratedKind kind;
|
||||
const char* suffix;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct GeneratedDiagnosticBitSpec {
|
||||
uint16_t offset;
|
||||
int device_type;
|
||||
const char* snapshot;
|
||||
const char* bool_key;
|
||||
const char* suffix;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
constexpr std::array<GeneratedPointSpec, 4> kGeneratedCoils{{
|
||||
{0, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
|
||||
GatewayModbusGeneratedKind::kShortOn, "on", "recall max"},
|
||||
{1, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
|
||||
GatewayModbusGeneratedKind::kShortOff, "off", "off"},
|
||||
{2, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
|
||||
GatewayModbusGeneratedKind::kShortRecallMax, "recall_max", "recall max"},
|
||||
{3, GatewayModbusSpace::kCoil, GatewayModbusAccess::kWriteOnly,
|
||||
GatewayModbusGeneratedKind::kShortRecallMin, "recall_min", "recall min"},
|
||||
}};
|
||||
|
||||
constexpr std::array<GeneratedPointSpec, 19> kGeneratedDiscreteInputs{{
|
||||
{0, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortDiscovered, "discovered", "discovered"},
|
||||
{1, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortOnline, "online", "online"},
|
||||
{2, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSupportsDt1, "supports_dt1", "supports DT1"},
|
||||
{3, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSupportsDt4, "supports_dt4", "supports DT4"},
|
||||
{4, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSupportsDt5, "supports_dt5", "supports DT5"},
|
||||
{5, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSupportsDt6, "supports_dt6", "supports DT6"},
|
||||
{6, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSupportsDt8, "supports_dt8", "supports DT8"},
|
||||
{7, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortGroupMaskKnown, "group_mask_known", "group mask known"},
|
||||
{8, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortActualLevelKnown, "actual_level_known", "actual level known"},
|
||||
{9, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSceneKnown, "scene_known", "scene known"},
|
||||
{10, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSettingsKnown, "settings_known", "settings known"},
|
||||
{16, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortControlGearPresent, "control_gear_present",
|
||||
"control gear present"},
|
||||
{17, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortLampFailure, "lamp_failure", "lamp failure"},
|
||||
{18, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortLampPowerOn, "lamp_power_on", "lamp power on"},
|
||||
{19, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortLimitError, "limit_error", "limit error"},
|
||||
{20, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortFadingCompleted, "fading_completed", "fading completed"},
|
||||
{21, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortResetState, "reset_state", "reset state"},
|
||||
{22, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortMissingShortAddress, "missing_short_address",
|
||||
"missing short address"},
|
||||
{23, GatewayModbusSpace::kDiscreteInput, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortPowerSupplyFault, "power_supply_fault",
|
||||
"power supply fault"},
|
||||
}};
|
||||
|
||||
constexpr std::array<GeneratedPointSpec, 9> kGeneratedHoldingRegisters{{
|
||||
{0, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortBrightness, "brightness", "brightness"},
|
||||
{1, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortColorTemperature, "color_temperature",
|
||||
"color temperature"},
|
||||
{2, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortGroupMask, "group_mask", "group mask"},
|
||||
{3, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortPowerOnLevel, "power_on_level", "power-on level"},
|
||||
{4, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortSystemFailureLevel, "system_failure_level",
|
||||
"system-failure level"},
|
||||
{5, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortMinLevel, "min_level", "minimum level"},
|
||||
{6, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortMaxLevel, "max_level", "maximum level"},
|
||||
{7, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortFadeTime, "fade_time", "fade time"},
|
||||
{8, GatewayModbusSpace::kHoldingRegister, GatewayModbusAccess::kReadWrite,
|
||||
GatewayModbusGeneratedKind::kShortFadeRate, "fade_rate", "fade rate"},
|
||||
}};
|
||||
|
||||
constexpr std::array<GeneratedPointSpec, 13> kGeneratedInputRegisters{{
|
||||
{0, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortInventoryState, "inventory_state", "inventory state"},
|
||||
{1, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortPrimaryType, "primary_type", "primary type"},
|
||||
{2, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortTypeMask, "type_mask", "device type mask"},
|
||||
{3, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortActualLevel, "actual_level", "actual level"},
|
||||
{4, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSceneId, "scene_id", "scene id"},
|
||||
{5, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortRawStatus, "raw_status", "raw status"},
|
||||
{6, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortGroupMask, "group_mask", "group mask"},
|
||||
{7, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortPowerOnLevel, "power_on_level", "power-on level"},
|
||||
{8, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortSystemFailureLevel, "system_failure_level",
|
||||
"system-failure level"},
|
||||
{9, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortMinLevel, "min_level", "minimum level"},
|
||||
{10, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortMaxLevel, "max_level", "maximum level"},
|
||||
{11, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortFadeTime, "fade_time", "fade time"},
|
||||
{12, GatewayModbusSpace::kInputRegister, GatewayModbusAccess::kReadOnly,
|
||||
GatewayModbusGeneratedKind::kShortFadeRate, "fade_rate", "fade rate"},
|
||||
}};
|
||||
|
||||
constexpr GeneratedDiagnosticBitSpec kGeneratedDiagnosticBits[] = {
|
||||
{0, 1, "dt1", "circuitFailure", "dt1_circuit_failure", "DT1 circuit failure"},
|
||||
{1, 1, "dt1", "batteryDurationFailure", "dt1_battery_duration_failure", "DT1 battery duration failure"},
|
||||
{2, 1, "dt1", "batteryFailure", "dt1_battery_failure", "DT1 battery failure"},
|
||||
{3, 1, "dt1", "emergencyLampFailure", "dt1_emergency_lamp_failure", "DT1 emergency lamp failure"},
|
||||
{4, 1, "dt1", "functionTestMaxDelayExceeded", "dt1_function_test_delay_exceeded", "DT1 function test delay exceeded"},
|
||||
{5, 1, "dt1", "durationTestMaxDelayExceeded", "dt1_duration_test_delay_exceeded", "DT1 duration test delay exceeded"},
|
||||
{6, 1, "dt1", "functionTestFailed", "dt1_function_test_failed", "DT1 function test failed"},
|
||||
{7, 1, "dt1", "durationTestFailed", "dt1_duration_test_failed", "DT1 duration test failed"},
|
||||
{8, 1, "dt1", "inhibitMode", "dt1_inhibit_mode", "DT1 inhibit mode"},
|
||||
{9, 1, "dt1", "functionTestResultValid", "dt1_function_result_valid", "DT1 function result valid"},
|
||||
{10, 1, "dt1", "durationTestResultValid", "dt1_duration_result_valid", "DT1 duration result valid"},
|
||||
{11, 1, "dt1", "batteryFullyCharged", "dt1_battery_fully_charged", "DT1 battery fully charged"},
|
||||
{12, 1, "dt1", "functionTestRequestPending", "dt1_function_request_pending", "DT1 function request pending"},
|
||||
{13, 1, "dt1", "durationTestRequestPending", "dt1_duration_request_pending", "DT1 duration request pending"},
|
||||
{14, 1, "dt1", "identificationActive", "dt1_identification_active", "DT1 identification active"},
|
||||
{15, 1, "dt1", "physicallySelected", "dt1_physically_selected", "DT1 physically selected"},
|
||||
{16, 1, "dt1", "restModeActive", "dt1_rest_mode_active", "DT1 rest mode active"},
|
||||
{17, 1, "dt1", "normalModeActive", "dt1_normal_mode_active", "DT1 normal mode active"},
|
||||
{18, 1, "dt1", "emergencyModeActive", "dt1_emergency_mode_active", "DT1 emergency mode active"},
|
||||
{19, 1, "dt1", "extendedEmergencyModeActive", "dt1_extended_mode_active", "DT1 extended emergency mode active"},
|
||||
{20, 1, "dt1", "functionTestInProgress", "dt1_function_test_in_progress", "DT1 function test in progress"},
|
||||
{21, 1, "dt1", "durationTestInProgress", "dt1_duration_test_in_progress", "DT1 duration test in progress"},
|
||||
{22, 1, "dt1", "hardwiredInhibitActive", "dt1_hardwired_inhibit_active", "DT1 hardwired inhibit active"},
|
||||
{23, 1, "dt1", "hardwiredSwitchOn", "dt1_hardwired_switch_on", "DT1 hardwired switch on"},
|
||||
{24, 1, "dt1", "integralEmergencyControlGear", "dt1_integral_emergency", "DT1 integral emergency gear"},
|
||||
{25, 1, "dt1", "maintainedControlGear", "dt1_maintained", "DT1 maintained gear"},
|
||||
{26, 1, "dt1", "switchedMaintainedControlGear", "dt1_switched_maintained", "DT1 switched maintained gear"},
|
||||
{27, 1, "dt1", "autoTestCapability", "dt1_auto_test_capability", "DT1 auto test capability"},
|
||||
{28, 1, "dt1", "adjustableEmergencyLevel", "dt1_adjustable_level", "DT1 adjustable emergency level"},
|
||||
{29, 1, "dt1", "hardwiredInhibitSupported", "dt1_hardwired_inhibit_supported", "DT1 hardwired inhibit supported"},
|
||||
{30, 1, "dt1", "physicalSelectionSupported", "dt1_physical_selection_supported", "DT1 physical selection supported"},
|
||||
{31, 1, "dt1", "relightInRestModeSupported", "dt1_relight_rest_supported", "DT1 relight in rest mode supported"},
|
||||
{32, 4, "dt4", "leadingEdgeModeRunning", "dt4_leading_edge_running", "DT4 leading edge running"},
|
||||
{33, 4, "dt4", "trailingEdgeModeRunning", "dt4_trailing_edge_running", "DT4 trailing edge running"},
|
||||
{34, 4, "dt4", "referenceMeasurementRunning", "dt4_reference_running", "DT4 reference measurement running"},
|
||||
{35, 4, "dt4", "nonLogarithmicDimmingCurveActive", "dt4_non_log_curve_active", "DT4 non-log curve active"},
|
||||
{36, 4, "dt4", "canQueryLoadOverCurrentShutdown", "dt4_can_load_over_current_shutdown", "DT4 can query over-current shutdown"},
|
||||
{37, 4, "dt4", "canQueryOpenCircuitDetection", "dt4_can_open_circuit", "DT4 can query open circuit"},
|
||||
{38, 4, "dt4", "canQueryLoadDecrease", "dt4_can_load_decrease", "DT4 can query load decrease"},
|
||||
{39, 4, "dt4", "canQueryLoadIncrease", "dt4_can_load_increase", "DT4 can query load increase"},
|
||||
{40, 4, "dt4", "canQueryThermalShutdown", "dt4_can_thermal_shutdown", "DT4 can query thermal shutdown"},
|
||||
{41, 4, "dt4", "canQueryThermalOverloadReduction", "dt4_can_thermal_overload", "DT4 can query thermal overload"},
|
||||
{42, 4, "dt4", "physicalSelectionSupported", "dt4_physical_selection_supported", "DT4 physical selection supported"},
|
||||
{43, 4, "dt4", "canQueryTemperature", "dt4_can_temperature", "DT4 can query temperature"},
|
||||
{44, 4, "dt4", "canQuerySupplyVoltage", "dt4_can_supply_voltage", "DT4 can query supply voltage"},
|
||||
{45, 4, "dt4", "canQuerySupplyFrequency", "dt4_can_supply_frequency", "DT4 can query supply frequency"},
|
||||
{46, 4, "dt4", "canQueryLoadVoltage", "dt4_can_load_voltage", "DT4 can query load voltage"},
|
||||
{47, 4, "dt4", "canQueryLoadCurrent", "dt4_can_load_current", "DT4 can query load current"},
|
||||
{48, 4, "dt4", "canQueryRealLoadPower", "dt4_can_load_power", "DT4 can query load power"},
|
||||
{49, 4, "dt4", "canQueryLoadRating", "dt4_can_load_rating", "DT4 can query load rating"},
|
||||
{50, 4, "dt4", "canQueryCurrentOverloadReduction", "dt4_can_current_overload", "DT4 can query current overload"},
|
||||
{51, 4, "dt4", "canSelectNonLogarithmicDimmingCurve", "dt4_can_non_log_curve", "DT4 can select non-log curve"},
|
||||
{52, 4, "dt4", "canQueryUnsuitableLoad", "dt4_can_unsuitable_load", "DT4 can query unsuitable load"},
|
||||
{53, 4, "dt4", "loadOverCurrentShutdown", "dt4_load_over_current_shutdown", "DT4 load over-current shutdown"},
|
||||
{54, 4, "dt4", "openCircuitDetected", "dt4_open_circuit_detected", "DT4 open circuit detected"},
|
||||
{55, 4, "dt4", "loadDecreaseDetected", "dt4_load_decrease_detected", "DT4 load decrease detected"},
|
||||
{56, 4, "dt4", "loadIncreaseDetected", "dt4_load_increase_detected", "DT4 load increase detected"},
|
||||
{57, 4, "dt4", "thermalShutdown", "dt4_thermal_shutdown", "DT4 thermal shutdown"},
|
||||
{58, 4, "dt4", "thermalOverloadReduction", "dt4_thermal_overload", "DT4 thermal overload reduction"},
|
||||
{59, 4, "dt4", "referenceMeasurementFailedStatus", "dt4_reference_failed", "DT4 reference failed"},
|
||||
{60, 4, "dt4", "loadUnsuitableForSelectedMethod", "dt4_unsuitable_load", "DT4 unsuitable load"},
|
||||
{61, 4, "dt4", "supplyVoltageOutOfLimits", "dt4_supply_voltage_limits", "DT4 supply voltage out of limits"},
|
||||
{62, 4, "dt4", "supplyFrequencyOutOfLimits", "dt4_supply_frequency_limits", "DT4 supply frequency out of limits"},
|
||||
{63, 4, "dt4", "loadVoltageOutOfLimits", "dt4_load_voltage_limits", "DT4 load voltage out of limits"},
|
||||
{64, 4, "dt4", "loadCurrentOverloadReduction", "dt4_load_current_overload", "DT4 load current overload"},
|
||||
{65, 5, "dt5", "outputRange0To10VSelectable", "dt5_output_range_selectable", "DT5 output range selectable"},
|
||||
{66, 5, "dt5", "internalPullUpSelectable", "dt5_pullup_selectable", "DT5 pull-up selectable"},
|
||||
{67, 5, "dt5", "outputFaultDetectionSelectable", "dt5_fault_detection_selectable", "DT5 fault detection selectable"},
|
||||
{68, 5, "dt5", "mainsRelay", "dt5_mains_relay", "DT5 mains relay"},
|
||||
{69, 5, "dt5", "outputLevelQueryable", "dt5_output_level_queryable", "DT5 output level queryable"},
|
||||
{70, 5, "dt5", "nonLogarithmicDimmingCurveSupported", "dt5_non_log_supported", "DT5 non-log curve supported"},
|
||||
{71, 5, "dt5", "physicalSelectionByOutputLossSupported", "dt5_output_loss_selection", "DT5 output-loss selection supported"},
|
||||
{72, 5, "dt5", "physicalSelectionSwitchSupported", "dt5_selection_switch", "DT5 selection switch supported"},
|
||||
{73, 5, "dt5", "outputFaultDetected", "dt5_output_fault", "DT5 output fault detected"},
|
||||
{74, 5, "dt5", "zeroToTenVoltOperation", "dt5_zero_to_ten", "DT5 0-10V operation"},
|
||||
{75, 5, "dt5", "internalPullUpOn", "dt5_pullup_on", "DT5 pull-up on"},
|
||||
{76, 5, "dt5", "nonLogarithmicDimmingCurveActive", "dt5_non_log_active", "DT5 non-log curve active"},
|
||||
{77, 6, "dt6", "ledPowerSupplyIntegrated", "dt6_power_supply_integrated", "DT6 power supply integrated"},
|
||||
{78, 6, "dt6", "ledModuleIntegrated", "dt6_module_integrated", "DT6 LED module integrated"},
|
||||
{79, 6, "dt6", "acSupplyPossible", "dt6_ac_supply_possible", "DT6 AC supply possible"},
|
||||
{80, 6, "dt6", "dcSupplyPossible", "dt6_dc_supply_possible", "DT6 DC supply possible"},
|
||||
{81, 6, "dt6", "pwmModePossible", "dt6_pwm_possible", "DT6 PWM possible"},
|
||||
{82, 6, "dt6", "amModePossible", "dt6_am_possible", "DT6 AM possible"},
|
||||
{83, 6, "dt6", "currentControlledOutputPossible", "dt6_current_control_possible", "DT6 current control possible"},
|
||||
{84, 6, "dt6", "highCurrentPulseModePossible", "dt6_high_current_possible", "DT6 high current possible"},
|
||||
{85, 6, "dt6", "canQueryShortCircuit", "dt6_can_short_circuit", "DT6 can query short circuit"},
|
||||
{86, 6, "dt6", "canQueryOpenCircuit", "dt6_can_open_circuit", "DT6 can query open circuit"},
|
||||
{87, 6, "dt6", "canQueryLoadDecrease", "dt6_can_load_decrease", "DT6 can query load decrease"},
|
||||
{88, 6, "dt6", "canQueryLoadIncrease", "dt6_can_load_increase", "DT6 can query load increase"},
|
||||
{89, 6, "dt6", "canQueryCurrentProtector", "dt6_can_current_protector", "DT6 can query current protector"},
|
||||
{90, 6, "dt6", "canQueryThermalShutdown", "dt6_can_thermal_shutdown", "DT6 can query thermal shutdown"},
|
||||
{91, 6, "dt6", "canQueryThermalOverloadReduction", "dt6_can_thermal_overload", "DT6 can query thermal overload"},
|
||||
{92, 6, "dt6", "shortCircuit", "dt6_short_circuit", "DT6 short circuit"},
|
||||
{93, 6, "dt6", "openCircuit", "dt6_open_circuit", "DT6 open circuit"},
|
||||
{94, 6, "dt6", "loadDecrease", "dt6_load_decrease", "DT6 load decrease"},
|
||||
{95, 6, "dt6", "loadIncrease", "dt6_load_increase", "DT6 load increase"},
|
||||
{96, 6, "dt6", "currentProtectorActive", "dt6_current_protector_active", "DT6 current protector active"},
|
||||
{97, 6, "dt6", "thermalShutdown", "dt6_thermal_shutdown", "DT6 thermal shutdown"},
|
||||
};
|
||||
|
||||
constexpr GeneratedDiagnosticBitSpec kGeneratedDiagnosticBitsTail[] = {
|
||||
{98, 6, "dt6", "thermalOverloadReduction", "dt6_thermal_overload", "DT6 thermal overload"},
|
||||
{99, 6, "dt6", "referenceMeasurementFailed", "dt6_reference_failed", "DT6 reference failed"},
|
||||
{100, 6, "dt6", "pwmModeActive", "dt6_pwm_active", "DT6 PWM active"},
|
||||
{101, 6, "dt6", "amModeActive", "dt6_am_active", "DT6 AM active"},
|
||||
{102, 6, "dt6", "currentControlledOutput", "dt6_current_controlled_output", "DT6 current controlled output"},
|
||||
{103, 6, "dt6", "highCurrentPulseModeActive", "dt6_high_current_active", "DT6 high current active"},
|
||||
{104, 6, "dt6", "nonLogarithmicDimmingCurveActive", "dt6_non_log_active", "DT6 non-log curve active"},
|
||||
{105, 8, "dt8_status", "xyOutOfRange", "dt8_xy_out_of_range", "DT8 xy out of range"},
|
||||
{106, 8, "dt8_status", "ctOutOfRange", "dt8_ct_out_of_range", "DT8 CT out of range"},
|
||||
{107, 8, "dt8_status", "autoCalibrationActive", "dt8_auto_cal_active", "DT8 auto calibration active"},
|
||||
{108, 8, "dt8_status", "autoCalibrationSuccess", "dt8_auto_cal_success", "DT8 auto calibration success"},
|
||||
{109, 8, "dt8_status", "xyActive", "dt8_xy_active", "DT8 xy active"},
|
||||
{110, 8, "dt8_status", "ctActive", "dt8_ct_active", "DT8 CT active"},
|
||||
{111, 8, "dt8_status", "primaryNActive", "dt8_primary_active", "DT8 primary-N active"},
|
||||
{112, 8, "dt8_status", "rgbwafActive", "dt8_rgbwaf_active", "DT8 RGBWAF active"},
|
||||
{113, 8, "dt8_status", "xyCapable", "dt8_xy_capable", "DT8 xy capable"},
|
||||
{114, 8, "dt8_status", "ctCapable", "dt8_ct_capable", "DT8 CT capable"},
|
||||
{115, 8, "dt8_status", "primaryNCapable", "dt8_primary_capable", "DT8 primary-N capable"},
|
||||
{116, 8, "dt8_status", "rgbwafCapable", "dt8_rgbwaf_capable", "DT8 RGBWAF capable"},
|
||||
{117, 6, "dt6", "physicalSelectionSupported", "dt6_physical_selection_supported", "DT6 physical selection supported"},
|
||||
{118, 6, "dt6", "currentProtectorEnabled", "dt6_current_protector_enabled", "DT6 current protector enabled"},
|
||||
{119, 1, "dt1", "controlGearFailure", "dt1_control_gear_failure", "DT1 control gear failure"},
|
||||
};
|
||||
|
||||
constexpr size_t kGeneratedDiagnosticBitCount =
|
||||
sizeof(kGeneratedDiagnosticBits) / sizeof(kGeneratedDiagnosticBits[0]) +
|
||||
sizeof(kGeneratedDiagnosticBitsTail) / sizeof(kGeneratedDiagnosticBitsTail[0]);
|
||||
constexpr size_t kGeneratedPointsPerShort = kGeneratedCoils.size() +
|
||||
kGeneratedDiscreteInputs.size() +
|
||||
kGeneratedHoldingRegisters.size() +
|
||||
kGeneratedInputRegisters.size() +
|
||||
kGeneratedDiagnosticBitCount;
|
||||
constexpr size_t kGeneratedPointCount = kShortAddressCount * kGeneratedPointsPerShort;
|
||||
|
||||
uint16_t baseForSpace(GatewayModbusSpace space) {
|
||||
switch (space) {
|
||||
case GatewayModbusSpace::kCoil:
|
||||
return kCoilBase;
|
||||
case GatewayModbusSpace::kDiscreteInput:
|
||||
return kDiscreteInputBase;
|
||||
case GatewayModbusSpace::kInputRegister:
|
||||
return kInputRegisterBase;
|
||||
case GatewayModbusSpace::kHoldingRegister:
|
||||
return kHoldingRegisterBase;
|
||||
}
|
||||
return kHoldingRegisterBase;
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusSpace> spaceForObjectType(BridgeObjectType type) {
|
||||
switch (type) {
|
||||
case BridgeObjectType::coil:
|
||||
return GatewayModbusSpace::kCoil;
|
||||
case BridgeObjectType::discreteInput:
|
||||
return GatewayModbusSpace::kDiscreteInput;
|
||||
case BridgeObjectType::inputRegister:
|
||||
return GatewayModbusSpace::kInputRegister;
|
||||
case BridgeObjectType::holdingRegister:
|
||||
return GatewayModbusSpace::kHoldingRegister;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayModbusAccess accessForSpace(GatewayModbusSpace space) {
|
||||
switch (space) {
|
||||
case GatewayModbusSpace::kCoil:
|
||||
case GatewayModbusSpace::kHoldingRegister:
|
||||
return GatewayModbusAccess::kReadWrite;
|
||||
case GatewayModbusSpace::kDiscreteInput:
|
||||
case GatewayModbusSpace::kInputRegister:
|
||||
return GatewayModbusAccess::kReadOnly;
|
||||
}
|
||||
return GatewayModbusAccess::kReadOnly;
|
||||
}
|
||||
|
||||
std::string generatedId(uint8_t short_address, const char* suffix) {
|
||||
char buffer[64];
|
||||
std::snprintf(buffer, sizeof(buffer), "dali_%02u_%s", static_cast<unsigned>(short_address),
|
||||
suffix == nullptr ? "point" : suffix);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::string generatedName(uint8_t short_address, const char* name) {
|
||||
char buffer[96];
|
||||
std::snprintf(buffer, sizeof(buffer), "DALI %u %s", static_cast<unsigned>(short_address),
|
||||
name == nullptr ? "point" : name);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
const char* literalOrEmpty(const char* value) {
|
||||
return value == nullptr ? "" : value;
|
||||
}
|
||||
|
||||
PointKey keyForPoint(const GatewayModbusPoint& point) {
|
||||
return PointKey{point.space, point.address};
|
||||
}
|
||||
|
||||
bool pointLess(const GatewayModbusPoint& lhs, const GatewayModbusPoint& rhs) {
|
||||
return keyForPoint(lhs) < keyForPoint(rhs);
|
||||
}
|
||||
|
||||
bool pointKeyEqual(const GatewayModbusPoint& lhs, const GatewayModbusPoint& rhs) {
|
||||
return lhs.space == rhs.space && lhs.address == rhs.address;
|
||||
}
|
||||
|
||||
bool pointKeyEqual(const GatewayModbusPoint& point, const PointKey& key) {
|
||||
return point.space == key.space && point.address == key.address;
|
||||
}
|
||||
|
||||
std::vector<GatewayModbusPoint>::const_iterator findStoredPoint(
|
||||
const std::vector<GatewayModbusPoint>& points, PointKey key) {
|
||||
const auto found = std::lower_bound(
|
||||
points.begin(), points.end(), key,
|
||||
[](const GatewayModbusPoint& point, const PointKey& value) {
|
||||
return keyForPoint(point) < value;
|
||||
});
|
||||
if (found == points.end() || !pointKeyEqual(*found, key)) {
|
||||
return points.end();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
GatewayModbusPoint makeGeneratedPoint(uint8_t short_address,
|
||||
const GeneratedPointSpec& spec) {
|
||||
const uint16_t address = static_cast<uint16_t>(baseForSpace(spec.space) +
|
||||
short_address * kShortStride + spec.offset);
|
||||
GatewayModbusPoint point;
|
||||
point.space = spec.space;
|
||||
point.access = spec.access;
|
||||
point.address = address;
|
||||
point.generated = true;
|
||||
point.generated_kind = spec.kind;
|
||||
point.generated_suffix = spec.suffix == nullptr ? "point" : spec.suffix;
|
||||
point.generated_name = spec.name == nullptr ? "point" : spec.name;
|
||||
point.short_address = short_address;
|
||||
return point;
|
||||
}
|
||||
|
||||
GatewayModbusPoint makeGeneratedDiagnosticPoint(uint8_t short_address,
|
||||
const GeneratedDiagnosticBitSpec& spec) {
|
||||
const uint16_t address = static_cast<uint16_t>(kDiagnosticDiscreteInputBase +
|
||||
short_address * kDiagnosticStride + spec.offset);
|
||||
GatewayModbusPoint point;
|
||||
point.space = GatewayModbusSpace::kDiscreteInput;
|
||||
point.access = GatewayModbusAccess::kReadOnly;
|
||||
point.address = address;
|
||||
point.generated = true;
|
||||
point.generated_kind = GatewayModbusGeneratedKind::kShortDiagnosticBit;
|
||||
point.generated_suffix = spec.suffix == nullptr ? "point" : spec.suffix;
|
||||
point.generated_name = spec.name == nullptr ? "point" : spec.name;
|
||||
point.short_address = short_address;
|
||||
point.diagnostic_snapshot = literalOrEmpty(spec.snapshot);
|
||||
point.diagnostic_bool = literalOrEmpty(spec.bool_key);
|
||||
point.diagnostic_device_type = spec.device_type;
|
||||
return point;
|
||||
}
|
||||
|
||||
void appendIfNotOverridden(const std::vector<GatewayModbusPoint>& stored_points,
|
||||
std::vector<GatewayModbusPoint>* points,
|
||||
GatewayModbusPoint point) {
|
||||
if (points == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (findStoredPoint(stored_points, keyForPoint(point)) != stored_points.end()) {
|
||||
return;
|
||||
}
|
||||
points->push_back(std::move(point));
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusPoint> generatedPointForAddress(GatewayModbusSpace space,
|
||||
uint16_t address) {
|
||||
if (space == GatewayModbusSpace::kDiscreteInput &&
|
||||
address >= kDiagnosticDiscreteInputBase) {
|
||||
const uint16_t relative = static_cast<uint16_t>(address - kDiagnosticDiscreteInputBase);
|
||||
const uint8_t short_address = static_cast<uint8_t>(relative / kDiagnosticStride);
|
||||
const uint16_t offset = static_cast<uint16_t>(relative % kDiagnosticStride);
|
||||
if (short_address >= kShortAddressCount) {
|
||||
return std::nullopt;
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBits) {
|
||||
if (spec.offset == offset) {
|
||||
return makeGeneratedDiagnosticPoint(short_address, spec);
|
||||
}
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBitsTail) {
|
||||
if (spec.offset == offset) {
|
||||
return makeGeneratedDiagnosticPoint(short_address, spec);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const uint16_t base = baseForSpace(space);
|
||||
if (address < base) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint16_t relative = static_cast<uint16_t>(address - base);
|
||||
const uint8_t short_address = static_cast<uint8_t>(relative / kShortStride);
|
||||
const uint16_t offset = static_cast<uint16_t>(relative % kShortStride);
|
||||
if (short_address >= kShortAddressCount) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto find_regular_point = [short_address, offset](const auto& specs)
|
||||
-> std::optional<GatewayModbusPoint> {
|
||||
for (const auto& spec : specs) {
|
||||
if (spec.offset == offset) {
|
||||
return makeGeneratedPoint(short_address, spec);
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
switch (space) {
|
||||
case GatewayModbusSpace::kCoil:
|
||||
return find_regular_point(kGeneratedCoils);
|
||||
case GatewayModbusSpace::kDiscreteInput:
|
||||
return find_regular_point(kGeneratedDiscreteInputs);
|
||||
case GatewayModbusSpace::kHoldingRegister:
|
||||
return find_regular_point(kGeneratedHoldingRegisters);
|
||||
case GatewayModbusSpace::kInputRegister:
|
||||
return find_regular_point(kGeneratedInputRegisters);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GatewayModbusPointBinding toBinding(const GatewayModbusPoint& point) {
|
||||
GatewayModbusPointBinding binding;
|
||||
binding.model_id = point.model_id;
|
||||
binding.space = point.space;
|
||||
binding.address = point.address;
|
||||
if (point.generated) {
|
||||
const auto short_address = static_cast<uint8_t>(point.short_address < 0 ? 0 : point.short_address);
|
||||
binding.id = generatedId(short_address, point.generated_suffix);
|
||||
binding.name = generatedName(short_address, point.generated_name);
|
||||
} else {
|
||||
binding.id = point.id;
|
||||
binding.name = point.name;
|
||||
}
|
||||
binding.generated = point.generated;
|
||||
binding.generated_kind = point.generated_kind;
|
||||
binding.short_address = point.short_address;
|
||||
binding.access = point.access;
|
||||
binding.bit_index = point.bit_index;
|
||||
binding.diagnostic_snapshot = literalOrEmpty(point.diagnostic_snapshot);
|
||||
binding.diagnostic_bool = literalOrEmpty(point.diagnostic_bool);
|
||||
binding.diagnostic_device_type = point.diagnostic_device_type;
|
||||
return binding;
|
||||
}
|
||||
|
||||
int clampedInt(const DaliValue::Object& json, const std::string& key, int fallback,
|
||||
int min_value, int max_value) {
|
||||
const int value = getObjectInt(json, key).value_or(fallback);
|
||||
return std::clamp(value, min_value, max_value);
|
||||
}
|
||||
|
||||
uint32_t clampedU32(const DaliValue::Object& json, const std::string& key, uint32_t fallback,
|
||||
uint32_t min_value, uint32_t max_value) {
|
||||
const int value = getObjectInt(json, key).value_or(static_cast<int>(fallback));
|
||||
return static_cast<uint32_t>(std::clamp(value, static_cast<int>(min_value),
|
||||
static_cast<int>(max_value)));
|
||||
}
|
||||
|
||||
size_t clampedSize(const DaliValue::Object& json, const std::string& key, size_t fallback,
|
||||
size_t min_value, size_t max_value) {
|
||||
const int value = getObjectInt(json, key).value_or(static_cast<int>(fallback));
|
||||
return static_cast<size_t>(std::clamp(value, static_cast<int>(min_value),
|
||||
static_cast<int>(max_value)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool GatewayModbusTransportIsTcp(const std::string& transport) {
|
||||
return transport.empty() || transport == "tcp" || transport == "tcp-server";
|
||||
}
|
||||
|
||||
bool GatewayModbusTransportIsRtu(const std::string& transport) {
|
||||
return transport == "rtu" || transport == "rtu-server" || transport == "modbus-rtu";
|
||||
}
|
||||
|
||||
bool GatewayModbusTransportIsAscii(const std::string& transport) {
|
||||
return transport == "ascii" || transport == "ascii-server" || transport == "modbus-ascii";
|
||||
}
|
||||
|
||||
bool GatewayModbusTransportIsSerial(const std::string& transport) {
|
||||
return GatewayModbusTransportIsRtu(transport) || GatewayModbusTransportIsAscii(transport);
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusConfig> GatewayModbusConfigFromValue(const DaliValue* value) {
|
||||
if (value == nullptr || value->asObject() == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto& json = *value->asObject();
|
||||
GatewayModbusConfig config;
|
||||
config.transport = getObjectString(json, "transport").value_or("tcp-server");
|
||||
config.host = getObjectString(json, "host").value_or("");
|
||||
config.port = static_cast<uint16_t>(
|
||||
getObjectInt(json, "port").value_or(kGatewayModbusDefaultTcpPort));
|
||||
config.unit_id = static_cast<uint8_t>(getObjectInt(json, "unitID").value_or(
|
||||
getObjectInt(json, "unitId").value_or(getObjectInt(json, "unit_id").value_or(1))));
|
||||
if (const auto* serial_value = getObjectValue(json, "serial")) {
|
||||
if (const auto* serial = serial_value->asObject()) {
|
||||
config.serial.uart_port = clampedInt(*serial, "uartPort", config.serial.uart_port, -1, 2);
|
||||
config.serial.tx_pin = clampedInt(*serial, "txPin", config.serial.tx_pin, -1, 48);
|
||||
config.serial.rx_pin = clampedInt(*serial, "rxPin", config.serial.rx_pin, -1, 48);
|
||||
config.serial.baudrate = clampedU32(*serial, "baudrate", config.serial.baudrate,
|
||||
1200, 921600);
|
||||
config.serial.data_bits = clampedInt(*serial, "dataBits", config.serial.data_bits, 7, 8);
|
||||
config.serial.parity = getObjectString(*serial, "parity").value_or(config.serial.parity);
|
||||
config.serial.stop_bits = clampedInt(*serial, "stopBits", config.serial.stop_bits, 1, 2);
|
||||
config.serial.rx_buffer_size = clampedSize(*serial, "rxBufferBytes",
|
||||
config.serial.rx_buffer_size, 128, 4096);
|
||||
config.serial.tx_buffer_size = clampedSize(*serial, "txBufferBytes",
|
||||
config.serial.tx_buffer_size, 0, 4096);
|
||||
config.serial.response_timeout_ms = clampedU32(*serial, "responseTimeoutMs",
|
||||
config.serial.response_timeout_ms, 1, 1000);
|
||||
config.serial.inter_frame_gap_us = clampedU32(*serial, "interFrameGapUs",
|
||||
config.serial.inter_frame_gap_us, 1000,
|
||||
100000);
|
||||
if (const auto* rs485_value = getObjectValue(*serial, "rs485")) {
|
||||
if (const auto* rs485 = rs485_value->asObject()) {
|
||||
config.serial.rs485.enabled = getObjectBool(*rs485, "enabled")
|
||||
.value_or(config.serial.rs485.enabled);
|
||||
config.serial.rs485.de_pin = clampedInt(*rs485, "dePin",
|
||||
config.serial.rs485.de_pin, -1, 48);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
DaliValue GatewayModbusConfigToValue(const GatewayModbusConfig& config) {
|
||||
DaliValue::Object out;
|
||||
out["transport"] = config.transport;
|
||||
out["host"] = config.host;
|
||||
out["port"] = static_cast<int>(config.port);
|
||||
out["unitID"] = static_cast<int>(config.unit_id);
|
||||
DaliValue::Object serial;
|
||||
serial["uartPort"] = config.serial.uart_port;
|
||||
serial["txPin"] = config.serial.tx_pin;
|
||||
serial["rxPin"] = config.serial.rx_pin;
|
||||
serial["baudrate"] = static_cast<int>(config.serial.baudrate);
|
||||
serial["dataBits"] = config.serial.data_bits;
|
||||
serial["parity"] = config.serial.parity;
|
||||
serial["stopBits"] = config.serial.stop_bits;
|
||||
serial["rxBufferBytes"] = static_cast<int>(config.serial.rx_buffer_size);
|
||||
serial["txBufferBytes"] = static_cast<int>(config.serial.tx_buffer_size);
|
||||
serial["responseTimeoutMs"] = static_cast<int>(config.serial.response_timeout_ms);
|
||||
serial["interFrameGapUs"] = static_cast<int>(config.serial.inter_frame_gap_us);
|
||||
DaliValue::Object rs485;
|
||||
rs485["enabled"] = config.serial.rs485.enabled;
|
||||
rs485["dePin"] = config.serial.rs485.de_pin;
|
||||
serial["rs485"] = std::move(rs485);
|
||||
out["serial"] = std::move(serial);
|
||||
return DaliValue(std::move(out));
|
||||
}
|
||||
|
||||
const char* GatewayModbusSpaceToString(GatewayModbusSpace space) {
|
||||
switch (space) {
|
||||
case GatewayModbusSpace::kCoil:
|
||||
return "coil";
|
||||
case GatewayModbusSpace::kDiscreteInput:
|
||||
return "discrete_input";
|
||||
case GatewayModbusSpace::kHoldingRegister:
|
||||
return "holding_register";
|
||||
case GatewayModbusSpace::kInputRegister:
|
||||
return "input_register";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* GatewayModbusAccessToString(GatewayModbusAccess access) {
|
||||
switch (access) {
|
||||
case GatewayModbusAccess::kReadOnly:
|
||||
return "read_only";
|
||||
case GatewayModbusAccess::kWriteOnly:
|
||||
return "write_only";
|
||||
case GatewayModbusAccess::kReadWrite:
|
||||
return "read_write";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const char* GatewayModbusGeneratedKindToString(GatewayModbusGeneratedKind kind) {
|
||||
switch (kind) {
|
||||
case GatewayModbusGeneratedKind::kShortOn:
|
||||
return "short_on";
|
||||
case GatewayModbusGeneratedKind::kShortOff:
|
||||
return "short_off";
|
||||
case GatewayModbusGeneratedKind::kShortRecallMax:
|
||||
return "short_recall_max";
|
||||
case GatewayModbusGeneratedKind::kShortRecallMin:
|
||||
return "short_recall_min";
|
||||
case GatewayModbusGeneratedKind::kShortDiscovered:
|
||||
return "short_discovered";
|
||||
case GatewayModbusGeneratedKind::kShortOnline:
|
||||
return "short_online";
|
||||
case GatewayModbusGeneratedKind::kShortSupportsDt1:
|
||||
return "short_supports_dt1";
|
||||
case GatewayModbusGeneratedKind::kShortSupportsDt4:
|
||||
return "short_supports_dt4";
|
||||
case GatewayModbusGeneratedKind::kShortSupportsDt5:
|
||||
return "short_supports_dt5";
|
||||
case GatewayModbusGeneratedKind::kShortSupportsDt6:
|
||||
return "short_supports_dt6";
|
||||
case GatewayModbusGeneratedKind::kShortSupportsDt8:
|
||||
return "short_supports_dt8";
|
||||
case GatewayModbusGeneratedKind::kShortGroupMaskKnown:
|
||||
return "short_group_mask_known";
|
||||
case GatewayModbusGeneratedKind::kShortActualLevelKnown:
|
||||
return "short_actual_level_known";
|
||||
case GatewayModbusGeneratedKind::kShortSceneKnown:
|
||||
return "short_scene_known";
|
||||
case GatewayModbusGeneratedKind::kShortSettingsKnown:
|
||||
return "short_settings_known";
|
||||
case GatewayModbusGeneratedKind::kShortControlGearPresent:
|
||||
return "short_control_gear_present";
|
||||
case GatewayModbusGeneratedKind::kShortLampFailure:
|
||||
return "short_lamp_failure";
|
||||
case GatewayModbusGeneratedKind::kShortLampPowerOn:
|
||||
return "short_lamp_power_on";
|
||||
case GatewayModbusGeneratedKind::kShortLimitError:
|
||||
return "short_limit_error";
|
||||
case GatewayModbusGeneratedKind::kShortFadingCompleted:
|
||||
return "short_fading_completed";
|
||||
case GatewayModbusGeneratedKind::kShortResetState:
|
||||
return "short_reset_state";
|
||||
case GatewayModbusGeneratedKind::kShortMissingShortAddress:
|
||||
return "short_missing_short_address";
|
||||
case GatewayModbusGeneratedKind::kShortPowerSupplyFault:
|
||||
return "short_power_supply_fault";
|
||||
case GatewayModbusGeneratedKind::kShortBrightness:
|
||||
return "short_brightness";
|
||||
case GatewayModbusGeneratedKind::kShortColorTemperature:
|
||||
return "short_color_temperature";
|
||||
case GatewayModbusGeneratedKind::kShortGroupMask:
|
||||
return "short_group_mask";
|
||||
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
|
||||
return "short_power_on_level";
|
||||
case GatewayModbusGeneratedKind::kShortSystemFailureLevel:
|
||||
return "short_system_failure_level";
|
||||
case GatewayModbusGeneratedKind::kShortMinLevel:
|
||||
return "short_min_level";
|
||||
case GatewayModbusGeneratedKind::kShortMaxLevel:
|
||||
return "short_max_level";
|
||||
case GatewayModbusGeneratedKind::kShortFadeTime:
|
||||
return "short_fade_time";
|
||||
case GatewayModbusGeneratedKind::kShortFadeRate:
|
||||
return "short_fade_rate";
|
||||
case GatewayModbusGeneratedKind::kShortInventoryState:
|
||||
return "short_inventory_state";
|
||||
case GatewayModbusGeneratedKind::kShortPrimaryType:
|
||||
return "short_primary_type";
|
||||
case GatewayModbusGeneratedKind::kShortTypeMask:
|
||||
return "short_type_mask";
|
||||
case GatewayModbusGeneratedKind::kShortActualLevel:
|
||||
return "short_actual_level";
|
||||
case GatewayModbusGeneratedKind::kShortSceneId:
|
||||
return "short_scene_id";
|
||||
case GatewayModbusGeneratedKind::kShortRawStatus:
|
||||
return "short_raw_status";
|
||||
case GatewayModbusGeneratedKind::kShortDiagnosticBit:
|
||||
return "short_diagnostic_bit";
|
||||
case GatewayModbusGeneratedKind::kNone:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
int GatewayModbusHumanAddressFromWire(GatewayModbusSpace space, uint16_t zero_based_address) {
|
||||
return baseForSpace(space) + static_cast<int>(zero_based_address);
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusSpace> GatewayModbusReadSpaceForFunction(uint8_t function_code) {
|
||||
switch (function_code) {
|
||||
case 0x01:
|
||||
return GatewayModbusSpace::kCoil;
|
||||
case 0x02:
|
||||
return GatewayModbusSpace::kDiscreteInput;
|
||||
case 0x03:
|
||||
return GatewayModbusSpace::kHoldingRegister;
|
||||
case 0x04:
|
||||
return GatewayModbusSpace::kInputRegister;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusSpace> GatewayModbusWriteSpaceForFunction(uint8_t function_code) {
|
||||
switch (function_code) {
|
||||
case 0x05:
|
||||
case 0x0F:
|
||||
return GatewayModbusSpace::kCoil;
|
||||
case 0x06:
|
||||
case 0x10:
|
||||
return GatewayModbusSpace::kHoldingRegister;
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayModbusBridge::GatewayModbusBridge(DaliBridgeEngine& engine) : engine_(engine) {
|
||||
rebuildMap();
|
||||
}
|
||||
|
||||
void GatewayModbusBridge::setConfig(const GatewayModbusConfig& config) { config_ = config; }
|
||||
|
||||
const GatewayModbusConfig& GatewayModbusBridge::config() const { return config_; }
|
||||
|
||||
void GatewayModbusBridge::rebuildMap() {
|
||||
auto models = engine_.listModels();
|
||||
points_.clear();
|
||||
points_.reserve(models.size());
|
||||
|
||||
for (const auto& model : models) {
|
||||
if (model.protocol != BridgeProtocolKind::modbus || !model.external.registerAddress.has_value()) {
|
||||
continue;
|
||||
}
|
||||
const auto space = spaceForObjectType(model.external.objectType);
|
||||
if (!space.has_value()) {
|
||||
continue;
|
||||
}
|
||||
GatewayModbusPoint point;
|
||||
point.space = space.value();
|
||||
point.access = accessForSpace(space.value());
|
||||
point.address = static_cast<uint16_t>(model.external.registerAddress.value());
|
||||
point.id = model.id;
|
||||
point.name = model.displayName();
|
||||
point.generated = false;
|
||||
point.generated_kind = GatewayModbusGeneratedKind::kNone;
|
||||
point.model_id = model.id;
|
||||
point.operation = model.operation;
|
||||
point.bit_index = model.external.bitIndex;
|
||||
if (model.dali.kind == BridgeDaliTargetKind::shortAddress && model.dali.shortAddress.has_value()) {
|
||||
point.short_address = model.dali.shortAddress.value();
|
||||
}
|
||||
points_.push_back(std::move(point));
|
||||
}
|
||||
|
||||
std::stable_sort(points_.begin(), points_.end(), pointLess);
|
||||
auto write = points_.begin();
|
||||
for (auto read = points_.begin(); read != points_.end();) {
|
||||
auto next = read + 1;
|
||||
while (next != points_.end() && pointKeyEqual(*read, *next)) {
|
||||
++next;
|
||||
}
|
||||
if (write != next - 1) {
|
||||
*write = std::move(*(next - 1));
|
||||
}
|
||||
++write;
|
||||
read = next;
|
||||
}
|
||||
points_.erase(write, points_.end());
|
||||
}
|
||||
|
||||
std::optional<GatewayModbusPoint> GatewayModbusBridge::findPoint(GatewayModbusSpace space,
|
||||
uint16_t address) const {
|
||||
const PointKey key{space, address};
|
||||
const auto found = findStoredPoint(points_, key);
|
||||
if (found != points_.end()) {
|
||||
return *found;
|
||||
}
|
||||
return generatedPointForAddress(space, address);
|
||||
}
|
||||
|
||||
GatewayModbusPointBinding GatewayModbusBridge::describePoint(
|
||||
const GatewayModbusPoint& point) const {
|
||||
return toBinding(point);
|
||||
}
|
||||
|
||||
void GatewayModbusBridge::appendGeneratedPointsForShortAddress(
|
||||
uint8_t short_address, std::vector<GatewayModbusPoint>* points) const {
|
||||
if (points == nullptr || short_address >= kShortAddressCount) {
|
||||
return;
|
||||
}
|
||||
for (const auto& spec : kGeneratedCoils) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiscreteInputs) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedHoldingRegisters) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedInputRegisters) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBits) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedDiagnosticPoint(short_address, spec));
|
||||
}
|
||||
for (const auto& spec : kGeneratedDiagnosticBitsTail) {
|
||||
appendIfNotOverridden(points_, points, makeGeneratedDiagnosticPoint(short_address, spec));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GatewayModbusPointBinding> GatewayModbusBridge::describePoints() const {
|
||||
std::vector<GatewayModbusPointBinding> bindings;
|
||||
bindings.reserve(kGeneratedPointCount + points_.size());
|
||||
std::vector<GatewayModbusPoint> generated_points;
|
||||
generated_points.reserve(kGeneratedPointsPerShort);
|
||||
for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) {
|
||||
generated_points.clear();
|
||||
appendGeneratedPointsForShortAddress(short_address, &generated_points);
|
||||
for (const auto& point : generated_points) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
}
|
||||
for (const auto& point : points_) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
std::sort(bindings.begin(), bindings.end(), [](const auto& lhs, const auto& rhs) {
|
||||
if (lhs.space != rhs.space) {
|
||||
return static_cast<uint8_t>(lhs.space) < static_cast<uint8_t>(rhs.space);
|
||||
}
|
||||
return lhs.address < rhs.address;
|
||||
});
|
||||
return bindings;
|
||||
}
|
||||
|
||||
std::vector<GatewayModbusPointBinding> GatewayModbusBridge::describeHoldingRegisters() const {
|
||||
std::vector<GatewayModbusPointBinding> bindings;
|
||||
std::vector<GatewayModbusPoint> generated_points;
|
||||
generated_points.reserve(kGeneratedPointsPerShort);
|
||||
for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) {
|
||||
generated_points.clear();
|
||||
appendGeneratedPointsForShortAddress(short_address, &generated_points);
|
||||
for (const auto& point : generated_points) {
|
||||
if (point.space == GatewayModbusSpace::kHoldingRegister) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& point : points_) {
|
||||
if (point.space == GatewayModbusSpace::kHoldingRegister) {
|
||||
bindings.push_back(toBinding(point));
|
||||
}
|
||||
}
|
||||
std::sort(bindings.begin(), bindings.end(), [](const auto& lhs, const auto& rhs) {
|
||||
return lhs.address < rhs.address;
|
||||
});
|
||||
return bindings;
|
||||
}
|
||||
|
||||
const std::vector<GatewayModbusPoint>& GatewayModbusBridge::points() const {
|
||||
return points_;
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayModbusBridge::readModelPoint(const GatewayModbusPoint& point) const {
|
||||
return executeModelPoint(point, std::nullopt);
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayModbusBridge::writeRegisterPoint(const GatewayModbusPoint& point,
|
||||
uint16_t value) const {
|
||||
return executeModelPoint(point, static_cast<int>(value));
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayModbusBridge::writeCoilPoint(const GatewayModbusPoint& point,
|
||||
bool value) const {
|
||||
return executeModelPoint(point, value ? 1 : 0);
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayModbusBridge::executeModelPoint(const GatewayModbusPoint& point,
|
||||
std::optional<int> value) const {
|
||||
DaliBridgeRequest request;
|
||||
request.sequence = "modbus-" + std::to_string(point.address);
|
||||
request.modelID = point.model_id;
|
||||
if (value.has_value()) {
|
||||
request.value = value.value();
|
||||
}
|
||||
|
||||
if (point.model_id.empty()) {
|
||||
DaliBridgeResult result;
|
||||
result.sequence = request.sequence;
|
||||
result.error = "generated Modbus point requires gateway handler";
|
||||
return result;
|
||||
}
|
||||
return engine_.execute(request);
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_network.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_domain esp_event esp_http_server esp_netif esp_wifi freertos gateway_bridge gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
REQUIRES dali_domain esp_driver_gpio esp_driver_spi esp_eth esp_event esp_http_server esp_hw_support esp_netif esp_wifi freertos gateway_bridge gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_eth.h"
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_eth_netif_glue.h"
|
||||
#include "esp_eth_phy.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_now.h"
|
||||
@@ -26,6 +30,8 @@ struct DaliRawFrame;
|
||||
|
||||
struct GatewayNetworkServiceConfig {
|
||||
bool wifi_enabled{true};
|
||||
bool ethernet_enabled{false};
|
||||
bool ethernet_ignore_init_failure{false};
|
||||
bool espnow_setup_enabled{true};
|
||||
bool espnow_setup_startup_enabled{false};
|
||||
bool smartconfig_enabled{true};
|
||||
@@ -35,12 +41,25 @@ struct GatewayNetworkServiceConfig {
|
||||
bool udp_enabled{true};
|
||||
uint16_t http_port{80};
|
||||
uint16_t udp_port{2020};
|
||||
int ethernet_spi_host{1};
|
||||
int ethernet_spi_sclk_gpio{14};
|
||||
int ethernet_spi_mosi_gpio{13};
|
||||
int ethernet_spi_miso_gpio{12};
|
||||
int ethernet_spi_cs_gpio{15};
|
||||
int ethernet_spi_int_gpio{4};
|
||||
uint32_t ethernet_poll_period_ms{0};
|
||||
uint8_t ethernet_spi_clock_mhz{36};
|
||||
int ethernet_phy_reset_gpio{5};
|
||||
int ethernet_phy_addr{1};
|
||||
uint32_t ethernet_rx_task_stack_size{3072};
|
||||
int status_led_gpio{-1};
|
||||
bool status_led_active_high{true};
|
||||
int boot_button_gpio{-1};
|
||||
bool boot_button_active_low{true};
|
||||
int setup_ap_button_gpio{-1};
|
||||
bool setup_ap_button_active_low{true};
|
||||
uint32_t boot_button_long_press_ms{3000};
|
||||
uint32_t boot_button_task_stack_size{2048};
|
||||
uint32_t boot_button_task_stack_size{8192};
|
||||
UBaseType_t boot_button_task_priority{2};
|
||||
uint32_t udp_task_stack_size{4096};
|
||||
UBaseType_t udp_task_priority{4};
|
||||
@@ -66,12 +85,17 @@ class GatewayNetworkService {
|
||||
static esp_err_t HandleLedOnGet(httpd_req_t* req);
|
||||
static esp_err_t HandleLedOffGet(httpd_req_t* req);
|
||||
static esp_err_t HandleJqJsGet(httpd_req_t* req);
|
||||
static void HandleEthernetEvent(void* arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data);
|
||||
static void HandleWifiEvent(void* arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data);
|
||||
static void HandleEspNowReceive(const esp_now_recv_info_t* info, const uint8_t* data,
|
||||
int data_len);
|
||||
|
||||
esp_err_t ensureNetworkStack();
|
||||
esp_err_t startEthernet();
|
||||
esp_err_t probeEthernetStartup();
|
||||
void stopEthernet();
|
||||
esp_err_t startWifi();
|
||||
esp_err_t startSetupAp();
|
||||
esp_err_t startSmartconfig();
|
||||
@@ -89,6 +113,7 @@ class GatewayNetworkService {
|
||||
void bootButtonTaskLoop();
|
||||
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);
|
||||
void handleWifiEvent(esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
void handleEspNowReceive(const esp_now_recv_info_t* info, const uint8_t* data, int data_len);
|
||||
void handleSetupUartFrame(int setup_id, const std::vector<uint8_t>& frame);
|
||||
@@ -106,8 +131,15 @@ class GatewayNetworkService {
|
||||
GatewayBridgeService* bridge_service_{nullptr};
|
||||
bool started_{false};
|
||||
httpd_handle_t http_server_{nullptr};
|
||||
esp_netif_t* eth_netif_{nullptr};
|
||||
esp_netif_t* wifi_sta_netif_{nullptr};
|
||||
esp_netif_t* wifi_ap_netif_{nullptr};
|
||||
esp_eth_handle_t eth_handle_{nullptr};
|
||||
esp_eth_mac_t* eth_mac_{nullptr};
|
||||
esp_eth_phy_t* eth_phy_{nullptr};
|
||||
esp_eth_netif_glue_handle_t eth_glue_{nullptr};
|
||||
bool ethernet_started_{false};
|
||||
bool ethernet_event_handlers_registered_{false};
|
||||
bool wifi_started_{false};
|
||||
bool wifi_event_handlers_registered_{false};
|
||||
bool setup_ap_started_{false};
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_eth_mac_spi.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_ip_addr.h"
|
||||
#include "esp_smartconfig.h"
|
||||
@@ -268,6 +272,19 @@ esp_err_t GatewayNetworkService::start() {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (config_.ethernet_enabled) {
|
||||
err = startEthernet();
|
||||
if (err != ESP_OK) {
|
||||
if (config_.ethernet_ignore_init_failure) {
|
||||
ESP_LOGW(kTag, "Ethernet init failed; Ethernet is disabled for this boot: %s",
|
||||
esp_err_to_name(err));
|
||||
config_.ethernet_enabled = false;
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.espnow_setup_startup_enabled) {
|
||||
err = startSetupAp();
|
||||
if (err != ESP_OK) {
|
||||
@@ -320,7 +337,8 @@ esp_err_t GatewayNetworkService::start() {
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "network service started http=%d udp=%d", config_.http_enabled,
|
||||
ESP_LOGI(kTag, "network service started eth=%d wifi=%d http=%d udp=%d",
|
||||
config_.ethernet_enabled, config_.wifi_enabled, config_.http_enabled,
|
||||
config_.udp_enabled);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -341,6 +359,234 @@ esp_err_t GatewayNetworkService::ensureNetworkStack() {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startEthernet() {
|
||||
if (ethernet_started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
if (eth_netif_ == nullptr) {
|
||||
esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH();
|
||||
eth_netif_ = esp_netif_new(&netif_config);
|
||||
if (eth_netif_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create Ethernet netif");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ethernet_event_handlers_registered_) {
|
||||
esp_err_t err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
|
||||
&GatewayNetworkService::HandleEthernetEvent, this);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to register Ethernet event handler: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP,
|
||||
&GatewayNetworkService::HandleEthernetEvent, this);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to register Ethernet IP event handler: %s", esp_err_to_name(err));
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
||||
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
ethernet_event_handlers_registered_ = true;
|
||||
}
|
||||
|
||||
if (config_.ethernet_spi_int_gpio >= 0) {
|
||||
esp_err_t err = gpio_install_isr_service(0);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to install GPIO ISR service for Ethernet: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
const auto spi_host = static_cast<spi_host_device_t>(config_.ethernet_spi_host);
|
||||
spi_bus_config_t bus_config = {};
|
||||
bus_config.miso_io_num = config_.ethernet_spi_miso_gpio;
|
||||
bus_config.mosi_io_num = config_.ethernet_spi_mosi_gpio;
|
||||
bus_config.sclk_io_num = config_.ethernet_spi_sclk_gpio;
|
||||
bus_config.quadwp_io_num = -1;
|
||||
bus_config.quadhd_io_num = -1;
|
||||
|
||||
esp_err_t err = spi_bus_initialize(spi_host, &bus_config, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to initialize Ethernet SPI host %d: %s", config_.ethernet_spi_host,
|
||||
esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
spi_device_interface_config_t spi_device_config = {};
|
||||
spi_device_config.mode = 0;
|
||||
spi_device_config.clock_speed_hz = static_cast<int>(config_.ethernet_spi_clock_mhz) * 1000 * 1000;
|
||||
spi_device_config.spics_io_num = config_.ethernet_spi_cs_gpio;
|
||||
spi_device_config.queue_size = 20;
|
||||
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
mac_config.rx_task_stack_size = config_.ethernet_rx_task_stack_size;
|
||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||
phy_config.phy_addr = config_.ethernet_phy_addr;
|
||||
phy_config.reset_gpio_num = config_.ethernet_phy_reset_gpio;
|
||||
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_host, &spi_device_config);
|
||||
w5500_config.int_gpio_num = config_.ethernet_spi_int_gpio;
|
||||
w5500_config.poll_period_ms = config_.ethernet_poll_period_ms;
|
||||
if (w5500_config.int_gpio_num < 0 && w5500_config.poll_period_ms == 0) {
|
||||
w5500_config.poll_period_ms = 100;
|
||||
}
|
||||
|
||||
eth_mac_ = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
if (eth_mac_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create W5500 Ethernet MAC");
|
||||
stopEthernet();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
eth_phy_ = esp_eth_phy_new_w5500(&phy_config);
|
||||
if (eth_phy_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create W5500 Ethernet PHY");
|
||||
stopEthernet();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(eth_mac_, eth_phy_);
|
||||
err = esp_eth_driver_install(ð_config, ð_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to install Ethernet driver: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
uint8_t eth_mac[6] = {};
|
||||
err = esp_read_mac(eth_mac, ESP_MAC_ETH);
|
||||
if (err == ESP_OK) {
|
||||
err = esp_eth_ioctl(eth_handle_, ETH_CMD_S_MAC_ADDR, eth_mac);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to set Ethernet MAC address: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
EthernetInfo info;
|
||||
info.mac = MacToHex(eth_mac);
|
||||
runtime_.setEthernetInfo(std::move(info));
|
||||
} else {
|
||||
ESP_LOGW(kTag, "failed to read Ethernet MAC address: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
eth_glue_ = esp_eth_new_netif_glue(eth_handle_);
|
||||
if (eth_glue_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create Ethernet netif glue");
|
||||
stopEthernet();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
err = esp_netif_attach(eth_netif_, eth_glue_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to attach Ethernet netif: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
err = esp_eth_start(eth_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to start Ethernet: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
ethernet_started_ = true;
|
||||
err = probeEthernetStartup();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "Ethernet startup probe failed: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag,
|
||||
"Ethernet W5500 started spi_host=%d sclk=%d mosi=%d miso=%d cs=%d int=%d reset=%d",
|
||||
config_.ethernet_spi_host, config_.ethernet_spi_sclk_gpio,
|
||||
config_.ethernet_spi_mosi_gpio, config_.ethernet_spi_miso_gpio,
|
||||
config_.ethernet_spi_cs_gpio, config_.ethernet_spi_int_gpio,
|
||||
config_.ethernet_phy_reset_gpio);
|
||||
return ESP_OK;
|
||||
#else
|
||||
ESP_LOGW(kTag, "Ethernet requested but W5500 support is not enabled in esp-eth");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::probeEthernetStartup() {
|
||||
if (eth_handle_ == nullptr || !ethernet_started_) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_eth_stop(eth_handle_);
|
||||
ethernet_started_ = false;
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = esp_eth_start(eth_handle_);
|
||||
if (err == ESP_OK) {
|
||||
ethernet_started_ = true;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void GatewayNetworkService::stopEthernet() {
|
||||
if (ethernet_event_handlers_registered_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
||||
IP_EVENT, IP_EVENT_ETH_GOT_IP, &GatewayNetworkService::HandleEthernetEvent));
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
||||
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
|
||||
ethernet_event_handlers_registered_ = false;
|
||||
}
|
||||
|
||||
if (eth_handle_ != nullptr) {
|
||||
const esp_err_t stop_err = esp_eth_stop(eth_handle_);
|
||||
if (stop_err != ESP_OK && stop_err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGW(kTag, "failed to stop Ethernet during disable: %s", esp_err_to_name(stop_err));
|
||||
}
|
||||
ethernet_started_ = false;
|
||||
}
|
||||
|
||||
if (eth_glue_ != nullptr) {
|
||||
const esp_err_t glue_err = esp_eth_del_netif_glue(eth_glue_);
|
||||
if (glue_err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to delete Ethernet netif glue: %s", esp_err_to_name(glue_err));
|
||||
} else {
|
||||
eth_glue_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (eth_handle_ != nullptr) {
|
||||
const esp_err_t uninstall_err = esp_eth_driver_uninstall(eth_handle_);
|
||||
if (uninstall_err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to uninstall Ethernet driver: %s", esp_err_to_name(uninstall_err));
|
||||
} else {
|
||||
eth_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (eth_phy_ != nullptr && eth_handle_ == nullptr) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_phy_->del(eth_phy_));
|
||||
eth_phy_ = nullptr;
|
||||
}
|
||||
if (eth_mac_ != nullptr && eth_handle_ == nullptr) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_mac_->del(eth_mac_));
|
||||
eth_mac_ = nullptr;
|
||||
}
|
||||
|
||||
if (eth_netif_ != nullptr && eth_glue_ == nullptr) {
|
||||
esp_netif_destroy(eth_netif_);
|
||||
eth_netif_ = nullptr;
|
||||
}
|
||||
|
||||
runtime_.clearEthernetInfo();
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startWifi() {
|
||||
if (wifi_started_) {
|
||||
return ESP_OK;
|
||||
@@ -663,22 +909,37 @@ esp_err_t GatewayNetworkService::configureStatusLed() {
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::configureBootButton() {
|
||||
if (config_.boot_button_gpio < 0) {
|
||||
if (config_.boot_button_gpio < 0 && config_.setup_ap_button_gpio < 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gpio_config_t io_config = {};
|
||||
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.boot_button_gpio);
|
||||
io_config.mode = GPIO_MODE_INPUT;
|
||||
io_config.pull_up_en = config_.boot_button_active_low ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
io_config.pull_down_en = config_.boot_button_active_low ? GPIO_PULLDOWN_DISABLE : GPIO_PULLDOWN_ENABLE;
|
||||
io_config.intr_type = GPIO_INTR_DISABLE;
|
||||
const esp_err_t err = gpio_config(&io_config);
|
||||
const auto configure_input = [](int gpio, bool active_low, const char* name) -> esp_err_t {
|
||||
if (gpio < 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
gpio_config_t io_config = {};
|
||||
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(gpio);
|
||||
io_config.mode = GPIO_MODE_INPUT;
|
||||
io_config.pull_up_en = active_low ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
io_config.pull_down_en = active_low ? GPIO_PULLDOWN_DISABLE : GPIO_PULLDOWN_ENABLE;
|
||||
io_config.intr_type = GPIO_INTR_DISABLE;
|
||||
const esp_err_t err = gpio_config(&io_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure %s GPIO%d: %s", name, gpio, esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
};
|
||||
|
||||
esp_err_t err = configure_input(config_.boot_button_gpio, config_.boot_button_active_low,
|
||||
"Wi-Fi reset button");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure boot button GPIO%d: %s", config_.boot_button_gpio,
|
||||
esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
if (config_.setup_ap_button_gpio == config_.boot_button_gpio) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return configure_input(config_.setup_ap_button_gpio, config_.setup_ap_button_active_low,
|
||||
"setup AP button");
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startHttpServer() {
|
||||
@@ -739,7 +1000,8 @@ esp_err_t GatewayNetworkService::startUdpTask() {
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startBootButtonTask() {
|
||||
if (config_.boot_button_gpio < 0 || boot_button_task_handle_ != nullptr) {
|
||||
if ((config_.boot_button_gpio < 0 && config_.setup_ap_button_gpio < 0) ||
|
||||
boot_button_task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -778,6 +1040,69 @@ void GatewayNetworkService::HandleEspNowReceive(const esp_now_recv_info_t* info,
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::HandleEthernetEvent(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(arg);
|
||||
if (service != nullptr) {
|
||||
service->handleEthernetEvent(event_base, event_id, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleEthernetEvent(esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data) {
|
||||
if (event_base == ETH_EVENT) {
|
||||
esp_eth_handle_t handle = eth_handle_;
|
||||
if (event_data != nullptr) {
|
||||
handle = *static_cast<esp_eth_handle_t*>(event_data);
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_CONNECTED) {
|
||||
uint8_t mac[6] = {};
|
||||
if (handle != nullptr && esp_eth_ioctl(handle, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
|
||||
const std::string mac_hex = MacToHex(mac);
|
||||
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
|
||||
info.mac = mac_hex;
|
||||
runtime_.setEthernetInfo(std::move(info));
|
||||
ESP_LOGI(kTag, "Ethernet link up mac=%s", mac_hex.c_str());
|
||||
} else {
|
||||
ESP_LOGI(kTag, "Ethernet link up");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_DISCONNECTED) {
|
||||
runtime_.clearEthernetIp();
|
||||
ESP_LOGI(kTag, "Ethernet link down");
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_START) {
|
||||
ESP_LOGI(kTag, "Ethernet driver started");
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_STOP) {
|
||||
runtime_.clearEthernetIp();
|
||||
ESP_LOGI(kTag, "Ethernet driver stopped");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP && event_data != nullptr) {
|
||||
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
|
||||
char ip[16] = {0};
|
||||
esp_ip4addr_ntoa(&event->ip_info.ip, ip, sizeof(ip));
|
||||
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
|
||||
uint8_t mac[6] = {};
|
||||
if (eth_handle_ != nullptr && esp_eth_ioctl(eth_handle_, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
|
||||
info.mac = MacToHex(mac);
|
||||
}
|
||||
info.ip = ip;
|
||||
runtime_.setEthernetInfo(std::move(info));
|
||||
ESP_LOGI(kTag, "Ethernet got IP %s", ip);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data) {
|
||||
if (!config_.wifi_enabled) {
|
||||
@@ -1040,39 +1365,88 @@ void GatewayNetworkService::bootButtonTaskLoop() {
|
||||
const TickType_t poll_ticks = pdMS_TO_TICKS(100);
|
||||
const uint32_t long_press_ms = std::max<uint32_t>(config_.boot_button_long_press_ms, 100);
|
||||
|
||||
auto is_pressed = [this]() {
|
||||
const int level = gpio_get_level(static_cast<gpio_num_t>(config_.boot_button_gpio));
|
||||
return config_.boot_button_active_low ? level == 0 : level != 0;
|
||||
auto is_pressed = [](int gpio, bool active_low) {
|
||||
if (gpio < 0) {
|
||||
return false;
|
||||
}
|
||||
const int level = gpio_get_level(static_cast<gpio_num_t>(gpio));
|
||||
return active_low ? level == 0 : level != 0;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
if (!is_pressed()) {
|
||||
vTaskDelay(poll_ticks);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto wait_release = [&](int gpio, bool active_low) {
|
||||
uint32_t pressed_ms = 0;
|
||||
while (is_pressed()) {
|
||||
while (is_pressed(gpio, active_low)) {
|
||||
vTaskDelay(poll_ticks);
|
||||
pressed_ms += 100;
|
||||
}
|
||||
return pressed_ms;
|
||||
};
|
||||
|
||||
if (pressed_ms >= long_press_ms) {
|
||||
ESP_LOGW(kTag, "BOOT long press clears Wi-Fi credentials and restarts");
|
||||
runtime_.clearWirelessInfo();
|
||||
stopEspNow();
|
||||
if (wifi_started_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
auto enter_setup_ap = [this]() {
|
||||
ESP_LOGI(kTag, "setup AP button enters setup AP mode");
|
||||
const uint32_t stack_size = std::max<uint32_t>(config_.boot_button_task_stack_size, 8192);
|
||||
const BaseType_t created = xTaskCreate(
|
||||
[](void* arg) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(arg);
|
||||
service->handleWifiControl(101);
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"gateway_setup_ap", stack_size, this, config_.boot_button_task_priority, nullptr);
|
||||
if (created != pdPASS) {
|
||||
ESP_LOGE(kTag, "failed to create setup AP task");
|
||||
}
|
||||
};
|
||||
|
||||
auto clear_wifi_and_restart = [this]() {
|
||||
ESP_LOGW(kTag, "Wi-Fi reset button clears Wi-Fi credentials and restarts");
|
||||
runtime_.clearWirelessInfo();
|
||||
stopEspNow();
|
||||
if (wifi_started_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
esp_restart();
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const bool same_button = config_.boot_button_gpio >= 0 &&
|
||||
config_.boot_button_gpio == config_.setup_ap_button_gpio;
|
||||
if (same_button && is_pressed(config_.boot_button_gpio, config_.boot_button_active_low)) {
|
||||
const uint32_t pressed_ms = wait_release(config_.boot_button_gpio,
|
||||
config_.boot_button_active_low);
|
||||
if (pressed_ms >= long_press_ms) {
|
||||
clear_wifi_and_restart();
|
||||
} else {
|
||||
enter_setup_ap();
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
esp_restart();
|
||||
} else {
|
||||
ESP_LOGI(kTag, "BOOT short press enters setup AP mode");
|
||||
handleWifiControl(101);
|
||||
continue;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
if (is_pressed(config_.setup_ap_button_gpio, config_.setup_ap_button_active_low)) {
|
||||
wait_release(config_.setup_ap_button_gpio, config_.setup_ap_button_active_low);
|
||||
enter_setup_ap();
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_pressed(config_.boot_button_gpio, config_.boot_button_active_low)) {
|
||||
const uint32_t pressed_ms = wait_release(config_.boot_button_gpio,
|
||||
config_.boot_button_active_low);
|
||||
if (pressed_ms >= long_press_ms) {
|
||||
clear_wifi_and_restart();
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config_.setup_ap_button_gpio < 0 && config_.boot_button_gpio < 0) {
|
||||
vTaskDelete(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
vTaskDelay(poll_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1168,6 +1542,15 @@ std::string GatewayNetworkService::deviceInfoJson() const {
|
||||
}
|
||||
}
|
||||
|
||||
if (info.eth.has_value()) {
|
||||
cJSON* eth = cJSON_CreateObject();
|
||||
if (eth != nullptr) {
|
||||
cJSON_AddStringToObject(eth, "mac", info.eth->mac.c_str());
|
||||
cJSON_AddStringToObject(eth, "IP", info.eth->ip.c_str());
|
||||
cJSON_AddItemToObject(root, "ethInfo", eth);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string rendered = PrintJson(root);
|
||||
cJSON_Delete(root);
|
||||
return rendered;
|
||||
|
||||
@@ -30,11 +30,17 @@ struct WirelessInfo {
|
||||
std::string ip;
|
||||
};
|
||||
|
||||
struct EthernetInfo {
|
||||
std::string mac;
|
||||
std::string ip;
|
||||
};
|
||||
|
||||
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};
|
||||
size_t command_queue_capacity{16};
|
||||
};
|
||||
|
||||
@@ -46,6 +52,7 @@ struct GatewayDeviceInfo {
|
||||
size_t dali_gateway_count{0};
|
||||
bool ble_enabled{false};
|
||||
std::optional<WirelessInfo> wlan;
|
||||
std::optional<EthernetInfo> eth;
|
||||
};
|
||||
|
||||
class GatewaySettingsStore {
|
||||
@@ -59,6 +66,9 @@ class GatewaySettingsStore {
|
||||
bool getBleEnabled(bool default_value = false) const;
|
||||
bool setBleEnabled(bool enabled);
|
||||
|
||||
bool getCacheEnabled(bool default_value = true) const;
|
||||
bool setCacheEnabled(bool enabled);
|
||||
|
||||
std::optional<std::string> getWifiSsid() const;
|
||||
std::optional<std::string> getWifiPassword() const;
|
||||
bool setWifiCredentials(std::string_view ssid, std::string_view password);
|
||||
@@ -83,6 +93,12 @@ class GatewayRuntime {
|
||||
kQueueFull,
|
||||
};
|
||||
|
||||
enum class CommandPriority : uint8_t {
|
||||
kControl = 0,
|
||||
kNormal = 1,
|
||||
kMaintenance = 2,
|
||||
};
|
||||
|
||||
GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
|
||||
DaliDomainService* dali_domain);
|
||||
~GatewayRuntime();
|
||||
@@ -93,22 +109,32 @@ class GatewayRuntime {
|
||||
static bool hasValidChecksum(const std::vector<uint8_t>& frame);
|
||||
static bool isGatewayCommandFrame(const std::vector<uint8_t>& frame);
|
||||
static std::vector<uint8_t> buildNotificationFrame(const std::vector<uint8_t>& payload);
|
||||
static CommandPriority classifyCommandPriority(const std::vector<uint8_t>& command);
|
||||
|
||||
bool enqueueCommand(std::vector<uint8_t> command);
|
||||
bool enqueueCommand(std::vector<uint8_t> command,
|
||||
CommandPriority priority = CommandPriority::kNormal);
|
||||
std::optional<std::vector<uint8_t>> popNextCommand();
|
||||
void completeCurrentCommand();
|
||||
bool hasPendingQueryCommand(const std::vector<uint8_t>& command) const;
|
||||
bool hasPendingControlCommand(uint8_t gateway_id) const;
|
||||
bool shouldYieldMaintenance(uint8_t gateway_id) const;
|
||||
bool hasActiveCommand(uint8_t gateway_id) const;
|
||||
bool hasActiveQueryCommand(uint8_t gateway_id) const;
|
||||
CommandDropReason lastEnqueueDropReason() const;
|
||||
|
||||
void setGatewayCount(size_t gateway_count);
|
||||
void setWirelessInfo(WirelessInfo info);
|
||||
bool clearWirelessInfo();
|
||||
void setEthernetInfo(EthernetInfo info);
|
||||
void clearEthernetInfo();
|
||||
void clearEthernetIp();
|
||||
void setCommandAddressResolver(std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver);
|
||||
|
||||
GatewayDeviceInfo deviceInfo() const;
|
||||
bool bleEnabled() const;
|
||||
bool setBleEnabled(bool enabled);
|
||||
bool cacheEnabled() const;
|
||||
bool setCacheEnabled(bool enabled);
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
bool setGatewayName(uint8_t gateway_id, std::string_view name);
|
||||
std::string gatewaySerialHex(uint8_t gateway_id) const;
|
||||
@@ -118,6 +144,9 @@ class GatewayRuntime {
|
||||
|
||||
private:
|
||||
bool isQueryCommand(const std::vector<uint8_t>& command) const;
|
||||
size_t pendingCommandCountLocked() const;
|
||||
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 defaultGatewayName(uint8_t gateway_id) const;
|
||||
std::vector<uint8_t> serialBytes() const;
|
||||
@@ -128,13 +157,18 @@ class GatewayRuntime {
|
||||
DaliDomainService* dali_domain_;
|
||||
GatewaySettingsStore settings_;
|
||||
std::optional<std::vector<uint8_t>> current_command_;
|
||||
std::deque<std::vector<uint8_t>> pending_commands_;
|
||||
CommandPriority current_command_priority_{CommandPriority::kNormal};
|
||||
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::map<uint8_t, std::string> gateway_names_;
|
||||
size_t gateway_count_{0};
|
||||
bool ble_enabled_{false};
|
||||
bool cache_enabled_{true};
|
||||
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_;
|
||||
std::optional<EthernetInfo> ethernet_info_;
|
||||
SemaphoreHandle_t command_lock_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace {
|
||||
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* kWifiSsidKey = "wifi_ssid";
|
||||
constexpr const char* kWifiPasswordKey = "wifi_passwd";
|
||||
constexpr size_t kMaxGatewayNameBytes = 32;
|
||||
@@ -109,6 +110,28 @@ bool GatewaySettingsStore::setBleEnabled(bool enabled) {
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::getCacheEnabled(bool default_value) const {
|
||||
if (handle_ == 0) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
uint8_t enabled = default_value ? 1 : 0;
|
||||
if (nvs_get_u8(handle_, kCacheEnabledKey, &enabled) != ESP_OK) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
return enabled != 0;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setCacheEnabled(bool enabled) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nvs_set_u8(handle_, kCacheEnabledKey, enabled ? 1 : 0) == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<std::string> GatewaySettingsStore::getWifiSsid() const {
|
||||
return readString(kWifiSsidKey);
|
||||
}
|
||||
@@ -222,6 +245,7 @@ esp_err_t GatewayRuntime::start() {
|
||||
}
|
||||
|
||||
ble_enabled_ = settings_.getBleEnabled(config_.default_ble_enabled);
|
||||
cache_enabled_ = settings_.getCacheEnabled(config_.default_cache_enabled);
|
||||
|
||||
if (!wireless_info_.has_value()) {
|
||||
WirelessInfo info;
|
||||
@@ -239,10 +263,10 @@ esp_err_t GatewayRuntime::start() {
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag,
|
||||
"runtime project=%.*s version=%.*s serial=%s ble=%d dali_bound=%d",
|
||||
"runtime project=%.*s version=%.*s serial=%s ble=%d cache=%d 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_,
|
||||
config_.serial_id.c_str(), ble_enabled_, cache_enabled_,
|
||||
dali_domain_ != nullptr && dali_domain_->isBound());
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -283,7 +307,30 @@ std::vector<uint8_t> GatewayRuntime::buildNotificationFrame(const std::vector<ui
|
||||
return checksum(std::move(frame));
|
||||
}
|
||||
|
||||
bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
|
||||
GatewayRuntime::CommandPriority GatewayRuntime::classifyCommandPriority(
|
||||
const std::vector<uint8_t>& command) {
|
||||
if (command.size() < 5 || !isGatewayCommandFrame(command)) {
|
||||
return CommandPriority::kNormal;
|
||||
}
|
||||
|
||||
const uint8_t opcode = command[3];
|
||||
const uint8_t addr = command[4];
|
||||
if (opcode == 0x30 && (addr == 1 || addr == 2)) {
|
||||
return CommandPriority::kMaintenance;
|
||||
}
|
||||
if (opcode == 0x32) {
|
||||
return CommandPriority::kMaintenance;
|
||||
}
|
||||
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 == 0x30 && addr == 0)) {
|
||||
return CommandPriority::kControl;
|
||||
}
|
||||
return CommandPriority::kNormal;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command, CommandPriority priority) {
|
||||
LockGuard guard(command_lock_);
|
||||
last_enqueue_drop_reason_ = CommandDropReason::kNone;
|
||||
if (isQueryCommand(command) && hasPendingQueryCommand(command)) {
|
||||
@@ -291,25 +338,30 @@ bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pending_commands_.size() >= config_.command_queue_capacity) {
|
||||
if (pendingCommandCountLocked() >= config_.command_queue_capacity) {
|
||||
last_enqueue_drop_reason_ = CommandDropReason::kQueueFull;
|
||||
return false;
|
||||
}
|
||||
|
||||
pending_commands_.push_back(std::move(command));
|
||||
queueForPriorityLocked(priority).push_back(std::move(command));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> GatewayRuntime::popNextCommand() {
|
||||
LockGuard guard(command_lock_);
|
||||
if (pending_commands_.empty()) {
|
||||
current_command_.reset();
|
||||
return std::nullopt;
|
||||
for (const auto priority : {CommandPriority::kControl, CommandPriority::kNormal,
|
||||
CommandPriority::kMaintenance}) {
|
||||
auto& queue = queueForPriorityLocked(priority);
|
||||
if (!queue.empty()) {
|
||||
current_command_ = std::move(queue.front());
|
||||
current_command_priority_ = priority;
|
||||
queue.pop_front();
|
||||
return current_command_;
|
||||
}
|
||||
}
|
||||
|
||||
current_command_ = std::move(pending_commands_.front());
|
||||
pending_commands_.pop_front();
|
||||
return current_command_;
|
||||
current_command_.reset();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void GatewayRuntime::completeCurrentCommand() {
|
||||
@@ -328,10 +380,29 @@ bool GatewayRuntime::hasPendingQueryCommand(const std::vector<uint8_t>& command)
|
||||
return true;
|
||||
}
|
||||
|
||||
return std::any_of(pending_commands_.begin(), pending_commands_.end(),
|
||||
[&](const std::vector<uint8_t>& pending) {
|
||||
return queryCommandKey(pending) == command_key;
|
||||
});
|
||||
const auto matches = [&](const std::vector<uint8_t>& pending) {
|
||||
return queryCommandKey(pending) == command_key;
|
||||
};
|
||||
return std::any_of(control_commands_.begin(), control_commands_.end(), matches) ||
|
||||
std::any_of(normal_commands_.begin(), normal_commands_.end(), matches) ||
|
||||
std::any_of(maintenance_commands_.begin(), maintenance_commands_.end(), matches);
|
||||
}
|
||||
|
||||
bool GatewayRuntime::hasPendingControlCommand(uint8_t gateway_id) const {
|
||||
LockGuard guard(command_lock_);
|
||||
return std::any_of(control_commands_.begin(), control_commands_.end(), [gateway_id](const auto& command) {
|
||||
return command.size() > 2 && command[2] == gateway_id;
|
||||
});
|
||||
}
|
||||
|
||||
bool GatewayRuntime::shouldYieldMaintenance(uint8_t gateway_id) const {
|
||||
return hasPendingControlCommand(gateway_id);
|
||||
}
|
||||
|
||||
bool GatewayRuntime::hasActiveCommand(uint8_t gateway_id) const {
|
||||
LockGuard guard(command_lock_);
|
||||
return current_command_.has_value() && current_command_->size() > 2 &&
|
||||
(*current_command_)[2] == gateway_id;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::hasActiveQueryCommand(uint8_t gateway_id) const {
|
||||
@@ -379,6 +450,23 @@ bool GatewayRuntime::clearWirelessInfo() {
|
||||
return settings_.clearWifiCredentials();
|
||||
}
|
||||
|
||||
void GatewayRuntime::setEthernetInfo(EthernetInfo info) {
|
||||
LockGuard guard(command_lock_);
|
||||
ethernet_info_ = std::move(info);
|
||||
}
|
||||
|
||||
void GatewayRuntime::clearEthernetInfo() {
|
||||
LockGuard guard(command_lock_);
|
||||
ethernet_info_.reset();
|
||||
}
|
||||
|
||||
void GatewayRuntime::clearEthernetIp() {
|
||||
LockGuard guard(command_lock_);
|
||||
if (ethernet_info_.has_value()) {
|
||||
ethernet_info_->ip.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayRuntime::setCommandAddressResolver(
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
|
||||
LockGuard guard(command_lock_);
|
||||
@@ -399,6 +487,7 @@ GatewayDeviceInfo GatewayRuntime::deviceInfo() const {
|
||||
info.dali_gateway_count = gateway_count_;
|
||||
info.ble_enabled = ble_enabled_;
|
||||
info.wlan = wireless_info_;
|
||||
info.eth = ethernet_info_;
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -422,6 +511,26 @@ bool GatewayRuntime::setBleEnabled(bool enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::cacheEnabled() const {
|
||||
LockGuard guard(command_lock_);
|
||||
return cache_enabled_;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setCacheEnabled(bool enabled) {
|
||||
{
|
||||
LockGuard guard(command_lock_);
|
||||
if (cache_enabled_ == enabled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!settings_.setCacheEnabled(enabled)) {
|
||||
return false;
|
||||
}
|
||||
LockGuard guard(command_lock_);
|
||||
cache_enabled_ = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const {
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = gateway_names_.find(gateway_id);
|
||||
@@ -492,6 +601,36 @@ bool GatewayRuntime::isQueryCommand(const std::vector<uint8_t>& command) const {
|
||||
command[3] <= 0x16;
|
||||
}
|
||||
|
||||
size_t GatewayRuntime::pendingCommandCountLocked() const {
|
||||
return control_commands_.size() + normal_commands_.size() + maintenance_commands_.size();
|
||||
}
|
||||
|
||||
std::deque<std::vector<uint8_t>>& GatewayRuntime::queueForPriorityLocked(
|
||||
CommandPriority priority) {
|
||||
switch (priority) {
|
||||
case CommandPriority::kControl:
|
||||
return control_commands_;
|
||||
case CommandPriority::kMaintenance:
|
||||
return maintenance_commands_;
|
||||
case CommandPriority::kNormal:
|
||||
default:
|
||||
return normal_commands_;
|
||||
}
|
||||
}
|
||||
|
||||
const std::deque<std::vector<uint8_t>>& GatewayRuntime::queueForPriorityLocked(
|
||||
CommandPriority priority) const {
|
||||
switch (priority) {
|
||||
case CommandPriority::kControl:
|
||||
return control_commands_;
|
||||
case CommandPriority::kMaintenance:
|
||||
return maintenance_commands_;
|
||||
case CommandPriority::kNormal:
|
||||
default:
|
||||
return normal_commands_;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> GatewayRuntime::queryCommandKey(
|
||||
const std::vector<uint8_t>& command) const {
|
||||
if (!isQueryCommand(command)) {
|
||||
|
||||
@@ -12,6 +12,13 @@ namespace gateway {
|
||||
namespace {
|
||||
constexpr const char* kTag = "gateway_usb";
|
||||
constexpr size_t kCommandFrameMinLen = 7;
|
||||
|
||||
std::vector<uint8_t> LegacyRawPayload(const std::vector<uint8_t>& data) {
|
||||
if (data.size() == 1) {
|
||||
return {0xBE, data[0]};
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayUsbSetupBridge::GatewayUsbSetupBridge(GatewayController& controller,
|
||||
@@ -82,6 +89,19 @@ void GatewayUsbSetupBridge::handleBytes(const uint8_t* data, size_t len) {
|
||||
}
|
||||
|
||||
const uint8_t gateway_id = setupGatewayId();
|
||||
if (data[0] == 0x12) {
|
||||
const auto response = dali_domain_.transactBridgeFrame(gateway_id, data, len);
|
||||
if (!response.empty()) {
|
||||
const int written = usb_serial_jtag_write_bytes(response.data(), response.size(),
|
||||
pdMS_TO_TICKS(config_.write_timeout_ms));
|
||||
if (written < 0 || static_cast<size_t>(written) != response.size()) {
|
||||
ESP_LOGW(kTag, "failed to write USB raw query response channel=%u len=%u",
|
||||
config_.channel_index, static_cast<unsigned>(response.size()));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dali_domain_.writeBridgeFrame(gateway_id, data, len)) {
|
||||
ESP_LOGW(kTag, "failed to write USB raw setup frame channel=%u len=%u", config_.channel_index,
|
||||
static_cast<unsigned>(len));
|
||||
@@ -93,11 +113,12 @@ void GatewayUsbSetupBridge::handleRawFrame(const DaliRawFrame& frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int written = usb_serial_jtag_write_bytes(frame.data.data(), frame.data.size(),
|
||||
const auto payload = LegacyRawPayload(frame.data);
|
||||
const int written = usb_serial_jtag_write_bytes(payload.data(), payload.size(),
|
||||
pdMS_TO_TICKS(config_.write_timeout_ms));
|
||||
if (written < 0 || static_cast<size_t>(written) != frame.data.size()) {
|
||||
if (written < 0 || static_cast<size_t>(written) != payload.size()) {
|
||||
ESP_LOGW(kTag, "failed to forward USB raw setup frame channel=%u len=%u", frame.channel_index,
|
||||
static_cast<unsigned>(frame.data.size()));
|
||||
static_cast<unsigned>(payload.size()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
set(OPENKNX_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../knx")
|
||||
set(TPUART_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../tpuart")
|
||||
|
||||
if(NOT EXISTS "${OPENKNX_ROOT}/src/knx/platform.h")
|
||||
message(FATAL_ERROR "OpenKNX submodule is missing at ${OPENKNX_ROOT}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${TPUART_ROOT}/src/TPUart/DataLinkLayer.h")
|
||||
message(FATAL_ERROR "TPUart submodule is missing at ${TPUART_ROOT}")
|
||||
endif()
|
||||
|
||||
file(GLOB OPENKNX_SRCS
|
||||
"${OPENKNX_ROOT}/src/knx/*.cpp"
|
||||
)
|
||||
|
||||
if(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||
list(APPEND OPENKNX_SRCS
|
||||
"${OPENKNX_ROOT}/src/knx/aes.c"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(TPUART_SRCS
|
||||
"${TPUART_ROOT}/src/TPUart/DataLinkLayer.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/Receiver.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/RepetitionFilter.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/RingBuffer.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/SearchBuffer.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/Statistics.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/SystemState.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/Transmitter.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart.cpp"
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"src/arduino_compat.cpp"
|
||||
"src/esp_idf_platform.cpp"
|
||||
"src/ets_device_runtime.cpp"
|
||||
"src/ets_memory_loader.cpp"
|
||||
"src/security_storage.cpp"
|
||||
"src/tpuart_uart_interface.cpp"
|
||||
${OPENKNX_SRCS}
|
||||
${TPUART_SRCS}
|
||||
INCLUDE_DIRS
|
||||
"include"
|
||||
"${OPENKNX_ROOT}/src"
|
||||
"${TPUART_ROOT}/src"
|
||||
REQUIRES
|
||||
esp_driver_gpio
|
||||
esp_driver_uart
|
||||
esp_netif
|
||||
esp_system
|
||||
esp_timer
|
||||
esp_wifi
|
||||
freertos
|
||||
log
|
||||
lwip
|
||||
mbedtls
|
||||
nvs_flash
|
||||
)
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB} PUBLIC
|
||||
MASK_VERSION=0x07B0
|
||||
KNX_FLASH_SIZE=4096
|
||||
KNX_NO_AUTOMATIC_GLOBAL_INSTANCE
|
||||
KNX_NO_SPI
|
||||
USE_CEMI_SERVER
|
||||
)
|
||||
|
||||
if(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||
target_compile_definitions(${COMPONENT_LIB} PUBLIC USE_DATASECURE)
|
||||
endif()
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||
-Wno-unused-parameter
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef DEC
|
||||
#define DEC 10
|
||||
#endif
|
||||
|
||||
#ifndef HEX
|
||||
#define HEX 16
|
||||
#endif
|
||||
|
||||
#ifndef INPUT
|
||||
#define INPUT 0x0
|
||||
#endif
|
||||
|
||||
#ifndef OUTPUT
|
||||
#define OUTPUT 0x1
|
||||
#endif
|
||||
|
||||
#ifndef INPUT_PULLUP
|
||||
#define INPUT_PULLUP 0x2
|
||||
#endif
|
||||
|
||||
#ifndef INPUT_PULLDOWN
|
||||
#define INPUT_PULLDOWN 0x3
|
||||
#endif
|
||||
|
||||
#ifndef LOW
|
||||
#define LOW 0x0
|
||||
#endif
|
||||
|
||||
#ifndef HIGH
|
||||
#define HIGH 0x1
|
||||
#endif
|
||||
|
||||
#ifndef CHANGE
|
||||
#define CHANGE 2
|
||||
#endif
|
||||
|
||||
#ifndef FALLING
|
||||
#define FALLING 3
|
||||
#endif
|
||||
|
||||
#ifndef RISING
|
||||
#define RISING 4
|
||||
#endif
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
uint32_t millis();
|
||||
uint32_t micros();
|
||||
void delay(uint32_t millis);
|
||||
void delayMicroseconds(unsigned int howLong);
|
||||
void pinMode(uint32_t pin, uint32_t mode);
|
||||
void digitalWrite(uint32_t pin, uint32_t value);
|
||||
uint32_t digitalRead(uint32_t pin);
|
||||
typedef void (*voidFuncPtr)(void);
|
||||
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode);
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "knx/platform.h"
|
||||
|
||||
#include "esp_netif.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
class EspIdfPlatform : public Platform {
|
||||
public:
|
||||
using OutboundCemiFrameCallback = bool (*)(CemiFrame& frame, void* context);
|
||||
|
||||
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
|
||||
const char* nvs_namespace = "openknx");
|
||||
~EspIdfPlatform() override;
|
||||
|
||||
void outboundCemiFrameCallback(OutboundCemiFrameCallback callback, void* context);
|
||||
bool handleOutboundCemiFrame(CemiFrame& frame) override;
|
||||
|
||||
void networkInterface(esp_netif_t* netif);
|
||||
esp_netif_t* networkInterface() const;
|
||||
|
||||
uint32_t currentIpAddress() override;
|
||||
uint32_t currentSubnetMask() override;
|
||||
uint32_t currentDefaultGateway() override;
|
||||
void macAddress(uint8_t* data) override;
|
||||
uint32_t uniqueSerialNumber() override;
|
||||
|
||||
void restart() override;
|
||||
void fatalError() override;
|
||||
|
||||
void setupMultiCast(uint32_t addr, uint16_t port) override;
|
||||
void closeMultiCast() override;
|
||||
bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override;
|
||||
int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override;
|
||||
int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr,
|
||||
uint16_t& src_port) override;
|
||||
bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer,
|
||||
uint16_t len) override;
|
||||
|
||||
uint8_t* getEepromBuffer(uint32_t size) override;
|
||||
void commitToEeprom() override;
|
||||
|
||||
private:
|
||||
esp_netif_t* effectiveNetif() const;
|
||||
void loadEeprom(size_t size);
|
||||
|
||||
esp_netif_t* netif_{nullptr};
|
||||
int udp_sock_{-1};
|
||||
sockaddr_in multicast_remote_{};
|
||||
sockaddr_in last_remote_{};
|
||||
bool has_last_remote_{false};
|
||||
std::vector<uint8_t> eeprom_;
|
||||
std::string nvs_namespace_;
|
||||
bool eeprom_loaded_{false};
|
||||
OutboundCemiFrameCallback outbound_cemi_frame_callback_{nullptr};
|
||||
void* outbound_cemi_frame_context_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx/cemi_frame.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
class EtsDeviceRuntime {
|
||||
public:
|
||||
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
||||
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
|
||||
size_t len)>;
|
||||
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response)>;
|
||||
|
||||
EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address,
|
||||
uint16_t tunnel_client_address = 0);
|
||||
~EtsDeviceRuntime();
|
||||
|
||||
uint16_t individualAddress() const;
|
||||
uint16_t tunnelClientAddress() const;
|
||||
bool configured() const;
|
||||
bool programmingMode() const;
|
||||
void setProgrammingMode(bool enabled);
|
||||
void toggleProgrammingMode();
|
||||
EtsMemorySnapshot snapshot() const;
|
||||
|
||||
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||
FunctionPropertyHandler state_handler);
|
||||
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||
void setNetworkInterface(esp_netif_t* netif);
|
||||
|
||||
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
|
||||
bool handleBusFrame(const uint8_t* data, size_t len);
|
||||
bool emitGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
|
||||
static void EmitTunnelFrame(CemiFrame& frame, void* context);
|
||||
static void HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
uint8_t data_length, void* context);
|
||||
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
static bool HandleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
static uint16_t DefaultTunnelClientAddress(uint16_t individual_address);
|
||||
static bool DispatchFunctionProperty(FunctionPropertyHandler* handler, uint8_t object_index,
|
||||
uint8_t property_id, uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
|
||||
bool shouldConsumeBusFrame(CemiFrame& frame) const;
|
||||
|
||||
std::string nvs_namespace_;
|
||||
EspIdfPlatform platform_;
|
||||
Bau07B0 device_;
|
||||
CemiFrameSender sender_;
|
||||
GroupWriteHandler group_write_handler_;
|
||||
FunctionPropertyHandler command_handler_;
|
||||
FunctionPropertyHandler state_handler_;
|
||||
};
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
struct EtsAssociation {
|
||||
uint16_t group_address{0};
|
||||
uint16_t group_object_number{0};
|
||||
};
|
||||
|
||||
struct EtsMemorySnapshot {
|
||||
bool configured{false};
|
||||
uint16_t individual_address{0};
|
||||
std::vector<EtsAssociation> associations;
|
||||
};
|
||||
|
||||
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace);
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
#include "openknx_idf/ets_device_runtime.h"
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
#include "openknx_idf/security_storage.h"
|
||||
#include "openknx_idf/tpuart_uart_interface.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx_facade.h"
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
using DaliGatewayDevice = KnxFacade<EspIdfPlatform, Bau07B0>;
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
struct FactoryFdskInfo {
|
||||
bool available{false};
|
||||
std::string serialNumber;
|
||||
std::string label;
|
||||
std::string qrCode;
|
||||
};
|
||||
|
||||
struct FactoryCertificatePayload {
|
||||
bool available{false};
|
||||
std::string productIdentity;
|
||||
std::string manufacturerId;
|
||||
std::string applicationNumber;
|
||||
std::string applicationVersion;
|
||||
std::string serialNumber;
|
||||
std::string fdskLabel;
|
||||
std::string fdskQrCode;
|
||||
std::string storage;
|
||||
std::string createdAt;
|
||||
std::string checksum;
|
||||
};
|
||||
|
||||
bool LoadFactoryFdsk(uint8_t* data, size_t len);
|
||||
FactoryFdskInfo LoadFactoryFdskInfo();
|
||||
bool GenerateFactoryFdsk(FactoryFdskInfo* info = nullptr);
|
||||
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info = nullptr);
|
||||
bool ResetFactoryFdskCache(FactoryFdskInfo* info = nullptr);
|
||||
FactoryCertificatePayload BuildFactoryCertificatePayload();
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "TPUart/Interface/Abstract.h"
|
||||
|
||||
#include "driver/uart.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
class TpuartUartInterface : public TPUart::Interface::Abstract {
|
||||
public:
|
||||
TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
|
||||
size_t rx_buffer_size = 512, size_t tx_buffer_size = 512,
|
||||
bool nine_bit_mode = true);
|
||||
~TpuartUartInterface();
|
||||
|
||||
void begin(int baud) override;
|
||||
void end() override;
|
||||
bool available() override;
|
||||
bool availableForWrite() override;
|
||||
bool write(char value) override;
|
||||
int read() override;
|
||||
bool overflow() override;
|
||||
void flush() override;
|
||||
bool hasCallback() override;
|
||||
void registerCallback(std::function<bool()> callback) override;
|
||||
|
||||
private:
|
||||
uart_port_t uart_port_;
|
||||
int tx_pin_;
|
||||
int rx_pin_;
|
||||
size_t rx_buffer_size_;
|
||||
size_t tx_buffer_size_;
|
||||
bool nine_bit_mode_;
|
||||
std::atomic_bool overflow_{false};
|
||||
std::function<bool()> callback_;
|
||||
};
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,180 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
|
||||
std::array<voidFuncPtr, GPIO_NUM_MAX> g_gpio_callbacks{};
|
||||
bool g_isr_service_installed = false;
|
||||
|
||||
void IRAM_ATTR gpioIsrThunk(void* arg) {
|
||||
const auto pin = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(arg));
|
||||
if (pin < g_gpio_callbacks.size() && g_gpio_callbacks[pin] != nullptr) {
|
||||
g_gpio_callbacks[pin]();
|
||||
}
|
||||
}
|
||||
|
||||
gpio_int_type_t toGpioInterrupt(uint32_t mode) {
|
||||
switch (mode) {
|
||||
case RISING:
|
||||
return GPIO_INTR_POSEDGE;
|
||||
case FALLING:
|
||||
return GPIO_INTR_NEGEDGE;
|
||||
case CHANGE:
|
||||
return GPIO_INTR_ANYEDGE;
|
||||
default:
|
||||
return GPIO_INTR_DISABLE;
|
||||
}
|
||||
}
|
||||
|
||||
void printUnsigned(unsigned long long value, int base) {
|
||||
if (base == HEX) {
|
||||
std::printf("%llX", value);
|
||||
} else {
|
||||
std::printf("%llu", value);
|
||||
}
|
||||
}
|
||||
|
||||
void printSigned(long long value, int base) {
|
||||
if (base == HEX) {
|
||||
std::printf("%llX", static_cast<unsigned long long>(value));
|
||||
} else {
|
||||
std::printf("%lld", value);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t millis() { return static_cast<uint32_t>(esp_timer_get_time() / 1000ULL); }
|
||||
|
||||
uint32_t micros() { return static_cast<uint32_t>(esp_timer_get_time()); }
|
||||
|
||||
void delay(uint32_t millis) { vTaskDelay(pdMS_TO_TICKS(millis)); }
|
||||
|
||||
void delayMicroseconds(unsigned int howLong) { esp_rom_delay_us(howLong); }
|
||||
|
||||
void pinMode(uint32_t pin, uint32_t mode) {
|
||||
if (pin >= GPIO_NUM_MAX) {
|
||||
return;
|
||||
}
|
||||
gpio_config_t config{};
|
||||
config.pin_bit_mask = 1ULL << pin;
|
||||
config.mode = mode == OUTPUT ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
|
||||
config.pull_up_en = mode == INPUT_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
config.pull_down_en = mode == INPUT_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
config.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&config);
|
||||
}
|
||||
|
||||
void digitalWrite(uint32_t pin, uint32_t value) {
|
||||
if (pin < GPIO_NUM_MAX) {
|
||||
gpio_set_level(static_cast<gpio_num_t>(pin), value == LOW ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t digitalRead(uint32_t pin) {
|
||||
if (pin >= GPIO_NUM_MAX) {
|
||||
return LOW;
|
||||
}
|
||||
return gpio_get_level(static_cast<gpio_num_t>(pin)) == 0 ? LOW : HIGH;
|
||||
}
|
||||
|
||||
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) {
|
||||
if (pin >= GPIO_NUM_MAX) {
|
||||
return;
|
||||
}
|
||||
if (!g_isr_service_installed) {
|
||||
const esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||
g_isr_service_installed = err == ESP_OK || err == ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (!g_isr_service_installed) {
|
||||
return;
|
||||
}
|
||||
gpio_set_intr_type(static_cast<gpio_num_t>(pin), toGpioInterrupt(mode));
|
||||
gpio_isr_handler_remove(static_cast<gpio_num_t>(pin));
|
||||
g_gpio_callbacks[pin] = callback;
|
||||
if (callback != nullptr) {
|
||||
gpio_isr_handler_add(static_cast<gpio_num_t>(pin), gpioIsrThunk,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(pin)));
|
||||
}
|
||||
}
|
||||
|
||||
void print(const char value[]) { std::printf("%s", value == nullptr ? "" : value); }
|
||||
|
||||
void print(char value) { std::printf("%c", value); }
|
||||
|
||||
void print(unsigned char value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(int value, int base) { printSigned(value, base); }
|
||||
|
||||
void print(unsigned int value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(long value, int base) { printSigned(value, base); }
|
||||
|
||||
void print(unsigned long value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(long long value, int base) { printSigned(value, base); }
|
||||
|
||||
void print(unsigned long long value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(double value) { std::printf("%f", value); }
|
||||
|
||||
void println(const char value[]) {
|
||||
print(value);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(char value) {
|
||||
print(value);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned char value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(int value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned int value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(long long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned long long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(double value) {
|
||||
print(value);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(void) { std::printf("\n"); }
|
||||
@@ -0,0 +1,316 @@
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
constexpr const char* kTag = "openknx_idf";
|
||||
constexpr const char* kEepromKey = "eeprom";
|
||||
|
||||
bool readBaseMac(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (esp_efuse_mac_get_default(data) == ESP_OK) {
|
||||
return true;
|
||||
}
|
||||
return esp_read_mac(data, ESP_MAC_WIFI_STA) == ESP_OK;
|
||||
}
|
||||
|
||||
esp_netif_t* findDefaultNetif() {
|
||||
constexpr const char* kPreferredIfKeys[] = {"ETH_DEF", "WIFI_STA_DEF", "WIFI_AP_DEF"};
|
||||
for (const char* key : kPreferredIfKeys) {
|
||||
auto* netif = esp_netif_get_handle_from_ifkey(key);
|
||||
if (netif == nullptr || !esp_netif_is_netif_up(netif)) {
|
||||
continue;
|
||||
}
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK && ip_info.ip.addr != 0) {
|
||||
return netif;
|
||||
}
|
||||
}
|
||||
for (const char* key : kPreferredIfKeys) {
|
||||
if (auto* netif = esp_netif_get_handle_from_ifkey(key)) {
|
||||
return netif;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ensureNvsReady() {
|
||||
const esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
if (nvs_flash_erase() != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
return nvs_flash_init() == ESP_OK;
|
||||
}
|
||||
return err == ESP_OK || err == ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface,
|
||||
const char* nvs_namespace)
|
||||
: nvs_namespace_(nvs_namespace == nullptr ? "openknx" : nvs_namespace) {
|
||||
this->interface(interface);
|
||||
}
|
||||
|
||||
EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); }
|
||||
|
||||
void EspIdfPlatform::outboundCemiFrameCallback(OutboundCemiFrameCallback callback,
|
||||
void* context) {
|
||||
outbound_cemi_frame_callback_ = callback;
|
||||
outbound_cemi_frame_context_ = context;
|
||||
}
|
||||
|
||||
bool EspIdfPlatform::handleOutboundCemiFrame(CemiFrame& frame) {
|
||||
if (outbound_cemi_frame_callback_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return outbound_cemi_frame_callback_(frame, outbound_cemi_frame_context_);
|
||||
}
|
||||
|
||||
void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
|
||||
|
||||
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
|
||||
|
||||
esp_netif_t* EspIdfPlatform::effectiveNetif() const {
|
||||
return netif_ == nullptr ? findDefaultNetif() : netif_;
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::currentIpAddress() {
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
esp_netif_t* netif = effectiveNetif();
|
||||
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
return ip_info.ip.addr;
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::currentSubnetMask() {
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
esp_netif_t* netif = effectiveNetif();
|
||||
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
return ip_info.netmask.addr;
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::currentDefaultGateway() {
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
esp_netif_t* netif = effectiveNetif();
|
||||
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
return ip_info.gw.addr;
|
||||
}
|
||||
|
||||
void EspIdfPlatform::macAddress(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!readBaseMac(data)) {
|
||||
std::memset(data, 0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::uniqueSerialNumber() {
|
||||
uint8_t mac[6]{};
|
||||
macAddress(mac);
|
||||
return (static_cast<uint32_t>(mac[2]) << 24) | (static_cast<uint32_t>(mac[3]) << 16) |
|
||||
(static_cast<uint32_t>(mac[4]) << 8) | mac[5];
|
||||
}
|
||||
|
||||
void EspIdfPlatform::restart() { esp_restart(); }
|
||||
|
||||
void EspIdfPlatform::fatalError() {
|
||||
ESP_LOGE(kTag, "OpenKNX fatal error");
|
||||
while (true) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
closeMultiCast();
|
||||
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (udp_sock_ < 0) {
|
||||
ESP_LOGE(kTag, "failed to create UDP socket: errno=%d", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
setsockopt(udp_sock_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
|
||||
sockaddr_in bind_addr{};
|
||||
bind_addr.sin_family = AF_INET;
|
||||
const uint32_t local_address = currentIpAddress();
|
||||
bind_addr.sin_addr.s_addr = local_address == 0 ? htonl(INADDR_ANY) : local_address;
|
||||
bind_addr.sin_port = htons(port);
|
||||
if (bind(udp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
||||
ESP_LOGE(kTag, "failed to bind UDP socket: errno=%d", errno);
|
||||
closeMultiCast();
|
||||
return;
|
||||
}
|
||||
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 1000;
|
||||
setsockopt(udp_sock_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
|
||||
ip_mreq mreq{};
|
||||
mreq.imr_multiaddr.s_addr = htonl(addr);
|
||||
mreq.imr_interface.s_addr = local_address == 0 ? htonl(INADDR_ANY) : local_address;
|
||||
if (setsockopt(udp_sock_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
||||
ESP_LOGW(kTag, "failed to join KNX multicast group: errno=%d", errno);
|
||||
}
|
||||
|
||||
if (local_address != 0) {
|
||||
in_addr multicast_interface{};
|
||||
multicast_interface.s_addr = local_address;
|
||||
if (setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_IF, &multicast_interface,
|
||||
sizeof(multicast_interface)) < 0) {
|
||||
ESP_LOGW(kTag, "failed to select KNX multicast interface: errno=%d", errno);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t loop = 0;
|
||||
setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
|
||||
|
||||
multicast_remote_ = {};
|
||||
multicast_remote_.sin_family = AF_INET;
|
||||
multicast_remote_.sin_addr.s_addr = htonl(addr);
|
||||
multicast_remote_.sin_port = htons(port);
|
||||
}
|
||||
|
||||
void EspIdfPlatform::closeMultiCast() {
|
||||
if (udp_sock_ >= 0) {
|
||||
shutdown(udp_sock_, SHUT_RDWR);
|
||||
close(udp_sock_);
|
||||
udp_sock_ = -1;
|
||||
}
|
||||
has_last_remote_ = false;
|
||||
}
|
||||
|
||||
bool EspIdfPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) {
|
||||
if (udp_sock_ < 0 || buffer == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
const int sent = sendto(udp_sock_, buffer, len, 0, reinterpret_cast<sockaddr*>(&multicast_remote_),
|
||||
sizeof(multicast_remote_));
|
||||
return sent == len;
|
||||
}
|
||||
|
||||
int EspIdfPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) {
|
||||
uint32_t src_addr = 0;
|
||||
uint16_t src_port = 0;
|
||||
return readBytesMultiCast(buffer, maxLen, src_addr, src_port);
|
||||
}
|
||||
|
||||
int EspIdfPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr,
|
||||
uint16_t& src_port) {
|
||||
if (udp_sock_ < 0 || buffer == nullptr || maxLen == 0) {
|
||||
return 0;
|
||||
}
|
||||
sockaddr_in remote{};
|
||||
socklen_t remote_len = sizeof(remote);
|
||||
const int len = recvfrom(udp_sock_, buffer, maxLen, 0, reinterpret_cast<sockaddr*>(&remote),
|
||||
&remote_len);
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
last_remote_ = remote;
|
||||
has_last_remote_ = true;
|
||||
src_addr = ntohl(remote.sin_addr.s_addr);
|
||||
src_port = ntohs(remote.sin_port);
|
||||
return len;
|
||||
}
|
||||
|
||||
bool EspIdfPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer,
|
||||
uint16_t len) {
|
||||
if (udp_sock_ < 0 || buffer == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
sockaddr_in remote{};
|
||||
if (addr == 0 && port == 0 && has_last_remote_) {
|
||||
remote = last_remote_;
|
||||
} else {
|
||||
remote.sin_family = AF_INET;
|
||||
remote.sin_addr.s_addr = htonl(addr);
|
||||
remote.sin_port = htons(port);
|
||||
}
|
||||
const int sent = sendto(udp_sock_, buffer, len, 0, reinterpret_cast<sockaddr*>(&remote),
|
||||
sizeof(remote));
|
||||
return sent == len;
|
||||
}
|
||||
|
||||
void EspIdfPlatform::loadEeprom(size_t size) {
|
||||
if (eeprom_loaded_ && eeprom_.size() == size) {
|
||||
return;
|
||||
}
|
||||
eeprom_.assign(size, 0xff);
|
||||
eeprom_loaded_ = true;
|
||||
|
||||
if (!ensureNvsReady()) {
|
||||
ESP_LOGW(kTag, "NVS is not ready for OpenKNX EEPROM load");
|
||||
return;
|
||||
}
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
if (nvs_open(nvs_namespace_.c_str(), NVS_READONLY, &handle) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
size_t stored_size = 0;
|
||||
if (nvs_get_blob(handle, kEepromKey, nullptr, &stored_size) == ESP_OK && stored_size > 0) {
|
||||
std::vector<uint8_t> stored(stored_size);
|
||||
if (nvs_get_blob(handle, kEepromKey, stored.data(), &stored_size) == ESP_OK) {
|
||||
std::memcpy(eeprom_.data(), stored.data(), std::min(eeprom_.size(), stored.size()));
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
uint8_t* EspIdfPlatform::getEepromBuffer(uint32_t size) {
|
||||
loadEeprom(size);
|
||||
return eeprom_.data();
|
||||
}
|
||||
|
||||
void EspIdfPlatform::commitToEeprom() {
|
||||
if (eeprom_.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!ensureNvsReady()) {
|
||||
ESP_LOGW(kTag, "NVS is not ready for OpenKNX EEPROM commit");
|
||||
return;
|
||||
}
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t err = nvs_open(nvs_namespace_.c_str(), NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to open OpenKNX NVS namespace: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
err = nvs_set_blob(handle, kEepromKey, eeprom_.data(), eeprom_.size());
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(handle);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to commit OpenKNX EEPROM: %s", esp_err_to_name(err));
|
||||
}
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,348 @@
|
||||
#include "openknx_idf/ets_device_runtime.h"
|
||||
|
||||
#include "knx/cemi_server.h"
|
||||
#include "knx/secure_application_layer.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
thread_local EtsDeviceRuntime* active_function_property_runtime = nullptr;
|
||||
|
||||
class ActiveFunctionPropertyRuntimeScope {
|
||||
public:
|
||||
explicit ActiveFunctionPropertyRuntimeScope(EtsDeviceRuntime* runtime)
|
||||
: previous_(active_function_property_runtime) {
|
||||
active_function_property_runtime = runtime;
|
||||
}
|
||||
|
||||
~ActiveFunctionPropertyRuntimeScope() { active_function_property_runtime = previous_; }
|
||||
|
||||
private:
|
||||
EtsDeviceRuntime* previous_;
|
||||
};
|
||||
|
||||
constexpr uint16_t kInvalidIndividualAddress = 0xffff;
|
||||
constexpr uint16_t kReg1DaliManufacturerId = 0x00a4;
|
||||
constexpr uint8_t kReg1DaliApplicationNumber = 0x01;
|
||||
constexpr uint8_t kReg1DaliApplicationVersion = 0x05;
|
||||
constexpr uint8_t kReg1DaliOrderNumber[10] = {'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
|
||||
|
||||
bool IsUsableIndividualAddress(uint16_t address) {
|
||||
return address != 0 && address != kInvalidIndividualAddress;
|
||||
}
|
||||
|
||||
bool IsErasedMemory(const uint8_t* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return true;
|
||||
}
|
||||
return std::all_of(data, data + size, [](uint8_t value) { return value == 0xff; });
|
||||
}
|
||||
|
||||
void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
|
||||
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||
device.deviceObject().orderNumber(kReg1DaliOrderNumber);
|
||||
const uint8_t program_version[5] = {0x00, 0xa4, 0x00, kReg1DaliApplicationNumber,
|
||||
kReg1DaliApplicationVersion};
|
||||
device.parameters().property(PID_PROG_VERSION)->write(program_version);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address,
|
||||
uint16_t tunnel_client_address)
|
||||
: nvs_namespace_(std::move(nvs_namespace)),
|
||||
platform_(nullptr, nvs_namespace_.c_str()),
|
||||
device_(platform_) {
|
||||
platform_.outboundCemiFrameCallback(&EtsDeviceRuntime::HandleOutboundCemiFrame, this);
|
||||
ApplyReg1DaliIdentity(device_, platform_);
|
||||
if (IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
const uint8_t* memory = platform_.getNonVolatileMemoryStart();
|
||||
const size_t memory_size = platform_.getNonVolatileMemorySize();
|
||||
if (!IsErasedMemory(memory, memory_size)) {
|
||||
device_.readMemory();
|
||||
}
|
||||
if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
|
||||
IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
if (auto* server = device_.getCemiServer()) {
|
||||
server->clientAddress(IsUsableIndividualAddress(tunnel_client_address)
|
||||
? tunnel_client_address
|
||||
: DefaultTunnelClientAddress(
|
||||
device_.deviceObject().individualAddress()));
|
||||
server->deviceAddressPropertiesTargetClient(false);
|
||||
server->tunnelFrameCallback(&EtsDeviceRuntime::EmitTunnelFrame, this);
|
||||
}
|
||||
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
|
||||
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
|
||||
#ifdef USE_DATASECURE
|
||||
device_.secureGroupWriteCallback(&EtsDeviceRuntime::HandleSecureGroupWrite, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
EtsDeviceRuntime::~EtsDeviceRuntime() {
|
||||
platform_.outboundCemiFrameCallback(nullptr, nullptr);
|
||||
#ifdef USE_DATASECURE
|
||||
device_.secureGroupWriteCallback(nullptr, nullptr);
|
||||
#endif
|
||||
device_.functionPropertyCallback(nullptr);
|
||||
device_.functionPropertyStateCallback(nullptr);
|
||||
if (auto* server = device_.getCemiServer()) {
|
||||
server->tunnelFrameCallback(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::individualAddress() const {
|
||||
return const_cast<Bau07B0&>(device_).deviceObject().individualAddress();
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::tunnelClientAddress() const {
|
||||
if (auto* server = const_cast<Bau07B0&>(device_).getCemiServer()) {
|
||||
return server->clientAddress();
|
||||
}
|
||||
return DefaultTunnelClientAddress(individualAddress());
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::configured() const { return const_cast<Bau07B0&>(device_).configured(); }
|
||||
|
||||
bool EtsDeviceRuntime::programmingMode() const {
|
||||
return const_cast<Bau07B0&>(device_).deviceObject().progMode();
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setProgrammingMode(bool enabled) {
|
||||
device_.deviceObject().progMode(enabled);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::toggleProgrammingMode() { setProgrammingMode(!programmingMode()); }
|
||||
|
||||
EtsMemorySnapshot EtsDeviceRuntime::snapshot() const {
|
||||
EtsMemorySnapshot out;
|
||||
auto& device = const_cast<Bau07B0&>(device_);
|
||||
out.configured = device.configured();
|
||||
out.individual_address = device.deviceObject().individualAddress();
|
||||
device.forEachEtsAssociation(
|
||||
[](uint16_t group_address, uint16_t group_object_number, void* context) {
|
||||
auto* associations = static_cast<std::vector<EtsAssociation>*>(context);
|
||||
if (associations != nullptr) {
|
||||
associations->push_back(EtsAssociation{group_address, group_object_number});
|
||||
}
|
||||
},
|
||||
&out.associations);
|
||||
std::sort(out.associations.begin(), out.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
if (lhs.group_address != rhs.group_address) {
|
||||
return lhs.group_address < rhs.group_address;
|
||||
}
|
||||
return lhs.group_object_number < rhs.group_object_number;
|
||||
});
|
||||
out.associations.erase(
|
||||
std::unique(out.associations.begin(), out.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
return lhs.group_address == rhs.group_address &&
|
||||
lhs.group_object_number == rhs.group_object_number;
|
||||
}),
|
||||
out.associations.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||
FunctionPropertyHandler state_handler) {
|
||||
command_handler_ = std::move(command_handler);
|
||||
state_handler_ = std::move(state_handler);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) {
|
||||
group_write_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setNetworkInterface(esp_netif_t* netif) {
|
||||
platform_.networkInterface(netif);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender) {
|
||||
auto* server = device_.getCemiServer();
|
||||
if (server == nullptr || data == nullptr || len < 2) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> frame_data(data, data + len);
|
||||
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
||||
const bool consumed = shouldConsumeTunnelFrame(frame);
|
||||
if (!consumed) {
|
||||
return false;
|
||||
}
|
||||
sender_ = std::move(sender);
|
||||
ActiveFunctionPropertyRuntimeScope callback_scope(this);
|
||||
server->frameReceived(frame);
|
||||
loop();
|
||||
sender_ = nullptr;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::handleBusFrame(const uint8_t* data, size_t len) {
|
||||
auto* data_link_layer = device_.getDataLinkLayer();
|
||||
if (data_link_layer == nullptr || data == nullptr || len < 2) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> frame_data(data, data + len);
|
||||
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
||||
const bool consumed = shouldConsumeBusFrame(frame);
|
||||
if (!consumed) {
|
||||
return false;
|
||||
}
|
||||
data_link_layer->externalFrameReceived(frame);
|
||||
loop();
|
||||
return consumed;
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::emitGroupValue(uint16_t group_object_number, const uint8_t* data,
|
||||
size_t len, CemiFrameSender sender) {
|
||||
if (group_object_number == 0 || data == nullptr || !sender || !device_.configured()) {
|
||||
return false;
|
||||
}
|
||||
auto& table = device_.groupObjectTable();
|
||||
if (group_object_number > table.entryCount()) {
|
||||
return false;
|
||||
}
|
||||
auto& group_object = table.get(group_object_number);
|
||||
if (len != group_object.valueSize() || group_object.valueRef() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (group_object.sizeInTelegram() == 0) {
|
||||
group_object.valueRef()[0] = data[0] & 0x01;
|
||||
} else {
|
||||
std::copy_n(data, len, group_object.valueRef());
|
||||
}
|
||||
sender_ = std::move(sender);
|
||||
group_object.objectWritten();
|
||||
loop();
|
||||
sender_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::loop() { device_.loop(); }
|
||||
|
||||
bool EtsDeviceRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) {
|
||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||
if (self == nullptr || !self->sender_) {
|
||||
return false;
|
||||
}
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
return true;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||
if (self == nullptr || !self->sender_) {
|
||||
return;
|
||||
}
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
uint8_t data_length, void* context) {
|
||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||
if (self == nullptr || !self->group_write_handler_) {
|
||||
return;
|
||||
}
|
||||
self->group_write_handler_(group_address, data, data_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data,
|
||||
uint8_t& result_length) {
|
||||
if (active_function_property_runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return DispatchFunctionProperty(&active_function_property_runtime->command_handler_, object_index,
|
||||
property_id, length, data, result_data, result_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::HandleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data,
|
||||
uint8_t& result_length) {
|
||||
if (active_function_property_runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return DispatchFunctionProperty(&active_function_property_runtime->state_handler_, object_index,
|
||||
property_id, length, data, result_data, result_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::DispatchFunctionProperty(FunctionPropertyHandler* handler,
|
||||
uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length) {
|
||||
if (handler == nullptr || !*handler || result_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> response;
|
||||
if (!(*handler)(object_index, property_id, data, length, &response)) {
|
||||
return false;
|
||||
}
|
||||
result_length = static_cast<uint8_t>(std::min<size_t>(response.size(), result_length));
|
||||
if (result_length > 0) {
|
||||
std::copy_n(response.begin(), result_length, result_data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::DefaultTunnelClientAddress(uint16_t individual_address) {
|
||||
if (!IsUsableIndividualAddress(individual_address)) {
|
||||
return 0x1101;
|
||||
}
|
||||
const uint16_t line_base = individual_address & 0xff00;
|
||||
uint16_t device = static_cast<uint16_t>((individual_address & 0x00ff) + 1);
|
||||
if (device == 0 || device > 0xff) {
|
||||
device = 1;
|
||||
}
|
||||
return static_cast<uint16_t>(line_base | device);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
switch (frame.messageCode()) {
|
||||
case M_PropRead_req:
|
||||
case M_PropWrite_req:
|
||||
case M_Reset_req:
|
||||
case M_FuncPropCommand_req:
|
||||
case M_FuncPropStateRead_req:
|
||||
return true;
|
||||
case L_data_req:
|
||||
if (!const_cast<Bau07B0&>(device_).configured() || programmingMode()) {
|
||||
return true;
|
||||
}
|
||||
if (frame.addressType() == IndividualAddress &&
|
||||
frame.destinationAddress() == individualAddress()) {
|
||||
return true;
|
||||
}
|
||||
#ifdef USE_DATASECURE
|
||||
return frame.addressType() == GroupAddress && frame.apdu().type() == SecureService;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::shouldConsumeBusFrame(CemiFrame& frame) const {
|
||||
#ifdef USE_DATASECURE
|
||||
return frame.messageCode() == L_data_ind && frame.addressType() == GroupAddress &&
|
||||
frame.apdu().type() == SecureService;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
void CollectAssociation(uint16_t group_address, uint16_t group_object_number,
|
||||
void* context) {
|
||||
auto* associations = static_cast<std::vector<EtsAssociation>*>(context);
|
||||
if (associations == nullptr) {
|
||||
return;
|
||||
}
|
||||
associations->push_back(EtsAssociation{group_address, group_object_number});
|
||||
}
|
||||
|
||||
bool IsErasedMemory(const uint8_t* data, size_t size) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return std::all_of(data, data + size, [](uint8_t value) { return value == 0xff; });
|
||||
}
|
||||
|
||||
constexpr uint16_t kReg1DaliManufacturerId = 0x00a4;
|
||||
constexpr uint8_t kReg1DaliApplicationNumber = 0x01;
|
||||
constexpr uint8_t kReg1DaliApplicationVersion = 0x05;
|
||||
constexpr uint8_t kReg1DaliOrderNumber[10] = {'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
|
||||
|
||||
void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
|
||||
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||
device.deviceObject().orderNumber(kReg1DaliOrderNumber);
|
||||
const uint8_t program_version[5] = {0x00, 0xa4, 0x00, kReg1DaliApplicationNumber,
|
||||
kReg1DaliApplicationVersion};
|
||||
device.parameters().property(PID_PROG_VERSION)->write(program_version);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace) {
|
||||
EspIdfPlatform platform(nullptr, nvs_namespace.c_str());
|
||||
EtsMemorySnapshot snapshot;
|
||||
const uint8_t* memory = platform.getNonVolatileMemoryStart();
|
||||
const size_t memory_size = platform.getNonVolatileMemorySize();
|
||||
if (memory == nullptr || memory_size == 0 || IsErasedMemory(memory, memory_size)) {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
auto device = std::make_unique<Bau07B0>(platform);
|
||||
ApplyReg1DaliIdentity(*device, platform);
|
||||
device->readMemory();
|
||||
|
||||
snapshot.configured = device->configured();
|
||||
snapshot.individual_address = device->deviceObject().individualAddress();
|
||||
device->forEachEtsAssociation(CollectAssociation, &snapshot.associations);
|
||||
std::sort(snapshot.associations.begin(), snapshot.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
if (lhs.group_address != rhs.group_address) {
|
||||
return lhs.group_address < rhs.group_address;
|
||||
}
|
||||
return lhs.group_object_number < rhs.group_object_number;
|
||||
});
|
||||
snapshot.associations.erase(
|
||||
std::unique(snapshot.associations.begin(), snapshot.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
return lhs.group_address == rhs.group_address &&
|
||||
lhs.group_object_number == rhs.group_object_number;
|
||||
}),
|
||||
snapshot.associations.end());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,374 @@
|
||||
#include "openknx_idf/security_storage.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_timer.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kTag = "openknx_sec";
|
||||
constexpr const char* kNamespace = "knx_sec";
|
||||
constexpr const char* kFactoryFdskKey = "factory_fdsk";
|
||||
constexpr size_t kFdskSize = 16;
|
||||
constexpr size_t kSerialSize = 6;
|
||||
constexpr size_t kFdskQrSize = 36;
|
||||
constexpr uint16_t kKnxManufacturerId = 0x00A4;
|
||||
constexpr const char* kProductIdentity = "REG1-Dali";
|
||||
constexpr const char* kManufacturerId = "00A4";
|
||||
constexpr const char* kApplicationNumber = "01";
|
||||
constexpr const char* kApplicationVersion = "05";
|
||||
constexpr const char* kDevelopmentStorage = "base_mac_derived_plain_nvs_development";
|
||||
constexpr char kFdskDerivationLabel[] = "DaliMaster REG1-Dali deterministic FDSK v1";
|
||||
constexpr uint8_t kCrc4Tab[16] = {
|
||||
0x0, 0x3, 0x6, 0x5, 0xc, 0xf, 0xa, 0x9,
|
||||
0xb, 0x8, 0xd, 0xe, 0x7, 0x4, 0x1, 0x2,
|
||||
};
|
||||
constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
constexpr char kHexAlphabet[] = "0123456789ABCDEF";
|
||||
|
||||
extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak));
|
||||
|
||||
bool ensureNvsReady() {
|
||||
const esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
if (nvs_flash_erase() != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
return nvs_flash_init() == ESP_OK;
|
||||
}
|
||||
return err == ESP_OK || err == ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
bool plausibleKey(const uint8_t* data) {
|
||||
const bool all_zero = std::all_of(data, data + kFdskSize, [](uint8_t value) {
|
||||
return value == 0x00;
|
||||
});
|
||||
const bool all_ff = std::all_of(data, data + kFdskSize, [](uint8_t value) {
|
||||
return value == 0xff;
|
||||
});
|
||||
return !all_zero && !all_ff;
|
||||
}
|
||||
|
||||
bool readBaseMac(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (esp_efuse_mac_get_default(data) == ESP_OK) {
|
||||
return true;
|
||||
}
|
||||
return esp_read_mac(data, ESP_MAC_WIFI_STA) == ESP_OK;
|
||||
}
|
||||
|
||||
void clearOpenKnxFdskCache() {
|
||||
if (knx_platform_clear_cached_fdsk != nullptr) {
|
||||
knx_platform_clear_cached_fdsk();
|
||||
}
|
||||
}
|
||||
|
||||
int fromHexDigit(char value) {
|
||||
if (value >= '0' && value <= '9') {
|
||||
return value - '0';
|
||||
}
|
||||
if (value >= 'a' && value <= 'f') {
|
||||
return value - 'a' + 10;
|
||||
}
|
||||
if (value >= 'A' && value <= 'F') {
|
||||
return value - 'A' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool parseHexKey(const std::string& value, uint8_t* out) {
|
||||
std::string digits;
|
||||
digits.reserve(value.size());
|
||||
for (char ch : value) {
|
||||
if (ch == ':' || ch == '-' || ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') {
|
||||
continue;
|
||||
}
|
||||
if (fromHexDigit(ch) < 0) {
|
||||
return false;
|
||||
}
|
||||
digits.push_back(ch);
|
||||
}
|
||||
if (digits.size() != kFdskSize * 2U) {
|
||||
return false;
|
||||
}
|
||||
for (size_t index = 0; index < kFdskSize; ++index) {
|
||||
const int hi = fromHexDigit(digits[index * 2U]);
|
||||
const int lo = fromHexDigit(digits[index * 2U + 1U]);
|
||||
if (hi < 0 || lo < 0) {
|
||||
return false;
|
||||
}
|
||||
out[index] = static_cast<uint8_t>((hi << 4) | lo);
|
||||
}
|
||||
return plausibleKey(out);
|
||||
}
|
||||
|
||||
bool loadKnxSerialNumber(uint8_t* serial) {
|
||||
if (serial == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, kSerialSize> mac{};
|
||||
if (!readBaseMac(mac.data())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
serial[0] = static_cast<uint8_t>((kKnxManufacturerId >> 8) & 0xff);
|
||||
serial[1] = static_cast<uint8_t>(kKnxManufacturerId & 0xff);
|
||||
std::copy(mac.begin() + 2, mac.end(), serial + 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deriveFactoryFdskFromSerial(const uint8_t* serial, uint8_t* key) {
|
||||
if (serial == nullptr || key == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, sizeof(kFdskDerivationLabel) - 1 + kSerialSize> material{};
|
||||
std::copy(kFdskDerivationLabel, kFdskDerivationLabel + sizeof(kFdskDerivationLabel) - 1,
|
||||
material.begin());
|
||||
std::copy(serial, serial + kSerialSize, material.begin() + sizeof(kFdskDerivationLabel) - 1);
|
||||
|
||||
std::array<uint8_t, 32> digest{};
|
||||
if (mbedtls_sha256(material.data(), material.size(), digest.data(), 0) != 0) {
|
||||
return false;
|
||||
}
|
||||
std::copy(digest.begin(), digest.begin() + kFdskSize, key);
|
||||
if (!plausibleKey(key)) {
|
||||
key[kFdskSize - 1] ^= 0xA5;
|
||||
}
|
||||
return plausibleKey(key);
|
||||
}
|
||||
|
||||
void syncFactoryFdskToNvs(const uint8_t* data) {
|
||||
if (data == nullptr || !plausibleKey(data) || !ensureNvsReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<uint8_t, kFdskSize> stored{};
|
||||
size_t stored_size = stored.size();
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t err = nvs_open(kNamespace, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
err = nvs_get_blob(handle, kFactoryFdskKey, stored.data(), &stored_size);
|
||||
if (err == ESP_OK && stored_size == stored.size() &&
|
||||
std::equal(stored.begin(), stored.end(), data)) {
|
||||
nvs_close(handle);
|
||||
return;
|
||||
}
|
||||
err = nvs_set_blob(handle, kFactoryFdskKey, data, kFdskSize);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(handle);
|
||||
}
|
||||
nvs_close(handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to mirror deterministic KNX factory FDSK: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
clearOpenKnxFdskCache();
|
||||
}
|
||||
|
||||
uint8_t crc4Array(const uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
crc = kCrc4Tab[crc ^ (data[i] >> 4)];
|
||||
crc = kCrc4Tab[crc ^ (data[i] & 0x0f)];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
std::string toBase32NoPadding(const uint8_t* data, size_t len) {
|
||||
std::string result;
|
||||
result.reserve(((len * 8) + 4) / 5);
|
||||
|
||||
uint32_t buffer = 0;
|
||||
int bits_left = 0;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
buffer = (buffer << 8) | data[i];
|
||||
bits_left += 8;
|
||||
while (bits_left >= 5) {
|
||||
const uint8_t index = static_cast<uint8_t>((buffer >> (bits_left - 5)) & 0x1f);
|
||||
result.push_back(kBase32Alphabet[index]);
|
||||
bits_left -= 5;
|
||||
}
|
||||
}
|
||||
|
||||
if (bits_left > 0) {
|
||||
const uint8_t index = static_cast<uint8_t>((buffer << (5 - bits_left)) & 0x1f);
|
||||
result.push_back(kBase32Alphabet[index]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string toHex(const uint8_t* data, size_t len) {
|
||||
std::string result;
|
||||
result.reserve(len * 2);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
result.push_back(kHexAlphabet[(data[i] >> 4) & 0x0f]);
|
||||
result.push_back(kHexAlphabet[data[i] & 0x0f]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string generateFdskQrCode(const uint8_t* serial, const uint8_t* key) {
|
||||
std::array<uint8_t, kSerialSize + kFdskSize + 1> buffer{};
|
||||
std::copy(serial, serial + kSerialSize, buffer.begin());
|
||||
std::copy(key, key + kFdskSize, buffer.begin() + kSerialSize);
|
||||
buffer[kSerialSize + kFdskSize] = static_cast<uint8_t>((crc4Array(buffer.data(), buffer.size() - 1) << 4) & 0xff);
|
||||
|
||||
std::string encoded = toBase32NoPadding(buffer.data(), buffer.size());
|
||||
if (encoded.size() > kFdskQrSize) {
|
||||
encoded.resize(kFdskQrSize);
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
std::string formatFdskLabel(const std::string& qr_code) {
|
||||
std::string label;
|
||||
label.reserve(qr_code.size() + (qr_code.size() / 6));
|
||||
for (size_t i = 0; i < qr_code.size(); ++i) {
|
||||
if (i != 0 && (i % 6) == 0) {
|
||||
label.push_back('-');
|
||||
}
|
||||
label.push_back(qr_code[i]);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
std::string fnv1aHex(const std::string& value) {
|
||||
uint32_t hash = 2166136261u;
|
||||
for (unsigned char ch : value) {
|
||||
hash ^= ch;
|
||||
hash *= 16777619u;
|
||||
}
|
||||
std::array<uint8_t, 4> bytes{
|
||||
static_cast<uint8_t>((hash >> 24) & 0xff),
|
||||
static_cast<uint8_t>((hash >> 16) & 0xff),
|
||||
static_cast<uint8_t>((hash >> 8) & 0xff),
|
||||
static_cast<uint8_t>(hash & 0xff),
|
||||
};
|
||||
return toHex(bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
bool LoadFactoryFdsk(uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len < kFdskSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<uint8_t, kSerialSize> serial{};
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
if (!loadKnxSerialNumber(serial.data()) ||
|
||||
!deriveFactoryFdskFromSerial(serial.data(), key.data())) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(data, key.data(), kFdskSize);
|
||||
syncFactoryFdskToNvs(key.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
FactoryFdskInfo LoadFactoryFdskInfo() {
|
||||
FactoryFdskInfo info;
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
std::array<uint8_t, kSerialSize> serial{};
|
||||
if (!loadKnxSerialNumber(serial.data()) || !LoadFactoryFdsk(key.data(), key.size())) {
|
||||
return info;
|
||||
}
|
||||
|
||||
info.available = true;
|
||||
info.serialNumber = toHex(serial.data(), serial.size());
|
||||
info.qrCode = generateFdskQrCode(serial.data(), key.data());
|
||||
info.label = formatFdskLabel(info.qrCode);
|
||||
return info;
|
||||
}
|
||||
|
||||
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
const bool stored = LoadFactoryFdsk(key.data(), key.size());
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
if (info != nullptr) {
|
||||
*info = LoadFactoryFdskInfo();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
if (!parseHexKey(hex_key, key.data())) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, kSerialSize> serial{};
|
||||
std::array<uint8_t, kFdskSize> derived{};
|
||||
const bool stored = loadKnxSerialNumber(serial.data()) &&
|
||||
deriveFactoryFdskFromSerial(serial.data(), derived.data()) &&
|
||||
std::equal(key.begin(), key.end(), derived.begin());
|
||||
if (stored) {
|
||||
syncFactoryFdskToNvs(derived.data());
|
||||
}
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
std::fill(derived.begin(), derived.end(), 0);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
if (info != nullptr) {
|
||||
*info = LoadFactoryFdskInfo();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResetFactoryFdskCache(FactoryFdskInfo* info) {
|
||||
clearOpenKnxFdskCache();
|
||||
const auto loaded = LoadFactoryFdskInfo();
|
||||
if (info != nullptr) {
|
||||
*info = loaded;
|
||||
}
|
||||
return loaded.available;
|
||||
}
|
||||
|
||||
FactoryCertificatePayload BuildFactoryCertificatePayload() {
|
||||
FactoryCertificatePayload payload;
|
||||
const auto info = LoadFactoryFdskInfo();
|
||||
if (!info.available) {
|
||||
return payload;
|
||||
}
|
||||
payload.available = true;
|
||||
payload.productIdentity = kProductIdentity;
|
||||
payload.manufacturerId = kManufacturerId;
|
||||
payload.applicationNumber = kApplicationNumber;
|
||||
payload.applicationVersion = kApplicationVersion;
|
||||
payload.serialNumber = info.serialNumber;
|
||||
payload.fdskLabel = info.label;
|
||||
payload.fdskQrCode = info.qrCode;
|
||||
payload.storage = kDevelopmentStorage;
|
||||
payload.createdAt = "uptime_us:" + std::to_string(esp_timer_get_time());
|
||||
payload.checksum = fnv1aHex(payload.productIdentity + "|" + payload.manufacturerId + "|" +
|
||||
payload.applicationNumber + "|" + payload.applicationVersion + "|" +
|
||||
payload.serialNumber + "|" + payload.fdskLabel + "|" +
|
||||
payload.fdskQrCode + "|" + payload.createdAt);
|
||||
return payload;
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
|
||||
extern "C" bool knx_platform_get_fdsk(uint8_t* data, size_t len) {
|
||||
return gateway::openknx::LoadFactoryFdsk(data, len);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
#include "openknx_idf/tpuart_uart_interface.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "soc/uart_periph.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
constexpr const char* kTag = "openknx_tpuart";
|
||||
|
||||
bool ResolveUartIoPin(uart_port_t uart_port, int configured_pin, uint32_t pin_index,
|
||||
int* resolved_pin) {
|
||||
if (resolved_pin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (configured_pin >= 0) {
|
||||
*resolved_pin = configured_pin;
|
||||
return true;
|
||||
}
|
||||
if (uart_port < 0 || uart_port >= SOC_UART_NUM || pin_index >= SOC_UART_PINS_COUNT) {
|
||||
*resolved_pin = UART_PIN_NO_CHANGE;
|
||||
return false;
|
||||
}
|
||||
const int default_pin = uart_periph_signal[uart_port].pins[pin_index].default_gpio;
|
||||
if (default_pin < 0) {
|
||||
*resolved_pin = UART_PIN_NO_CHANGE;
|
||||
return false;
|
||||
}
|
||||
*resolved_pin = default_pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TpuartUartInterface::TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
|
||||
size_t rx_buffer_size, size_t tx_buffer_size,
|
||||
bool nine_bit_mode)
|
||||
: uart_port_(uart_port),
|
||||
tx_pin_(tx_pin),
|
||||
rx_pin_(rx_pin),
|
||||
rx_buffer_size_(rx_buffer_size),
|
||||
tx_buffer_size_(tx_buffer_size),
|
||||
nine_bit_mode_(nine_bit_mode) {}
|
||||
|
||||
TpuartUartInterface::~TpuartUartInterface() { end(); }
|
||||
|
||||
void TpuartUartInterface::begin(int baud) {
|
||||
if (_running) {
|
||||
end();
|
||||
}
|
||||
|
||||
uart_config_t config{};
|
||||
config.baud_rate = baud;
|
||||
config.data_bits = UART_DATA_8_BITS;
|
||||
config.parity = nine_bit_mode_ ? UART_PARITY_EVEN : UART_PARITY_DISABLE;
|
||||
config.stop_bits = UART_STOP_BITS_1;
|
||||
config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
int tx_pin = UART_PIN_NO_CHANGE;
|
||||
int rx_pin = UART_PIN_NO_CHANGE;
|
||||
if (!ResolveUartIoPin(uart_port_, tx_pin_, SOC_UART_TX_PIN_IDX, &tx_pin) ||
|
||||
!ResolveUartIoPin(uart_port_, rx_pin_, SOC_UART_RX_PIN_IDX, &rx_pin)) {
|
||||
ESP_LOGE(kTag, "UART%d has no ESP-IDF default TX/RX pin; configure explicit pins",
|
||||
uart_port_);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = uart_param_config(uart_port_, &config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure UART%d: %s", uart_port_, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_set_pin(uart_port_, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to route UART%d pins tx=%d rx=%d: %s", uart_port_, tx_pin,
|
||||
rx_pin, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_driver_install(uart_port_, rx_buffer_size_, tx_buffer_size_, 0, nullptr, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to install UART%d driver: %s", uart_port_, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
uart_set_rx_full_threshold(uart_port_, 1);
|
||||
_running = true;
|
||||
}
|
||||
|
||||
void TpuartUartInterface::end() {
|
||||
if (!_running) {
|
||||
return;
|
||||
}
|
||||
_running = false;
|
||||
uart_driver_delete(uart_port_);
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::available() {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
size_t len = 0;
|
||||
return uart_get_buffered_data_len(uart_port_, &len) == ESP_OK && len > 0;
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::availableForWrite() {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
size_t len = 0;
|
||||
return uart_get_tx_buffer_free_size(uart_port_, &len) == ESP_OK && len > 0;
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::write(char value) {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
return uart_write_bytes(uart_port_, &value, 1) == 1;
|
||||
}
|
||||
|
||||
int TpuartUartInterface::read() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
uint8_t value = 0;
|
||||
return uart_read_bytes(uart_port_, &value, 1, 0) == 1 ? value : -1;
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::overflow() { return overflow_.exchange(false); }
|
||||
|
||||
void TpuartUartInterface::flush() {
|
||||
if (_running) {
|
||||
uart_flush(uart_port_);
|
||||
}
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::hasCallback() { return false; }
|
||||
|
||||
void TpuartUartInterface::registerCallback(std::function<bool()> callback) {
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
Submodule
+1
Submodule knx added at 346b704cbe
Submodule
+1
Submodule knx_dali_gw added at 6064d84520
Submodule
+1
Submodule tpuart added at f8c01e6a32
Reference in New Issue
Block a user