Compare commits
6 Commits
main
...
39ef630608
| Author | SHA1 | Date | |
|---|---|---|---|
| 39ef630608 | |||
| b74367e5a0 | |||
| df1dd472cc | |||
| 888d021343 | |||
| de0edd5ad9 | |||
| e58115d303 |
@@ -4,6 +4,8 @@ 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.
|
||||
@@ -12,19 +14,37 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
||||
- `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, ETS cEMI programming support, UDP multicast/unicast plumbing, and a native TP-UART interface without the Arduino framework.
|
||||
- `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, W5500 SPI Ethernet startup/teardown, 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, 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, and BOOT-button Wi-Fi credential clearing, and an optional `gateway_485_control` bridge that claims UART0 for Lua-compatible framed command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE and wired Ethernet are enabled by default, W5500 initialization and startup probe failures are ignored by default for boards without populated Ethernet hardware by fully disabling Ethernet for that boot, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, W5500 SPI Ethernet with DHCP, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, setup AP GPIO entry, and optional Wi-Fi credential reset GPIO handling, and an optional `gateway_485_control` bridge that claims UART0 for 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 default IO routing for that UART, not disabled. 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
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -542,8 +546,11 @@ config GATEWAY_MODBUS_UNIT_ID
|
||||
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 0 2
|
||||
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"
|
||||
@@ -621,6 +628,41 @@ config GATEWAY_START_KNX_BRIDGE_ENABLED
|
||||
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
|
||||
@@ -651,32 +693,78 @@ config GATEWAY_KNX_MULTICAST_ADDRESS
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_MULTICAST_ENABLED
|
||||
default "224.0.23.12"
|
||||
|
||||
config GATEWAY_KNX_INDIVIDUAL_ADDRESS
|
||||
int "KNX individual address raw value"
|
||||
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 4353
|
||||
default 65281
|
||||
help
|
||||
Raw 16-bit individual address advertised to KNXnet/IP tunnel clients.
|
||||
The default 4353 is 1.1.1.
|
||||
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 0 2
|
||||
default 1
|
||||
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"
|
||||
@@ -688,7 +776,7 @@ config GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE
|
||||
int "KNX/IP bridge task stack bytes"
|
||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
range 6144 24576
|
||||
default 8192
|
||||
default 12288
|
||||
|
||||
config GATEWAY_BRIDGE_KNX_TASK_PRIORITY
|
||||
int "KNX/IP bridge task priority"
|
||||
@@ -881,23 +969,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
|
||||
@@ -40,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
|
||||
@@ -193,7 +208,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE 8192
|
||||
#define CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE 12288
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY
|
||||
@@ -213,11 +228,23 @@
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS
|
||||
#define CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS 4353
|
||||
#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
|
||||
#define CONFIG_GATEWAY_KNX_TP_UART_PORT -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_KNX_TP_TX_PIN
|
||||
@@ -579,21 +606,22 @@ bool ValidateChannelBindings() {
|
||||
|
||||
if (kKnxBridgeSupported) {
|
||||
const int knx_uart = CONFIG_GATEWAY_KNX_TP_UART_PORT;
|
||||
if (k485ControlEnabled && knx_uart == 0) {
|
||||
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 && kConsoleOnUart0) {
|
||||
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 (kModbusBridgeSupported && kModbusDefaultSerialTransport &&
|
||||
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 (channels[i].enabled && channels[i].serial_phy && channels[i].uart_port == knx_uart) {
|
||||
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;
|
||||
@@ -838,8 +866,22 @@ extern "C" void app_main(void) {
|
||||
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;
|
||||
@@ -898,7 +940,10 @@ extern "C" void app_main(void) {
|
||||
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
|
||||
@@ -908,6 +953,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,
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
otadata, data, ota, 0xf000, 0x2000,
|
||||
phy_init, data, phy, 0x11000, 0x1000,
|
||||
factory, app, factory, 0x20000, 0x200000,
|
||||
storage, data, spiffs, 0x220000, 0x180000,
|
||||
factory, app, factory, 0x20000, 0x280000,
|
||||
storage, data, spiffs, 0x300000, 0x100000,
|
||||
|
||||
|
+34
-32
@@ -596,7 +596,7 @@ CONFIG_PARTITION_TABLE_MD5=y
|
||||
#
|
||||
# Gateway App
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL_COUNT=2
|
||||
CONFIG_GATEWAY_CHANNEL_COUNT=1
|
||||
|
||||
#
|
||||
# Gateway Channel 1
|
||||
@@ -617,17 +617,6 @@ CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS=100
|
||||
#
|
||||
# Gateway Channel 2
|
||||
#
|
||||
CONFIG_GATEWAY_CHANNEL2_GW_ID=4
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE is not set
|
||||
# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set
|
||||
CONFIG_GATEWAY_CHANNEL2_PHY_UART2=y
|
||||
CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_PIN=6
|
||||
CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_PIN=7
|
||||
CONFIG_GATEWAY_CHANNEL2_SERIAL_BAUDRATE=9600
|
||||
CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER=512
|
||||
CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_BUFFER=512
|
||||
CONFIG_GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS=100
|
||||
# end of Gateway Channel 2
|
||||
|
||||
#
|
||||
@@ -663,17 +652,17 @@ CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||
#
|
||||
# Gateway Wired Ethernet
|
||||
#
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=1
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=14
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=13
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=12
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=15
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=4
|
||||
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=36
|
||||
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=5
|
||||
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=3072
|
||||
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=4096
|
||||
# end of Gateway Wired Ethernet
|
||||
|
||||
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
||||
@@ -688,17 +677,26 @@ 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_INDIVIDUAL_ADDRESS=4353
|
||||
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_BRIDGE_KNX_TASK_STACK_SIZE=8192
|
||||
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
|
||||
@@ -722,6 +720,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
|
||||
|
||||
@@ -1071,12 +1071,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
|
||||
@@ -1807,7 +1807,7 @@ 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
|
||||
@@ -2180,8 +2180,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
|
||||
@@ -2200,13 +2199,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
|
||||
|
||||
@@ -2939,7 +2941,7 @@ CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y
|
||||
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_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
|
||||
|
||||
@@ -7,6 +7,8 @@ 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
|
||||
|
||||
@@ -15,4 +17,5 @@ CONFIG_ETH_USE_SPI_ETHERNET=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_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||
|
||||
+55
-18
@@ -656,6 +656,26 @@ 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
|
||||
@@ -666,7 +686,29 @@ 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 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_BRIDGE_KNX_TASK_STACK_SIZE=8192
|
||||
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
|
||||
@@ -675,16 +717,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=y
|
||||
CONFIG_GATEWAY_485_CONTROL_BAUDRATE=9600
|
||||
CONFIG_GATEWAY_485_CONTROL_TX_PIN=-1
|
||||
CONFIG_GATEWAY_485_CONTROL_RX_PIN=-1
|
||||
CONFIG_GATEWAY_485_CONTROL_RX_BUFFER=256
|
||||
CONFIG_GATEWAY_485_CONTROL_TX_BUFFER=256
|
||||
CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS=20
|
||||
CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS=20
|
||||
CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE=4096
|
||||
CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY=4
|
||||
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
|
||||
# end of Gateway Startup Services
|
||||
|
||||
#
|
||||
@@ -698,6 +731,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
|
||||
|
||||
@@ -1047,12 +1082,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
|
||||
@@ -2156,8 +2191,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
|
||||
@@ -2176,13 +2210,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
|
||||
|
||||
|
||||
@@ -290,8 +290,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));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -16,6 +17,7 @@
|
||||
namespace gateway {
|
||||
|
||||
class DaliDomainService;
|
||||
struct DaliRawFrame;
|
||||
class GatewayCache;
|
||||
|
||||
struct GatewayBridgeServiceConfig {
|
||||
@@ -36,7 +38,7 @@ struct GatewayBridgeServiceConfig {
|
||||
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{8192};
|
||||
uint32_t knx_task_stack_size{12288};
|
||||
UBaseType_t knx_task_priority{5};
|
||||
std::optional<GatewayKnxConfig> default_knx_config;
|
||||
};
|
||||
@@ -65,6 +67,15 @@ 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,
|
||||
@@ -74,6 +85,7 @@ class GatewayBridgeService {
|
||||
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
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_knx.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_cpp esp_driver_uart freertos log lwip openknx_idf
|
||||
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)
|
||||
@@ -5,16 +5,19 @@
|
||||
|
||||
#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>
|
||||
|
||||
@@ -60,7 +63,12 @@ struct GatewayKnxConfig {
|
||||
uint8_t main_group{0};
|
||||
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
||||
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
||||
uint16_t individual_address{0x1101};
|
||||
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;
|
||||
};
|
||||
@@ -110,6 +118,7 @@ struct GatewayKnxCommissioningBallast {
|
||||
|
||||
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);
|
||||
@@ -131,6 +140,8 @@ class GatewayKnxBridge {
|
||||
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);
|
||||
@@ -185,74 +196,173 @@ class GatewayKnxBridge {
|
||||
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);
|
||||
const ::sockaddr_in& remote, uint8_t connection_type,
|
||||
uint16_t tunnel_address);
|
||||
void sendRoutingIndication(const uint8_t* data, size_t len);
|
||||
bool handleOpenKnxTunnelFrame(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 effectiveIndividualAddress() const;
|
||||
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};
|
||||
uint8_t tunnel_channel_id_{1};
|
||||
uint8_t expected_tunnel_sequence_{0};
|
||||
uint8_t tunnel_send_sequence_{0};
|
||||
bool tunnel_connected_{false};
|
||||
::sockaddr_in tunnel_remote_{};
|
||||
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_;
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -560,7 +560,7 @@ std::optional<GatewayModbusConfig> GatewayModbusConfigFromValue(const DaliValue*
|
||||
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, 0, 2);
|
||||
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,
|
||||
|
||||
@@ -56,8 +56,10 @@ struct GatewayNetworkServiceConfig {
|
||||
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};
|
||||
|
||||
@@ -909,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() {
|
||||
@@ -985,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;
|
||||
}
|
||||
|
||||
@@ -1349,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ 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"
|
||||
@@ -31,6 +37,7 @@ idf_component_register(
|
||||
"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}
|
||||
@@ -42,11 +49,13 @@ idf_component_register(
|
||||
esp_driver_gpio
|
||||
esp_driver_uart
|
||||
esp_netif
|
||||
esp_system
|
||||
esp_timer
|
||||
esp_wifi
|
||||
freertos
|
||||
log
|
||||
lwip
|
||||
mbedtls
|
||||
nvs_flash
|
||||
)
|
||||
|
||||
@@ -58,6 +67,10 @@ target_compile_definitions(${COMPONENT_LIB} PUBLIC
|
||||
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
|
||||
)
|
||||
|
||||
@@ -14,10 +14,15 @@ 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;
|
||||
|
||||
@@ -54,6 +59,8 @@ class EspIdfPlatform : public Platform {
|
||||
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
|
||||
@@ -17,26 +17,41 @@ 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);
|
||||
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);
|
||||
@@ -48,11 +63,13 @@ class EtsDeviceRuntime {
|
||||
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_;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#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"
|
||||
|
||||
@@ -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
|
||||
@@ -20,12 +20,32 @@ namespace {
|
||||
constexpr const char* kTag = "openknx_idf";
|
||||
constexpr const char* kEepromKey = "eeprom";
|
||||
|
||||
esp_netif_t* findDefaultNetif() {
|
||||
if (auto* sta = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF")) {
|
||||
return sta;
|
||||
bool readBaseMac(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (auto* eth = esp_netif_get_handle_from_ifkey("ETH_DEF")) {
|
||||
return eth;
|
||||
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;
|
||||
}
|
||||
@@ -51,6 +71,19 @@ EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* 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_; }
|
||||
@@ -90,7 +123,7 @@ void EspIdfPlatform::macAddress(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (esp_read_mac(data, ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||
if (!readBaseMac(data)) {
|
||||
std::memset(data, 0, 6);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +131,7 @@ void EspIdfPlatform::macAddress(uint8_t* data) {
|
||||
uint32_t EspIdfPlatform::uniqueSerialNumber() {
|
||||
uint8_t mac[6]{};
|
||||
macAddress(mac);
|
||||
return (static_cast<uint32_t>(mac[0]) << 24) | (static_cast<uint32_t>(mac[1]) << 16) |
|
||||
return (static_cast<uint32_t>(mac[2]) << 24) | (static_cast<uint32_t>(mac[3]) << 16) |
|
||||
(static_cast<uint32_t>(mac[4]) << 8) | mac[5];
|
||||
}
|
||||
|
||||
@@ -113,7 +146,7 @@ void EspIdfPlatform::fatalError() {
|
||||
|
||||
void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
closeMultiCast();
|
||||
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
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;
|
||||
@@ -124,7 +157,8 @@ void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
|
||||
sockaddr_in bind_addr{};
|
||||
bind_addr.sin_family = AF_INET;
|
||||
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
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);
|
||||
@@ -139,11 +173,20 @@ void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
|
||||
ip_mreq mreq{};
|
||||
mreq.imr_multiaddr.s_addr = htonl(addr);
|
||||
mreq.imr_interface.s_addr = currentIpAddress();
|
||||
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));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "openknx_idf/ets_device_runtime.h"
|
||||
|
||||
#include "knx/cemi_server.h"
|
||||
#include "knx/secure_application_layer.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -36,6 +37,13 @@ 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());
|
||||
@@ -48,28 +56,44 @@ void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
} // namespace
|
||||
|
||||
EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address)
|
||||
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);
|
||||
}
|
||||
device_.readMemory();
|
||||
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(DefaultTunnelClientAddress(device_.deviceObject().individualAddress()));
|
||||
server->clientAddress(IsUsableIndividualAddress(tunnel_client_address)
|
||||
? tunnel_client_address
|
||||
: DefaultTunnelClientAddress(
|
||||
device_.deviceObject().individualAddress()));
|
||||
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()) {
|
||||
@@ -90,6 +114,16 @@ uint16_t EtsDeviceRuntime::tunnelClientAddress() const {
|
||||
|
||||
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_);
|
||||
@@ -126,6 +160,14 @@ void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler comma
|
||||
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();
|
||||
@@ -146,8 +188,58 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
||||
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_) {
|
||||
@@ -156,6 +248,15 @@ void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
||||
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,
|
||||
@@ -217,11 +318,30 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
case M_FuncPropStateRead_req:
|
||||
return true;
|
||||
case L_data_req:
|
||||
return frame.addressType() == IndividualAddress &&
|
||||
frame.destinationAddress() == individualAddress();
|
||||
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,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);
|
||||
}
|
||||
+1
-1
Submodule knx updated: 339d8472e7...5d7d6e573b
Reference in New Issue
Block a user