10 Commits

Author SHA1 Message Date
Tony 70ae1ae6cf feat(dali): enhance DALI timing configurations and add new timeout parameters
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-15 03:17:35 +08:00
Tony f2ffb45ca6 feat(gateway): implement LegacyRawPayload function for DALI and USB components
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-15 02:35:37 +08:00
Tony 0b2d00472e Enhance DALI Component Configuration and Functionality
- Updated README.md to include new configuration options for native timing values, TX/RX polarity, power-down polling, and logging levels.
- Introduced new default values for query response timeout and double-send delay in dali.c.
- Implemented a function to drain stale RX frames from the queue to improve query handling.
- Enhanced DALI HAL implementation in dali_hal_idf5.c with additional configuration options for timer resolution and bus power check intervals.
- Added logging capabilities to track bus states and message transmissions in the DALI HAL.
- Improved error handling and message response mechanisms in dali_domain.cpp and gateway_usb_setup.cpp for better communication reliability.
- Refactored GPIO handling to support configurable TX/RX active states in dali_hal.h.
- Introduced legacy query response handling for backward compatibility in the DALI domain.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-15 01:26:13 +08:00
Tony 4553ed32e7 feat(gateway): add support for KNX TP UART 9-bit mode and enhance UART pin configuration
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-14 22:39:14 +08:00
Tony 39ef630608 feat: Enhance EtsDeviceRuntime constructor and multicast handling
- Updated EtsDeviceRuntime constructor to accept an optional tunnel_client_address parameter with a default value of 0.
- Modified EspIdfPlatform::setupMultiCast to use IPPROTO_UDP for socket creation and improved multicast interface selection based on the current IP address.
- Ensured that the multicast interface is set only if a valid local address is available.
- Adjusted the client address assignment in EtsDeviceRuntime to use the provided tunnel_client_address if valid, falling back to the default otherwise.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-13 12:47:37 +08:00
Tony b74367e5a0 Refactor GatewayModbus and GatewayNetwork components
- Updated GatewayModbusConfig to allow uart_port and pin values to be -1, indicating an unconfigured state.
- Enhanced GatewayNetworkService to support an additional setup AP button with configurable GPIO and active low settings.
- Refactored boot button configuration logic to reduce redundancy and improve clarity.
- Introduced a new method for handling GPIO input configuration.
- Improved boot button task loop to handle both boot and setup AP buttons more effectively.
- Added programming mode functionality to EtsDeviceRuntime, allowing toggling and querying of the programming state.
- Implemented memory checks to avoid unnecessary reads in EtsDeviceRuntime.
- Enhanced security storage to derive factory FDSK from the device's serial number and store it in NVS.
- Updated factory FDSK loading logic to ensure proper key generation and storage.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-13 12:36:16 +08:00
Tony df1dd472cc feat(gateway): implement KNX security features including secure session handling and factory certificate management
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-12 21:29:40 +08:00
Tony 888d021343 feat(gateway): update partition sizes and GPIO configurations for W5500 Ethernet
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-12 21:29:00 +08:00
Tony de0edd5ad9 feat(gateway): enhance KNX support with DALI integration and configuration updates
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-12 20:34:33 +08:00
Tony e58115d303 feat(gateway): add KNX Data Secure support and related configurations
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-12 12:48:18 +08:00
36 changed files with 7960 additions and 511 deletions
+23 -3
View File
@@ -4,6 +4,8 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
## Layout ## 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/`: 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. - `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. - `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. - `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.
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out. - `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_runtime/`: persistent runtime state, command queueing, and device info services.
- `gateway_485_control/`: optional 485 Lua control bridge for framed `0x28 0x01` commands and `0x22 ... checksum` notifications at `9600 8N1`; disabled by default because UART0 must be moved off the ESP-IDF console first. - `gateway_485_control/`: optional 485 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. - `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 ## 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 target default pins for that UART, not disabled. `GATEWAY_KNX_TP_UART_9BIT_MODE` enables the NCN5120/OpenKNX-style 9-bit host frame on the wire, represented on ESP-IDF as 8 data bits plus even parity. Non-UART GPIO options use `-1` as disabled, including the KNX programming button, KNX programming LED, setup AP button, Wi-Fi reset button, and status LED.
When no KNX bridge config or ETS application data has been downloaded, the KNXnet/IP router starts in commissioning mode: OpenKNX receives tunnel programming traffic from ETS, while DALI group routing and REG1-Dali function-property actions stay inactive until ETS reports a configured application.
The bridge service exposes one shared KNXnet/IP endpoint per physical gateway on the configured UDP port. Per-channel DALI/KNX bridge runtimes keep their own group-address mappings behind that endpoint, and incoming group writes are dispatched to the matching channel instead of starting one UDP socket per DALI channel.
KNX programming mode can be controlled locally with `GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO`, and `GATEWAY_KNX_PROGRAMMING_LED_GPIO` mirrors the current programming-mode state. The setup AP entry button is configured separately with `GATEWAY_SETUP_AP_BUTTON_GPIO`; Wi-Fi credential reset remains a separate long-press function on `GATEWAY_BOOT_BUTTON_GPIO` when enabled.
When `GATEWAY_KNX_SECURITY_DEV_ENDPOINTS` is enabled, the bridge HTTP action surface exposes development-only operations for reading, writing, generating, and resetting the factory setup key, exporting the factory certificate payload, and clearing local KNX security failure diagnostics. These endpoints require explicit confirmation fields in the JSON body and should stay disabled in production builds. The default development storage mode is plain NVS via `GATEWAY_KNX_SECURITY_PLAIN_NVS`; production builds should replace that with encrypted NVS, flash encryption, and secure boot before handling real commissioning keys.
The normal bridge status response includes a `knx.security` object with compile-time capability flags, storage mode, factory setup key metadata, factory certificate metadata, and security failure counters/log entries. Secret FDSK strings are returned only by the explicit development actions, not by passive status polling.
## Modbus ## Modbus
+141 -22
View File
@@ -71,18 +71,20 @@ config GATEWAY_CHANNEL1_NATIVE_BAUDRATE
config GATEWAY_CHANNEL1_SERIAL_TX_PIN config GATEWAY_CHANNEL1_SERIAL_TX_PIN
int "Serial PHY TX pin" int "Serial PHY TX pin"
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2 depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
range 0 48 range -1 48
default 1 default 1
help 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 config GATEWAY_CHANNEL1_SERIAL_RX_PIN
int "Serial PHY RX pin" int "Serial PHY RX pin"
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2 depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
range 0 48 range -1 48
default 2 default 2
help 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 config GATEWAY_CHANNEL1_SERIAL_BAUDRATE
int "Serial PHY baudrate" int "Serial PHY baudrate"
@@ -178,18 +180,20 @@ config GATEWAY_CHANNEL2_NATIVE_BAUDRATE
config GATEWAY_CHANNEL2_SERIAL_TX_PIN config GATEWAY_CHANNEL2_SERIAL_TX_PIN
int "Serial PHY TX pin" int "Serial PHY TX pin"
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2) depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
range 0 48 range -1 48
default 6 default 6
help 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 config GATEWAY_CHANNEL2_SERIAL_RX_PIN
int "Serial PHY RX pin" int "Serial PHY RX pin"
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2) depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
range 0 48 range -1 48
default 7 default 7
help 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 config GATEWAY_CHANNEL2_SERIAL_BAUDRATE
int "Serial PHY baudrate" int "Serial PHY baudrate"
@@ -542,8 +546,11 @@ config GATEWAY_MODBUS_UNIT_ID
config GATEWAY_MODBUS_SERIAL_UART_PORT config GATEWAY_MODBUS_SERIAL_UART_PORT
int "Default 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) depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED && (GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU || GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
range 0 2 range -1 2
default 1 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 config GATEWAY_MODBUS_ALLOW_UART0
bool "Allow Modbus/setup to claim 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 Starts the KNXnet/IP tunneling/multicast listener at boot. Disabled by
default so UDP port 3671 is opened only after provisioning or explicit start. 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 config GATEWAY_KNX_MAIN_GROUP
int "KNX DALI main group" int "KNX DALI main group"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED 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 depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_MULTICAST_ENABLED
default "224.0.23.12" default "224.0.23.12"
config GATEWAY_KNX_INDIVIDUAL_ADDRESS config GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS
int "KNX individual address raw value" int "KNXnet/IP interface individual address raw value"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED depends on GATEWAY_KNX_BRIDGE_SUPPORTED
range 0 65535 range 0 65535
default 4353 default 65281
help help
Raw 16-bit individual address advertised to KNXnet/IP tunnel clients. Raw 16-bit individual address advertised by the KNXnet/IP interface.
The default 4353 is 1.1.1. 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 config GATEWAY_KNX_TP_UART_PORT
int "KNX TP UART port" int "KNX TP UART port"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED depends on GATEWAY_KNX_BRIDGE_SUPPORTED
range 0 2 range -1 2
default 1 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 config GATEWAY_KNX_TP_TX_PIN
int "KNX TP UART TX pin" int "KNX TP UART TX pin"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED depends on GATEWAY_KNX_BRIDGE_SUPPORTED
range -1 48 range -1 48
default -1 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 config GATEWAY_KNX_TP_RX_PIN
int "KNX TP UART RX pin" int "KNX TP UART RX pin"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED depends on GATEWAY_KNX_BRIDGE_SUPPORTED
range -1 48 range -1 48
default -1 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 config GATEWAY_KNX_TP_BAUDRATE
int "KNX TP UART baudrate" int "KNX TP UART baudrate"
@@ -684,11 +772,21 @@ config GATEWAY_KNX_TP_BAUDRATE
range 1200 921600 range 1200 921600
default 19200 default 19200
config GATEWAY_KNX_TP_UART_9BIT_MODE
bool "KNX TP UART 9-bit mode"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
default y
help
Enable the NCN5120/OpenKNX-style 9-bit UART frame on the wire. ESP-IDF
exposes this as 8 data bits plus even parity, matching the TP-UART host
mode commonly described as 19200 baud 9-bit UART. Disable only for
hardware wired for 8N1 host UART mode.
config GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE config GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE
int "KNX/IP bridge task stack bytes" int "KNX/IP bridge task stack bytes"
depends on GATEWAY_KNX_BRIDGE_SUPPORTED depends on GATEWAY_KNX_BRIDGE_SUPPORTED
range 6144 24576 range 6144 24576
default 8192 default 12288
config GATEWAY_BRIDGE_KNX_TASK_PRIORITY config GATEWAY_BRIDGE_KNX_TASK_PRIORITY
int "KNX/IP bridge task priority" int "KNX/IP bridge task priority"
@@ -881,23 +979,44 @@ config GATEWAY_STATUS_LED_ACTIVE_HIGH
default y default y
config GATEWAY_BOOT_BUTTON_GPIO config GATEWAY_BOOT_BUTTON_GPIO
int "BOOT button GPIO" int "Wi-Fi reset button GPIO"
range -1 48 range -1 48
default 0 default -1
help 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 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 depends on GATEWAY_BOOT_BUTTON_GPIO >= 0
default y default y
config GATEWAY_BOOT_BUTTON_LONG_PRESS_MS 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 depends on GATEWAY_BOOT_BUTTON_GPIO >= 0
range 500 10000 range 500 10000
default 3000 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
endmenu endmenu
+62 -7
View File
@@ -40,6 +40,21 @@
#define CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS 3000 #define CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS 3000
#endif #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 #ifndef CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX
#define CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX 0 #define CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX 0
#endif #endif
@@ -193,7 +208,7 @@
#endif #endif
#ifndef CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE #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 #endif
#ifndef CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY #ifndef CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY
@@ -213,11 +228,23 @@
#endif #endif
#ifndef CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS #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 #endif
#ifndef CONFIG_GATEWAY_KNX_TP_UART_PORT #ifndef CONFIG_GATEWAY_KNX_TP_UART_PORT
#define CONFIG_GATEWAY_KNX_TP_UART_PORT 1 #define CONFIG_GATEWAY_KNX_TP_UART_PORT -1
#endif #endif
#ifndef CONFIG_GATEWAY_KNX_TP_TX_PIN #ifndef CONFIG_GATEWAY_KNX_TP_TX_PIN
@@ -579,21 +606,22 @@ bool ValidateChannelBindings() {
if (kKnxBridgeSupported) { if (kKnxBridgeSupported) {
const int knx_uart = CONFIG_GATEWAY_KNX_TP_UART_PORT; 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"); ESP_LOGE(kTag, "KNX TP UART0 conflicts with the UART0 control bridge");
return false; 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"); ESP_LOGE(kTag, "KNX TP-UART on UART0 requires moving the ESP-IDF console off UART0");
return false; return false;
} }
if (kModbusBridgeSupported && kModbusDefaultSerialTransport && if (knx_uart >= 0 && kModbusBridgeSupported && kModbusDefaultSerialTransport &&
knx_uart == CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT) { knx_uart == CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT) {
ESP_LOGE(kTag, "KNX TP UART%d conflicts with default Modbus serial UART", knx_uart); ESP_LOGE(kTag, "KNX TP UART%d conflicts with default Modbus serial UART", knx_uart);
return false; return false;
} }
for (int i = 0; i < CONFIG_GATEWAY_CHANNEL_COUNT; ++i) { 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, ESP_LOGE(kTag, "KNX TP UART%d conflicts with DALI channel %d serial PHY", knx_uart,
i + 1); i + 1);
return false; return false;
@@ -838,12 +866,31 @@ extern "C" void app_main(void) {
default_knx.main_group = static_cast<uint8_t>(CONFIG_GATEWAY_KNX_MAIN_GROUP); 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.udp_port = static_cast<uint16_t>(CONFIG_GATEWAY_KNX_UDP_PORT);
default_knx.multicast_address = CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS; 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 = default_knx.individual_address =
static_cast<uint16_t>(CONFIG_GATEWAY_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.uart_port = CONFIG_GATEWAY_KNX_TP_UART_PORT;
default_knx.tp_uart.tx_pin = CONFIG_GATEWAY_KNX_TP_TX_PIN; default_knx.tp_uart.tx_pin = CONFIG_GATEWAY_KNX_TP_TX_PIN;
default_knx.tp_uart.rx_pin = CONFIG_GATEWAY_KNX_TP_RX_PIN; default_knx.tp_uart.rx_pin = CONFIG_GATEWAY_KNX_TP_RX_PIN;
default_knx.tp_uart.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_KNX_TP_BAUDRATE); default_knx.tp_uart.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_KNX_TP_BAUDRATE);
#ifdef CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE
default_knx.tp_uart.nine_bit_mode = true;
#else
default_knx.tp_uart.nine_bit_mode = false;
#endif
bridge_config.default_knx_config = default_knx; bridge_config.default_knx_config = default_knx;
} }
bridge_config.knx_task_stack_size = bridge_config.knx_task_stack_size =
@@ -898,7 +945,10 @@ extern "C" void app_main(void) {
static_cast<uint32_t>(CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE); static_cast<uint32_t>(CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE);
network_config.status_led_gpio = CONFIG_GATEWAY_STATUS_LED_GPIO; network_config.status_led_gpio = CONFIG_GATEWAY_STATUS_LED_GPIO;
network_config.boot_button_gpio = CONFIG_GATEWAY_BOOT_BUTTON_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_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 #ifdef CONFIG_GATEWAY_STATUS_LED_ACTIVE_HIGH
network_config.status_led_active_high = true; network_config.status_led_active_high = true;
#else #else
@@ -908,6 +958,11 @@ extern "C" void app_main(void) {
network_config.boot_button_active_low = true; network_config.boot_button_active_low = true;
#else #else
network_config.boot_button_active_low = false; 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 #endif
s_network = std::make_unique<gateway::GatewayNetworkService>(*s_controller, *s_runtime, s_network = std::make_unique<gateway::GatewayNetworkService>(*s_controller, *s_runtime,
*s_dali_domain, network_config, *s_dali_domain, network_config,
+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
+69 -45
View File
@@ -596,38 +596,25 @@ CONFIG_PARTITION_TABLE_MD5=y
# #
# Gateway App # Gateway App
# #
CONFIG_GATEWAY_CHANNEL_COUNT=2 CONFIG_GATEWAY_CHANNEL_COUNT=1
# #
# Gateway Channel 1 # Gateway Channel 1
# #
CONFIG_GATEWAY_CHANNEL1_GW_ID=3 CONFIG_GATEWAY_CHANNEL1_GW_ID=3
# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set # CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set
# CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE is not set CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y
CONFIG_GATEWAY_CHANNEL1_PHY_UART1=y # CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set # CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set
CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_PIN=1 CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID=0
CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_PIN=2 CONFIG_GATEWAY_CHANNEL1_NATIVE_TX_PIN=2
CONFIG_GATEWAY_CHANNEL1_SERIAL_BAUDRATE=9600 CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN=1
CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER=512 CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_BUFFER=512
CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS=100
# end of Gateway Channel 1 # end of Gateway Channel 1
# #
# Gateway Channel 2 # 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 # end of Gateway Channel 2
# #
@@ -637,7 +624,7 @@ CONFIG_GATEWAY_CACHE_SUPPORTED=y
CONFIG_GATEWAY_CACHE_START_ENABLED=y CONFIG_GATEWAY_CACHE_START_ENABLED=y
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
# CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set # CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=5000 CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=60000
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set # CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
# end of Gateway Cache # end of Gateway Cache
@@ -663,17 +650,17 @@ 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=4096
# end of Gateway Wired Ethernet # end of Gateway Wired Ethernet
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
@@ -688,17 +675,27 @@ CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=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_MAIN_GROUP=0
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
CONFIG_GATEWAY_KNX_UDP_PORT=3671 CONFIG_GATEWAY_KNX_UDP_PORT=3671
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12" 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_UART_PORT=0
CONFIG_GATEWAY_KNX_TP_TX_PIN=-1 CONFIG_GATEWAY_KNX_TP_TX_PIN=-1
CONFIG_GATEWAY_KNX_TP_RX_PIN=-1 CONFIG_GATEWAY_KNX_TP_RX_PIN=-1
CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200 CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=8192 CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5 CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
@@ -722,6 +719,8 @@ CONFIG_GATEWAY_STATUS_LED_GPIO=-1
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0 CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS=3000 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 Network Services
# end of Gateway App # end of Gateway App
@@ -1071,12 +1070,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 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_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_P12 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 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_P18 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 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_SUPP=y
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
@@ -1807,7 +1806,7 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 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_CPU0=y
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set # CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set # CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
@@ -2180,8 +2179,7 @@ CONFIG_LWIP_GARP_TMR_INTERVAL=60
CONFIG_LWIP_ESP_MLDV6_REPORT=y CONFIG_LWIP_ESP_MLDV6_REPORT=y
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y CONFIG_LWIP_DHCP_DOES_ACD_CHECK=y
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set # CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set # CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
@@ -2200,13 +2198,16 @@ CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
CONFIG_LWIP_DHCPS_ADD_DNS=y CONFIG_LWIP_DHCPS_ADD_DNS=y
# end of DHCP server # 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_IPV4=y
CONFIG_LWIP_IPV6=y CONFIG_LWIP_IPV6=y
# CONFIG_LWIP_IPV6_AUTOCONFIG is not set # CONFIG_LWIP_IPV6_AUTOCONFIG is not set
CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 CONFIG_LWIP_IPV6_NUM_ADDRESSES=3
# CONFIG_LWIP_IPV6_FORWARD is not set # 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_NETIF_LOOPBACK=y
CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
@@ -2770,13 +2771,36 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
# #
CONFIG_DALI_PHY_COUNT=16 CONFIG_DALI_PHY_COUNT=16
CONFIG_DALI_DEFAULT_BAUDRATE=1200 CONFIG_DALI_DEFAULT_BAUDRATE=1200
CONFIG_DALI_API_QUEUE_LEN=10 CONFIG_DALI_TIMER_RESOLUTION_HZ=3000000
CONFIG_DALI_TX_QUEUE_LEN=1 CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0
CONFIG_DALI_TX_STOP_CONDITION_US=0
CONFIG_DALI_RX_STOP_CONDITION_US=0
CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=20
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=12
CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=25
CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=5
CONFIG_DALI_FORWARD_MAX_WAIT_MS=50
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=9
CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500
CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS=3000
# CONFIG_DALI_LOG_LEVEL_NONE is not set
# CONFIG_DALI_LOG_LEVEL_ERROR is not set
# CONFIG_DALI_LOG_LEVEL_WARN is not set
# CONFIG_DALI_LOG_LEVEL_INFO is not set
# CONFIG_DALI_LOG_LEVEL_DEBUG is not set
CONFIG_DALI_LOG_LEVEL_VERBOSE=y
CONFIG_DALI_LOG_LEVEL=5
CONFIG_DALI_TX_ACTIVE_LOW=y
# CONFIG_DALI_TX_ACTIVE_HIGH is not set
CONFIG_DALI_RX_ACTIVE_LOW=y
# CONFIG_DALI_RX_ACTIVE_HIGH is not set
CONFIG_DALI_API_QUEUE_LEN=64
CONFIG_DALI_TX_QUEUE_LEN=4
CONFIG_DALI_TX_REPLY_QUEUE_LEN=4 CONFIG_DALI_TX_REPLY_QUEUE_LEN=4
CONFIG_DALI_RX_QUEUE_LEN=50 CONFIG_DALI_RX_QUEUE_LEN=50
CONFIG_DALI_DEBUG_QUEUE_LEN=100 CONFIG_DALI_DEBUG_QUEUE_LEN=100
# CONFIG_DALI_ENABLE_DEBUG_TASK is not set CONFIG_DALI_ENABLE_DEBUG_TASK=y
CONFIG_DALI_DALI_TASK_STACK_SIZE=2048 CONFIG_DALI_DALI_TASK_STACK_SIZE=8192
CONFIG_DALI_DALI_TASK_PRIORITY=2 CONFIG_DALI_DALI_TASK_PRIORITY=2
CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048 CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048
CONFIG_DALI_DEBUG_TASK_PRIORITY=1 CONFIG_DALI_DEBUG_TASK_PRIORITY=1
@@ -2939,7 +2963,7 @@ CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160 CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 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_DEFAULT is not set
# CONFIG_CONSOLE_UART_CUSTOM is not set # CONFIG_CONSOLE_UART_CUSTOM is not set
# CONFIG_CONSOLE_UART_NONE is not set # CONFIG_CONSOLE_UART_NONE is not set
+4 -1
View File
@@ -7,6 +7,8 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y CONFIG_PARTITION_TABLE_MD5=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_BT_ENABLED=y CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y
@@ -15,4 +17,5 @@ CONFIG_ETH_USE_SPI_ETHERNET=y
CONFIG_ETH_SPI_ETHERNET_W5500=y CONFIG_ETH_SPI_ETHERNET_W5500=y
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
CONFIG_GATEWAY_START_ETHERNET_ENABLED=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
File diff suppressed because it is too large Load Diff
+92 -44
View File
@@ -596,38 +596,25 @@ CONFIG_PARTITION_TABLE_MD5=y
# #
# Gateway App # Gateway App
# #
CONFIG_GATEWAY_CHANNEL_COUNT=2 CONFIG_GATEWAY_CHANNEL_COUNT=1
# #
# Gateway Channel 1 # Gateway Channel 1
# #
CONFIG_GATEWAY_CHANNEL1_GW_ID=3 CONFIG_GATEWAY_CHANNEL1_GW_ID=3
# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set # CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set
# CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE is not set CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y
CONFIG_GATEWAY_CHANNEL1_PHY_UART1=y # CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set # CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set
CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_PIN=1 CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID=0
CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_PIN=2 CONFIG_GATEWAY_CHANNEL1_NATIVE_TX_PIN=2
CONFIG_GATEWAY_CHANNEL1_SERIAL_BAUDRATE=9600 CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN=1
CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER=512 CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_BUFFER=512
CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS=100
# end of Gateway Channel 1 # end of Gateway Channel 1
# #
# Gateway Channel 2 # 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 # end of Gateway Channel 2
# #
@@ -637,7 +624,7 @@ CONFIG_GATEWAY_CACHE_SUPPORTED=y
CONFIG_GATEWAY_CACHE_START_ENABLED=y CONFIG_GATEWAY_CACHE_START_ENABLED=y
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
# CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set # CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=5000 CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=60000
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set # CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
# end of Gateway Cache # end of Gateway Cache
@@ -656,6 +643,26 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set # CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set # CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60 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_BRIDGE_SUPPORTED=y
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
@@ -666,7 +673,30 @@ CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
CONFIG_GATEWAY_MODBUS_UNIT_ID=1 CONFIG_GATEWAY_MODBUS_UNIT_ID=1
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set # 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_KNX_TP_UART_9BIT_MODE=y
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
@@ -675,16 +705,7 @@ CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5 CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set # CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
CONFIG_GATEWAY_485_CONTROL_ENABLED=y # CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
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
# end of Gateway Startup Services # end of Gateway Startup Services
# #
@@ -698,6 +719,8 @@ CONFIG_GATEWAY_STATUS_LED_GPIO=-1
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0 CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS=3000 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 Network Services
# end of Gateway App # end of Gateway App
@@ -1047,12 +1070,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 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_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_P12 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 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_P18 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 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_SUPP=y
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
@@ -1783,7 +1806,7 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 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_CPU0=y
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set # CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set # CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
@@ -2156,8 +2179,7 @@ CONFIG_LWIP_GARP_TMR_INTERVAL=60
CONFIG_LWIP_ESP_MLDV6_REPORT=y CONFIG_LWIP_ESP_MLDV6_REPORT=y
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y CONFIG_LWIP_DHCP_DOES_ACD_CHECK=y
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set # CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set # CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
@@ -2176,13 +2198,16 @@ CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
CONFIG_LWIP_DHCPS_ADD_DNS=y CONFIG_LWIP_DHCPS_ADD_DNS=y
# end of DHCP server # 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_IPV4=y
CONFIG_LWIP_IPV6=y CONFIG_LWIP_IPV6=y
# CONFIG_LWIP_IPV6_AUTOCONFIG is not set # CONFIG_LWIP_IPV6_AUTOCONFIG is not set
CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 CONFIG_LWIP_IPV6_NUM_ADDRESSES=3
# CONFIG_LWIP_IPV6_FORWARD is not set # 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_NETIF_LOOPBACK=y
CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
@@ -2746,13 +2771,36 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
# #
CONFIG_DALI_PHY_COUNT=16 CONFIG_DALI_PHY_COUNT=16
CONFIG_DALI_DEFAULT_BAUDRATE=1200 CONFIG_DALI_DEFAULT_BAUDRATE=1200
CONFIG_DALI_API_QUEUE_LEN=10 CONFIG_DALI_TIMER_RESOLUTION_HZ=3000000
CONFIG_DALI_TX_QUEUE_LEN=1 CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0
CONFIG_DALI_TX_STOP_CONDITION_US=0
CONFIG_DALI_RX_STOP_CONDITION_US=0
CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=12
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=12
CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=25
CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=5
CONFIG_DALI_FORWARD_MAX_WAIT_MS=50
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=9
CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500
CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS=3000
# CONFIG_DALI_LOG_LEVEL_NONE is not set
# CONFIG_DALI_LOG_LEVEL_ERROR is not set
# CONFIG_DALI_LOG_LEVEL_WARN is not set
# CONFIG_DALI_LOG_LEVEL_INFO is not set
# CONFIG_DALI_LOG_LEVEL_DEBUG is not set
CONFIG_DALI_LOG_LEVEL_VERBOSE=y
CONFIG_DALI_LOG_LEVEL=5
CONFIG_DALI_TX_ACTIVE_LOW=y
# CONFIG_DALI_TX_ACTIVE_HIGH is not set
CONFIG_DALI_RX_ACTIVE_LOW=y
# CONFIG_DALI_RX_ACTIVE_HIGH is not set
CONFIG_DALI_API_QUEUE_LEN=64
CONFIG_DALI_TX_QUEUE_LEN=4
CONFIG_DALI_TX_REPLY_QUEUE_LEN=4 CONFIG_DALI_TX_REPLY_QUEUE_LEN=4
CONFIG_DALI_RX_QUEUE_LEN=50 CONFIG_DALI_RX_QUEUE_LEN=50
CONFIG_DALI_DEBUG_QUEUE_LEN=100 CONFIG_DALI_DEBUG_QUEUE_LEN=100
# CONFIG_DALI_ENABLE_DEBUG_TASK is not set CONFIG_DALI_ENABLE_DEBUG_TASK=y
CONFIG_DALI_DALI_TASK_STACK_SIZE=2048 CONFIG_DALI_DALI_TASK_STACK_SIZE=8192
CONFIG_DALI_DALI_TASK_PRIORITY=2 CONFIG_DALI_DALI_TASK_PRIORITY=2
CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048 CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048
CONFIG_DALI_DEBUG_TASK_PRIORITY=1 CONFIG_DALI_DEBUG_TASK_PRIORITY=1
+169 -1
View File
@@ -14,6 +14,174 @@ config DALI_DEFAULT_BAUDRATE
help help
Default baudrate used during initialization. Default baudrate used during initialization.
config DALI_TIMER_RESOLUTION_HZ
int "Native DALI timer resolution Hz"
range 1000000 8000000
default 3000000
help
GPTimer resolution for native DALI Manchester timing. The default 3 MHz
allows a 1200 bps half-bit period of 416.67 us to be represented as
1250 timer ticks.
config DALI_CUSTOM_HALF_BIT_TIME_X100_US
int "Custom native DALI half-bit time x100 us"
range 0 500000
default 0
help
Development override for the native DALI half-bit period, expressed in
1/100 us. Set to 0 to derive the value from the configured baudrate.
Standard 1200 bps DALI is 41667, meaning 416.67 us.
config DALI_TX_STOP_CONDITION_US
int "Custom TX stop condition us"
range 0 10000
default 0
help
Development override for the native TX stop-condition wait. Set to 0 to
use the scaled standard timing.
config DALI_RX_STOP_CONDITION_US
int "Custom RX stop condition us"
range 0 10000
default 0
help
Development override for the native RX stop-condition wait. Set to 0 to
use the scaled standard timing.
config DALI_QUERY_RESPONSE_TIMEOUT_MS
int "DALI query response timeout ms"
range 10 100
default 25
help
Time to wait for a complete backward frame after a forward query has
finished transmitting. DALI backward frames start 5.5-10.5 ms after the
forward frame and last about 9.95 ms, so 25 ms leaves margin without the
legacy 50 ms no-response delay.
config DALI_DOUBLE_SEND_DELAY_MS
int "Double-send delay ms"
range 0 100
default 10
help
Delay between the two frames sent by dali_send_double(), measured after
the first frame has completed. Exposed for development tuning.
config DALI_FORWARD_ACTIVITY_WAIT_MS
int "Forward-frame wait after bus activity ms"
range 0 1000
default 25
help
Minimum delay before sending a 2- to 4-byte forward frame after normal
bus activity. This is the native queue anti-collision wait.
config DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
int "Forward-frame wait after backward frame ms"
range 0 1000
default 5
help
Minimum delay before sending a 2- to 4-byte forward frame after the last
valid 1-byte backward frame received by the native bus.
config DALI_FORWARD_MAX_WAIT_MS
int "Forward-frame maximum queue wait ms"
range 0 5000
default 50
help
Maximum time the native queue waits for a forward-frame send window. If
no valid send window is reached before this timeout, the frame is
dropped with an error status.
config DALI_BACKWARD_IDLE_TIMEOUT_MS
int "Backward-frame idle wait timeout ms"
range 0 1000
default 9
help
Time a 1-byte backward frame waits for an idle bus before being sent
anyway. Backward frame sends are not echo-verified because collisions
during addressing responses are valid DALI behavior.
config DALI_BUS_POWER_CHECK_INTERVAL_MS
int "Bus power-down check interval ms"
range 10 5000
default 100
help
Interval used to resample the RX pin while the native DALI bus is marked
power-down. This lets the HAL recover when the bus was already powered
before gateway startup and no RX edge is generated.
config DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS
int "Legacy bus abnormal report interval ms"
range 0 60000
default 1000
help
Interval for publishing the legacy two-byte FF FD bus-abnormal raw frame
while the native DALI bus is power-down. Set to 0 to disable the report.
choice DALI_LOG_LEVEL_CHOICE
prompt "DALI log level"
default DALI_LOG_LEVEL_WARN
help
Runtime log level applied to the native DALI HAL ESP-IDF log tag.
config DALI_LOG_LEVEL_NONE
bool "No output"
config DALI_LOG_LEVEL_ERROR
bool "Error"
config DALI_LOG_LEVEL_WARN
bool "Warning"
config DALI_LOG_LEVEL_INFO
bool "Info"
config DALI_LOG_LEVEL_DEBUG
bool "Debug"
config DALI_LOG_LEVEL_VERBOSE
bool "Verbose"
endchoice
config DALI_LOG_LEVEL
int
default 0 if DALI_LOG_LEVEL_NONE
default 1 if DALI_LOG_LEVEL_ERROR
default 2 if DALI_LOG_LEVEL_WARN
default 3 if DALI_LOG_LEVEL_INFO
default 4 if DALI_LOG_LEVEL_DEBUG
default 5 if DALI_LOG_LEVEL_VERBOSE
choice DALI_TX_ACTIVE_LEVEL
prompt "DALI TX pin active level"
default DALI_TX_ACTIVE_LOW
help
Select the physical GPIO level that drives the DALI bus active. The
native gateway default is TX active low.
config DALI_TX_ACTIVE_LOW
bool "Active low"
config DALI_TX_ACTIVE_HIGH
bool "Active high"
endchoice
choice DALI_RX_ACTIVE_LEVEL
prompt "DALI RX pin active level"
default DALI_RX_ACTIVE_HIGH
help
Select the physical GPIO level read when the DALI bus is active. The
native gateway default is RX active high.
config DALI_RX_ACTIVE_LOW
bool "Active low"
config DALI_RX_ACTIVE_HIGH
bool "Active high"
endchoice
config DALI_API_QUEUE_LEN config DALI_API_QUEUE_LEN
int "Global API queue length" int "Global API queue length"
range 1 64 range 1 64
@@ -48,7 +216,7 @@ config DALI_ENABLE_DEBUG_TASK
config DALI_DALI_TASK_STACK_SIZE config DALI_DALI_TASK_STACK_SIZE
int "DALI task stack size" int "DALI task stack size"
range 1024 8192 range 1024 8192
default 2048 default 4096
config DALI_DALI_TASK_PRIORITY config DALI_DALI_TASK_PRIORITY
int "DALI task priority" int "DALI task priority"
+24
View File
@@ -41,10 +41,34 @@ void app_main(void) {
Use `menuconfig` under `DALI Component` to configure: Use `menuconfig` under `DALI Component` to configure:
- Bus count and default baudrate. - Bus count and default baudrate.
- Native timing values for development, including timer resolution, half-bit period,
TX/RX stop conditions, query response timeout, double-send delay, and TX queue
arbitration waits.
- TX/RX active polarity. The native gateway default is TX active low and RX active high.
- Native bus power-down polling and legacy `FF FD` bus-abnormal raw-frame reporting intervals.
- Native DALI HAL log level for the `dali_hal` ESP-IDF log tag.
- Queue sizes. - Queue sizes.
- Task stack sizes and priorities. - Task stack sizes and priorities.
- Optional debug task. - Optional debug task.
The native bus monitor uses `CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS` to resample RX while
power-down and `CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS` to publish legacy `FF FD`
raw frames while down. The report interval defaults to 1000 ms; set it to 0 to disable the
compatibility report.
Native timing defaults target standard 1200 bps DALI: a 416.67 us half-bit period is
generated by the default 3 MHz timer as 1250 ticks. `CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US`
can override the half-bit period for development; keep it at 0 for baudrate-derived timing.
Query no-response timeout defaults to 25 ms, which covers the 5.5-10.5 ms backward-frame
start window plus the approximately 9.95 ms backward frame duration.
Native TX queue arbitration uses frame length as the frame type signal. Two- to four-byte
forward frames wait up to `CONFIG_DALI_FORWARD_MAX_WAIT_MS` for a valid send window,
using `CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS` after normal bus activity and
`CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS` after a valid backward frame. One-byte
backward frames wait up to `CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS` for idle, then are
sent without echo verification because addressing-phase collisions are valid.
## API Note ## API Note
The global TX response queue symbol was renamed: The global TX response queue symbol was renamed:
+28 -7
View File
@@ -6,6 +6,14 @@
#include <memory.h> // for memset #include <memory.h> // for memset
#include "freertos/semphr.h" #include "freertos/semphr.h"
#ifndef CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS
#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 25
#endif
#ifndef CONFIG_DALI_DOUBLE_SEND_DELAY_MS
#define CONFIG_DALI_DOUBLE_SEND_DELAY_MS 10
#endif
static SemaphoreHandle_t s_dali_core_lock; static SemaphoreHandle_t s_dali_core_lock;
static SemaphoreHandle_t dali_core_mutex(void) static SemaphoreHandle_t dali_core_mutex(void)
@@ -31,6 +39,20 @@ static inline void dali_core_unlock(void)
} }
} }
static UBaseType_t drain_rx_queue(QueueHandle_t rx_q)
{
if (rx_q == NULL) {
return 0;
}
Dali_msg_t stale = {0};
UBaseType_t drained = 0;
while (xQueueReceive(rx_q, &stale, 0) == pdTRUE) {
drained++;
}
return drained;
}
Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) { Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) {
Dali_msg_t dali_msg; Dali_msg_t dali_msg;
dali_msg.id = 0; dali_msg.id = 0;
@@ -90,7 +112,7 @@ void dali_send_double(Dali_msg_t *dali_msg) {
dali_send_locked(dali_msg); dali_send_locked(dali_msg);
// TODO check status // TODO check status
dali_msg->id++; // increment message ID dali_msg->id++; // increment message ID
dali_delay_ms(10); // delay 13ms 101.8.1.2: 13.5 - 75ms dali_delay_ms(CONFIG_DALI_DOUBLE_SEND_DELAY_MS);
dali_send_locked(dali_msg); dali_send_locked(dali_msg);
// TODO check status // TODO check status
dali_core_unlock(); dali_core_unlock();
@@ -110,17 +132,16 @@ int dali_query(Dali_msg_t *tx_msg, Dali_msg_t *rx_msg) {
return -1; return -1;
} }
// TODO check empty queue dali_core_lock();
if(xQueueReceive(rx_q, rx_msg, 0) == pdTRUE) { UBaseType_t drained = drain_rx_queue(rx_q);
printf("Queue not empty\n"); if (drained > 0) {
return -1; printf("dali_query: dropped %u stale RX frame(s)\n", (unsigned)drained);
} }
// printf("check A tx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0]); // printf("check A tx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0]);
dali_core_lock();
dali_send_locked(tx_msg); dali_send_locked(tx_msg);
// receive message from DALI task // receive message from DALI task
ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(50)); ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS));
dali_core_unlock(); dali_core_unlock();
// printf("B rx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, rx_msg->type, rx_msg->status, rx_msg->length, rx_msg->data[0]); // printf("B rx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, rx_msg->type, rx_msg->status, rx_msg->length, rx_msg->data[0]);
return ret; return ret;
+621 -52
View File
@@ -4,6 +4,15 @@
//ESP-IDF HAL //ESP-IDF HAL
#ifdef CONFIG_IDF_TARGET #ifdef CONFIG_IDF_TARGET
#include "sdkconfig.h" #include "sdkconfig.h"
#ifndef CONFIG_DALI_LOG_LEVEL
#define CONFIG_DALI_LOG_LEVEL 2
#endif
#ifndef LOG_LOCAL_LEVEL
#define LOG_LOCAL_LEVEL CONFIG_DALI_LOG_LEVEL
#endif
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/queue.h" #include "freertos/queue.h"
@@ -39,7 +48,7 @@
#endif #endif
#ifndef CONFIG_DALI_DALI_TASK_STACK_SIZE #ifndef CONFIG_DALI_DALI_TASK_STACK_SIZE
#define CONFIG_DALI_DALI_TASK_STACK_SIZE 2048 #define CONFIG_DALI_DALI_TASK_STACK_SIZE 4096
#endif #endif
#ifndef CONFIG_DALI_DALI_TASK_PRIORITY #ifndef CONFIG_DALI_DALI_TASK_PRIORITY
@@ -54,14 +63,57 @@
#define CONFIG_DALI_DEBUG_TASK_PRIORITY 1 #define CONFIG_DALI_DEBUG_TASK_PRIORITY 1
#endif #endif
#ifndef CONFIG_DALI_TIMER_RESOLUTION_HZ
#define CONFIG_DALI_TIMER_RESOLUTION_HZ 3000000
#endif
#ifndef CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US
#define CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US 0
#endif
#ifndef CONFIG_DALI_TX_STOP_CONDITION_US
#define CONFIG_DALI_TX_STOP_CONDITION_US 0
#endif
#ifndef CONFIG_DALI_RX_STOP_CONDITION_US
#define CONFIG_DALI_RX_STOP_CONDITION_US 0
#endif
#ifndef CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS
#define CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS 100
#endif
#ifndef CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS
#define CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS 1000
#endif
#ifndef CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS
#define CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS 25
#endif
#ifndef CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
#define CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS 5
#endif
#ifndef CONFIG_DALI_FORWARD_MAX_WAIT_MS
#define CONFIG_DALI_FORWARD_MAX_WAIT_MS 50
#endif
#ifndef CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS
#define CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS 9
#endif
#define WITHIN_RANGE(x, min, max) ((x) > (min) && (x) < (max)) #define WITHIN_RANGE(x, min, max) ((x) > (min) && (x) < (max))
#define MAX_DELTA_RELOAD_TIME 600000000 // 600s - max u32: 4,294,967,295~4,294s #define MAX_DELTA_RELOAD_TIME 600000000 // 600s - max u32: 4,294,967,295~4,294s
#define DALI_BAUDRATE_MIN 400U #define DALI_BAUDRATE_MIN 400U
#define DALI_BAUDRATE_MAX 2400U #define DALI_BAUDRATE_MAX 2400U
#define DALI_BUS_POWER_CHECK_INTERVAL_US ((uint64_t)CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS * 1000ULL)
#define DALI_BUS_ABNORMAL_REPORT_INTERVAL_US ((uint64_t)CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS * 1000ULL)
typedef struct { typedef struct {
uint32_t hb; uint32_t hb;
uint32_t timer_alarm_ticks;
uint32_t rx_hb_min; uint32_t rx_hb_min;
uint32_t rx_hb_max; uint32_t rx_hb_max;
uint32_t rx_2hb_min; uint32_t rx_2hb_min;
@@ -76,6 +128,11 @@ typedef struct {
static const char *TAG = "dali_hal"; static const char *TAG = "dali_hal";
static inline void apply_dali_log_level(void)
{
esp_log_level_set(TAG, (esp_log_level_t)CONFIG_DALI_LOG_LEVEL);
}
typedef struct { typedef struct {
uint8_t bus_id; uint8_t bus_id;
uint8_t tx_pin; uint8_t tx_pin;
@@ -91,8 +148,17 @@ typedef struct {
uint32_t rx_tx_delta; uint32_t rx_tx_delta;
uint32_t rx_pulse_width; uint32_t rx_pulse_width;
uint8_t rx_level; uint8_t rx_level;
uint8_t tx_level;
uint64_t bus_level_check_time;
uint64_t bus_abnormal_report_time;
Dali_msg_t tx_data; Dali_msg_t tx_data;
bool bus_activity_seen;
uint64_t rx_last_frame_time;
uint8_t rx_last_frame_length;
uint8_t rx_last_frame_status;
volatile bool force_backward_tx;
Dali_msg_t rx_data; Dali_msg_t rx_data;
uint8_t tx_half_bit_counter; uint8_t tx_half_bit_counter;
@@ -137,17 +203,31 @@ static inline void dali_hal_unlock(void)
} }
} }
static uint32_t dali_half_bit_from_baud(uint32_t baudrate) static uint32_t dali_half_bit_x100_from_baud(uint32_t baudrate)
{ {
if (baudrate < DALI_BAUDRATE_MIN || baudrate > DALI_BAUDRATE_MAX) { if (baudrate < DALI_BAUDRATE_MIN || baudrate > DALI_BAUDRATE_MAX) {
return 0; return 0;
} }
uint64_t hb = 500000ULL + (baudrate / 2U); // round to nearest uint64_t hb_x100 = 50000000ULL + (baudrate / 2U); // round to nearest 0.01 us
hb /= baudrate; hb_x100 /= baudrate;
if (hb == 0 || hb > 2000000ULL) { // should never happen with checked bounds if (hb_x100 == 0 || hb_x100 > 200000000ULL) { // should never happen with checked bounds
return 0; return 0;
} }
return (uint32_t)hb; return (uint32_t)hb_x100;
}
static uint32_t half_bit_us_from_x100(uint32_t hb_x100)
{
return (hb_x100 + 50U) / 100U;
}
static uint32_t timer_alarm_ticks_from_x100(uint32_t hb_x100)
{
uint64_t ticks = ((uint64_t)CONFIG_DALI_TIMER_RESOLUTION_HZ * hb_x100 + 50000000ULL) / 100000000ULL;
if (ticks == 0 || ticks > UINT32_MAX) {
return 0;
}
return (uint32_t)ticks;
} }
static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us) static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
@@ -155,25 +235,40 @@ static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
return (uint32_t)(((uint64_t)base_us * hb_us + (DALI_TIME_HB / 2U)) / DALI_TIME_HB); return (uint32_t)(((uint64_t)base_us * hb_us + (DALI_TIME_HB / 2U)) / DALI_TIME_HB);
} }
static inline uint32_t configured_or_scaled_time(uint32_t configured_us, uint32_t base_us,
uint32_t hb_us)
{
return configured_us > 0 ? configured_us : scale_time_by_hb(base_us, hb_us);
}
static esp_err_t update_timing_locked(uint32_t baudrate) static esp_err_t update_timing_locked(uint32_t baudrate)
{ {
uint32_t hb = dali_half_bit_from_baud(baudrate); uint32_t hb_x100 = CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US;
if (hb == 0) { if (hb_x100 == 0) {
ESP_LOGE(TAG, "invalid baudrate: %u", baudrate); hb_x100 = dali_half_bit_x100_from_baud(baudrate);
}
uint32_t hb = half_bit_us_from_x100(hb_x100);
uint32_t timer_alarm_ticks = timer_alarm_ticks_from_x100(hb_x100);
if (hb == 0 || timer_alarm_ticks == 0) {
ESP_LOGE(TAG, "invalid native timing: baudrate=%u hbX100Us=%u timerHz=%u",
baudrate, hb_x100, CONFIG_DALI_TIMER_RESOLUTION_HZ);
return ESP_ERR_INVALID_ARG; return ESP_ERR_INVALID_ARG;
} }
dali_timing_t new_timing = { dali_timing_t new_timing = {
.hb = hb, .hb = hb,
.timer_alarm_ticks = timer_alarm_ticks,
.rx_hb_min = scale_time_by_hb(DALI_RX_HB_MIN, hb), .rx_hb_min = scale_time_by_hb(DALI_RX_HB_MIN, hb),
.rx_hb_max = scale_time_by_hb(DALI_RX_HB_MAX, hb), .rx_hb_max = scale_time_by_hb(DALI_RX_HB_MAX, hb),
.rx_2hb_min = scale_time_by_hb(DALI_RX_2HB_MIN, hb), .rx_2hb_min = scale_time_by_hb(DALI_RX_2HB_MIN, hb),
.rx_2hb_max = scale_time_by_hb(DALI_RX_2HB_MAX, hb), .rx_2hb_max = scale_time_by_hb(DALI_RX_2HB_MAX, hb),
.rx_stop_cond = scale_time_by_hb(DALI_RX_STOP_COND, hb), .rx_stop_cond = configured_or_scaled_time(CONFIG_DALI_RX_STOP_CONDITION_US,
DALI_RX_STOP_COND, hb),
.time_bus_down = scale_time_by_hb(DALI_TIME_BUS_DOWN, hb), .time_bus_down = scale_time_by_hb(DALI_TIME_BUS_DOWN, hb),
.time_break_min = scale_time_by_hb(DALI_TIME_BREAK_MIN, hb), .time_break_min = scale_time_by_hb(DALI_TIME_BREAK_MIN, hb),
.time_recovery_min = scale_time_by_hb(DALI_TIME_RECOVERY_MIN, hb), .time_recovery_min = scale_time_by_hb(DALI_TIME_RECOVERY_MIN, hb),
.tx_stop_cond = scale_time_by_hb(DALI_TX_STOP_COND, hb), .tx_stop_cond = configured_or_scaled_time(CONFIG_DALI_TX_STOP_CONDITION_US,
DALI_TX_STOP_COND, hb),
.collision_txrx_delta = scale_time_by_hb(DALI_COLLISION_TXRX_DELTA, hb), .collision_txrx_delta = scale_time_by_hb(DALI_COLLISION_TXRX_DELTA, hb),
}; };
@@ -199,7 +294,7 @@ static esp_err_t apply_timer_alarm_locked(void)
return ESP_OK; return ESP_OK;
} }
gptimer_alarm_config_t timer_alarm_config = { gptimer_alarm_config_t timer_alarm_config = {
.alarm_count = s_timing.hb, .alarm_count = s_timing.timer_alarm_ticks,
.reload_count = 0, .reload_count = 0,
.flags = { .flags = {
.auto_reload_on_alarm = true, .auto_reload_on_alarm = true,
@@ -235,22 +330,330 @@ static bool s_debug_task_created = false;
#endif #endif
static bool s_dali_task_created = false; static bool s_dali_task_created = false;
#define DALI_SET_BUS_HIGH(bus) gpio_set_level((bus)->tx_pin, DALI_TX_HIGH) // set bus level #define DALI_SET_BUS_HIGH(bus) do { gpio_set_level((bus)->tx_pin, DALI_TX_HIGH); (bus)->tx_level = 1; } while (0)
#define DALI_SET_BUS_LOW(bus) gpio_set_level((bus)->tx_pin, DALI_TX_LOW) // set bus level #define DALI_SET_BUS_LOW(bus) do { gpio_set_level((bus)->tx_pin, DALI_TX_LOW); (bus)->tx_level = 0; } while (0)
#define DALI_SET_BUS_LEVEL(bus, x) gpio_set_level((bus)->tx_pin, ((x)==DALI_TX_HIGH)) // set bus level #define DALI_SET_BUS_LEVEL(bus, x) do { \
uint8_t level = (x) ? 1 : 0; \
gpio_set_level((bus)->tx_pin, level ? DALI_TX_HIGH : DALI_TX_LOW); \
(bus)->tx_level = level; \
} while (0)
// !!! read from RX pin, we need real bus level, not logic level of TX pin // !!! read from RX pin, we need real bus level, not logic level of TX pin
// return: 0 - bus level low, active state // return: 0 - bus level low, active state
// 1 - bus level high, idle state // 1 - bus level high, idle state
#define DALI_GET_BUS_LEVEL(bus) (gpio_get_level((bus)->rx_pin) == (DALI_RX_HIGH)) // get bus level #define DALI_GET_BUS_LEVEL(bus) (gpio_get_level((bus)->rx_pin) == (DALI_RX_HIGH)) // get bus level
// return: 0 - tx pin drive bus low, active state // return: 0 - TX drives bus low, active state
// 1 - tx pin drive bus high, idle state // 1 - TX releases/drives bus high, idle state
#define DALI_GET_TX_LEVEL(bus) (gpio_get_level((bus)->tx_pin) == (DALI_TX_HIGH)) // get TX pin level #define DALI_GET_TX_LEVEL(bus) ((bus)->tx_level)
static inline bool bus_valid(uint8_t bus_id) { static inline bool bus_valid(uint8_t bus_id) {
return bus_id < DALI_PHY_COUNT && s_bus[bus_id].inited; return bus_id < DALI_PHY_COUNT && s_bus[bus_id].inited;
} }
static const char *bus_state_name(dali_bus_state_t state)
{
switch (state) {
case DALI_BUS_UNKNOWN: return "unknown";
case DALI_BUS_POWER_DOWN: return "power_down";
case DALI_BUS_ERROR: return "error";
case DALI_BUS_READY: return "ready";
case DALI_BUS_TRANSMITTING: return "transmitting";
case DALI_BUS_RECEIVING: return "receiving";
case DALI_BUS_TIME_BREAK: return "time_break";
case DALI_BUS_RECOVERY: return "recovery";
default: return "invalid";
}
}
static const char *tx_state_name(dali_tx_state_t state)
{
switch (state) {
case TX_STATE_ERROR: return "error";
case TX_STATE_COLLISION: return "collision";
case TX_STATE_IDLE: return "idle";
case TX_STATE_START: return "start";
case TX_STATE_DATA: return "data";
case TX_STATE_STOP: return "stop";
default: return "invalid";
}
}
static const char *rx_state_name(dali_rx_state_t state)
{
switch (state) {
case RX_STATE_ERROR: return "error";
case RX_STATE_IDLE: return "idle";
case RX_STATE_START: return "start";
case RX_STATE_DATA: return "data";
case RX_STATE_STOP: return "stop";
case RX_STATE_END: return "end";
default: return "invalid";
}
}
static uint32_t tx_completion_timeout_ms(const Dali_msg_t *msg)
{
uint32_t hb = s_timing_ready ? s_timing.hb : DALI_TIME_HB;
uint32_t stop_us = s_timing_ready ? s_timing.tx_stop_cond : DALI_TX_STOP_COND;
uint32_t bits = msg ? msg->length : DALI_MAX_BITS;
if (bits == 0 || bits > DALI_MAX_BITS) {
bits = DALI_MAX_BITS;
}
uint64_t frame_us = ((uint64_t)(2U + bits * 2U) * hb) + stop_us;
uint32_t frame_ms = (uint32_t)((frame_us + 999U) / 1000U);
uint32_t timeout_ms = frame_ms + 30U;
if (timeout_ms < 30U) {
timeout_ms = 30U;
}
if (timeout_ms > 500U) {
timeout_ms = 500U;
}
return timeout_ms;
}
static inline bool IRAM_ATTR is_backward_tx_msg(const Dali_msg_t *msg)
{
return msg != NULL && msg->length == 8;
}
static bool is_forward_tx_msg(const Dali_msg_t *msg)
{
return msg != NULL && msg->length >= 16 && msg->length <= 32 && (msg->length % 8) == 0;
}
static UBaseType_t queue_waiting(QueueHandle_t queue);
static uint64_t bus_last_activity_time_us(const dali_bus_ctx_t *bus)
{
if (bus == NULL || !bus->bus_activity_seen) {
return 0;
}
return bus->rx_last_edge_time > bus->tx_last_edge_time ? bus->rx_last_edge_time
: bus->tx_last_edge_time;
}
static bool bus_idle_for_tx(const dali_bus_ctx_t *bus)
{
return bus != NULL && bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE &&
bus->rx_state == RX_STATE_IDLE && bus->rx_level == 1 && queue_waiting(bus->tx_queue) == 0;
}
static bool last_activity_was_backward_frame(const dali_bus_ctx_t *bus, uint64_t last_activity)
{
return bus != NULL && bus->rx_last_frame_status == DALI_FRAME_OK &&
bus->rx_last_frame_length == 8 && bus->rx_last_frame_time != 0 &&
bus->rx_last_frame_time >= last_activity;
}
static TickType_t tx_wait_poll_ticks(void)
{
TickType_t ticks = pdMS_TO_TICKS(1);
return ticks == 0 ? 1 : ticks;
}
static bool wait_for_forward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg)
{
if (bus == NULL || !is_forward_tx_msg(msg)) {
return true;
}
const uint64_t started = esp_timer_get_time();
const uint64_t max_wait_us = (uint64_t)CONFIG_DALI_FORWARD_MAX_WAIT_MS * 1000ULL;
const TickType_t poll_ticks = tx_wait_poll_ticks();
while (true) {
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
return false;
}
const uint64_t now = esp_timer_get_time();
if (bus_idle_for_tx(bus)) {
const uint64_t last_activity = bus_last_activity_time_us(bus);
if (last_activity == 0) {
return true;
}
const uint32_t wait_ms = last_activity_was_backward_frame(bus, last_activity)
? CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
: CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS;
if (wait_ms == 0 || (now - last_activity) >= ((uint64_t)wait_ms * 1000ULL)) {
return true;
}
}
if (max_wait_us == 0 || (now - started) >= max_wait_us) {
return false;
}
vTaskDelay(poll_ticks);
}
}
static bool wait_for_backward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg,
bool *force_start)
{
if (force_start != NULL) {
*force_start = false;
}
if (bus == NULL || !is_backward_tx_msg(msg)) {
return true;
}
const uint64_t started = esp_timer_get_time();
const uint64_t timeout_us = (uint64_t)CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS * 1000ULL;
const TickType_t poll_ticks = tx_wait_poll_ticks();
while (true) {
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
return false;
}
if (bus_idle_for_tx(bus)) {
return true;
}
const uint64_t now = esp_timer_get_time();
if (timeout_us == 0 || (now - started) >= timeout_us) {
if (force_start != NULL) {
*force_start = true;
}
return true;
}
vTaskDelay(poll_ticks);
}
}
static UBaseType_t queue_waiting(QueueHandle_t queue)
{
return queue ? uxQueueMessagesWaiting(queue) : 0;
}
static void log_tx_message(const char *prefix, const dali_bus_ctx_t *bus,
const Dali_msg_t *msg, uint32_t timeout_ms)
{
if (bus == NULL || msg == NULL) {
return;
}
ESP_LOGW(TAG,
"%s bus=%u timeoutMs=%lu busState=%s txState=%s rxState=%s "
"txQ=%u replyQ=%u status=%u len=%u data=%02x %02x %02x %02x",
prefix, bus->bus_id, (unsigned long)timeout_ms,
bus_state_name(bus->bus_state), tx_state_name(bus->tx_state),
rx_state_name(bus->rx_state), (unsigned)queue_waiting(bus->tx_queue),
(unsigned)queue_waiting(bus->tx_reply_queue), msg->status, msg->length,
msg->data[0], msg->data[1], msg->data[2], msg->data[3]);
}
static void drain_tx_reply_queue(dali_bus_ctx_t *bus)
{
if (bus == NULL || bus->tx_reply_queue == NULL) {
return;
}
Dali_msg_t stale = {0};
UBaseType_t drained = 0;
while (xQueueReceive(bus->tx_reply_queue, &stale, 0) == pdTRUE) {
drained++;
}
if (drained > 0) {
ESP_LOGW(TAG,
"bus=%u dropped %u stale TX completion(s) status=%u len=%u data=%02x %02x %02x %02x",
bus->bus_id, (unsigned)drained, stale.status, stale.length, stale.data[0],
stale.data[1], stale.data[2], stale.data[3]);
}
}
static void recover_tx_timeout(dali_bus_ctx_t *bus)
{
if (bus == NULL) {
return;
}
if (bus->tx_queue) {
xQueueReset(bus->tx_queue);
}
if (bus->tx_reply_queue) {
xQueueReset(bus->tx_reply_queue);
}
bus->tx_data.status = DALI_FRAME_ERROR;
bus->tx_state = TX_STATE_IDLE;
bus->rx_state = RX_STATE_IDLE;
bus->tx_half_bit_counter = 0;
bus->tx_data_bit_counter = 0;
bus->rx_half_bit_counter = 0;
bus->rx_data_bit_counter = 0;
if (bus->bus_state == DALI_BUS_TRANSMITTING || bus->bus_state == DALI_BUS_TIME_BREAK ||
bus->bus_state == DALI_BUS_RECOVERY) {
bus->tx_last_edge_time = esp_timer_get_time();
DALI_SET_BUS_HIGH(bus);
bus->bus_state = DALI_BUS_READY;
}
ESP_LOGW(TAG, "bus=%u native TX queue recovered busState=%s txState=%s rxState=%s",
bus->bus_id, bus_state_name(bus->bus_state), tx_state_name(bus->tx_state),
rx_state_name(bus->rx_state));
}
static inline bool IRAM_ATTR interval_elapsed_us(uint64_t time_now, uint64_t last_time,
uint64_t interval_us)
{
return interval_us > 0 && (time_now - last_time) >= interval_us;
}
static void IRAM_ATTR publish_bus_abnormal_from_isr(dali_bus_ctx_t *bus, BaseType_t *yield)
{
if (bus == NULL || dali_raw_receive_queue == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0) {
return;
}
Dali_msg_t abnormal = {0};
abnormal.id = bus->bus_id;
abnormal.type = DALI_MSG_FORWARD;
abnormal.status = DALI_FRAME_OK;
abnormal.length = 16;
abnormal.data[0] = 0xFF;
abnormal.data[1] = 0xFD;
if (xQueueSendToBackFromISR(dali_raw_receive_queue, &abnormal, yield) != pdTRUE) {
Dali_msg_t dropped = {0};
xQueueReceiveFromISR(dali_raw_receive_queue, &dropped, yield);
xQueueSendToBackFromISR(dali_raw_receive_queue, &abnormal, yield);
}
}
static void IRAM_ATTR maybe_report_bus_abnormal_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
BaseType_t *yield)
{
if (bus == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0) {
return;
}
if (!interval_elapsed_us(time_now, bus->bus_abnormal_report_time,
DALI_BUS_ABNORMAL_REPORT_INTERVAL_US)) {
return;
}
publish_bus_abnormal_from_isr(bus, yield);
bus->bus_abnormal_report_time = time_now;
}
static void IRAM_ATTR poll_power_down_bus_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
BaseType_t *yield)
{
if (bus == NULL || bus->bus_state != DALI_BUS_POWER_DOWN) {
return;
}
if (interval_elapsed_us(time_now, bus->bus_level_check_time,
DALI_BUS_POWER_CHECK_INTERVAL_US)) {
const uint8_t current_level = DALI_GET_BUS_LEVEL(bus);
bus->bus_level_check_time = time_now;
if (current_level != bus->rx_level) {
bus->rx_level = current_level;
bus->rx_last_edge_time = time_now;
}
}
maybe_report_bus_abnormal_from_isr(bus, time_now, yield);
}
static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queue, BaseType_t *yield) static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queue, BaseType_t *yield)
{ {
if (msg == NULL) { if (msg == NULL) {
@@ -264,6 +667,48 @@ static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queu
} }
} }
static inline void IRAM_ATTR note_rx_frame_from_isr(dali_bus_ctx_t *bus)
{
if (bus == NULL) {
return;
}
bus->rx_last_frame_time = bus->rx_last_edge_time;
bus->rx_last_frame_length = bus->rx_data.length;
bus->rx_last_frame_status = bus->rx_data.status;
}
static void IRAM_ATTR complete_tx_from_isr(dali_bus_ctx_t *bus, BaseType_t *yield)
{
if (bus == NULL) {
return;
}
bus->tx_data.status = DALI_FRAME_OK;
if (!is_backward_tx_msg(&bus->tx_data) && bus->tx_reply_queue) {
if (xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, yield) != pdTRUE) {
Dali_msg_t dropped = {0};
xQueueReceiveFromISR(bus->tx_reply_queue, &dropped, yield);
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, yield);
}
}
bus->tx_state = TX_STATE_IDLE;
}
static void IRAM_ATTR start_tx_collision_recovery_from_isr(dali_bus_ctx_t *bus,
uint64_t time_now)
{
if (bus == NULL) {
return;
}
bus->rx_last_edge_time = time_now;
bus->tx_last_edge_time = time_now;
DALI_SET_BUS_LOW(bus);
bus->bus_state = DALI_BUS_TIME_BREAK;
bus->tx_data.status = DALI_FRAME_COLLISION;
}
// GPIO ISR handler // GPIO ISR handler
// define rx_gpio_isr_handler on any edge // define rx_gpio_isr_handler on any edge
static void IRAM_ATTR rx_gpio_isr_handler(void* arg) static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
@@ -273,6 +718,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
return; return;
} }
BaseType_t yield = false;
uint64_t rx_current_edge_time = esp_timer_get_time(); // get time in us uint64_t rx_current_edge_time = esp_timer_get_time(); // get time in us
uint8_t rx_previous_level = bus->rx_level; uint8_t rx_previous_level = bus->rx_level;
@@ -280,6 +726,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
// rx_level = 1 if and only if DALI bus is really high, idle // rx_level = 1 if and only if DALI bus is really high, idle
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin - not depend on hw: 0 - low, 1 - high bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin - not depend on hw: 0 - low, 1 - high
bus->bus_activity_seen = true;
bus->rx_pulse_width = rx_current_edge_time - bus->rx_last_edge_time; // time from last edge bus->rx_pulse_width = rx_current_edge_time - bus->rx_last_edge_time; // time from last edge
bus->rx_tx_delta = rx_current_edge_time - bus->tx_last_edge_time; // time from last edge bus->rx_tx_delta = rx_current_edge_time - bus->tx_last_edge_time; // time from last edge
@@ -287,8 +734,16 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
// always save time of last edge // always save time of last edge
bus->rx_last_edge_time = rx_current_edge_time; // get time in us bus->rx_last_edge_time = rx_current_edge_time; // get time in us
if(bus->bus_state == DALI_BUS_READY && bus->rx_level == 0) // found start bit bool tx_stop_released = bus->bus_state == DALI_BUS_TRANSMITTING &&
bus->tx_state == TX_STATE_STOP &&
DALI_GET_TX_LEVEL(bus) == 1;
if((bus->bus_state == DALI_BUS_READY || tx_stop_released) && bus->rx_level == 0) // found start bit
{ {
if (tx_stop_released) {
complete_tx_from_isr(bus, &yield);
}
// within range for backward frame // within range for backward frame
uint32_t time_ms = bus->rx_pulse_width / 1000; // 1ms = 1000us uint32_t time_ms = bus->rx_pulse_width / 1000; // 1ms = 1000us
if(time_ms>255) bus->rx_data.type = 255; if(time_ms>255) bus->rx_data.type = 255;
@@ -378,12 +833,14 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
// if collision detected: we are too late after bit was transmitted // if collision detected: we are too late after bit was transmitted
else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->rx_tx_delta > s_timing.collision_txrx_delta) else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state != TX_STATE_STOP &&
!is_backward_tx_msg(&bus->tx_data) &&
bus->rx_tx_delta > s_timing.collision_txrx_delta)
{ {
// we need now to start collision recovery with time break: 101.9.2.4 // we need now to start collision recovery with time break: 101.9.2.4
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_LOW(bus); // force TX low - active state, inform about collision, this also generate new GPIO ISR DALI_SET_BUS_LOW(bus); // force TX low - active state, inform about collision, this also generate new GPIO ISR
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->bus_state = DALI_BUS_TIME_BREAK; // we are in time break state bus->bus_state = DALI_BUS_TIME_BREAK; // we are in time break state
bus->tx_data.status = DALI_FRAME_COLLISION; // collision detected bus->tx_data.status = DALI_FRAME_COLLISION; // collision detected
} }
@@ -399,7 +856,11 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
dbg.rx_pulse_width = bus->rx_pulse_width; dbg.rx_pulse_width = bus->rx_pulse_width;
dbg.rx_tx_delta = bus->rx_tx_delta; dbg.rx_tx_delta = bus->rx_tx_delta;
dbg.bus_id = bus->bus_id; dbg.bus_id = bus->bus_id;
xQueueSendToBackFromISR(rx_dbg_queue, &dbg, NULL); // send data to queue xQueueSendToBackFromISR(rx_dbg_queue, &dbg, &yield); // send data to queue
}
if (yield) {
portYIELD_FROM_ISR();
} }
} }
@@ -423,6 +884,18 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
bus->tx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time bus->tx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time
} }
poll_power_down_bus_from_isr(bus, time_now, &yield);
rx_delta = time_now - bus->rx_last_edge_time;
tx_delta = time_now - bus->tx_last_edge_time;
if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state == TX_STATE_STOP &&
DALI_GET_TX_LEVEL(bus) == 1 &&
(bus->rx_state == RX_STATE_START || bus->rx_state == RX_STATE_DATA ||
bus->rx_state == RX_STATE_STOP)) {
complete_tx_from_isr(bus, &yield);
bus->bus_state = DALI_BUS_RECEIVING;
}
// recovery from different error states: UNKNOWN, ERROR, POWER_DOWN // recovery from different error states: UNKNOWN, ERROR, POWER_DOWN
if(bus->bus_state <= DALI_BUS_ERROR) if(bus->bus_state <= DALI_BUS_ERROR)
{ // 101.8.2.4 - startup BUS after 2.4ms { // 101.8.2.4 - startup BUS after 2.4ms
@@ -434,8 +907,13 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// if bus power down - if bus is low for more then 45ms // if bus power down - if bus is low for more then 45ms
if(bus->rx_level==0 && rx_delta > s_timing.time_bus_down) if(bus->rx_level==0 && rx_delta > s_timing.time_bus_down)
{ // power lost { // power lost
bool entered_power_down = bus->bus_state != DALI_BUS_POWER_DOWN;
bus->bus_state = DALI_BUS_POWER_DOWN; // bus is power down - recovery see previous if bus->bus_state = DALI_BUS_POWER_DOWN; // bus is power down - recovery see previous if
DALI_SET_BUS_HIGH(bus); // make sure TX is high DALI_SET_BUS_HIGH(bus); // make sure TX is high
if (entered_power_down) {
bus->bus_level_check_time = time_now;
bus->bus_abnormal_report_time = time_now;
}
} }
// recovery from collision detection // recovery from collision detection
@@ -444,9 +922,9 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// BUS: ACTIVE, LOW - BUS is busy, let caller to restart transmission // BUS: ACTIVE, LOW - BUS is busy, let caller to restart transmission
if(bus->bus_state == DALI_BUS_TIME_BREAK && rx_delta > s_timing.time_break_min) if(bus->bus_state == DALI_BUS_TIME_BREAK && rx_delta > s_timing.time_break_min)
{ {
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_HIGH(bus); // TX high - idle state - generate ISR on RX pin DALI_SET_BUS_HIGH(bus); // TX high - idle state - generate ISR on RX pin
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
// read bus state // read bus state
if(DALI_GET_BUS_LEVEL(bus) == 0) // other device is keeping bus low if(DALI_GET_BUS_LEVEL(bus) == 0) // other device is keeping bus low
{ {
@@ -467,33 +945,58 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
} }
// start transmitting // start transmitting
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE && bus->tx_queue && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, NULL) == pdTRUE) else if(bus->tx_state == TX_STATE_IDLE && bus->tx_queue)
{ {
bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success Dali_msg_t pending = {0};
bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting bool can_start = bus->bus_state == DALI_BUS_READY;
bus->tx_state = TX_STATE_START; // start transmitting bool force_backward = false;
bus->tx_half_bit_counter = 0; if (!can_start && bus->force_backward_tx &&
bus->tx_data_bit_counter = 0; // actually sent bits count xQueuePeekFromISR(bus->tx_queue, &pending) == pdTRUE) {
DALI_SET_BUS_LOW(bus); // start bit first half if (is_backward_tx_msg(&pending) && bus->bus_state != DALI_BUS_POWER_DOWN) {
can_start = true;
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us force_backward = true;
} else {
bus->force_backward_tx = false;
}
}
if (can_start && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, &yield) == pdTRUE) {
bus->force_backward_tx = false;
if (force_backward) {
bus->rx_state = RX_STATE_IDLE;
}
bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success
bus->bus_activity_seen = true;
bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting
bus->tx_state = TX_STATE_START; // start transmitting
bus->tx_half_bit_counter = 0;
bus->tx_data_bit_counter = 0; // actually sent bits count
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_LOW(bus); // start bit first half
}
} }
else if(bus->bus_state == DALI_BUS_TRANSMITTING) else if(bus->bus_state == DALI_BUS_TRANSMITTING)
{ {
uint8_t bus_level = DALI_GET_BUS_LEVEL(bus);
if(bus->tx_state != TX_STATE_STOP && !is_backward_tx_msg(&bus->tx_data) &&
tx_delta > s_timing.collision_txrx_delta &&
bus_level != DALI_GET_TX_LEVEL(bus)) {
bus->rx_level = bus_level;
bus->rx_tx_delta = tx_delta;
start_tx_collision_recovery_from_isr(bus, time_now);
}
// transmit data // transmit data
if(bus->tx_state == TX_STATE_START) { else if(bus->tx_state == TX_STATE_START) {
bus->tx_state = TX_STATE_DATA; // start transmitting data bus->tx_state = TX_STATE_DATA; // start transmitting data
bus->tx_half_bit_counter++; bus->tx_half_bit_counter++;
DALI_SET_BUS_HIGH(bus); // start bit second half
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_HIGH(bus); // start bit second half
} }
else if(bus->tx_state == TX_STATE_DATA) { else if(bus->tx_state == TX_STATE_DATA) {
bool value = (bus->tx_data.data[bus->tx_data_bit_counter/8] >> ( 7 - (bus->tx_data_bit_counter % 8) )) & 0x01; bool value = (bus->tx_data.data[bus->tx_data_bit_counter/8] >> ( 7 - (bus->tx_data_bit_counter % 8) )) & 0x01;
value ^= bus->tx_half_bit_counter & 0x01; // xor=invert value for odd half bit 1:0->1 and 0:1->0 value ^= bus->tx_half_bit_counter & 0x01; // xor=invert value for odd half bit 1:0->1 and 0:1->0
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_LEVEL(bus, value); DALI_SET_BUS_LEVEL(bus, value);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->tx_half_bit_counter++; // increment half bit counter before next test bus->tx_half_bit_counter++; // increment half bit counter before next test
if(bus->tx_half_bit_counter & 0x01) { // next bit if(bus->tx_half_bit_counter & 0x01) { // next bit
bus->tx_data_bit_counter++; bus->tx_data_bit_counter++;
@@ -506,15 +1009,11 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// here we check TX (NOT RX) bit state // here we check TX (NOT RX) bit state
if(DALI_GET_TX_LEVEL(bus) == 0) // really ok - otherwise we will keep bus low forever if(DALI_GET_TX_LEVEL(bus) == 0) // really ok - otherwise we will keep bus low forever
{ {
DALI_SET_BUS_HIGH(bus);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_HIGH(bus);
} }
else if(tx_delta > s_timing.tx_stop_cond) { else if(tx_delta > s_timing.tx_stop_cond) {
bus->tx_data.status = DALI_FRAME_OK; // frame is OK complete_tx_from_isr(bus, &yield);
if (bus->tx_reply_queue) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
}
bus->tx_state = TX_STATE_IDLE; // final state with transmitted data
bus->bus_state = DALI_BUS_READY; // bus is ready bus->bus_state = DALI_BUS_READY; // bus is ready
} }
} }
@@ -522,7 +1021,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state > TX_STATE_IDLE) else if(bus->bus_state == DALI_BUS_READY && bus->tx_state > TX_STATE_IDLE)
{ {
// we are not transmitting but we have data - reply to queue and let error state in tx_data.status // we are not transmitting but we have data - reply to queue and let error state in tx_data.status
if (bus->tx_reply_queue) { if (bus->tx_reply_queue && !is_backward_tx_msg(&bus->tx_data)) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
} }
bus->tx_state = TX_STATE_IDLE; // clear state bus->tx_state = TX_STATE_IDLE; // clear state
@@ -538,6 +1037,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// rx_data.status = DALI_FRAME_ERROR; // should be set inside ISR // rx_data.status = DALI_FRAME_ERROR; // should be set inside ISR
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
// rx_data.data[0] = 0xAA; // debug // rx_data.data[0] = 0xAA; // debug
note_rx_frame_from_isr(bus);
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
} }
} }
@@ -549,9 +1049,14 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
bus->rx_data.status = DALI_FRAME_OK; // frame is OK bus->rx_data.status = DALI_FRAME_OK; // frame is OK
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
// rx_data.data[0] = 0xBB; // debug // rx_data.data[0] = 0xBB; // debug
note_rx_frame_from_isr(bus);
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
} }
} }
else if(bus->rx_state == RX_STATE_START && rx_delta > s_timing.rx_hb_max) {
bus->rx_state = RX_STATE_ERROR;
bus->rx_data.status = DALI_FRAME_TIME_VIOLATION;
}
} }
return yield; return yield;
} }
@@ -588,7 +1093,7 @@ static void debug_task(void *pvParameters)
else v = '0' + dbg.level; else v = '0' + dbg.level;
} }
else v=' '; else v=' ';
printf("bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]", ESP_LOGD(TAG, "bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]",
dbg.bus_id, i++, dbg.rx_pulse_width, dbg.level, dbg.rx_tx_delta, v); dbg.bus_id, i++, dbg.rx_pulse_width, dbg.level, dbg.rx_tx_delta, v);
if(dbg.rx_pulse_width < 1000) { if(dbg.rx_pulse_width < 1000) {
if(dbg.rx_pulse_width > 550) HB+=2; if(dbg.rx_pulse_width > 550) HB+=2;
@@ -659,7 +1164,7 @@ static esp_err_t ensure_timer_started(void)
.flags = { .flags = {
.intr_shared = true, .intr_shared = true,
}, },
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us .resolution_hz = CONFIG_DALI_TIMER_RESOLUTION_HZ,
}; };
err = gptimer_new_timer(&timer_config, &gptimer); err = gptimer_new_timer(&timer_config, &gptimer);
if (err != ESP_OK) { if (err != ESP_OK) {
@@ -726,6 +1231,9 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
bus->bus_id = bus_id; bus->bus_id = bus_id;
bus->tx_pin = tx_pin; bus->tx_pin = tx_pin;
bus->rx_pin = rx_pin; bus->rx_pin = rx_pin;
bus->tx_level = 1;
gpio_set_level(bus->tx_pin, DALI_TX_HIGH);
gpio_config_t io_conf; gpio_config_t io_conf;
@@ -771,6 +1279,13 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
bus->rx_last_edge_time = esp_timer_get_time(); // get time in us - startup time bus->rx_last_edge_time = esp_timer_get_time(); // get time in us - startup time
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin
bus->tx_last_edge_time = bus->rx_last_edge_time; bus->tx_last_edge_time = bus->rx_last_edge_time;
bus->bus_level_check_time = bus->rx_last_edge_time;
bus->bus_abnormal_report_time = 0;
bus->bus_activity_seen = false;
bus->rx_last_frame_time = 0;
bus->rx_last_frame_length = 0;
bus->rx_last_frame_status = DALI_FRAME_UNKNOWN;
bus->force_backward_tx = false;
err = ensure_isr_service(); err = ensure_isr_service();
if (err != ESP_OK) { if (err != ESP_OK) {
@@ -805,17 +1320,69 @@ static int dali_tx_bus(dali_bus_ctx_t *bus, Dali_msg_t *dali_msg)
if (bus == NULL || !bus->inited) { if (bus == NULL || !bus->inited) {
return ESP_FAIL; return ESP_FAIL;
} }
if(xQueueSendToBack(bus->tx_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) { if (bus->tx_queue == NULL || bus->tx_reply_queue == NULL || dali_msg == NULL) {
xQueueReset(bus->tx_queue); // clear queue
printf("dali_tx: Queue full\n");
return ESP_FAIL; return ESP_FAIL;
} }
if(xQueueReceive(bus->tx_reply_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
xQueueReset(bus->tx_reply_queue); // clear queue const uint32_t timeout_ms = tx_completion_timeout_ms(dali_msg);
printf("dali_tx: No reply\n"); const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
drain_tx_reply_queue(bus);
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
dali_msg->status = DALI_FRAME_ERROR;
log_tx_message("native TX skipped, bus power down", bus, dali_msg, timeout_ms);
return ESP_FAIL; return ESP_FAIL;
} }
return ESP_OK;
ESP_LOGD(TAG, "bus=%u queue TX len=%u data=%02x %02x %02x %02x timeoutMs=%lu",
bus->bus_id, dali_msg->length, dali_msg->data[0], dali_msg->data[1],
dali_msg->data[2], dali_msg->data[3], (unsigned long)timeout_ms);
if (is_forward_tx_msg(dali_msg) && !wait_for_forward_tx_window(bus, dali_msg)) {
dali_msg->status = DALI_FRAME_ERROR;
log_tx_message("native TX wait timeout", bus, dali_msg,
CONFIG_DALI_FORWARD_MAX_WAIT_MS);
return ESP_FAIL;
}
bool force_backward = false;
if (is_backward_tx_msg(dali_msg) &&
!wait_for_backward_tx_window(bus, dali_msg, &force_backward)) {
dali_msg->status = DALI_FRAME_ERROR;
log_tx_message("native backward TX wait timeout", bus, dali_msg,
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS);
return ESP_FAIL;
}
bus->force_backward_tx = force_backward;
if(xQueueSendToBack(bus->tx_queue, dali_msg, 0) == pdFALSE) {
bus->force_backward_tx = false;
log_tx_message("native TX queue full", bus, dali_msg, timeout_ms);
if (bus->tx_state == TX_STATE_IDLE) {
xQueueReset(bus->tx_queue);
}
return ESP_FAIL;
}
if (is_backward_tx_msg(dali_msg)) {
dali_msg->status = DALI_FRAME_OK;
return ESP_OK;
}
if(xQueueReceive(bus->tx_reply_queue, dali_msg, timeout_ticks) == pdFALSE) {
if (bus->tx_state == TX_STATE_IDLE && queue_waiting(bus->tx_queue) == 0) {
*dali_msg = bus->tx_data;
log_tx_message("native TX completion queue missed", bus, dali_msg, timeout_ms);
return dali_msg->status == DALI_FRAME_OK ? ESP_OK : ESP_FAIL;
}
log_tx_message("native TX completion timeout", bus, dali_msg, timeout_ms);
recover_tx_timeout(bus);
return ESP_FAIL;
}
ESP_LOGD(TAG, "bus=%u TX complete status=%u len=%u data=%02x %02x %02x %02x",
bus->bus_id, dali_msg->status, dali_msg->length, dali_msg->data[0],
dali_msg->data[1], dali_msg->data[2], dali_msg->data[3]);
return dali_msg->status == DALI_FRAME_OK ? ESP_OK : ESP_FAIL;
} }
// dali_task - should run at highest priority // dali_task - should run at highest priority
@@ -843,6 +1410,7 @@ void dali_task(void *pvParameters)
esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin) esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
{ {
dali_hal_lock(); dali_hal_lock();
apply_dali_log_level();
esp_err_t err = ensure_timing_ready_locked(); esp_err_t err = ensure_timing_ready_locked();
if (err == ESP_OK) { if (err == ESP_OK) {
@@ -873,6 +1441,7 @@ esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
esp_err_t dali_hal_set_baudrate(uint32_t baudrate) esp_err_t dali_hal_set_baudrate(uint32_t baudrate)
{ {
dali_hal_lock(); dali_hal_lock();
apply_dali_log_level();
bool resume_timer = s_timer_started && gptimer; bool resume_timer = s_timer_started && gptimer;
if (resume_timer) { if (resume_timer) {
+8
View File
@@ -3,6 +3,10 @@
#include <stdint.h> #include <stdint.h>
#include "dali_hal.h" #include "dali_hal.h"
#ifdef __cplusplus
extern "C" {
#endif
/* /*
Addressing: 102.7.2.1 Addressing: 102.7.2.1
0AAA AAAx - short address AAAAAA 0-63 0AAA AAAx - short address AAAAAA 0-63
@@ -77,5 +81,9 @@ void dali_set_search_addr24(uint32_t addr24);
uint32_t dali_binary_search(); uint32_t dali_binary_search();
void dali_change_short_address(int addr1, int addr2); void dali_change_short_address(int addr1, int addr2);
#ifdef __cplusplus
}
#endif
+24 -7
View File
@@ -8,6 +8,10 @@
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/queue.h" #include "freertos/queue.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef CONFIG_DALI_PHY_COUNT #ifdef CONFIG_DALI_PHY_COUNT
#define DALI_PHY_COUNT CONFIG_DALI_PHY_COUNT #define DALI_PHY_COUNT CONFIG_DALI_PHY_COUNT
#else #else
@@ -182,13 +186,22 @@ typedef struct Dali_msg Dali_msg_t;
// define HW DALI for gpio functions // define HW DALI for gpio functions
#ifndef DALI_HW_PINS #ifndef DALI_HW_PINS
// define HW DALI for gpio functions // Physical GPIO levels for logical DALI idle/high and active/low bus states.
// - what we should write to pin to get HIGH/LOW #ifdef CONFIG_DALI_TX_ACTIVE_HIGH
#define DALI_TX_HIGH 1 // idle state #define DALI_TX_HIGH 0 // idle state
#define DALI_TX_LOW 0 // active state #define DALI_TX_LOW 1 // active state
// - what we should read from pin to get HIGH/LOW #else
#define DALI_RX_HIGH 1 // idle state #define DALI_TX_HIGH 1 // idle state
#define DALI_RX_LOW 0 // active state #define DALI_TX_LOW 0 // active state
#endif
#ifdef CONFIG_DALI_RX_ACTIVE_LOW
#define DALI_RX_HIGH 1 // idle state
#define DALI_RX_LOW 0 // active state
#else
#define DALI_RX_HIGH 0 // idle state
#define DALI_RX_LOW 1 // active state
#endif
#endif #endif
// LED onboard - debug // LED onboard - debug
@@ -228,3 +241,7 @@ esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info);
QueueHandle_t dali_hal_raw_receive_queue(void); QueueHandle_t dali_hal_raw_receive_queue(void);
void dali_task(void *pvParameters); void dali_task(void *pvParameters);
#ifdef __cplusplus
}
#endif
+80 -8
View File
@@ -10,6 +10,10 @@
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
#ifndef CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS
#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 25
#endif
namespace gateway { namespace gateway {
namespace { namespace {
@@ -17,6 +21,48 @@ namespace {
constexpr const char* kTag = "dali_domain"; constexpr const char* kTag = "dali_domain";
constexpr size_t kSerialRxPacketMaxBytes = 8; constexpr size_t kSerialRxPacketMaxBytes = 8;
constexpr UBaseType_t kSerialRxQueueDepth = 8; constexpr UBaseType_t kSerialRxQueueDepth = 8;
constexpr uint32_t kHardwareQueryRawSuppressMs = CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS + 10;
portMUX_TYPE s_query_raw_suppress_lock = portMUX_INITIALIZER_UNLOCKED;
TickType_t s_query_raw_suppress_until[DALI_PHY_COUNT] = {};
void BeginHardwareQueryRawSuppress(uint8_t bus_id) {
if (bus_id >= DALI_PHY_COUNT) {
return;
}
const TickType_t until = xTaskGetTickCount() + pdMS_TO_TICKS(kHardwareQueryRawSuppressMs);
portENTER_CRITICAL(&s_query_raw_suppress_lock);
s_query_raw_suppress_until[bus_id] = until;
portEXIT_CRITICAL(&s_query_raw_suppress_lock);
}
bool TakeHardwareQueryRawSuppress(uint8_t bus_id) {
if (bus_id >= DALI_PHY_COUNT) {
return false;
}
bool suppress = false;
const TickType_t now = xTaskGetTickCount();
portENTER_CRITICAL(&s_query_raw_suppress_lock);
const TickType_t until = s_query_raw_suppress_until[bus_id];
if (until != 0 && now <= until) {
suppress = true;
s_query_raw_suppress_until[bus_id] = 0;
} else if (until != 0) {
s_query_raw_suppress_until[bus_id] = 0;
}
portEXIT_CRITICAL(&s_query_raw_suppress_lock);
return suppress;
}
void ClearHardwareQueryRawSuppress(uint8_t bus_id) {
if (bus_id >= DALI_PHY_COUNT) {
return;
}
portENTER_CRITICAL(&s_query_raw_suppress_lock);
s_query_raw_suppress_until[bus_id] = 0;
portEXIT_CRITICAL(&s_query_raw_suppress_lock);
}
DaliDomainSnapshot MakeSnapshot(uint8_t gateway_id, int address, const char* kind) { DaliDomainSnapshot MakeSnapshot(uint8_t gateway_id, int address, const char* kind) {
DaliDomainSnapshot snapshot; DaliDomainSnapshot snapshot;
@@ -64,6 +110,10 @@ struct SerialRxPacket {
uint8_t data[kSerialRxPacketMaxBytes]{}; uint8_t data[kSerialRxPacketMaxBytes]{};
}; };
std::vector<uint8_t> LegacyQueryResponse(uint8_t status, uint8_t value = 0x00) {
return {status, value};
}
bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) { bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
if (data == nullptr || len != 3) { if (data == nullptr || len != 3) {
return false; return false;
@@ -89,9 +139,12 @@ bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
} }
std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) { std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
if (data == nullptr || len != 3) { if (data == nullptr) {
return {}; return {};
} }
if (len != 3) {
return len > 0 && data[0] == 0x12 ? LegacyQueryResponse(0xFD) : std::vector<uint8_t>{};
}
switch (data[0]) { switch (data[0]) {
case 0x00: case 0x00:
@@ -106,10 +159,16 @@ std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data,
Dali_msg_t tx = dali_msg_new(data[1], data[2]); Dali_msg_t tx = dali_msg_new(data[1], data[2]);
tx.id = bus_id; tx.id = bus_id;
Dali_msg_t rx = {}; Dali_msg_t rx = {};
BeginHardwareQueryRawSuppress(bus_id);
if (dali_query(&tx, &rx) == pdTRUE) { if (dali_query(&tx, &rx) == pdTRUE) {
if (rx.status != DALI_FRAME_OK || rx.length != 8) {
ClearHardwareQueryRawSuppress(bus_id);
return LegacyQueryResponse(0xFD);
}
return {0xFF, rx.data[0]}; return {0xFF, rx.data[0]};
} }
return {0xFE}; ClearHardwareQueryRawSuppress(bus_id);
return LegacyQueryResponse(0xFE);
} }
default: default:
return {}; return {};
@@ -151,13 +210,19 @@ std::vector<uint8_t> ReadSerialFrame(QueueHandle_t queue, size_t len, uint32_t t
std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue, std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
uint32_t query_timeout_ms, const uint8_t* data, uint32_t query_timeout_ms, const uint8_t* data,
size_t len) { size_t len) {
if (data == nullptr || len == 0) {
return LegacyQueryResponse(0xFD);
}
if (data[0] == 0x12 && len != 3) {
return LegacyQueryResponse(0xFD);
}
if (data != nullptr && len > 0 && data[0] == 0x12) { if (data != nullptr && len > 0 && data[0] == 0x12) {
DrainSerialQueue(queue); DrainSerialQueue(queue);
} }
if (!WriteSerialFrame(uart_port, data, len)) { if (!WriteSerialFrame(uart_port, data, len)) {
return {0xFD}; return LegacyQueryResponse(0xFD);
} }
if (data == nullptr || len == 0 || data[0] != 0x12) { if (data[0] != 0x12) {
return {0xFF}; return {0xFF};
} }
@@ -173,10 +238,10 @@ std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
auto response = PacketToVector(packet, 2); auto response = PacketToVector(packet, 2);
if (!response.empty() && if (!response.empty() &&
(response[0] == 0xFF || response[0] == 0xFE || response[0] == 0xFD)) { (response[0] == 0xFF || response[0] == 0xFE || response[0] == 0xFD)) {
return response; return LegacyQueryResponse(response[0], response.size() > 1 ? response[1] : 0x00);
} }
} }
return {0xFE}; return LegacyQueryResponse(0xFE);
} }
} // namespace } // namespace
@@ -290,8 +355,9 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
ESP_LOGE(kTag, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err)); ESP_LOGE(kTag, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err));
return err; return err;
} }
err = uart_set_pin(uart, config.tx_pin, config.rx_pin, UART_PIN_NO_CHANGE, err = uart_set_pin(uart, config.tx_pin < 0 ? UART_PIN_NO_CHANGE : config.tx_pin,
UART_PIN_NO_CHANGE); config.rx_pin < 0 ? UART_PIN_NO_CHANGE : config.rx_pin,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set uart%d pins tx=%d rx=%d: %s", config.uart_port, 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)); config.tx_pin, config.rx_pin, esp_err_to_name(err));
@@ -1233,6 +1299,12 @@ void DaliDomainService::rawFrameTaskLoop() {
if (byte_count > DALI_MAX_BYTES) { if (byte_count > DALI_MAX_BYTES) {
byte_count = DALI_MAX_BYTES; byte_count = DALI_MAX_BYTES;
} }
if (byte_count == 1 && TakeHardwareQueryRawSuppress(message.id)) {
continue;
}
if (byte_count != 1 && byte_count != 2 && byte_count != 3) {
continue;
}
DaliRawFrame frame; DaliRawFrame frame;
frame.channel_index = channel->config.channel_index; frame.channel_index = channel->config.channel_index;
frame.gateway_id = channel->config.gateway_id; frame.gateway_id = channel->config.gateway_id;
+8 -1
View File
@@ -141,6 +141,13 @@ void RegisterGatt(struct ble_gatt_register_ctxt* ctxt, void* arg) {
} }
} }
std::vector<uint8_t> LegacyRawPayload(const std::vector<uint8_t>& data) {
if (data.size() == 1) {
return {0xBE, data[0]};
}
return data;
}
const struct ble_gatt_svc_def kGattServices[] = { const struct ble_gatt_svc_def kGattServices[] = {
{ {
.type = BLE_GATT_SVC_TYPE_PRIMARY, .type = BLE_GATT_SVC_TYPE_PRIMARY,
@@ -377,7 +384,7 @@ void GatewayBleBridge::handleDaliRawFrame(const DaliRawFrame& frame) {
if (!enabled_ || conn_handle_ == kInvalidConnectionHandle || frame.data.empty()) { if (!enabled_ || conn_handle_ == kInvalidConnectionHandle || frame.data.empty()) {
return; return;
} }
notifyCharacteristic(frame.channel_index, frame.data); notifyCharacteristic(frame.channel_index, LegacyRawPayload(frame.data));
} }
void GatewayBleBridge::handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload) { void GatewayBleBridge::handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload) {
@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -16,6 +17,7 @@
namespace gateway { namespace gateway {
class DaliDomainService; class DaliDomainService;
struct DaliRawFrame;
class GatewayCache; class GatewayCache;
struct GatewayBridgeServiceConfig { struct GatewayBridgeServiceConfig {
@@ -36,7 +38,7 @@ struct GatewayBridgeServiceConfig {
std::vector<int> reserved_uart_ports; std::vector<int> reserved_uart_ports;
uint32_t bacnet_task_stack_size{8192}; uint32_t bacnet_task_stack_size{8192};
UBaseType_t bacnet_task_priority{5}; 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}; UBaseType_t knx_task_priority{5};
std::optional<GatewayKnxConfig> default_knx_config; std::optional<GatewayKnxConfig> default_knx_config;
}; };
@@ -65,6 +67,15 @@ class GatewayBridgeService {
ChannelRuntime* findRuntime(uint8_t gateway_id); ChannelRuntime* findRuntime(uint8_t gateway_id);
const ChannelRuntime* findRuntime(uint8_t gateway_id) const; 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, void collectUsedRuntimeResources(uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports, std::set<uint16_t>* modbus_tcp_ports,
std::set<uint16_t>* knx_udp_ports, std::set<uint16_t>* knx_udp_ports,
@@ -74,6 +85,7 @@ class GatewayBridgeService {
GatewayCache& cache_; GatewayCache& cache_;
GatewayBridgeServiceConfig config_; GatewayBridgeServiceConfig config_;
std::vector<std::unique_ptr<ChannelRuntime>> runtimes_; std::vector<std::unique_ptr<ChannelRuntime>> runtimes_;
ChannelRuntime* knx_endpoint_runtime_{nullptr};
}; };
} // namespace gateway } // namespace gateway
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,7 +1,7 @@
idf_component_register( idf_component_register(
SRCS "src/gateway_knx.cpp" SRCS "src/gateway_knx.cpp"
INCLUDE_DIRS "include" 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) set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
+122 -9
View File
@@ -5,16 +5,19 @@
#include "esp_err.h" #include "esp_err.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "lwip/sockets.h" #include "lwip/sockets.h"
#include <atomic> #include <atomic>
#include <array>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <map> #include <map>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <set>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -36,6 +39,7 @@ struct GatewayKnxTpUartConfig {
size_t rx_buffer_size{1024}; size_t rx_buffer_size{1024};
size_t tx_buffer_size{1024}; size_t tx_buffer_size{1024};
uint32_t read_timeout_ms{20}; uint32_t read_timeout_ms{20};
bool nine_bit_mode{true};
}; };
enum class GatewayKnxMappingMode : uint8_t { enum class GatewayKnxMappingMode : uint8_t {
@@ -60,7 +64,12 @@ struct GatewayKnxConfig {
uint8_t main_group{0}; uint8_t main_group{0};
uint16_t udp_port{kGatewayKnxDefaultUdpPort}; uint16_t udp_port{kGatewayKnxDefaultUdpPort};
std::string multicast_address{kGatewayKnxDefaultMulticastAddress}; 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; std::vector<GatewayKnxEtsAssociation> ets_associations;
GatewayKnxTpUartConfig tp_uart; GatewayKnxTpUartConfig tp_uart;
}; };
@@ -110,6 +119,7 @@ struct GatewayKnxCommissioningBallast {
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value); std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config); DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
bool GatewayKnxConfigUsesTpUart(const GatewayKnxConfig& config);
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode); const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode);
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value); GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value);
@@ -131,6 +141,8 @@ class GatewayKnxBridge {
size_t etsBindingCount() const; size_t etsBindingCount() const;
std::vector<GatewayKnxDaliBinding> describeDaliBindings() 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 handleCemiFrame(const uint8_t* data, size_t len);
DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data, DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data,
size_t len); size_t len);
@@ -185,74 +197,175 @@ class GatewayKnxBridge {
class GatewayKnxTpIpRouter { class GatewayKnxTpIpRouter {
public: public:
using CemiFrameHandler = std::function<DaliBridgeResult(const uint8_t* data, size_t len)>; 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, GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler,
std::string openknx_namespace = "openknx"); std::string openknx_namespace = "openknx");
~GatewayKnxTpIpRouter(); ~GatewayKnxTpIpRouter();
void setConfig(const GatewayKnxConfig& config); void setConfig(const GatewayKnxConfig& config);
void setCommissioningOnly(bool enabled);
void setGroupWriteHandler(GroupWriteHandler handler);
const GatewayKnxConfig& config() const; 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 start(uint32_t task_stack_size, UBaseType_t task_priority);
esp_err_t stop(); esp_err_t stop();
bool started() const; bool started() const;
const std::string& lastError() const; const std::string& lastError() const;
bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level);
private: 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); static void TaskEntry(void* arg);
esp_err_t initializeRuntime();
void taskLoop(); void taskLoop();
void finishTask(); void finishTask();
void closeSockets(); void closeSockets();
bool configureSocket(); bool configureSocket();
void handleTcpAccept();
void handleTcpClient(TcpClient& client);
void closeTcpClient(TcpClient& client);
bool configureTpUart(); bool configureTpUart();
bool initializeTpUart(); bool initializeTpUart();
bool configureProgrammingGpio();
void refreshNetworkInterfaces(bool force_log = false);
void handleUdpDatagram(const uint8_t* data, size_t len, const ::sockaddr_in& remote); 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 handleRoutingIndication(const uint8_t* body, size_t len);
void handleTunnellingRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote); 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 handleConnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
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 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 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, void sendConnectionStateResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote); const ::sockaddr_in& remote);
void sendDisconnectResponse(uint8_t channel_id, uint8_t status, void sendDisconnectResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote); const ::sockaddr_in& remote);
void sendConnectResponse(uint8_t channel_id, uint8_t status, 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); 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(); void syncOpenKnxConfigFromDevice();
uint16_t effectiveIndividualAddress() const; uint16_t effectiveIpInterfaceIndividualAddress() const;
uint16_t effectiveKnxDeviceIndividualAddress() const;
uint16_t effectiveTunnelAddress() const; uint16_t effectiveTunnelAddress() const;
void pollTpUart(); void pollTpUart();
void pollProgrammingButton();
void updateProgrammingLed();
void setProgrammingLed(bool on);
void handleTpUartControlByte(uint8_t byte); void handleTpUartControlByte(uint8_t byte);
void handleTpTelegram(const uint8_t* data, size_t len); void handleTpTelegram(const uint8_t* data, size_t len);
void forwardCemiToTp(const uint8_t* data, size_t len); void forwardCemiToTp(const uint8_t* data, size_t len);
GatewayKnxBridge& bridge_; GatewayKnxBridge& bridge_;
CemiFrameHandler handler_; CemiFrameHandler handler_;
GroupWriteHandler group_write_handler_;
std::string openknx_namespace_; std::string openknx_namespace_;
GatewayKnxConfig config_; GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_; std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
TaskHandle_t task_handle_{nullptr}; 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 stop_requested_{false};
std::atomic_bool started_{false}; std::atomic_bool started_{false};
int udp_sock_{-1}; int udp_sock_{-1};
int tcp_sock_{-1};
int active_tcp_sock_{-1};
int tp_uart_port_{-1}; int tp_uart_port_{-1};
uint8_t tunnel_channel_id_{1}; int tp_uart_tx_pin_{-1};
uint8_t expected_tunnel_sequence_{0}; int tp_uart_rx_pin_{-1};
uint8_t tunnel_send_sequence_{0}; std::vector<uint32_t> multicast_joined_interfaces_;
bool tunnel_connected_{false}; TickType_t network_refresh_tick_{0};
::sockaddr_in tunnel_remote_{}; 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_rx_frame_;
std::vector<uint8_t> tp_last_sent_telegram_; std::vector<uint8_t> tp_last_sent_telegram_;
TickType_t tp_uart_last_byte_tick_{0}; TickType_t tp_uart_last_byte_tick_{0};
bool tp_uart_extended_frame_{false}; bool tp_uart_extended_frame_{false};
bool tp_uart_online_{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_; 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)))); getObjectInt(json, "unitId").value_or(getObjectInt(json, "unit_id").value_or(1))));
if (const auto* serial_value = getObjectValue(json, "serial")) { if (const auto* serial_value = getObjectValue(json, "serial")) {
if (const auto* serial = serial_value->asObject()) { 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.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.rx_pin = clampedInt(*serial, "rxPin", config.serial.rx_pin, -1, 48);
config.serial.baudrate = clampedU32(*serial, "baudrate", config.serial.baudrate, config.serial.baudrate = clampedU32(*serial, "baudrate", config.serial.baudrate,
@@ -56,8 +56,10 @@ struct GatewayNetworkServiceConfig {
bool status_led_active_high{true}; bool status_led_active_high{true};
int boot_button_gpio{-1}; int boot_button_gpio{-1};
bool boot_button_active_low{true}; 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_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}; UBaseType_t boot_button_task_priority{2};
uint32_t udp_task_stack_size{4096}; uint32_t udp_task_stack_size{4096};
UBaseType_t udp_task_priority{4}; UBaseType_t udp_task_priority{4};
@@ -909,22 +909,37 @@ esp_err_t GatewayNetworkService::configureStatusLed() {
} }
esp_err_t GatewayNetworkService::configureBootButton() { 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; return ESP_OK;
} }
gpio_config_t io_config = {}; const auto configure_input = [](int gpio, bool active_low, const char* name) -> esp_err_t {
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.boot_button_gpio); if (gpio < 0) {
io_config.mode = GPIO_MODE_INPUT; return ESP_OK;
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; gpio_config_t io_config = {};
io_config.intr_type = GPIO_INTR_DISABLE; io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(gpio);
const esp_err_t err = gpio_config(&io_config); 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) { if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to configure boot button GPIO%d: %s", config_.boot_button_gpio, return err;
esp_err_to_name(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() { esp_err_t GatewayNetworkService::startHttpServer() {
@@ -985,7 +1000,8 @@ esp_err_t GatewayNetworkService::startUdpTask() {
} }
esp_err_t GatewayNetworkService::startBootButtonTask() { 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; return ESP_OK;
} }
@@ -1349,39 +1365,88 @@ void GatewayNetworkService::bootButtonTaskLoop() {
const TickType_t poll_ticks = pdMS_TO_TICKS(100); 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); const uint32_t long_press_ms = std::max<uint32_t>(config_.boot_button_long_press_ms, 100);
auto is_pressed = [this]() { auto is_pressed = [](int gpio, bool active_low) {
const int level = gpio_get_level(static_cast<gpio_num_t>(config_.boot_button_gpio)); if (gpio < 0) {
return config_.boot_button_active_low ? level == 0 : level != 0; return false;
}
const int level = gpio_get_level(static_cast<gpio_num_t>(gpio));
return active_low ? level == 0 : level != 0;
}; };
while (true) { auto wait_release = [&](int gpio, bool active_low) {
if (!is_pressed()) {
vTaskDelay(poll_ticks);
continue;
}
uint32_t pressed_ms = 0; uint32_t pressed_ms = 0;
while (is_pressed()) { while (is_pressed(gpio, active_low)) {
vTaskDelay(poll_ticks); vTaskDelay(poll_ticks);
pressed_ms += 100; pressed_ms += 100;
} }
return pressed_ms;
};
if (pressed_ms >= long_press_ms) { auto enter_setup_ap = [this]() {
ESP_LOGW(kTag, "BOOT long press clears Wi-Fi credentials and restarts"); ESP_LOGI(kTag, "setup AP button enters setup AP mode");
runtime_.clearWirelessInfo(); const uint32_t stack_size = std::max<uint32_t>(config_.boot_button_task_stack_size, 8192);
stopEspNow(); const BaseType_t created = xTaskCreate(
if (wifi_started_) { [](void* arg) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect()); auto* service = static_cast<GatewayNetworkService*>(arg);
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop()); 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)); vTaskDelay(pdMS_TO_TICKS(300));
esp_restart(); continue;
} else {
ESP_LOGI(kTag, "BOOT short press enters setup AP mode");
handleWifiControl(101);
} }
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);
} }
} }
@@ -12,6 +12,13 @@ namespace gateway {
namespace { namespace {
constexpr const char* kTag = "gateway_usb"; constexpr const char* kTag = "gateway_usb";
constexpr size_t kCommandFrameMinLen = 7; constexpr size_t kCommandFrameMinLen = 7;
std::vector<uint8_t> LegacyRawPayload(const std::vector<uint8_t>& data) {
if (data.size() == 1) {
return {0xBE, data[0]};
}
return data;
}
} }
GatewayUsbSetupBridge::GatewayUsbSetupBridge(GatewayController& controller, GatewayUsbSetupBridge::GatewayUsbSetupBridge(GatewayController& controller,
@@ -82,6 +89,19 @@ void GatewayUsbSetupBridge::handleBytes(const uint8_t* data, size_t len) {
} }
const uint8_t gateway_id = setupGatewayId(); const uint8_t gateway_id = setupGatewayId();
if (data[0] == 0x12) {
const auto response = dali_domain_.transactBridgeFrame(gateway_id, data, len);
if (!response.empty()) {
const int written = usb_serial_jtag_write_bytes(response.data(), response.size(),
pdMS_TO_TICKS(config_.write_timeout_ms));
if (written < 0 || static_cast<size_t>(written) != response.size()) {
ESP_LOGW(kTag, "failed to write USB raw query response channel=%u len=%u",
config_.channel_index, static_cast<unsigned>(response.size()));
}
}
return;
}
if (!dali_domain_.writeBridgeFrame(gateway_id, data, len)) { if (!dali_domain_.writeBridgeFrame(gateway_id, data, len)) {
ESP_LOGW(kTag, "failed to write USB raw setup frame channel=%u len=%u", config_.channel_index, ESP_LOGW(kTag, "failed to write USB raw setup frame channel=%u len=%u", config_.channel_index,
static_cast<unsigned>(len)); static_cast<unsigned>(len));
@@ -93,11 +113,12 @@ void GatewayUsbSetupBridge::handleRawFrame(const DaliRawFrame& frame) {
return; return;
} }
const int written = usb_serial_jtag_write_bytes(frame.data.data(), frame.data.size(), const auto payload = LegacyRawPayload(frame.data);
const int written = usb_serial_jtag_write_bytes(payload.data(), payload.size(),
pdMS_TO_TICKS(config_.write_timeout_ms)); pdMS_TO_TICKS(config_.write_timeout_ms));
if (written < 0 || static_cast<size_t>(written) != frame.data.size()) { if (written < 0 || static_cast<size_t>(written) != payload.size()) {
ESP_LOGW(kTag, "failed to forward USB raw setup frame channel=%u len=%u", frame.channel_index, ESP_LOGW(kTag, "failed to forward USB raw setup frame channel=%u len=%u", frame.channel_index,
static_cast<unsigned>(frame.data.size())); static_cast<unsigned>(payload.size()));
} }
} }
+13
View File
@@ -13,6 +13,12 @@ file(GLOB OPENKNX_SRCS
"${OPENKNX_ROOT}/src/knx/*.cpp" "${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 set(TPUART_SRCS
"${TPUART_ROOT}/src/TPUart/DataLinkLayer.cpp" "${TPUART_ROOT}/src/TPUart/DataLinkLayer.cpp"
"${TPUART_ROOT}/src/TPUart/Receiver.cpp" "${TPUART_ROOT}/src/TPUart/Receiver.cpp"
@@ -31,6 +37,7 @@ idf_component_register(
"src/esp_idf_platform.cpp" "src/esp_idf_platform.cpp"
"src/ets_device_runtime.cpp" "src/ets_device_runtime.cpp"
"src/ets_memory_loader.cpp" "src/ets_memory_loader.cpp"
"src/security_storage.cpp"
"src/tpuart_uart_interface.cpp" "src/tpuart_uart_interface.cpp"
${OPENKNX_SRCS} ${OPENKNX_SRCS}
${TPUART_SRCS} ${TPUART_SRCS}
@@ -42,11 +49,13 @@ idf_component_register(
esp_driver_gpio esp_driver_gpio
esp_driver_uart esp_driver_uart
esp_netif esp_netif
esp_system
esp_timer esp_timer
esp_wifi esp_wifi
freertos freertos
log log
lwip lwip
mbedtls
nvs_flash nvs_flash
) )
@@ -58,6 +67,10 @@ target_compile_definitions(${COMPONENT_LIB} PUBLIC
USE_CEMI_SERVER 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 target_compile_options(${COMPONENT_LIB} PRIVATE
-Wno-unused-parameter -Wno-unused-parameter
) )
@@ -14,10 +14,15 @@ namespace gateway::openknx {
class EspIdfPlatform : public Platform { class EspIdfPlatform : public Platform {
public: public:
using OutboundCemiFrameCallback = bool (*)(CemiFrame& frame, void* context);
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr, explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
const char* nvs_namespace = "openknx"); const char* nvs_namespace = "openknx");
~EspIdfPlatform() override; ~EspIdfPlatform() override;
void outboundCemiFrameCallback(OutboundCemiFrameCallback callback, void* context);
bool handleOutboundCemiFrame(CemiFrame& frame) override;
void networkInterface(esp_netif_t* netif); void networkInterface(esp_netif_t* netif);
esp_netif_t* networkInterface() const; esp_netif_t* networkInterface() const;
@@ -54,6 +59,8 @@ class EspIdfPlatform : public Platform {
std::vector<uint8_t> eeprom_; std::vector<uint8_t> eeprom_;
std::string nvs_namespace_; std::string nvs_namespace_;
bool eeprom_loaded_{false}; bool eeprom_loaded_{false};
OutboundCemiFrameCallback outbound_cemi_frame_callback_{nullptr};
void* outbound_cemi_frame_context_{nullptr};
}; };
} // namespace gateway::openknx } // namespace gateway::openknx
@@ -17,26 +17,41 @@ namespace gateway::openknx {
class EtsDeviceRuntime { class EtsDeviceRuntime {
public: public:
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>; 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, using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len, const uint8_t* data, size_t len,
std::vector<uint8_t>* response)>; 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(); ~EtsDeviceRuntime();
uint16_t individualAddress() const; uint16_t individualAddress() const;
uint16_t tunnelClientAddress() const; uint16_t tunnelClientAddress() const;
bool configured() const; bool configured() const;
bool programmingMode() const;
void setProgrammingMode(bool enabled);
void toggleProgrammingMode();
EtsMemorySnapshot snapshot() const; EtsMemorySnapshot snapshot() const;
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler, void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
FunctionPropertyHandler state_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 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(); void loop();
private: private:
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
static void EmitTunnelFrame(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, static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length); 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 property_id, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length); uint8_t* result_data, uint8_t& result_length);
bool shouldConsumeTunnelFrame(CemiFrame& frame) const; bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
bool shouldConsumeBusFrame(CemiFrame& frame) const;
std::string nvs_namespace_; std::string nvs_namespace_;
EspIdfPlatform platform_; EspIdfPlatform platform_;
Bau07B0 device_; Bau07B0 device_;
CemiFrameSender sender_; CemiFrameSender sender_;
GroupWriteHandler group_write_handler_;
FunctionPropertyHandler command_handler_; FunctionPropertyHandler command_handler_;
FunctionPropertyHandler state_handler_; FunctionPropertyHandler state_handler_;
}; };
@@ -3,6 +3,7 @@
#include "openknx_idf/ets_memory_loader.h" #include "openknx_idf/ets_memory_loader.h"
#include "openknx_idf/ets_device_runtime.h" #include "openknx_idf/ets_device_runtime.h"
#include "openknx_idf/esp_idf_platform.h" #include "openknx_idf/esp_idf_platform.h"
#include "openknx_idf/security_storage.h"
#include "openknx_idf/tpuart_uart_interface.h" #include "openknx_idf/tpuart_uart_interface.h"
#include "knx/bau07B0.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
@@ -14,7 +14,8 @@ namespace gateway::openknx {
class TpuartUartInterface : public TPUart::Interface::Abstract { class TpuartUartInterface : public TPUart::Interface::Abstract {
public: public:
TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin, TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
size_t rx_buffer_size = 512, size_t tx_buffer_size = 512); size_t rx_buffer_size = 512, size_t tx_buffer_size = 512,
bool nine_bit_mode = true);
~TpuartUartInterface(); ~TpuartUartInterface();
void begin(int baud) override; void begin(int baud) override;
@@ -34,6 +35,7 @@ class TpuartUartInterface : public TPUart::Interface::Abstract {
int rx_pin_; int rx_pin_;
size_t rx_buffer_size_; size_t rx_buffer_size_;
size_t tx_buffer_size_; size_t tx_buffer_size_;
bool nine_bit_mode_;
std::atomic_bool overflow_{false}; std::atomic_bool overflow_{false};
std::function<bool()> callback_; std::function<bool()> callback_;
}; };
+53 -10
View File
@@ -20,12 +20,32 @@ namespace {
constexpr const char* kTag = "openknx_idf"; constexpr const char* kTag = "openknx_idf";
constexpr const char* kEepromKey = "eeprom"; constexpr const char* kEepromKey = "eeprom";
esp_netif_t* findDefaultNetif() { bool readBaseMac(uint8_t* data) {
if (auto* sta = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF")) { if (data == nullptr) {
return sta; return false;
} }
if (auto* eth = esp_netif_get_handle_from_ifkey("ETH_DEF")) { if (esp_efuse_mac_get_default(data) == ESP_OK) {
return eth; 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; return nullptr;
} }
@@ -51,6 +71,19 @@ EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface,
EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); } 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; } void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; } esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
@@ -90,7 +123,7 @@ void EspIdfPlatform::macAddress(uint8_t* data) {
if (data == nullptr) { if (data == nullptr) {
return; return;
} }
if (esp_read_mac(data, ESP_MAC_WIFI_STA) != ESP_OK) { if (!readBaseMac(data)) {
std::memset(data, 0, 6); std::memset(data, 0, 6);
} }
} }
@@ -98,7 +131,7 @@ void EspIdfPlatform::macAddress(uint8_t* data) {
uint32_t EspIdfPlatform::uniqueSerialNumber() { uint32_t EspIdfPlatform::uniqueSerialNumber() {
uint8_t mac[6]{}; uint8_t mac[6]{};
macAddress(mac); 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]; (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) { void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
closeMultiCast(); closeMultiCast();
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (udp_sock_ < 0) { if (udp_sock_ < 0) {
ESP_LOGE(kTag, "failed to create UDP socket: errno=%d", errno); ESP_LOGE(kTag, "failed to create UDP socket: errno=%d", errno);
return; return;
@@ -124,7 +157,8 @@ void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
sockaddr_in bind_addr{}; sockaddr_in bind_addr{};
bind_addr.sin_family = AF_INET; 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); bind_addr.sin_port = htons(port);
if (bind(udp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) { if (bind(udp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
ESP_LOGE(kTag, "failed to bind UDP socket: errno=%d", errno); 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{}; ip_mreq mreq{};
mreq.imr_multiaddr.s_addr = htonl(addr); 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) { 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); 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; uint8_t loop = 0;
setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)); setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
@@ -1,6 +1,7 @@
#include "openknx_idf/ets_device_runtime.h" #include "openknx_idf/ets_device_runtime.h"
#include "knx/cemi_server.h" #include "knx/cemi_server.h"
#include "knx/secure_application_layer.h"
#include "knx/property.h" #include "knx/property.h"
#include <algorithm> #include <algorithm>
@@ -36,6 +37,13 @@ bool IsUsableIndividualAddress(uint16_t address) {
return address != 0 && address != kInvalidIndividualAddress; 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) { void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
device.deviceObject().manufacturerId(kReg1DaliManufacturerId); device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
device.deviceObject().bauNumber(platform.uniqueSerialNumber()); device.deviceObject().bauNumber(platform.uniqueSerialNumber());
@@ -48,28 +56,45 @@ void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
} // namespace } // namespace
EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_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)), : nvs_namespace_(std::move(nvs_namespace)),
platform_(nullptr, nvs_namespace_.c_str()), platform_(nullptr, nvs_namespace_.c_str()),
device_(platform_) { device_(platform_) {
platform_.outboundCemiFrameCallback(&EtsDeviceRuntime::HandleOutboundCemiFrame, this);
ApplyReg1DaliIdentity(device_, platform_); ApplyReg1DaliIdentity(device_, platform_);
if (IsUsableIndividualAddress(fallback_individual_address)) { if (IsUsableIndividualAddress(fallback_individual_address)) {
device_.deviceObject().individualAddress(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()) && if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
IsUsableIndividualAddress(fallback_individual_address)) { IsUsableIndividualAddress(fallback_individual_address)) {
device_.deviceObject().individualAddress(fallback_individual_address); device_.deviceObject().individualAddress(fallback_individual_address);
} }
if (auto* server = device_.getCemiServer()) { 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->deviceAddressPropertiesTargetClient(false);
server->tunnelFrameCallback(&EtsDeviceRuntime::EmitTunnelFrame, this); server->tunnelFrameCallback(&EtsDeviceRuntime::EmitTunnelFrame, this);
} }
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand); device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState); device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
#ifdef USE_DATASECURE
device_.secureGroupWriteCallback(&EtsDeviceRuntime::HandleSecureGroupWrite, this);
#endif
} }
EtsDeviceRuntime::~EtsDeviceRuntime() { EtsDeviceRuntime::~EtsDeviceRuntime() {
platform_.outboundCemiFrameCallback(nullptr, nullptr);
#ifdef USE_DATASECURE
device_.secureGroupWriteCallback(nullptr, nullptr);
#endif
device_.functionPropertyCallback(nullptr); device_.functionPropertyCallback(nullptr);
device_.functionPropertyStateCallback(nullptr); device_.functionPropertyStateCallback(nullptr);
if (auto* server = device_.getCemiServer()) { if (auto* server = device_.getCemiServer()) {
@@ -90,6 +115,16 @@ uint16_t EtsDeviceRuntime::tunnelClientAddress() const {
bool EtsDeviceRuntime::configured() const { return const_cast<Bau07B0&>(device_).configured(); } 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 EtsDeviceRuntime::snapshot() const {
EtsMemorySnapshot out; EtsMemorySnapshot out;
auto& device = const_cast<Bau07B0&>(device_); auto& device = const_cast<Bau07B0&>(device_);
@@ -126,6 +161,14 @@ void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler comma
state_handler_ = std::move(state_handler); state_handler_ = std::move(state_handler);
} }
void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) {
group_write_handler_ = std::move(handler);
}
void EtsDeviceRuntime::setNetworkInterface(esp_netif_t* netif) {
platform_.networkInterface(netif);
}
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len, bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
CemiFrameSender sender) { CemiFrameSender sender) {
auto* server = device_.getCemiServer(); auto* server = device_.getCemiServer();
@@ -146,8 +189,58 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
return consumed; 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(); } 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) { void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
auto* self = static_cast<EtsDeviceRuntime*>(context); auto* self = static_cast<EtsDeviceRuntime*>(context);
if (self == nullptr || !self->sender_) { if (self == nullptr || !self->sender_) {
@@ -156,6 +249,15 @@ void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
self->sender_(frame.data(), frame.dataLength()); 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, bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t* result_data,
@@ -217,11 +319,30 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
case M_FuncPropStateRead_req: case M_FuncPropStateRead_req:
return true; return true;
case L_data_req: case L_data_req:
return frame.addressType() == IndividualAddress && if (!const_cast<Bau07B0&>(device_).configured() || programmingMode()) {
frame.destinationAddress() == individualAddress(); 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: default:
return false; 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 } // 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,6 +1,7 @@
#include "openknx_idf/tpuart_uart_interface.h" #include "openknx_idf/tpuart_uart_interface.h"
#include "esp_log.h" #include "esp_log.h"
#include "soc/uart_periph.h"
#include <utility> #include <utility>
@@ -9,15 +10,39 @@ namespace {
constexpr const char* kTag = "openknx_tpuart"; constexpr const char* kTag = "openknx_tpuart";
bool ResolveUartIoPin(uart_port_t uart_port, int configured_pin, uint32_t pin_index,
int* resolved_pin) {
if (resolved_pin == nullptr) {
return false;
}
if (configured_pin >= 0) {
*resolved_pin = configured_pin;
return true;
}
if (uart_port < 0 || uart_port >= SOC_UART_NUM || pin_index >= SOC_UART_PINS_COUNT) {
*resolved_pin = UART_PIN_NO_CHANGE;
return false;
}
const int default_pin = uart_periph_signal[uart_port].pins[pin_index].default_gpio;
if (default_pin < 0) {
*resolved_pin = UART_PIN_NO_CHANGE;
return false;
}
*resolved_pin = default_pin;
return true;
}
} // namespace } // namespace
TpuartUartInterface::TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin, TpuartUartInterface::TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
size_t rx_buffer_size, size_t tx_buffer_size) size_t rx_buffer_size, size_t tx_buffer_size,
bool nine_bit_mode)
: uart_port_(uart_port), : uart_port_(uart_port),
tx_pin_(tx_pin), tx_pin_(tx_pin),
rx_pin_(rx_pin), rx_pin_(rx_pin),
rx_buffer_size_(rx_buffer_size), rx_buffer_size_(rx_buffer_size),
tx_buffer_size_(tx_buffer_size) {} tx_buffer_size_(tx_buffer_size),
nine_bit_mode_(nine_bit_mode) {}
TpuartUartInterface::~TpuartUartInterface() { end(); } TpuartUartInterface::~TpuartUartInterface() { end(); }
@@ -29,22 +54,30 @@ void TpuartUartInterface::begin(int baud) {
uart_config_t config{}; uart_config_t config{};
config.baud_rate = baud; config.baud_rate = baud;
config.data_bits = UART_DATA_8_BITS; config.data_bits = UART_DATA_8_BITS;
config.parity = UART_PARITY_EVEN; config.parity = nine_bit_mode_ ? UART_PARITY_EVEN : UART_PARITY_DISABLE;
config.stop_bits = UART_STOP_BITS_1; config.stop_bits = UART_STOP_BITS_1;
config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
config.source_clk = UART_SCLK_DEFAULT; config.source_clk = UART_SCLK_DEFAULT;
int tx_pin = UART_PIN_NO_CHANGE;
int rx_pin = UART_PIN_NO_CHANGE;
if (!ResolveUartIoPin(uart_port_, tx_pin_, SOC_UART_TX_PIN_IDX, &tx_pin) ||
!ResolveUartIoPin(uart_port_, rx_pin_, SOC_UART_RX_PIN_IDX, &rx_pin)) {
ESP_LOGE(kTag, "UART%d has no ESP-IDF default TX/RX pin; configure explicit pins",
uart_port_);
return;
}
esp_err_t err = uart_param_config(uart_port_, &config); esp_err_t err = uart_param_config(uart_port_, &config);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to configure UART%d: %s", uart_port_, esp_err_to_name(err)); ESP_LOGE(kTag, "failed to configure UART%d: %s", uart_port_, esp_err_to_name(err));
return; return;
} }
err = uart_set_pin(uart_port_, tx_pin_ < 0 ? UART_PIN_NO_CHANGE : tx_pin_, err = uart_set_pin(uart_port_, tx_pin, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
rx_pin_ < 0 ? UART_PIN_NO_CHANGE : rx_pin_, UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to route UART%d pins: %s", uart_port_, esp_err_to_name(err)); ESP_LOGE(kTag, "failed to route UART%d pins tx=%d rx=%d: %s", uart_port_, tx_pin,
rx_pin, esp_err_to_name(err));
return; return;
} }
+1 -1
Submodule knx updated: 339d8472e7...346b704cbe