Compare commits

...

2 Commits

10 changed files with 766 additions and 260 deletions
+9 -1
View File
@@ -12,7 +12,7 @@ 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. - `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges. - `gateway_cache/`: 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. - `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_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_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_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
@@ -26,6 +26,14 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
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, 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.
## 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.
When `GATEWAY_KNX_SECURITY_DEV_ENDPOINTS` is enabled, the bridge HTTP action surface exposes development-only operations for reading, writing, generating, and resetting the factory setup key, exporting the factory certificate payload, and clearing local KNX security failure diagnostics. These endpoints require explicit confirmation fields in the JSON body and should stay disabled in production builds. The default development storage mode is plain NVS via `GATEWAY_KNX_SECURITY_PLAIN_NVS`; production builds should replace that with encrypted NVS, flash encryption, and secure boot before handling real commissioning keys.
The normal bridge status response includes a `knx.security` object with compile-time capability flags, storage mode, factory setup key metadata, factory certificate metadata, and security failure counters/log entries. Secret FDSK strings are returned only by the explicit development actions, not by passive status polling.
## Modbus ## Modbus
Modbus TCP, RTU, and ASCII are owned by `gateway/components/gateway_modbus` and started through the per-channel bridge service. The gateway keeps the existing bridge config JSON shape with a top-level `modbus` object containing `transport`, `host`, `port`, and `unitID`, and now adds nested serial UART settings for RTU/ASCII. Parsing and runtime behavior live in the gateway project rather than in `dali_cpp`. Modbus TCP, RTU, and ASCII are owned by `gateway/components/gateway_modbus` and started through the per-channel bridge service. The gateway keeps the existing bridge config JSON shape with a top-level `modbus` object containing `transport`, `host`, `port`, and `unitID`, and now adds nested serial UART settings for RTU/ASCII. Parsing and runtime behavior live in the gateway project rather than in `dali_cpp`.
+2 -2
View File
@@ -2,5 +2,5 @@
nvs, data, nvs, 0x9000, 0x6000, nvs, data, nvs, 0x9000, 0x6000,
otadata, data, ota, 0xf000, 0x2000, otadata, data, ota, 0xf000, 0x2000,
phy_init, data, phy, 0x11000, 0x1000, phy_init, data, phy, 0x11000, 0x1000,
factory, app, factory, 0x20000, 0x200000, factory, app, factory, 0x20000, 0x280000,
storage, data, spiffs, 0x220000, 0x180000, storage, data, spiffs, 0x300000, 0x100000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x6000
3 otadata data ota 0xf000 0x2000
4 phy_init data phy 0x11000 0x1000
5 factory app factory 0x20000 0x200000 0x280000
6 storage data spiffs 0x220000 0x300000 0x180000 0x100000
+8 -8
View File
@@ -663,15 +663,15 @@ CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
# #
# Gateway Wired Ethernet # Gateway Wired Ethernet
# #
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=1 CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=2
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=14 CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=48
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=13 CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=47
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=12 CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=33
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=15 CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=34
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=4 CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=36
CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0 CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0
CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=36 CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=40
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=5 CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=-1
CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1 CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=3072 CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=3072
# end of Gateway Wired Ethernet # end of Gateway Wired Ethernet
+247 -247
View File
@@ -1093,253 +1093,7 @@ CONFIG_BT_CTRL_SCAN_DUPL_CACHE_SIZE=100
CONFIG_BT_CTRL_DUPL_SCAN_CACHE_REFRESH_PERIOD=0 CONFIG_BT_CTRL_DUPL_SCAN_CACHE_REFRESH_PERIOD=0
# CONFIG_BT_CTRL_BLE_MESH_SCAN_DUPL_EN is not set # CONFIG_BT_CTRL_BLE_MESH_SCAN_DUPL_EN is not set
# CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EN is not set # CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EN is not set
CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_DIS=y # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set
CONFIG_BT_CTRL_COEX_PHY_CODED_TX_RX_TLIM_EFF=0
#
# MODEM SLEEP Options
#
# CONFIG_BT_CTRL_MODEM_SLEEP is not set
# end of MODEM SLEEP Options
CONFIG_BT_CTRL_SLEEP_MODE_EFF=0
CONFIG_BT_CTRL_SLEEP_CLOCK_EFF=0
CONFIG_BT_CTRL_HCI_TL_EFF=1
# CONFIG_BT_CTRL_AGC_RECORRECT_EN is not set
# CONFIG_BT_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set
# CONFIG_BT_BLE_ADV_DATA_LENGTH_ZERO_AUX is not set
CONFIG_BT_CTRL_CHAN_ASS_EN=y
CONFIG_BT_CTRL_LE_PING_EN=y
#
# BLE disconnects when Instant Passed (0x28) occurs
#
# CONFIG_BT_CTRL_BLE_LLCP_CONN_UPDATE is not set
# CONFIG_BT_CTRL_BLE_LLCP_CHAN_MAP_UPDATE is not set
# CONFIG_BT_CTRL_BLE_LLCP_PHY_UPDATE is not set
# end of BLE disconnects when Instant Passed (0x28) occurs
# CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY is not set
CONFIG_BT_CTRL_DTM_ENABLE=y
CONFIG_BT_CTRL_BLE_MASTER=y
# CONFIG_BT_CTRL_BLE_TEST is not set
CONFIG_BT_CTRL_BLE_SCAN=y
CONFIG_BT_CTRL_BLE_SECURITY_ENABLE=y
CONFIG_BT_CTRL_BLE_ADV=y
# CONFIG_BT_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set
#
# Controller debug log Options (Experimental)
#
# end of Controller debug log Options (Experimental)
# end of Controller Options
#
# Common Options
#
CONFIG_BT_ALARM_MAX_NUM=50
CONFIG_BT_SMP_CRYPTO_STACK_TINYCRYPT=y
# CONFIG_BT_SMP_CRYPTO_STACK_MBEDTLS is not set
#
# BLE Log
#
# CONFIG_BLE_LOG_ENABLED is not set
# end of BLE Log
# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set
# CONFIG_BT_BLE_LOG_UHCI_OUT_ENABLED is not set
# CONFIG_BT_LE_USED_MEM_STATISTICS_ENABLED is not set
# end of Common Options
# CONFIG_BT_HCI_LOG_DEBUG_EN is not set
# end of Bluetooth
# CONFIG_BLE_MESH is not set
#
# Console Library
#
# CONFIG_CONSOLE_SORTED_HELP is not set
# end of Console Library
#
# Driver Configurations
#
#
# Legacy TWAI Driver Configurations
#
# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set
CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y
# end of Legacy TWAI Driver Configurations
#
# Legacy ADC Driver Configuration
#
# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set
#
# Legacy ADC Calibration Configuration
#
# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set
# end of Legacy ADC Calibration Configuration
# end of Legacy ADC Driver Configuration
#
# Legacy MCPWM Driver Configurations
#
# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy MCPWM Driver Configurations
#
# Legacy Timer Group Driver Configurations
#
# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy Timer Group Driver Configurations
#
# Legacy RMT Driver Configurations
#
# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy RMT Driver Configurations
#
# Legacy I2S Driver Configurations
#
# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy I2S Driver Configurations
#
# Legacy I2C Driver Configurations
#
# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy I2C Driver Configurations
#
# Legacy PCNT Driver Configurations
#
# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy PCNT Driver Configurations
#
# Legacy SDM Driver Configurations
#
# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy SDM Driver Configurations
#
# Legacy Temperature Sensor Driver Configurations
#
# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy Temperature Sensor Driver Configurations
#
# Legacy Touch Sensor Driver Configurations
#
# CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN is not set
# CONFIG_TOUCH_SKIP_LEGACY_CONFLICT_CHECK is not set
# end of Legacy Touch Sensor Driver Configurations
# end of Driver Configurations
#
# eFuse Bit Manager
#
# CONFIG_EFUSE_CUSTOM_TABLE is not set
# CONFIG_EFUSE_VIRTUAL is not set
CONFIG_EFUSE_MAX_BLK_LEN=256
# end of eFuse Bit Manager
#
# ESP-TLS
#
CONFIG_ESP_TLS_USING_MBEDTLS=y
# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set
CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y
# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set
# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set
# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set
# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set
# CONFIG_ESP_TLS_PSK_VERIFICATION is not set
# CONFIG_ESP_TLS_INSECURE is not set
CONFIG_ESP_TLS_DYN_BUF_STRATEGY_SUPPORTED=y
# end of ESP-TLS
#
# ADC and ADC Calibration
#
# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set
# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set
# CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3 is not set
# CONFIG_ADC_ENABLE_DEBUG_LOG is not set
# end of ADC and ADC Calibration
#
# Wireless Coexistence
#
CONFIG_ESP_COEX_ENABLED=y
CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set
# CONFIG_ESP_COEX_GPIO_DEBUG is not set
# end of Wireless Coexistence
#
# Common ESP-related
#
CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
# end of Common ESP-related
#
# ESP-Driver:Camera Controller Configurations
#
# CONFIG_CAM_CTLR_DVP_CAM_ISR_CACHE_SAFE is not set
# end of ESP-Driver:Camera Controller Configurations
#
# ESP-Driver:GPIO Configurations
#
# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set
# end of ESP-Driver:GPIO Configurations
#
# ESP-Driver:GPTimer Configurations
#
CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y
# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set
# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set
CONFIG_GPTIMER_OBJ_CACHE_SAFE=y
# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set
# end of ESP-Driver:GPTimer Configurations
#
# ESP-Driver:I2C Configurations
#
# CONFIG_I2C_ISR_IRAM_SAFE is not set
# CONFIG_I2C_ENABLE_DEBUG_LOG is not set
# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set
CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y
# end of ESP-Driver:I2C Configurations
#
# ESP-Driver:I2S Configurations
#
# CONFIG_I2S_ISR_IRAM_SAFE is not set
# CONFIG_I2S_ENABLE_DEBUG_LOG is not set
# end of ESP-Driver:I2S Configurations
#
# ESP-Driver:LEDC Configurations
#
# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set
# end of ESP-Driver:LEDC Configurations
# #
# ESP-Driver:MCPWM Configurations # ESP-Driver:MCPWM Configurations
@@ -2811,3 +2565,249 @@ CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y
# end of Component config # end of Component config
# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set
# Deprecated options for backward compatibility
# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set
# CONFIG_NO_BLOBS is not set
# CONFIG_APP_ROLLBACK_ENABLE is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set
CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y
# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set
# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set
CONFIG_LOG_BOOTLOADER_LEVEL=3
# CONFIG_FLASH_ENCRYPTION_ENABLED is not set
# CONFIG_FLASHMODE_QIO is not set
# CONFIG_FLASHMODE_QOUT is not set
CONFIG_FLASHMODE_DIO=y
# CONFIG_FLASHMODE_DOUT is not set
CONFIG_MONITOR_BAUD=115200
CONFIG_OPTIMIZATION_LEVEL_DEBUG=y
CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y
CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y
# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set
# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set
CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2
# CONFIG_CXX_EXCEPTIONS is not set
CONFIG_STACK_CHECK_NONE=y
# CONFIG_STACK_CHECK_NORM is not set
# CONFIG_STACK_CHECK_STRONG is not set
# CONFIG_STACK_CHECK_ALL is not set
# CONFIG_WARN_WRITE_STRINGS is not set
# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set
CONFIG_ESP32_APPTRACE_DEST_NONE=y
CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
# CONFIG_BLUEDROID_ENABLED is not set
CONFIG_NIMBLE_ENABLED=y
CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
# CONFIG_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set
# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
CONFIG_NIMBLE_PINNED_TO_CORE=0
CONFIG_NIMBLE_PINNED_TO_CORE_0=y
# CONFIG_NIMBLE_PINNED_TO_CORE_1 is not set
CONFIG_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_NIMBLE_ROLE_CENTRAL=y
CONFIG_NIMBLE_ROLE_PERIPHERAL=y
CONFIG_NIMBLE_ROLE_BROADCASTER=y
CONFIG_NIMBLE_ROLE_OBSERVER=y
CONFIG_NIMBLE_SM_LEGACY=y
CONFIG_NIMBLE_SM_SC=y
# CONFIG_NIMBLE_SM_SC_DEBUG_KEYS is not set
CONFIG_BT_NIMBLE_SM_SC_LVL=0
# CONFIG_NIMBLE_NVS_PERSIST is not set
CONFIG_NIMBLE_MAX_BONDS=3
CONFIG_NIMBLE_RPA_TIMEOUT=900
CONFIG_NIMBLE_MAX_CONNECTIONS=3
CONFIG_NIMBLE_MAX_CCCDS=8
CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y
# CONFIG_NIMBLE_HS_FLOW_CTRL is not set
CONFIG_NIMBLE_ATT_PREFERRED_MTU=256
CONFIG_NIMBLE_L2CAP_COC_MAX_NUM=0
CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=12
CONFIG_BT_NIMBLE_ACL_BUF_COUNT=24
CONFIG_BT_NIMBLE_ACL_BUF_SIZE=255
CONFIG_BT_NIMBLE_HCI_EVT_BUF_SIZE=70
CONFIG_BT_NIMBLE_HCI_EVT_HI_BUF_COUNT=30
CONFIG_BT_NIMBLE_HCI_EVT_LO_BUF_COUNT=8
CONFIG_NIMBLE_SVC_GAP_DEVICE_NAME="nimble"
CONFIG_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=31
CONFIG_NIMBLE_SVC_GAP_APPEARANCE=0
# CONFIG_NIMBLE_MESH is not set
# CONFIG_NIMBLE_DEBUG is not set
# CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_EN is not set
CONFIG_BT_NIMBLE_COEX_PHY_CODED_TX_RX_TLIM_DIS=y
CONFIG_SW_COEXIST_ENABLE=y
CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y
CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y
# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set
# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set
# CONFIG_MCPWM_ISR_IRAM_SAFE is not set
# CONFIG_EVENT_LOOP_PROFILING is not set
CONFIG_POST_EVENTS_FROM_ISR=y
CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
CONFIG_GDBSTUB_SUPPORT_TASKS=y
CONFIG_GDBSTUB_MAX_TASKS=32
# CONFIG_OTA_ALLOW_HTTP is not set
CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000
CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000
CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y
# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set
# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set
# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set
CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024
CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y
CONFIG_BROWNOUT_DET=y
CONFIG_ESP32S3_BROWNOUT_DET=y
CONFIG_BROWNOUT_DET_LVL_SEL_7=y
CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y
# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set
# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set
# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set
# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set
# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set
# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set
# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set
# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set
CONFIG_BROWNOUT_DET_LVL=7
CONFIG_ESP32S3_BROWNOUT_DET_LVL=7
CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y
CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y
# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set
CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
CONFIG_ESP32_PHY_MAX_TX_POWER=20
# CONFIG_REDUCE_PHY_TX_POWER is not set
# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set
CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y
CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
CONFIG_DEFAULT_PSRAM_CLK_IO=30
CONFIG_DEFAULT_PSRAM_CS_IO=26
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_MAIN_TASK_STACK_SIZE=3584
# CONFIG_CONSOLE_UART_DEFAULT is not set
# CONFIG_CONSOLE_UART_CUSTOM is not set
# CONFIG_CONSOLE_UART_NONE is not set
# CONFIG_ESP_CONSOLE_UART_NONE is not set
CONFIG_CONSOLE_UART_NUM=-1
CONFIG_INT_WDT=y
CONFIG_INT_WDT_TIMEOUT_MS=300
CONFIG_INT_WDT_CHECK_CPU1=y
CONFIG_TASK_WDT=y
CONFIG_ESP_TASK_WDT=y
# CONFIG_TASK_WDT_PANIC is not set
CONFIG_TASK_WDT_TIMEOUT_S=5
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y
# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set
CONFIG_ESP32S3_DEBUG_OCDAWARE=y
CONFIG_IPC_TASK_STACK_SIZE=1280
CONFIG_TIMER_TASK_STACK_SIZE=3584
CONFIG_ESP32_WIFI_ENABLED=y
CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10
CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32
# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
# CONFIG_ESP32_WIFI_CSI_ENABLED is not set
CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
CONFIG_ESP32_WIFI_TX_BA_WIN=6
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP32_WIFI_RX_BA_WIN=6
CONFIG_ESP32_WIFI_NVS_ENABLED=y
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y
# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set
CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
CONFIG_ESP32_WIFI_IRAM_OPT=y
CONFIG_ESP32_WIFI_RX_IRAM_OPT=y
CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y
CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y
CONFIG_WPA_MBEDTLS_CRYPTO=y
CONFIG_WPA_MBEDTLS_TLS_CLIENT=y
# CONFIG_WPA_WAPI_PSK is not set
# CONFIG_WPA_SUITE_B_192 is not set
# CONFIG_WPA_11KV_SUPPORT is not set
# CONFIG_WPA_MBO_SUPPORT is not set
# CONFIG_WPA_DPP_SUPPORT is not set
# CONFIG_WPA_11R_SUPPORT is not set
# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set
# CONFIG_WPA_WPS_STRICT is not set
# CONFIG_WPA_DEBUG_PRINT is not set
# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set
# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set
CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2048
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
# CONFIG_HAL_ASSERTION_SILIENT is not set
# CONFIG_L2_TO_L3_COPY is not set
CONFIG_ESP_GRATUITOUS_ARP=y
CONFIG_GARP_TMR_INTERVAL=60
CONFIG_TCPIP_RECVMBOX_SIZE=32
CONFIG_TCP_MAXRTX=12
CONFIG_TCP_SYNMAXRTX=12
CONFIG_TCP_MSS=1440
CONFIG_TCP_MSL=60000
CONFIG_TCP_SND_BUF_DEFAULT=5760
CONFIG_TCP_WND_DEFAULT=5760
CONFIG_TCP_RECVMBOX_SIZE=6
CONFIG_TCP_QUEUE_OOSEQ=y
CONFIG_TCP_OVERSIZE_MSS=y
# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set
# CONFIG_TCP_OVERSIZE_DISABLE is not set
CONFIG_UDP_RECVMBOX_SIZE=6
CONFIG_TCPIP_TASK_STACK_SIZE=3072
CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set
# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set
CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF
# CONFIG_PPP_SUPPORT is not set
CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y
# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set
# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set
# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set
# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set
CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y
# CONFIG_NEWLIB_NANO_FORMAT is not set
CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y
CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_SYSTIMER=y
CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y
# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set
# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set
# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set
# CONFIG_ESP32S3_TIME_SYSCALL_USE_SYSTIMER is not set
# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set
# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set
# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set
CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5
CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072
CONFIG_ESP32_PTHREAD_STACK_MIN=768
CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y
# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set
# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set
CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1
CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread"
CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y
# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set
# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set
CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y
CONFIG_SUPPORT_TERMIOS=y
CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1
# End of deprecated options
@@ -82,6 +82,11 @@ struct DaliKnxStatusUpdate {
using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>; using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>;
extern "C" uint8_t knx_platform_copy_security_failures(uint8_t* counters, size_t countersLen,
uint8_t* records, size_t recordSize,
size_t maxRecords) __attribute__((weak));
extern "C" void knx_platform_clear_security_failures() __attribute__((weak));
class LockGuard { class LockGuard {
public: public:
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) { explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
@@ -150,11 +155,90 @@ cJSON* FactoryFdskInfoToCjson(const openknx::FactoryFdskInfo& fdsk_info,
return root; return root;
} }
cJSON* FactoryCertificateToCjson(const openknx::FactoryCertificatePayload& certificate,
bool include_secret_strings) {
cJSON* root = cJSON_CreateObject();
if (root == nullptr) {
return nullptr;
}
cJSON_AddBoolToObject(root, "available", certificate.available);
if (!certificate.available) {
return root;
}
cJSON_AddStringToObject(root, "productIdentity", certificate.productIdentity.c_str());
cJSON_AddStringToObject(root, "manufacturerId", certificate.manufacturerId.c_str());
cJSON_AddStringToObject(root, "applicationNumber", certificate.applicationNumber.c_str());
cJSON_AddStringToObject(root, "applicationVersion", certificate.applicationVersion.c_str());
cJSON_AddStringToObject(root, "serialNumber", certificate.serialNumber.c_str());
cJSON_AddStringToObject(root, "storage", certificate.storage.c_str());
cJSON_AddStringToObject(root, "createdAt", certificate.createdAt.c_str());
cJSON_AddStringToObject(root, "checksum", certificate.checksum.c_str());
cJSON_AddNumberToObject(root, "payloadLength",
static_cast<double>(certificate.fdskLabel.size() +
certificate.fdskQrCode.size()));
if (include_secret_strings) {
cJSON_AddStringToObject(root, "fdskLabel", certificate.fdskLabel.c_str());
cJSON_AddStringToObject(root, "fdskQrCode", certificate.fdskQrCode.c_str());
}
return root;
}
cJSON* SecurityFailuresToCjson() {
cJSON* root = cJSON_CreateObject();
if (root == nullptr) {
return nullptr;
}
cJSON* counters_json = cJSON_CreateArray();
cJSON* log_json = cJSON_CreateArray();
std::array<uint8_t, 8> counters{};
std::array<uint8_t, 8 * 8> records{};
uint8_t count = 0;
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
if (knx_platform_copy_security_failures != nullptr) {
count = knx_platform_copy_security_failures(counters.data(), counters.size(), records.data(), 8, 8);
}
#endif
if (counters_json != nullptr) {
for (uint8_t value : counters) {
cJSON_AddItemToArray(counters_json, cJSON_CreateNumber(static_cast<double>(value)));
}
cJSON_AddItemToObject(root, "counters", counters_json);
}
if (log_json != nullptr) {
for (uint8_t index = 0; index < count && index < 8; ++index) {
const size_t offset = static_cast<size_t>(index) * 8U;
cJSON* entry = cJSON_CreateObject();
if (entry == nullptr) {
continue;
}
cJSON_AddNumberToObject(entry, "class", records[offset]);
cJSON_AddNumberToObject(entry, "detail", records[offset + 1]);
cJSON_AddNumberToObject(entry, "source",
static_cast<double>((records[offset + 2] << 8) | records[offset + 3]));
cJSON_AddNumberToObject(entry, "destination",
static_cast<double>((records[offset + 4] << 8) | records[offset + 5]));
cJSON_AddNumberToObject(entry, "count", records[offset + 6]);
cJSON_AddNumberToObject(entry, "loadState", records[offset + 7]);
cJSON_AddItemToArray(log_json, entry);
}
cJSON_AddItemToObject(root, "log", log_json);
}
return root;
}
const char* JsonString(const cJSON* parent, const char* name) { const char* JsonString(const cJSON* parent, const char* name) {
const cJSON* item = cJSON_GetObjectItemCaseSensitive(parent, name); const cJSON* item = cJSON_GetObjectItemCaseSensitive(parent, name);
return cJSON_IsString(item) && item->valuestring != nullptr ? item->valuestring : nullptr; return cJSON_IsString(item) && item->valuestring != nullptr ? item->valuestring : nullptr;
} }
[[maybe_unused]] bool JsonBool(const cJSON* parent, const char* name, bool fallback = false) {
const cJSON* item = cJSON_GetObjectItemCaseSensitive(parent, name);
if (cJSON_IsBool(item)) {
return cJSON_IsTrue(item);
}
return fallback;
}
std::optional<int> JsonInt(const cJSON* parent, const char* name) { std::optional<int> JsonInt(const cJSON* parent, const char* name) {
const cJSON* item = cJSON_GetObjectItemCaseSensitive(parent, name); const cJSON* item = cJSON_GetObjectItemCaseSensitive(parent, name);
if (!cJSON_IsNumber(item)) { if (!cJSON_IsNumber(item)) {
@@ -2126,6 +2210,11 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", true); cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", true);
#else #else
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", false); cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", false);
#endif
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
cJSON_AddBoolToObject(security_json, "knxnetIpSecureServicesRecognized", true);
#else
cJSON_AddBoolToObject(security_json, "knxnetIpSecureServicesRecognized", false);
#endif #endif
cJSON_AddBoolToObject(security_json, "knxnetIpSecureImplemented", false); cJSON_AddBoolToObject(security_json, "knxnetIpSecureImplemented", false);
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) #if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
@@ -2146,6 +2235,15 @@ struct GatewayBridgeService::ChannelRuntime {
if (fdsk_json != nullptr) { if (fdsk_json != nullptr) {
cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json); cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json);
} }
cJSON* certificate_json = FactoryCertificateToCjson(
openknx::BuildFactoryCertificatePayload(), false);
if (certificate_json != nullptr) {
cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json);
}
cJSON* failures_json = SecurityFailuresToCjson();
if (failures_json != nullptr) {
cJSON_AddItemToObject(security_json, "failures", failures_json);
}
#endif #endif
cJSON_AddItemToObject(knx_json, "security", security_json); cJSON_AddItemToObject(knx_json, "security", security_json);
} }
@@ -4163,6 +4261,170 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
#else #else
return ErrorResponse(ESP_ERR_NOT_SUPPORTED, return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
"KNX security development endpoints are disabled"); "KNX security development endpoints are disabled");
#endif
}
if (action == "knx_security_generate_factory_key") {
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
if (body_root == nullptr) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
}
const char* confirm = JsonString(body_root, "confirm");
const bool include_secret = JsonBool(body_root, "includeSecret", false);
const bool confirmed = confirm != nullptr &&
std::string_view(confirm) == "generate-factory-setup-key";
cJSON_Delete(body_root);
if (!confirmed) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "factory setup key generation confirmation is required");
}
openknx::FactoryFdskInfo info;
if (!openknx::GenerateFactoryFdsk(&info)) {
return ErrorResponse(ESP_FAIL, "failed to generate factory setup key");
}
cJSON* response = cJSON_CreateObject();
if (response == nullptr) {
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate security response");
}
cJSON* fdsk_json = FactoryFdskInfoToCjson(info, include_secret);
if (fdsk_json != nullptr) {
cJSON_AddItemToObject(response, "factorySetupKey", fdsk_json);
}
return JsonOk(response);
#else
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
"KNX security development endpoints are disabled");
#endif
}
if (action == "knx_security_write_factory_key") {
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
if (body_root == nullptr) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "factory setup key JSON is required");
}
const char* confirm = JsonString(body_root, "confirm");
const char* key_hex = JsonString(body_root, "keyHex");
const bool include_secret = JsonBool(body_root, "includeSecret", false);
const bool confirmed = confirm != nullptr &&
std::string_view(confirm) == "write-factory-setup-key";
if (!confirmed || key_hex == nullptr) {
cJSON_Delete(body_root);
return ErrorResponse(ESP_ERR_INVALID_ARG,
"factory setup key write confirmation and keyHex are required");
}
const std::string key_value(key_hex);
cJSON_Delete(body_root);
openknx::FactoryFdskInfo info;
if (!openknx::WriteFactoryFdskHex(key_value, &info)) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "invalid factory setup key hex value");
}
cJSON* response = cJSON_CreateObject();
if (response == nullptr) {
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate security response");
}
cJSON* fdsk_json = FactoryFdskInfoToCjson(info, include_secret);
if (fdsk_json != nullptr) {
cJSON_AddItemToObject(response, "factorySetupKey", fdsk_json);
}
return JsonOk(response);
#else
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
"KNX security development endpoints are disabled");
#endif
}
if (action == "knx_security_reset_factory_key") {
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
if (body_root == nullptr) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
}
const char* confirm = JsonString(body_root, "confirm");
const bool confirmed = confirm != nullptr &&
std::string_view(confirm) == "reset-factory-setup-key";
cJSON_Delete(body_root);
if (!confirmed) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "factory setup key reset confirmation is required");
}
openknx::FactoryFdskInfo info;
if (!openknx::ResetFactoryFdskCache(&info)) {
return ErrorResponse(ESP_FAIL, "failed to reload factory setup key");
}
cJSON* response = cJSON_CreateObject();
if (response == nullptr) {
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate security response");
}
cJSON* fdsk_json = FactoryFdskInfoToCjson(info, false);
if (fdsk_json != nullptr) {
cJSON_AddItemToObject(response, "factorySetupKey", fdsk_json);
}
return JsonOk(response);
#else
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
"KNX security development endpoints are disabled");
#endif
}
if (action == "knx_security_export_factory_certificate") {
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
if (body_root == nullptr) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
}
const char* confirm = JsonString(body_root, "confirm");
const bool confirmed = confirm != nullptr &&
std::string_view(confirm) == "export-factory-certificate";
cJSON_Delete(body_root);
if (!confirmed) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "factory certificate export confirmation is required");
}
cJSON* response = cJSON_CreateObject();
if (response == nullptr) {
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate security response");
}
cJSON* certificate_json = FactoryCertificateToCjson(
openknx::BuildFactoryCertificatePayload(), true);
if (certificate_json == nullptr) {
cJSON_Delete(response);
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate factory certificate response");
}
cJSON_AddItemToObject(response, "factoryCertificate", certificate_json);
return JsonOk(response);
#else
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
"KNX security development endpoints are disabled");
#endif
}
if (action == "knx_security_clear_failures") {
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
if (body_root == nullptr) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
}
const char* confirm = JsonString(body_root, "confirm");
const bool confirmed = confirm != nullptr &&
std::string_view(confirm) == "clear-security-failures";
cJSON_Delete(body_root);
if (!confirmed) {
return ErrorResponse(ESP_ERR_INVALID_ARG, "security failure clear confirmation is required");
}
if (knx_platform_clear_security_failures != nullptr) {
knx_platform_clear_security_failures();
}
cJSON* response = cJSON_CreateObject();
if (response == nullptr) {
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate security response");
}
cJSON* failures_json = SecurityFailuresToCjson();
if (failures_json != nullptr) {
cJSON_AddItemToObject(response, "failures", failures_json);
}
return JsonOk(response);
#else
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
"KNX security development endpoints are disabled");
#endif #endif
} }
if (action == "bacnet_start") { if (action == "bacnet_start") {
@@ -216,8 +216,11 @@ class GatewayKnxTpIpRouter {
void handleConnectionStateRequest(const uint8_t* body, size_t len, void handleConnectionStateRequest(const uint8_t* body, size_t len,
const ::sockaddr_in& remote); const ::sockaddr_in& remote);
void handleDisconnectRequest(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, void sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
const ::sockaddr_in& remote); const ::sockaddr_in& remote);
void sendSecureSessionStatus(uint8_t status, const ::sockaddr_in& remote);
void sendTunnelIndication(const uint8_t* data, size_t len); void sendTunnelIndication(const uint8_t* data, size_t len);
void sendConnectionStateResponse(uint8_t channel_id, uint8_t status, void sendConnectionStateResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote); const ::sockaddr_in& remote);
@@ -35,6 +35,12 @@ constexpr uint16_t kServiceDisconnectResponse = 0x020a;
constexpr uint16_t kServiceTunnellingRequest = 0x0420; constexpr uint16_t kServiceTunnellingRequest = 0x0420;
constexpr uint16_t kServiceTunnellingAck = 0x0421; constexpr uint16_t kServiceTunnellingAck = 0x0421;
constexpr uint16_t kServiceRoutingIndication = 0x0530; constexpr uint16_t kServiceRoutingIndication = 0x0530;
constexpr uint16_t kServiceSecureWrapper = 0x0950;
constexpr uint16_t kServiceSecureSessionRequest = 0x0951;
constexpr uint16_t kServiceSecureSessionResponse = 0x0952;
constexpr uint16_t kServiceSecureSessionAuth = 0x0953;
constexpr uint16_t kServiceSecureSessionStatus = 0x0954;
constexpr uint16_t kServiceSecureGroupSync = 0x0955;
constexpr uint8_t kKnxNetIpHeaderSize = 0x06; constexpr uint8_t kKnxNetIpHeaderSize = 0x06;
constexpr uint8_t kKnxNetIpVersion10 = 0x10; constexpr uint8_t kKnxNetIpVersion10 = 0x10;
constexpr uint8_t kKnxNoError = 0x00; constexpr uint8_t kKnxNoError = 0x00;
@@ -42,6 +48,8 @@ constexpr uint8_t kKnxErrorConnectionId = 0x21;
constexpr uint8_t kKnxErrorConnectionType = 0x22; constexpr uint8_t kKnxErrorConnectionType = 0x22;
constexpr uint8_t kKnxErrorNoMoreConnections = 0x24; constexpr uint8_t kKnxErrorNoMoreConnections = 0x24;
constexpr uint8_t kKnxErrorSequenceNumber = 0x04; constexpr uint8_t kKnxErrorSequenceNumber = 0x04;
constexpr uint8_t kKnxSecureStatusAuthFailed = 0x01;
constexpr uint8_t kKnxSecureStatusUnauthenticated = 0x02;
constexpr uint8_t kKnxConnectionTypeTunnel = 0x04; constexpr uint8_t kKnxConnectionTypeTunnel = 0x04;
constexpr uint8_t kKnxTunnelLayerLink = 0x02; constexpr uint8_t kKnxTunnelLayerLink = 0x02;
constexpr uint8_t kTpUartResetRequest = 0x01; constexpr uint8_t kTpUartResetRequest = 0x01;
@@ -507,6 +515,20 @@ bool ParseKnxNetIpHeader(const uint8_t* data, size_t len, uint16_t* service,
return *total_len >= 6 && *total_len <= len; return *total_len >= 6 && *total_len <= len;
} }
bool IsKnxNetIpSecureService(uint16_t service) {
switch (service) {
case kServiceSecureWrapper:
case kServiceSecureSessionRequest:
case kServiceSecureSessionResponse:
case kServiceSecureSessionAuth:
case kServiceSecureSessionStatus:
case kServiceSecureGroupSync:
return true;
default:
return false;
}
}
bool IsExtendedTpFrame(const uint8_t* data, size_t len) { bool IsExtendedTpFrame(const uint8_t* data, size_t len) {
return len > 0 && (data[0] & 0xD3) == 0x10; return len > 0 && (data[0] & 0xD3) == 0x10;
} }
@@ -1875,6 +1897,10 @@ void GatewayKnxTpIpRouter::handleUdpDatagram(const uint8_t* data, size_t len,
} }
const uint8_t* body = data + 6; const uint8_t* body = data + 6;
const size_t body_len = total_len - 6; const size_t body_len = total_len - 6;
if (IsKnxNetIpSecureService(service)) {
handleSecureService(service, body, body_len, remote);
return;
}
switch (service) { switch (service) {
case kServiceRoutingIndication: case kServiceRoutingIndication:
if (config_.multicast_enabled) { if (config_.multicast_enabled) {
@@ -2001,6 +2027,34 @@ void GatewayKnxTpIpRouter::handleDisconnectRequest(const uint8_t* body, size_t l
sendDisconnectResponse(channel_id, status, remote); sendDisconnectResponse(channel_id, status, remote);
} }
void GatewayKnxTpIpRouter::handleSecureService(uint16_t service, const uint8_t* body,
size_t len, const sockaddr_in& remote) {
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
switch (service) {
case kServiceSecureSessionRequest:
case kServiceSecureSessionAuth:
ESP_LOGW(kTag, "KNXnet/IP Secure service 0x%04x rejected: secure sessions are not provisioned", service);
sendSecureSessionStatus(kKnxSecureStatusAuthFailed, remote);
break;
case kServiceSecureWrapper:
ESP_LOGW(kTag, "KNXnet/IP Secure wrapper rejected: no authenticated secure session");
sendSecureSessionStatus(kKnxSecureStatusUnauthenticated, remote);
break;
case kServiceSecureGroupSync:
ESP_LOGD(kTag, "KNXnet/IP Secure group sync ignored until secure routing is provisioned");
break;
default:
ESP_LOGD(kTag, "KNXnet/IP Secure service 0x%04x ignored", service);
break;
}
#else
(void)service;
(void)body;
(void)len;
(void)remote;
#endif
}
void GatewayKnxTpIpRouter::sendTunnellingAck(uint8_t channel_id, uint8_t sequence, void GatewayKnxTpIpRouter::sendTunnellingAck(uint8_t channel_id, uint8_t sequence,
uint8_t status, const sockaddr_in& remote) { uint8_t status, const sockaddr_in& remote) {
const std::vector<uint8_t> body{0x04, channel_id, sequence, status}; const std::vector<uint8_t> body{0x04, channel_id, sequence, status};
@@ -2008,6 +2062,12 @@ void GatewayKnxTpIpRouter::sendTunnellingAck(uint8_t channel_id, uint8_t sequenc
SendAll(udp_sock_, packet.data(), packet.size(), remote); SendAll(udp_sock_, packet.data(), packet.size(), remote);
} }
void GatewayKnxTpIpRouter::sendSecureSessionStatus(uint8_t status, const sockaddr_in& remote) {
const std::vector<uint8_t> body{status, 0x00};
const auto packet = KnxNetIpPacket(kServiceSecureSessionStatus, body);
SendAll(udp_sock_, packet.data(), packet.size(), remote);
}
void GatewayKnxTpIpRouter::sendTunnelIndication(const uint8_t* data, size_t len) { void GatewayKnxTpIpRouter::sendTunnelIndication(const uint8_t* data, size_t len) {
if (!tunnel_connected_ || udp_sock_ < 0 || data == nullptr || len == 0) { if (!tunnel_connected_ || udp_sock_ < 0 || data == nullptr || len == 0) {
return; return;
@@ -13,7 +13,25 @@ struct FactoryFdskInfo {
std::string qrCode; 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); bool LoadFactoryFdsk(uint8_t* data, size_t len);
FactoryFdskInfo LoadFactoryFdskInfo(); 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 } // namespace gateway::openknx
@@ -3,6 +3,7 @@
#include "esp_log.h" #include "esp_log.h"
#include "esp_mac.h" #include "esp_mac.h"
#include "esp_random.h" #include "esp_random.h"
#include "esp_timer.h"
#include "nvs.h" #include "nvs.h"
#include "nvs_flash.h" #include "nvs_flash.h"
@@ -10,6 +11,7 @@
#include <array> #include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring>
#include <string> #include <string>
namespace { namespace {
@@ -20,6 +22,11 @@ constexpr const char* kFactoryFdskKey = "factory_fdsk";
constexpr size_t kFdskSize = 16; constexpr size_t kFdskSize = 16;
constexpr size_t kSerialSize = 6; constexpr size_t kSerialSize = 6;
constexpr size_t kFdskQrSize = 36; constexpr size_t kFdskQrSize = 36;
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 = "plain_nvs_development";
constexpr uint8_t kCrc4Tab[16] = { constexpr uint8_t kCrc4Tab[16] = {
0x0, 0x3, 0x6, 0x5, 0xc, 0xf, 0xa, 0x9, 0x0, 0x3, 0x6, 0x5, 0xc, 0xf, 0xa, 0x9,
0xb, 0x8, 0xd, 0xe, 0x7, 0x4, 0x1, 0x2, 0xb, 0x8, 0xd, 0xe, 0x7, 0x4, 0x1, 0x2,
@@ -27,6 +34,8 @@ constexpr uint8_t kCrc4Tab[16] = {
constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
constexpr char kHexAlphabet[] = "0123456789ABCDEF"; constexpr char kHexAlphabet[] = "0123456789ABCDEF";
extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak));
bool ensureNvsReady() { bool ensureNvsReady() {
const esp_err_t err = nvs_flash_init(); const esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
@@ -54,6 +63,75 @@ void generateKey(uint8_t* data) {
} while (!plausibleKey(data)); } while (!plausibleKey(data));
} }
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 storeFactoryFdsk(const uint8_t* data) {
if (data == nullptr || !plausibleKey(data) || !ensureNvsReady()) {
return false;
}
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 false;
}
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 store KNX factory FDSK: %s", esp_err_to_name(err));
return false;
}
clearOpenKnxFdskCache();
return true;
}
uint8_t crc4Array(const uint8_t* data, size_t len) { uint8_t crc4Array(const uint8_t* data, size_t len) {
uint8_t crc = 0; uint8_t crc = 0;
for (size_t i = 0; i < len; ++i) { for (size_t i = 0; i < len; ++i) {
@@ -121,6 +199,21 @@ std::string formatFdskLabel(const std::string& qr_code) {
return label; 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
namespace gateway::openknx { namespace gateway::openknx {
@@ -174,6 +267,68 @@ FactoryFdskInfo LoadFactoryFdskInfo() {
return info; return info;
} }
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
std::array<uint8_t, kFdskSize> key{};
generateKey(key.data());
const bool stored = storeFactoryFdsk(key.data());
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;
}
const bool stored = storeFactoryFdsk(key.data());
std::fill(key.begin(), key.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 } // namespace gateway::openknx
extern "C" bool knx_platform_get_fdsk(uint8_t* data, size_t len) { extern "C" bool knx_platform_get_fdsk(uint8_t* data, size_t len) {
+1 -1
Submodule knx updated: 1549366447...5d7d6e573b