65 Commits

Author SHA1 Message Date
Tony 29920195ba fix: update MaxUserEntries configuration in Gateway-OAM-IP-Router.xml
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-28 19:22:25 +08:00
Tony 67c2410be7 fix: remove unused language configuration for en-US in Gateway-SecureTemplateRouter.xml
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-28 19:07:03 +08:00
Tony 8bda4f727e fix: update language configuration to include translations for multiple languages
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-28 17:47:34 +08:00
Tony a46cef3ee9 fix: correct XML tag from includetemplate to include for language configuration
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-28 17:38:02 +08:00
Tony 4c9f26a638 Add InfoLed configuration XML for KNX application
- Introduced a new XML file `InfoLed.part.xml` to define the Info LED configuration for the KNX application.
- The file includes manufacturer data, application programs, and dynamic parameter blocks for configuring Info LEDs.
- Logic for showing/hiding default checkboxes and function tables based on user selections is implemented.
- Parameter blocks are structured to allow customization of Info LEDs with appropriate UI hints and layout.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-28 17:20:10 +08:00
Tony 803348ea3a Add Gateway OAM IP Router XML and README documentation
- Introduced `Gateway-SecureTemplateRouter.xml` for the OAM-compatible BAU091A KNX/IP Router, incorporating secure application metadata and supporting secure tunneling.
- Created a `README.md` file detailing the purpose, structure, and validation steps for the new XML configuration, including instructions for running XML checks and using OpenKNXproducer.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-28 16:01:38 +08:00
Tony 8211514fe3 feat: Enhance OAM router functionality and improve KNX device handling
- Added support for OAM router configuration in app_main, allowing the IP interface individual address to be set based on the OAM router's individual address.
- Updated GatewayBridgeService to validate IP interface addresses only when OAM router is disabled, ensuring proper address management.
- Introduced KnxResponseDeduplicator to prevent duplicate responses in KNX communication.
- Enhanced ETS device runtime to handle bus frames and set up frame receivers for OAM router.
- Improved GatewayKnxTpIpRouter to manage OAM router interactions, including handling tunnel frames and bus frames.
- Updated CMakeLists to include new knx_device_broker source file.
- Refined logging messages to provide clearer context regarding the IP interface being used.
- Added methods to retrieve IP interface names and friendly names based on the OAM router configuration.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-28 15:44:17 +08:00
Tony 078c37a20f feat(gateway): enhance DALI and KNX handling with broadcast management support and configurable task stack size
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-27 21:06:58 +08:00
Tony 6d0b36b60a Refactor channel binding configuration and validation
- Introduced `NativeChannelConfig` and `SerialChannelConfig` functions to streamline the creation of `ChannelBindingConfig` instances.
- Replaced manual channel configuration with macros `GATEWAY_CONFIGURE_NATIVE_CHANNEL` and `GATEWAY_CONFIGURE_SERIAL_CHANNEL` for better maintainability.
- Updated `BuildChannelBindings` to utilize the new configuration functions, reducing redundancy.
- Enhanced `ValidateChannelBindings` to check for required TX and RX GPIO pins for native channels.
- Simplified `BindConfiguredChannels` to iterate over the built channel bindings, improving readability and reducing code duplication.
- Adjusted the handling of reserved UART ports in `app_main` to dynamically include all serial channels.
- Updated SDK configuration for channel 1 gateway ID and modified KNX-related configurations for enhanced functionality.
- Removed obsolete NVS flash initialization code and replaced it with a more streamlined approach for managing runtime data.
- Added a new command in `GatewayController` to publish gateway IDs when requested.
- Defined a constant for maximum KNX instance count to improve clarity in the codebase.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-27 18:08:53 +08:00
Tony 7a820e700c feat(gateway): add cloud integration for KNX and DALI with configurable transport options
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-27 15:20:29 +08:00
Tony 5622e6ba81 fix(gateway): update clangd configuration and sdkconfig for improved build settings
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-26 23:18:13 +08:00
Tony 865bf8425a feat(gateway): enhance DALI host activity tracking and presence management
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-26 22:21:36 +08:00
Tony f922993d2f feat(gateway): enhance DALI and KNX settings management with instance support
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-26 12:57:13 +08:00
Tony 2b779d5532 Add secure transport and OAM router runtime implementations
- Implement secure transport mechanisms in `gateway_knx_secure_transport.cpp` for handling secure sessions, including AES encryption, session key generation, and secure packet wrapping and unwrapping.
- Introduce `OamRouterRuntime` in `oam_router_runtime.cpp` to manage OAM router identity, individual addresses, and tunnel frame handling.
- Enhance secure session management with functions for session allocation, authentication, and secure packet processing.
- Ensure compatibility with existing KNXnet/IP protocols while adding support for secure communications.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-25 08:18:01 +08:00
Tony 0467179f70 fix(gateway): update DALI configuration parameters for improved timing and cache reconciliation
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-22 03:05:02 +08:00
Tony 20642e5ec3 fix(gateway): update DALI configuration parameters for improved performance and reliability
This commit add comat parameter for some old DALI-1 control gear.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-21 17:03:35 +08:00
Tony 0827befb06 feat(gateway_cache): enhance DALI state management and caching
- Increased flush interval to 10 seconds and added a refresh interval of 120 seconds in GatewayCacheConfig.
- Introduced a new boolean `stale` in GatewayCacheDaliRuntimeStatus to track stale states.
- Added methods for setting actual DALI levels and persisting DALI address states.
- Implemented functions to build and apply DALI state payloads, including handling scene levels.
- Enhanced the GatewayCache class to manage DALI states more effectively, including loading and persisting states.
- Updated GatewayController to support cache refresh operations, including handling cache commands and reporting cache status.
- Added mechanisms for periodic cache refresh based on idle time and configured intervals.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-21 15:34:26 +08:00
Tony 2b8ef31263 Add GatewayKnxTpIpRouter implementation for handling KNXnet/IP services
- Implemented handleUdpDatagram to process incoming UDP datagrams and route them to appropriate handlers based on service type.
- Added methods for handling various KNXnet/IP requests including search, description, tunneling, device configuration, connection state, and disconnect requests.
- Introduced TunnelClient management for handling multiple tunnel connections, including allocation, resetting, and pruning stale clients.
- Implemented secure service handling with appropriate logging for unsupported secure sessions.
- Enhanced logging for better traceability of incoming requests and responses.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-21 14:12:46 +08:00
Tony 8e80fd05b4 fix(gateway): enhance DALI logging with query packet logging and configuration
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-21 13:39:31 +08:00
Tony 7ab1855295 fix(gateway): enhance DALI logging and error handling in gateway_knx
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-21 13:22:38 +08:00
Tony e8bf8c56cd fix(gateway): update subproject commit reference for knx module
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-21 11:59:24 +08:00
Tony 416320604b fix(gateway): update subproject commit reference for knx module
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-20 06:33:10 +08:00
Tony e489a2de36 fix(gateway): update subproject commit reference for knx module
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-20 06:18:30 +08:00
Tony b73e0c6138 fix(gateway): update subproject commit reference for knx module
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-20 05:54:48 +08:00
Tony 3165afe8a4 fix(gateway): update subproject commit reference for knx module
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-20 04:57:25 +08:00
Tony 57950e7b0b feat(gateway): implement commissioning scan functionality with options for new, randomize, delete, and assign
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-19 10:47:44 +08:00
Tony a3f03719f9 feat(gateway): add extended function property handlers and diagnostics support for OpenKNX
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-19 10:02:57 +08:00
Tony e091b4301e fix(gateway): enable KNX security device endpoints and enhance tunnel frame handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-19 08:16:28 +08:00
Tony 226855362b feat(gateway): integrate bridge service into gateway controller for transport handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-19 05:52:36 +08:00
Tony 3bc5355041 feat(gateway): enable full IP forwarding for KNX TP and enhance group value write handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-19 04:18:53 +08:00
Tony 3af2995b40 feat(gateway): add support for full IP forwarding of KNX TP telegrams and enhance tunnel frame handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-19 03:14:03 +08:00
Tony b447da5bfc feat(gateway): enhance handling of local routing and tunnel frames in KNX integration
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-19 00:09:17 +08:00
Tony f2b7dee8bd fix(gateway): improve TP-UART configuration logging and handling for ETS device states
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-16 10:25:20 +08:00
Tony 77fe8c1b02 feat(gateway): enhance DALI and KNX integration with tunnel confirmation handling and improved message routing
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-16 09:34:14 +08:00
Tony 82142dd46c feat(gateway): add support for DALI scene handling and relative brightness adjustments
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-16 04:38:15 +08:00
Tony 323ff24c04 feat(gateway): add KNX TP UART startup timeout configuration and enhance initialization logic
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-16 03:40:45 +08:00
Tony 277379abd7 feat(gateway): add DALI bus ID configuration and enhance group object write handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-16 03:31:12 +08:00
Tony e79223c87e Refactor KNX DALI Gateway Configuration and Update Device Parameters
- Introduced configuration macros for OEM manufacturer ID, application number, and application version in knxprod.h.
- Updated product identity definitions to use the new configuration macros.
- Modified device type enumeration to include additional device types.
- Adjusted color space enumeration values for consistency.
- Defined generated group object layout constants for memory offsets and block sizes.
- Enhanced knx_dali_gw.cpp to utilize the new configuration macros for manufacturer ID and program version.
- Updated the device initialization logic to reflect the new hardware and program version structures.
- Removed obsolete knx_dali_gw subproject and updated related submodules.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-16 01:51:01 +08:00
Tony 2a3808c1e4 feat(gateway): enhance OpenKNX integration with new DIB construction methods and improve BLE configuration
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-16 01:50:26 +08:00
Tony 449a3a801a Add KNX DALI Gateway Module and Message Queue Implementation
- Introduced KnxDaliModule class for handling DALI message queuing, commissioning, and KNX group-object dispatch.
- Implemented Message and MessageQueue classes for managing message operations.
- Removed obsolete OpenKNX IDF component files and CMake configurations.
- Updated submodule reference for KNX.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-15 12:34:13 +08:00
Tony 3f15cd7f3f fix(gateway): update DALI query response timeout and activity wait parameters
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-15 10:33:22 +08:00
Tony f005d2bc09 feat(gateway): update BLE connection parameters and enhance DALI timeout configurations
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-15 08:42:20 +08:00
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
Tony 626f86ec4e feat: add support for W5500 SPI Ethernet in gateway
- Introduced configuration options for wired Ethernet support in Kconfig and sdkconfig.
- Implemented Ethernet initialization and event handling in GatewayNetworkService.
- Enhanced app_main to manage Ethernet alongside Wi-Fi.
- Updated GatewayRuntime to store Ethernet information.
- Modified CMakeLists and include files to accommodate new Ethernet dependencies.
- Ensured backward compatibility by allowing Ethernet initialization failures to be ignored.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-12 08:42:10 +08:00
Tony 36d10702da Add EtsDeviceRuntime class for handling KNX device runtime operations
- Introduced EtsDeviceRuntime class to manage device runtime functionalities including handling tunnel frames and function property commands.
- Added support for individual address management and memory snapshot retrieval.
- Updated EtsMemorySnapshot structure to include individual address.
- Implemented identity application for DALI devices in the memory loader.
- Enhanced CMakeLists.txt to include new source files and compile definitions.
- Updated header files to include new dependencies and declarations.
- Refactored existing memory loading logic to accommodate new device runtime features.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-12 05:19:14 +08:00
Tony d231460612 fix(git): update .gitignore to include .DS_Store and change knx submodule branch to tonycloud-dev
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-11 12:16:48 +08:00
Tony 70367f53ca Add OpenKNX IDF component with TPUart integration
- Created CMakeLists.txt for the OpenKNX IDF component, ensuring dependencies on OpenKNX and TPUart submodules.
- Implemented Arduino compatibility header for basic functions like millis, delay, pinMode, and digitalRead.
- Developed EspIdfPlatform class for network interface management and multicast communication.
- Added EtsMemoryLoader for loading ETS memory snapshots and managing associations.
- Introduced TpuartUartInterface for UART communication with methods for reading, writing, and managing callbacks.
- Implemented arduino_compat.cpp for Arduino-like functionality on ESP-IDF.
- Created source files for platform and memory loader implementations.
- Updated submodules for knx, knx_dali_gw, and tpuart.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-11 07:05:40 +08:00
Tony 1b8753636f feat(gateway): add KNX submodule with branch configuration
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-11 04:42:01 +08:00
Tony e94945fc0f feat(gateway): enhance UART configuration validation for Modbus and KNX
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-11 03:44:14 +08:00
Tony bf23cf0b79 feat(gateway_knx): add TP-UART control and initialization functionality
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-11 02:40:31 +08:00
Tony 1a8ee06ec1 Implement KNX Gateway functionality with support for DALI integration
- Added gateway_knx.cpp to handle KNX communication and DALI bridge requests.
- Implemented functions for encoding/decoding KNX telegrams and managing group writes.
- Introduced GatewayKnxBridge and GatewayKnxTpIpRouter classes for managing KNX to DALI routing and IP tunneling.
- Added configuration handling for KNX settings, including UART and multicast options.
- Implemented error handling and logging for various KNX operations.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-08 18:19:37 +08:00
Tony 029785ff1d feat(gateway): Update SDK configuration and add 485 control bridge
- Changed flash size configuration from 16MB to 4MB and updated partition table filename.
- Introduced two gateway channels with UART configurations for communication.
- Added support for gateway cache and startup services including BLE and Wi-Fi.
- Enabled SPI RAM and configured its parameters for better memory management.
- Enhanced the gateway bridge service to handle generated Modbus points more efficiently.
- Refactored the gateway Modbus component to improve point management and added new methods for point description and generation.
- Implemented a new Gateway485ControlBridge for handling 485 control communication with UART.
- Added necessary files for the 485 control bridge including configuration and implementation.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-06 00:39:58 +08:00
Tony 34d2d9caa0 Add serial configuration support to Gateway Modbus
- Introduced GatewayModbusSerialConfig structure to encapsulate serial communication settings.
- Added clamping functions for integer and size values to ensure valid configuration ranges.
- Updated GatewayModbusConfigFromValue to parse serial configuration from JSON input.
- Implemented transport type checking functions for TCP, RTU, ASCII, and Serial.
- Enhanced GatewayModbusConfigToValue to include serial configuration in output.

Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-04 14:34:05 +08:00
Tony 640e78f688 feat(gateway_bridge): enhance DaliBridgeRequest parsing with support for unknown keys
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-04 09:46:24 +08:00
Tony ee1246c942 feat(gateway_bacnet): add bacnet_stack submodule and update CMakeLists.txt path
Signed-off-by: Tony <tonylu@tony-cloud.com>
2026-05-04 03:23:34 +08:00
Tony 7424b43bdd Add diagnostic bit support to Gateway Modbus
- Introduced new enum value `kShortDiagnosticBit` to `GatewayModbusGeneratedKind`.
- Enhanced `GatewayModbusPoint` and `GatewayModbusPointBinding` structures to include diagnostic snapshot, boolean key, and device type.
- Added new diagnostic bit specifications and updated the corresponding arrays for generated discrete inputs and holding registers.
- Implemented `addGeneratedDiagnosticPoint` function to handle the creation of diagnostic points.
- Updated `rebuildMap` method to include generated diagnostic points during the map rebuilding process.

Co-authored-by: Copilot <copilot@github.com>
2026-05-04 02:26:09 +08:00
322 changed files with 29175 additions and 801 deletions
+1
View File
@@ -1,3 +1,4 @@
**/build/
build/
**/managed_components/
.DS_Store
+11
View File
@@ -0,0 +1,11 @@
[submodule "bacnet_stack"]
path = bacnet_stack
url = https://git.tonycloud.org/dali/bacnet_stack.git
[submodule "knx"]
path = knx
url = https://git.tonycloud.org/knx/knx.git
branch = v1
[submodule "tpuart"]
path = tpuart
url = https://git.tonycloud.org/knx/tpuart.git
branch = main
+361
View File
@@ -0,0 +1,361 @@
# Gateway Modbus
The native gateway exposes each DALI channel as a Modbus server. Supported transports are:
- `tcp-server`: Modbus TCP server, default TCP port `1502`.
- `rtu-server`: Modbus RTU slave/server on an ESP-IDF UART.
- `ascii-server`: Modbus ASCII slave/server on an ESP-IDF UART.
The generated Modbus map is identical for TCP, RTU, and ASCII. Only the frame transport changes.
## Configuration
Compile-time defaults live in `apps/gateway/main/Kconfig.projbuild`:
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP`
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU`
- `GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII`
- `GATEWAY_MODBUS_TCP_PORT`
- `GATEWAY_MODBUS_UNIT_ID`
- `GATEWAY_MODBUS_SERIAL_UART_PORT`
- `GATEWAY_MODBUS_SERIAL_TX_PIN`
- `GATEWAY_MODBUS_SERIAL_RX_PIN`
- `GATEWAY_MODBUS_SERIAL_BAUDRATE`
- `GATEWAY_MODBUS_SERIAL_RESPONSE_TIMEOUT_MS`
- `GATEWAY_MODBUS_SERIAL_RS485_ENABLED`
- `GATEWAY_MODBUS_SERIAL_RS485_DE_PIN`
- `GATEWAY_MODBUS_ALLOW_UART0`
UART0 is reserved for the ESP-IDF console by default. Select `GATEWAY_MODBUS_ALLOW_UART0` only when the console has been moved away from UART0 or the deployment intentionally repurposes it. The app validates the Kconfig default UART against DALI serial PHY assignments at boot.
Runtime bridge config keeps the existing top-level `modbus` object. The serial fields are nested under `serial`:
```json
{
"modbus": {
"transport": "rtu-server",
"unitID": 1,
"port": 1502,
"serial": {
"uartPort": 1,
"txPin": 17,
"rxPin": 18,
"baudrate": 9600,
"dataBits": 8,
"parity": "none",
"stopBits": 1,
"rxBufferBytes": 512,
"txBufferBytes": 512,
"responseTimeoutMs": 20,
"interFrameGapUs": 4000,
"rs485": {
"enabled": true,
"dePin": 16
}
}
}
}
```
`unitID` accepts aliases `unitId` and `unit_id`. A configured `unitID` of `0` keeps the gateway compatibility behavior where incoming unit ids are accepted as a wildcard. Incoming serial requests addressed to unit id `0` are treated as Modbus broadcast and do not generate a response.
## UART Management Commands
When a Modbus serial transport is active, the UART also accepts management lines before Modbus frame parsing. A management line starts with `@DALIGW `, contains JSON, and ends with newline.
Examples:
```text
@DALIGW {"action":"modbus_status","gw":3}
@DALIGW {"action":"modbus_stop","gw":3}
@DALIGW {"action":"modbus_config","gw":3,"modbus":{"transport":"ascii-server","unitID":1,"serial":{"uartPort":1,"txPin":17,"rxPin":18,"baudrate":9600,"rs485":{"enabled":true,"dePin":16}}}}
```
Responses are newline-terminated `@DALIGW` JSON lines. A successful `modbus_config` command is saved to NVS and restarts the Modbus transport after the response is written.
HTTP still supports the bridge routes:
- `GET /bridge?action=status&gw=N`
- `GET /bridge?action=config&gw=N`
- `GET /bridge?action=modbus&gw=N`
- `POST /bridge?action=config&gw=N`
- `POST /bridge?action=modbus_start&gw=N`
- `POST /bridge?action=modbus_stop&gw=N`
## Function Codes
Supported function codes are:
| Code | Name | Map space |
| --- | --- | --- |
| `0x01` | Read coils | Coils |
| `0x02` | Read discrete inputs | Discrete inputs |
| `0x03` | Read holding registers | Holding registers |
| `0x04` | Read input registers | Input registers |
| `0x05` | Write single coil | Coils |
| `0x06` | Write single holding register | Holding registers |
| `0x0F` | Write multiple coils | Coils |
| `0x10` | Write multiple holding registers | Holding registers |
Limits are `2000` read bits, `125` read registers, `1968` write bits, `123` write registers, and `252` PDU bytes.
Exception codes used by the gateway:
| Code | Meaning |
| --- | --- |
| `0x01` | Unsupported function |
| `0x02` | Unmapped address or read failure |
| `0x03` | Invalid quantity, value, or request shape |
| `0x04` | DALI execution failure |
| `0x0B` | Unit id mismatch |
## Address Calculation
Modbus protocol frames use zero-based wire offsets. Most tools show human addresses with traditional bases:
| Space | Human base | Function codes |
| --- | ---: | --- |
| Coil | `1` | `0x01`, `0x05`, `0x0F` |
| Discrete input | `10001` | `0x02` |
| Input register | `30001` | `0x04` |
| Holding register | `40001` | `0x03`, `0x06`, `0x10` |
For the main generated slice, every DALI short address `0-63` gets a stride of `32` points in each space:
```text
human = base + shortAddress * 32 + offset
wire = human - base
```
Example: short address `5` brightness is holding-register offset `0`:
```text
human = 40001 + 5 * 32 + 0 = 40161
wire = 40161 - 40001 = 160
```
The diagnostic discrete-input extension starts after the first `64 * 32` discrete inputs:
```text
diagnosticBase = 10001 + 64 * 32 = 12049
human = 12049 + shortAddress * 128 + diagnosticOffset
wire = human - 10001
```
Example: short address `5`, diagnostic offset `105` (`DT8 xy out of range`):
```text
human = 12049 + 5 * 128 + 105 = 12794
wire = 12794 - 10001 = 2793
```
## Main Generated Map
Generated points exist for all short addresses `0-63`, even when no device has been discovered. Unknown numeric values read as `0xFFFF`; unknown booleans read as false unless inventory or cache state proves otherwise. Generated reads prefer gateway cache state and do not poll the DALI bus for every Modbus read.
### Coils
| Offset | Address formula | Access | Function |
| ---: | --- | --- | --- |
| `0` | `1 + short * 32 + 0` | Write | On / recall max |
| `1` | `1 + short * 32 + 1` | Write | Off |
| `2` | `1 + short * 32 + 2` | Write | Recall max |
| `3` | `1 + short * 32 + 3` | Write | Recall min |
### Discrete Inputs
| Offset | Address formula | Function |
| ---: | --- | --- |
| `0` | `10001 + short * 32 + 0` | Discovered |
| `1` | `10001 + short * 32 + 1` | Online |
| `2` | `10001 + short * 32 + 2` | Supports DT1 |
| `3` | `10001 + short * 32 + 3` | Supports DT4 |
| `4` | `10001 + short * 32 + 4` | Supports DT5 |
| `5` | `10001 + short * 32 + 5` | Supports DT6 |
| `6` | `10001 + short * 32 + 6` | Supports DT8 |
| `7` | `10001 + short * 32 + 7` | Group mask known |
| `8` | `10001 + short * 32 + 8` | Actual level known |
| `9` | `10001 + short * 32 + 9` | Scene known |
| `10` | `10001 + short * 32 + 10` | Settings known |
| `16` | `10001 + short * 32 + 16` | Control gear present |
| `17` | `10001 + short * 32 + 17` | Lamp failure |
| `18` | `10001 + short * 32 + 18` | Lamp power on |
| `19` | `10001 + short * 32 + 19` | Limit error |
| `20` | `10001 + short * 32 + 20` | Fading completed |
| `21` | `10001 + short * 32 + 21` | Reset state |
| `22` | `10001 + short * 32 + 22` | Missing short address |
| `23` | `10001 + short * 32 + 23` | Power supply fault |
### Holding Registers
| Offset | Address formula | Access | Function |
| ---: | --- | --- | --- |
| `0` | `40001 + short * 32 + 0` | Read/write | Brightness |
| `1` | `40001 + short * 32 + 1` | Write, cache-read if known | Color temperature |
| `2` | `40001 + short * 32 + 2` | Read/write | Group mask |
| `3` | `40001 + short * 32 + 3` | Read/write | Power-on level |
| `4` | `40001 + short * 32 + 4` | Read/write | System-failure level |
| `5` | `40001 + short * 32 + 5` | Read/write | Minimum level |
| `6` | `40001 + short * 32 + 6` | Read/write | Maximum level |
| `7` | `40001 + short * 32 + 7` | Read/write | Fade time |
| `8` | `40001 + short * 32 + 8` | Read/write | Fade rate |
### Input Registers
| Offset | Address formula | Function |
| ---: | --- | --- |
| `0` | `30001 + short * 32 + 0` | Inventory state: `0` never seen, `1` offline, `2` online |
| `1` | `30001 + short * 32 + 1` | Primary DALI device type |
| `2` | `30001 + short * 32 + 2` | Device type mask |
| `3` | `30001 + short * 32 + 3` | Actual level |
| `4` | `30001 + short * 32 + 4` | Scene id |
| `5` | `30001 + short * 32 + 5` | Raw status |
| `6` | `30001 + short * 32 + 6` | Group mask |
| `7` | `30001 + short * 32 + 7` | Power-on level |
| `8` | `30001 + short * 32 + 8` | System-failure level |
| `9` | `30001 + short * 32 + 9` | Minimum level |
| `10` | `30001 + short * 32 + 10` | Maximum level |
| `11` | `30001 + short * 32 + 11` | Fade time |
| `12` | `30001 + short * 32 + 12` | Fade rate |
## Diagnostic Discrete Inputs
Diagnostic addresses use `12049 + short * 128 + offset`.
| Offset | Function |
| ---: | --- |
| `0` | DT1 circuit failure |
| `1` | DT1 battery duration failure |
| `2` | DT1 battery failure |
| `3` | DT1 emergency lamp failure |
| `4` | DT1 function test max delay exceeded |
| `5` | DT1 duration test max delay exceeded |
| `6` | DT1 function test failed |
| `7` | DT1 duration test failed |
| `8` | DT1 inhibit mode |
| `9` | DT1 function test result valid |
| `10` | DT1 duration test result valid |
| `11` | DT1 battery fully charged |
| `12` | DT1 function test request pending |
| `13` | DT1 duration test request pending |
| `14` | DT1 identification active |
| `15` | DT1 physically selected |
| `16` | DT1 rest mode active |
| `17` | DT1 normal mode active |
| `18` | DT1 emergency mode active |
| `19` | DT1 extended emergency mode active |
| `20` | DT1 function test in progress |
| `21` | DT1 duration test in progress |
| `22` | DT1 hardwired inhibit active |
| `23` | DT1 hardwired switch on |
| `24` | DT1 integral emergency gear |
| `25` | DT1 maintained gear |
| `26` | DT1 switched maintained gear |
| `27` | DT1 auto test capability |
| `28` | DT1 adjustable emergency level |
| `29` | DT1 hardwired inhibit supported |
| `30` | DT1 physical selection supported |
| `31` | DT1 relight in rest mode supported |
| `32` | DT4 leading edge running |
| `33` | DT4 trailing edge running |
| `34` | DT4 reference measurement running |
| `35` | DT4 non-log curve active |
| `36` | DT4 can query load over-current shutdown |
| `37` | DT4 can query open circuit |
| `38` | DT4 can query load decrease |
| `39` | DT4 can query load increase |
| `40` | DT4 can query thermal shutdown |
| `41` | DT4 can query thermal overload |
| `42` | DT4 physical selection supported |
| `43` | DT4 can query temperature |
| `44` | DT4 can query supply voltage |
| `45` | DT4 can query supply frequency |
| `46` | DT4 can query load voltage |
| `47` | DT4 can query load current |
| `48` | DT4 can query real load power |
| `49` | DT4 can query load rating |
| `50` | DT4 can query current overload |
| `51` | DT4 can select non-log curve |
| `52` | DT4 can query unsuitable load |
| `53` | DT4 load over-current shutdown |
| `54` | DT4 open circuit detected |
| `55` | DT4 load decrease detected |
| `56` | DT4 load increase detected |
| `57` | DT4 thermal shutdown |
| `58` | DT4 thermal overload reduction |
| `59` | DT4 reference failed |
| `60` | DT4 unsuitable load |
| `61` | DT4 supply voltage out of limits |
| `62` | DT4 supply frequency out of limits |
| `63` | DT4 load voltage out of limits |
| `64` | DT4 load current overload |
| `65` | DT5 output range selectable |
| `66` | DT5 pull-up selectable |
| `67` | DT5 fault detection selectable |
| `68` | DT5 mains relay |
| `69` | DT5 output level queryable |
| `70` | DT5 non-log curve supported |
| `71` | DT5 output-loss selection supported |
| `72` | DT5 selection switch supported |
| `73` | DT5 output fault detected |
| `74` | DT5 0-10V operation |
| `75` | DT5 pull-up on |
| `76` | DT5 non-log curve active |
| `77` | DT6 power supply integrated |
| `78` | DT6 LED module integrated |
| `79` | DT6 AC supply possible |
| `80` | DT6 DC supply possible |
| `81` | DT6 PWM possible |
| `82` | DT6 AM possible |
| `83` | DT6 current control possible |
| `84` | DT6 high current possible |
| `85` | DT6 can query short circuit |
| `86` | DT6 can query open circuit |
| `87` | DT6 can query load decrease |
| `88` | DT6 can query load increase |
| `89` | DT6 can query current protector |
| `90` | DT6 can query thermal shutdown |
| `91` | DT6 can query thermal overload |
| `92` | DT6 short circuit |
| `93` | DT6 open circuit |
| `94` | DT6 load decrease |
| `95` | DT6 load increase |
| `96` | DT6 current protector active |
| `97` | DT6 thermal shutdown |
| `98` | DT6 thermal overload |
| `99` | DT6 reference failed |
| `100` | DT6 PWM active |
| `101` | DT6 AM active |
| `102` | DT6 current controlled output |
| `103` | DT6 high current active |
| `104` | DT6 non-log curve active |
| `105` | DT8 xy out of range |
| `106` | DT8 color-temperature out of range |
| `107` | DT8 auto calibration active |
| `108` | DT8 auto calibration success |
| `109` | DT8 xy active |
| `110` | DT8 color-temperature active |
| `111` | DT8 primary-N active |
| `112` | DT8 RGBWAF active |
| `113` | DT8 xy capable |
| `114` | DT8 color-temperature capable |
| `115` | DT8 primary-N capable |
| `116` | DT8 RGBWAF capable |
| `117` | DT6 physical selection supported |
| `118` | DT6 current protector enabled |
| `119` | DT1 control gear failure |
## Provisioned Overrides
Provisioned bridge models are applied after the generated map. If a Modbus model uses the same space and human address as a generated point, the model replaces that generated point.
For Modbus models:
- `external.objectType` must be `coil`, `discrete_input`, `input_register`, or `holding_register`.
- `external.registerAddress` is the human address, not the zero-based wire offset.
- `external.bitIndex` exposes one bit from a numeric read result for boolean objects.
- DALI targets use short addresses `0-63`, groups as `64 + group`, and broadcast as `127`.
- DALI query/read operations must target short addresses only.
- `valueTransform` scaling, offset, rounding, and clamps are applied by the bridge engine.
Use `GET /bridge?action=modbus&gw=N` to inspect the effective generated and provisioned bindings served by a running gateway.
+85 -7
View File
@@ -4,6 +4,8 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
## Layout
This is the list of top-level directories and their purposes,
update as the project evolves:
- `apps/`: standard ESP-IDF applications for each firmware role.
- `apps/gateway/main/Kconfig.projbuild`: project-visible gateway-role settings such as per-channel native/serial PHY selection, gateway ids, pin mapping, and startup transport policy.
- `components/`: reusable components shared by all gateway applications.
@@ -12,21 +14,77 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
- `gateway_modbus/`: gateway-owned Modbus TCP config, generated DALI point tables, and provisioned Modbus model override dispatch.
- `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack.
- `openknx_idf/`: ESP-IDF port layer for the OpenKNX `gateway/knx` and `gateway/tpuart` submodules, including NVS-backed OpenKNX memory, development KNX security storage, ETS cEMI programming support, UDP multicast/unicast plumbing, and a native TP-UART interface without the Arduino framework.
- `gateway_modbus/`: gateway-owned Modbus TCP/RTU/ASCII config, generated DALI point tables, and provisioned Modbus model override dispatch.
- `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack, including the gateway-owned BACnet bridge model adapter.
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
- `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, and BOOT-button Wi-Fi reset for the native gateway.
- `gateway_network/`: HTTP `/info`, `/dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP port `2020` command/notify routing, Wi-Fi STA lifecycle, W5500 SPI Ethernet startup/teardown, ESP-Touch smartconfig, setup AP mode, ESP-NOW setup ingress, setup AP GPIO handling, and optional Wi-Fi reset GPIO handling for the native gateway.
- `gateway_runtime/`: persistent runtime state, command queueing, and device info services.
- `gateway_485_control/`: optional 485 Lua control bridge for framed `0x28 0x01` commands and `0x22 ... checksum` notifications at `9600 8N1`; disabled by default because UART0 must be moved off the ESP-IDF console first.
- `gateway_usb_setup/`: optional USB Serial/JTAG setup bridge; disabled by default so USB remains available for debug at boot.
- `knx`: The forked OpenKNX cEMI programming support, ESP-IDF port, and KNX security storage used by the gateway. You can edit this code when necessary to support missing ETS programming features or to implement the secure-session transport path.
- `knx_dali_gw`: The forked OpenKNX DALI-GW function-property support used by the gateway. You can edit this code when necessary to support missing DALI-GW features or to fix bugs.
## Gateway DALI cache
The gateway owns the shared DALI cache for multi-user deployments. App, BLE,
IP, Modbus, BACnet, KNX, and local control paths should treat the gateway cache
as the shared read surface instead of maintaining separate app-side device-state
caches. Transparent/setup raw forwarding paths remain bypass-oriented, but raw
DALI bus observation still feeds the passive decoder when frames are visible to
the gateway.
`gateway_cache` stores internal scene/group data and per-short-address DALI
state. Device settings, group masks, scene levels, known flags, and the last
runtime status are batched to NVS using `GATEWAY_CACHE_FLUSH_INTERVAL_MS`, which
defaults to 10000 ms. Runtime status loaded from disk is marked stale until the
gateway observes a bus command or the background refresher verifies it again.
`GATEWAY_CACHE_REFRESH_INTERVAL_MS` defaults to 120000 ms. When nonzero, the
controller maintenance loop refreshes direct short-address actual levels one
small step at a time. The refresh is low priority: it yields to queued gateway
commands, address allocation, live management reads, bridge traffic, and any raw
DALI bus activity from another master until the bus has been idle. Group and
broadcast targets are never queried for refresh.
Gateway feature opcode `0x06` advertises cache support with bit `0x40`. Gateway
opcode `0x39` returns cache summary and target snapshots so frontend clients can
read cached state without issuing live DALI queries on supported gateways.
## Current status
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, and a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing. Startup behavior is configured in `main/Kconfig.projbuild`: BLE is enabled by default, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, and the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, W5500 SPI Ethernet with DHCP, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, setup AP GPIO entry, and optional Wi-Fi credential reset GPIO handling, and an optional `gateway_485_control` bridge that claims UART0 for Lua-compatible framed command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE and wired Ethernet are enabled by default, W5500 initialization and startup probe failures are ignored by default for boards without populated Ethernet hardware by fully disabling Ethernet for that boot, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
## Modbus TCP
## KNX Security
Modbus TCP is owned by `gateway/components/gateway_modbus` and started through the per-channel bridge service. The gateway keeps the existing bridge config JSON shape with a top-level `modbus` object containing `transport`, `host`, `port`, and `unitID`, but parsing and runtime behavior now live in the gateway project rather than in `dali_cpp`.
KNX Data Secure and KNXnet/IP Secure support are controlled by `GATEWAY_KNX_DATA_SECURE_SUPPORTED` and `GATEWAY_KNX_IP_SECURE_SUPPORTED`. KNXnet/IP Secure now recognizes the secure service family, performs secure session setup/authentication with provisioned tunnel user keys, wraps secure tunnel responses, handles secure group-sync frames, and wraps/unpacks secure multicast routing frames when an OAM backbone key is active. 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 shared KNXnet/IP endpoint can also be provisioned with an OAM-compatible IP-Router persona by enabling `GATEWAY_KNX_OAM_ROUTER_SUPPORTED` and the nested `knx.oamRouter` config. This second logical application is part of the same KNX/IP router endpoint: it does not open a second UDP/TCP listener and it does not own a second TP-UART driver. When OAM is enabled, KNXnet/IP search, description, tunnel, and multicast identity are advertised as the OAM IP router and follow the OAM individual address instead of a DALI-channel namespace. The gateway hosts a BAU091A/OAM router runtime beside the REG1-Dali BAU07B0 runtime, with separate individual/tunnel addresses, separate programming button/LED GPIOs, separate ETS/security storage, and a KNX serial derived from the ESP base MAC plus one. Secure tunnels are assigned to the OAM persona, and non-secure IP management/tunnel connections opened while OAM programming mode is active are also assigned to the OAM persona so ETS can modify and verify the OAM individual address. OAM-addressed management frames are dispatched to the BAU091A runtime while DALI group/function-property traffic stays on the REG1-Dali application. Physical TP ingress is brokered at the shared TP-UART owner: OAM-addressed TP telegrams and broadcast management telegrams are delivered to the matching local logical runtime, and OAM responses are transmitted through the same TP-UART and mirrored to KNXnet/IP once. Normal REG1-Dali TP group/application traffic remains on the existing REG1 data-link path so ETS group-object dispatch and DALI routing do not duplicate. The default OAM identity follows the OAM-IP-Router release database (`0x00FA` manufacturer, `0xA11F` application number, version `0x07`) unless overridden in Kconfig.
OAM IP Secure keyring preparation uses the `knx_oam_sec` NVS namespace. Development HTTP actions can read/generate/reset/export the OAM factory setup key and store already-extracted IP Secure keyring material (`backboneKeyHex`, tunnel user keys, and an optional device-authentication key). Stored OAM credentials are reported in `knx.security.oamRouter.ipSecureCredentials`; the tunnel user keys authenticate secure sessions and the backbone key protects secure routing/group-sync traffic. The routing sequence counter is persisted back to NVS after secure routing sends or authenticated sync updates.
Cloud KNX remote-access preparation is part of the `knx.oamRouter.cloudRemote` config. The status JSON reports the selected mode (`mqtt`, relay, or UDP punch-through-oriented deployments), whether secure tunnels are required, and whether relay endpoint, MQTT topic prefix, and token-reference fields are configured. The firmware does not start an external relay client yet; this config is the stable handoff surface for a future UDP relay/MQTT tunnel transport that will reuse the secure OAM tunnel path.
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. Enable `GATEWAY_KNX_TP_FULL_IP_FORWARD` when the gateway must mirror all physical TP telegrams back out to KNXnet/IP tunnelling and multicast so ETS can monitor or download other TP devices through the gateway's IP endpoint. Local logical-device responses are de-duplicated by exact cEMI payload at the tunnel egress, so the same response is not replayed through tunnel, routing, and TP echo paths while distinct source addresses remain visible as separate devices. 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 chip-level KNXnet/IP endpoint per physical gateway module 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 matching channel bridges instead of starting one UDP socket or multicast responder per DALI channel. If a non-owner channel router was already running, starting the shared endpoint stops it so KNXnet/IP search and description requests receive one response from the chip-level interface.
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 the OAM router persona is enabled, use `GATEWAY_KNX_OAM_PROGRAMMING_BUTTON_GPIO` and `GATEWAY_KNX_OAM_PROGRAMMING_LED_GPIO` for its separate programming controls. The bridge config validator rejects duplicate REG1-Dali and OAM programming button or LED GPIO assignments so ETS programming-mode selection remains unambiguous.
When `GATEWAY_KNX_SECURITY_DEV_ENDPOINTS` is enabled, the bridge HTTP action surface exposes development-only operations for reading, writing, generating, and resetting the factory setup key, exporting the factory certificate payload, and clearing local KNX security failure diagnostics. These endpoints require explicit confirmation fields in the JSON body and should stay disabled in production builds. The default development storage mode is plain NVS via `GATEWAY_KNX_SECURITY_PLAIN_NVS`; production builds should replace that with encrypted NVS, flash encryption, and secure boot before handling real commissioning keys.
The normal bridge status response includes a `knx.security` object with compile-time capability flags, storage mode, factory setup key metadata, factory certificate metadata, and security failure counters/log entries. Secret FDSK strings are returned only by the explicit development actions, not by passive status polling.
## Modbus
Modbus TCP, RTU, and ASCII are owned by `gateway/components/gateway_modbus` and started through the per-channel bridge service. The gateway keeps the existing bridge config JSON shape with a top-level `modbus` object containing `transport`, `host`, `port`, and `unitID`, and now adds nested serial UART settings for RTU/ASCII. Parsing and runtime behavior live in the gateway project rather than in `dali_cpp`.
See `MODBUS.md` for transport setup, UART0 policy, RS485 wiring, runtime `@DALIGW` management commands, supported function codes, and the full generated address map with address formulas.
The first generated map slice creates stable points for every DALI short address `0-63` whether the device is online, offline, or never seen. Per short address, the generated map reserves a 32-point stride in each Modbus space:
@@ -35,4 +93,24 @@ The first generated map slice creates stable points for every DALI short address
- Holding registers: writable brightness, color temperature, group mask, power-on level, system-failure level, min/max level, and fade time.
- Input registers: read-only inventory state, primary type, type mask, cached actual level, scene id, raw status placeholder, group mask, and cached settings.
Unknown numeric values read as `0xFFFF`; booleans read as false unless inventory or cache state proves otherwise. Provisioned Modbus models still work as overrides at their configured Modbus point, and normal generated reads prefer gateway cache state to avoid DALI bus polling.
Unknown numeric values read as `0xFFFF`; booleans read as false unless inventory or cache state proves otherwise. Provisioned Modbus models still work as overrides at their configured Modbus point, and normal generated reads prefer gateway cache state to avoid DALI bus polling.
An extension discrete-input range starts immediately after the legacy `0-63` short-address block. It publishes decoded DALI status, failure, and feature bits as individual booleans for base status, DT1 emergency, DT4/5/6 control-gear feature/failure status, and DT8 color status/features. This keeps existing Modbus addresses stable while making bit-level diagnostics readable without consumers masking packed status registers.
## BACnet/IP
BACnet/IP is owned by `gateway/components/gateway_bacnet` and is started through the per-channel bridge service. Runtime BACnet server settings live under top-level `bacnetServer` in bridge config:
```json
{
"bacnetServer": {
"deviceInstance": 4194303,
"localAddress": "",
"udpPort": 47808
}
}
```
Provisioned BACnet models still use generic `BridgeModel` fields such as object type, object instance, property, and optional `bitIndex`. Query-style models refresh BACnet `Present_Value` from live DALI reads, and binary models with `bitIndex` expose a single packed status bit.
For discovered DALI short addresses, the gateway also mirrors the generated Modbus discrete diagnostics as BACnet binary-input objects. Object instances are allocated in a gateway-owned generated range using the channel index plus the generated Modbus discrete-input offset, so generated objects stay deterministic while avoiding the provisioned-object address space in normal deployments.
+2
View File
@@ -0,0 +1,2 @@
CompileFlags:
Remove: [-f*, -m*]
+13
View File
@@ -0,0 +1,13 @@
{
"idf.currentSetup": "/Users/tonylu/.espressif/v5.5.4/esp-idf",
"idf.customExtraVars": {
"OPENOCD_SCRIPTS": "/Users/tonylu/.espressif/tools/openocd-esp32/v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts",
"ESP_ROM_ELF_DIR": "/Users/tonylu/.espressif/tools/esp-rom-elfs/20240305/"
},
"clangd.path": "/Users/tonylu/.espressif/tools/esp-clang/esp-20.1.1_20250829/esp-clang/bin/clangd",
"clangd.arguments": [
"--background-index",
"--query-driver=**",
"--compile-commands-dir=/Users/tonylu/StudioProjects/dalimaster/gateway/apps/gateway/build"
]
}
+1
View File
@@ -6,6 +6,7 @@ endif()
set(EXTRA_COMPONENT_DIRS
"${CMAKE_CURRENT_LIST_DIR}/../../components"
"${CMAKE_CURRENT_LIST_DIR}/../../knx"
"${CMAKE_CURRENT_LIST_DIR}/../../../dali_cpp"
)
+1 -1
View File
@@ -1,6 +1,6 @@
idf_component_register(
SRCS "app_main.cpp"
REQUIRES gateway_core gateway_controller gateway_network gateway_bridge gateway_cache dali_domain gateway_runtime gateway_ble gateway_usb_setup log
REQUIRES gateway_core gateway_controller gateway_network gateway_bridge gateway_cache dali_domain gateway_runtime gateway_ble gateway_usb_setup gateway_485_control gateway_knx log
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x14000,
otadata, data, ota, 0x1D000, 0x2000,
phy_init, data, phy, 0x1F000, 0x1000,
factory, app, factory, 0x20000, 0x300000,
knxprops, data, 0x40, 0x320000, 0x60000,
storage, data, spiffs, 0x380000, 0x80000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x14000
3 otadata data ota 0x1D000 0x2000
4 phy_init data phy 0x1F000 0x1000
5 factory app factory 0x20000 0x300000
6 knxprops data 0x40 0x320000 0x60000
7 storage data spiffs 0x380000 0x80000
+346 -86
View File
@@ -562,13 +562,13 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
CONFIG_ESPTOOLPY_BEFORE_RESET=y
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
@@ -587,8 +587,8 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4M-single.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions-4M-single.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
@@ -596,44 +596,120 @@ CONFIG_PARTITION_TABLE_MD5=y
#
# Gateway App
#
CONFIG_GATEWAY_CHANNEL_COUNT=2
#
# DALI Settings
#
CONFIG_GATEWAY_CHANNEL_COUNT=1
#
# Gateway Channel 1
#
CONFIG_GATEWAY_CHANNEL1_GW_ID=3
CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED=y
# CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE is not set
CONFIG_GATEWAY_CHANNEL1_GW_ID=0
# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set
CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y
# CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set
CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID=0
CONFIG_GATEWAY_CHANNEL1_NATIVE_TX_PIN=2
CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN=1
CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
# end of Gateway Channel 1
#
# Gateway Channel 2
#
CONFIG_GATEWAY_CHANNEL2_GW_ID=4
CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED=y
# CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE is not set
# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set
# end of Gateway Channel 2
#
# Gateway Channel 3
#
# end of Gateway Channel 3
#
# Gateway Channel 4
#
# end of Gateway Channel 4
#
# Gateway Channel 5
#
# end of Gateway Channel 5
#
# Gateway Channel 6
#
# end of Gateway Channel 6
#
# Gateway Channel 7
#
# end of Gateway Channel 7
#
# Gateway Channel 8
#
# end of Gateway Channel 8
#
# Gateway Channel 9
#
# end of Gateway Channel 9
#
# Gateway Channel 10
#
# end of Gateway Channel 10
#
# Gateway Channel 11
#
# end of Gateway Channel 11
#
# Gateway Channel 12
#
# end of Gateway Channel 12
#
# Gateway Channel 13
#
# end of Gateway Channel 13
#
# Gateway Channel 14
#
# end of Gateway Channel 14
#
# Gateway Channel 15
#
# end of Gateway Channel 15
#
# Gateway Channel 16
#
# end of Gateway Channel 16
#
# Gateway Cache
#
CONFIG_GATEWAY_CACHE_SUPPORTED=y
CONFIG_GATEWAY_CACHE_START_ENABLED=y
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
# CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=5000
CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR=y
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=600000
CONFIG_GATEWAY_CACHE_REFRESH_INTERVAL_MS=120000
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
# end of Gateway Cache
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
CONFIG_GATEWAY_CONTROLLER_TASK_STACK_SIZE=12288
# end of DALI Settings
#
# Gateway Startup Services
# Connectivity Startup
#
CONFIG_GATEWAY_BLE_SUPPORTED=y
CONFIG_GATEWAY_START_BLE_ENABLED=y
@@ -644,20 +720,136 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
# end of Connectivity Startup
#
# 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=20
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
#
# Bridge Runtime
#
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
# end of Bridge Runtime
#
# Modbus Settings
#
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU is not set
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set
CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
# end of Modbus Settings
#
# BACnet Settings
#
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
# end of BACnet Settings
#
# KNX Settings
#
CONFIG_GATEWAY_KNX_INSTANCE_COUNT=1
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=y
CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS=y
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID=0x01e5
CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID=0xa401
CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER=0x0001
CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION=0x08
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
CONFIG_GATEWAY_KNX_DALI_BUS_ID=0
# CONFIG_GATEWAY_KNX_DEBUG_DUMP_MEMORY is not set
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_OAM_ROUTER_SUPPORTED=y
CONFIG_GATEWAY_KNX_OAM_ROUTER_ENABLED=y
CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID=0x00FA
CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID=0x0001
CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER=0xA11F
CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION=0x07
CONFIG_GATEWAY_KNX_OAM_ROUTER_INDIVIDUAL_ADDRESS=65282
CONFIG_GATEWAY_KNX_OAM_ROUTER_TUNNEL_ADDRESS_BASE=65296
CONFIG_GATEWAY_KNX_OAM_PROGRAMMING_BUTTON_GPIO=-1
CONFIG_GATEWAY_KNX_OAM_PROGRAMMING_LED_GPIO=-1
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_STARTUP_TIMEOUT_MS=2000
CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y
CONFIG_GATEWAY_KNX_TP_FULL_IP_FORWARD=y
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
# end of KNX Settings
#
# Cloud Settings
#
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX="devices"
CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT=y
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_LTE_UART is not set
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT_AND_LTE_UART is not set
CONFIG_GATEWAY_CLOUD_LTE_UART_PORT=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE=115200
# end of Cloud Settings
#
# Bridge Task Settings
#
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
# end of Bridge Task Settings
#
# USB Setup
#
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
# end of Gateway Startup Services
# end of USB Setup
#
# UART0 Control
#
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
# end of UART0 Control
#
# Gateway Network Services
@@ -670,6 +862,8 @@ CONFIG_GATEWAY_STATUS_LED_GPIO=-1
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS=3000
CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO=-1
CONFIG_GATEWAY_BUTTON_TASK_STACK_SIZE=8192
# end of Gateway Network Services
# end of Gateway App
@@ -717,6 +911,7 @@ CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y
#
# CONFIG_APPTRACE_DEST_JTAG is not set
CONFIG_APPTRACE_DEST_NONE=y
# CONFIG_APPTRACE_DEST_UART0 is not set
# CONFIG_APPTRACE_DEST_UART1 is not set
# CONFIG_APPTRACE_DEST_UART2 is not set
# CONFIG_APPTRACE_DEST_USB_CDC is not set
@@ -742,11 +937,12 @@ CONFIG_BT_CONTROLLER_ENABLED=y
#
# General
#
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL is not set
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
CONFIG_BT_NIMBLE_PINNED_TO_CORE=0
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y
# CONFIG_BT_NIMBLE_PINNED_TO_CORE_1 is not set
CONFIG_BT_NIMBLE_PINNED_TO_CORE=1
# CONFIG_BT_NIMBLE_PINNED_TO_CORE_0 is not set
CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y
CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096
CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE=y
# end of General
@@ -788,7 +984,7 @@ CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3
CONFIG_BT_NIMBLE_HS_PVCY=y
# CONFIG_BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN is not set
# CONFIG_BT_NIMBLE_HOST_QUEUE_CONG_CHECK is not set
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4
CONFIG_BT_NIMBLE_MAX_CCCDS=8
CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=y
CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000
@@ -951,10 +1147,10 @@ CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23
# CONFIG_BT_NIMBLE_MEM_DEBUG is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_NONE is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING is not set
CONFIG_BT_NIMBLE_LOG_LEVEL_INFO=y
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
# CONFIG_BT_NIMBLE_LOG_LEVEL_INFO is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_DEBUG is not set
CONFIG_BT_NIMBLE_LOG_LEVEL=1
CONFIG_BT_NIMBLE_LOG_LEVEL=2
CONFIG_BT_NIMBLE_PRINT_ERR_NAME=y
# CONFIG_BT_NIMBLE_DEBUG is not set
# CONFIG_BT_NIMBLE_TEST_THROUGHPUT_TEST is not set
@@ -983,9 +1179,9 @@ CONFIG_BT_CTRL_MODE_EFF=1
CONFIG_BT_CTRL_BLE_MAX_ACT=6
CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=6
CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0
CONFIG_BT_CTRL_PINNED_TO_CORE_0=y
# CONFIG_BT_CTRL_PINNED_TO_CORE_1 is not set
CONFIG_BT_CTRL_PINNED_TO_CORE=0
# CONFIG_BT_CTRL_PINNED_TO_CORE_0 is not set
CONFIG_BT_CTRL_PINNED_TO_CORE_1=y
CONFIG_BT_CTRL_PINNED_TO_CORE=1
CONFIG_BT_CTRL_HCI_MODE_VHCI=y
# CONFIG_BT_CTRL_HCI_MODE_UART_H4 is not set
CONFIG_BT_CTRL_HCI_TL=1
@@ -1017,12 +1213,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
@@ -1248,15 +1444,15 @@ CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
#
# ESP-Driver:GPIO Configurations
#
# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
# end of ESP-Driver:GPIO Configurations
#
# ESP-Driver:GPTimer Configurations
#
CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y
# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set
# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_ISR_CACHE_SAFE=y
CONFIG_GPTIMER_OBJ_CACHE_SAFE=y
# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set
# end of ESP-Driver:GPTimer Configurations
@@ -1495,8 +1691,8 @@ CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4
#
# Sleep Config
#
# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set
CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y
CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y
CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y
CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y
CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y
@@ -1589,7 +1785,7 @@ CONFIG_ESP_NETIF_TCPIP_LWIP=y
# CONFIG_ESP_NETIF_LOOPBACK is not set
CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y
# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set
CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS=y
# CONFIG_ESP_NETIF_L2_TAP is not set
# CONFIG_ESP_NETIF_BRIDGE_EN is not set
# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
@@ -1635,7 +1831,40 @@ CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y
#
# ESP PSRAM
#
# CONFIG_SPIRAM is not set
CONFIG_SPIRAM=y
#
# SPI RAM config
#
CONFIG_SPIRAM_MODE_QUAD=y
# CONFIG_SPIRAM_MODE_OCT is not set
CONFIG_SPIRAM_TYPE_AUTO=y
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
CONFIG_SPIRAM_CLK_IO=30
CONFIG_SPIRAM_CS_IO=26
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
# CONFIG_SPIRAM_RODATA is not set
# CONFIG_SPIRAM_SPEED_120M is not set
CONFIG_SPIRAM_SPEED_80M=y
# CONFIG_SPIRAM_SPEED_40M is not set
CONFIG_SPIRAM_SPEED=80
CONFIG_SPIRAM_BOOT_HW_INIT=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
# CONFIG_SPIRAM_USE_MEMMAP is not set
# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
# end of SPI RAM config
# end of ESP PSRAM
#
@@ -1659,9 +1888,9 @@ CONFIG_ESP_ROM_PRINT_IN_IRAM=y
# ESP System Settings
#
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
#
# Cache config
@@ -1707,7 +1936,7 @@ CONFIG_ESP_SYSTEM_IN_IRAM=y
CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y
# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set
# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set
CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0
CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=2
CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y
CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y
@@ -1720,24 +1949,21 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
# CONFIG_ESP_CONSOLE_UART_DEFAULT is not set
# CONFIG_ESP_CONSOLE_USB_CDC is not set
# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
# CONFIG_ESP_CONSOLE_NONE is not set
# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set
CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y
CONFIG_ESP_CONSOLE_UART=y
CONFIG_ESP_CONSOLE_UART_NUM=0
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
CONFIG_ESP_CONSOLE_UART_NUM=-1
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=4
CONFIG_ESP_INT_WDT=y
CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
CONFIG_ESP_INT_WDT_CHECK_CPU1=y
@@ -1790,9 +2016,9 @@ CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y
# CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0
# CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER=y
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=1
CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5
# CONFIG_ESP_WIFI_CSI_ENABLED is not set
CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y
@@ -1800,8 +2026,8 @@ CONFIG_ESP_WIFI_TX_BA_WIN=6
CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP_WIFI_RX_BA_WIN=6
CONFIG_ESP_WIFI_NVS_ENABLED=y
CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y
# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set
# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0 is not set
CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y
CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32
CONFIG_ESP_WIFI_IRAM_OPT=y
@@ -1890,6 +2116,7 @@ CONFIG_FATFS_CODEPAGE=437
CONFIG_FATFS_FS_LOCK=0
CONFIG_FATFS_TIMEOUT_MS=10000
CONFIG_FATFS_PER_FILE_CACHE=y
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
# CONFIG_FATFS_USE_FASTSEEK is not set
CONFIG_FATFS_USE_STRFUNC_NONE=y
# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set
@@ -1917,12 +2144,12 @@ CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0
#
# CONFIG_FREERTOS_SMP is not set
# CONFIG_FREERTOS_UNICORE is not set
CONFIG_FREERTOS_HZ=100
CONFIG_FREERTOS_HZ=1000
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=2048
# CONFIG_FREERTOS_USE_IDLE_HOOK is not set
# CONFIG_FREERTOS_USE_TICK_HOOK is not set
CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
@@ -1967,6 +2194,7 @@ CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
#
# Extra
#
CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y
# end of Extra
CONFIG_FREERTOS_PORT=y
@@ -2094,8 +2322,7 @@ CONFIG_LWIP_GARP_TMR_INTERVAL=60
CONFIG_LWIP_ESP_MLDV6_REPORT=y
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
CONFIG_LWIP_DHCP_DOES_ACD_CHECK=y
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
@@ -2114,13 +2341,16 @@ CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
CONFIG_LWIP_DHCPS_ADD_DNS=y
# end of DHCP server
# CONFIG_LWIP_AUTOIP is not set
CONFIG_LWIP_AUTOIP=y
CONFIG_LWIP_AUTOIP_TRIES=2
CONFIG_LWIP_AUTOIP_MAX_CONFLICTS=9
CONFIG_LWIP_AUTOIP_RATE_LIMIT_INTERVAL=20
CONFIG_LWIP_IPV4=y
CONFIG_LWIP_IPV6=y
# CONFIG_LWIP_IPV6_AUTOCONFIG is not set
CONFIG_LWIP_IPV6_NUM_ADDRESSES=3
# CONFIG_LWIP_IPV6_FORWARD is not set
# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set
CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
CONFIG_LWIP_NETIF_LOOPBACK=y
CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
@@ -2251,6 +2481,7 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
# mbedTLS
#
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
@@ -2407,12 +2638,15 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y
CONFIG_LIBC_ASSERT_BUFFER_SIZE=200
# end of LibC
CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y
#
# NVS
#
# CONFIG_NVS_ENCRYPTION is not set
# CONFIG_NVS_ASSERT_ERROR_CHECK is not set
# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set
# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set
# end of NVS
#
@@ -2680,13 +2914,36 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
#
CONFIG_DALI_PHY_COUNT=16
CONFIG_DALI_DEFAULT_BAUDRATE=1200
CONFIG_DALI_API_QUEUE_LEN=10
CONFIG_DALI_TX_QUEUE_LEN=1
CONFIG_DALI_TIMER_RESOLUTION_HZ=3636363
CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0
CONFIG_DALI_TX_STOP_CONDITION_US=0
CONFIG_DALI_RX_STOP_CONDITION_US=0
CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=30
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=20
CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=20
CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=10
CONFIG_DALI_FORWARD_MAX_WAIT_MS=50
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=10
CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500
CONFIG_DALI_BUS_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=y
# CONFIG_DALI_LOG_LEVEL_VERBOSE is not set
CONFIG_DALI_LOG_LEVEL=4
CONFIG_DALI_TX_ACTIVE_LOW=y
# CONFIG_DALI_TX_ACTIVE_HIGH is not set
CONFIG_DALI_RX_ACTIVE_LOW=y
# CONFIG_DALI_RX_ACTIVE_HIGH is not set
CONFIG_DALI_API_QUEUE_LEN=64
CONFIG_DALI_TX_QUEUE_LEN=4
CONFIG_DALI_TX_REPLY_QUEUE_LEN=4
CONFIG_DALI_RX_QUEUE_LEN=50
CONFIG_DALI_DEBUG_QUEUE_LEN=100
# CONFIG_DALI_ENABLE_DEBUG_TASK is not set
CONFIG_DALI_DALI_TASK_STACK_SIZE=2048
CONFIG_DALI_ENABLE_DEBUG_TASK=y
CONFIG_DALI_DALI_TASK_STACK_SIZE=8192
CONFIG_DALI_DALI_TASK_PRIORITY=2
CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048
CONFIG_DALI_DEBUG_TASK_PRIORITY=1
@@ -2708,10 +2965,12 @@ CONFIG_MQTT_TRANSPORT_SSL=y
CONFIG_MQTT_TRANSPORT_WEBSOCKET=y
CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y
# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set
# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set
CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED=y
# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set
# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set
# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set
CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED=y
# CONFIG_MQTT_USE_CORE_0 is not set
CONFIG_MQTT_USE_CORE_1=y
# CONFIG_MQTT_CUSTOM_OUTBOX is not set
# end of ESP-MQTT Configurations
# end of Component config
@@ -2755,11 +3014,12 @@ CONFIG_ESP32_APPTRACE_DEST_NONE=y
CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
# CONFIG_BLUEDROID_ENABLED is not set
CONFIG_NIMBLE_ENABLED=y
CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
# CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL is not set
CONFIG_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y
# CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
CONFIG_NIMBLE_PINNED_TO_CORE=0
CONFIG_NIMBLE_PINNED_TO_CORE_0=y
# CONFIG_NIMBLE_PINNED_TO_CORE_1 is not set
CONFIG_NIMBLE_PINNED_TO_CORE=1
# CONFIG_NIMBLE_PINNED_TO_CORE_0 is not set
CONFIG_NIMBLE_PINNED_TO_CORE_1=y
CONFIG_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096
CONFIG_NIMBLE_ROLE_CENTRAL=y
@@ -2773,7 +3033,7 @@ CONFIG_BT_NIMBLE_SM_SC_LVL=0
# CONFIG_NIMBLE_NVS_PERSIST is not set
CONFIG_NIMBLE_MAX_BONDS=3
CONFIG_NIMBLE_RPA_TIMEOUT=900
CONFIG_NIMBLE_MAX_CONNECTIONS=3
CONFIG_NIMBLE_MAX_CONNECTIONS=4
CONFIG_NIMBLE_MAX_CCCDS=8
CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS=y
# CONFIG_NIMBLE_HS_FLOW_CTRL is not set
@@ -2796,7 +3056,7 @@ CONFIG_SW_COEXIST_ENABLE=y
CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y
CONFIG_ESP_WIFI_SW_COEXIST_ENABLE=y
# CONFIG_CAM_CTLR_DVP_CAM_ISR_IRAM_SAFE is not set
# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set
CONFIG_GPTIMER_ISR_IRAM_SAFE=y
# CONFIG_MCPWM_ISR_IRAM_SAFE is not set
# CONFIG_EVENT_LOOP_PROFILING is not set
CONFIG_POST_EVENTS_FROM_ISR=y
@@ -2804,7 +3064,6 @@ CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
CONFIG_GDBSTUB_SUPPORT_TASKS=y
CONFIG_GDBSTUB_MAX_TASKS=32
# CONFIG_OTA_ALLOW_HTTP is not set
# CONFIG_ESP_SYSTEM_PD_FLASH is not set
CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000
CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000
CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y
@@ -2840,21 +3099,21 @@ CONFIG_ESP32_PHY_MAX_TX_POWER=20
# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set
CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y
CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y
# CONFIG_ESP32S3_SPIRAM_SUPPORT is not set
CONFIG_ESP32S3_SPIRAM_SUPPORT=y
CONFIG_DEFAULT_PSRAM_CLK_IO=30
CONFIG_DEFAULT_PSRAM_CS_IO=26
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160
# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160 is not set
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240
CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_MAIN_TASK_STACK_SIZE=3584
CONFIG_CONSOLE_UART_DEFAULT=y
CONFIG_MAIN_TASK_STACK_SIZE=8192
# CONFIG_CONSOLE_UART_DEFAULT is not set
# CONFIG_CONSOLE_UART_CUSTOM is not set
# CONFIG_CONSOLE_UART_NONE is not set
# CONFIG_ESP_CONSOLE_UART_NONE is not set
CONFIG_CONSOLE_UART=y
CONFIG_CONSOLE_UART_NUM=0
CONFIG_CONSOLE_UART_BAUDRATE=115200
CONFIG_CONSOLE_UART_NUM=-1
CONFIG_INT_WDT=y
CONFIG_INT_WDT_TIMEOUT_MS=300
CONFIG_INT_WDT_CHECK_CPU1=y
@@ -2881,8 +3140,8 @@ CONFIG_ESP32_WIFI_TX_BA_WIN=6
CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP32_WIFI_RX_BA_WIN=6
CONFIG_ESP32_WIFI_NVS_ENABLED=y
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y
# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set
# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 is not set
CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1=y
CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
CONFIG_ESP32_WIFI_IRAM_OPT=y
@@ -2907,6 +3166,7 @@ CONFIG_TIMER_TASK_PRIORITY=1
CONFIG_TIMER_TASK_STACK_DEPTH=2048
CONFIG_TIMER_QUEUE_LENGTH=10
# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
# CONFIG_HAL_ASSERTION_SILIENT is not set
# CONFIG_L2_TO_L3_COPY is not set
CONFIG_ESP_GRATUITOUS_ARP=y
+7 -1
View File
@@ -7,9 +7,15 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_BT_ENABLED=y
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_ETH_ENABLED=y
CONFIG_ETH_USE_SPI_ETHERNET=y
CONFIG_ETH_SPI_ETHERNET_W5500=y
CONFIG_ETH_SPI_ETHERNET_W5500=y
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
File diff suppressed because it is too large Load Diff
+293 -55
View File
@@ -562,13 +562,13 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
CONFIG_ESPTOOLPY_BEFORE_RESET=y
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
@@ -587,8 +587,8 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4M-single.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions-4M-single.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
@@ -596,7 +596,180 @@ CONFIG_PARTITION_TABLE_MD5=y
#
# Gateway App
#
#
# DALI Settings
#
CONFIG_GATEWAY_CHANNEL_COUNT=2
#
# Gateway Channel 1
#
CONFIG_GATEWAY_CHANNEL1_GW_ID=3
# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set
CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE=y
# CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set
CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID=0
CONFIG_GATEWAY_CHANNEL1_NATIVE_TX_PIN=2
CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN=1
CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
# end of Gateway Channel 1
#
# Gateway Channel 2
#
CONFIG_GATEWAY_CHANNEL2_GW_ID=4
# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set
CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE=y
# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set
CONFIG_GATEWAY_CHANNEL2_NATIVE_BUS_ID=1
CONFIG_GATEWAY_CHANNEL2_NATIVE_TX_PIN=4
CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN=3
CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE=1200
# end of Gateway Channel 2
#
# Gateway Cache
#
CONFIG_GATEWAY_CACHE_SUPPORTED=y
CONFIG_GATEWAY_CACHE_START_ENABLED=y
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR=y
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=600000
CONFIG_GATEWAY_CACHE_REFRESH_INTERVAL_MS=120000
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
# end of Gateway Cache
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
# end of DALI Settings
#
# Gateway Startup Services
#
CONFIG_GATEWAY_BLE_SUPPORTED=y
CONFIG_GATEWAY_START_BLE_ENABLED=y
CONFIG_GATEWAY_WIFI_SUPPORTED=y
# CONFIG_GATEWAY_START_WIFI_STA_ENABLED is not set
CONFIG_GATEWAY_ESPNOW_SETUP_SUPPORTED=y
CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
#
# Gateway Wired Ethernet
#
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=2
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=48
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=47
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=33
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=34
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=36
CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0
CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=20
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=-1
CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=4096
# end of Gateway Wired Ethernet
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU is not set
# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set
CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
#
# KNX Settings
#
CONFIG_GATEWAY_KNX_INSTANCE_COUNT=1
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=y
CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS=y
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID=0x01e5
CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID=0xa401
CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER=0x0001
CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION=0x08
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
CONFIG_GATEWAY_KNX_DALI_BUS_ID=0
# CONFIG_GATEWAY_KNX_DEBUG_DUMP_MEMORY is not set
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_OAM_ROUTER_SUPPORTED=y
CONFIG_GATEWAY_KNX_OAM_ROUTER_ENABLED=y
CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID=0x00FA
CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID=0x0001
CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER=0xA11F
CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION=0x07
CONFIG_GATEWAY_KNX_OAM_ROUTER_INDIVIDUAL_ADDRESS=65282
CONFIG_GATEWAY_KNX_OAM_ROUTER_TUNNEL_ADDRESS_BASE=65296
CONFIG_GATEWAY_KNX_OAM_PROGRAMMING_BUTTON_GPIO=-1
CONFIG_GATEWAY_KNX_OAM_PROGRAMMING_LED_GPIO=-1
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_STARTUP_TIMEOUT_MS=2000
CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y
CONFIG_GATEWAY_KNX_TP_FULL_IP_FORWARD=y
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
# end of KNX Settings
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX="devices"
CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT=y
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_LTE_UART is not set
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT_AND_LTE_UART is not set
CONFIG_GATEWAY_CLOUD_LTE_UART_PORT=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE=115200
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
# end of Gateway Startup Services
#
# Gateway Network Services
#
CONFIG_GATEWAY_NETWORK_HTTP_ENABLED=y
CONFIG_GATEWAY_NETWORK_HTTP_PORT=80
CONFIG_GATEWAY_NETWORK_UDP_ROUTER_ENABLED=y
CONFIG_GATEWAY_NETWORK_UDP_PORT=2020
CONFIG_GATEWAY_STATUS_LED_GPIO=-1
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS=3000
CONFIG_GATEWAY_SETUP_AP_BUTTON_GPIO=-1
CONFIG_GATEWAY_BUTTON_TASK_STACK_SIZE=8192
# end of Gateway Network Services
# end of Gateway App
#
@@ -643,6 +816,7 @@ CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y
#
# CONFIG_APPTRACE_DEST_JTAG is not set
CONFIG_APPTRACE_DEST_NONE=y
# CONFIG_APPTRACE_DEST_UART0 is not set
# CONFIG_APPTRACE_DEST_UART1 is not set
# CONFIG_APPTRACE_DEST_UART2 is not set
# CONFIG_APPTRACE_DEST_USB_CDC is not set
@@ -668,11 +842,12 @@ CONFIG_BT_CONTROLLER_ENABLED=y
#
# General
#
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL is not set
CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y
# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set
CONFIG_BT_NIMBLE_PINNED_TO_CORE=0
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y
# CONFIG_BT_NIMBLE_PINNED_TO_CORE_1 is not set
CONFIG_BT_NIMBLE_PINNED_TO_CORE=1
# CONFIG_BT_NIMBLE_PINNED_TO_CORE_0 is not set
CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y
CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=4096
CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE=y
# end of General
@@ -714,7 +889,7 @@ CONFIG_BT_NIMBLE_MAX_CONN_REATTEMPT=3
CONFIG_BT_NIMBLE_HS_PVCY=y
# CONFIG_BT_NIMBLE_HOST_ALLOW_CONNECT_WITH_SCAN is not set
# CONFIG_BT_NIMBLE_HOST_QUEUE_CONG_CHECK is not set
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=3
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4
CONFIG_BT_NIMBLE_MAX_CCCDS=8
CONFIG_BT_NIMBLE_CRYPTO_STACK_MBEDTLS=y
CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=2000
@@ -877,10 +1052,10 @@ CONFIG_BT_NIMBLE_HCI_UART_CTS_PIN=23
# CONFIG_BT_NIMBLE_MEM_DEBUG is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_NONE is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING is not set
CONFIG_BT_NIMBLE_LOG_LEVEL_INFO=y
CONFIG_BT_NIMBLE_LOG_LEVEL_WARNING=y
# CONFIG_BT_NIMBLE_LOG_LEVEL_INFO is not set
# CONFIG_BT_NIMBLE_LOG_LEVEL_DEBUG is not set
CONFIG_BT_NIMBLE_LOG_LEVEL=1
CONFIG_BT_NIMBLE_LOG_LEVEL=2
CONFIG_BT_NIMBLE_PRINT_ERR_NAME=y
# CONFIG_BT_NIMBLE_DEBUG is not set
# CONFIG_BT_NIMBLE_TEST_THROUGHPUT_TEST is not set
@@ -909,9 +1084,9 @@ CONFIG_BT_CTRL_MODE_EFF=1
CONFIG_BT_CTRL_BLE_MAX_ACT=6
CONFIG_BT_CTRL_BLE_MAX_ACT_EFF=6
CONFIG_BT_CTRL_BLE_STATIC_ACL_TX_BUF_NB=0
CONFIG_BT_CTRL_PINNED_TO_CORE_0=y
# CONFIG_BT_CTRL_PINNED_TO_CORE_1 is not set
CONFIG_BT_CTRL_PINNED_TO_CORE=0
# CONFIG_BT_CTRL_PINNED_TO_CORE_0 is not set
CONFIG_BT_CTRL_PINNED_TO_CORE_1=y
CONFIG_BT_CTRL_PINNED_TO_CORE=1
CONFIG_BT_CTRL_HCI_MODE_VHCI=y
# CONFIG_BT_CTRL_HCI_MODE_UART_H4 is not set
CONFIG_BT_CTRL_HCI_TL=1
@@ -943,12 +1118,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
@@ -1174,15 +1349,15 @@ CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
#
# ESP-Driver:GPIO Configurations
#
# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set
CONFIG_GPIO_CTRL_FUNC_IN_IRAM=y
# end of ESP-Driver:GPIO Configurations
#
# ESP-Driver:GPTimer Configurations
#
CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y
# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set
# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set
CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM=y
CONFIG_GPTIMER_ISR_CACHE_SAFE=y
CONFIG_GPTIMER_OBJ_CACHE_SAFE=y
# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set
# end of ESP-Driver:GPTimer Configurations
@@ -1421,8 +1596,8 @@ CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4
#
# Sleep Config
#
# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set
CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y
CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y
CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y
CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y
CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y
@@ -1515,7 +1690,7 @@ CONFIG_ESP_NETIF_TCPIP_LWIP=y
# CONFIG_ESP_NETIF_LOOPBACK is not set
CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y
# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set
CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS=y
# CONFIG_ESP_NETIF_L2_TAP is not set
# CONFIG_ESP_NETIF_BRIDGE_EN is not set
# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
@@ -1561,7 +1736,40 @@ CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y
#
# ESP PSRAM
#
# CONFIG_SPIRAM is not set
CONFIG_SPIRAM=y
#
# SPI RAM config
#
CONFIG_SPIRAM_MODE_QUAD=y
# CONFIG_SPIRAM_MODE_OCT is not set
CONFIG_SPIRAM_TYPE_AUTO=y
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
CONFIG_SPIRAM_CLK_IO=30
CONFIG_SPIRAM_CS_IO=26
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
# CONFIG_SPIRAM_RODATA is not set
# CONFIG_SPIRAM_SPEED_120M is not set
CONFIG_SPIRAM_SPEED_80M=y
# CONFIG_SPIRAM_SPEED_40M is not set
CONFIG_SPIRAM_SPEED=80
CONFIG_SPIRAM_BOOT_HW_INIT=y
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
# CONFIG_SPIRAM_USE_MEMMAP is not set
# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
# end of SPI RAM config
# end of ESP PSRAM
#
@@ -1585,9 +1793,9 @@ CONFIG_ESP_ROM_PRINT_IN_IRAM=y
# ESP System Settings
#
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
#
# Cache config
@@ -1633,7 +1841,7 @@ CONFIG_ESP_SYSTEM_IN_IRAM=y
CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y
# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set
# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set
CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0
CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=2
CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y
CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y
@@ -1646,24 +1854,21 @@ CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304
CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048
CONFIG_ESP_CONSOLE_UART_DEFAULT=y
# CONFIG_ESP_CONSOLE_UART_DEFAULT is not set
# CONFIG_ESP_CONSOLE_USB_CDC is not set
# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
# CONFIG_ESP_CONSOLE_NONE is not set
# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set
CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y
CONFIG_ESP_CONSOLE_UART=y
CONFIG_ESP_CONSOLE_UART_NUM=0
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0
CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
CONFIG_ESP_CONSOLE_UART_NUM=-1
CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=4
CONFIG_ESP_INT_WDT=y
CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
CONFIG_ESP_INT_WDT_CHECK_CPU1=y
@@ -1716,9 +1921,9 @@ CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32
CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y
# CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0
# CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER is not set
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER=y
CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=1
CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5
# CONFIG_ESP_WIFI_CSI_ENABLED is not set
CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y
@@ -1726,8 +1931,8 @@ CONFIG_ESP_WIFI_TX_BA_WIN=6
CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y
CONFIG_ESP_WIFI_RX_BA_WIN=6
CONFIG_ESP_WIFI_NVS_ENABLED=y
CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y
# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set
# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0 is not set
CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y
CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32
CONFIG_ESP_WIFI_IRAM_OPT=y
@@ -1816,6 +2021,7 @@ CONFIG_FATFS_CODEPAGE=437
CONFIG_FATFS_FS_LOCK=0
CONFIG_FATFS_TIMEOUT_MS=10000
CONFIG_FATFS_PER_FILE_CACHE=y
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
# CONFIG_FATFS_USE_FASTSEEK is not set
CONFIG_FATFS_USE_STRFUNC_NONE=y
# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set
@@ -1843,12 +2049,12 @@ CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0
#
# CONFIG_FREERTOS_SMP is not set
# CONFIG_FREERTOS_UNICORE is not set
CONFIG_FREERTOS_HZ=100
CONFIG_FREERTOS_HZ=1000
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=2048
# CONFIG_FREERTOS_USE_IDLE_HOOK is not set
# CONFIG_FREERTOS_USE_TICK_HOOK is not set
CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
@@ -1893,6 +2099,7 @@ CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
#
# Extra
#
CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y
# end of Extra
CONFIG_FREERTOS_PORT=y
@@ -2020,8 +2227,7 @@ CONFIG_LWIP_GARP_TMR_INTERVAL=60
CONFIG_LWIP_ESP_MLDV6_REPORT=y
CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
CONFIG_LWIP_DHCP_DOES_ACD_CHECK=y
# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
@@ -2040,13 +2246,16 @@ CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
CONFIG_LWIP_DHCPS_ADD_DNS=y
# end of DHCP server
# CONFIG_LWIP_AUTOIP is not set
CONFIG_LWIP_AUTOIP=y
CONFIG_LWIP_AUTOIP_TRIES=2
CONFIG_LWIP_AUTOIP_MAX_CONFLICTS=9
CONFIG_LWIP_AUTOIP_RATE_LIMIT_INTERVAL=20
CONFIG_LWIP_IPV4=y
CONFIG_LWIP_IPV6=y
# CONFIG_LWIP_IPV6_AUTOCONFIG is not set
CONFIG_LWIP_IPV6_NUM_ADDRESSES=3
# CONFIG_LWIP_IPV6_FORWARD is not set
# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set
CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
CONFIG_LWIP_NETIF_LOOPBACK=y
CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
@@ -2177,6 +2386,7 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
# mbedTLS
#
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
@@ -2333,12 +2543,15 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y
CONFIG_LIBC_ASSERT_BUFFER_SIZE=200
# end of LibC
CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y
#
# NVS
#
# CONFIG_NVS_ENCRYPTION is not set
# CONFIG_NVS_ASSERT_ERROR_CHECK is not set
# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set
# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set
# end of NVS
#
@@ -2606,13 +2819,36 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
#
CONFIG_DALI_PHY_COUNT=16
CONFIG_DALI_DEFAULT_BAUDRATE=1200
CONFIG_DALI_API_QUEUE_LEN=10
CONFIG_DALI_TX_QUEUE_LEN=1
CONFIG_DALI_TIMER_RESOLUTION_HZ=3636363
CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US=0
CONFIG_DALI_TX_STOP_CONDITION_US=0
CONFIG_DALI_RX_STOP_CONDITION_US=0
CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS=30
CONFIG_DALI_DOUBLE_SEND_DELAY_MS=20
CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS=20
CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS=10
CONFIG_DALI_FORWARD_MAX_WAIT_MS=50
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS=10
CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS=500
CONFIG_DALI_BUS_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=y
# CONFIG_DALI_LOG_LEVEL_VERBOSE is not set
CONFIG_DALI_LOG_LEVEL=4
CONFIG_DALI_TX_ACTIVE_LOW=y
# CONFIG_DALI_TX_ACTIVE_HIGH is not set
CONFIG_DALI_RX_ACTIVE_LOW=y
# CONFIG_DALI_RX_ACTIVE_HIGH is not set
CONFIG_DALI_API_QUEUE_LEN=64
CONFIG_DALI_TX_QUEUE_LEN=4
CONFIG_DALI_TX_REPLY_QUEUE_LEN=4
CONFIG_DALI_RX_QUEUE_LEN=50
CONFIG_DALI_DEBUG_QUEUE_LEN=100
# CONFIG_DALI_ENABLE_DEBUG_TASK is not set
CONFIG_DALI_DALI_TASK_STACK_SIZE=2048
CONFIG_DALI_ENABLE_DEBUG_TASK=y
CONFIG_DALI_DALI_TASK_STACK_SIZE=8192
CONFIG_DALI_DALI_TASK_PRIORITY=2
CONFIG_DALI_DEBUG_TASK_STACK_SIZE=2048
CONFIG_DALI_DEBUG_TASK_PRIORITY=1
@@ -2634,10 +2870,12 @@ CONFIG_MQTT_TRANSPORT_SSL=y
CONFIG_MQTT_TRANSPORT_WEBSOCKET=y
CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y
# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set
# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set
CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED=y
# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set
# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set
# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set
CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED=y
# CONFIG_MQTT_USE_CORE_0 is not set
CONFIG_MQTT_USE_CORE_1=y
# CONFIG_MQTT_CUSTOM_OUTBOX is not set
# end of ESP-MQTT Configurations
# end of Component config
Submodule
+1
Submodule bacnet_stack added at b1f4389f31
+169 -1
View File
@@ -14,6 +14,174 @@ config DALI_DEFAULT_BAUDRATE
help
Default baudrate used during initialization.
config DALI_TIMER_RESOLUTION_HZ
int "Native DALI timer resolution Hz"
range 1000000 8000000
default 3636363
help
GPTimer resolution for native DALI Manchester timing. The default
3.636363 MHz timing matches the gateway's tuned 1200 bps behavior for
older control gear that needs more recovery margin between commands.
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 30
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 20
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 20
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 10
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 10
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 500
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 3000
help
Interval for publishing the legacy two-byte FF FD bus-abnormal raw frame
while the native DALI bus is power-down. Set to 0 to disable the report.
choice DALI_LOG_LEVEL_CHOICE
prompt "DALI log level"
default DALI_LOG_LEVEL_WARN
help
Runtime log level applied to the native DALI HAL ESP-IDF log tag.
config DALI_LOG_LEVEL_NONE
bool "No output"
config DALI_LOG_LEVEL_ERROR
bool "Error"
config DALI_LOG_LEVEL_WARN
bool "Warning"
config DALI_LOG_LEVEL_INFO
bool "Info"
config DALI_LOG_LEVEL_DEBUG
bool "Debug"
config DALI_LOG_LEVEL_VERBOSE
bool "Verbose"
endchoice
config DALI_LOG_LEVEL
int
default 0 if DALI_LOG_LEVEL_NONE
default 1 if DALI_LOG_LEVEL_ERROR
default 2 if DALI_LOG_LEVEL_WARN
default 3 if DALI_LOG_LEVEL_INFO
default 4 if DALI_LOG_LEVEL_DEBUG
default 5 if DALI_LOG_LEVEL_VERBOSE
choice DALI_TX_ACTIVE_LEVEL
prompt "DALI TX pin active level"
default DALI_TX_ACTIVE_LOW
help
Select the physical GPIO level that drives the DALI bus active. The
native gateway default is TX active low.
config DALI_TX_ACTIVE_LOW
bool "Active low"
config DALI_TX_ACTIVE_HIGH
bool "Active high"
endchoice
choice DALI_RX_ACTIVE_LEVEL
prompt "DALI RX pin active level"
default DALI_RX_ACTIVE_HIGH
help
Select the physical GPIO level read when the DALI bus is active. The
native gateway default is RX active high.
config DALI_RX_ACTIVE_LOW
bool "Active low"
config DALI_RX_ACTIVE_HIGH
bool "Active high"
endchoice
config DALI_API_QUEUE_LEN
int "Global API queue length"
range 1 64
@@ -48,7 +216,7 @@ config DALI_ENABLE_DEBUG_TASK
config DALI_DALI_TASK_STACK_SIZE
int "DALI task stack size"
range 1024 8192
default 2048
default 4096
config DALI_DALI_TASK_PRIORITY
int "DALI task priority"
+24
View File
@@ -41,10 +41,34 @@ void app_main(void) {
Use `menuconfig` under `DALI Component` to configure:
- Bus count and default baudrate.
- Native timing values for development, including timer resolution, half-bit period,
TX/RX stop conditions, query response timeout, double-send delay, and TX queue
arbitration waits.
- TX/RX active polarity. The native gateway default is TX active low and RX active high.
- Native bus power-down polling and legacy `FF FD` bus-abnormal raw-frame reporting intervals.
- Native DALI HAL log level for the `dali_hal` ESP-IDF log tag.
- Queue sizes.
- Task stack sizes and priorities.
- Optional debug task.
The native bus monitor uses `CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS` to resample RX while
power-down and `CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS` to publish legacy `FF FD`
raw frames while down. The report interval defaults to 3000 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.636363 MHz timer. `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 30 ms, which covers the 5.5-10.5 ms backward-frame
start window plus the approximately 9.95 ms backward frame duration.
Native TX queue arbitration uses frame length as the frame type signal. Two- to four-byte
forward frames wait up to `CONFIG_DALI_FORWARD_MAX_WAIT_MS` for a valid send window,
using `CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS` after normal bus activity and
`CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS` after a valid backward frame. One-byte
backward frames wait up to `CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS` for idle, then are
sent without echo verification because addressing-phase collisions are valid.
## API Note
The global TX response queue symbol was renamed:
+28 -7
View File
@@ -6,6 +6,14 @@
#include <memory.h> // for memset
#include "freertos/semphr.h"
#ifndef CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS
#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 30
#endif
#ifndef CONFIG_DALI_DOUBLE_SEND_DELAY_MS
#define CONFIG_DALI_DOUBLE_SEND_DELAY_MS 20
#endif
static SemaphoreHandle_t s_dali_core_lock;
static SemaphoreHandle_t dali_core_mutex(void)
@@ -31,6 +39,20 @@ static inline void dali_core_unlock(void)
}
}
static UBaseType_t drain_rx_queue(QueueHandle_t rx_q)
{
if (rx_q == NULL) {
return 0;
}
Dali_msg_t stale = {0};
UBaseType_t drained = 0;
while (xQueueReceive(rx_q, &stale, 0) == pdTRUE) {
drained++;
}
return drained;
}
Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3) {
Dali_msg_t dali_msg;
dali_msg.id = 0;
@@ -90,7 +112,7 @@ void dali_send_double(Dali_msg_t *dali_msg) {
dali_send_locked(dali_msg);
// TODO check status
dali_msg->id++; // increment message ID
dali_delay_ms(10); // delay 13ms 101.8.1.2: 13.5 - 75ms
dali_delay_ms(CONFIG_DALI_DOUBLE_SEND_DELAY_MS);
dali_send_locked(dali_msg);
// TODO check status
dali_core_unlock();
@@ -110,17 +132,16 @@ int dali_query(Dali_msg_t *tx_msg, Dali_msg_t *rx_msg) {
return -1;
}
// TODO check empty queue
if(xQueueReceive(rx_q, rx_msg, 0) == pdTRUE) {
printf("Queue not empty\n");
return -1;
dali_core_lock();
UBaseType_t drained = drain_rx_queue(rx_q);
if (drained > 0) {
printf("dali_query: dropped %u stale RX frame(s)\n", (unsigned)drained);
}
// printf("check A tx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, tx_msg->type, tx_msg->status, tx_msg->length, tx_msg->data[0]);
dali_core_lock();
dali_send_locked(tx_msg);
// receive message from DALI task
ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(50));
ret = xQueueReceive(rx_q, rx_msg, pdMS_TO_TICKS(CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS));
dali_core_unlock();
// printf("B rx=%d tm=%d[ms] st=%d len=%d d0=0x%X\n", ret, rx_msg->type, rx_msg->status, rx_msg->length, rx_msg->data[0]);
return ret;
+621 -52
View File
@@ -4,6 +4,15 @@
//ESP-IDF HAL
#ifdef CONFIG_IDF_TARGET
#include "sdkconfig.h"
#ifndef CONFIG_DALI_LOG_LEVEL
#define CONFIG_DALI_LOG_LEVEL 2
#endif
#ifndef LOG_LOCAL_LEVEL
#define LOG_LOCAL_LEVEL CONFIG_DALI_LOG_LEVEL
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
@@ -39,7 +48,7 @@
#endif
#ifndef CONFIG_DALI_DALI_TASK_STACK_SIZE
#define CONFIG_DALI_DALI_TASK_STACK_SIZE 2048
#define CONFIG_DALI_DALI_TASK_STACK_SIZE 4096
#endif
#ifndef CONFIG_DALI_DALI_TASK_PRIORITY
@@ -54,14 +63,57 @@
#define CONFIG_DALI_DEBUG_TASK_PRIORITY 1
#endif
#ifndef CONFIG_DALI_TIMER_RESOLUTION_HZ
#define CONFIG_DALI_TIMER_RESOLUTION_HZ 3636363
#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 500
#endif
#ifndef CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS
#define CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS 3000
#endif
#ifndef CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS
#define CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS 20
#endif
#ifndef CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
#define CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS 10
#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 10
#endif
#define WITHIN_RANGE(x, min, max) ((x) > (min) && (x) < (max))
#define MAX_DELTA_RELOAD_TIME 600000000 // 600s - max u32: 4,294,967,295~4,294s
#define DALI_BAUDRATE_MIN 400U
#define DALI_BAUDRATE_MAX 2400U
#define DALI_BUS_POWER_CHECK_INTERVAL_US ((uint64_t)CONFIG_DALI_BUS_POWER_CHECK_INTERVAL_MS * 1000ULL)
#define DALI_BUS_ABNORMAL_REPORT_INTERVAL_US ((uint64_t)CONFIG_DALI_BUS_ABNORMAL_REPORT_INTERVAL_MS * 1000ULL)
typedef struct {
uint32_t hb;
uint32_t timer_alarm_ticks;
uint32_t rx_hb_min;
uint32_t rx_hb_max;
uint32_t rx_2hb_min;
@@ -76,6 +128,11 @@ typedef struct {
static const char *TAG = "dali_hal";
static inline void apply_dali_log_level(void)
{
esp_log_level_set(TAG, (esp_log_level_t)CONFIG_DALI_LOG_LEVEL);
}
typedef struct {
uint8_t bus_id;
uint8_t tx_pin;
@@ -91,8 +148,17 @@ typedef struct {
uint32_t rx_tx_delta;
uint32_t rx_pulse_width;
uint8_t rx_level;
uint8_t tx_level;
uint64_t bus_level_check_time;
uint64_t bus_abnormal_report_time;
Dali_msg_t tx_data;
bool bus_activity_seen;
uint64_t rx_last_frame_time;
uint8_t rx_last_frame_length;
uint8_t rx_last_frame_status;
volatile bool force_backward_tx;
Dali_msg_t rx_data;
uint8_t tx_half_bit_counter;
@@ -137,17 +203,31 @@ static inline void dali_hal_unlock(void)
}
}
static uint32_t dali_half_bit_from_baud(uint32_t baudrate)
static uint32_t dali_half_bit_x100_from_baud(uint32_t baudrate)
{
if (baudrate < DALI_BAUDRATE_MIN || baudrate > DALI_BAUDRATE_MAX) {
return 0;
}
uint64_t hb = 500000ULL + (baudrate / 2U); // round to nearest
hb /= baudrate;
if (hb == 0 || hb > 2000000ULL) { // should never happen with checked bounds
uint64_t hb_x100 = 50000000ULL + (baudrate / 2U); // round to nearest 0.01 us
hb_x100 /= baudrate;
if (hb_x100 == 0 || hb_x100 > 200000000ULL) { // should never happen with checked bounds
return 0;
}
return (uint32_t)hb;
return (uint32_t)hb_x100;
}
static uint32_t half_bit_us_from_x100(uint32_t hb_x100)
{
return (hb_x100 + 50U) / 100U;
}
static uint32_t timer_alarm_ticks_from_x100(uint32_t hb_x100)
{
uint64_t ticks = ((uint64_t)CONFIG_DALI_TIMER_RESOLUTION_HZ * hb_x100 + 50000000ULL) / 100000000ULL;
if (ticks == 0 || ticks > UINT32_MAX) {
return 0;
}
return (uint32_t)ticks;
}
static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
@@ -155,25 +235,40 @@ static inline uint32_t scale_time_by_hb(uint32_t base_us, uint32_t hb_us)
return (uint32_t)(((uint64_t)base_us * hb_us + (DALI_TIME_HB / 2U)) / DALI_TIME_HB);
}
static inline uint32_t configured_or_scaled_time(uint32_t configured_us, uint32_t base_us,
uint32_t hb_us)
{
return configured_us > 0 ? configured_us : scale_time_by_hb(base_us, hb_us);
}
static esp_err_t update_timing_locked(uint32_t baudrate)
{
uint32_t hb = dali_half_bit_from_baud(baudrate);
if (hb == 0) {
ESP_LOGE(TAG, "invalid baudrate: %u", baudrate);
uint32_t hb_x100 = CONFIG_DALI_CUSTOM_HALF_BIT_TIME_X100_US;
if (hb_x100 == 0) {
hb_x100 = dali_half_bit_x100_from_baud(baudrate);
}
uint32_t hb = half_bit_us_from_x100(hb_x100);
uint32_t timer_alarm_ticks = timer_alarm_ticks_from_x100(hb_x100);
if (hb == 0 || timer_alarm_ticks == 0) {
ESP_LOGE(TAG, "invalid native timing: baudrate=%u hbX100Us=%u timerHz=%u",
baudrate, hb_x100, CONFIG_DALI_TIMER_RESOLUTION_HZ);
return ESP_ERR_INVALID_ARG;
}
dali_timing_t new_timing = {
.hb = hb,
.timer_alarm_ticks = timer_alarm_ticks,
.rx_hb_min = scale_time_by_hb(DALI_RX_HB_MIN, hb),
.rx_hb_max = scale_time_by_hb(DALI_RX_HB_MAX, hb),
.rx_2hb_min = scale_time_by_hb(DALI_RX_2HB_MIN, hb),
.rx_2hb_max = scale_time_by_hb(DALI_RX_2HB_MAX, hb),
.rx_stop_cond = scale_time_by_hb(DALI_RX_STOP_COND, hb),
.rx_stop_cond = configured_or_scaled_time(CONFIG_DALI_RX_STOP_CONDITION_US,
DALI_RX_STOP_COND, hb),
.time_bus_down = scale_time_by_hb(DALI_TIME_BUS_DOWN, hb),
.time_break_min = scale_time_by_hb(DALI_TIME_BREAK_MIN, hb),
.time_recovery_min = scale_time_by_hb(DALI_TIME_RECOVERY_MIN, hb),
.tx_stop_cond = scale_time_by_hb(DALI_TX_STOP_COND, hb),
.tx_stop_cond = configured_or_scaled_time(CONFIG_DALI_TX_STOP_CONDITION_US,
DALI_TX_STOP_COND, hb),
.collision_txrx_delta = scale_time_by_hb(DALI_COLLISION_TXRX_DELTA, hb),
};
@@ -199,7 +294,7 @@ static esp_err_t apply_timer_alarm_locked(void)
return ESP_OK;
}
gptimer_alarm_config_t timer_alarm_config = {
.alarm_count = s_timing.hb,
.alarm_count = s_timing.timer_alarm_ticks,
.reload_count = 0,
.flags = {
.auto_reload_on_alarm = true,
@@ -235,22 +330,330 @@ static bool s_debug_task_created = false;
#endif
static bool s_dali_task_created = false;
#define DALI_SET_BUS_HIGH(bus) gpio_set_level((bus)->tx_pin, DALI_TX_HIGH) // set bus level
#define DALI_SET_BUS_LOW(bus) gpio_set_level((bus)->tx_pin, DALI_TX_LOW) // set bus level
#define DALI_SET_BUS_LEVEL(bus, x) gpio_set_level((bus)->tx_pin, ((x)==DALI_TX_HIGH)) // set bus level
#define DALI_SET_BUS_HIGH(bus) do { gpio_set_level((bus)->tx_pin, DALI_TX_HIGH); (bus)->tx_level = 1; } while (0)
#define DALI_SET_BUS_LOW(bus) do { gpio_set_level((bus)->tx_pin, DALI_TX_LOW); (bus)->tx_level = 0; } while (0)
#define DALI_SET_BUS_LEVEL(bus, x) do { \
uint8_t level = (x) ? 1 : 0; \
gpio_set_level((bus)->tx_pin, level ? DALI_TX_HIGH : DALI_TX_LOW); \
(bus)->tx_level = level; \
} while (0)
// !!! read from RX pin, we need real bus level, not logic level of TX pin
// return: 0 - bus level low, active state
// 1 - bus level high, idle state
#define DALI_GET_BUS_LEVEL(bus) (gpio_get_level((bus)->rx_pin) == (DALI_RX_HIGH)) // get bus level
// return: 0 - tx pin drive bus low, active state
// 1 - tx pin drive bus high, idle state
#define DALI_GET_TX_LEVEL(bus) (gpio_get_level((bus)->tx_pin) == (DALI_TX_HIGH)) // get TX pin level
// return: 0 - TX drives bus low, active state
// 1 - TX releases/drives bus high, idle state
#define DALI_GET_TX_LEVEL(bus) ((bus)->tx_level)
static inline bool bus_valid(uint8_t bus_id) {
return bus_id < DALI_PHY_COUNT && s_bus[bus_id].inited;
}
static const char *bus_state_name(dali_bus_state_t state)
{
switch (state) {
case DALI_BUS_UNKNOWN: return "unknown";
case DALI_BUS_POWER_DOWN: return "power_down";
case DALI_BUS_ERROR: return "error";
case DALI_BUS_READY: return "ready";
case DALI_BUS_TRANSMITTING: return "transmitting";
case DALI_BUS_RECEIVING: return "receiving";
case DALI_BUS_TIME_BREAK: return "time_break";
case DALI_BUS_RECOVERY: return "recovery";
default: return "invalid";
}
}
static const char *tx_state_name(dali_tx_state_t state)
{
switch (state) {
case TX_STATE_ERROR: return "error";
case TX_STATE_COLLISION: return "collision";
case TX_STATE_IDLE: return "idle";
case TX_STATE_START: return "start";
case TX_STATE_DATA: return "data";
case TX_STATE_STOP: return "stop";
default: return "invalid";
}
}
static const char *rx_state_name(dali_rx_state_t state)
{
switch (state) {
case RX_STATE_ERROR: return "error";
case RX_STATE_IDLE: return "idle";
case RX_STATE_START: return "start";
case RX_STATE_DATA: return "data";
case RX_STATE_STOP: return "stop";
case RX_STATE_END: return "end";
default: return "invalid";
}
}
static uint32_t tx_completion_timeout_ms(const Dali_msg_t *msg)
{
uint32_t hb = s_timing_ready ? s_timing.hb : DALI_TIME_HB;
uint32_t stop_us = s_timing_ready ? s_timing.tx_stop_cond : DALI_TX_STOP_COND;
uint32_t bits = msg ? msg->length : DALI_MAX_BITS;
if (bits == 0 || bits > DALI_MAX_BITS) {
bits = DALI_MAX_BITS;
}
uint64_t frame_us = ((uint64_t)(2U + bits * 2U) * hb) + stop_us;
uint32_t frame_ms = (uint32_t)((frame_us + 999U) / 1000U);
uint32_t timeout_ms = frame_ms + 20U;
if (timeout_ms < 20U) {
timeout_ms = 20U;
}
if (timeout_ms > 500U) {
timeout_ms = 500U;
}
return timeout_ms;
}
static inline bool IRAM_ATTR is_backward_tx_msg(const Dali_msg_t *msg)
{
return msg != NULL && msg->length == 8;
}
static bool is_forward_tx_msg(const Dali_msg_t *msg)
{
return msg != NULL && msg->length >= 16 && msg->length <= 32 && (msg->length % 8) == 0;
}
static UBaseType_t queue_waiting(QueueHandle_t queue);
static uint64_t bus_last_activity_time_us(const dali_bus_ctx_t *bus)
{
if (bus == NULL || !bus->bus_activity_seen) {
return 0;
}
return bus->rx_last_edge_time > bus->tx_last_edge_time ? bus->rx_last_edge_time
: bus->tx_last_edge_time;
}
static bool bus_idle_for_tx(const dali_bus_ctx_t *bus)
{
return bus != NULL && bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE &&
bus->rx_state == RX_STATE_IDLE && bus->rx_level == 1 && queue_waiting(bus->tx_queue) == 0;
}
static bool last_activity_was_backward_frame(const dali_bus_ctx_t *bus, uint64_t last_activity)
{
return bus != NULL && bus->rx_last_frame_status == DALI_FRAME_OK &&
bus->rx_last_frame_length == 8 && bus->rx_last_frame_time != 0 &&
bus->rx_last_frame_time >= last_activity;
}
static TickType_t tx_wait_poll_ticks(void)
{
TickType_t ticks = pdMS_TO_TICKS(1);
return ticks == 0 ? 1 : ticks;
}
static bool wait_for_forward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg)
{
if (bus == NULL || !is_forward_tx_msg(msg)) {
return true;
}
const uint64_t started = esp_timer_get_time();
const uint64_t max_wait_us = (uint64_t)CONFIG_DALI_FORWARD_MAX_WAIT_MS * 1000ULL;
const TickType_t poll_ticks = tx_wait_poll_ticks();
while (true) {
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
return false;
}
const uint64_t now = esp_timer_get_time();
if (bus_idle_for_tx(bus)) {
const uint64_t last_activity = bus_last_activity_time_us(bus);
if (last_activity == 0) {
return true;
}
const uint32_t wait_ms = last_activity_was_backward_frame(bus, last_activity)
? CONFIG_DALI_FORWARD_AFTER_BACKWARD_WAIT_MS
: CONFIG_DALI_FORWARD_ACTIVITY_WAIT_MS;
if (wait_ms == 0 || (now - last_activity) >= ((uint64_t)wait_ms * 1000ULL)) {
return true;
}
}
if (max_wait_us == 0 || (now - started) >= max_wait_us) {
return false;
}
vTaskDelay(poll_ticks);
}
}
static bool wait_for_backward_tx_window(dali_bus_ctx_t *bus, const Dali_msg_t *msg,
bool *force_start)
{
if (force_start != NULL) {
*force_start = false;
}
if (bus == NULL || !is_backward_tx_msg(msg)) {
return true;
}
const uint64_t started = esp_timer_get_time();
const uint64_t timeout_us = (uint64_t)CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS * 1000ULL;
const TickType_t poll_ticks = tx_wait_poll_ticks();
while (true) {
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
return false;
}
if (bus_idle_for_tx(bus)) {
return true;
}
const uint64_t now = esp_timer_get_time();
if (timeout_us == 0 || (now - started) >= timeout_us) {
if (force_start != NULL) {
*force_start = true;
}
return true;
}
vTaskDelay(poll_ticks);
}
}
static UBaseType_t queue_waiting(QueueHandle_t queue)
{
return queue ? uxQueueMessagesWaiting(queue) : 0;
}
static void log_tx_message(const char *prefix, const dali_bus_ctx_t *bus,
const Dali_msg_t *msg, uint32_t timeout_ms)
{
if (bus == NULL || msg == NULL) {
return;
}
ESP_LOGW(TAG,
"%s bus=%u timeoutMs=%lu busState=%s txState=%s rxState=%s "
"txQ=%u replyQ=%u status=%u len=%u data=%02x %02x %02x %02x",
prefix, bus->bus_id, (unsigned long)timeout_ms,
bus_state_name(bus->bus_state), tx_state_name(bus->tx_state),
rx_state_name(bus->rx_state), (unsigned)queue_waiting(bus->tx_queue),
(unsigned)queue_waiting(bus->tx_reply_queue), msg->status, msg->length,
msg->data[0], msg->data[1], msg->data[2], msg->data[3]);
}
static void drain_tx_reply_queue(dali_bus_ctx_t *bus)
{
if (bus == NULL || bus->tx_reply_queue == NULL) {
return;
}
Dali_msg_t stale = {0};
UBaseType_t drained = 0;
while (xQueueReceive(bus->tx_reply_queue, &stale, 0) == pdTRUE) {
drained++;
}
if (drained > 0) {
ESP_LOGW(TAG,
"bus=%u dropped %u stale TX completion(s) status=%u len=%u data=%02x %02x %02x %02x",
bus->bus_id, (unsigned)drained, stale.status, stale.length, stale.data[0],
stale.data[1], stale.data[2], stale.data[3]);
}
}
static void recover_tx_timeout(dali_bus_ctx_t *bus)
{
if (bus == NULL) {
return;
}
if (bus->tx_queue) {
xQueueReset(bus->tx_queue);
}
if (bus->tx_reply_queue) {
xQueueReset(bus->tx_reply_queue);
}
bus->tx_data.status = DALI_FRAME_ERROR;
bus->tx_state = TX_STATE_IDLE;
bus->rx_state = RX_STATE_IDLE;
bus->tx_half_bit_counter = 0;
bus->tx_data_bit_counter = 0;
bus->rx_half_bit_counter = 0;
bus->rx_data_bit_counter = 0;
if (bus->bus_state == DALI_BUS_TRANSMITTING || bus->bus_state == DALI_BUS_TIME_BREAK ||
bus->bus_state == DALI_BUS_RECOVERY) {
bus->tx_last_edge_time = esp_timer_get_time();
DALI_SET_BUS_HIGH(bus);
bus->bus_state = DALI_BUS_READY;
}
ESP_LOGW(TAG, "bus=%u native TX queue recovered busState=%s txState=%s rxState=%s",
bus->bus_id, bus_state_name(bus->bus_state), tx_state_name(bus->tx_state),
rx_state_name(bus->rx_state));
}
static inline bool IRAM_ATTR interval_elapsed_us(uint64_t time_now, uint64_t last_time,
uint64_t interval_us)
{
return interval_us > 0 && (time_now - last_time) >= interval_us;
}
static void IRAM_ATTR publish_bus_abnormal_from_isr(dali_bus_ctx_t *bus, BaseType_t *yield)
{
if (bus == NULL || dali_raw_receive_queue == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0) {
return;
}
Dali_msg_t abnormal = {0};
abnormal.id = bus->bus_id;
abnormal.type = DALI_MSG_FORWARD;
abnormal.status = DALI_FRAME_OK;
abnormal.length = 16;
abnormal.data[0] = 0xFF;
abnormal.data[1] = 0xFD;
if (xQueueSendToBackFromISR(dali_raw_receive_queue, &abnormal, yield) != pdTRUE) {
Dali_msg_t dropped = {0};
xQueueReceiveFromISR(dali_raw_receive_queue, &dropped, yield);
xQueueSendToBackFromISR(dali_raw_receive_queue, &abnormal, yield);
}
}
static void IRAM_ATTR maybe_report_bus_abnormal_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
BaseType_t *yield)
{
if (bus == NULL || DALI_BUS_ABNORMAL_REPORT_INTERVAL_US == 0) {
return;
}
if (!interval_elapsed_us(time_now, bus->bus_abnormal_report_time,
DALI_BUS_ABNORMAL_REPORT_INTERVAL_US)) {
return;
}
publish_bus_abnormal_from_isr(bus, yield);
bus->bus_abnormal_report_time = time_now;
}
static void IRAM_ATTR poll_power_down_bus_from_isr(dali_bus_ctx_t *bus, uint64_t time_now,
BaseType_t *yield)
{
if (bus == NULL || bus->bus_state != DALI_BUS_POWER_DOWN) {
return;
}
if (interval_elapsed_us(time_now, bus->bus_level_check_time,
DALI_BUS_POWER_CHECK_INTERVAL_US)) {
const uint8_t current_level = DALI_GET_BUS_LEVEL(bus);
bus->bus_level_check_time = time_now;
if (current_level != bus->rx_level) {
bus->rx_level = current_level;
bus->rx_last_edge_time = time_now;
}
}
maybe_report_bus_abnormal_from_isr(bus, time_now, yield);
}
static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queue, BaseType_t *yield)
{
if (msg == NULL) {
@@ -264,6 +667,48 @@ static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queu
}
}
static inline void IRAM_ATTR note_rx_frame_from_isr(dali_bus_ctx_t *bus)
{
if (bus == NULL) {
return;
}
bus->rx_last_frame_time = bus->rx_last_edge_time;
bus->rx_last_frame_length = bus->rx_data.length;
bus->rx_last_frame_status = bus->rx_data.status;
}
static void IRAM_ATTR complete_tx_from_isr(dali_bus_ctx_t *bus, BaseType_t *yield)
{
if (bus == NULL) {
return;
}
bus->tx_data.status = DALI_FRAME_OK;
if (!is_backward_tx_msg(&bus->tx_data) && bus->tx_reply_queue) {
if (xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, yield) != pdTRUE) {
Dali_msg_t dropped = {0};
xQueueReceiveFromISR(bus->tx_reply_queue, &dropped, yield);
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, yield);
}
}
bus->tx_state = TX_STATE_IDLE;
}
static void IRAM_ATTR start_tx_collision_recovery_from_isr(dali_bus_ctx_t *bus,
uint64_t time_now)
{
if (bus == NULL) {
return;
}
bus->rx_last_edge_time = time_now;
bus->tx_last_edge_time = time_now;
DALI_SET_BUS_LOW(bus);
bus->bus_state = DALI_BUS_TIME_BREAK;
bus->tx_data.status = DALI_FRAME_COLLISION;
}
// GPIO ISR handler
// define rx_gpio_isr_handler on any edge
static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
@@ -273,6 +718,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
return;
}
BaseType_t yield = false;
uint64_t rx_current_edge_time = esp_timer_get_time(); // get time in us
uint8_t rx_previous_level = bus->rx_level;
@@ -280,6 +726,7 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
// rx_level = 1 if and only if DALI bus is really high, idle
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin - not depend on hw: 0 - low, 1 - high
bus->bus_activity_seen = true;
bus->rx_pulse_width = rx_current_edge_time - bus->rx_last_edge_time; // time from last edge
bus->rx_tx_delta = rx_current_edge_time - bus->tx_last_edge_time; // time from last edge
@@ -287,8 +734,16 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
// always save time of last edge
bus->rx_last_edge_time = rx_current_edge_time; // get time in us
if(bus->bus_state == DALI_BUS_READY && bus->rx_level == 0) // found start bit
bool tx_stop_released = bus->bus_state == DALI_BUS_TRANSMITTING &&
bus->tx_state == TX_STATE_STOP &&
DALI_GET_TX_LEVEL(bus) == 1;
if((bus->bus_state == DALI_BUS_READY || tx_stop_released) && bus->rx_level == 0) // found start bit
{
if (tx_stop_released) {
complete_tx_from_isr(bus, &yield);
}
// within range for backward frame
uint32_t time_ms = bus->rx_pulse_width / 1000; // 1ms = 1000us
if(time_ms>255) bus->rx_data.type = 255;
@@ -378,12 +833,14 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
// if collision detected: we are too late after bit was transmitted
else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->rx_tx_delta > s_timing.collision_txrx_delta)
else if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state != TX_STATE_STOP &&
!is_backward_tx_msg(&bus->tx_data) &&
bus->rx_tx_delta > s_timing.collision_txrx_delta)
{
// we need now to start collision recovery with time break: 101.9.2.4
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_LOW(bus); // force TX low - active state, inform about collision, this also generate new GPIO ISR
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->bus_state = DALI_BUS_TIME_BREAK; // we are in time break state
bus->tx_data.status = DALI_FRAME_COLLISION; // collision detected
}
@@ -399,7 +856,11 @@ static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
dbg.rx_pulse_width = bus->rx_pulse_width;
dbg.rx_tx_delta = bus->rx_tx_delta;
dbg.bus_id = bus->bus_id;
xQueueSendToBackFromISR(rx_dbg_queue, &dbg, NULL); // send data to queue
xQueueSendToBackFromISR(rx_dbg_queue, &dbg, &yield); // send data to queue
}
if (yield) {
portYIELD_FROM_ISR();
}
}
@@ -423,6 +884,18 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
bus->tx_last_edge_time = time_now - MAX_DELTA_RELOAD_TIME/2; // half of max time
}
poll_power_down_bus_from_isr(bus, time_now, &yield);
rx_delta = time_now - bus->rx_last_edge_time;
tx_delta = time_now - bus->tx_last_edge_time;
if (bus->bus_state == DALI_BUS_TRANSMITTING && bus->tx_state == TX_STATE_STOP &&
DALI_GET_TX_LEVEL(bus) == 1 &&
(bus->rx_state == RX_STATE_START || bus->rx_state == RX_STATE_DATA ||
bus->rx_state == RX_STATE_STOP)) {
complete_tx_from_isr(bus, &yield);
bus->bus_state = DALI_BUS_RECEIVING;
}
// recovery from different error states: UNKNOWN, ERROR, POWER_DOWN
if(bus->bus_state <= DALI_BUS_ERROR)
{ // 101.8.2.4 - startup BUS after 2.4ms
@@ -434,8 +907,13 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// if bus power down - if bus is low for more then 45ms
if(bus->rx_level==0 && rx_delta > s_timing.time_bus_down)
{ // power lost
bool entered_power_down = bus->bus_state != DALI_BUS_POWER_DOWN;
bus->bus_state = DALI_BUS_POWER_DOWN; // bus is power down - recovery see previous if
DALI_SET_BUS_HIGH(bus); // make sure TX is high
if (entered_power_down) {
bus->bus_level_check_time = time_now;
bus->bus_abnormal_report_time = time_now;
}
}
// recovery from collision detection
@@ -444,9 +922,9 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// BUS: ACTIVE, LOW - BUS is busy, let caller to restart transmission
if(bus->bus_state == DALI_BUS_TIME_BREAK && rx_delta > s_timing.time_break_min)
{
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_HIGH(bus); // TX high - idle state - generate ISR on RX pin
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
// read bus state
if(DALI_GET_BUS_LEVEL(bus) == 0) // other device is keeping bus low
{
@@ -467,33 +945,58 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
}
// start transmitting
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state == TX_STATE_IDLE && bus->tx_queue && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, NULL) == pdTRUE)
else if(bus->tx_state == TX_STATE_IDLE && bus->tx_queue)
{
bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success
bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting
bus->tx_state = TX_STATE_START; // start transmitting
bus->tx_half_bit_counter = 0;
bus->tx_data_bit_counter = 0; // actually sent bits count
DALI_SET_BUS_LOW(bus); // start bit first half
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
Dali_msg_t pending = {0};
bool can_start = bus->bus_state == DALI_BUS_READY;
bool force_backward = false;
if (!can_start && bus->force_backward_tx &&
xQueuePeekFromISR(bus->tx_queue, &pending) == pdTRUE) {
if (is_backward_tx_msg(&pending) && bus->bus_state != DALI_BUS_POWER_DOWN) {
can_start = true;
force_backward = true;
} else {
bus->force_backward_tx = false;
}
}
if (can_start && xQueueReceiveFromISR(bus->tx_queue, &bus->tx_data, &yield) == pdTRUE) {
bus->force_backward_tx = false;
if (force_backward) {
bus->rx_state = RX_STATE_IDLE;
}
bus->tx_data.status = DALI_FRAME_ERROR; // error status - will be set ok on success
bus->bus_activity_seen = true;
bus->bus_state = DALI_BUS_TRANSMITTING; // bus is transmitting
bus->tx_state = TX_STATE_START; // start transmitting
bus->tx_half_bit_counter = 0;
bus->tx_data_bit_counter = 0; // actually sent bits count
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_LOW(bus); // start bit first half
}
}
else if(bus->bus_state == DALI_BUS_TRANSMITTING)
{
uint8_t bus_level = DALI_GET_BUS_LEVEL(bus);
if(bus->tx_state != TX_STATE_STOP && !is_backward_tx_msg(&bus->tx_data) &&
tx_delta > s_timing.collision_txrx_delta &&
bus_level != DALI_GET_TX_LEVEL(bus)) {
bus->rx_level = bus_level;
bus->rx_tx_delta = tx_delta;
start_tx_collision_recovery_from_isr(bus, time_now);
}
// transmit data
if(bus->tx_state == TX_STATE_START) {
else if(bus->tx_state == TX_STATE_START) {
bus->tx_state = TX_STATE_DATA; // start transmitting data
bus->tx_half_bit_counter++;
DALI_SET_BUS_HIGH(bus); // start bit second half
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_HIGH(bus); // start bit second half
}
else if(bus->tx_state == TX_STATE_DATA) {
bool value = (bus->tx_data.data[bus->tx_data_bit_counter/8] >> ( 7 - (bus->tx_data_bit_counter % 8) )) & 0x01;
value ^= bus->tx_half_bit_counter & 0x01; // xor=invert value for odd half bit 1:0->1 and 0:1->0
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_LEVEL(bus, value);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
bus->tx_half_bit_counter++; // increment half bit counter before next test
if(bus->tx_half_bit_counter & 0x01) { // next bit
bus->tx_data_bit_counter++;
@@ -506,15 +1009,11 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// here we check TX (NOT RX) bit state
if(DALI_GET_TX_LEVEL(bus) == 0) // really ok - otherwise we will keep bus low forever
{
DALI_SET_BUS_HIGH(bus);
bus->tx_last_edge_time = esp_timer_get_time(); // get time in us
DALI_SET_BUS_HIGH(bus);
}
else if(tx_delta > s_timing.tx_stop_cond) {
bus->tx_data.status = DALI_FRAME_OK; // frame is OK
if (bus->tx_reply_queue) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
}
bus->tx_state = TX_STATE_IDLE; // final state with transmitted data
complete_tx_from_isr(bus, &yield);
bus->bus_state = DALI_BUS_READY; // bus is ready
}
}
@@ -522,7 +1021,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
else if(bus->bus_state == DALI_BUS_READY && bus->tx_state > TX_STATE_IDLE)
{
// we are not transmitting but we have data - reply to queue and let error state in tx_data.status
if (bus->tx_reply_queue) {
if (bus->tx_reply_queue && !is_backward_tx_msg(&bus->tx_data)) {
xQueueSendToBackFromISR(bus->tx_reply_queue, &bus->tx_data, &yield); // send data to queue
}
bus->tx_state = TX_STATE_IDLE; // clear state
@@ -538,6 +1037,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
// rx_data.status = DALI_FRAME_ERROR; // should be set inside ISR
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
// rx_data.data[0] = 0xAA; // debug
note_rx_frame_from_isr(bus);
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
}
}
@@ -549,9 +1049,14 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
bus->rx_data.status = DALI_FRAME_OK; // frame is OK
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
// rx_data.data[0] = 0xBB; // debug
note_rx_frame_from_isr(bus);
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
}
}
else if(bus->rx_state == RX_STATE_START && rx_delta > s_timing.rx_hb_max) {
bus->rx_state = RX_STATE_ERROR;
bus->rx_data.status = DALI_FRAME_TIME_VIOLATION;
}
}
return yield;
}
@@ -588,7 +1093,7 @@ static void debug_task(void *pvParameters)
else v = '0' + dbg.level;
}
else v=' ';
printf("bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]",
ESP_LOGD(TAG, "bus[%u] rx: [%2d] pw=%lu v=%u rtd=%lu [%c]",
dbg.bus_id, i++, dbg.rx_pulse_width, dbg.level, dbg.rx_tx_delta, v);
if(dbg.rx_pulse_width < 1000) {
if(dbg.rx_pulse_width > 550) HB+=2;
@@ -659,7 +1164,7 @@ static esp_err_t ensure_timer_started(void)
.flags = {
.intr_shared = true,
},
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1 tick = 1us
.resolution_hz = CONFIG_DALI_TIMER_RESOLUTION_HZ,
};
err = gptimer_new_timer(&timer_config, &gptimer);
if (err != ESP_OK) {
@@ -726,6 +1231,9 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
bus->bus_id = bus_id;
bus->tx_pin = tx_pin;
bus->rx_pin = rx_pin;
bus->tx_level = 1;
gpio_set_level(bus->tx_pin, DALI_TX_HIGH);
gpio_config_t io_conf;
@@ -771,6 +1279,13 @@ static esp_err_t init_bus(uint8_t bus_id, uint8_t tx_pin, uint8_t rx_pin)
bus->rx_last_edge_time = esp_timer_get_time(); // get time in us - startup time
bus->rx_level = DALI_GET_BUS_LEVEL(bus); // get level of RX pin
bus->tx_last_edge_time = bus->rx_last_edge_time;
bus->bus_level_check_time = bus->rx_last_edge_time;
bus->bus_abnormal_report_time = 0;
bus->bus_activity_seen = false;
bus->rx_last_frame_time = 0;
bus->rx_last_frame_length = 0;
bus->rx_last_frame_status = DALI_FRAME_UNKNOWN;
bus->force_backward_tx = false;
err = ensure_isr_service();
if (err != ESP_OK) {
@@ -805,17 +1320,69 @@ static int dali_tx_bus(dali_bus_ctx_t *bus, Dali_msg_t *dali_msg)
if (bus == NULL || !bus->inited) {
return ESP_FAIL;
}
if(xQueueSendToBack(bus->tx_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
xQueueReset(bus->tx_queue); // clear queue
printf("dali_tx: Queue full\n");
if (bus->tx_queue == NULL || bus->tx_reply_queue == NULL || dali_msg == NULL) {
return ESP_FAIL;
}
if(xQueueReceive(bus->tx_reply_queue, dali_msg, pdMS_TO_TICKS(50)) == pdFALSE) {
xQueueReset(bus->tx_reply_queue); // clear queue
printf("dali_tx: No reply\n");
const uint32_t timeout_ms = tx_completion_timeout_ms(dali_msg);
const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
drain_tx_reply_queue(bus);
if (bus->bus_state == DALI_BUS_POWER_DOWN) {
dali_msg->status = DALI_FRAME_ERROR;
log_tx_message("native TX skipped, bus power down", bus, dali_msg, timeout_ms);
return ESP_FAIL;
}
return ESP_OK;
ESP_LOGD(TAG, "bus=%u queue TX len=%u data=%02x %02x %02x %02x timeoutMs=%lu",
bus->bus_id, dali_msg->length, dali_msg->data[0], dali_msg->data[1],
dali_msg->data[2], dali_msg->data[3], (unsigned long)timeout_ms);
if (is_forward_tx_msg(dali_msg) && !wait_for_forward_tx_window(bus, dali_msg)) {
dali_msg->status = DALI_FRAME_ERROR;
log_tx_message("native TX wait timeout", bus, dali_msg,
CONFIG_DALI_FORWARD_MAX_WAIT_MS);
return ESP_FAIL;
}
bool force_backward = false;
if (is_backward_tx_msg(dali_msg) &&
!wait_for_backward_tx_window(bus, dali_msg, &force_backward)) {
dali_msg->status = DALI_FRAME_ERROR;
log_tx_message("native backward TX wait timeout", bus, dali_msg,
CONFIG_DALI_BACKWARD_IDLE_TIMEOUT_MS);
return ESP_FAIL;
}
bus->force_backward_tx = force_backward;
if(xQueueSendToBack(bus->tx_queue, dali_msg, 0) == pdFALSE) {
bus->force_backward_tx = false;
log_tx_message("native TX queue full", bus, dali_msg, timeout_ms);
if (bus->tx_state == TX_STATE_IDLE) {
xQueueReset(bus->tx_queue);
}
return ESP_FAIL;
}
if (is_backward_tx_msg(dali_msg)) {
dali_msg->status = DALI_FRAME_OK;
return ESP_OK;
}
if(xQueueReceive(bus->tx_reply_queue, dali_msg, timeout_ticks) == pdFALSE) {
if (bus->tx_state == TX_STATE_IDLE && queue_waiting(bus->tx_queue) == 0) {
*dali_msg = bus->tx_data;
log_tx_message("native TX completion queue missed", bus, dali_msg, timeout_ms);
return dali_msg->status == DALI_FRAME_OK ? ESP_OK : ESP_FAIL;
}
log_tx_message("native TX completion timeout", bus, dali_msg, timeout_ms);
recover_tx_timeout(bus);
return ESP_FAIL;
}
ESP_LOGD(TAG, "bus=%u TX complete status=%u len=%u data=%02x %02x %02x %02x",
bus->bus_id, dali_msg->status, dali_msg->length, dali_msg->data[0],
dali_msg->data[1], dali_msg->data[2], dali_msg->data[3]);
return dali_msg->status == DALI_FRAME_OK ? ESP_OK : ESP_FAIL;
}
// dali_task - should run at highest priority
@@ -843,6 +1410,7 @@ void dali_task(void *pvParameters)
esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
{
dali_hal_lock();
apply_dali_log_level();
esp_err_t err = ensure_timing_ready_locked();
if (err == ESP_OK) {
@@ -873,6 +1441,7 @@ esp_err_t dali_hal_init(uint8_t dali_id, uint8_t tx_pin, uint8_t rx_pin)
esp_err_t dali_hal_set_baudrate(uint32_t baudrate)
{
dali_hal_lock();
apply_dali_log_level();
bool resume_timer = s_timer_started && gptimer;
if (resume_timer) {
+8
View File
@@ -3,6 +3,10 @@
#include <stdint.h>
#include "dali_hal.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
Addressing: 102.7.2.1
0AAA AAAx - short address AAAAAA 0-63
@@ -77,5 +81,9 @@ void dali_set_search_addr24(uint32_t addr24);
uint32_t dali_binary_search();
void dali_change_short_address(int addr1, int addr2);
#ifdef __cplusplus
}
#endif
+24 -7
View File
@@ -8,6 +8,10 @@
#include "freertos/task.h"
#include "freertos/queue.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef CONFIG_DALI_PHY_COUNT
#define DALI_PHY_COUNT CONFIG_DALI_PHY_COUNT
#else
@@ -182,13 +186,22 @@ typedef struct Dali_msg Dali_msg_t;
// define HW DALI for gpio functions
#ifndef DALI_HW_PINS
// define HW DALI for gpio functions
// - what we should write to pin to get HIGH/LOW
#define DALI_TX_HIGH 1 // idle state
#define DALI_TX_LOW 0 // active state
// - what we should read from pin to get HIGH/LOW
#define DALI_RX_HIGH 1 // idle state
#define DALI_RX_LOW 0 // active state
// Physical GPIO levels for logical DALI idle/high and active/low bus states.
#ifdef CONFIG_DALI_TX_ACTIVE_HIGH
#define DALI_TX_HIGH 0 // idle state
#define DALI_TX_LOW 1 // active state
#else
#define DALI_TX_HIGH 1 // idle state
#define DALI_TX_LOW 0 // active state
#endif
#ifdef CONFIG_DALI_RX_ACTIVE_LOW
#define DALI_RX_HIGH 1 // idle state
#define DALI_RX_LOW 0 // active state
#else
#define DALI_RX_HIGH 0 // idle state
#define DALI_RX_LOW 1 // active state
#endif
#endif
// LED onboard - debug
@@ -228,3 +241,7 @@ esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info);
QueueHandle_t dali_hal_raw_receive_queue(void);
void dali_task(void *pvParameters);
#ifdef __cplusplus
}
#endif
@@ -68,6 +68,7 @@ struct DaliChannelInfo {
uint8_t gateway_id{0};
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
std::string name;
std::optional<uint8_t> native_bus_id;
};
struct DaliRawFrame {
@@ -125,6 +126,12 @@ class DaliDomainService {
void addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink);
bool resetBus(uint8_t gateway_id) const;
bool isBusIdle(uint8_t gateway_id, uint32_t quiet_ms) const;
void markHostActivity(uint8_t gateway_id) const;
void markHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
bool hasRecentHostActivity(uint8_t gateway_id, uint32_t window_ms) const;
bool matchesRecentHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr, uint8_t command,
uint32_t window_ms) const;
bool writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const;
std::vector<uint8_t> transactBridgeFrame(uint8_t gateway_id, const uint8_t* data,
size_t len) const;
@@ -134,9 +141,14 @@ class DaliDomainService {
std::optional<DaliDomainSnapshot> discoverDeviceTypes(
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types = {},
int max_next_types = 16) const;
std::optional<DaliDomainSnapshot> baseStatusSnapshot(uint8_t gateway_id,
int short_address) const;
std::optional<DaliDomainSnapshot> dt1Snapshot(uint8_t gateway_id, int short_address) const;
std::optional<DaliDomainSnapshot> dt4Snapshot(uint8_t gateway_id, int short_address) const;
std::optional<DaliDomainSnapshot> dt5Snapshot(uint8_t gateway_id, int short_address) const;
std::optional<DaliDomainSnapshot> dt6Snapshot(uint8_t gateway_id, int short_address) const;
std::optional<DaliDomainSnapshot> dt8StatusSnapshot(uint8_t gateway_id,
int short_address) const;
std::optional<DaliDomainSnapshot> dt8SceneColorReport(uint8_t gateway_id, int short_address,
int scene) const;
std::optional<DaliDomainSnapshot> dt8PowerOnLevelColorReport(uint8_t gateway_id,
@@ -157,6 +169,7 @@ class DaliDomainService {
bool on(uint8_t gateway_id, int short_address) const;
bool off(uint8_t gateway_id, int short_address) const;
bool off(int short_address) const;
std::optional<uint8_t> queryActualLevel(uint8_t gateway_id, int short_address) const;
std::optional<uint16_t> queryGroupMask(uint8_t gateway_id, int short_address) const;
std::optional<uint8_t> querySceneLevel(uint8_t gateway_id, int short_address, int scene) const;
std::optional<DaliAddressSettingsSnapshot> queryAddressSettings(uint8_t gateway_id,
@@ -176,6 +189,12 @@ class DaliDomainService {
private:
struct DaliChannel;
struct RecentHostCommandFrame {
uint8_t raw_addr{0};
uint8_t command{0};
TickType_t tick{0};
bool valid{false};
};
DaliChannel* findChannelByGateway(uint8_t gateway_id);
const DaliChannel* findChannelByGateway(uint8_t gateway_id) const;
@@ -189,10 +208,16 @@ class DaliDomainService {
static void RawFrameTaskEntry(void* arg);
void rawFrameTaskLoop();
void notifyRawFrameSinks(const DaliRawFrame& frame);
void markBusActivity(uint8_t gateway_id) const;
std::vector<std::unique_ptr<DaliChannel>> channels_;
std::vector<std::function<void(const DaliRawFrame& frame)>> raw_frame_sinks_;
SemaphoreHandle_t raw_frame_sink_lock_{nullptr};
mutable SemaphoreHandle_t bus_activity_lock_{nullptr};
mutable std::map<uint8_t, TickType_t> last_bus_activity_ticks_;
mutable SemaphoreHandle_t host_activity_lock_{nullptr};
mutable std::map<uint8_t, TickType_t> last_host_activity_ticks_;
mutable std::map<uint8_t, RecentHostCommandFrame> recent_host_command_frames_;
TaskHandle_t raw_frame_task_handle_{nullptr};
};
+576 -54
View File
@@ -1,22 +1,82 @@
#include "dali_domain.hpp"
#define LOG_LOCAL_LEVEL CONFIG_DALI_LOG_LEVEL
#include "esp_log.h"
#include "dali.h"
#include "dali_hal.h"
#include "dali.hpp"
#include "driver/uart.h"
#include "esp_log.h"
#include "freertos/queue.h"
#include <algorithm>
#include <utility>
#ifndef CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS
#define CONFIG_DALI_QUERY_RESPONSE_TIMEOUT_MS 25
#endif
static const char *TAG = "dali_domain";
namespace gateway {
namespace {
constexpr const char* kTag = "dali_domain";
constexpr size_t kSerialRxPacketMaxBytes = 8;
constexpr UBaseType_t kSerialRxQueueDepth = 8;
constexpr uint32_t kHardwareQueryRawPostSuppressMs = 10;
portMUX_TYPE s_query_raw_suppress_lock = portMUX_INITIALIZER_UNLOCKED;
uint8_t s_query_raw_suppress_inflight[DALI_PHY_COUNT] = {};
TickType_t s_query_raw_suppress_post_until[DALI_PHY_COUNT] = {};
void BeginHardwareQueryRawSuppress(uint8_t bus_id) {
if (bus_id >= DALI_PHY_COUNT) {
return;
}
portENTER_CRITICAL(&s_query_raw_suppress_lock);
if (s_query_raw_suppress_inflight[bus_id] < UINT8_MAX) {
++s_query_raw_suppress_inflight[bus_id];
}
s_query_raw_suppress_post_until[bus_id] = 0;
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 post_until = s_query_raw_suppress_post_until[bus_id];
if (s_query_raw_suppress_inflight[bus_id] > 0) {
suppress = true;
} else if (post_until != 0 && now <= post_until) {
suppress = true;
} else if (post_until != 0) {
s_query_raw_suppress_post_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;
}
const TickType_t post_until = xTaskGetTickCount() +
pdMS_TO_TICKS(kHardwareQueryRawPostSuppressMs);
portENTER_CRITICAL(&s_query_raw_suppress_lock);
if (s_query_raw_suppress_inflight[bus_id] > 0) {
--s_query_raw_suppress_inflight[bus_id];
}
if (s_query_raw_suppress_inflight[bus_id] == 0) {
s_query_raw_suppress_post_until[bus_id] = post_until;
}
portEXIT_CRITICAL(&s_query_raw_suppress_lock);
}
DaliDomainSnapshot MakeSnapshot(uint8_t gateway_id, int address, const char* kind) {
DaliDomainSnapshot snapshot;
@@ -64,6 +124,20 @@ struct SerialRxPacket {
uint8_t data[kSerialRxPacketMaxBytes]{};
};
std::vector<uint8_t> LegacyQueryResponse(uint8_t status, uint8_t value = 0x00) {
return {status, value};
}
void LogQueryRxPacket(const char* transport, int id, const std::vector<uint8_t>& packet,
const char* note = nullptr) {
const unsigned first = packet.size() > 0 ? packet[0] : 0;
const unsigned second = packet.size() > 1 ? packet[1] : 0;
ESP_LOGD(TAG, "%s query rx packet id=%d len=%u data=%02x %02x%s%s",
transport == nullptr ? "dali" : transport, id,
static_cast<unsigned>(packet.size()), first, second,
note == nullptr ? "" : " note=", note == nullptr ? "" : note);
}
bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
if (data == nullptr || len != 3) {
return false;
@@ -78,9 +152,11 @@ bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
switch (data[0]) {
case 0x10:
ESP_LOGD(TAG, "sending hardware frame for bus=%u data=%02x %02x", bus_id, data[1], data[2]);
dali_send(&tx);
return true;
case 0x11:
ESP_LOGD(TAG, "sending extended frame for bus=%u data=%02x %02x", bus_id, data[1], data[2]);
dali_send_double(&tx);
return true;
default:
@@ -89,13 +165,21 @@ bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
}
std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
if (data == nullptr || len != 3) {
if (data == nullptr) {
return {};
}
if (len != 3) {
if (len > 0 && data[0] == 0x12) {
const auto packet = LegacyQueryResponse(0xFD);
LogQueryRxPacket("hardware", bus_id, packet, "invalid-query-len");
return packet;
}
return std::vector<uint8_t>{};
}
switch (data[0]) {
case 0x00:
return {0xFF};
ESP_LOGD(TAG, "received reset for bus=%u", bus_id);
case 0x01:
return {1};
case 0x10:
@@ -103,13 +187,31 @@ std::vector<uint8_t> TransactHardwareFrame(uint8_t bus_id, const uint8_t* data,
return SendHardwareFrame(bus_id, data, len) ? std::vector<uint8_t>{0xFF}
: std::vector<uint8_t>{0xFD};
case 0x12: {
ESP_LOGD(TAG, "received hardware query frame for bus=%u data=%02x %02x", bus_id, data[1],
data[2]);
Dali_msg_t tx = dali_msg_new(data[1], data[2]);
tx.id = bus_id;
Dali_msg_t rx = {};
BeginHardwareQueryRawSuppress(bus_id);
if (dali_query(&tx, &rx) == pdTRUE) {
return {0xFF, rx.data[0]};
ClearHardwareQueryRawSuppress(bus_id);
if (rx.status != DALI_FRAME_OK || rx.length != 8) {
ESP_LOGW(TAG, "hardware query response for bus=%u has invalid status or length", bus_id);
const auto packet = LegacyQueryResponse(0xFD);
LogQueryRxPacket("hardware", bus_id, packet, "invalid-status-or-length");
return packet;
}
ESP_LOGD(TAG, "got hardware query response for bus=%u status=%u len=%u data=%02x %02x "
"%02x %02x",
bus_id, rx.status, rx.length, rx.data[0], rx.data[1], rx.data[2], rx.data[3]);
const std::vector<uint8_t> packet{0xFF, rx.data[0]};
LogQueryRxPacket("hardware", bus_id, packet, "ok");
return packet;
}
return {0xFE};
ClearHardwareQueryRawSuppress(bus_id);
const auto packet = LegacyQueryResponse(0xFE);
LogQueryRxPacket("hardware", bus_id, packet, "no-response");
return packet;
}
default:
return {};
@@ -151,13 +253,25 @@ std::vector<uint8_t> ReadSerialFrame(QueueHandle_t queue, size_t len, uint32_t t
std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
uint32_t query_timeout_ms, const uint8_t* data,
size_t len) {
if (data == nullptr || len == 0) {
const auto packet = LegacyQueryResponse(0xFD);
LogQueryRxPacket("serial", uart_port, packet, "empty-query");
return packet;
}
if (data[0] == 0x12 && len != 3) {
const auto packet = LegacyQueryResponse(0xFD);
LogQueryRxPacket("serial", uart_port, packet, "invalid-query-len");
return packet;
}
if (data != nullptr && len > 0 && data[0] == 0x12) {
DrainSerialQueue(queue);
}
if (!WriteSerialFrame(uart_port, data, len)) {
return {0xFD};
const auto packet = LegacyQueryResponse(0xFD);
LogQueryRxPacket("serial", uart_port, packet, "write-failed");
return packet;
}
if (data == nullptr || len == 0 || data[0] != 0x12) {
if (data[0] != 0x12) {
return {0xFF};
}
@@ -171,12 +285,17 @@ std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
break;
}
auto response = PacketToVector(packet, 2);
LogQueryRxPacket("serial", uart_port, response, "raw");
if (!response.empty() &&
(response[0] == 0xFF || response[0] == 0xFE || response[0] == 0xFD)) {
return response;
const auto packet = LegacyQueryResponse(response[0], response.size() > 1 ? response[1] : 0x00);
LogQueryRxPacket("serial", uart_port, packet, "accepted");
return packet;
}
}
return {0xFE};
const auto packet = LegacyQueryResponse(0xFE);
LogQueryRxPacket("serial", uart_port, packet, "timeout");
return packet;
}
} // namespace
@@ -195,9 +314,26 @@ struct DaliDomainService::DaliChannel {
};
DaliDomainService::DaliDomainService()
: raw_frame_sink_lock_(xSemaphoreCreateMutex()) {}
: raw_frame_sink_lock_(xSemaphoreCreateMutex()),
bus_activity_lock_(xSemaphoreCreateMutex()),
host_activity_lock_(xSemaphoreCreateMutex()) {
esp_log_level_set(TAG, (esp_log_level_t)CONFIG_DALI_LOG_LEVEL);
}
DaliDomainService::~DaliDomainService() = default;
DaliDomainService::~DaliDomainService() {
if (raw_frame_sink_lock_ != nullptr) {
vSemaphoreDelete(raw_frame_sink_lock_);
raw_frame_sink_lock_ = nullptr;
}
if (bus_activity_lock_ != nullptr) {
vSemaphoreDelete(bus_activity_lock_);
bus_activity_lock_ = nullptr;
}
if (host_activity_lock_ != nullptr) {
vSemaphoreDelete(host_activity_lock_);
host_activity_lock_ = nullptr;
}
}
bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) {
if (!hooks.send) {
@@ -225,14 +361,14 @@ bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTrans
esp_err_t DaliDomainService::bindHardwareBus(const DaliHardwareBusConfig& config) {
esp_err_t err = dali_hal_set_baudrate(config.baudrate);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set baudrate=%lu: %s", static_cast<unsigned long>(config.baudrate),
ESP_LOGE(TAG, "failed to set baudrate=%lu: %s", static_cast<unsigned long>(config.baudrate),
esp_err_to_name(err));
return err;
}
err = dali_hal_init(config.bus_id, config.tx_pin, config.rx_pin);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to init bus=%u tx=%u rx=%u: %s", config.bus_id, config.tx_pin,
ESP_LOGE(TAG, "failed to init bus=%u tx=%u rx=%u: %s", config.bus_id, config.tx_pin,
config.rx_pin, esp_err_to_name(err));
return err;
}
@@ -258,9 +394,9 @@ esp_err_t DaliDomainService::bindHardwareBus(const DaliHardwareBusConfig& config
}
err = startRawFrameTask();
if (err != ESP_OK) {
ESP_LOGW(kTag, "failed to start raw frame task: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "failed to start raw frame task: %s", esp_err_to_name(err));
}
ESP_LOGI(kTag, "bound channel=%u gateway=%u hardware bus=%u tx=%u rx=%u baudrate=%lu",
ESP_LOGI(TAG, "bound channel=%u gateway=%u hardware bus=%u tx=%u rx=%u baudrate=%lu",
config.channel_index, config.gateway_id, config.bus_id, config.tx_pin, config.rx_pin,
static_cast<unsigned long>(config.baudrate));
return ESP_OK;
@@ -271,7 +407,7 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
return ESP_ERR_INVALID_ARG;
}
if (hasSerialPort(config.uart_port)) {
ESP_LOGE(kTag, "uart%d is already assigned to another DALI channel", config.uart_port);
ESP_LOGE(TAG, "uart%d is already assigned to another DALI channel", config.uart_port);
return ESP_ERR_INVALID_STATE;
}
@@ -287,26 +423,27 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
auto uart = static_cast<uart_port_t>(config.uart_port);
esp_err_t err = uart_param_config(uart, &uart_config);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err));
ESP_LOGE(TAG, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err));
return err;
}
err = uart_set_pin(uart, config.tx_pin, config.rx_pin, UART_PIN_NO_CHANGE,
UART_PIN_NO_CHANGE);
err = uart_set_pin(uart, config.tx_pin < 0 ? UART_PIN_NO_CHANGE : config.tx_pin,
config.rx_pin < 0 ? UART_PIN_NO_CHANGE : config.rx_pin,
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set uart%d pins tx=%d rx=%d: %s", config.uart_port,
ESP_LOGE(TAG, "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));
return err;
}
err = uart_driver_install(uart, config.rx_buffer_size, config.tx_buffer_size, 0, nullptr, 0);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(kTag, "failed to install uart%d driver: %s", config.uart_port, esp_err_to_name(err));
ESP_LOGE(TAG, "failed to install uart%d driver: %s", config.uart_port, esp_err_to_name(err));
return err;
}
uart_flush_input(uart);
QueueHandle_t serial_rx_queue = xQueueCreate(kSerialRxQueueDepth, sizeof(SerialRxPacket));
if (serial_rx_queue == nullptr) {
ESP_LOGE(kTag, "failed to create uart%d RX queue", config.uart_port);
ESP_LOGE(TAG, "failed to create uart%d RX queue", config.uart_port);
return ESP_ERR_NO_MEM;
}
@@ -336,12 +473,12 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
channel->serial_rx_queue = serial_rx_queue;
err = startSerialRxTask(*channel);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to start uart%d RX task: %s", config.uart_port,
ESP_LOGE(TAG, "failed to start uart%d RX task: %s", config.uart_port,
esp_err_to_name(err));
return err;
}
}
ESP_LOGI(kTag, "bound channel=%u gateway=%u serial uart%d tx=%d rx=%d baudrate=%lu",
ESP_LOGI(TAG, "bound channel=%u gateway=%u serial uart%d tx=%d rx=%d baudrate=%lu",
config.channel_index, config.gateway_id, config.uart_port, config.tx_pin, config.rx_pin,
static_cast<unsigned long>(config.baudrate));
return ESP_OK;
@@ -386,8 +523,12 @@ std::vector<DaliChannelInfo> DaliDomainService::channelInfo() const {
std::vector<DaliChannelInfo> info;
info.reserve(channels_.size());
for (const auto& channel : channels_) {
info.push_back(DaliChannelInfo{channel->config.channel_index, channel->config.gateway_id,
channel->phy_kind, channel->config.name});
DaliChannelInfo item{channel->config.channel_index, channel->config.gateway_id,
channel->phy_kind, channel->config.name};
if (channel->hardware_bus.has_value()) {
item.native_bus_id = channel->hardware_bus->bus_id;
}
info.push_back(std::move(item));
}
return info;
}
@@ -407,12 +548,112 @@ void DaliDomainService::addRawFrameSink(std::function<void(const DaliRawFrame& f
bool DaliDomainService::resetBus(uint8_t gateway_id) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->comm != nullptr && channel->comm->resetBus();
if (channel == nullptr || channel->comm == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->comm->resetBus();
}
bool DaliDomainService::isBusIdle(uint8_t gateway_id, uint32_t quiet_ms) const {
TickType_t last_activity = 0;
if (bus_activity_lock_ != nullptr) {
xSemaphoreTake(bus_activity_lock_, portMAX_DELAY);
}
if (const auto it = last_bus_activity_ticks_.find(gateway_id);
it != last_bus_activity_ticks_.end()) {
last_activity = it->second;
}
if (bus_activity_lock_ != nullptr) {
xSemaphoreGive(bus_activity_lock_);
}
if (last_activity == 0) {
return true;
}
return (xTaskGetTickCount() - last_activity) >= pdMS_TO_TICKS(quiet_ms);
}
void DaliDomainService::markHostActivity(uint8_t gateway_id) const {
if (host_activity_lock_ != nullptr) {
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
}
last_host_activity_ticks_[gateway_id] = xTaskGetTickCount();
if (host_activity_lock_ != nullptr) {
xSemaphoreGive(host_activity_lock_);
}
}
void DaliDomainService::markHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr,
uint8_t command) const {
const TickType_t now = xTaskGetTickCount();
if (host_activity_lock_ != nullptr) {
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
}
last_host_activity_ticks_[gateway_id] = now;
recent_host_command_frames_[gateway_id] = RecentHostCommandFrame{raw_addr, command, now, true};
if (host_activity_lock_ != nullptr) {
xSemaphoreGive(host_activity_lock_);
}
}
bool DaliDomainService::hasRecentHostActivity(uint8_t gateway_id, uint32_t window_ms) const {
if (window_ms == 0) {
return false;
}
TickType_t last_activity = 0;
if (host_activity_lock_ != nullptr) {
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
}
if (const auto it = last_host_activity_ticks_.find(gateway_id);
it != last_host_activity_ticks_.end()) {
last_activity = it->second;
}
if (host_activity_lock_ != nullptr) {
xSemaphoreGive(host_activity_lock_);
}
if (last_activity == 0) {
return false;
}
return (xTaskGetTickCount() - last_activity) < pdMS_TO_TICKS(window_ms);
}
bool DaliDomainService::matchesRecentHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr,
uint8_t command,
uint32_t window_ms) const {
if (window_ms == 0) {
return false;
}
RecentHostCommandFrame frame;
if (host_activity_lock_ != nullptr) {
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
}
if (const auto it = recent_host_command_frames_.find(gateway_id);
it != recent_host_command_frames_.end()) {
frame = it->second;
}
if (host_activity_lock_ != nullptr) {
xSemaphoreGive(host_activity_lock_);
}
if (!frame.valid || frame.raw_addr != raw_addr || frame.command != command) {
return false;
}
return (xTaskGetTickCount() - frame.tick) < pdMS_TO_TICKS(window_ms);
}
bool DaliDomainService::writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->hooks.send && channel->hooks.send(data, len);
if (channel == nullptr || !channel->hooks.send) {
return false;
}
if (data != nullptr && len == 3 && (data[0] == 0x10 || data[0] == 0x11)) {
markHostCommandFrame(gateway_id, data[1], data[2]);
} else {
markHostActivity(gateway_id);
}
markBusActivity(gateway_id);
return channel->hooks.send(data, len);
}
std::vector<uint8_t> DaliDomainService::transactBridgeFrame(uint8_t gateway_id,
@@ -422,17 +663,33 @@ std::vector<uint8_t> DaliDomainService::transactBridgeFrame(uint8_t gateway_id,
if (channel == nullptr || !channel->hooks.transact) {
return {};
}
if (data != nullptr && len == 3 && (data[0] == 0x10 || data[0] == 0x11 || data[0] == 0x12)) {
markHostCommandFrame(gateway_id, data[1], data[2]);
} else {
markHostActivity(gateway_id);
}
markBusActivity(gateway_id);
return channel->hooks.transact(data, len);
}
bool DaliDomainService::sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->comm != nullptr && channel->comm->sendRawNew(raw_addr, command);
if (channel == nullptr || channel->comm == nullptr) {
return false;
}
markHostCommandFrame(gateway_id, raw_addr, command);
markBusActivity(gateway_id);
return channel->comm->sendRawNew(raw_addr, command);
}
bool DaliDomainService::sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->comm != nullptr && channel->comm->sendExtRawNew(raw_addr, command);
if (channel == nullptr || channel->comm == nullptr) {
return false;
}
markHostCommandFrame(gateway_id, raw_addr, command);
markBusActivity(gateway_id);
return channel->comm->sendExtRawNew(raw_addr, command);
}
std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t raw_addr,
@@ -441,6 +698,8 @@ std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t r
if (channel == nullptr || channel->comm == nullptr) {
return std::nullopt;
}
markHostCommandFrame(gateway_id, raw_addr, command);
markBusActivity(gateway_id);
return channel->comm->queryRawNew(raw_addr, command);
}
@@ -451,6 +710,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
const std::vector<int> fallback = fallback_types.empty() ? std::vector<int>{1, 4, 5, 6, 8}
: fallback_types;
auto discovery = channel->dali->base.discoverDeviceTypes(short_address, fallback,
@@ -466,12 +726,141 @@ std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
return snapshot;
}
std::optional<DaliDomainSnapshot> DaliDomainService::baseStatusSnapshot(
uint8_t gateway_id, int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
const auto raw_status = channel->dali->base.getStatus(short_address);
if (!raw_status.has_value()) {
return std::nullopt;
}
const DaliStatus status = DaliStatus::fromByte(static_cast<uint8_t>(raw_status.value()));
auto snapshot = MakeSnapshot(gateway_id, short_address, "base_status");
snapshot.ints["rawStatus"] = raw_status.value() & 0xFF;
snapshot.bools["controlGearPresent"] = status.controlGearPresent;
snapshot.bools["lampFailure"] = status.lampFailure;
snapshot.bools["lampPowerOn"] = status.lampPowerOn;
snapshot.bools["limitError"] = status.limitError;
snapshot.bools["fadingCompleted"] = status.fadingCompleted;
snapshot.bools["resetState"] = status.resetState;
snapshot.bools["missingShortAddress"] = status.missingShortAddress;
snapshot.bools["powerSupplyFault"] = status.psFault;
snapshot.bools["psFault"] = status.psFault;
return snapshot;
}
std::optional<DaliDomainSnapshot> DaliDomainService::dt1Snapshot(uint8_t gateway_id,
int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
const auto detailed = channel->dali->dt1.getDT1TestStatusDetailed(short_address);
if (!detailed.has_value()) {
return std::nullopt;
}
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt1");
PutOptionalInt(snapshot, "failureStatusRaw", detailed->failureStatus);
PutOptionalInt(snapshot, "emergencyStatusRaw", detailed->emergencyStatus);
PutOptionalInt(snapshot, "emergencyModeRaw", detailed->emergencyMode);
PutOptionalInt(snapshot, "featuresRaw", detailed->feature);
PutOptionalInt(snapshot, "deviceStatusRaw", detailed->deviceStatus);
PutOptionalInt(snapshot, "batteryChargeLevel", detailed->batteryChargeLevel);
PutOptionalInt(snapshot, "functionTestDelayTime", detailed->functionTestDelayTime);
PutOptionalInt(snapshot, "durationTestDelayTime", detailed->durationTestDelayTime);
PutOptionalInt(snapshot, "functionTestIntervalDays", detailed->functionTestIntervalDays);
PutOptionalInt(snapshot, "durationTestIntervalWeeks", detailed->durationTestIntervalWeeks);
PutOptionalInt(snapshot, "testExecutionTimeoutDays", detailed->testExecutionTimeoutDays);
PutOptionalInt(snapshot, "prolongTimeHalfMinutes", detailed->prolongTimeHalfMinutes);
PutOptionalInt(snapshot, "durationTestResultMinutes", detailed->durationTestResultMinutes);
PutOptionalInt(snapshot, "lampEmergencyTimeHours", detailed->lampEmergencyTimeHours);
PutOptionalInt(snapshot, "lampTotalOperationTimeHours", detailed->lampTotalOperationTimeHours);
PutOptionalInt(snapshot, "emergencyLevel", detailed->emergencyLevel);
PutOptionalInt(snapshot, "emergencyMinLevel", detailed->emergencyMinLevel);
PutOptionalInt(snapshot, "emergencyMaxLevel", detailed->emergencyMaxLevel);
PutOptionalInt(snapshot, "ratedDurationMinutes", detailed->ratedDurationMinutes);
PutOptionalInt(snapshot, "extendedVersion", detailed->extendedVersion);
PutOptionalInt(snapshot, "physicalMinLevel", detailed->physicalMinLevel);
PutOptionalInt(snapshot, "emergencyDeviceTypeCode", detailed->emergencyDeviceTypeCode);
snapshot.bools["testInProgress"] = detailed->testInProgress;
snapshot.bools["lampFailure"] = detailed->lampFailure;
snapshot.bools["batteryFailure"] = detailed->batteryFailure;
snapshot.bools["functionTestActive"] = detailed->functionTestActive;
snapshot.bools["durationTestActive"] = detailed->durationTestActive;
snapshot.bools["testDone"] = detailed->testDone;
snapshot.bools["identifyActive"] = detailed->identifyActive;
snapshot.bools["physicalSelectionActive"] = detailed->physicalSelectionActive;
snapshot.bools["circuitFailure"] = detailed->circuitFailure;
snapshot.bools["batteryDurationFailure"] = detailed->batteryDurationFailure;
snapshot.bools["emergencyLampFailure"] = detailed->emergencyLampFailure;
snapshot.bools["functionTestMaxDelayExceeded"] = detailed->functionTestMaxDelayExceeded;
snapshot.bools["durationTestMaxDelayExceeded"] = detailed->durationTestMaxDelayExceeded;
snapshot.bools["functionTestFailed"] = detailed->functionTestFailed;
snapshot.bools["durationTestFailed"] = detailed->durationTestFailed;
snapshot.bools["functionTestResultValid"] = detailed->functionTestResultValid;
snapshot.bools["durationTestResultValid"] = detailed->durationTestResultValid;
snapshot.bools["batteryFullyCharged"] = detailed->batteryFullyCharged;
snapshot.bools["functionTestPending"] = detailed->functionTestPending;
snapshot.bools["durationTestPending"] = detailed->durationTestPending;
snapshot.bools["restModeActive"] = detailed->restModeActive;
snapshot.bools["normalModeActive"] = detailed->normalModeActive;
snapshot.bools["emergencyModeActive"] = detailed->emergencyModeActive;
snapshot.bools["extendedEmergencyModeActive"] = detailed->extendedEmergencyModeActive;
snapshot.bools["hardwiredInhibitActive"] = detailed->hardwiredInhibitActive;
snapshot.bools["hardwiredSwitchOn"] = detailed->hardwiredSwitchOn;
snapshot.bools["supportsAutoTest"] = detailed->supportsAutoTest;
snapshot.bools["supportsAdjustableEmergencyLevel"] = detailed->supportsAdjustableEmergencyLevel;
if (detailed->emergencyStatus.has_value()) {
const DaliDT1EmergencyStatus status(detailed->emergencyStatus.value());
snapshot.bools["inhibitMode"] = status.inhibitMode();
snapshot.bools["functionTestRequestPending"] = status.functionTestRequestPending();
snapshot.bools["durationTestRequestPending"] = status.durationTestRequestPending();
snapshot.bools["identificationActive"] = status.identificationActive();
snapshot.bools["physicallySelected"] = status.physicallySelected();
}
if (detailed->emergencyMode.has_value()) {
const DaliDT1EmergencyMode mode(detailed->emergencyMode.value());
snapshot.bools["functionTestInProgress"] = mode.functionTestInProgress();
snapshot.bools["durationTestInProgress"] = mode.durationTestInProgress();
}
if (detailed->feature.has_value()) {
const DaliDT1Features features(detailed->feature.value());
snapshot.bools["integralEmergencyControlGear"] = features.integralEmergencyControlGear();
snapshot.bools["maintainedControlGear"] = features.maintainedControlGear();
snapshot.bools["switchedMaintainedControlGear"] = features.switchedMaintainedControlGear();
snapshot.bools["autoTestCapability"] = features.autoTestCapability();
snapshot.bools["adjustableEmergencyLevel"] = features.adjustableEmergencyLevel();
snapshot.bools["hardwiredInhibitSupported"] = features.hardwiredInhibitSupported();
snapshot.bools["physicalSelectionSupported"] = features.physicalSelectionSupported();
snapshot.bools["relightInRestModeSupported"] = features.relightInRestModeSupported();
snapshot.ints["derivedEmergencyDeviceTypeCode"] =
features.emergencyDeviceTypeCode(detailed->physicalMinLevel);
}
if (detailed->deviceStatus.has_value()) {
const DaliDT1DeviceStatus status(detailed->deviceStatus.value());
snapshot.bools["controlGearFailure"] = status.controlGearFailure();
snapshot.bools["controlGearOk"] = status.controlGearOk();
snapshot.bools["lampPoweredByEmergencyGear"] = status.lampPoweredByEmergencyGear();
snapshot.bools["deviceResetState"] = status.resetState();
snapshot.bools["deviceMissingShortAddress"] = status.missingShortAddress();
}
return snapshot;
}
std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway_id,
int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt4");
auto& dt4 = channel->dali->dt4;
PutOptionalInt(snapshot, "extendedVersion", dt4.getExtendedVersion(short_address));
@@ -506,15 +895,27 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway
snapshot.ints["featuresRaw2"] = features->raw2();
snapshot.ints["featuresRaw3"] = features->raw3();
snapshot.ints["dimmingMethodCode"] = features->dimmingMethodCode();
snapshot.bools["canQueryLoadOverCurrentShutdown"] =
features->canQueryLoadOverCurrentShutdown();
snapshot.bools["canQueryOpenCircuitDetection"] = features->canQueryOpenCircuitDetection();
snapshot.bools["canQueryLoadDecrease"] = features->canQueryLoadDecrease();
snapshot.bools["canQueryLoadIncrease"] = features->canQueryLoadIncrease();
snapshot.bools["canQueryThermalShutdown"] = features->canQueryThermalShutdown();
snapshot.bools["canQueryThermalOverloadReduction"] =
features->canQueryThermalOverloadReduction();
snapshot.bools["canQueryTemperature"] = features->canQueryTemperature();
snapshot.bools["canQuerySupplyVoltage"] = features->canQuerySupplyVoltage();
snapshot.bools["canQuerySupplyFrequency"] = features->canQuerySupplyFrequency();
snapshot.bools["canQueryLoadVoltage"] = features->canQueryLoadVoltage();
snapshot.bools["canQueryLoadCurrent"] = features->canQueryLoadCurrent();
snapshot.bools["canQueryRealLoadPower"] = features->canQueryRealLoadPower();
snapshot.bools["canQueryLoadRating"] = features->canQueryLoadRating();
snapshot.bools["canQueryCurrentOverloadReduction"] =
features->canQueryCurrentOverloadReduction();
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
snapshot.bools["canSelectNonLogarithmicDimmingCurve"] =
features->canSelectNonLogarithmicDimmingCurve();
snapshot.bools["canQueryUnsuitableLoad"] = features->canQueryUnsuitableLoad();
}
if (const auto failure = dt4.getFailureStatus(short_address)) {
snapshot.ints["failureRaw1"] = failure->raw1();
@@ -526,7 +927,10 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
snapshot.bools["referenceMeasurementFailedStatus"] = failure->referenceMeasurementFailed();
snapshot.bools["loadUnsuitableForSelectedMethod"] =
failure->loadUnsuitableForSelectedMethod();
snapshot.bools["supplyVoltageOutOfLimits"] = failure->supplyVoltageOutOfLimits();
snapshot.bools["supplyFrequencyOutOfLimits"] = failure->supplyFrequencyOutOfLimits();
snapshot.bools["loadVoltageOutOfLimits"] = failure->loadVoltageOutOfLimits();
snapshot.bools["loadCurrentOverloadReduction"] = failure->loadCurrentOverloadReduction();
}
@@ -539,6 +943,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt5Snapshot(uint8_t gateway
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt5");
auto& dt5 = channel->dali->dt5;
PutOptionalInt(snapshot, "extendedVersion", dt5.getExtendedVersion(short_address));
@@ -580,6 +985,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt6Snapshot(uint8_t gateway
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt6");
auto& dt6 = channel->dali->dt6;
PutOptionalInt(snapshot, "extendedVersion", dt6.getExtendedVersion(short_address));
@@ -637,12 +1043,48 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt6Snapshot(uint8_t gateway
return snapshot;
}
std::optional<DaliDomainSnapshot> DaliDomainService::dt8StatusSnapshot(
uint8_t gateway_id, int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_status");
bool has_data = false;
if (const auto status = channel->dali->dt8.getColorStatus(short_address)) {
has_data = true;
snapshot.ints["colorStatusRaw"] = status->raw();
snapshot.bools["xyOutOfRange"] = status->xyOutOfRange();
snapshot.bools["ctOutOfRange"] = status->ctOutOfRange();
snapshot.bools["autoCalibrationActive"] = status->autoCalibrationActive();
snapshot.bools["autoCalibrationSuccess"] = status->autoCalibrationSuccess();
snapshot.bools["xyActive"] = status->xyActive();
snapshot.bools["ctActive"] = status->ctActive();
snapshot.bools["primaryNActive"] = status->primaryNActive();
snapshot.bools["rgbwafActive"] = status->rgbwafActive();
}
if (const auto features = channel->dali->dt8.getColorTypeFeature(short_address)) {
has_data = true;
snapshot.ints["colorTypeFeaturesRaw"] = features->features();
snapshot.ints["primaryCount"] = features->primaryCount();
snapshot.ints["rgbwafChannels"] = features->rgbwafChannels();
snapshot.bools["xyCapable"] = features->xyCapable();
snapshot.bools["ctCapable"] = features->ctCapable();
snapshot.bools["primaryNCapable"] = features->primaryNCapable();
snapshot.bools["rgbwafCapable"] = features->rgbwafCapable();
}
return has_data ? std::optional<DaliDomainSnapshot>(std::move(snapshot)) : std::nullopt;
}
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
uint8_t gateway_id, int short_address, int scene) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
const auto report = channel->dali->dt8.getSceneColorReport(short_address, scene);
if (!report.has_value()) {
return std::nullopt;
@@ -666,6 +1108,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8PowerOnLevelColorReport(
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
const auto report = channel->dali->dt8.getPowerOnLevelColorReport(short_address);
if (!report.has_value()) {
return std::nullopt;
@@ -688,6 +1131,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8SystemFailureLevelColorR
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
const auto report = channel->dali->dt8.getSystemFailureLevelColorReport(short_address);
if (!report.has_value()) {
return std::nullopt;
@@ -710,8 +1154,11 @@ bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_addr
int color_temperature, int red, int green,
int blue) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.storeSceneSnapshot(short_address, scene, brightness,
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->dt8.storeSceneSnapshot(short_address, scene, brightness,
ToDaliCppColorMode(color_mode), color_temperature,
red, green, blue);
}
@@ -719,56 +1166,85 @@ bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_addr
bool DaliDomainService::storeDt8PowerOnLevelSnapshot(uint8_t gateway_id, int short_address,
int level) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.storePowerOnLevelSnapshot(short_address, level);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->dt8.storePowerOnLevelSnapshot(short_address, level);
}
bool DaliDomainService::storeDt8SystemFailureLevelSnapshot(uint8_t gateway_id,
int short_address, int level) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.storeSystemFailureLevelSnapshot(short_address, level);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->dt8.storeSystemFailureLevelSnapshot(short_address, level);
}
bool DaliDomainService::setBright(uint8_t gateway_id, int short_address, int brightness) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->base.setBright(short_address, brightness);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->base.setBright(short_address, brightness);
}
bool DaliDomainService::setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColTempRaw(short_address, mirek);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->dt8.setColTempRaw(short_address, mirek);
}
bool DaliDomainService::setColTemp(uint8_t gateway_id, int short_address, int kelvin) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColorTemperature(short_address, kelvin);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->dt8.setColorTemperature(short_address, kelvin);
}
bool DaliDomainService::setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColourRaw(raw_addr, x, y);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->dt8.setColourRaw(raw_addr, x, y);
}
bool DaliDomainService::setColourRGB(uint8_t gateway_id, int short_address, int r, int g,
int b) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->dt8.setColourRGB(short_address, r, g, b);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->dt8.setColourRGB(short_address, r, g, b);
}
bool DaliDomainService::on(uint8_t gateway_id, int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr && channel->dali->base.on(short_address);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->base.on(short_address);
}
bool DaliDomainService::off(uint8_t gateway_id, int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr && channel->dali->base.off(short_address);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->base.off(short_address);
}
bool DaliDomainService::off(int short_address) const {
@@ -779,6 +1255,20 @@ bool DaliDomainService::off(int short_address) const {
return off(channels_.front()->config.gateway_id, short_address);
}
std::optional<uint8_t> DaliDomainService::queryActualLevel(uint8_t gateway_id,
int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
markBusActivity(gateway_id);
const auto level = channel->dali->base.getBright(short_address);
if (!level.has_value()) {
return std::nullopt;
}
return static_cast<uint8_t>(std::clamp(*level, 0, 254));
}
std::optional<uint16_t> DaliDomainService::queryGroupMask(uint8_t gateway_id,
int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
@@ -786,6 +1276,8 @@ std::optional<uint16_t> DaliDomainService::queryGroupMask(uint8_t gateway_id,
return std::nullopt;
}
markBusActivity(gateway_id);
const auto group_mask = channel->dali->base.getGroup(short_address);
if (!group_mask.has_value()) {
return std::nullopt;
@@ -801,6 +1293,8 @@ std::optional<uint8_t> DaliDomainService::querySceneLevel(uint8_t gateway_id, in
return std::nullopt;
}
markBusActivity(gateway_id);
const auto level = channel->dali->base.getScene(short_address, scene);
if (!level.has_value()) {
return std::nullopt;
@@ -816,11 +1310,15 @@ std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettin
return std::nullopt;
}
markBusActivity(gateway_id);
DaliAddressSettingsSnapshot settings{};
if (const auto value = channel->dali->base.getPowerOnLevel(short_address); value.has_value()) {
settings.power_on_level = static_cast<uint8_t>(*value);
const auto power_on_level = channel->dali->base.getPowerOnLevel(short_address);
if (!power_on_level.has_value()) {
return std::nullopt;
}
settings.power_on_level = static_cast<uint8_t>(*power_on_level);
if (const auto value = channel->dali->base.getSystemFailureLevel(short_address);
value.has_value()) {
settings.system_failure_level = static_cast<uint8_t>(*value);
@@ -848,8 +1346,11 @@ std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettin
bool DaliDomainService::applyGroupMask(uint8_t gateway_id, int short_address,
uint16_t group_mask) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->base.setGroup(short_address, group_mask);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
markBusActivity(gateway_id);
return channel->dali->base.setGroup(short_address, group_mask);
}
bool DaliDomainService::applySceneLevel(uint8_t gateway_id, int short_address, int scene,
@@ -859,6 +1360,8 @@ bool DaliDomainService::applySceneLevel(uint8_t gateway_id, int short_address, i
return false;
}
markBusActivity(gateway_id);
if (*level == 255U) {
return channel->dali->base.removeScene(short_address, scene);
}
@@ -874,6 +1377,8 @@ bool DaliDomainService::applyAddressSettings(uint8_t gateway_id, int short_addre
return false;
}
markBusActivity(gateway_id);
bool ok = true;
if (settings.power_on_level.has_value()) {
ok = ok && channel->dali->base.setPowerOnLevel(short_address, *settings.power_on_level);
@@ -1059,7 +1564,7 @@ void DaliDomainService::rawFrameTaskLoop() {
Dali_msg_t message = {};
while (true) {
if (queue == nullptr) {
vTaskDelay(pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(10));
queue = dali_hal_raw_receive_queue();
continue;
}
@@ -1077,6 +1582,12 @@ void DaliDomainService::rawFrameTaskLoop() {
if (byte_count > DALI_MAX_BYTES) {
byte_count = DALI_MAX_BYTES;
}
if (byte_count == 1 && TakeHardwareQueryRawSuppress(message.id)) {
continue;
}
if (byte_count != 1 && byte_count != 2 && byte_count != 3) {
continue;
}
DaliRawFrame frame;
frame.channel_index = channel->config.channel_index;
frame.gateway_id = channel->config.gateway_id;
@@ -1087,6 +1598,7 @@ void DaliDomainService::rawFrameTaskLoop() {
}
void DaliDomainService::notifyRawFrameSinks(const DaliRawFrame& frame) {
markBusActivity(frame.gateway_id);
if (raw_frame_sink_lock_ != nullptr) {
xSemaphoreTake(raw_frame_sink_lock_, portMAX_DELAY);
}
@@ -1099,6 +1611,16 @@ void DaliDomainService::notifyRawFrameSinks(const DaliRawFrame& frame) {
}
}
void DaliDomainService::markBusActivity(uint8_t gateway_id) const {
if (bus_activity_lock_ != nullptr) {
xSemaphoreTake(bus_activity_lock_, portMAX_DELAY);
}
last_bus_activity_ticks_[gateway_id] = xTaskGetTickCount();
if (bus_activity_lock_ != nullptr) {
xSemaphoreGive(bus_activity_lock_);
}
}
bool DaliDomainService::hasSerialPort(int uart_port) const {
return std::any_of(channels_.begin(), channels_.end(), [uart_port](const auto& channel) {
return channel->serial_bus.has_value() && channel->serial_bus->uart_port == uart_port;
@@ -0,0 +1,7 @@
idf_component_register(
SRCS "src/gateway_485_control.cpp"
INCLUDE_DIRS "include"
REQUIRES esp_driver_uart freertos gateway_controller log
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -0,0 +1,47 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <vector>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
namespace gateway {
class GatewayController;
struct Gateway485ControlBridgeConfig {
bool enabled{false};
int tx_pin{-1};
int rx_pin{-1};
uint32_t baudrate{9600};
size_t rx_buffer_size{256};
size_t tx_buffer_size{256};
uint32_t read_timeout_ms{20};
uint32_t write_timeout_ms{20};
uint32_t task_stack_size{4096};
UBaseType_t task_priority{4};
};
class Gateway485ControlBridge {
public:
Gateway485ControlBridge(GatewayController& controller,
Gateway485ControlBridgeConfig config = {});
esp_err_t start();
private:
static void TaskEntry(void* arg);
void taskLoop();
void handleBytes(const uint8_t* data, size_t len);
void handleGatewayNotification(const std::vector<uint8_t>& frame);
GatewayController& controller_;
Gateway485ControlBridgeConfig config_;
TaskHandle_t task_handle_{nullptr};
bool started_{false};
};
} // namespace gateway
@@ -0,0 +1,134 @@
#include "gateway_485_control.hpp"
#include "gateway_controller.hpp"
#include "driver/uart.h"
#include "esp_log.h"
#include <algorithm>
namespace gateway {
namespace {
constexpr const char* kTag = "gateway_485";
constexpr uart_port_t kControlUart = UART_NUM_0;
constexpr size_t kCommandFrameMinLen = 7;
int EffectivePin(int pin) {
return pin >= 0 ? pin : UART_PIN_NO_CHANGE;
}
} // namespace
Gateway485ControlBridge::Gateway485ControlBridge(GatewayController& controller,
Gateway485ControlBridgeConfig config)
: controller_(controller), config_(config) {}
esp_err_t Gateway485ControlBridge::start() {
if (started_) {
return ESP_OK;
}
if (!config_.enabled) {
ESP_LOGI(kTag, "UART0 control bridge disabled");
return ESP_OK;
}
if (uart_is_driver_installed(kControlUart)) {
ESP_LOGE(kTag, "UART0 driver already installed; move console or other users off UART0");
return ESP_ERR_INVALID_STATE;
}
uart_config_t uart_config{};
uart_config.baud_rate = static_cast<int>(config_.baudrate);
uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_config.source_clk = UART_SCLK_DEFAULT;
esp_err_t err = uart_param_config(kControlUart, &uart_config);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to configure UART0: %s", esp_err_to_name(err));
return err;
}
err = uart_set_pin(kControlUart, EffectivePin(config_.tx_pin), EffectivePin(config_.rx_pin),
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to set UART0 pins: %s", esp_err_to_name(err));
return err;
}
err = uart_driver_install(kControlUart, static_cast<int>(config_.rx_buffer_size),
static_cast<int>(config_.tx_buffer_size), 0, nullptr, 0);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to install UART0 driver: %s", esp_err_to_name(err));
return err;
}
controller_.addNotificationSink(
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
const BaseType_t created = xTaskCreate(&Gateway485ControlBridge::TaskEntry,
"gateway_485_ctrl",
static_cast<uint32_t>(config_.task_stack_size), this,
config_.task_priority, &task_handle_);
if (created != pdPASS) {
uart_driver_delete(kControlUart);
task_handle_ = nullptr;
ESP_LOGE(kTag, "failed to create 485 control task");
return ESP_ERR_NO_MEM;
}
started_ = true;
ESP_LOGI(kTag, "485 control bridge started baud=%lu", static_cast<unsigned long>(config_.baudrate));
return ESP_OK;
}
void Gateway485ControlBridge::TaskEntry(void* arg) {
static_cast<Gateway485ControlBridge*>(arg)->taskLoop();
}
void Gateway485ControlBridge::taskLoop() {
std::vector<uint8_t> read_buffer(std::max<size_t>(config_.rx_buffer_size, 64));
std::vector<uint8_t> pending;
pending.reserve(std::max<size_t>(config_.rx_buffer_size, 64));
const TickType_t timeout = pdMS_TO_TICKS(config_.read_timeout_ms);
while (true) {
const int read_len = uart_read_bytes(kControlUart, read_buffer.data(), read_buffer.size(), timeout);
if (read_len > 0) {
pending.insert(pending.end(), read_buffer.begin(), read_buffer.begin() + read_len);
continue;
}
if (!pending.empty()) {
handleBytes(pending.data(), pending.size());
pending.clear();
}
}
}
void Gateway485ControlBridge::handleBytes(const uint8_t* data, size_t len) {
if (data == nullptr || len < kCommandFrameMinLen) {
return;
}
if (data[0] != 0x28 || data[1] != 0x01) {
ESP_LOGD(kTag, "ignored non-gateway UART0 burst len=%u", static_cast<unsigned>(len));
return;
}
controller_.enqueueCommandFrame(std::vector<uint8_t>(data, data + len));
}
void Gateway485ControlBridge::handleGatewayNotification(const std::vector<uint8_t>& frame) {
if (!started_ || frame.empty()) {
return;
}
const int written = uart_write_bytes(kControlUart, frame.data(), frame.size());
if (written < 0 || static_cast<size_t>(written) != frame.size()) {
ESP_LOGW(kTag, "failed to write UART0 notification len=%u", static_cast<unsigned>(frame.size()));
return;
}
if (uart_wait_tx_done(kControlUart, pdMS_TO_TICKS(config_.write_timeout_ms)) != ESP_OK) {
ESP_LOGW(kTag, "timed out flushing UART0 notification len=%u", static_cast<unsigned>(frame.size()));
}
}
} // namespace gateway
+2 -1
View File
@@ -6,7 +6,7 @@ if(NOT CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
return()
endif()
set(BACNET_STACK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../bacnet-stack")
set(BACNET_STACK_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../bacnet_stack")
set(BACNET_SRC_ROOT "${BACNET_STACK_ROOT}/src")
set(BACNET_ESP32_PORT "${BACNET_STACK_ROOT}/ports/esp32/src")
idf_build_get_property(IDF_PATH IDF_PATH)
@@ -92,6 +92,7 @@ set(BACNET_PORT_SRCS
idf_component_register(
SRCS
"src/gateway_bacnet_bridge.cpp"
"src/gateway_bacnet.cpp"
"src/gateway_bacnet_stack_port.c"
"src/bip_socket_lwip.cpp"
@@ -9,6 +9,7 @@
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <vector>
@@ -32,6 +33,7 @@ struct GatewayBacnetObjectBinding {
std::string property{"presentValue"};
bool out_of_service{false};
uint32_t reliability{0};
bool readable{false};
};
struct GatewayBacnetServerStatus {
@@ -46,13 +48,19 @@ using GatewayBacnetWriteCallback =
std::function<bool(BridgeObjectType object_type, uint32_t object_instance,
const std::string& property, const DaliValue& value)>;
using GatewayBacnetReadCallback =
std::function<std::optional<DaliValue>(BridgeObjectType object_type,
uint32_t object_instance,
const std::string& property)>;
class GatewayBacnetServer {
public:
static GatewayBacnetServer& instance();
esp_err_t registerChannel(uint8_t gateway_id, const GatewayBacnetServerConfig& config,
std::vector<GatewayBacnetObjectBinding> bindings,
GatewayBacnetWriteCallback write_callback);
GatewayBacnetWriteCallback write_callback,
GatewayBacnetReadCallback read_callback = nullptr);
GatewayBacnetServerStatus status() const;
bool configCompatible(const GatewayBacnetServerConfig& config) const;
bool handleWrite(BridgeObjectType object_type, uint32_t object_instance,
@@ -70,6 +78,7 @@ class GatewayBacnetServer {
esp_err_t startStackLocked(const GatewayBacnetServerConfig& config);
esp_err_t rebuildObjectsLocked();
void refreshPresentValues();
static void TaskEntry(void* arg);
void taskLoop();
@@ -0,0 +1,56 @@
#pragma once
#include "bridge.hpp"
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace gateway {
struct GatewayBacnetBridgeConfig {
uint32_t deviceInstance{4194303};
std::string localAddress;
uint16_t udpPort{47808};
};
struct GatewayBacnetModelBinding {
std::string modelID;
BridgeObjectType objectType{BridgeObjectType::unknown};
int objectInstance{-1};
std::string property;
BridgeOperation operation{BridgeOperation::unknown};
BridgeDaliTarget target;
std::optional<int> bitIndex;
};
class GatewayBacnetBridgeAdapter {
public:
explicit GatewayBacnetBridgeAdapter(DaliBridgeEngine& engine);
void setConfig(const GatewayBacnetBridgeConfig& config);
const GatewayBacnetBridgeConfig& config() const;
DaliBridgeResult handlePropertyWrite(BridgeObjectType object_type,
int object_instance,
const std::string& property,
const DaliValue& value) const;
DaliBridgeResult readProperty(BridgeObjectType object_type,
int object_instance,
const std::string& property) const;
std::optional<GatewayBacnetModelBinding> findObject(BridgeObjectType object_type,
int object_instance,
const std::string& property) const;
std::vector<GatewayBacnetModelBinding> describeObjects() const;
private:
DaliBridgeResult executeBinding(const GatewayBacnetModelBinding& binding,
const std::string& sequence,
const DaliValue* value) const;
DaliBridgeEngine& engine_;
GatewayBacnetBridgeConfig config_;
};
} // namespace gateway
@@ -52,9 +52,20 @@ bool gateway_bacnet_stack_upsert_object(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
const char* object_name,
const char* description,
bool out_of_service,
uint32_t reliability);
const char* description,
bool out_of_service,
uint32_t reliability);
bool gateway_bacnet_stack_set_object_state(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
bool out_of_service,
uint32_t reliability);
bool gateway_bacnet_stack_set_present_value(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
const gateway_bacnet_write_value_t* value);
bool gateway_bacnet_stack_clear_objects(void);
@@ -18,6 +18,8 @@ namespace {
constexpr const char* kTag = "gateway_bacnet";
constexpr TickType_t kPollDelayTicks = pdMS_TO_TICKS(10);
constexpr TickType_t kValueRefreshTicks = pdMS_TO_TICKS(2000);
constexpr uint32_t kReliabilityCommunicationFailure = 12;
class LockGuard {
public:
@@ -125,6 +127,59 @@ DaliValue StackWriteValueToDali(const gateway_bacnet_write_value_t& value) {
}
}
bool DaliValueToStackPresentValue(BridgeObjectType object_type, const DaliValue& value,
gateway_bacnet_write_value_t* out) {
if (out == nullptr) {
return false;
}
switch (ToBacnetKind(object_type)) {
case GW_BACNET_OBJECT_ANALOG_INPUT:
case GW_BACNET_OBJECT_ANALOG_VALUE:
case GW_BACNET_OBJECT_ANALOG_OUTPUT: {
const auto parsed = value.asDouble();
if (!parsed.has_value()) {
return false;
}
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_REAL,
parsed.value(),
false,
0};
return true;
}
case GW_BACNET_OBJECT_BINARY_INPUT:
case GW_BACNET_OBJECT_BINARY_VALUE:
case GW_BACNET_OBJECT_BINARY_OUTPUT: {
const auto parsed = value.asBool();
if (!parsed.has_value()) {
return false;
}
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_BOOLEAN,
0.0,
parsed.value(),
0};
return true;
}
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT: {
const auto parsed = value.asInt();
if (!parsed.has_value()) {
return false;
}
const uint32_t unsigned_value = parsed.value() <= 0
? 1
: static_cast<uint32_t>(parsed.value());
*out = gateway_bacnet_write_value_t{GW_BACNET_WRITE_VALUE_UNSIGNED,
0.0,
false,
unsigned_value};
return true;
}
default:
return false;
}
}
void HandleStackWrite(gateway_bacnet_object_kind_t object_kind, uint32_t object_instance,
const gateway_bacnet_write_value_t* value, void*) {
if (g_server == nullptr || value == nullptr) {
@@ -140,6 +195,7 @@ struct GatewayBacnetServer::ChannelRegistration {
GatewayBacnetServerConfig config;
std::vector<GatewayBacnetObjectBinding> bindings;
GatewayBacnetWriteCallback write_callback;
GatewayBacnetReadCallback read_callback;
};
struct GatewayBacnetServer::RuntimeBinding {
@@ -150,7 +206,9 @@ struct GatewayBacnetServer::RuntimeBinding {
std::string property{"presentValue"};
bool out_of_service{false};
uint32_t reliability{0};
bool readable{false};
GatewayBacnetWriteCallback write_callback;
GatewayBacnetReadCallback read_callback;
};
GatewayBacnetServer& GatewayBacnetServer::instance() {
@@ -187,7 +245,8 @@ GatewayBacnetServerStatus GatewayBacnetServer::status() const {
esp_err_t GatewayBacnetServer::registerChannel(
uint8_t gateway_id, const GatewayBacnetServerConfig& config,
std::vector<GatewayBacnetObjectBinding> bindings,
GatewayBacnetWriteCallback write_callback) {
GatewayBacnetWriteCallback write_callback,
GatewayBacnetReadCallback read_callback) {
if (write_callback == nullptr) {
return ESP_ERR_INVALID_ARG;
}
@@ -210,7 +269,7 @@ esp_err_t GatewayBacnetServer::registerChannel(
return ESP_ERR_NOT_FOUND;
}
ChannelRegistration registration{gateway_id, config, std::move(bindings),
std::move(write_callback)};
std::move(write_callback), std::move(read_callback)};
if (channel == channels_.end()) {
channels_.push_back(std::move(registration));
} else {
@@ -297,7 +356,9 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() {
: binding.property,
binding.out_of_service,
binding.reliability,
channel.write_callback});
binding.readable,
channel.write_callback,
channel.read_callback});
}
}
@@ -338,12 +399,47 @@ bool GatewayBacnetServer::handleWrite(BridgeObjectType object_type, uint32_t obj
return ok;
}
void GatewayBacnetServer::refreshPresentValues() {
std::vector<RuntimeBinding> bindings;
{
LockGuard guard(lock_);
bindings = runtime_bindings_;
}
for (const auto& binding : bindings) {
const auto object_kind = ToBacnetKind(binding.object_type);
if (object_kind == GW_BACNET_OBJECT_UNKNOWN) {
continue;
}
if (!binding.readable || binding.read_callback == nullptr) {
LockGuard guard(lock_);
gateway_bacnet_stack_set_object_state(object_kind, binding.object_instance,
binding.out_of_service, binding.reliability);
continue;
}
gateway_bacnet_write_value_t stack_value = {};
const auto value = binding.read_callback(binding.object_type, binding.object_instance,
binding.property);
const bool converted = value.has_value() &&
DaliValueToStackPresentValue(binding.object_type, value.value(),
&stack_value);
LockGuard guard(lock_);
const bool ok = converted && gateway_bacnet_stack_set_present_value(
object_kind, binding.object_instance, &stack_value);
gateway_bacnet_stack_set_object_state(
object_kind, binding.object_instance, binding.out_of_service || !ok,
ok ? binding.reliability : kReliabilityCommunicationFailure);
}
}
void GatewayBacnetServer::TaskEntry(void* arg) {
static_cast<GatewayBacnetServer*>(arg)->taskLoop();
}
void GatewayBacnetServer::taskLoop() {
TickType_t last_timer = xTaskGetTickCount();
TickType_t last_refresh = last_timer;
while (true) {
const TickType_t now = xTaskGetTickCount();
@@ -353,9 +449,17 @@ void GatewayBacnetServer::taskLoop() {
elapsed_ms = static_cast<uint16_t>(elapsed * portTICK_PERIOD_MS);
last_timer = now;
}
bool refresh_due = false;
{
LockGuard guard(lock_);
gateway_bacnet_stack_poll(elapsed_ms);
if ((now - last_refresh) >= kValueRefreshTicks) {
refresh_due = true;
}
}
if (refresh_due) {
refreshPresentValues();
last_refresh = now;
}
vTaskDelay(kPollDelayTicks);
}
@@ -0,0 +1,101 @@
#include "gateway_bacnet_bridge.hpp"
#include <utility>
namespace gateway {
GatewayBacnetBridgeAdapter::GatewayBacnetBridgeAdapter(DaliBridgeEngine& engine)
: engine_(engine) {}
void GatewayBacnetBridgeAdapter::setConfig(const GatewayBacnetBridgeConfig& config) {
config_ = config;
}
const GatewayBacnetBridgeConfig& GatewayBacnetBridgeAdapter::config() const { return config_; }
DaliBridgeResult GatewayBacnetBridgeAdapter::handlePropertyWrite(
BridgeObjectType object_type, int object_instance, const std::string& property,
const DaliValue& value) const {
const auto binding = findObject(object_type, object_instance, property);
if (!binding.has_value()) {
DaliBridgeResult result;
result.sequence = "bacnet-" + std::to_string(object_instance);
result.error = "unmapped bacnet object";
return result;
}
const std::string sequence = "bacnet-" + std::to_string(object_instance);
return executeBinding(binding.value(), sequence, &value);
}
DaliBridgeResult GatewayBacnetBridgeAdapter::readProperty(BridgeObjectType object_type,
int object_instance,
const std::string& property) const {
const auto binding = findObject(object_type, object_instance, property);
if (!binding.has_value()) {
DaliBridgeResult result;
result.sequence = "bacnet-read-" + std::to_string(object_instance);
result.error = "unmapped bacnet object";
return result;
}
const std::string sequence = "bacnet-read-" + std::to_string(object_instance);
return executeBinding(binding.value(), sequence, nullptr);
}
std::optional<GatewayBacnetModelBinding> GatewayBacnetBridgeAdapter::findObject(
BridgeObjectType object_type, int object_instance, const std::string& property) const {
for (const auto& model : engine_.listModels()) {
if (model.protocol != BridgeProtocolKind::bacnet) {
continue;
}
if (model.external.objectType != object_type) {
continue;
}
if (model.external.objectInstance.value_or(-1) != object_instance) {
continue;
}
if (!model.external.property.empty() && model.external.property != property) {
continue;
}
const std::string binding_property = model.external.property.empty() ? property
: model.external.property;
return GatewayBacnetModelBinding{model.id,
object_type,
object_instance,
binding_property,
model.operation,
model.dali,
model.external.bitIndex};
}
return std::nullopt;
}
std::vector<GatewayBacnetModelBinding> GatewayBacnetBridgeAdapter::describeObjects() const {
std::vector<GatewayBacnetModelBinding> bindings;
for (const auto& model : engine_.listModels()) {
if (model.protocol != BridgeProtocolKind::bacnet || !model.external.objectInstance.has_value()) {
continue;
}
bindings.push_back(GatewayBacnetModelBinding{model.id,
model.external.objectType,
model.external.objectInstance.value(),
model.external.property,
model.operation,
model.dali,
model.external.bitIndex});
}
return bindings;
}
DaliBridgeResult GatewayBacnetBridgeAdapter::executeBinding(
const GatewayBacnetModelBinding& binding, const std::string& sequence,
const DaliValue* value) const {
DaliBridgeRequest request;
request.sequence = sequence;
request.modelID = binding.modelID;
if (value != nullptr) {
request.value = *value;
}
return engine_.execute(request);
}
} // namespace gateway
@@ -522,6 +522,91 @@ bool gateway_bacnet_stack_upsert_object(
}
}
bool gateway_bacnet_stack_set_object_state(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
bool out_of_service,
uint32_t reliability)
{
BACNET_RELIABILITY object_reliability = (BACNET_RELIABILITY)reliability;
switch (object_kind) {
case GW_BACNET_OBJECT_ANALOG_VALUE:
set_analog_value_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
set_analog_output_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_BINARY_VALUE:
set_binary_value_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_BINARY_OUTPUT:
set_binary_output_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
set_multistate_value_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_ANALOG_INPUT:
set_analog_input_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_BINARY_INPUT:
set_binary_input_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
set_multistate_input_state(object_instance, out_of_service, object_reliability);
return true;
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
set_multistate_output_state(object_instance, out_of_service, object_reliability);
return true;
default:
return false;
}
}
bool gateway_bacnet_stack_set_present_value(
gateway_bacnet_object_kind_t object_kind,
uint32_t object_instance,
const gateway_bacnet_write_value_t* value)
{
if (!value) {
return false;
}
switch (object_kind) {
case GW_BACNET_OBJECT_ANALOG_VALUE:
return Analog_Value_Present_Value_Set(
object_instance, (float)value->real_value, BACNET_NO_PRIORITY);
case GW_BACNET_OBJECT_ANALOG_OUTPUT:
return Analog_Output_Present_Value_Set(
object_instance, (float)value->real_value, BACNET_MAX_PRIORITY);
case GW_BACNET_OBJECT_ANALOG_INPUT:
Analog_Input_Present_Value_Set(object_instance, (float)value->real_value);
return true;
case GW_BACNET_OBJECT_BINARY_VALUE:
return Binary_Value_Present_Value_Set(
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE);
case GW_BACNET_OBJECT_BINARY_OUTPUT:
return Binary_Output_Present_Value_Set(
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE,
BACNET_MAX_PRIORITY);
case GW_BACNET_OBJECT_BINARY_INPUT:
return Binary_Input_Present_Value_Set(
object_instance, value->boolean_value ? BINARY_ACTIVE : BINARY_INACTIVE);
case GW_BACNET_OBJECT_MULTI_STATE_VALUE:
return Multistate_Value_Present_Value_Set(
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value);
case GW_BACNET_OBJECT_MULTI_STATE_OUTPUT:
return Multistate_Output_Present_Value_Set(
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value,
BACNET_MAX_PRIORITY);
case GW_BACNET_OBJECT_MULTI_STATE_INPUT:
return Multistate_Input_Present_Value_Set(
object_instance, value->unsigned_value == 0 ? 1 : value->unsigned_value);
default:
return false;
}
}
bool gateway_bacnet_stack_clear_objects(void)
{
return clear_analog_value_objects() &&
+43 -3
View File
@@ -4,6 +4,9 @@
#include "gateway_controller.hpp"
#include "gateway_runtime.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "host/ble_gap.h"
@@ -29,6 +32,8 @@ constexpr uint16_t kChannel2Uuid = 0xFFF2;
constexpr uint16_t kGatewayUuid = 0xFFF3;
constexpr int64_t kGenericDedupeWindowUs = 120000;
constexpr size_t kGatewayCharacteristicIndex = 2;
constexpr int kGatewayNotifyAllocationAttempts = 6;
constexpr TickType_t kGatewayNotifyRetryDelayTicks = pdMS_TO_TICKS(20);
gateway::GatewayBleBridge* s_active_bridge = nullptr;
uint16_t s_value_handles[3] = {0, 0, 0};
@@ -141,6 +146,13 @@ void RegisterGatt(struct ble_gatt_register_ctxt* ctxt, void* arg) {
}
}
std::vector<uint8_t> LegacyRawPayload(const std::vector<uint8_t>& data) {
if (data.size() == 1) {
return {0xBE, data[0]};
}
return data;
}
const struct ble_gatt_svc_def kGattServices[] = {
{
.type = BLE_GATT_SVC_TYPE_PRIMARY,
@@ -341,9 +353,25 @@ void GatewayBleBridge::notifyCharacteristic(size_t index, const std::vector<uint
}
characteristic_values_[index] = payload;
struct os_mbuf* buffer = ble_hs_mbuf_from_flat(payload.data(), payload.size());
const int allocation_attempts =
index == kGatewayCharacteristicIndex ? kGatewayNotifyAllocationAttempts : 1;
struct os_mbuf* buffer = nullptr;
for (int attempt = 0; attempt < allocation_attempts; ++attempt) {
if (conn_handle_ == kInvalidConnectionHandle || !notify_enabled_[index]) {
return;
}
buffer = ble_hs_mbuf_from_flat(payload.data(), payload.size());
if (buffer != nullptr) {
break;
}
if (attempt + 1 < allocation_attempts) {
vTaskDelay(kGatewayNotifyRetryDelayTicks);
}
}
if (buffer == nullptr) {
ESP_LOGW(kTag, "failed to allocate notify mbuf idx=%u", static_cast<unsigned>(index));
ESP_LOGW(kTag, "failed to allocate notify mbuf idx=%u attempts=%d len=%u",
static_cast<unsigned>(index), allocation_attempts,
static_cast<unsigned>(payload.size()));
return;
}
const int rc = ble_gatts_notify_custom(conn_handle_, s_value_handles[index], buffer);
@@ -377,7 +405,7 @@ void GatewayBleBridge::handleDaliRawFrame(const DaliRawFrame& frame) {
if (!enabled_ || conn_handle_ == kInvalidConnectionHandle || frame.data.empty()) {
return;
}
notifyCharacteristic(frame.channel_index, frame.data);
notifyCharacteristic(frame.channel_index, LegacyRawPayload(frame.data));
}
void GatewayBleBridge::handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload) {
@@ -448,6 +476,18 @@ int GatewayBleBridge::handleGapEvent(struct ble_gap_event* event) {
notify_enabled_.fill(false);
last_notify_payload_.clear();
last_notify_at_us_ = 0;
struct ble_gap_upd_params params = {
.itvl_min = 15,
.itvl_max = 15,
.latency = 3,
.supervision_timeout = 1000,
.min_ce_len = 0,
.max_ce_len = 0,
};
int rc = ble_gap_update_params(event->connect.conn_handle, &params);
if (rc != 0) {
ESP_LOGW(kTag, "ble_gap_update_params rc=%d", rc);
}
ESP_LOGI(kTag, "BLE client connected handle=%u", conn_handle_);
} else if (enabled_) {
startAdvertising();
+6 -1
View File
@@ -2,16 +2,21 @@ set(GATEWAY_BRIDGE_REQUIRES
dali_domain
dali_cpp
espressif__cjson
esp_driver_uart
freertos
gateway_cache
gateway_knx
gateway_modbus
knx
log
lwip
nvs_flash
)
idf_component_register(
SRCS "src/gateway_bridge.cpp"
SRCS
"src/gateway_bridge.cpp"
"src/security_storage.cpp"
INCLUDE_DIRS "include"
REQUIRES ${GATEWAY_BRIDGE_REQUIRES}
PRIV_REQUIRES gateway_bacnet
@@ -1,17 +1,23 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "gateway_knx.hpp"
#include "gateway_modbus.hpp"
namespace gateway {
class DaliDomainService;
struct DaliRawFrame;
class GatewayCache;
struct GatewayBridgeServiceConfig {
@@ -20,12 +26,21 @@ struct GatewayBridgeServiceConfig {
bool modbus_startup_enabled{false};
bool bacnet_enabled{false};
bool bacnet_startup_enabled{false};
bool knx_enabled{false};
bool knx_startup_enabled{false};
bool cloud_enabled{true};
bool cloud_startup_enabled{false};
uint32_t modbus_task_stack_size{6144};
UBaseType_t modbus_task_priority{4};
std::optional<GatewayModbusConfig> default_modbus_config;
bool allow_modbus_uart0{false};
bool allow_knx_uart0{false};
std::vector<int> reserved_uart_ports;
uint32_t bacnet_task_stack_size{8192};
UBaseType_t bacnet_task_priority{5};
uint32_t knx_task_stack_size{12288};
UBaseType_t knx_task_priority{5};
std::optional<GatewayKnxConfig> default_knx_config;
};
struct GatewayBridgeHttpResponse {
@@ -46,17 +61,33 @@ class GatewayBridgeService {
const std::string& query = {});
GatewayBridgeHttpResponse handlePost(const std::string& action, int gateway_id,
const std::string& body);
std::string handleTransportRequest(uint8_t gateway_id, std::string_view request);
private:
struct ChannelRuntime;
ChannelRuntime* findRuntime(uint8_t gateway_id);
const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
ChannelRuntime* selectKnxEndpointRuntime();
bool isKnxEndpointRuntime(const ChannelRuntime* runtime) const;
esp_err_t startKnxEndpoint(ChannelRuntime* requested_runtime,
std::set<int>* used_uarts = nullptr);
esp_err_t stopKnxEndpoint(ChannelRuntime* requested_runtime);
DaliBridgeResult routeKnxGroupWrite(uint16_t group_address, const uint8_t* data,
size_t len);
DaliBridgeResult routeKnxGroupObjectWrite(uint16_t group_object_number,
const uint8_t* data, size_t len);
void handleDaliRawFrame(const DaliRawFrame& frame);
void collectUsedRuntimeResources(uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports,
std::set<uint16_t>* knx_udp_ports,
std::set<int>* serial_uarts) const;
DaliDomainService& dali_domain_;
GatewayCache& cache_;
GatewayBridgeServiceConfig config_;
std::vector<std::unique_ptr<ChannelRuntime>> runtimes_;
ChannelRuntime* knx_endpoint_runtime_{nullptr};
};
} // namespace gateway
@@ -0,0 +1,69 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
#include "gateway_knx.hpp"
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;
};
struct IpSecureCredentialStatus {
bool activated{false};
bool backboneKeyAvailable{false};
bool deviceAuthenticationKeyAvailable{false};
uint8_t tunnelUserCount{0};
uint64_t routingSequence{0};
};
bool LoadFactoryFdsk(uint8_t* data, size_t len);
FactoryFdskInfo LoadFactoryFdskInfo();
bool LoadFactoryFdskForInstance(uint32_t instance_id, uint8_t* data, size_t len);
FactoryFdskInfo LoadFactoryFdskInfoForInstance(uint32_t instance_id);
bool GenerateFactoryFdsk(FactoryFdskInfo* info = nullptr);
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info = nullptr);
bool ResetFactoryFdskCache(FactoryFdskInfo* info = nullptr);
bool ResetFactorySecurityForInstance(uint32_t instance_id, FactoryFdskInfo* info = nullptr);
FactoryCertificatePayload BuildFactoryCertificatePayload();
FactoryCertificatePayload BuildFactoryCertificatePayloadForInstance(uint32_t instance_id);
bool LoadOamFactoryFdsk(uint8_t* data, size_t len);
FactoryFdskInfo LoadOamFactoryFdskInfo();
bool GenerateOamFactoryFdsk(FactoryFdskInfo* info = nullptr);
bool WriteOamFactoryFdskHex(const std::string& hex_key,
FactoryFdskInfo* info = nullptr);
bool ResetOamFactoryFdskCache(FactoryFdskInfo* info = nullptr);
FactoryCertificatePayload BuildOamFactoryCertificatePayload();
IpSecureCredentialStatus LoadOamIpSecureCredentialStatus();
::gateway::GatewayKnxIpSecureCredentialMaterial LoadOamIpSecureCredentialMaterial();
bool WriteOamIpSecureKeyringHex(const std::string& backbone_key_hex,
const std::vector<std::string>& tunnel_user_key_hex,
const std::string& device_auth_key_hex,
bool activated);
bool StoreOamIpSecureRoutingSequence(uint64_t sequence);
bool ClearOamIpSecureKeyring();
} // namespace gateway::openknx
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,713 @@
#include "security_storage.h"
#include "gateway_knx_internal.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 <cinttypes>
#include <cstddef>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <string>
namespace {
constexpr const char* kTag = "openknx_sec";
constexpr const char* kNamespace = "knx_sec";
constexpr const char* kOamNamespace = "knx_oam_sec";
constexpr const char* kFactoryFdskKey = "factory_fdsk";
constexpr const char* kIpSecureBackboneKey = "ipsec_backbone";
constexpr const char* kIpSecureDeviceAuthKey = "ipsec_dev_auth";
constexpr const char* kIpSecureTunnelCountKey = "ipsec_tunnels";
constexpr const char* kIpSecureActivatedKey = "ipsec_active";
constexpr const char* kIpSecureRoutingSeqKey = "ipsec_route_seq";
constexpr size_t kFdskSize = 16;
constexpr size_t kSerialSize = 6;
constexpr size_t kFdskQrSize = 36;
constexpr const char* kProductIdentity = "REG1-Dali";
constexpr const char* kOamProductIdentity = "OpenKNX-IP-Router";
constexpr const char* kDevelopmentStorage = "base_mac_derived_plain_nvs_development";
constexpr char kFdskDerivationLabel[] = "DaliMaster REG1-Dali deterministic FDSK v1";
constexpr char kOamFdskDerivationLabel[] = "DaliMaster OAM-IP-Router 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";
struct FactoryFdskContext {
std::string nvsNamespace;
const char* productIdentity;
const char* derivationLabel;
uint16_t manufacturerId;
uint16_t applicationNumber;
uint8_t applicationVersion;
uint32_t serialMacIncrement;
bool clearOpenKnxCache;
};
const FactoryFdskContext kReg1Context{
kNamespace,
kProductIdentity,
kFdskDerivationLabel,
gateway::knx_internal::kReg1DaliManufacturerId,
gateway::knx_internal::kReg1DaliApplicationNumber,
gateway::knx_internal::kReg1DaliApplicationVersion,
gateway::knx_internal::kReg1DaliSerialMacIncrement,
true};
const FactoryFdskContext kOamContext{
kOamNamespace,
kOamProductIdentity,
kOamFdskDerivationLabel,
gateway::knx_internal::kOamRouterManufacturerId,
gateway::knx_internal::kOamRouterApplicationNumber,
gateway::knx_internal::kOamRouterApplicationVersion,
gateway::knx_internal::kOamRouterSerialMacIncrement,
false};
extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak));
FactoryFdskContext reg1ContextForInstance(uint32_t instance_id) {
FactoryFdskContext context = kReg1Context;
context.serialMacIncrement = gateway::knx_internal::kReg1DaliSerialMacIncrement + instance_id;
context.clearOpenKnxCache = instance_id == 0;
if (instance_id != 0) {
context.nvsNamespace = std::string(kNamespace) + "_" + std::to_string(instance_id);
}
return context;
}
std::string hexValue(uint32_t value, int width) {
std::array<char, 9> buffer{};
std::snprintf(buffer.data(), buffer.size(), "%0*" PRIX32, width, value);
return buffer.data();
}
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(const FactoryFdskContext& context, uint8_t* serial) {
if (serial == nullptr) {
return false;
}
std::array<uint8_t, kSerialSize> mac{};
if (!readBaseMac(mac.data())) {
return false;
}
uint32_t suffix = (static_cast<uint32_t>(mac[2]) << 24) |
(static_cast<uint32_t>(mac[3]) << 16) |
(static_cast<uint32_t>(mac[4]) << 8) |
static_cast<uint32_t>(mac[5]);
suffix += context.serialMacIncrement;
serial[0] = static_cast<uint8_t>((context.manufacturerId >> 8) & 0xff);
serial[1] = static_cast<uint8_t>(context.manufacturerId & 0xff);
serial[2] = static_cast<uint8_t>((suffix >> 24) & 0xff);
serial[3] = static_cast<uint8_t>((suffix >> 16) & 0xff);
serial[4] = static_cast<uint8_t>((suffix >> 8) & 0xff);
serial[5] = static_cast<uint8_t>(suffix & 0xff);
return true;
}
bool deriveFactoryFdskFromSerial(const FactoryFdskContext& context,
const uint8_t* serial, uint8_t* key) {
if (serial == nullptr || key == nullptr) {
return false;
}
const size_t label_len = std::strlen(context.derivationLabel);
std::vector<uint8_t> material(label_len + kSerialSize);
std::copy(context.derivationLabel, context.derivationLabel + label_len, material.begin());
std::copy(serial, serial + kSerialSize, material.begin() + label_len);
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 FactoryFdskContext& context, 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(context.nvsNamespace.c_str(), 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;
}
if (context.clearOpenKnxCache) {
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 LoadFactoryFdskForContext(const FactoryFdskContext& context, 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(context, serial.data()) ||
!deriveFactoryFdskFromSerial(context, serial.data(), key.data())) {
return false;
}
std::memcpy(data, key.data(), kFdskSize);
syncFactoryFdskToNvs(context, key.data());
return true;
}
FactoryFdskInfo LoadFactoryFdskInfoForContext(const FactoryFdskContext& context) {
FactoryFdskInfo info;
std::array<uint8_t, kFdskSize> key{};
std::array<uint8_t, kSerialSize> serial{};
if (!loadKnxSerialNumber(context, serial.data()) ||
!LoadFactoryFdskForContext(context, 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 GenerateFactoryFdskForContext(const FactoryFdskContext& context,
FactoryFdskInfo* info) {
std::array<uint8_t, kFdskSize> key{};
const bool stored = LoadFactoryFdskForContext(context, key.data(), key.size());
std::fill(key.begin(), key.end(), 0);
if (!stored) {
return false;
}
if (info != nullptr) {
*info = LoadFactoryFdskInfoForContext(context);
}
return true;
}
bool WriteFactoryFdskHexForContext(const FactoryFdskContext& context,
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(context, serial.data()) &&
deriveFactoryFdskFromSerial(context, serial.data(), derived.data()) &&
std::equal(key.begin(), key.end(), derived.begin());
if (stored) {
syncFactoryFdskToNvs(context, derived.data());
}
std::fill(key.begin(), key.end(), 0);
std::fill(derived.begin(), derived.end(), 0);
if (!stored) {
return false;
}
if (info != nullptr) {
*info = LoadFactoryFdskInfoForContext(context);
}
return true;
}
bool ResetFactoryFdskCacheForContext(const FactoryFdskContext& context,
FactoryFdskInfo* info) {
if (context.clearOpenKnxCache) {
clearOpenKnxFdskCache();
}
const auto loaded = LoadFactoryFdskInfoForContext(context);
if (info != nullptr) {
*info = loaded;
}
return loaded.available;
}
FactoryCertificatePayload BuildFactoryCertificatePayloadForContext(
const FactoryFdskContext& context) {
FactoryCertificatePayload payload;
const auto info = LoadFactoryFdskInfoForContext(context);
if (!info.available) {
return payload;
}
payload.available = true;
payload.productIdentity = context.productIdentity;
payload.manufacturerId = hexValue(context.manufacturerId, 4);
payload.applicationNumber = hexValue(context.applicationNumber,
context.applicationNumber <= 0xff ? 2 : 4);
payload.applicationVersion = hexValue(context.applicationVersion, 2);
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;
}
std::string TunnelUserKeyName(uint8_t index) {
std::array<char, 18> buffer{};
std::snprintf(buffer.data(), buffer.size(), "ipsec_tunnel_%02u",
static_cast<unsigned>(index));
return buffer.data();
}
bool BlobKeyAvailable(nvs_handle_t handle, const char* key) {
std::array<uint8_t, kFdskSize> data{};
size_t len = data.size();
return nvs_get_blob(handle, key, data.data(), &len) == ESP_OK && len == data.size() &&
plausibleKey(data.data());
}
bool SetOptionalHexBlob(nvs_handle_t handle, const char* key, const std::string& hex) {
if (hex.empty()) {
nvs_erase_key(handle, key);
return true;
}
std::array<uint8_t, kFdskSize> data{};
if (!parseHexKey(hex, data.data())) {
return false;
}
const esp_err_t err = nvs_set_blob(handle, key, data.data(), data.size());
std::fill(data.begin(), data.end(), 0);
return err == ESP_OK;
}
bool LoadOptionalKey(nvs_handle_t handle, const char* key, std::array<uint8_t, kFdskSize>* out) {
if (out == nullptr) {
return false;
}
std::array<uint8_t, kFdskSize> data{};
size_t len = data.size();
if (nvs_get_blob(handle, key, data.data(), &len) != ESP_OK || len != data.size() ||
!plausibleKey(data.data())) {
return false;
}
*out = data;
return true;
}
bool LoadFactoryFdsk(uint8_t* data, size_t len) {
return LoadFactoryFdskForContext(kReg1Context, data, len);
}
FactoryFdskInfo LoadFactoryFdskInfo() {
return LoadFactoryFdskInfoForContext(kReg1Context);
}
bool LoadFactoryFdskForInstance(uint32_t instance_id, uint8_t* data, size_t len) {
const auto context = reg1ContextForInstance(instance_id);
return LoadFactoryFdskForContext(context, data, len);
}
FactoryFdskInfo LoadFactoryFdskInfoForInstance(uint32_t instance_id) {
const auto context = reg1ContextForInstance(instance_id);
return LoadFactoryFdskInfoForContext(context);
}
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
return GenerateFactoryFdskForContext(kReg1Context, info);
}
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
return WriteFactoryFdskHexForContext(kReg1Context, hex_key, info);
}
bool ResetFactoryFdskCache(FactoryFdskInfo* info) {
return ResetFactoryFdskCacheForContext(kReg1Context, info);
}
bool ResetFactorySecurityForInstance(uint32_t instance_id, FactoryFdskInfo* info) {
const auto context = reg1ContextForInstance(instance_id);
if (ensureNvsReady()) {
nvs_handle_t handle = 0;
if (nvs_open(context.nvsNamespace.c_str(), NVS_READWRITE, &handle) == ESP_OK) {
nvs_erase_key(handle, kFactoryFdskKey);
nvs_commit(handle);
nvs_close(handle);
}
}
return ResetFactoryFdskCacheForContext(context, info);
}
FactoryCertificatePayload BuildFactoryCertificatePayload() {
return BuildFactoryCertificatePayloadForContext(kReg1Context);
}
FactoryCertificatePayload BuildFactoryCertificatePayloadForInstance(uint32_t instance_id) {
const auto context = reg1ContextForInstance(instance_id);
return BuildFactoryCertificatePayloadForContext(context);
}
bool LoadOamFactoryFdsk(uint8_t* data, size_t len) {
return LoadFactoryFdskForContext(kOamContext, data, len);
}
FactoryFdskInfo LoadOamFactoryFdskInfo() {
return LoadFactoryFdskInfoForContext(kOamContext);
}
bool GenerateOamFactoryFdsk(FactoryFdskInfo* info) {
return GenerateFactoryFdskForContext(kOamContext, info);
}
bool WriteOamFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
return WriteFactoryFdskHexForContext(kOamContext, hex_key, info);
}
bool ResetOamFactoryFdskCache(FactoryFdskInfo* info) {
return ResetFactoryFdskCacheForContext(kOamContext, info);
}
FactoryCertificatePayload BuildOamFactoryCertificatePayload() {
return BuildFactoryCertificatePayloadForContext(kOamContext);
}
IpSecureCredentialStatus LoadOamIpSecureCredentialStatus() {
IpSecureCredentialStatus status;
if (!ensureNvsReady()) {
return status;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READONLY, &handle) != ESP_OK) {
return status;
}
uint8_t activated = 0;
if (nvs_get_u8(handle, kIpSecureActivatedKey, &activated) == ESP_OK) {
status.activated = activated != 0;
}
uint8_t tunnel_count = 0;
if (nvs_get_u8(handle, kIpSecureTunnelCountKey, &tunnel_count) == ESP_OK) {
status.tunnelUserCount = tunnel_count;
}
uint64_t routing_sequence = 0;
if (nvs_get_u64(handle, kIpSecureRoutingSeqKey, &routing_sequence) == ESP_OK) {
status.routingSequence = routing_sequence;
}
status.backboneKeyAvailable = BlobKeyAvailable(handle, kIpSecureBackboneKey);
status.deviceAuthenticationKeyAvailable = BlobKeyAvailable(handle, kIpSecureDeviceAuthKey);
nvs_close(handle);
return status;
}
::gateway::GatewayKnxIpSecureCredentialMaterial LoadOamIpSecureCredentialMaterial() {
::gateway::GatewayKnxIpSecureCredentialMaterial material;
if (!ensureNvsReady()) {
return material;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READONLY, &handle) != ESP_OK) {
return material;
}
uint8_t activated = 0;
if (nvs_get_u8(handle, kIpSecureActivatedKey, &activated) == ESP_OK) {
material.activated = activated != 0;
}
material.backbone_key_available = LoadOptionalKey(handle, kIpSecureBackboneKey,
&material.backbone_key);
material.device_authentication_key_available =
LoadOptionalKey(handle, kIpSecureDeviceAuthKey,
&material.device_authentication_key);
uint8_t tunnel_count = 0;
if (nvs_get_u8(handle, kIpSecureTunnelCountKey, &tunnel_count) == ESP_OK) {
tunnel_count = std::min<uint8_t>(tunnel_count, 16);
material.tunnel_user_keys.reserve(tunnel_count);
for (uint8_t index = 0; index < tunnel_count; ++index) {
std::array<uint8_t, kFdskSize> key{};
const std::string key_name = TunnelUserKeyName(index);
if (LoadOptionalKey(handle, key_name.c_str(), &key)) {
material.tunnel_user_keys.push_back(key);
}
}
}
uint64_t routing_sequence = 0;
if (nvs_get_u64(handle, kIpSecureRoutingSeqKey, &routing_sequence) == ESP_OK) {
material.routing_sequence = routing_sequence;
}
nvs_close(handle);
return material;
}
bool WriteOamIpSecureKeyringHex(const std::string& backbone_key_hex,
const std::vector<std::string>& tunnel_user_key_hex,
const std::string& device_auth_key_hex,
bool activated) {
if (tunnel_user_key_hex.size() > 16 || !ensureNvsReady()) {
return false;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return false;
}
bool ok = SetOptionalHexBlob(handle, kIpSecureBackboneKey, backbone_key_hex) &&
SetOptionalHexBlob(handle, kIpSecureDeviceAuthKey, device_auth_key_hex);
for (uint8_t index = 0; index < 16; ++index) {
const std::string key = TunnelUserKeyName(index);
nvs_erase_key(handle, key.c_str());
}
if (ok) {
for (size_t index = 0; index < tunnel_user_key_hex.size(); ++index) {
const std::string key = TunnelUserKeyName(static_cast<uint8_t>(index));
ok = SetOptionalHexBlob(handle, key.c_str(), tunnel_user_key_hex[index]);
if (!ok) {
break;
}
}
}
if (ok) {
ok = nvs_set_u8(handle, kIpSecureTunnelCountKey,
static_cast<uint8_t>(tunnel_user_key_hex.size())) == ESP_OK &&
nvs_set_u8(handle, kIpSecureActivatedKey, activated ? 1 : 0) == ESP_OK;
}
if (ok) {
uint64_t existing_sequence = 0;
if (nvs_get_u64(handle, kIpSecureRoutingSeqKey, &existing_sequence) != ESP_OK) {
ok = nvs_set_u64(handle, kIpSecureRoutingSeqKey, 0) == ESP_OK;
}
}
if (ok) {
ok = nvs_commit(handle) == ESP_OK;
}
nvs_close(handle);
return ok;
}
bool StoreOamIpSecureRoutingSequence(uint64_t sequence) {
if (!ensureNvsReady()) {
return false;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return false;
}
const bool ok = nvs_set_u64(handle, kIpSecureRoutingSeqKey, sequence) == ESP_OK &&
nvs_commit(handle) == ESP_OK;
nvs_close(handle);
return ok;
}
bool ClearOamIpSecureKeyring() {
if (!ensureNvsReady()) {
return false;
}
nvs_handle_t handle = 0;
if (nvs_open(kOamNamespace, NVS_READWRITE, &handle) != ESP_OK) {
return false;
}
nvs_erase_key(handle, kIpSecureBackboneKey);
nvs_erase_key(handle, kIpSecureDeviceAuthKey);
nvs_erase_key(handle, kIpSecureTunnelCountKey);
nvs_erase_key(handle, kIpSecureActivatedKey);
nvs_erase_key(handle, kIpSecureRoutingSeqKey);
for (uint8_t index = 0; index < 16; ++index) {
const std::string key = TunnelUserKeyName(index);
nvs_erase_key(handle, key.c_str());
}
const bool ok = nvs_commit(handle) == ESP_OK;
nvs_close(handle);
return ok;
}
} // namespace gateway::openknx
extern "C" bool knx_platform_get_fdsk(uint8_t* data, size_t len) {
return gateway::openknx::LoadFactoryFdsk(data, len);
}
@@ -5,6 +5,7 @@
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
@@ -24,7 +25,8 @@ struct GatewayCacheConfig {
bool cache_enabled{true};
bool reconciliation_enabled{true};
bool full_state_mirror_enabled{false};
uint32_t flush_interval_ms{5000};
uint32_t flush_interval_ms{10000};
uint32_t refresh_interval_ms{120000};
uint32_t task_stack_size{4096};
UBaseType_t task_priority{3};
GatewayCachePriorityMode default_priority_mode{GatewayCachePriorityMode::kOutsideBusFirst};
@@ -41,6 +43,12 @@ enum class GatewayCacheDaliTargetKind : uint8_t {
kBroadcast = 2,
};
enum class GatewayCacheDaliPresence : uint8_t {
kUnknown = 0,
kOnline = 1,
kOffline = 2,
};
struct GatewayCacheDaliTarget {
GatewayCacheDaliTargetKind kind{GatewayCacheDaliTargetKind::kShortAddress};
uint8_t value{0};
@@ -71,6 +79,7 @@ struct GatewayCacheDaliRuntimeStatus {
std::optional<uint8_t> actual_level;
std::optional<uint8_t> scene_id;
bool use_min_level{false};
bool stale{false};
uint32_t revision{0};
bool anyKnown() const {
@@ -136,6 +145,12 @@ class GatewayCache {
GatewayCacheChannelFlags channelFlags(uint8_t gateway_id);
GatewayCacheChannelFlags pendingChannelFlags(uint8_t gateway_id);
GatewayCacheDaliAddressState daliAddressState(uint8_t gateway_id, uint8_t short_address);
GatewayCacheDaliPresence daliAddressPresence(uint8_t gateway_id, uint8_t short_address);
void markDaliAddressPresence(uint8_t gateway_id, uint8_t short_address,
GatewayCacheDaliPresence presence);
std::optional<GatewayCacheDaliTarget> decodeDaliTarget(uint8_t raw_addr);
std::vector<uint8_t> reconciliationAddresses(
uint8_t gateway_id, std::optional<GatewayCacheDaliTarget> target);
GatewayCacheDaliRuntimeStatus daliGroupStatus(uint8_t gateway_id, uint8_t group_id);
GatewayCacheDaliRuntimeStatus daliBroadcastStatus(uint8_t gateway_id);
bool setDaliGroupMask(uint8_t gateway_id, uint8_t short_address,
@@ -144,6 +159,8 @@ class GatewayCache {
std::optional<uint8_t> level);
bool setDaliSettings(uint8_t gateway_id, uint8_t short_address,
std::optional<GatewayCacheDaliSettingsSnapshot> settings);
bool setDaliActualLevel(uint8_t gateway_id, uint8_t short_address,
std::optional<uint8_t> level);
bool clearChannelFlagsIfMatched(uint8_t gateway_id, const GatewayCacheChannelFlags& flags);
void markGroupUpdateNeeded(uint8_t gateway_id, bool needed = true);
void markSceneUpdateNeeded(uint8_t gateway_id, bool needed = true);
@@ -174,9 +191,13 @@ class GatewayCache {
void closeStorageLocked();
bool persistSceneLocked(uint8_t gateway_id, uint8_t scene_id, const SceneEntry& scene);
bool persistGroupLocked(uint8_t gateway_id, uint8_t group_id, const GroupEntry& group);
bool persistDaliAddressStateLocked(uint8_t gateway_id, uint8_t short_address,
const GatewayCacheDaliAddressState& state);
bool commitStorageLocked();
bool shouldTrackUpdateFlagsLocked() const;
uint32_t nextDaliRuntimeRevisionLocked();
void markDaliAddressPresenceLocked(uint8_t gateway_id, uint8_t short_address,
GatewayCacheDaliPresence presence);
bool mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
void clearDaliTargetStateLocked(uint8_t gateway_id, const GatewayCacheDaliTarget& target,
uint32_t revision);
@@ -205,6 +226,8 @@ class GatewayCache {
GroupStore& ensureGroupStoreLocked(uint8_t gateway_id);
void loadSceneStoreLocked(uint8_t gateway_id, SceneStore& scenes);
void loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups);
void loadDaliStateStoreLocked(uint8_t gateway_id,
std::array<GatewayCacheDaliAddressState, 64>& states);
std::string readStringLocked(std::string_view key);
bool writeStringLocked(std::string_view key, std::string_view value);
bool eraseKeyLocked(std::string_view key);
@@ -217,6 +240,7 @@ class GatewayCache {
std::map<uint8_t, SceneStore> scenes_;
std::map<uint8_t, GroupStore> groups_;
std::map<uint8_t, std::array<GatewayCacheDaliAddressState, 64>> dali_states_;
std::map<uint8_t, std::array<GatewayCacheDaliPresence, 64>> dali_presence_;
std::map<uint8_t, std::array<GatewayCacheDaliRuntimeStatus, 16>> dali_group_status_;
std::map<uint8_t, GatewayCacheDaliRuntimeStatus> dali_broadcast_status_;
std::map<uint8_t, DtrState> dtr_states_;
+348 -4
View File
@@ -43,6 +43,18 @@ constexpr uint8_t kDaliCmdDt8StoreDtrAsColorX = 0xE0;
constexpr uint8_t kDaliCmdDt8StoreDtrAsColorY = 0xE1;
constexpr uint8_t kDaliCmdDt8StorePrimaryMin = 0xF0;
constexpr uint8_t kDaliCmdDt8StartAutoCalibration = 0xF6;
constexpr int kDaliStatePayloadVersion = 1;
constexpr uint32_t kDaliStateGroupMaskKnown = 1U << 0;
constexpr uint32_t kDaliStateActualKnown = 1U << 1;
constexpr uint32_t kDaliStateSceneKnown = 1U << 2;
constexpr uint32_t kDaliStateUseMinLevel = 1U << 3;
constexpr uint32_t kDaliStateStatusStale = 1U << 4;
constexpr uint32_t kDaliStatePowerOnKnown = 1U << 5;
constexpr uint32_t kDaliStateSystemFailureKnown = 1U << 6;
constexpr uint32_t kDaliStateMinKnown = 1U << 7;
constexpr uint32_t kDaliStateMaxKnown = 1U << 8;
constexpr uint32_t kDaliStateFadeTimeKnown = 1U << 9;
constexpr uint32_t kDaliStateFadeRateKnown = 1U << 10;
class LockGuard {
public:
@@ -134,6 +146,18 @@ bool ShouldMirrorObservedMutation(GatewayCacheRawFrameOrigin origin,
priority_mode == GatewayCachePriorityMode::kOutsideBusFirst;
}
bool ShouldAlwaysMirrorObservedStatus(uint8_t raw_addr, uint8_t command) {
if (!DecodeDaliTarget(raw_addr).has_value()) {
return false;
}
if ((raw_addr & 0x01) == 0) {
return command <= 254;
}
return command == kDaliCmdOff || command == kDaliCmdRecallMax ||
command == kDaliCmdRecallMin ||
(command >= kDaliCmdGoToSceneMin && command <= kDaliCmdGoToSceneMax);
}
void ClearDaliState(GatewayCacheDaliAddressState& state) {
state.group_mask_known = false;
state.group_mask = 0;
@@ -215,6 +239,139 @@ std::string BuildGroupPayload(const GatewayCache::GroupEntry& group) {
return std::string(payload);
}
uint8_t ByteValue(int value) {
return static_cast<uint8_t>(std::clamp(value, 0, 255));
}
uint16_t WordValue(int value) {
return static_cast<uint16_t>(std::clamp(value, 0, 0xffff));
}
uint16_t SceneKnownMask(const GatewayCacheDaliAddressState& state) {
uint16_t mask = 0;
for (size_t index = 0; index < state.scene_levels.size(); ++index) {
if (state.scene_levels[index].has_value()) {
mask |= static_cast<uint16_t>(1U << index);
}
}
return mask;
}
bool IsDefaultDaliAddressState(const GatewayCacheDaliAddressState& state) {
return !state.group_mask_known && state.group_mask == 0 && SceneKnownMask(state) == 0 &&
!state.settings.anyKnown() && !state.status.anyKnown();
}
uint32_t DaliStateFlags(const GatewayCacheDaliAddressState& state) {
uint32_t flags = 0;
if (state.group_mask_known) {
flags |= kDaliStateGroupMaskKnown;
}
if (state.status.actual_level.has_value()) {
flags |= kDaliStateActualKnown;
}
if (state.status.scene_id.has_value()) {
flags |= kDaliStateSceneKnown;
}
if (state.status.use_min_level) {
flags |= kDaliStateUseMinLevel;
}
if (state.status.stale) {
flags |= kDaliStateStatusStale;
}
if (state.settings.power_on_level.has_value()) {
flags |= kDaliStatePowerOnKnown;
}
if (state.settings.system_failure_level.has_value()) {
flags |= kDaliStateSystemFailureKnown;
}
if (state.settings.min_level.has_value()) {
flags |= kDaliStateMinKnown;
}
if (state.settings.max_level.has_value()) {
flags |= kDaliStateMaxKnown;
}
if (state.settings.fade_time.has_value()) {
flags |= kDaliStateFadeTimeKnown;
}
if (state.settings.fade_rate.has_value()) {
flags |= kDaliStateFadeRateKnown;
}
return flags;
}
std::string BuildDaliStatePayload(const GatewayCacheDaliAddressState& state) {
const uint16_t scene_known_mask = SceneKnownMask(state);
std::string payload = std::to_string(kDaliStatePayloadVersion);
payload += "," + std::to_string(DaliStateFlags(state));
payload += "," + std::to_string(state.status.revision);
payload += "," + std::to_string(state.group_mask);
payload += "," + std::to_string(state.status.actual_level.value_or(0));
payload += "," + std::to_string(state.status.scene_id.value_or(0));
payload += "," + std::to_string(state.settings.power_on_level.value_or(0));
payload += "," + std::to_string(state.settings.system_failure_level.value_or(0));
payload += "," + std::to_string(state.settings.min_level.value_or(0));
payload += "," + std::to_string(state.settings.max_level.value_or(0));
payload += "," + std::to_string(state.settings.fade_time.value_or(0));
payload += "," + std::to_string(state.settings.fade_rate.value_or(0));
payload += "," + std::to_string(scene_known_mask);
for (const auto& scene_level : state.scene_levels) {
payload += "," + std::to_string(scene_level.value_or(255));
}
return payload;
}
void ApplyDaliStatePayload(std::string_view raw, GatewayCacheDaliAddressState& state) {
const auto values = ParseCsv(raw);
if (values.size() < 13 || values[0] != kDaliStatePayloadVersion) {
return;
}
const uint32_t flags = static_cast<uint32_t>(std::max(values[1], 0));
state.group_mask_known = (flags & kDaliStateGroupMaskKnown) != 0;
state.group_mask = state.group_mask_known ? WordValue(values[3]) : 0;
state.status = {};
state.status.revision = static_cast<uint32_t>(std::max(values[2], 0));
state.status.stale = (flags & kDaliStateStatusStale) != 0;
state.status.use_min_level = (flags & kDaliStateUseMinLevel) != 0;
if ((flags & kDaliStateActualKnown) != 0) {
state.status.actual_level = ByteValue(values[4]);
}
if ((flags & kDaliStateSceneKnown) != 0) {
state.status.scene_id = static_cast<uint8_t>(std::min<int>(ByteValue(values[5]), 15));
}
state.settings = {};
if ((flags & kDaliStatePowerOnKnown) != 0) {
state.settings.power_on_level = ByteValue(values[6]);
}
if ((flags & kDaliStateSystemFailureKnown) != 0) {
state.settings.system_failure_level = ByteValue(values[7]);
}
if ((flags & kDaliStateMinKnown) != 0) {
state.settings.min_level = ByteValue(values[8]);
}
if ((flags & kDaliStateMaxKnown) != 0) {
state.settings.max_level = ByteValue(values[9]);
}
if ((flags & kDaliStateFadeTimeKnown) != 0) {
state.settings.fade_time = ByteValue(values[10]);
}
if ((flags & kDaliStateFadeRateKnown) != 0) {
state.settings.fade_rate = ByteValue(values[11]);
}
state.scene_levels.fill(std::nullopt);
const uint16_t scene_known_mask = WordValue(values[12]);
for (uint8_t scene_id = 0; scene_id < state.scene_levels.size(); ++scene_id) {
const size_t value_index = 13 + scene_id;
if ((scene_known_mask & (1U << scene_id)) != 0 && value_index < values.size()) {
state.scene_levels[scene_id] = ByteValue(values[value_index]);
}
}
}
} // namespace
GatewayCache::GatewayCache(GatewayCacheConfig config)
@@ -269,8 +426,11 @@ esp_err_t GatewayCache::start() {
return ESP_ERR_NO_MEM;
}
ESP_LOGI(kTag, "cache started namespace=%s flush_interval_ms=%u reconciliation=%d full_mirror=%d",
ESP_LOGI(kTag,
"cache started namespace=%s flush_interval_ms=%u refresh_interval_ms=%u "
"reconciliation=%d full_mirror=%d",
config_.storage_namespace.c_str(), static_cast<unsigned>(config_.flush_interval_ms),
static_cast<unsigned>(config_.refresh_interval_ms),
config_.reconciliation_enabled, config_.full_state_mirror_enabled);
return ESP_OK;
}
@@ -282,6 +442,10 @@ void GatewayCache::preloadChannel(uint8_t gateway_id) {
}
ensureSceneStoreLocked(gateway_id);
ensureGroupStoreLocked(gateway_id);
auto [it, inserted] = dali_states_.try_emplace(gateway_id);
if (inserted) {
loadDaliStateStoreLocked(gateway_id, it->second);
}
}
GatewayCache::SceneStore GatewayCache::scenes(uint8_t gateway_id) {
@@ -595,6 +759,86 @@ GatewayCacheDaliAddressState GatewayCache::daliAddressState(uint8_t gateway_id,
return ensureDaliAddressStateLocked(gateway_id, short_address);
}
GatewayCacheDaliPresence GatewayCache::daliAddressPresence(uint8_t gateway_id,
uint8_t short_address) {
LockGuard guard(lock_);
if (short_address >= 64) {
return GatewayCacheDaliPresence::kUnknown;
}
if (const auto it = dali_presence_.find(gateway_id); it != dali_presence_.end()) {
return it->second[short_address];
}
return GatewayCacheDaliPresence::kUnknown;
}
void GatewayCache::markDaliAddressPresence(uint8_t gateway_id, uint8_t short_address,
GatewayCacheDaliPresence presence) {
LockGuard guard(lock_);
markDaliAddressPresenceLocked(gateway_id, short_address, presence);
}
std::optional<GatewayCacheDaliTarget> GatewayCache::decodeDaliTarget(uint8_t raw_addr) {
return DecodeDaliTarget(raw_addr);
}
std::vector<uint8_t> GatewayCache::reconciliationAddresses(
uint8_t gateway_id, std::optional<GatewayCacheDaliTarget> target) {
LockGuard guard(lock_);
std::vector<uint8_t> addresses;
auto presence = [&](uint8_t short_address) {
if (const auto it = dali_presence_.find(gateway_id); it != dali_presence_.end()) {
return it->second[short_address];
}
return GatewayCacheDaliPresence::kUnknown;
};
auto add_if_known_online = [&](uint8_t short_address) {
if (short_address < 64 && presence(short_address) == GatewayCacheDaliPresence::kOnline) {
addresses.push_back(short_address);
}
};
if (!target.has_value()) {
for (uint8_t short_address = 0; short_address < 64; ++short_address) {
add_if_known_online(short_address);
}
return addresses;
}
switch (target->kind) {
case GatewayCacheDaliTargetKind::kShortAddress:
if (target->value < 64 && presence(target->value) != GatewayCacheDaliPresence::kOffline) {
addresses.push_back(target->value);
}
break;
case GatewayCacheDaliTargetKind::kGroup: {
if (target->value >= 16) {
break;
}
const uint16_t bit = static_cast<uint16_t>(1U << target->value);
auto [states_it, inserted] = dali_states_.try_emplace(gateway_id);
if (inserted) {
loadDaliStateStoreLocked(gateway_id, states_it->second);
}
for (uint8_t short_address = 0; short_address < states_it->second.size(); ++short_address) {
const auto& state = states_it->second[short_address];
if (state.group_mask_known && (state.group_mask & bit) != 0) {
add_if_known_online(short_address);
}
}
break;
}
case GatewayCacheDaliTargetKind::kBroadcast:
for (uint8_t short_address = 0; short_address < 64; ++short_address) {
add_if_known_online(short_address);
}
break;
default:
break;
}
return addresses;
}
GatewayCacheDaliRuntimeStatus GatewayCache::daliGroupStatus(uint8_t gateway_id,
uint8_t group_id) {
LockGuard guard(lock_);
@@ -620,6 +864,7 @@ bool GatewayCache::setDaliGroupMask(uint8_t gateway_id, uint8_t short_address,
state.group_mask_known = group_mask.has_value();
state.group_mask = group_mask.value_or(0);
refreshDaliAddressAggregateStatusLocked(gateway_id, state);
dirty_ = true;
return true;
}
@@ -632,6 +877,7 @@ bool GatewayCache::setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address,
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
state.scene_levels[scene_id] = level;
dirty_ = true;
return true;
}
@@ -644,6 +890,31 @@ bool GatewayCache::setDaliSettings(uint8_t gateway_id, uint8_t short_address,
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
state.settings = settings.value_or(GatewayCacheDaliSettingsSnapshot{});
dirty_ = true;
return true;
}
bool GatewayCache::setDaliActualLevel(uint8_t gateway_id, uint8_t short_address,
std::optional<uint8_t> level) {
LockGuard guard(lock_);
if (short_address >= 64) {
return false;
}
GatewayCacheDaliRuntimeStatus status;
status.actual_level = level;
status.revision = nextDaliRuntimeRevisionLocked();
status.stale = false;
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
state.status.scene_id.reset();
state.status.use_min_level = false;
applyDaliRuntimeStatusToAddressLocked(state, status);
if (!level.has_value()) {
state.status.actual_level.reset();
state.status.revision = status.revision;
state.status.stale = false;
}
dirty_ = true;
return true;
}
@@ -712,7 +983,8 @@ bool GatewayCache::observeDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint
return false;
}
if (ShouldMirrorObservedMutation(origin, priority_mode_)) {
if (ShouldAlwaysMirrorObservedStatus(raw_addr, command) ||
ShouldMirrorObservedMutation(origin, priority_mode_)) {
mirrorDaliCommandLocked(gateway_id, raw_addr, command);
}
@@ -791,12 +1063,15 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
GatewayCacheDaliRuntimeStatus status;
status.actual_level = command;
status.revision = nextDaliRuntimeRevisionLocked();
status.stale = false;
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
dirty_ = true;
return true;
}
if (command == kDaliCmdReset) {
clearDaliTargetStateLocked(gateway_id, *target, nextDaliRuntimeRevisionLocked());
dirty_ = true;
return true;
}
@@ -804,7 +1079,9 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
GatewayCacheDaliRuntimeStatus status;
status.actual_level = command == kDaliCmdOff ? 0 : 254;
status.revision = nextDaliRuntimeRevisionLocked();
status.stale = false;
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
dirty_ = true;
return true;
}
@@ -812,7 +1089,9 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
GatewayCacheDaliRuntimeStatus status;
status.use_min_level = true;
status.revision = nextDaliRuntimeRevisionLocked();
status.stale = false;
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
dirty_ = true;
return true;
}
@@ -820,7 +1099,9 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
GatewayCacheDaliRuntimeStatus status;
status.scene_id = static_cast<uint8_t>(command - kDaliCmdGoToSceneMin);
status.revision = nextDaliRuntimeRevisionLocked();
status.stale = false;
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
dirty_ = true;
return true;
}
@@ -828,6 +1109,7 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
applyDaliTargetGroupMutationLocked(gateway_id, *target,
static_cast<uint8_t>(command & 0x0F),
command < (kDaliCmdAddToGroupMin + 16));
dirty_ = true;
return true;
}
@@ -836,6 +1118,7 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
applyDaliTargetSceneLevelLocked(gateway_id, *target,
static_cast<uint8_t>(command - kDaliCmdSetSceneMin),
*dtr_state.dtr0);
dirty_ = true;
return true;
}
@@ -843,12 +1126,14 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
applyDaliTargetSceneLevelLocked(
gateway_id, *target, static_cast<uint8_t>(command - (kDaliCmdSetSceneMin + 16)),
static_cast<uint8_t>(255U));
dirty_ = true;
return true;
}
if (command >= kDaliCmdStoreDtrAsMaxLevel && command <= kDaliCmdStoreDtrAsFadeRate &&
dtr_state.dtr0.has_value()) {
applyDaliTargetSettingsLocked(gateway_id, *target, command, *dtr_state.dtr0);
dirty_ = true;
return true;
}
@@ -977,6 +1262,7 @@ void GatewayCache::applyDaliRuntimeStatusToAddressLocked(
}
}
state.status.revision = status.revision;
state.status.stale = status.stale;
}
void GatewayCache::applyDaliTargetGroupMutationLocked(uint8_t gateway_id,
@@ -1127,7 +1413,9 @@ void GatewayCache::refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id,
if (const auto broadcast = dali_broadcast_status_.find(gateway_id);
broadcast != dali_broadcast_status_.end()) {
applyDaliRuntimeStatusToAddressLocked(state, broadcast->second);
if (!broadcast->second.stale) {
applyDaliRuntimeStatusToAddressLocked(state, broadcast->second);
}
}
const auto groups = dali_group_status_.find(gateway_id);
@@ -1137,6 +1425,9 @@ void GatewayCache::refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id,
for (uint8_t group_id = 0; group_id < groups->second.size(); ++group_id) {
const uint16_t bit = static_cast<uint16_t>(1U << group_id);
if ((state.group_mask & bit) != 0) {
if (groups->second[group_id].stale) {
continue;
}
applyDaliRuntimeStatusToAddressLocked(state, groups->second[group_id]);
}
}
@@ -1209,6 +1500,14 @@ bool GatewayCache::flushDirty() {
}
}
for (const auto& [gateway_id, states] : dali_states_) {
for (uint8_t short_address = 0; short_address < states.size(); ++short_address) {
if (!persistDaliAddressStateLocked(gateway_id, short_address, states[short_address])) {
return false;
}
}
}
const esp_err_t commit_err = nvs_commit(storage_);
if (commit_err != ESP_OK) {
ESP_LOGE(kTag, "cache commit failed: %s", esp_err_to_name(commit_err));
@@ -1288,6 +1587,18 @@ bool GatewayCache::persistGroupLocked(uint8_t gateway_id, uint8_t group_id,
return commitStorageLocked();
}
bool GatewayCache::persistDaliAddressStateLocked(
uint8_t gateway_id, uint8_t short_address, const GatewayCacheDaliAddressState& state) {
if (short_address >= 64) {
return false;
}
if (!IsDefaultDaliAddressState(state)) {
return writeStringLocked(ShortKey("ds", gateway_id, short_address),
BuildDaliStatePayload(state));
}
return eraseKeyLocked(ShortKey("ds", gateway_id, short_address));
}
bool GatewayCache::commitStorageLocked() {
if (storage_ == 0) {
return false;
@@ -1304,10 +1615,26 @@ bool GatewayCache::shouldTrackUpdateFlagsLocked() const {
return config_.cache_enabled && config_.reconciliation_enabled;
}
void GatewayCache::markDaliAddressPresenceLocked(uint8_t gateway_id, uint8_t short_address,
GatewayCacheDaliPresence presence) {
if (short_address >= 64) {
return;
}
auto& states = dali_presence_[gateway_id];
const auto previous = states[short_address];
states[short_address] = presence;
if (previous != presence) {
ESP_LOGD(kTag, "presence gateway=%u short=%u state=%u", gateway_id, short_address,
static_cast<unsigned>(presence));
}
}
GatewayCacheDaliAddressState& GatewayCache::ensureDaliAddressStateLocked(uint8_t gateway_id,
uint8_t short_address) {
auto [it, inserted] = dali_states_.try_emplace(gateway_id);
(void)inserted;
if (inserted) {
loadDaliStateStoreLocked(gateway_id, it->second);
}
return it->second[short_address];
}
@@ -1377,6 +1704,23 @@ void GatewayCache::loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups)
}
}
void GatewayCache::loadDaliStateStoreLocked(
uint8_t gateway_id, std::array<GatewayCacheDaliAddressState, 64>& states) {
for (uint8_t short_address = 0; short_address < states.size(); ++short_address) {
ClearDaliState(states[short_address]);
const auto raw = readStringLocked(ShortKey("ds", gateway_id, short_address));
if (!raw.empty()) {
ApplyDaliStatePayload(raw, states[short_address]);
if (states[short_address].status.anyKnown()) {
states[short_address].status.stale = true;
}
if (states[short_address].status.revision > dali_runtime_revision_) {
dali_runtime_revision_ = states[short_address].status.revision;
}
}
}
}
std::string GatewayCache::readStringLocked(std::string_view key) {
if (!openStorageLocked()) {
return {};
+1 -1
View File
@@ -1,7 +1,7 @@
idf_component_register(
SRCS "src/gateway_controller.cpp"
INCLUDE_DIRS "include"
REQUIRES dali_domain gateway_runtime gateway_cache freertos log
REQUIRES dali_domain gateway_runtime gateway_cache gateway_bridge freertos log
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -5,6 +5,7 @@
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -19,10 +20,11 @@ namespace gateway {
class DaliDomainService;
struct DaliRawFrame;
class GatewayBridgeService;
class GatewayRuntime;
struct GatewayControllerConfig {
uint32_t task_stack_size{6144};
uint32_t task_stack_size{12288};
UBaseType_t task_priority{5};
int color_temperature_min{2000};
int color_temperature_max{6500};
@@ -32,6 +34,11 @@ struct GatewayControllerConfig {
bool ip_router_supported{true};
bool internal_scene_supported{true};
bool internal_group_supported{true};
bool cache_supported{true};
uint32_t cache_refresh_interval_ms{120000};
uint32_t cache_refresh_idle_ms{100};
uint32_t cache_host_snooze_ms{5000};
uint32_t cache_host_echo_ms{250};
};
struct GatewayChannelSnapshot {
@@ -76,6 +83,7 @@ class GatewayController {
void addBleStateSink(BleStateSink sink);
void addWifiStateSink(WifiStateSink sink);
void addGatewayNameSink(GatewayNameSink sink);
void setBridgeService(GatewayBridgeService* bridge_service);
bool setupMode() const;
bool wirelessSetupMode() const;
@@ -96,17 +104,35 @@ class GatewayController {
GatewayCacheChannelFlags flags{};
Phase phase{Phase::kReloadFlags};
uint8_t short_address{0};
std::optional<GatewayCacheDaliTarget> target;
std::vector<uint8_t> addresses;
size_t address_index{0};
uint8_t scene_id{0};
};
struct BridgeTransportRequestState {
uint8_t version{0};
uint16_t payload_length{0};
uint8_t total_chunks{0};
std::map<uint8_t, std::vector<uint8_t>> chunks;
};
struct CacheRefreshJob {
TickType_t next_due_tick{0};
uint8_t short_address{0};
};
static void TaskEntry(void* arg);
void taskLoop();
void dispatchCommand(const std::vector<uint8_t>& command);
void scheduleReconciliation(uint8_t gateway_id);
void scheduleReconciliation(uint8_t gateway_id,
std::optional<GatewayCacheDaliTarget> target = std::nullopt);
bool hasPendingReconciliation() const;
bool cacheRefreshEnabled() const;
bool cacheMaintenanceSnoozed(uint8_t gateway_id) const;
bool runMaintenanceStep();
bool runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job);
bool runCacheRefreshStep();
void reconcileGroupStep(uint8_t gateway_id, uint8_t short_address);
void reconcileSceneStep(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id);
void reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address);
@@ -156,10 +182,15 @@ class GatewayController {
void handleAllocationCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleInternalSceneCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleInternalGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleGatewayCacheCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleBridgeTransportCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void publishBridgeTransportResponse(uint8_t gateway_id, uint8_t version, uint8_t sequence,
std::string_view response);
GatewayRuntime& runtime_;
DaliDomainService& dali_domain_;
GatewayCache& cache_;
GatewayBridgeService* bridge_service_{nullptr};
GatewayControllerConfig config_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t maintenance_lock_{nullptr};
@@ -167,7 +198,9 @@ class GatewayController {
std::vector<BleStateSink> ble_state_sinks_;
std::vector<WifiStateSink> wifi_state_sinks_;
std::vector<GatewayNameSink> gateway_name_sinks_;
std::map<uint16_t, BridgeTransportRequestState> bridge_transport_requests_;
std::map<uint8_t, ReconciliationJob> reconciliation_jobs_;
std::map<uint8_t, CacheRefreshJob> cache_refresh_jobs_;
std::atomic<int> maintenance_activity_gateway_{-1};
bool setup_mode_{false};
bool wireless_setup_mode_{false};
@@ -3,11 +3,13 @@
#include "dali_domain.hpp"
#include "esp_log.h"
#include "esp_system.h"
#include "gateway_bridge.hpp"
#include "gateway_runtime.hpp"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <optional>
#include <utility>
namespace gateway {
@@ -21,6 +23,38 @@ constexpr uint8_t kDaliSceneCount = 16;
constexpr uint8_t kDaliCmdOff = 0x00;
constexpr uint8_t kDaliCmdRecallMax = 0x05;
constexpr TickType_t kMaintenancePollTicks = pdMS_TO_TICKS(20);
constexpr TickType_t kIdleMaintenancePollTicks = pdMS_TO_TICKS(1000);
constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0;
constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1;
constexpr uint8_t kBridgeTransportVersion = 1;
constexpr size_t kBridgeTransportMaxChunkBytes = 120;
constexpr uint8_t kGatewayFeatureCache = 0x40;
constexpr uint8_t kGatewayCacheOpcode = 0x39;
constexpr uint8_t kGatewayCacheProtocolVersion = 1;
constexpr uint8_t kGatewayCacheOpSummary = 0x00;
constexpr uint8_t kGatewayCacheOpShortAddress = 0x01;
constexpr uint8_t kGatewayCacheOpGroup = 0x02;
constexpr uint8_t kGatewayCacheOpBroadcast = 0x03;
constexpr uint8_t kGatewayCacheStatusOk = 0x00;
constexpr uint8_t kGatewayCacheStatusDisabled = 0x01;
constexpr uint8_t kGatewayCacheStatusInvalidArgument = 0x02;
constexpr uint16_t kCacheFlagActualKnown = 1U << 0;
constexpr uint16_t kCacheFlagSceneKnown = 1U << 1;
constexpr uint16_t kCacheFlagUseMinLevel = 1U << 2;
constexpr uint16_t kCacheFlagStatusStale = 1U << 3;
constexpr uint16_t kCacheFlagGroupMaskKnown = 1U << 4;
constexpr uint16_t kCacheFlagPowerOnKnown = 1U << 5;
constexpr uint16_t kCacheFlagSystemFailureKnown = 1U << 6;
constexpr uint16_t kCacheFlagMinKnown = 1U << 7;
constexpr uint16_t kCacheFlagMaxKnown = 1U << 8;
constexpr uint16_t kCacheFlagFadeTimeKnown = 1U << 9;
constexpr uint16_t kCacheFlagFadeRateKnown = 1U << 10;
constexpr const char* kBridgeTransportInvalidFrameResponse =
"{\"statusCode\":400,\"error\":\"invalid bridge transport frame\","
"\"message\":\"invalid bridge transport frame\"}";
constexpr const char* kBridgeTransportUnavailableResponse =
"{\"statusCode\":500,\"error\":\"bridge service is not enabled\","
"\"message\":\"bridge service is not enabled\"}";
class LockGuard {
public:
@@ -44,6 +78,43 @@ bool AnyFlagSet(const GatewayCacheChannelFlags& flags) {
return flags.need_update_group || flags.need_update_scene || flags.need_update_settings;
}
bool SameTarget(const std::optional<GatewayCacheDaliTarget>& lhs,
const std::optional<GatewayCacheDaliTarget>& rhs) {
if (lhs.has_value() != rhs.has_value()) {
return false;
}
if (!lhs.has_value()) {
return true;
}
return lhs->kind == rhs->kind && lhs->value == rhs->value;
}
bool IsDaliHostCommandOpcode(uint8_t opcode) {
switch (opcode) {
case 0x07:
case 0x08:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
case 0x16:
case 0x17:
case 0x18:
case 0x30:
case 0x32:
case 0x37:
case 0x38:
case 0xA0:
case 0xA2:
case kBridgeTransportRequestOpcode:
return true;
default:
return false;
}
}
std::string NormalizeName(std::string_view name) {
std::string normalized(name);
if (normalized.size() > kMaxNameBytes) {
@@ -73,6 +144,79 @@ void AppendStringBytes(std::vector<uint8_t>& out, std::string_view value) {
}
}
void AppendLe16(std::vector<uint8_t>& out, uint16_t value) {
out.push_back(static_cast<uint8_t>(value & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
}
void AppendLe32(std::vector<uint8_t>& out, uint32_t value) {
out.push_back(static_cast<uint8_t>(value & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
out.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
}
uint8_t CacheByte(std::optional<uint8_t> value) {
return value.value_or(0xFF);
}
uint16_t CacheRuntimeFlags(const GatewayCacheDaliRuntimeStatus& status) {
uint16_t flags = 0;
if (status.actual_level.has_value()) {
flags |= kCacheFlagActualKnown;
}
if (status.scene_id.has_value()) {
flags |= kCacheFlagSceneKnown;
}
if (status.use_min_level) {
flags |= kCacheFlagUseMinLevel;
}
if (status.stale) {
flags |= kCacheFlagStatusStale;
}
return flags;
}
uint16_t CacheAddressFlags(const GatewayCacheDaliAddressState& state) {
uint16_t flags = CacheRuntimeFlags(state.status);
if (state.group_mask_known) {
flags |= kCacheFlagGroupMaskKnown;
}
if (state.settings.power_on_level.has_value()) {
flags |= kCacheFlagPowerOnKnown;
}
if (state.settings.system_failure_level.has_value()) {
flags |= kCacheFlagSystemFailureKnown;
}
if (state.settings.min_level.has_value()) {
flags |= kCacheFlagMinKnown;
}
if (state.settings.max_level.has_value()) {
flags |= kCacheFlagMaxKnown;
}
if (state.settings.fade_time.has_value()) {
flags |= kCacheFlagFadeTimeKnown;
}
if (state.settings.fade_rate.has_value()) {
flags |= kCacheFlagFadeRateKnown;
}
return flags;
}
uint16_t CacheSceneKnownMask(const GatewayCacheDaliAddressState& state) {
uint16_t mask = 0;
for (size_t index = 0; index < state.scene_levels.size(); ++index) {
if (state.scene_levels[index].has_value()) {
mask |= static_cast<uint16_t>(1U << index);
}
}
return mask;
}
uint16_t BridgeTransportRequestKey(uint8_t gateway_id, uint8_t sequence) {
return static_cast<uint16_t>((static_cast<uint16_t>(gateway_id) << 8) | sequence);
}
void AppendPaddedName(std::vector<uint8_t>& out, std::string_view name) {
const auto normalized = NormalizeName(name);
out.push_back(static_cast<uint8_t>(normalized.size()));
@@ -185,6 +329,10 @@ void GatewayController::addGatewayNameSink(GatewayNameSink sink) {
}
}
void GatewayController::setBridgeService(GatewayBridgeService* bridge_service) {
bridge_service_ = bridge_service;
}
bool GatewayController::setupMode() const {
return setup_mode_;
}
@@ -256,12 +404,17 @@ void GatewayController::taskLoop() {
worked = runMaintenanceStep();
}
if (!worked) {
ulTaskNotifyTake(pdTRUE, hasPendingReconciliation() ? kMaintenancePollTicks : portMAX_DELAY);
const TickType_t wait_ticks = hasPendingReconciliation()
? kMaintenancePollTicks
: cacheRefreshEnabled() ? kIdleMaintenancePollTicks
: portMAX_DELAY;
ulTaskNotifyTake(pdTRUE, wait_ticks);
}
}
}
void GatewayController::scheduleReconciliation(uint8_t gateway_id) {
void GatewayController::scheduleReconciliation(uint8_t gateway_id,
std::optional<GatewayCacheDaliTarget> target) {
if (!cache_.reconciliationEnabled()) {
return;
}
@@ -276,7 +429,16 @@ void GatewayController::scheduleReconciliation(uint8_t gateway_id) {
{
LockGuard guard(maintenance_lock_);
reconciliation_jobs_.try_emplace(gateway_id);
auto [it, inserted] = reconciliation_jobs_.try_emplace(gateway_id);
if (inserted) {
it->second.target = target;
} else if (!SameTarget(it->second.target, target)) {
it->second.target.reset();
it->second.phase = ReconciliationJob::Phase::kReloadFlags;
it->second.addresses.clear();
it->second.address_index = 0;
it->second.scene_id = 0;
}
}
if (task_handle_ != nullptr) {
@@ -289,42 +451,58 @@ bool GatewayController::hasPendingReconciliation() const {
return !reconciliation_jobs_.empty();
}
bool GatewayController::cacheRefreshEnabled() const {
return config_.cache_supported && cache_.cacheEnabled() &&
config_.cache_refresh_interval_ms > 0;
}
bool GatewayController::cacheMaintenanceSnoozed(uint8_t gateway_id) const {
return config_.cache_host_snooze_ms > 0 &&
dali_domain_.hasRecentHostActivity(gateway_id, config_.cache_host_snooze_ms);
}
bool GatewayController::runMaintenanceStep() {
if (!cache_.reconciliationEnabled()) {
return false;
}
uint8_t gateway_id = 0;
ReconciliationJob job;
{
LockGuard guard(maintenance_lock_);
if (reconciliation_jobs_.empty()) {
return false;
if (cache_.reconciliationEnabled()) {
bool has_job = false;
uint8_t gateway_id = 0;
ReconciliationJob job;
{
LockGuard guard(maintenance_lock_);
if (!reconciliation_jobs_.empty()) {
const auto it = reconciliation_jobs_.begin();
has_job = true;
gateway_id = it->first;
job = it->second;
}
}
const auto it = reconciliation_jobs_.begin();
gateway_id = it->first;
job = it->second;
}
if (runtime_.shouldYieldMaintenance(gateway_id)) {
return false;
}
if (has_job) {
if (runtime_.shouldYieldMaintenance(gateway_id)) {
return false;
}
if (cacheMaintenanceSnoozed(gateway_id)) {
return false;
}
const bool keep_job = runReconciliationStep(gateway_id, job);
const bool keep_job = runReconciliationStep(gateway_id, job);
{
LockGuard guard(maintenance_lock_);
auto it = reconciliation_jobs_.find(gateway_id);
if (it == reconciliation_jobs_.end()) {
{
LockGuard guard(maintenance_lock_);
auto it = reconciliation_jobs_.find(gateway_id);
if (it == reconciliation_jobs_.end()) {
return true;
}
if (keep_job) {
it->second = job;
} else {
reconciliation_jobs_.erase(it);
}
}
return true;
}
if (keep_job) {
it->second = job;
} else {
reconciliation_jobs_.erase(it);
}
}
return true;
return runCacheRefreshStep();
}
bool GatewayController::runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job) {
@@ -337,8 +515,13 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
return false;
}
job.short_address = 0;
job.addresses = cache_.reconciliationAddresses(gateway_id, job.target);
job.address_index = 0;
job.scene_id = 0;
if (job.addresses.empty()) {
cache_.clearChannelFlagsIfMatched(gateway_id, job.flags);
return false;
}
if (job.flags.need_update_group) {
job.phase = ReconciliationJob::Phase::kGroups;
} else if (job.flags.need_update_scene) {
@@ -350,9 +533,9 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
switch (job.phase) {
case ReconciliationJob::Phase::kGroups:
reconcileGroupStep(gateway_id, job.short_address++);
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
reconcileGroupStep(gateway_id, job.addresses[job.address_index++]);
if (job.address_index >= job.addresses.size()) {
job.address_index = 0;
if (job.flags.need_update_scene) {
job.phase = ReconciliationJob::Phase::kScenes;
} else if (job.flags.need_update_settings) {
@@ -364,15 +547,22 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
}
}
return true;
case ReconciliationJob::Phase::kScenes:
reconcileSceneStep(gateway_id, job.short_address, job.scene_id);
++job.scene_id;
case ReconciliationJob::Phase::kScenes: {
const uint8_t short_address = job.addresses[job.address_index];
if (cache_.daliAddressPresence(gateway_id, short_address) ==
GatewayCacheDaliPresence::kOffline) {
job.scene_id = 0;
++job.address_index;
} else {
reconcileSceneStep(gateway_id, short_address, job.scene_id);
++job.scene_id;
}
if (job.scene_id >= kDaliSceneCount) {
job.scene_id = 0;
++job.short_address;
++job.address_index;
}
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
if (job.address_index >= job.addresses.size()) {
job.address_index = 0;
if (job.flags.need_update_settings) {
job.phase = ReconciliationJob::Phase::kSettings;
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
@@ -382,10 +572,15 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
}
}
return true;
case ReconciliationJob::Phase::kSettings:
reconcileSettingsStep(gateway_id, job.short_address++);
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
}
case ReconciliationJob::Phase::kSettings: {
const uint8_t short_address = job.addresses[job.address_index++];
if (cache_.daliAddressPresence(gateway_id, short_address) !=
GatewayCacheDaliPresence::kOffline) {
reconcileSettingsStep(gateway_id, short_address);
}
if (job.address_index >= job.addresses.size()) {
job.address_index = 0;
if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
job.phase = ReconciliationJob::Phase::kReloadFlags;
} else {
@@ -393,12 +588,70 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
}
}
return true;
}
case ReconciliationJob::Phase::kReloadFlags:
default:
return true;
}
}
bool GatewayController::runCacheRefreshStep() {
if (!cacheRefreshEnabled()) {
return false;
}
const TickType_t interval_ticks =
std::max<TickType_t>(1, pdMS_TO_TICKS(config_.cache_refresh_interval_ms));
const auto channels = dali_domain_.channelInfo();
const TickType_t now = xTaskGetTickCount();
for (const auto& channel : channels) {
auto& job = cache_refresh_jobs_[channel.gateway_id];
if (job.next_due_tick == 0) {
job.next_due_tick = now + interval_ticks;
continue;
}
if (now < job.next_due_tick) {
continue;
}
if (runtime_.shouldYieldMaintenance(channel.gateway_id) ||
dali_domain_.isAllocAddr(channel.gateway_id) ||
cacheMaintenanceSnoozed(channel.gateway_id) ||
!dali_domain_.isBusIdle(channel.gateway_id, config_.cache_refresh_idle_ms)) {
continue;
}
auto advance_job = [&]() {
++job.short_address;
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
job.next_due_tick = xTaskGetTickCount() + interval_ticks;
} else {
job.next_due_tick = xTaskGetTickCount();
}
};
if (cache_.daliAddressPresence(channel.gateway_id, job.short_address) ==
GatewayCacheDaliPresence::kOffline) {
advance_job();
return true;
}
maintenance_activity_gateway_.store(channel.gateway_id);
const auto actual_level = dali_domain_.queryActualLevel(channel.gateway_id, job.short_address);
maintenance_activity_gateway_.store(-1);
cache_.markDaliAddressPresence(channel.gateway_id, job.short_address,
actual_level.has_value()
? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliActualLevel(channel.gateway_id, job.short_address, actual_level);
advance_job();
return true;
}
return false;
}
void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_address) {
const auto policy = cache_.priorityMode();
const auto state = cache_.daliAddressState(gateway_id, short_address);
@@ -408,6 +661,10 @@ void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_add
const bool applied = dali_domain_.applyGroupMask(gateway_id, short_address, state.group_mask);
maintenance_activity_gateway_.store(-1);
const auto verified_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
cache_.markDaliAddressPresence(gateway_id, short_address,
verified_mask.has_value()
? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliGroupMask(gateway_id, short_address, verified_mask);
if (!applied && verified_mask.has_value()) {
ESP_LOGW(kTag, "group reconcile fallback gateway=%u short=%u", gateway_id, short_address);
@@ -415,8 +672,11 @@ void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_add
return;
}
cache_.setDaliGroupMask(gateway_id, short_address,
dali_domain_.queryGroupMask(gateway_id, short_address));
const auto group_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
cache_.markDaliAddressPresence(gateway_id, short_address,
group_mask.has_value() ? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliGroupMask(gateway_id, short_address, group_mask);
}
void GatewayController::reconcileSceneStep(uint8_t gateway_id, uint8_t short_address,
@@ -431,8 +691,11 @@ void GatewayController::reconcileSceneStep(uint8_t gateway_id, uint8_t short_add
maintenance_activity_gateway_.store(-1);
}
cache_.setDaliSceneLevel(gateway_id, short_address, scene_id,
dali_domain_.querySceneLevel(gateway_id, short_address, scene_id));
const auto level = dali_domain_.querySceneLevel(gateway_id, short_address, scene_id);
cache_.markDaliAddressPresence(gateway_id, short_address,
level.has_value() ? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliSceneLevel(gateway_id, short_address, scene_id, level);
}
void GatewayController::reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address) {
@@ -453,6 +716,9 @@ void GatewayController::reconcileSettingsStep(uint8_t gateway_id, uint8_t short_
}
const auto settings = dali_domain_.queryAddressSettings(gateway_id, short_address);
cache_.markDaliAddressPresence(gateway_id, short_address,
settings.has_value() ? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
if (settings.has_value()) {
cache_.setDaliSettings(gateway_id, short_address,
GatewayCacheDaliSettingsSnapshot{settings->power_on_level,
@@ -476,10 +742,21 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
const uint8_t opcode = command[3];
const uint8_t addr = command[4];
const uint8_t data = command[5];
if (opcode == 0x09 && addr == 0x00) {
const auto ids = gatewayIds();
const auto count = std::min<size_t>(ids.size(), 16);
std::vector<uint8_t> payload{0x09, static_cast<uint8_t>(count)};
payload.insert(payload.end(), ids.begin(), ids.begin() + count);
publishPayload(gateway_id, payload);
return;
}
if (!hasGateway(gateway_id)) {
ESP_LOGW(kTag, "command for unknown gateway=%u opcode=0x%02x", gateway_id, opcode);
return;
}
if (IsDaliHostCommandOpcode(opcode)) {
dali_domain_.markHostActivity(gateway_id);
}
switch (opcode) {
case 0x00:
@@ -555,6 +832,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
if (config_.internal_group_supported) {
feature |= 0x20;
}
if (config_.cache_supported) {
feature |= kGatewayFeatureCache;
}
publishPayload(gateway_id, {0x03, gateway_id, feature});
break;
}
@@ -653,12 +933,18 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
}
}
break;
case kGatewayCacheOpcode:
handleGatewayCacheCommand(gateway_id, command);
break;
case 0xA0:
handleInternalSceneCommand(gateway_id, command);
break;
case 0xA2:
handleInternalGroupCommand(gateway_id, command);
break;
case kBridgeTransportRequestOpcode:
handleBridgeTransportCommand(gateway_id, command);
break;
default:
ESP_LOGW(kTag, "unhandled opcode=0x%02x gateway=%u", opcode, gateway_id);
break;
@@ -714,12 +1000,120 @@ void GatewayController::publishPayload(uint8_t, const std::vector<uint8_t>& payl
publishFrame(GatewayRuntime::buildNotificationFrame(payload));
}
void GatewayController::publishBridgeTransportResponse(uint8_t gateway_id, uint8_t version,
uint8_t sequence,
std::string_view response) {
const size_t total_chunks =
std::max<size_t>(1, (response.size() + kBridgeTransportMaxChunkBytes - 1) /
kBridgeTransportMaxChunkBytes);
for (size_t index = 0; index < total_chunks; ++index) {
const size_t start = index * kBridgeTransportMaxChunkBytes;
const size_t chunk_length =
std::min(kBridgeTransportMaxChunkBytes, response.size() - start);
std::vector<uint8_t> payload{
kBridgeTransportResponseOpcode,
gateway_id,
version,
sequence,
static_cast<uint8_t>(total_chunks),
static_cast<uint8_t>(index),
static_cast<uint8_t>(response.size() & 0xFF),
static_cast<uint8_t>((response.size() >> 8) & 0xFF),
static_cast<uint8_t>(chunk_length & 0xFF),
static_cast<uint8_t>((chunk_length >> 8) & 0xFF),
};
payload.reserve(payload.size() + chunk_length);
for (size_t offset = 0; offset < chunk_length; ++offset) {
payload.push_back(static_cast<uint8_t>(response[start + offset]));
}
publishPayload(gateway_id, payload);
}
}
void GatewayController::publishFrame(const std::vector<uint8_t>& frame) {
for (const auto& sink : notification_sinks_) {
sink(frame);
}
}
void GatewayController::handleBridgeTransportCommand(uint8_t gateway_id,
const std::vector<uint8_t>& command) {
const uint8_t version = command.size() > 4 ? command[4] : kBridgeTransportVersion;
const uint8_t sequence = command.size() > 5 ? command[5] : 0;
const uint16_t request_key = BridgeTransportRequestKey(gateway_id, sequence);
if (command.size() < 11) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
const uint8_t total_chunks = command[6];
const uint8_t chunk_index = command[7];
const uint16_t payload_length =
static_cast<uint16_t>(command[8] | (static_cast<uint16_t>(command[9]) << 8));
if (version != kBridgeTransportVersion || total_chunks == 0 || chunk_index >= total_chunks) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
auto& state = bridge_transport_requests_[request_key];
if (chunk_index == 0 || state.version != version || state.payload_length != payload_length ||
state.total_chunks != total_chunks) {
state = BridgeTransportRequestState{};
state.version = version;
state.payload_length = payload_length;
state.total_chunks = total_chunks;
}
const size_t payload_start = 10;
const size_t payload_end = command.size() - 1;
if (payload_end < payload_start) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
if (state.chunks.find(chunk_index) == state.chunks.end()) {
state.chunks[chunk_index] =
std::vector<uint8_t>(command.begin() + payload_start, command.begin() + payload_end);
}
if (state.chunks.size() < total_chunks) {
return;
}
std::vector<uint8_t> request_bytes;
request_bytes.reserve(payload_length);
for (uint8_t index = 0; index < total_chunks; ++index) {
const auto it = state.chunks.find(index);
if (it == state.chunks.end()) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
request_bytes.insert(request_bytes.end(), it->second.begin(), it->second.end());
}
bridge_transport_requests_.erase(request_key);
if (request_bytes.size() != payload_length) {
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
const std::string response =
bridge_service_ == nullptr
? std::string(kBridgeTransportUnavailableResponse)
: bridge_service_->handleTransportRequest(
gateway_id,
std::string_view(reinterpret_cast<const char*>(request_bytes.data()),
request_bytes.size()));
publishBridgeTransportResponse(gateway_id, version, sequence, response);
}
void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
if (frame.data.size() != 2 && frame.data.size() != 3) {
return;
@@ -739,14 +1133,18 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
}
const bool maintenance_activity = maintenance_activity_gateway_.load() == frame.gateway_id;
const bool host_echo_activity =
dali_domain_.matchesRecentHostCommandFrame(frame.gateway_id, addr, data,
config_.cache_host_echo_ms) ||
dali_domain_.hasRecentHostActivity(frame.gateway_id, config_.cache_host_echo_ms);
const bool local_activity = maintenance_activity || runtime_.hasActiveCommand(frame.gateway_id) ||
dali_domain_.isAllocAddr(frame.gateway_id);
host_echo_activity || dali_domain_.isAllocAddr(frame.gateway_id);
const bool flagged = cache_.observeDaliCommand(frame.gateway_id, addr, data,
local_activity
? GatewayCacheRawFrameOrigin::kLocalGateway
: GatewayCacheRawFrameOrigin::kOutsideBus);
if (flagged) {
scheduleReconciliation(frame.gateway_id);
scheduleReconciliation(frame.gateway_id, cache_.decodeDaliTarget(addr));
}
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) || maintenance_activity ||
@@ -775,26 +1173,31 @@ bool GatewayController::sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr
}
bool GatewayController::setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level) {
const uint8_t raw_addr = rawArcAddressFromDec(dec_address);
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, level);
const bool sent = dali_domain_.setBright(gateway_id, dec_address, level);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, rawArcAddressFromDec(dec_address), level);
cache_.mirrorDaliCommand(gateway_id, raw_addr, level);
}
return sent;
}
bool GatewayController::offAndMirror(uint8_t gateway_id, int dec_address) {
const uint8_t raw_addr = rawCommandAddressFromDec(dec_address);
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, kDaliCmdOff);
const bool sent = dali_domain_.off(gateway_id, dec_address);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address), kDaliCmdOff);
cache_.mirrorDaliCommand(gateway_id, raw_addr, kDaliCmdOff);
}
return sent;
}
bool GatewayController::onAndMirror(uint8_t gateway_id, int dec_address) {
const uint8_t raw_addr = rawCommandAddressFromDec(dec_address);
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, kDaliCmdRecallMax);
const bool sent = dali_domain_.on(gateway_id, dec_address);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address),
kDaliCmdRecallMax);
cache_.mirrorDaliCommand(gateway_id, raw_addr, kDaliCmdRecallMax);
}
return sent;
}
@@ -1230,4 +1633,90 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
}
}
void GatewayController::handleGatewayCacheCommand(uint8_t gateway_id,
const std::vector<uint8_t>& command) {
const uint8_t op = command.size() > 4 ? command[4] : kGatewayCacheOpSummary;
const uint8_t arg = command.size() > 5 ? command[5] : 0;
const bool enabled = config_.cache_supported && cache_.cacheEnabled();
if (op == kGatewayCacheOpSummary) {
const uint8_t flags = static_cast<uint8_t>((config_.cache_supported ? 0x01 : 0x00) |
(cache_.cacheEnabled() ? 0x02 : 0x00) |
(cacheRefreshEnabled() ? 0x04 : 0x00));
std::vector<uint8_t> payload{kGatewayCacheOpcode,
gateway_id,
op,
kGatewayCacheStatusOk,
kGatewayCacheProtocolVersion,
flags};
AppendLe16(payload, static_cast<uint16_t>(std::min<uint32_t>(
config_.cache_refresh_interval_ms / 1000U, 0xffffU)));
publishPayload(gateway_id, payload);
return;
}
if (!enabled) {
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusDisabled, arg});
return;
}
if (op == kGatewayCacheOpShortAddress) {
if (arg >= kDaliShortAddressCount) {
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusInvalidArgument, arg});
return;
}
const auto state = cache_.daliAddressState(gateway_id, arg);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
AppendLe16(payload, CacheAddressFlags(state));
payload.push_back(CacheByte(state.status.actual_level));
payload.push_back(CacheByte(state.status.scene_id));
AppendLe16(payload, state.group_mask_known ? state.group_mask : 0);
payload.push_back(CacheByte(state.settings.power_on_level));
payload.push_back(CacheByte(state.settings.system_failure_level));
payload.push_back(CacheByte(state.settings.min_level));
payload.push_back(CacheByte(state.settings.max_level));
payload.push_back(CacheByte(state.settings.fade_time));
payload.push_back(CacheByte(state.settings.fade_rate));
AppendLe32(payload, state.status.revision);
AppendLe16(payload, CacheSceneKnownMask(state));
for (const auto& level : state.scene_levels) {
payload.push_back(CacheByte(level));
}
publishPayload(gateway_id, payload);
return;
}
if (op == kGatewayCacheOpGroup) {
if (arg >= kDaliSceneCount) {
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusInvalidArgument, arg});
return;
}
const auto status = cache_.daliGroupStatus(gateway_id, arg);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
AppendLe16(payload, CacheRuntimeFlags(status));
payload.push_back(CacheByte(status.actual_level));
payload.push_back(CacheByte(status.scene_id));
AppendLe32(payload, status.revision);
publishPayload(gateway_id, payload);
return;
}
if (op == kGatewayCacheOpBroadcast) {
const auto status = cache_.daliBroadcastStatus(gateway_id);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, 0};
AppendLe16(payload, CacheRuntimeFlags(status));
payload.push_back(CacheByte(status.actual_level));
payload.push_back(CacheByte(status.scene_id));
AppendLe32(payload, status.revision);
publishPayload(gateway_id, payload);
return;
}
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
kGatewayCacheStatusInvalidArgument, arg});
}
} // namespace gateway
+18
View File
@@ -0,0 +1,18 @@
idf_component_register(
SRCS
"src/gateway_knx.cpp"
"src/gateway_knx_bridge.cpp"
"src/gateway_knx_router_lifecycle.cpp"
"src/gateway_knx_router_openknx.cpp"
"src/gateway_knx_router_packets.cpp"
"src/gateway_knx_router_services.cpp"
"src/gateway_knx_secure_transport.cpp"
"src/knx_device_broker.cpp"
"src/oam_router_runtime.cpp"
"src/ets_device_runtime.cpp"
"src/ets_memory_loader.cpp"
INCLUDE_DIRS "include"
REQUIRES dali_cpp esp_driver_gpio esp_driver_uart esp_hw_support esp_netif freertos log lwip knx mbedtls
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -0,0 +1,137 @@
#pragma once
#include "esp_idf_platform.h"
#include "ets_memory_loader.h"
#include "knx/bau07B0.h"
#include "knx/cemi_frame.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
class GroupObject;
namespace gateway::openknx {
class TpuartUartInterface;
class EtsDeviceRuntime {
public:
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
using CemiFrameReceiver = std::function<bool(const uint8_t* data, size_t len)>;
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
size_t len)>;
using GroupObjectWriteHandler = std::function<void(uint16_t group_object_number,
const uint8_t* data, size_t len)>;
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response)>;
using FunctionPropertyExtHandler = std::function<bool(uint16_t object_type,
uint8_t object_instance,
uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response)>;
EtsDeviceRuntime(std::string nvs_namespace,
uint16_t fallback_individual_address,
uint16_t tunnel_client_address = 0,
std::unique_ptr<TpuartUartInterface> tp_uart_interface = nullptr);
~EtsDeviceRuntime();
uint16_t individualAddress() const;
uint16_t tunnelClientAddress() const;
bool configured() const;
bool programmingMode() const;
void setProgrammingMode(bool enabled);
void toggleProgrammingMode();
EtsMemorySnapshot snapshot() const;
uint8_t paramByte(uint32_t addr) const;
bool paramBit(uint32_t addr, uint8_t shift) const;
// Accessors for OpenKNX integration (DIB construction, IP parameter object).
DeviceObject& deviceObject();
Platform& platform();
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
FunctionPropertyHandler state_handler);
void setFunctionPropertyExtHandlers(FunctionPropertyExtHandler command_handler,
FunctionPropertyExtHandler state_handler);
void setGroupWriteHandler(GroupWriteHandler handler);
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
void setBusFrameSender(CemiFrameSender sender);
void setTpFrameReceiver(CemiFrameReceiver receiver);
void setNetworkInterface(esp_netif_t* netif);
bool hasTpUart() const;
bool enableTpUart(bool enabled = true);
bool tpUartOnline() const;
bool transmitTpFrame(const uint8_t* data, size_t len);
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
bool handleLocalBroadcastManagementFrame(const uint8_t* data, size_t len,
CemiFrameSender sender);
bool handleBusFrame(const uint8_t* data, size_t len);
bool emitGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len,
CemiFrameSender sender);
void loop();
private:
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
static void EmitTunnelFrame(CemiFrame& frame, void* context);
static void HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
uint8_t data_length, void* context);
static void HandleGroupObjectWrite(GroupObject& ko);
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length);
static bool HandleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length);
static bool HandleFunctionPropertyExtCommand(uint16_t object_type,
uint8_t object_instance,
uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data,
uint8_t& result_length);
static bool HandleFunctionPropertyExtState(uint16_t object_type,
uint8_t object_instance,
uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data,
uint8_t& result_length);
static uint16_t DefaultTunnelClientAddress(uint16_t individual_address);
static bool DispatchFunctionProperty(FunctionPropertyHandler* handler, uint8_t object_index,
uint8_t property_id, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length);
static bool DispatchFunctionPropertyExt(FunctionPropertyExtHandler* handler,
uint16_t object_type,
uint8_t object_instance,
uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data,
uint8_t& result_length);
void installGroupObjectCallbacks();
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
bool shouldConsumeBusFrame(CemiFrame& frame) const;
std::string nvs_namespace_;
std::unique_ptr<TpuartUartInterface> tp_uart_interface_;
EspIdfPlatform platform_;
Bau07B0 device_;
CemiFrameSender sender_;
CemiFrameSender bus_frame_sender_;
CemiFrameReceiver tp_frame_receiver_;
GroupWriteHandler group_write_handler_;
GroupObjectWriteHandler group_object_write_handler_;
FunctionPropertyHandler command_handler_;
FunctionPropertyHandler state_handler_;
FunctionPropertyExtHandler command_ext_handler_;
FunctionPropertyExtHandler state_ext_handler_;
bool suppress_group_object_write_callback_{false};
uint16_t group_object_callback_count_{0};
};
} // namespace gateway::openknx
@@ -0,0 +1,22 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace gateway::openknx {
struct EtsAssociation {
uint16_t group_address{0};
uint16_t group_object_number{0};
};
struct EtsMemorySnapshot {
bool configured{false};
uint16_t individual_address{0};
std::vector<EtsAssociation> associations;
};
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace);
} // namespace gateway::openknx
@@ -0,0 +1,538 @@
#pragma once
#include "bridge.hpp"
#include "model_value.hpp"
#include "knx/ip_parameter_object.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "lwip/sockets.h"
#include <atomic>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
namespace gateway {
namespace openknx {
class EtsDeviceRuntime;
class OamRouterRuntime;
class TpuartUartInterface;
}
constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671;
constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12";
constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200;
constexpr uint32_t kGatewayKnxDefaultTpStartupTimeoutMs = 2000;
struct GatewayKnxTpUartConfig {
int uart_port{1};
int tx_pin{-1};
int rx_pin{-1};
uint32_t baudrate{kGatewayKnxDefaultTpBaudrate};
size_t rx_buffer_size{1024};
size_t tx_buffer_size{1024};
uint32_t startup_timeout_ms{kGatewayKnxDefaultTpStartupTimeoutMs};
uint32_t read_timeout_ms{20};
bool nine_bit_mode{true};
};
enum class GatewayKnxMappingMode : uint8_t {
kFormula = 0,
kGwReg1Direct = 1,
kManual = 2,
kEtsDatabase = 3,
};
struct GatewayKnxEtsAssociation {
uint16_t group_address{0};
uint16_t group_object_number{0};
};
struct GatewayKnxCloudRemoteConfig {
bool enabled{false};
std::string mode{"mqtt"};
std::string relay_endpoint;
std::string mqtt_topic_prefix;
std::string auth_token_ref;
bool require_secure_tunnel{true};
bool udp_punch_enabled{false};
};
struct GatewayKnxOamRouterConfig {
bool enabled{false};
bool ets_database_enabled{true};
bool secure_tunnel_enabled{true};
bool secure_routing_enabled{true};
uint16_t individual_address{0xff02};
uint16_t tunnel_address_base{0xff10};
int programming_button_gpio{-1};
bool programming_button_active_low{true};
int programming_led_gpio{-1};
bool programming_led_active_high{true};
GatewayKnxCloudRemoteConfig cloud_remote;
};
struct GatewayKnxIpSecureCredentialMaterial {
bool activated{false};
bool backbone_key_available{false};
std::array<uint8_t, 16> backbone_key{};
bool device_authentication_key_available{false};
std::array<uint8_t, 16> device_authentication_key{};
std::vector<std::array<uint8_t, 16>> tunnel_user_keys;
uint64_t routing_sequence{0};
};
struct GatewayKnxConfig {
bool dali_router_enabled{true};
bool ip_router_enabled{false};
bool tunnel_enabled{true};
bool multicast_enabled{true};
bool ets_database_enabled{true};
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
uint8_t main_group{0};
uint8_t dali_bus_id{0};
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
uint16_t ip_interface_individual_address{0xff01};
uint16_t individual_address{0xfffe};
int programming_button_gpio{-1};
bool programming_button_active_low{true};
int programming_led_gpio{-1};
bool programming_led_active_high{true};
GatewayKnxOamRouterConfig oam_router;
std::vector<GatewayKnxEtsAssociation> ets_associations;
GatewayKnxTpUartConfig tp_uart;
};
enum class GatewayKnxDaliDataType : uint8_t {
kUnknown = 0,
kSwitch = 1,
kBrightness = 2,
kColorTemperature = 3,
kRgb = 4,
kBrightnessRelative = 5,
kScene = 6,
};
enum class GatewayKnxDaliTargetKind : uint8_t {
kNone = 0,
kBroadcast = 1,
kShortAddress = 2,
kGroup = 3,
};
struct GatewayKnxDaliTarget {
GatewayKnxDaliTargetKind kind{GatewayKnxDaliTargetKind::kNone};
int address{-1};
};
struct GatewayKnxDaliBinding {
uint16_t group_address{0};
uint8_t main_group{0};
uint8_t middle_group{0};
uint8_t sub_group{0};
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
int group_object_number{-1};
int channel_index{-1};
std::string address;
std::string name;
std::string object_role;
std::string datapoint_type;
GatewayKnxDaliDataType data_type{GatewayKnxDaliDataType::kUnknown};
GatewayKnxDaliTarget target;
};
struct GatewayKnxCommissioningBallast {
uint8_t high{0};
uint8_t middle{0};
uint8_t low{0};
uint8_t short_address{0xff};
};
struct GatewayKnxReg1ScanOptions {
bool only_new{false};
bool randomize{false};
bool delete_all{false};
bool assign{false};
};
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
std::optional<GatewayKnxOamRouterConfig> GatewayKnxOamRouterConfigFromValue(
const DaliValue* value);
DaliValue GatewayKnxOamRouterConfigToValue(const GatewayKnxOamRouterConfig& config);
bool GatewayKnxConfigUsesTpUart(const GatewayKnxConfig& config);
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode);
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value);
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type);
const char* GatewayKnxTargetKindToString(GatewayKnxDaliTargetKind kind);
std::optional<GatewayKnxDaliDataType> GatewayKnxDaliDataTypeForMiddleGroup(
uint8_t middle_group);
std::optional<GatewayKnxDaliTarget> GatewayKnxDaliTargetForSubgroup(uint8_t sub_group);
uint16_t GatewayKnxGroupAddress(uint8_t main_group, uint8_t middle_group,
uint8_t sub_group);
std::string GatewayKnxGroupAddressString(uint16_t group_address);
class GatewayKnxBridge {
public:
explicit GatewayKnxBridge(DaliBridgeEngine& engine);
~GatewayKnxBridge();
void setConfig(const GatewayKnxConfig& config);
void setRuntimeContext(const openknx::EtsDeviceRuntime* runtime);
const GatewayKnxConfig& config() const;
size_t etsBindingCount() const;
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
bool matchesGroupAddress(uint16_t group_address) const;
DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data,
size_t len);
DaliBridgeResult handleGroupObjectWrite(uint16_t group_object_number,
const uint8_t* data, size_t len);
bool handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
private:
DaliBridgeResult executeForDecodedWrite(uint16_t group_address,
GatewayKnxDaliDataType data_type,
GatewayKnxDaliTarget target,
const uint8_t* data, size_t len);
DaliBridgeResult executeReg1SceneWrite(uint16_t group_address, const uint8_t* data,
size_t len);
DaliBridgeResult executeEtsBindings(uint16_t group_address,
const std::vector<GatewayKnxDaliBinding>& bindings,
const uint8_t* data, size_t len);
void rebuildEtsBindings();
bool handleReg1TypeCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1ScanCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1AssignCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1EvgWriteCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1EvgReadCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1SetSceneCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1GetSceneCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1IdentifyCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1ScanState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1AssignState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1FoundEvgsState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
static void CommissioningScanTaskEntry(void* arg);
void runCommissioningScanTask();
DaliBridgeEngine& engine_;
GatewayKnxConfig config_;
const openknx::EtsDeviceRuntime* runtime_{nullptr};
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
SemaphoreHandle_t commissioning_lock_{nullptr};
TaskHandle_t commissioning_scan_task_{nullptr};
std::atomic_bool commissioning_scan_cancel_requested_{false};
GatewayKnxReg1ScanOptions commissioning_scan_options_;
bool commissioning_scan_done_{true};
bool commissioning_assign_done_{true};
std::vector<GatewayKnxCommissioningBallast> commissioning_found_ballasts_;
};
class GatewayKnxTpIpRouter {
public:
using GroupWriteHandler = std::function<DaliBridgeResult(uint16_t group_address,
const uint8_t* data,
size_t len)>;
using GroupObjectWriteHandler = std::function<DaliBridgeResult(uint16_t group_object_number,
const uint8_t* data,
size_t len)>;
using RoutingSequenceStoreHandler = std::function<void(uint64_t sequence)>;
using CloudCemiPublisher = std::function<void(const uint8_t* data, size_t len)>;
struct CloudCemiStats {
bool enabled{false};
uint64_t uplink_frames{0};
uint64_t downlink_frames{0};
};
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge,
std::string openknx_namespace = "openknx");
~GatewayKnxTpIpRouter();
void setConfig(const GatewayKnxConfig& config);
void setCommissioningOnly(bool enabled);
void setGroupWriteHandler(GroupWriteHandler handler);
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
void setOamIpSecureCredentials(const GatewayKnxIpSecureCredentialMaterial& credentials);
void setOamIpSecureRoutingSequenceStoreHandler(RoutingSequenceStoreHandler handler);
void setCloudCemiPublisher(CloudCemiPublisher publisher);
const GatewayKnxConfig& config() const;
bool injectCloudCemiFrame(const uint8_t* data, size_t len);
CloudCemiStats cloudCemiStats() const;
bool tpUartOnline() const;
bool programmingMode();
esp_err_t setProgrammingMode(bool enabled);
esp_err_t toggleProgrammingMode();
bool oamProgrammingMode();
esp_err_t setOamProgrammingMode(bool enabled);
esp_err_t toggleOamProgrammingMode();
esp_err_t start(uint32_t task_stack_size, UBaseType_t task_priority);
esp_err_t stop();
bool started() const;
const std::string& lastError() const;
bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level);
private:
bool handleFunctionPropertyExtCommand(uint16_t object_type,
uint8_t object_instance,
uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleFunctionPropertyExtState(uint16_t object_type,
uint8_t object_instance,
uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
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};
uint8_t last_tunnel_confirmation_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{};
std::vector<uint8_t> last_received_cemi;
std::vector<uint8_t> last_tunnel_confirmation_packet;
std::vector<uint8_t> last_sent_cemi;
uint16_t secure_session_id{0};
bool oam_router_persona{false};
TickType_t last_sent_cemi_tick{0};
};
struct SecureSession {
bool active{false};
bool authenticated{false};
uint16_t session_id{0};
int tcp_sock{-1};
::sockaddr_in remote{};
std::array<uint8_t, 32> client_public_key{};
std::array<uint8_t, 32> server_public_key{};
std::array<uint8_t, 32> shared_secret{};
std::array<uint8_t, 16> session_key{};
uint64_t send_sequence{0};
uint64_t receive_sequence{0};
uint8_t user_id{0};
TickType_t last_activity_tick{0};
};
static void TaskEntry(void* arg);
esp_err_t initializeRuntime();
void taskLoop();
void finishTask();
void closeSockets();
bool configureSocket();
void handleTcpAccept();
void handleTcpClient(TcpClient& client);
void closeTcpClient(TcpClient& client);
void closeSecureSessionsForTcp(int sock);
std::unique_ptr<openknx::TpuartUartInterface> createOpenKnxTpUartInterface();
bool configureTpUart();
bool configureProgrammingGpio();
void refreshNetworkInterfaces(bool force_log = false);
void handleUdpDatagram(const uint8_t* data, size_t len, const ::sockaddr_in& remote);
void handleSearchRequest(uint16_t service, const uint8_t* packet_data, size_t len,
const ::sockaddr_in& remote);
void handleDescriptionRequest(const uint8_t* packet_data, size_t len,
const ::sockaddr_in& remote);
void handleRoutingIndication(const uint8_t* packet_data, size_t len);
void handleTunnellingRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote);
void handleDeviceConfigurationRequest(const uint8_t* packet_data, size_t len,
const ::sockaddr_in& remote);
void handleConnectRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote);
void handleConnectionStateRequest(const uint8_t* packet_data, size_t len,
const ::sockaddr_in& remote);
void handleDisconnectRequest(const uint8_t* packet_data, size_t len, const ::sockaddr_in& remote);
void handleSecureService(uint16_t service, const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleSecureSessionRequest(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleSecureWrapper(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleSecureGroupSync(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
const ::sockaddr_in& remote);
void sendDeviceConfigurationAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
const ::sockaddr_in& remote);
void sendConnectionHeaderAck(uint16_t service, uint8_t channel_id, uint8_t sequence,
uint8_t status, const ::sockaddr_in& remote);
void sendSecureSessionStatus(uint8_t status, const ::sockaddr_in& remote);
void sendTunnelIndication(const uint8_t* data, size_t len);
void sendTunnelIndicationToClient(TunnelClient& client, const uint8_t* data, size_t len);
bool sendCemiFrameToClient(TunnelClient& client, uint16_t service,
const uint8_t* data, size_t len);
void sendConnectionStateResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote);
void sendDisconnectResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote);
void sendConnectResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote, uint8_t connection_type,
uint16_t tunnel_address);
void sendRoutingIndication(const uint8_t* data, size_t len);
bool sendPacket(const std::vector<uint8_t>& packet, const ::sockaddr_in& remote);
bool sendPacketToTunnelClient(const TunnelClient& client,
const std::vector<uint8_t>& packet);
bool currentTransportAllowsTcpHpai() const;
std::optional<std::array<uint8_t, 8>> localHpaiForRemote(const ::sockaddr_in& remote,
bool tcp = false) const;
// --- OpenKNX-backed DIB construction (uses KnxIpSearchResponse / KnxIpDescriptionResponse) ---
std::vector<uint8_t> buildOpenKnxSearchResponse(const ::sockaddr_in& remote) const;
std::vector<uint8_t> buildOpenKnxDescriptionResponse(const ::sockaddr_in& remote) const;
// --- Hand-rolled DIB builders (fallback when OpenKNX is unavailable) ---
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);
SecureSession* findSecureSession(uint16_t session_id, const ::sockaddr_in& remote);
const SecureSession* findSecureSession(uint16_t session_id,
const ::sockaddr_in& remote) const;
SecureSession* allocateSecureSession(const ::sockaddr_in& remote);
SecureSession* activeSecureSession();
bool wrapSecurePacket(SecureSession& session, const std::vector<uint8_t>& inner,
std::vector<uint8_t>* wrapped);
bool wrapSecureRoutingPacket(const std::vector<uint8_t>& inner,
std::vector<uint8_t>* wrapped);
bool decryptSecureWrapper(SecureSession& session, const uint8_t* body, size_t len,
std::vector<uint8_t>* inner);
bool decryptSecureRoutingWrapper(const uint8_t* body, size_t len,
std::vector<uint8_t>* inner);
bool verifySecureSessionAuth(SecureSession& session, const uint8_t* packet,
size_t len, uint8_t* status);
bool secureCredentialsReady() const;
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, uint16_t response_service,
const uint8_t* suppress_routing_echo = nullptr,
size_t suppress_routing_echo_len = 0);
bool handleOamRouterTunnelFrame(const uint8_t* data, size_t len,
TunnelClient* response_client, uint16_t response_service,
const uint8_t* suppress_routing_echo = nullptr,
size_t suppress_routing_echo_len = 0);
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
bool transmitOpenKnxTpFrame(const uint8_t* data, size_t len);
bool transmitOpenKnxTpFrameLocked(const uint8_t* data, size_t len);
bool handleOpenKnxTpIngressFrame(const uint8_t* data, size_t len);
void publishCloudCemiFrame(const uint8_t* data, size_t len);
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
bool routeOpenKnxGroupWrite(const uint8_t* data, size_t len, const char* context);
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
bool shouldRouteDaliApplicationFrames() const;
uint8_t advertisedMedium() const;
const char* ipInterfaceName() const;
const char* ipInterfaceFriendlyName() const;
void syncOpenKnxConfigFromDevice();
uint16_t effectiveIpInterfaceIndividualAddress() const;
uint16_t effectiveKnxDeviceIndividualAddress() const;
uint16_t effectiveTunnelAddress() const;
void pollProgrammingButton();
void updateProgrammingLed();
void setProgrammingLed(bool on);
void setOamProgrammingLed(bool on);
GatewayKnxBridge& bridge_;
GroupWriteHandler group_write_handler_;
GroupObjectWriteHandler group_object_write_handler_;
RoutingSequenceStoreHandler routing_sequence_store_handler_;
CloudCemiPublisher cloud_cemi_publisher_;
std::string openknx_namespace_;
GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
std::unique_ptr<openknx::OamRouterRuntime> oam_router_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t openknx_lock_{nullptr};
SemaphoreHandle_t startup_semaphore_{nullptr};
esp_err_t startup_result_{ESP_OK};
std::atomic_bool stop_requested_{false};
std::atomic_bool started_{false};
int udp_sock_{-1};
int tcp_sock_{-1};
int active_tcp_sock_{-1};
int tp_uart_port_{-1};
int tp_uart_tx_pin_{-1};
int tp_uart_rx_pin_{-1};
std::vector<uint32_t> multicast_joined_interfaces_;
TickType_t network_refresh_tick_{0};
std::array<TcpClient, kMaxTcpClients> tcp_clients_{};
std::array<TunnelClient, kMaxTunnelClients> tunnel_clients_{};
std::array<SecureSession, kMaxTcpClients> secure_sessions_{};
std::unique_ptr<IpParameterObject> knx_ip_parameters_;
uint8_t last_tunnel_channel_id_{0};
uint16_t last_secure_session_id_{0};
uint16_t active_secure_session_id_{0};
GatewayKnxIpSecureCredentialMaterial oam_ip_secure_credentials_{};
bool tp_uart_online_{false};
bool commissioning_only_{false};
std::atomic_bool openknx_configured_{false};
std::atomic<uint64_t> cloud_cemi_uplink_frames_{0};
std::atomic<uint64_t> cloud_cemi_downlink_frames_{0};
bool programming_button_last_pressed_{false};
bool programming_led_state_{false};
TickType_t programming_button_last_toggle_tick_{0};
bool oam_programming_mode_{false};
bool oam_programming_button_last_pressed_{false};
bool oam_programming_led_state_{false};
TickType_t oam_programming_button_last_toggle_tick_{0};
std::string last_error_;
};
} // namespace gateway
@@ -0,0 +1,146 @@
#pragma once
// Internal helpers and product identity shared by gateway_knx component sources.
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "sdkconfig.h"
#include "soc/uart_periph.h"
#include <cstdint>
#include <string>
namespace gateway {
namespace knx_internal {
constexpr const char* kTag = "gateway_knx";
#ifndef CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID
#define CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID 0x00A4
#endif
#ifndef CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER
#define CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER 0x0001
#endif
#ifndef CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION
#define CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION 0x08
#endif
#ifndef CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID
#define CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID 0xA401
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID 0x00FA
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER 0xA11F
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION 0x07
#endif
#ifndef CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID
#define CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID 0x0001
#endif
inline constexpr uint16_t kReg1DaliManufacturerId =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID);
inline constexpr uint16_t kReg1DaliHardwareId =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OEM_HARDWARE_ID);
inline constexpr uint16_t kReg1DaliApplicationNumber =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OEM_APPLICATION_NUMBER);
inline constexpr uint8_t kReg1DaliApplicationVersion =
static_cast<uint8_t>(CONFIG_GATEWAY_KNX_OEM_APPLICATION_VERSION);
inline constexpr uint8_t kReg1DaliHardwareType[6] = {
0x00,
0x00,
static_cast<uint8_t>((kReg1DaliHardwareId >> 8) & 0xff),
static_cast<uint8_t>(kReg1DaliHardwareId & 0xff),
kReg1DaliApplicationVersion,
0x00};
inline constexpr uint8_t kReg1DaliOrderNumber[10] = {
'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
inline constexpr uint8_t kReg1DaliProgramVersion[5] = {
static_cast<uint8_t>((kReg1DaliManufacturerId >> 8) & 0xff),
static_cast<uint8_t>(kReg1DaliManufacturerId & 0xff),
static_cast<uint8_t>((kReg1DaliApplicationNumber >> 8) & 0xff),
static_cast<uint8_t>(kReg1DaliApplicationNumber & 0xff),
kReg1DaliApplicationVersion};
inline constexpr uint32_t kDaliMaxKnxInstanceCount = 16;
inline constexpr uint32_t kReg1DaliSerialMacIncrement = 0;
inline constexpr uint32_t kOamRouterSerialMacIncrement = kDaliMaxKnxInstanceCount;
inline constexpr uint16_t kOamRouterDeviceDescriptor = 0x091A;
inline constexpr uint16_t kOamRouterManufacturerId =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID);
inline constexpr uint16_t kOamRouterHardwareId =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID);
inline constexpr uint16_t kOamRouterApplicationNumber =
static_cast<uint16_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER);
inline constexpr uint8_t kOamRouterApplicationVersion =
static_cast<uint8_t>(CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION);
inline constexpr uint8_t kOamRouterHardwareType[6] = {
0x00,
0x00,
static_cast<uint8_t>((kOamRouterHardwareId >> 8) & 0xff),
static_cast<uint8_t>(kOamRouterHardwareId & 0xff),
kOamRouterApplicationVersion,
0x00};
inline constexpr uint8_t kOamRouterOrderNumber[10] = {
'I', 'P', '-', 'R', 'o', 'u', 't', 'e', 'r', 0};
inline constexpr uint8_t kOamRouterProgramVersion[5] = {
static_cast<uint8_t>((kOamRouterManufacturerId >> 8) & 0xff),
static_cast<uint8_t>(kOamRouterManufacturerId & 0xff),
static_cast<uint8_t>((kOamRouterApplicationNumber >> 8) & 0xff),
static_cast<uint8_t>(kOamRouterApplicationNumber & 0xff),
kOamRouterApplicationVersion};
// RAII semaphore guard.
class SemaphoreGuard {
public:
explicit SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore_(semaphore) {
if (semaphore_ != nullptr) {
xSemaphoreTake(semaphore_, portMAX_DELAY);
locked_ = true;
}
}
~SemaphoreGuard() {
if (locked_) {
xSemaphoreGive(semaphore_);
}
}
private:
SemaphoreHandle_t semaphore_{nullptr};
bool locked_{false};
};
// Resolve a UART IO pin from config or SoC defaults.
inline 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 knx_internal
} // namespace gateway
@@ -0,0 +1,38 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <vector>
namespace gateway {
enum class KnxPortKind : uint8_t {
kIpTunnel,
kIpRouting,
kTpUart,
kCloud,
kRfReserved,
};
struct KnxIngressContext {
KnxPortKind port{KnxPortKind::kIpTunnel};
bool oam_persona_hint{false};
bool broadcast_management{false};
};
class KnxResponseDeduplicator {
public:
KnxResponseDeduplicator() = default;
KnxResponseDeduplicator(const uint8_t* original, size_t len);
bool remember(const uint8_t* data, size_t len);
bool remember(const std::vector<uint8_t>& data);
size_t suppressedCount() const;
private:
std::vector<uint8_t> original_;
std::vector<std::vector<uint8_t>> sent_;
size_t suppressed_count_{0};
};
} // namespace gateway
@@ -0,0 +1,69 @@
#pragma once
#include "esp_idf_platform.h"
#include "ets_memory_loader.h"
#include "esp_netif.h"
#include "freertos/FreeRTOS.h"
#include "knx/cemi_frame.h"
#include "knx/device_object.h"
#include "knx/platform.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#if defined(ENABLE_BAU091A_PERSONA)
#include "knx/bau091A.h"
#endif
namespace gateway::openknx {
class OamRouterRuntime {
public:
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
OamRouterRuntime(std::string nvs_namespace,
uint16_t fallback_individual_address,
uint16_t tunnel_client_address = 0);
~OamRouterRuntime();
bool available() const;
uint16_t individualAddress() const;
uint16_t tunnelClientAddress() const;
bool configured() const;
bool programmingMode() const;
void setProgrammingMode(bool enabled);
void toggleProgrammingMode();
EtsMemorySnapshot snapshot() const;
DeviceObject* deviceObject();
Platform* platform();
void setNetworkInterface(esp_netif_t* netif);
void setBusFrameSender(CemiFrameSender sender);
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
bool handleLocalBroadcastManagementFrame(const uint8_t* data, size_t len,
CemiFrameSender sender);
bool handleBusFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
void loop();
private:
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
static void EmitTunnelFrame(CemiFrame& frame, void* context);
static uint16_t DefaultTunnelClientAddress(uint16_t individual_address);
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
bool shouldConsumeBusFrame(CemiFrame& frame) const;
std::string nvs_namespace_;
CemiFrameSender sender_;
CemiFrameSender bus_frame_sender_;
#if defined(ENABLE_BAU091A_PERSONA)
EspIdfPlatform platform_;
Bau091A device_;
#endif
};
} // namespace gateway::openknx
@@ -0,0 +1,225 @@
<?xml version="1.0" encoding="utf-8"?>
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
<ManufacturerData>
<Manufacturer RefId="M-00FA">
<Languages>
<Language Identifier="en-US">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%_PT-DelayBase_EN-0"><Translation AttributeName="Text" Text="second(s)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase_EN-1"><Translation AttributeName="Text" Text="minute(s)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase_EN-2"><Translation AttributeName="Text" Text="hour(s)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase10_EN-3"><Translation AttributeName="Text" Text="1/10 second(s)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase10_EN-0"><Translation AttributeName="Text" Text="second(s)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase10_EN-1"><Translation AttributeName="Text" Text="minute(s)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase10_EN-2"><Translation AttributeName="Text" Text="hour(s)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNo_EN-0"><Translation AttributeName="Text" Text="No" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNo_EN-1"><Translation AttributeName="Text" Text="Yes" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNoInverted_EN-1"><Translation AttributeName="Text" Text="No" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNoInverted_EN-0"><Translation AttributeName="Text" Text="Yes" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt1_EN-0"><Translation AttributeName="Text" Text="OFF (0)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt1_EN-1"><Translation AttributeName="Text" Text="ON (1)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2_EN-4"><Translation AttributeName="Text" Text="not used" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2_EN-0"><Translation AttributeName="Text" Text="normal OFF (00)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2_EN-1"><Translation AttributeName="Text" Text="normal ON (01)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2_EN-2"><Translation AttributeName="Text" Text="priority OFF (10)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2_EN-3"><Translation AttributeName="Text" Text="priority ON (11)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2Out_EN-0"><Translation AttributeName="Text" Text="normal OFF (00)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2Out_EN-1"><Translation AttributeName="Text" Text="normal ON (01)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2Out_EN-2"><Translation AttributeName="Text" Text="priority OFF (10)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ValueDpt2Out_EN-3"><Translation AttributeName="Text" Text="priority ON (11)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CombinedTimeDate_EN-1"><Translation AttributeName="Text" Text="one combined communication object" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CombinedTimeDate_EN-0"><Translation AttributeName="Text" Text="two separate communication objects" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-0"><Translation AttributeName="Text" Text="Communication object 'daylight saving time active'" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-1"><Translation AttributeName="Text" Text="Combined date/time communication object (DPT 19)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-2"><Translation AttributeName="Text" Text="Internal calculation" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeDE_EN-0"><Translation AttributeName="Text" Text="Communication object 'daylight saving time active'" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeDE_EN-2"><Translation AttributeName="Text" Text="Internal calculation" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeWorld_EN-0"><Translation AttributeName="Text" Text="Communication object 'daylight saving time active'" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeWorld_EN-1"><Translation AttributeName="Text" Text="Combined date/time communication object (DPT 19)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeKO_EN-0"><Translation AttributeName="Text" Text="Communication object 'daylight saving time active'" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-1"><Translation AttributeName="Text" Text="Amsterdam, Berlin, Bern, Rome, Vienna (+1 hour)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-31"><Translation AttributeName="Text" Text="Custom" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-27"><Translation AttributeName="Text" Text="UTC -11 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-26"><Translation AttributeName="Text" Text="UTC -10 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-25"><Translation AttributeName="Text" Text="UTC -9 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-24"><Translation AttributeName="Text" Text="UTC -8 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-23"><Translation AttributeName="Text" Text="UTC -7 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-22"><Translation AttributeName="Text" Text="UTC -6 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-21"><Translation AttributeName="Text" Text="UTC -5 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-20"><Translation AttributeName="Text" Text="UTC -4 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-19"><Translation AttributeName="Text" Text="UTC -3 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-18"><Translation AttributeName="Text" Text="UTC -2 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-17"><Translation AttributeName="Text" Text="UTC -1 hour" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-0"><Translation AttributeName="Text" Text="UTC +0 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-28"><Translation AttributeName="Text" Text="UTC +1 hour" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-2"><Translation AttributeName="Text" Text="UTC +2 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-3"><Translation AttributeName="Text" Text="UTC +3 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-4"><Translation AttributeName="Text" Text="UTC +4 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-5"><Translation AttributeName="Text" Text="UTC +5 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-6"><Translation AttributeName="Text" Text="UTC +6 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-7"><Translation AttributeName="Text" Text="UTC +7 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-8"><Translation AttributeName="Text" Text="UTC +8 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-9"><Translation AttributeName="Text" Text="UTC +9 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-10"><Translation AttributeName="Text" Text="UTC +10 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-11"><Translation AttributeName="Text" Text="UTC +11 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-12"><Translation AttributeName="Text" Text="UTC +12 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-0"><Translation AttributeName="Text" Text="Disabled" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-1"><Translation AttributeName="Text" Text="Every hour" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-2"><Translation AttributeName="Text" Text="Every 2 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-4"><Translation AttributeName="Text" Text="Every 4 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-6"><Translation AttributeName="Text" Text="Every 6 hours" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-24"><Translation AttributeName="Text" Text="Daily" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-168"><Translation AttributeName="Text" Text="Weekly" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ManualSave_EN-0"><Translation AttributeName="Text" Text="Disabled" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ManualSave_EN-1"><Translation AttributeName="Text" Text="Active with 5 min. write protection" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ManualSave_EN-2"><Translation AttributeName="Text" Text="Active with 15 min. write protection" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ManualSave_EN-3"><Translation AttributeName="Text" Text="Active with 60 min. write protection" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-0"><Translation AttributeName="Text" Text="Disabled" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-1"><Translation AttributeName="Text" Text="Prog LED" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-2"><Translation AttributeName="Text" Text="Device status" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-3"><Translation AttributeName="Text" Text="Bus status" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-4"><Translation AttributeName="Text" Text="Time status" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-10"><Translation AttributeName="Text" Text="Network status" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CustomHostname_EN-0"><Translation AttributeName="Text" Text="Default" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CustomHostname_EN-1"><Translation AttributeName="Text" Text="Custom" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-0"><Translation AttributeName="Text" Text="In programming mode" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-1"><Translation AttributeName="Text" Text="Always active" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-2"><Translation AttributeName="Text" Text="Disabled" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-LanMode_EN-0"><Translation AttributeName="Text" Text="Automatic" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-LanMode_EN-3"><Translation AttributeName="Text" Text="10 MBit/s (power saving)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-LanMode_EN-1"><Translation AttributeName="Text" Text="100 MBit/s" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000002"><Translation AttributeName="Text" Text="Time base" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000003"><Translation AttributeName="Text" Text="Time" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000147"><Translation AttributeName="Text" Text="Enable watchdog" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000144"><Translation AttributeName="Text" Text="Time zone" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000156"><Translation AttributeName="Text" Text="Receive via" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000159"><Translation AttributeName="Text" Text="Determine daylight saving time by" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100011"><Translation AttributeName="Text" Text="IP address" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100012"><Translation AttributeName="Text" Text="Subnet mask" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100013"><Translation AttributeName="Text" Text="Default gateway" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100001"><Translation AttributeName="Text" Text="Customize hostname" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100032"><Translation AttributeName="Text" Text="Web server" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100052"><Translation AttributeName="Text" Text="LAN mode" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100061"><Translation AttributeName="Text" Text="Time server" /></TranslationElement>
<TranslationElement RefId="%AID%_M-1100001"><Translation AttributeName="Text" Text="Must be no more than 24 characters and consist only of letters, numbers, and hyphens." /></TranslationElement>
<TranslationElement RefId="%AID%_CH-BASE"><Translation AttributeName="Text" Text="OpenKNX" /></TranslationElement>
<TranslationElement RefId="%AID%_CH-NET"><Translation AttributeName="Text" Text="Network" /></TranslationElement>
<TranslationElement RefId="%AID%_MD-11000100_B-1100000"><Translation AttributeName="Text" Text="Transfer" /></TranslationElement>
</TranslationUnit>
</Language>
<Language Identifier="zh-CN">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%_PT-DelayBase_EN-0"><Translation AttributeName="Text" Text="秒" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase_EN-1"><Translation AttributeName="Text" Text="分钟" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase_EN-2"><Translation AttributeName="Text" Text="小时" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase10_EN-3"><Translation AttributeName="Text" Text="1/10 秒" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNo_EN-0"><Translation AttributeName="Text" Text="否" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNo_EN-1"><Translation AttributeName="Text" Text="是" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNoInverted_EN-1"><Translation AttributeName="Text" Text="否" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNoInverted_EN-0"><Translation AttributeName="Text" Text="是" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CombinedTimeDate_EN-1"><Translation AttributeName="Text" Text="一个组合通信对象" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CombinedTimeDate_EN-0"><Translation AttributeName="Text" Text="两个独立通信对象" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-0"><Translation AttributeName="Text" Text="通信对象“夏令时激活”" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-1"><Translation AttributeName="Text" Text="组合日期/时间通信对象 (DPT 19)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-2"><Translation AttributeName="Text" Text="内部计算" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeDE_EN-0"><Translation AttributeName="Text" Text="通信对象“夏令时激活”" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeDE_EN-2"><Translation AttributeName="Text" Text="内部计算" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeWorld_EN-0"><Translation AttributeName="Text" Text="通信对象“夏令时激活”" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeWorld_EN-1"><Translation AttributeName="Text" Text="组合日期/时间通信对象 (DPT 19)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeKO_EN-0"><Translation AttributeName="Text" Text="通信对象“夏令时激活”" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-1"><Translation AttributeName="Text" Text="阿姆斯特丹、柏林、伯尔尼、罗马、维也纳(+1 小时)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-31"><Translation AttributeName="Text" Text="自定义" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-0"><Translation AttributeName="Text" Text="已禁用" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ManualSave_EN-0"><Translation AttributeName="Text" Text="已禁用" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-0"><Translation AttributeName="Text" Text="已禁用" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-1"><Translation AttributeName="Text" Text="编程 LED" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-2"><Translation AttributeName="Text" Text="设备状态" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-3"><Translation AttributeName="Text" Text="总线状态" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-4"><Translation AttributeName="Text" Text="时间状态" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-10"><Translation AttributeName="Text" Text="网络状态" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CustomHostname_EN-0"><Translation AttributeName="Text" Text="默认" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CustomHostname_EN-1"><Translation AttributeName="Text" Text="自定义" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-0"><Translation AttributeName="Text" Text="编程模式下" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-1"><Translation AttributeName="Text" Text="始终启用" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-2"><Translation AttributeName="Text" Text="已禁用" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-LanMode_EN-0"><Translation AttributeName="Text" Text="自动" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-LanMode_EN-3"><Translation AttributeName="Text" Text="10 MBit/s(省电)" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000002"><Translation AttributeName="Text" Text="时间基准" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000003"><Translation AttributeName="Text" Text="时间" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000147"><Translation AttributeName="Text" Text="启用看门狗" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000144"><Translation AttributeName="Text" Text="时区" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000156"><Translation AttributeName="Text" Text="接收方式" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000159"><Translation AttributeName="Text" Text="夏令时判定方式" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100011"><Translation AttributeName="Text" Text="IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100012"><Translation AttributeName="Text" Text="子网掩码" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100013"><Translation AttributeName="Text" Text="默认网关" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100001"><Translation AttributeName="Text" Text="自定义主机名" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100032"><Translation AttributeName="Text" Text="Web 服务器" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100052"><Translation AttributeName="Text" Text="LAN 模式" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100061"><Translation AttributeName="Text" Text="时间服务器" /></TranslationElement>
<TranslationElement RefId="%AID%_M-1100001"><Translation AttributeName="Text" Text="最多 24 个字符,且只能包含字母、数字和连字符。" /></TranslationElement>
<TranslationElement RefId="%AID%_CH-NET"><Translation AttributeName="Text" Text="网络" /></TranslationElement>
<TranslationElement RefId="%AID%_MD-11000100_B-1100000"><Translation AttributeName="Text" Text="传输" /></TranslationElement>
</TranslationUnit>
</Language>
<Language Identifier="de-DE">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-2"><Translation AttributeName="Text" Text="Gerätestatus" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100032"><Translation AttributeName="Text" Text="Webserver" /></TranslationElement>
<TranslationElement RefId="%AID%_M-1100001"><Translation AttributeName="Text" Text="Darf nur 24 Zeichen lang sein und muss aus Buchstaben, Zahlen und Bindestrichen bestehen." /></TranslationElement>
</TranslationUnit>
</Language>
<Language Identifier="ja-JP">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%_PT-DelayBase_EN-0"><Translation AttributeName="Text" Text="秒" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase_EN-1"><Translation AttributeName="Text" Text="分" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase_EN-2"><Translation AttributeName="Text" Text="時間" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-DelayBase10_EN-3"><Translation AttributeName="Text" Text="1/10 秒" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNo_EN-0"><Translation AttributeName="Text" Text="いいえ" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNo_EN-1"><Translation AttributeName="Text" Text="はい" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNoInverted_EN-1"><Translation AttributeName="Text" Text="いいえ" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OnOffYesNoInverted_EN-0"><Translation AttributeName="Text" Text="はい" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CombinedTimeDate_EN-1"><Translation AttributeName="Text" Text="1 つの結合通信オブジェクト" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CombinedTimeDate_EN-0"><Translation AttributeName="Text" Text="2 つの個別通信オブジェクト" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-0"><Translation AttributeName="Text" Text="通信オブジェクト「夏時間有効」" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-1"><Translation AttributeName="Text" Text="結合日付/時刻通信オブジェクト (DPT 19)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SummertimeAll_EN-2"><Translation AttributeName="Text" Text="内部計算" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-1"><Translation AttributeName="Text" Text="アムステルダム、ベルリン、ベルン、ローマ、ウィーン(+1 時間)" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-Timezone_EN-31"><Translation AttributeName="Text" Text="カスタム" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-PeriodicSave_EN-0"><Translation AttributeName="Text" Text="無効" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-ManualSave_EN-0"><Translation AttributeName="Text" Text="無効" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-0"><Translation AttributeName="Text" Text="無効" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-1"><Translation AttributeName="Text" Text="Prog LED" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-2"><Translation AttributeName="Text" Text="デバイス状態" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-3"><Translation AttributeName="Text" Text="バス状態" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-4"><Translation AttributeName="Text" Text="時刻状態" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-SLEDFunc_EN-10"><Translation AttributeName="Text" Text="ネットワーク状態" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CustomHostname_EN-0"><Translation AttributeName="Text" Text="デフォルト" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-CustomHostname_EN-1"><Translation AttributeName="Text" Text="カスタム" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-0"><Translation AttributeName="Text" Text="プログラミングモード時" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-1"><Translation AttributeName="Text" Text="常に有効" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-OTAUpdate_EN-2"><Translation AttributeName="Text" Text="無効" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-LanMode_EN-0"><Translation AttributeName="Text" Text="自動" /></TranslationElement>
<TranslationElement RefId="%AID%_PT-LanMode_EN-3"><Translation AttributeName="Text" Text="10 MBit/s(省電力)" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000002"><Translation AttributeName="Text" Text="時間基準" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000003"><Translation AttributeName="Text" Text="時間" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000147"><Translation AttributeName="Text" Text="ウォッチドッグを有効化" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000144"><Translation AttributeName="Text" Text="タイムゾーン" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000156"><Translation AttributeName="Text" Text="受信方法" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1000159"><Translation AttributeName="Text" Text="夏時間の判定方法" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100011"><Translation AttributeName="Text" Text="IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100012"><Translation AttributeName="Text" Text="サブネットマスク" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100013"><Translation AttributeName="Text" Text="デフォルトゲートウェイ" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100001"><Translation AttributeName="Text" Text="ホスト名をカスタマイズ" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100032"><Translation AttributeName="Text" Text="Web サーバー" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100052"><Translation AttributeName="Text" Text="LAN モード" /></TranslationElement>
<TranslationElement RefId="%AID%_UP-1100061"><Translation AttributeName="Text" Text="時刻サーバー" /></TranslationElement>
<TranslationElement RefId="%AID%_M-1100001"><Translation AttributeName="Text" Text="24 文字以内で、文字、数字、ハイフンのみを使用してください。" /></TranslationElement>
<TranslationElement RefId="%AID%_CH-NET"><Translation AttributeName="Text" Text="ネットワーク" /></TranslationElement>
<TranslationElement RefId="%AID%_MD-11000100_B-1100000"><Translation AttributeName="Text" Text="転送" /></TranslationElement>
</TranslationUnit>
</Language>
</Languages>
</Manufacturer>
</ManufacturerData>
</KNX>
@@ -0,0 +1,448 @@
<?xml version="1.0" encoding="utf-8"?>
<KNX xmlns:op="http://github.com/OpenKNX/OpenKNXproducer" xmlns="http://knx.org/xml/project/20" CreatedBy="KNX MT" ToolVersion="5.1.255.16695">
<ManufacturerData>
<Manufacturer>
<ApplicationPrograms>
<ApplicationProgram>
<Static>
<ParameterTypes>
<ParameterType Id="%AID%_PT-Bool" Name="Bool">
<TypeRestriction Base="Value" SizeInBit="1">
<Enumeration Text="Disabled" Value="0" Id="%AID%_PT-Bool_EN-0" />
<Enumeration Text="Enabled" Value="1" Id="%AID%_PT-Bool_EN-1" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="%AID%_PT-RoutingWithFilter" Name="RoutingWithFilter">
<TypeRestriction Base="Value" SizeInBit="2">
<Enumeration Text="Block" Value="2" Id="%AID%_PT-RoutingWithFilter_EN-2" />
<Enumeration Text="Route" Value="1" Id="%AID%_PT-RoutingWithFilter_EN-1" />
<Enumeration Text="Filter" Value="3" Id="%AID%_PT-RoutingWithFilter_EN-3" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="%AID%_PT-GrpAcknowledge" Name="GrpAcknowledge">
<TypeRestriction Base="Value" SizeInBit="1">
<Enumeration Text="Always" Value="0" Id="%AID%_PT-GrpAcknowledge_EN-0" />
<Enumeration Text="Only when routed" Value="1" Id="%AID%_PT-GrpAcknowledge_EN-1" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="%AID%_PT-BroadcastLock" Name="BroadcastLock">
<TypeRestriction Base="Value" SizeInBit="1">
<Enumeration Text="Block" Value="1" Id="%AID%_PT-BroadcastLock_EN-1" />
<Enumeration Text="Route" Value="0" Id="%AID%_PT-BroadcastLock_EN-0" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="%AID%_PT-PhysAcknowledge" Name="PhysAcknowledge">
<TypeRestriction Base="Value" SizeInBit="2">
<Enumeration Text="Only when routed" Value="1" Id="%AID%_PT-PhysAcknowledge_EN-1" />
<Enumeration Text="Always" Value="2" Id="%AID%_PT-PhysAcknowledge_EN-2" />
<Enumeration Text="Always reject (NACK)" Value="3" Id="%AID%_PT-PhysAcknowledge_EN-3" />
</TypeRestriction>
</ParameterType>
<ParameterType Id="%AID%_PT-TunnelResOpt" Name="TunnelResOpt">
<TypeRestriction Base="Value" SizeInBit="2">
<Enumeration Text="Reject tunnel connection" Value="1" Id="%AID%_PT-TunnelResOpt_EN-1" />
<Enumeration Text="Disconnect previous tunnel connection" Value="2" Id="%AID%_PT-TunnelResOpt_EN-2" />
<Enumeration Text="Use another free tunnel" Value="3" Id="%AID%_PT-TunnelResOpt_EN-3" />
</TypeRestriction>
</ParameterType>
</ParameterTypes>
<Parameters>
<Parameter Id="%AID%_P-9900002" Name="AckOfPhysTelSubMain" ParameterType="%AID%_PT-PhysAcknowledge" Text="ACK for physically addressed telegrams" Value="1">
<Property ObjectType="6" PropertyId="53" Offset="0" BitOffset="0" />
</Parameter>
<Parameter Id="%AID%_P-9900003" Name="AckOfGrpTelSubMain" ParameterType="%AID%_PT-GrpAcknowledge" Text="ACK for group telegrams" Value="1">
<Property ObjectType="6" PropertyId="53" Offset="0" BitOffset="2" />
</Parameter>
<Parameter Id="%AID%_P-9900004" Name="BroadcastTelSubMain" ParameterType="%AID%_PT-BroadcastLock" Text="Broadcast telegrams" Value="0">
<Property ObjectType="6" PropertyId="53" Offset="0" BitOffset="4" />
</Parameter>
<Parameter Id="%AID%_P-9900005" Name="PhysTelSubMain" ParameterType="%AID%_PT-RoutingWithFilter" Text="Physically addressed telegrams" Value="3">
<Property ObjectType="6" PropertyId="53" Offset="0" BitOffset="6" />
</Parameter>
<Parameter Id="%AID%_P-9900006" Name="GrpTelSubMain_14_31" ParameterType="%AID%_PT-RoutingWithFilter" Text="Group telegrams (main group 14 - 31)" Value="3">
<Property ObjectType="6" PropertyId="55" Offset="0" BitOffset="4" />
</Parameter>
<Parameter Id="%AID%_P-9900007" Name="GrpTelSubMain_0_13" ParameterType="%AID%_PT-RoutingWithFilter" Text="Group telegrams (main group 0 - 13)" Value="3">
<Property ObjectType="6" PropertyId="55" Offset="0" BitOffset="6" />
</Parameter>
<Parameter Id="%AID%_P-9900008" Name="RepetitionBroadcastTelMainSub" ParameterType="%AID%_PT-Bool" Text="Repetition of broadcast telegrams" Value="1">
<Property ObjectType="6" PropertyId="52" Offset="0" BitOffset="3" />
</Parameter>
<Parameter Id="%AID%_P-9900009" Name="BroadcastTelMainSub" ParameterType="%AID%_PT-BroadcastLock" Text="Broadcast telegrams" Value="0">
<Property ObjectType="6" PropertyId="52" Offset="0" BitOffset="4" />
</Parameter>
<Parameter Id="%AID%_P-9900010" Name="RepetitionPhysTelMainSub" ParameterType="%AID%_PT-Bool" Text="Repetition of physically addressed telegrams" Value="1">
<Property ObjectType="6" PropertyId="52" Offset="0" BitOffset="5" />
</Parameter>
<Parameter Id="%AID%_P-9900011" Name="PhysTelMainSub" ParameterType="%AID%_PT-RoutingWithFilter" Text="Physically addressed telegrams" Value="3">
<Property ObjectType="6" PropertyId="52" Offset="0" BitOffset="6" />
</Parameter>
<Parameter Id="%AID%_P-9900012" Name="RepetitionGrpTelMainSub" ParameterType="%AID%_PT-Bool" Text="Repetition of group telegrams" Value="1">
<Property ObjectType="6" PropertyId="54" Offset="0" BitOffset="3" />
</Parameter>
<Parameter Id="%AID%_P-9900013" Name="GrpTelMainSub_14_31" ParameterType="%AID%_PT-RoutingWithFilter" Text="Group telegrams (main group 14 - 31)" Value="3">
<Property ObjectType="6" PropertyId="54" Offset="0" BitOffset="4" />
</Parameter>
<Parameter Id="%AID%_P-9900014" Name="GrpTelMainSub_0_13" ParameterType="%AID%_PT-RoutingWithFilter" Text="Group telegrams (main group 0 - 13)" Value="3">
<Property ObjectType="6" PropertyId="54" Offset="0" BitOffset="6" />
</Parameter>
</Parameters>
<ParameterRefs>
<ParameterRef Id="%AID%_P-9900002_R-990000201" RefId="%AID%_P-9900002" Tag="42" />
<ParameterRef Id="%AID%_P-9900003_R-990000301" RefId="%AID%_P-9900003" Tag="27" />
<ParameterRef Id="%AID%_P-9900004_R-990000401" RefId="%AID%_P-9900004" Tag="26" />
<ParameterRef Id="%AID%_P-9900005_R-990000501" RefId="%AID%_P-9900005" Tag="25" />
<ParameterRef Id="%AID%_P-9900006_R-990000601" RefId="%AID%_P-9900006" Tag="24" />
<ParameterRef Id="%AID%_P-9900007_R-990000701" RefId="%AID%_P-9900007" Tag="23" />
<ParameterRef Id="%AID%_P-9900008_R-990000801" RefId="%AID%_P-9900008" Tag="34" />
<ParameterRef Id="%AID%_P-9900009_R-990000901" RefId="%AID%_P-9900009" Tag="31" />
<ParameterRef Id="%AID%_P-9900010_R-990001001" RefId="%AID%_P-9900010" Tag="33" />
<ParameterRef Id="%AID%_P-9900011_R-990001101" RefId="%AID%_P-9900011" Tag="30" />
<ParameterRef Id="%AID%_P-9900012_R-990001201" RefId="%AID%_P-9900012" Tag="32" />
<ParameterRef Id="%AID%_P-9900013_R-990001301" RefId="%AID%_P-9900013" Tag="29" />
<ParameterRef Id="%AID%_P-9900014_R-990001401" RefId="%AID%_P-9900014" Tag="28" />
</ParameterRefs>
<ComObjectTable>
</ComObjectTable>
<ComObjectRefs>
</ComObjectRefs>
</Static>
<Dynamic>
<Channel Id="%AID%_CH-%PREFIX%-Routing" Number="%PREFIX%" Name="OAM IP Router" Text="OAM IP Router" Icon="router-network">
<ParameterBlock Id="%AID%_PB-nnn" Name="RoutingKNXIP" Text="Routing KNX -&gt; IP" Icon="router">
<ParameterSeparator Id="%AID%_PS-" Text="Settings for routing telegrams from TP to IP" UIHint="Headline" />
<ParameterRefRef RefId="%AID%_P-9900007_R-990000701" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900006_R-990000601" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900005_R-990000501" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900004_R-990000401" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900003_R-990000301" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900002_R-990000201" IndentLevel="1" />
<choose ParamRefId="%AID%_P-9900002_R-990000201">
<when test="3">
<ParameterSeparator Id="%AID%_PS-" Text="With always reject (NACK), programming is only possible over IP." />
</when>
</choose>
</ParameterBlock>
<ParameterBlock Id="%AID%_PB-nnn" Name="RoutingIPKNX" Text="Routing IP -&gt; KNX" Icon="router">
<ParameterSeparator Id="%AID%_PS-" Text="Settings for routing telegrams from IP to TP" UIHint="Headline" />
<ParameterRefRef RefId="%AID%_P-9900014_R-990001401" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900013_R-990001301" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900011_R-990001101" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900009_R-990000901" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900012_R-990001201" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900010_R-990001001" IndentLevel="1" />
<ParameterRefRef RefId="%AID%_P-9900008_R-990000801" IndentLevel="1" />
</ParameterBlock>
<ParameterBlock Id="%AID%_PB-nnn" Name="Tunnel" Text="Tunnel connections" Icon="lan-connect">
<ParameterSeparator Id="%AID%_PS-" Text="Settings for reserved tunnel connections" UIHint="Headline" />
<ParameterSeparator Id="%AID%_PS-nnn" Text="The physical addresses of tunnel connections are configured in the ETS topology view." UIHint="Information" />
<ParameterBlock Id="%AID%_PB-nnn" Inline="true" Layout="Grid">
<Rows>
<Row Id="%AID%_PB-nnn_R-1" />
</Rows>
<Columns>
<Column Id="%AID%_PB-nnn_C-1" Width="2%" />
<Column Id="%AID%_PB-nnn_C-2" Width="18%" />
<Column Id="%AID%_PB-nnn_C-3" Width="5%" />
<Column Id="%AID%_PB-nnn_C-4" Width="20%" />
<Column Id="%AID%_PB-nnn_C-5" Width="50%" />
</Columns>
<ParameterSeparator Id="%AID%_PS-nnn" Text="Assignment" UIHint="Headline" Cell="1,2" />
<ParameterSeparator Id="%AID%_PS-nnn" Text="IP address" UIHint="Headline" Cell="1,4" />
<ParameterSeparator Id="%AID%_PS-nnn" Text="Behavior when tunnel is busy" UIHint="Headline" Cell="1,5" />
</ParameterBlock>
<op:include href="Gateway-OAM-IP-Router.templ.xml" xpath="//Dynamic/ChannelIndependentBlock/*" IsInner="true" type="template" prefix="ROUTE" />
</ParameterBlock>
</Channel>
</Dynamic>
</ApplicationProgram>
</ApplicationPrograms>
<Languages>
<Language Identifier="zh-CN">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%_CH-%PREFIX%-Routing">
<Translation AttributeName="Name" Text="OAM IP 路由器" />
<Translation AttributeName="Text" Text="OAM IP 路由器" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-Bool_EN-0">
<Translation AttributeName="Text" Text="禁用" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-Bool_EN-1">
<Translation AttributeName="Text" Text="启用" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-2">
<Translation AttributeName="Text" Text="阻止" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-1">
<Translation AttributeName="Text" Text="路由" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-3">
<Translation AttributeName="Text" Text="过滤" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-GrpAcknowledge_EN-0">
<Translation AttributeName="Text" Text="始终" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-GrpAcknowledge_EN-1">
<Translation AttributeName="Text" Text="仅在路由时" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-BroadcastLock_EN-1">
<Translation AttributeName="Text" Text="阻止" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-BroadcastLock_EN-0">
<Translation AttributeName="Text" Text="路由" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-1">
<Translation AttributeName="Text" Text="仅在路由时" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-2">
<Translation AttributeName="Text" Text="始终" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-3">
<Translation AttributeName="Text" Text="始终拒绝 (NACK)" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-1">
<Translation AttributeName="Text" Text="拒绝隧道连接" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-2">
<Translation AttributeName="Text" Text="断开之前的隧道连接" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-3">
<Translation AttributeName="Text" Text="使用另一个空闲隧道" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900002">
<Translation AttributeName="Text" Text="物理寻址报文的 ACK" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900003">
<Translation AttributeName="Text" Text="组报文的 ACK" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900004">
<Translation AttributeName="Text" Text="广播报文" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900005">
<Translation AttributeName="Text" Text="物理寻址报文" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900006">
<Translation AttributeName="Text" Text="组报文(主组 14 - 31" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900007">
<Translation AttributeName="Text" Text="组报文(主组 0 - 13" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900008">
<Translation AttributeName="Text" Text="重复广播报文" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900009">
<Translation AttributeName="Text" Text="广播报文" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900010">
<Translation AttributeName="Text" Text="重复物理寻址报文" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900011">
<Translation AttributeName="Text" Text="物理寻址报文" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900012">
<Translation AttributeName="Text" Text="重复组报文" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900013">
<Translation AttributeName="Text" Text="组报文(主组 14 - 31" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900014">
<Translation AttributeName="Text" Text="组报文(主组 0 - 13" />
</TranslationElement>
</TranslationUnit>
</Language>
<Language Identifier="de-DE">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%_CH-%PREFIX%-Routing">
<Translation AttributeName="Name" Text="OAM IP-Router" />
<Translation AttributeName="Text" Text="OAM IP-Router" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-Bool_EN-0">
<Translation AttributeName="Text" Text="Deaktiviert" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-Bool_EN-1">
<Translation AttributeName="Text" Text="Aktiviert" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-2">
<Translation AttributeName="Text" Text="Sperren" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-1">
<Translation AttributeName="Text" Text="Weiterleiten" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-3">
<Translation AttributeName="Text" Text="Filtern" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-GrpAcknowledge_EN-0">
<Translation AttributeName="Text" Text="Immer" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-GrpAcknowledge_EN-1">
<Translation AttributeName="Text" Text="Nur wenn weitergeleitet" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-BroadcastLock_EN-1">
<Translation AttributeName="Text" Text="Sperren" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-BroadcastLock_EN-0">
<Translation AttributeName="Text" Text="Weiterleiten" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-1">
<Translation AttributeName="Text" Text="Nur wenn weitergeleitet" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-2">
<Translation AttributeName="Text" Text="Immer" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-3">
<Translation AttributeName="Text" Text="Immer ablehnen (NACK)" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-1">
<Translation AttributeName="Text" Text="Tunnelverbindung ablehnen" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-2">
<Translation AttributeName="Text" Text="Vorherige Tunnelverbindung trennen" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-3">
<Translation AttributeName="Text" Text="Anderen freien Tunnel verwenden" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900002">
<Translation AttributeName="Text" Text="ACK für physikalisch adressierte Telegramme" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900003">
<Translation AttributeName="Text" Text="ACK für Gruppentelegramme" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900004">
<Translation AttributeName="Text" Text="Broadcast-Telegramme" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900005">
<Translation AttributeName="Text" Text="Physikalisch adressierte Telegramme" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900006">
<Translation AttributeName="Text" Text="Gruppentelegramme (Hauptgruppe 14 - 31)" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900007">
<Translation AttributeName="Text" Text="Gruppentelegramme (Hauptgruppe 0 - 13)" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900008">
<Translation AttributeName="Text" Text="Wiederholung von Broadcast-Telegrammen" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900009">
<Translation AttributeName="Text" Text="Broadcast-Telegramme" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900010">
<Translation AttributeName="Text" Text="Wiederholung von physikalisch adressierten Telegrammen" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900011">
<Translation AttributeName="Text" Text="Physikalisch adressierte Telegramme" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900012">
<Translation AttributeName="Text" Text="Wiederholung von Gruppentelegrammen" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900013">
<Translation AttributeName="Text" Text="Gruppentelegramme (Hauptgruppe 14 - 31)" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900014">
<Translation AttributeName="Text" Text="Gruppentelegramme (Hauptgruppe 0 - 13)" />
</TranslationElement>
</TranslationUnit>
</Language>
<Language Identifier="ja-JP">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%_CH-%PREFIX%-Routing">
<Translation AttributeName="Name" Text="OAM IP ルーター" />
<Translation AttributeName="Text" Text="OAM IP ルーター" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-Bool_EN-0">
<Translation AttributeName="Text" Text="無効" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-Bool_EN-1">
<Translation AttributeName="Text" Text="有効" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-2">
<Translation AttributeName="Text" Text="ブロック" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-1">
<Translation AttributeName="Text" Text="ルーティング" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-RoutingWithFilter_EN-3">
<Translation AttributeName="Text" Text="フィルター" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-GrpAcknowledge_EN-0">
<Translation AttributeName="Text" Text="常時" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-GrpAcknowledge_EN-1">
<Translation AttributeName="Text" Text="ルーティング時のみ" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-BroadcastLock_EN-1">
<Translation AttributeName="Text" Text="ブロック" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-BroadcastLock_EN-0">
<Translation AttributeName="Text" Text="ルーティング" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-1">
<Translation AttributeName="Text" Text="ルーティング時のみ" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-2">
<Translation AttributeName="Text" Text="常時" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-PhysAcknowledge_EN-3">
<Translation AttributeName="Text" Text="常時拒否 (NACK)" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-1">
<Translation AttributeName="Text" Text="トンネル接続を拒否" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-2">
<Translation AttributeName="Text" Text="以前のトンネル接続を切断" />
</TranslationElement>
<TranslationElement RefId="%AID%_PT-TunnelResOpt_EN-3">
<Translation AttributeName="Text" Text="別の空きトンネルを使用" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900002">
<Translation AttributeName="Text" Text="物理アドレス宛テレグラムの ACK" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900003">
<Translation AttributeName="Text" Text="グループテレグラムの ACK" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900004">
<Translation AttributeName="Text" Text="ブロードキャストテレグラム" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900005">
<Translation AttributeName="Text" Text="物理アドレス宛テレグラム" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900006">
<Translation AttributeName="Text" Text="グループテレグラム (メイングループ 14 - 31)" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900007">
<Translation AttributeName="Text" Text="グループテレグラム (メイングループ 0 - 13)" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900008">
<Translation AttributeName="Text" Text="ブロードキャストテレグラムの繰り返し" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900009">
<Translation AttributeName="Text" Text="ブロードキャストテレグラム" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900010">
<Translation AttributeName="Text" Text="物理アドレス宛テレグラムの繰り返し" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900011">
<Translation AttributeName="Text" Text="物理アドレス宛テレグラム" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900012">
<Translation AttributeName="Text" Text="グループテレグラムの繰り返し" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900013">
<Translation AttributeName="Text" Text="グループテレグラム (メイングループ 14 - 31)" />
</TranslationElement>
<TranslationElement RefId="%AID%_P-9900014">
<Translation AttributeName="Text" Text="グループテレグラム (メイングループ 0 - 13)" />
</TranslationElement>
</TranslationUnit>
</Language>
</Languages>
<Baggages>
<Baggage TargetPath="" Name="Help_en.zip" Id="%FILE-HELP-de%">
<FileInfo TimeInfo="%DATETIME%" />
</Baggage>
<Baggage TargetPath="" Name="Icons.zip" Id="%FILE-ICONS%">
<FileInfo TimeInfo="%DATETIME%" />
</Baggage>
</Baggages>
</Manufacturer>
</ManufacturerData>
</KNX>
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8" ?>
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
<ManufacturerData>
<Manufacturer RefId="M-00FA">
<ApplicationPrograms>
<ApplicationProgram>
<Static>
<Parameters>
<Parameter Id="%AID%_P-%TT%%CC%001" Name="ResTunnel%C%" ParameterType="%AID%_PT-CheckBox" Text="Reserve tunnel %C%" Value="0">
<Property ObjectType="11" PropertyId="201" Offset="%C-1%" BitOffset="0" />
</Parameter>
<Parameter Id="%AID%_P-%TT%%CC%002" Name="Tunnel%C%IP" ParameterType="%AID%_PT-HostAddress" Text="IP address for tunnel %C%" Value="0.0.0.0">
<Property ObjectType="11" PropertyId="202" Offset="%C*4-4%" BitOffset="0" />
</Parameter>
<Parameter Id="%AID%_P-%TT%%CC%003" Name="OptTunnel%C%" ParameterType="%AID%_PT-TunnelResOpt" Text="Behavior when tunnel is busy" Value="1">
<Property ObjectType="11" PropertyId="201" Offset="%C-1%" BitOffset="1" />
</Parameter>
</Parameters>
<ParameterRefs>
<ParameterRef Id="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" RefId="%AID%_P-%TT%%CC%001" />
<ParameterRef Id="%AID%_P-%TT%%CC%002_R-%TT%%CC%00201" RefId="%AID%_P-%TT%%CC%002" />
<ParameterRef Id="%AID%_P-%TT%%CC%003_R-%TT%%CC%00301" RefId="%AID%_P-%TT%%CC%003" />
</ParameterRefs>
<BusInterfaces>
<BusInterface Id="%AID%_BI-%C%" AddressIndex="%C%" AccessType="Tunneling" Text="Tunnel %C%" />
</BusInterfaces>
</Static>
<Dynamic>
<ChannelIndependentBlock>
<ParameterBlock Id="%AID%_PB-nnn" Inline="true" Layout="Grid">
<Rows>
<Row Id="%AID%_PB-nnn_R-1" />
</Rows>
<Columns>
<Column Id="%AID%_PB-nnn_C-1" Width="2%" />
<Column Id="%AID%_PB-nnn_C-2" Width="18%" />
<Column Id="%AID%_PB-nnn_C-3" Width="5%" />
<Column Id="%AID%_PB-nnn_C-4" Width="20%" />
<Column Id="%AID%_PB-nnn_C-5" Width="50%" />
</Columns>
<ParameterSeparator Id="%AID%_PS-nnn" Text="Tunnel %C%" Cell="1,2" />
<ParameterRefRef RefId="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101" Cell="1,3" />
<choose ParamRefId="%AID%_P-%TT%%CC%001_R-%TT%%CC%00101">
<when test="1">
<ParameterRefRef RefId="%AID%_P-%TT%%CC%002_R-%TT%%CC%00201" Cell="1,4" />
<ParameterRefRef RefId="%AID%_P-%TT%%CC%003_R-%TT%%CC%00301" Cell="1,5" />
</when>
</choose>
</ParameterBlock>
</ChannelIndependentBlock>
</Dynamic>
</ApplicationProgram>
</ApplicationPrograms>
</Manufacturer>
</ManufacturerData>
</KNX>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8" ?>
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
<op:config name="%IsSecureEnabled%" value="true" />
<op:config name="%NET_IPConfigTool%" value="1" />
<op:config name="%Tunnelcount%" value="16" />
<op:config name="%MaxUserEntries%" value="17" />
<op:config name="%NET_ServiceNTP%" value="1" />
<op:config name="%SecurityProxyGroupKeyTableEntries%" value="3000" />
<op:config name="%BASE_Info1LedLabel%" value=" (N/A)" />
<op:config name="%BASE_Info2LedLabel%" value=" (IP)" />
<op:config name="%BASE_Info3LedLabel%" value=" (KNX)" />
<op:config name="%BASE_Info1LedFunc%" value="0" />
<op:config name="%BASE_Info2LedFunc%" value="10" />
<op:config name="%BASE_Info3LedFunc%" value="3" />
<op:config name="%BASE_Info1LedFunc_Access%" value="ReadWrite" />
<op:config name="%BASE_Info2LedFunc_Access%" value="ReadWrite" />
<op:config name="%BASE_Info3LedFunc_Access%" value="ReadWrite" />
<op:nowarn id="1" regex="%BASE_" />
<op:nowarn id="3" regex="BASE_getUnsupportedEtsModules" />
<op:ETS OpenKnxId="0xA1"
ApplicationNumber="31"
ApplicationVersion="0.7"
ReplacesVersions="0.6 0.4 0.3 0.2 0.1"
ApplicationRevision="0"
ProductName="Gateway OAM IP Router"
SerialNumber="1"
OrderNumber="GatewayOAMIPRouter"
ApplicationName="Gateway OAM IP Router"
IsRailMounted="true"
BusCurrent="30"
MaskVersion="MV-091A" />
<op:define prefix="BASE" ModuleType="10">
<op:verify File="deps/OGM-Common/library.json" ModuleVersion="1.7" />
</op:define>
<op:define prefix="NET" ModuleType="11" share="deps/OFM-Network/src/Network.share.xml">
<op:verify File="deps/OFM-Network/library.json" ModuleVersion="0.5" />
</op:define>
<op:define prefix="ROUTE" NumChannels="%Tunnelcount%" ModuleType="99" share="Gateway-OAM-IP-Router.share.xml" template="Gateway-OAM-IP-Router.templ.xml" />
<generate base="Gateway-SecureTemplateRouter.xml" />
</KNX>
@@ -0,0 +1,348 @@
<?xml version="1.0" encoding="utf-8" ?>
<KNX xmlns="http://knx.org/xml/project/20" xmlns:op="http://github.com/OpenKNX/OpenKNXproducer">
<ManufacturerData>
<Manufacturer RefId="M-00FA">
<Catalog>
<CatalogSection Id="M-00FA_CS-OpenKNX" Name="OpenKNX" Number="OpenKNX" DefaultLanguage="en-US">
<CatalogItem Id="%CatalogItemId%" Name="OpenKNX: %CatalogName%%BuildSuffixText%" Number="1" ProductRefId="%ProductId%" Hardware2ProgramRefId="%Hardware2ProgramId%" DefaultLanguage="en-US" />
</CatalogSection>
</Catalog>
<ApplicationPrograms>
<op:version OpenKnxId="%OpenKnxId%" ApplicationNumber="%ApplicationNumber%" ApplicationVersion="%ApplicationVersion%" ReplacesVersions="%ReplacesVersions%" ApplicationRevision="%ApplicationRevision%" FirmwareRevision="%FirmwareRevision%" />
<ApplicationProgram Id="%AID%" ProgramType="ApplicationProgram" MaskVersion="%MaskVersion%" Name="%ApplicationName%%BuildSuffix%" LoadProcedureStyle="MergedProcedure" PeiType="0" DefaultLanguage="en-US" IsSecureEnabled="%IsSecureEnabled%" MaxUserEntries="%MaxUserEntries%" MaxSecurityGroupKeyTableEntries="3000" MaxSecurityIndividualAddressEntries="1000" MaxSecurityProxyGroupKeyTableEntries="%SecurityProxyGroupKeyTableEntries%" DynamicTableManagement="false" Linkable="false" MinEtsVersion="6.0" ContextHelpFile="%FILE-HELP-de%" IconFile="%FILE-ICONS%" IPConfig="Custom" ApplicationNumber="0" ApplicationVersion="0" ReplacesVersions="0" AdditionalAddressesCount="%Tunnelcount%" MaxTunnelingUserEntries="%Tunnelcount%">
<Static>
<Code>
<AbsoluteSegment Id="%AID%_AS-0100" Address="256" Size="256" />
</Code>
<ParameterTypes>
<op:include href="deps/OGM-Common/src/Common.share.xml" xpath="//ParameterTypes/ParameterType" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/Static/ParameterTypes/ParameterType" prefix="%prefix%" />
</ParameterTypes>
<Parameters>
<op:include href="deps/OGM-Common/src/Common.Router.share.xml" xpath="//ApplicationProgram/Static/Parameters/Parameter|//ApplicationProgram/Static/Parameters/Union" type="parameter" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/Static/Parameters/Parameter|//ApplicationProgram/Static/Parameters/Union" type="parameter" prefix="%prefix%" />
<op:includetemplate href="%templ%" xpath="//ApplicationProgram/Static/Parameters/Parameter|//ApplicationProgram/Static/Parameters/Union" type="template" prefix="%prefix%" />
</Parameters>
<ParameterRefs>
<op:include href="deps/OGM-Common/src/Common.Router.share.xml" xpath="//ApplicationProgram/Static/ParameterRefs/ParameterRef" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/Static/ParameterRefs/ParameterRef" prefix="%prefix%" />
<op:includetemplate href="%templ%" xpath="//ApplicationProgram/Static/ParameterRefs/ParameterRef" type="template" prefix="%prefix%" />
</ParameterRefs>
<ParameterCalculations>
<op:include href="deps/OGM-Common/src/Common.share.xml" xpath="//ApplicationProgram/Static/ParameterCalculations/ParameterCalculation" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/Static/ParameterCalculations/ParameterCalculation" prefix="%prefix%" />
<op:includetemplate href="%templ%" xpath="//ApplicationProgram/Static/ParameterCalculations/ParameterCalculation" type="template" prefix="%prefix%" />
</ParameterCalculations>
<ParameterValidations>
<op:include href="deps/OGM-Common/src/Common.share.xml" xpath="//ApplicationProgram/Static/ParameterValidations/ParameterValidation" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/Static/ParameterValidations/ParameterValidation" prefix="%prefix%" />
<op:includetemplate href="%templ%" xpath="//ApplicationProgram/Static/ParameterValidations/ParameterValidation" type="template" prefix="%prefix%" />
</ParameterValidations>
<AddressTable MaxEntries="2047" />
<AssociationTable MaxEntries="2047" />
<LoadProcedures>
<LoadProcedure MergeId="1">
<LdCtrlCompareProp InlineData="00FA" ObjIdx="0" PropId="12">
<OnError Cause="CompareMismatch" MessageRef="%AID%_M-1" />
</LdCtrlCompareProp>
<LdCtrlCompareProp InlineData="%VersionCheck%" ObjIdx="0" PropId="78">
<OnError Cause="CompareMismatch" MessageRef="%AID%_M-2" />
</LdCtrlCompareProp>
</LoadProcedure>
<LoadProcedure MergeId="4">
<LdCtrlWriteMem Address="256" Size="256" Verify="true" />
<LdCtrlWriteProp ObjType="11" PropId="94" Count="1" Verify="true" />
<LdCtrlWriteProp ObjType="11" PropId="97" Count="%Tunnelcount%" Verify="true" />
<LdCtrlWriteProp ObjType="11" PropId="201" Count="%Tunnelcount%" Verify="true" />
<LdCtrlWriteProp ObjType="11" PropId="202" Count="%Tunnelcount%" Verify="true" />
<LdCtrlWriteProp ObjType="6" PropId="81" Count="%SecurityProxyGroupKeyTableEntries%" Verify="true" />
</LoadProcedure>
</LoadProcedures>
<Messages>
<Message Id="%AID%_M-1" Name="VersionMismatch" Text="Manufacturer identifier is incompatible." />
<Message Id="%AID%_M-2" Name="VersionMismatch" Text="Hardware type or firmware version is incompatible." />
<op:include href="deps/OGM-Common/src/Common.share.xml" xpath="//ApplicationProgram/Static/Messages/*" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/Static/Messages/*" prefix="%prefix%" />
</Messages>
<Script>
<op:include href="deps/OGM-Common/src/Common.share.xml" xpath="//ApplicationProgram/Static/Script/text()" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/Static/Script/text()" prefix="%prefix%" />
</Script>
<BusInterfaces>
<op:include href="Gateway-OAM-IP-Router.templ.xml" xpath="//BusInterfaces/BusInterface" type="template" prefix="ROUTE" />
</BusInterfaces>
<Options LineCoupler0912NewProgrammingStyle="true" MaxRoutingApduLength="66" Comparable="true" Reconstructable="true" DownloadInvisibleParameters="None" SupportsExtendedMemoryServices="true" SupportsExtendedPropertyServices="true" SupportsIpSystemBroadcast="true" />
</Static>
<ModuleDefs>
<op:include href="deps/OGM-Common/src/Common.share.xml" xpath="//ApplicationProgram/ModuleDefs/*" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//ApplicationProgram/ModuleDefs/*" prefix="%prefix%" />
<op:includetemplate href="%templ%" xpath="//ApplicationProgram/ModuleDefs/*" type="template" prefix="%prefix%" />
</ModuleDefs>
<Dynamic>
<op:include href="deps/OGM-Common/src/Common.Router.share.xml" xpath="//Dynamic/*" prefix="BASE" />
<op:include href="deps/OFM-Network/src/Network.share.xml" xpath="//Dynamic/*" prefix="NET" />
<op:include href="Gateway-OAM-IP-Router.share.xml" xpath="//Dynamic/*" prefix="ROUTE" />
</Dynamic>
</ApplicationProgram>
</ApplicationPrograms>
<Baggages>
<op:include href="deps/OGM-Common/src/Common.share.xml" xpath="//Baggages/*" prefix="BASE" />
<op:includetemplate href="%share%" xpath="//Manufacturer/Baggages/*" prefix="%prefix%" />
</Baggages>
<Hardware>
<Hardware Id="%HardwareId%" Name="OpenKNX-%HardwareName%%BuildSuffix%" SerialNumber="%SerialNumber%" VersionNumber="1" BusCurrent="%BusCurrent%" HasIndividualAddress="true" IsCoupler="true" HasApplicationProgram="true" IsIPEnabled="1">
<Products>
<Product Id="%ProductId%" Text="OpenKNX: %ProductName%%BuildSuffixText%" OrderNumber="%OrderNumber%" IsRailMounted="%IsRailMounted%" WidthInMillimeter="18" DefaultLanguage="en-US">
<RegistrationInfo RegistrationStatus="Registered" />
</Product>
</Products>
<Hardware2Programs>
<Hardware2Program Id="%Hardware2ProgramId%" MediumTypes="%MediumTypes%" CouplerCapabilities="SecurityProxy">
<ApplicationProgramRef RefId="%AID%" />
<RegistrationInfo RegistrationStatus="Registered" RegistrationNumber="0001/%HardwareVersionEncoded%1" />
</Hardware2Program>
</Hardware2Programs>
</Hardware>
</Hardware>
<Languages>
<Language Identifier="zh-CN">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%">
<Translation AttributeName="Name" Text="网关 OAM IP 路由器%BuildSuffix%" />
</TranslationElement>
<TranslationElement RefId="%AID%_M-1">
<Translation AttributeName="Text" Text="制造商标识不兼容。" />
</TranslationElement>
<TranslationElement RefId="%AID%_M-2">
<Translation AttributeName="Text" Text="硬件类型或固件版本不兼容。" />
</TranslationElement>
<op:include href="Gateway-OAM-IP-Router.share.xml" xpath="//Manufacturer/Languages/Language[@Identifier='zh-CN']/TranslationUnit/TranslationElement" prefix="ROUTE" />
<op:include href="Gateway-Dependency-Translations.xml" xpath="//Manufacturer/Languages/Language[@Identifier='zh-CN']/TranslationUnit/TranslationElement" />
<TranslationElement RefId="%AID%_BI-1"><Translation AttributeName="Text" Text="隧道 1" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901001"><Translation AttributeName="Text" Text="预留隧道 1" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901002"><Translation AttributeName="Text" Text="隧道 1 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-2"><Translation AttributeName="Text" Text="隧道 2" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902001"><Translation AttributeName="Text" Text="预留隧道 2" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902002"><Translation AttributeName="Text" Text="隧道 2 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-3"><Translation AttributeName="Text" Text="隧道 3" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903001"><Translation AttributeName="Text" Text="预留隧道 3" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903002"><Translation AttributeName="Text" Text="隧道 3 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-4"><Translation AttributeName="Text" Text="隧道 4" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904001"><Translation AttributeName="Text" Text="预留隧道 4" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904002"><Translation AttributeName="Text" Text="隧道 4 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-5"><Translation AttributeName="Text" Text="隧道 5" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905001"><Translation AttributeName="Text" Text="预留隧道 5" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905002"><Translation AttributeName="Text" Text="隧道 5 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-6"><Translation AttributeName="Text" Text="隧道 6" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906001"><Translation AttributeName="Text" Text="预留隧道 6" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906002"><Translation AttributeName="Text" Text="隧道 6 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-7"><Translation AttributeName="Text" Text="隧道 7" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907001"><Translation AttributeName="Text" Text="预留隧道 7" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907002"><Translation AttributeName="Text" Text="隧道 7 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-8"><Translation AttributeName="Text" Text="隧道 8" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908001"><Translation AttributeName="Text" Text="预留隧道 8" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908002"><Translation AttributeName="Text" Text="隧道 8 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-9"><Translation AttributeName="Text" Text="隧道 9" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909001"><Translation AttributeName="Text" Text="预留隧道 9" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909002"><Translation AttributeName="Text" Text="隧道 9 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-10"><Translation AttributeName="Text" Text="隧道 10" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910001"><Translation AttributeName="Text" Text="预留隧道 10" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910002"><Translation AttributeName="Text" Text="隧道 10 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-11"><Translation AttributeName="Text" Text="隧道 11" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911001"><Translation AttributeName="Text" Text="预留隧道 11" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911002"><Translation AttributeName="Text" Text="隧道 11 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-12"><Translation AttributeName="Text" Text="隧道 12" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912001"><Translation AttributeName="Text" Text="预留隧道 12" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912002"><Translation AttributeName="Text" Text="隧道 12 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-13"><Translation AttributeName="Text" Text="隧道 13" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913001"><Translation AttributeName="Text" Text="预留隧道 13" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913002"><Translation AttributeName="Text" Text="隧道 13 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-14"><Translation AttributeName="Text" Text="隧道 14" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914001"><Translation AttributeName="Text" Text="预留隧道 14" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914002"><Translation AttributeName="Text" Text="隧道 14 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-15"><Translation AttributeName="Text" Text="隧道 15" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915001"><Translation AttributeName="Text" Text="预留隧道 15" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915002"><Translation AttributeName="Text" Text="隧道 15 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-16"><Translation AttributeName="Text" Text="隧道 16" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916001"><Translation AttributeName="Text" Text="预留隧道 16" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916002"><Translation AttributeName="Text" Text="隧道 16 的 IP 地址" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916003"><Translation AttributeName="Text" Text="隧道忙碌时的行为" /></TranslationElement>
</TranslationUnit>
</Language>
<Language Identifier="de-DE">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%">
<Translation AttributeName="Name" Text="Gateway OAM IP-Router%BuildSuffix%" />
</TranslationElement>
<TranslationElement RefId="%AID%_M-1">
<Translation AttributeName="Text" Text="Herstellerkennung ist inkompatibel." />
</TranslationElement>
<TranslationElement RefId="%AID%_M-2">
<Translation AttributeName="Text" Text="Hardwaretyp oder Firmwareversion ist inkompatibel." />
</TranslationElement>
<op:include href="Gateway-OAM-IP-Router.share.xml" xpath="//Manufacturer/Languages/Language[@Identifier='de-DE']/TranslationUnit/TranslationElement" prefix="ROUTE" />
<op:include href="Gateway-Dependency-Translations.xml" xpath="//Manufacturer/Languages/Language[@Identifier='de-DE']/TranslationUnit/TranslationElement" />
<TranslationElement RefId="%AID%_BI-1"><Translation AttributeName="Text" Text="Tunnel 1" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901001"><Translation AttributeName="Text" Text="Tunnel 1 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 1" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-2"><Translation AttributeName="Text" Text="Tunnel 2" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902001"><Translation AttributeName="Text" Text="Tunnel 2 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 2" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-3"><Translation AttributeName="Text" Text="Tunnel 3" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903001"><Translation AttributeName="Text" Text="Tunnel 3 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 3" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-4"><Translation AttributeName="Text" Text="Tunnel 4" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904001"><Translation AttributeName="Text" Text="Tunnel 4 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 4" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-5"><Translation AttributeName="Text" Text="Tunnel 5" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905001"><Translation AttributeName="Text" Text="Tunnel 5 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 5" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-6"><Translation AttributeName="Text" Text="Tunnel 6" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906001"><Translation AttributeName="Text" Text="Tunnel 6 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 6" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-7"><Translation AttributeName="Text" Text="Tunnel 7" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907001"><Translation AttributeName="Text" Text="Tunnel 7 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 7" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-8"><Translation AttributeName="Text" Text="Tunnel 8" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908001"><Translation AttributeName="Text" Text="Tunnel 8 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 8" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-9"><Translation AttributeName="Text" Text="Tunnel 9" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909001"><Translation AttributeName="Text" Text="Tunnel 9 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 9" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-10"><Translation AttributeName="Text" Text="Tunnel 10" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910001"><Translation AttributeName="Text" Text="Tunnel 10 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 10" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-11"><Translation AttributeName="Text" Text="Tunnel 11" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911001"><Translation AttributeName="Text" Text="Tunnel 11 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 11" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-12"><Translation AttributeName="Text" Text="Tunnel 12" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912001"><Translation AttributeName="Text" Text="Tunnel 12 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 12" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-13"><Translation AttributeName="Text" Text="Tunnel 13" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913001"><Translation AttributeName="Text" Text="Tunnel 13 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 13" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-14"><Translation AttributeName="Text" Text="Tunnel 14" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914001"><Translation AttributeName="Text" Text="Tunnel 14 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 14" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-15"><Translation AttributeName="Text" Text="Tunnel 15" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915001"><Translation AttributeName="Text" Text="Tunnel 15 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 15" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-16"><Translation AttributeName="Text" Text="Tunnel 16" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916001"><Translation AttributeName="Text" Text="Tunnel 16 reservieren" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916002"><Translation AttributeName="Text" Text="IP-Adresse für Tunnel 16" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916003"><Translation AttributeName="Text" Text="Verhalten bei belegtem Tunnel" /></TranslationElement>
</TranslationUnit>
</Language>
<Language Identifier="ja-JP">
<TranslationUnit RefId="%AID%">
<TranslationElement RefId="%AID%">
<Translation AttributeName="Name" Text="ゲートウェイ OAM IP ルーター%BuildSuffix%" />
</TranslationElement>
<TranslationElement RefId="%AID%_M-1">
<Translation AttributeName="Text" Text="メーカー識別子に互換性がありません。" />
</TranslationElement>
<TranslationElement RefId="%AID%_M-2">
<Translation AttributeName="Text" Text="ハードウェアタイプまたはファームウェアバージョンに互換性がありません。" />
</TranslationElement>
<op:include href="Gateway-OAM-IP-Router.share.xml" xpath="//Manufacturer/Languages/Language[@Identifier='ja-JP']/TranslationUnit/TranslationElement" prefix="ROUTE" />
<op:include href="Gateway-Dependency-Translations.xml" xpath="//Manufacturer/Languages/Language[@Identifier='ja-JP']/TranslationUnit/TranslationElement" />
<TranslationElement RefId="%AID%_BI-1"><Translation AttributeName="Text" Text="トンネル 1" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901001"><Translation AttributeName="Text" Text="トンネル 1 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901002"><Translation AttributeName="Text" Text="トンネル 1 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9901003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-2"><Translation AttributeName="Text" Text="トンネル 2" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902001"><Translation AttributeName="Text" Text="トンネル 2 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902002"><Translation AttributeName="Text" Text="トンネル 2 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9902003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-3"><Translation AttributeName="Text" Text="トンネル 3" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903001"><Translation AttributeName="Text" Text="トンネル 3 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903002"><Translation AttributeName="Text" Text="トンネル 3 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9903003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-4"><Translation AttributeName="Text" Text="トンネル 4" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904001"><Translation AttributeName="Text" Text="トンネル 4 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904002"><Translation AttributeName="Text" Text="トンネル 4 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9904003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-5"><Translation AttributeName="Text" Text="トンネル 5" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905001"><Translation AttributeName="Text" Text="トンネル 5 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905002"><Translation AttributeName="Text" Text="トンネル 5 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9905003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-6"><Translation AttributeName="Text" Text="トンネル 6" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906001"><Translation AttributeName="Text" Text="トンネル 6 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906002"><Translation AttributeName="Text" Text="トンネル 6 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9906003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-7"><Translation AttributeName="Text" Text="トンネル 7" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907001"><Translation AttributeName="Text" Text="トンネル 7 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907002"><Translation AttributeName="Text" Text="トンネル 7 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9907003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-8"><Translation AttributeName="Text" Text="トンネル 8" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908001"><Translation AttributeName="Text" Text="トンネル 8 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908002"><Translation AttributeName="Text" Text="トンネル 8 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9908003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-9"><Translation AttributeName="Text" Text="トンネル 9" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909001"><Translation AttributeName="Text" Text="トンネル 9 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909002"><Translation AttributeName="Text" Text="トンネル 9 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9909003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-10"><Translation AttributeName="Text" Text="トンネル 10" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910001"><Translation AttributeName="Text" Text="トンネル 10 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910002"><Translation AttributeName="Text" Text="トンネル 10 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9910003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-11"><Translation AttributeName="Text" Text="トンネル 11" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911001"><Translation AttributeName="Text" Text="トンネル 11 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911002"><Translation AttributeName="Text" Text="トンネル 11 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9911003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-12"><Translation AttributeName="Text" Text="トンネル 12" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912001"><Translation AttributeName="Text" Text="トンネル 12 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912002"><Translation AttributeName="Text" Text="トンネル 12 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9912003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-13"><Translation AttributeName="Text" Text="トンネル 13" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913001"><Translation AttributeName="Text" Text="トンネル 13 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913002"><Translation AttributeName="Text" Text="トンネル 13 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9913003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-14"><Translation AttributeName="Text" Text="トンネル 14" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914001"><Translation AttributeName="Text" Text="トンネル 14 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914002"><Translation AttributeName="Text" Text="トンネル 14 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9914003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-15"><Translation AttributeName="Text" Text="トンネル 15" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915001"><Translation AttributeName="Text" Text="トンネル 15 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915002"><Translation AttributeName="Text" Text="トンネル 15 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9915003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
<TranslationElement RefId="%AID%_BI-16"><Translation AttributeName="Text" Text="トンネル 16" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916001"><Translation AttributeName="Text" Text="トンネル 16 を予約" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916002"><Translation AttributeName="Text" Text="トンネル 16 の IP アドレス" /></TranslationElement>
<TranslationElement RefId="%AID%_P-9916003"><Translation AttributeName="Text" Text="トンネル使用中の動作" /></TranslationElement>
</TranslationUnit>
</Language>
</Languages>
</Manufacturer>
</ManufacturerData>
</KNX>
@@ -0,0 +1,55 @@
# Gateway OAM IP Router KNX Product XML
This folder contains the gateway-owned OpenKNXproducer source for the OAM-compatible BAU091A KNX/IP Router persona.
The source is derived from the upstream OpenKNX OAM-IP-Router database and merges the secure application metadata used by OAM-AccessControl:
- Manufacturer: `0x00FA`
- Application: `0xA11F`
- Version: `0x07`
- Mask: `MV-091A`
- Tunnels: `16`
- Security: `IsSecureEnabled="true"`
- Default language: `en-US`
- Translations: `zh-CN`, `de-DE`, `ja-JP`
The XML keeps the upstream router/coupler shape and absolute memory segment while adding KNXnet/IP Secure download hooks for:
- OT-11 PID 94, `PID_SECURED_SERVICE_FAMILIES`
- OT-11 PID 97, `PID_TUNNELLING_USERS`
- OT-6 PID 81, `PID_SECURITY_PROXY_GRP_KEY_TABLE`
The secure proxy capability is advertised with `CouplerCapabilities="SecurityProxy"` on `Hardware2Program` and `MaxSecurityProxyGroupKeyTableEntries` on the application program, matching the secure proxy-enabled ETS database shape.
The OpenKNX module dependencies needed by this producer source are vendored under `deps/` so generation does not require an external OpenKNX checkout:
- `deps/OGM-Common/` provides `Common.share.xml`, `Common.Router.share.xml`, `InfoLed.part.xml`, `Common.script.js`, `library.json`, and producer baggage assets.
- `deps/OFM-Network/` provides `Network.share.xml`, `Network.script.js`, `library.json`, and producer baggage assets.
Dependency strings are localized by `Gateway-Dependency-Translations.xml`. Dependency context-help documents are kept with the vendored modules in German plus translated English, Chinese, and Japanese help folders; the generated application uses the English help source as its default context help through OpenKNXproducer's legacy context-help baggage placeholder. OpenKNXproducer still emits that selected context-help baggage with its legacy `Help_de.zip` generated name, so keep the `Help_en.zip` source mapped to the `%FILE-HELP-de%` placeholder unless producer support for language-specific context-help baggage changes.
The runtime identity must stay synchronized with `gateway/components/gateway_knx/include/gateway_knx_internal.h` and the OAM router Kconfig defaults in `gateway/apps/gateway/main/Kconfig.projbuild`.
## Validation
Run a well-formed XML check from the repository root:
```sh
xmllint --noout \
gateway/components/gateway_knx/oam_knxprod/*.xml \
gateway/components/gateway_knx/oam_knxprod/deps/OGM-Common/src/*.xml \
gateway/components/gateway_knx/oam_knxprod/deps/OFM-Network/src/*.xml
```
Then run OpenKNXproducer with `Gateway-OAM-IP-Router.xml` and import the generated product database into ETS. ETS validation should confirm the router imports as a secure-capable KNX/IP router, exposes 16 tunneling interfaces, and supports secure tunneling plus security proxy table download/verify.
For a local sanity check without ETS or the KNX XSD support folder installed, run:
```sh
OpenKNXproducer create --NoXsd \
-h /tmp/gateway_oam_knxprod.h \
-o /tmp/Gateway-OAM-IP-Router.knxprod \
gateway/components/gateway_knx/oam_knxprod/Gateway-OAM-IP-Router.xml
```
This verifies OpenKNXproducer include expansion, generated IDs, translation references, and internal sanity checks. A full `.knxprod` export still requires ETS on the machine running OpenKNXproducer.
@@ -0,0 +1,7 @@
{
"name": "OFM-Network",
"version": "0.5.4",
"dependencies": {
}
}
@@ -0,0 +1,4 @@
### Allgemein
In diesem Abschnitt werden die Basiseinstellungen und verfügbaren Dienste festgelegt.
@@ -0,0 +1,7 @@
### Dokumentation
Eine vollständige Applikationsbeschreibung ist unter folgendem Link verfügbar: https://github.com/openknx/OFM-Network/blob/v1/doc/Applikationsbeschreibung-Netzwerk.md
Über die Netzwerkeinstellungen kann nicht nur die IP-Adresse des Geräts angepasst werden, sondern es können auch verschiedene Dienste ein- oder ausgeschaltet werden. Dazu gehören z. B. der NTP-Client zum Abrufen der aktuellen Zeit, mDNS für das automatisierte Auffinden von OpenKNX-Geräten sowie die Möglichkeit, die Geräte-Firmware per Netzwerk zu aktualisieren.
@@ -0,0 +1,4 @@
### Erweitere Einstellungen
In diesem Abschnitt werden Einstellungen vorgenommen die vorwiegen von Netzwerkexperten benötigt werden.
@@ -0,0 +1,4 @@
### Webserver
Hier kann später ein Webserver aktiviert werden, der dann über den Browser aufgerufen werden kann. Diese Funktion ist derzeit noch nicht integriert und dient aktuell nur als Platzhalter.
@@ -0,0 +1,4 @@
### Hostname
Der Hostname wird automatisch aus der Seriennummer generiert (OpenKNX-XXXXXXXX) und erfordert in der Regel keine Anpassung. Sollte jedoch eine individuelle Anpassung gewünscht sein, darf die Länge von 24 Zeichen nicht überschritten werden. Darüber hinaus sind nur Buchstaben, Zahlen und Bindestriche erlaubt. Der Hostname muss zudem mit einem Buchstaben beginnen und darf nicht mit einem Bindestrich enden.
@@ -0,0 +1,6 @@
### IP-Adresse
In diesem Eingabeformular kannst du entscheiden, ob die IP-Adresse dynamisch durch einen DHCP-Server zugewiesen oder manuell festgelegt werden soll. Bei manueller Konfiguration sind neben der IP-Adresse auch die Netzmaske, ein Standardgateway (Router) und ein Nameserver (DNS) erforderlich.
**Hinweis**: Es wird empfohlen, die DHCP-Einstellungen beizubehalten und stattdessen eine feste IP-Adresse direkt im Router zuzuweisen. Dies liegt daran, dass die Netzwerkeinstellungen nur im programmierten Zustand gelten. Nach einem Update kann das Gerät beispielsweise wieder in den DHCP-Modus wechseln, bis es erneut programmiert wird.
@@ -0,0 +1,7 @@
### LAN-Modus
Wähle den gewünschten Modus für die LAN-Schnittstelle aus. Die Auswahl des 10 MBit/s Modus kann genutzt werden, um den Stromverbrauch zu reduzieren.
**Hinweis**: In neueren Switches mit Geschwindigkeiten ab 2,5 GBit/s ist der 10 MBit/s Modus in der Regel nicht mehr vorgesehen. Es besteht daher die Möglichkeit, dass in solchen Fällen keine Verbindung hergestellt werden kann. Dennoch lohnt es sich, dies auszuprobieren, da einige Geräte diesen Modus unterstützen.
@@ -0,0 +1,4 @@
### mDNS
Der mDNS Service ermöglicht das Auflösen von "Hostname.local" und kann auch später zum Auffinden von OpenKNX-Geräten im eigenen Netzwerk genutzt werden.
@@ -0,0 +1,7 @@
### NTP-Client
Durch das Aktivieren des NTP-Clients kann das Gerät die aktuelle Zeit zyklisch von einem Zeitserver abrufen, anstatt sie vom Bus zu beziehen. Zudem kann das Gerät auf Wunsch die aktuelle Zeit auch auf den Bus senden.
Die bisherigen Einstellungen bzw. Kommunikationsobjekte zum Abrufen der Zeit vom Bus entfallen. Stattdessen stehen drei neue Kommunikationsobjekte zur Verfügung, mit denen Zeit, Datum und beides kombiniert auf dem Bus bereitgestellt werden können.
Außerdem kann der Zeitserver (NTP-Server) angepasst werden, von dem die aktuelle Zeit bezogen wird. In der Regel ist eine Änderung nicht erforderlich, da der voreingestellte Server (pool.ntp.org) zuverlässig arbeitet und weit verbreitet ist. Dieser Server fungiert als Alias für eine Vielzahl von Zeitservern.
@@ -0,0 +1,8 @@
### OTA-Update
Ermöglicht eine direkte Firmwareaktualisierung, ohne den Einsatz von KNX oder einem USB-Anschluss.
* **Im Prog-Modus:** Für ein Update muss das Gerät zuvor in den Programmiermodus versetzt werden (z. B. durch Drücken der PROG-Taste).
* **Immer aktiv:** Aktiviert den dauerhaften Update-Modus für das Gerät. Wir raten von diesem Modus ab, da es schnell zu Verwechslungen zwischen Geräten kommen kann.
* **Ausgeschaltet** Deaktiviert die Möglichkeit, Updates über das Netzwerk durchzuführen.
@@ -0,0 +1,4 @@
### Webserver
Hier kann später der Webserver konfiguriert werden, der dann über den Browser aufgerufen werden kann. Diese Funktion ist derzeit noch nicht integriert und dient aktuell nur als Platzhalter.
@@ -0,0 +1,7 @@
### WiFi-Assistent
Dieser WiFi-Assistent ermöglicht das Übertragen von WiFi-Zugangsdaten auf das Gerät. Voraussetzung ist, dass die verwendete Hardware einen WiFi-Adapter verwendet. Geräte, die per IP-Netzwerk angebunden werden, müssen bereits über eine WiFi-Verbindung und somit über gültige Zugangsdaten verfügen. Der Assistent kann daher nur die bestehenden Zugangsdaten ändern. TP-Geräte können hingegen immer per Bus angepasst werden.
IP-Geräte ohne WiFi-Zugangsdaten müssen initial auf anderem Wege eingerichtet werden. Dies hängt sowohl von der Gerätesoftware als auch der verwendeten Hardware ab. Die Einrichtung per Terminal (USB) sollte immer funktionieren. Dafür muss auf der Konsole nur `wifi SSID PSK` eingegeben werden.
Bei Geräten auf Arduino-Pico-Basis können die WiFi-Zugangsdaten in der Regel zusätzlich per USB übertragen werden. Dazu muss das Gerät am Rechner angeschlossen und der Transfermodus durch einen Doppelklick auf die Prog-Taste gestartet werden. Kopiere dann eine Datei namens "WIFI.TXT" auf das Wechsellaufwerk. Die erste Zeile muss die SSID und die zweite den PSK enthalten. Danach den Modus wieder mit einem Doppelklick beenden und das Gerät neu starten.
@@ -0,0 +1,5 @@
### Documentation
A complete application description is available at: https://github.com/openknx/OFM-Network/blob/v1/doc/Applikationsbeschreibung-Netzwerk.md
The network settings can be used not only to adjust the device IP address, but also to enable or disable several services. These include the NTP client for retrieving the current time, mDNS for automated discovery of OpenKNX devices, and the option to update the device firmware over the network.
@@ -0,0 +1,3 @@
### Web Server
A web server can be enabled here in the future and then opened in a browser. This function is not currently integrated and is only a placeholder.
@@ -0,0 +1,3 @@
### Hostname
The hostname is generated automatically from the serial number (OpenKNX-XXXXXXXX) and normally does not need to be changed. If a custom hostname is desired, it must not exceed 24 characters. Only letters, numbers, and hyphens are allowed. The hostname must start with a letter and must not end with a hyphen.
@@ -0,0 +1,5 @@
### IP Address
In this form you can decide whether the IP address is assigned dynamically by a DHCP server or set manually. For manual configuration, the subnet mask, a default gateway (router), and a name server (DNS) are required in addition to the IP address.
**Note:** It is recommended to keep DHCP enabled and assign a fixed IP address directly in the router instead. Network settings only apply while the device is programmed. After an update, for example, the device may return to DHCP mode until it is programmed again.
@@ -0,0 +1,5 @@
### LAN Mode
Select the desired mode for the LAN interface. The 10 MBit/s mode can be used to reduce power consumption.
**Note:** Newer switches with speeds of 2.5 GBit/s and above usually no longer provide a 10 MBit/s mode. In such cases, a connection may not be established. It is still worth trying because some devices support this mode.
@@ -0,0 +1,3 @@
### mDNS
The mDNS service resolves "Hostname.local" and can also be used later to discover OpenKNX devices automatically in the local network.
@@ -0,0 +1,7 @@
### NTP Client
Enabling the NTP client allows the device to retrieve the current time cyclically from a time server instead of obtaining it from the bus. If desired, the device can also send the current time to the bus.
The previous settings and communication objects for retrieving time from the bus are no longer used. Instead, three new communication objects are available to provide time, date, or both combined on the bus.
The time server (NTP server) used to retrieve the current time can also be changed. Usually no change is required because the preset server (pool.ntp.org) works reliably and is widely used. This server is an alias for a large number of time servers.
@@ -0,0 +1,7 @@
### OTA Update
Allows firmware to be updated directly without using KNX or a USB connection.
* **In programming mode:** The device must first be put into programming mode for an update, for example by pressing the PROG button.
* **Always active:** Enables permanent update mode for the device. This mode is not recommended because devices can easily be confused with each other.
* **Disabled:** Disables updates over the network.
@@ -0,0 +1,3 @@
### Web Server
The web server can be configured here in the future and then opened in a browser. This function is not currently integrated and is only a placeholder.
@@ -0,0 +1,7 @@
### WiFi Assistant
This WiFi assistant transfers WiFi credentials to the device. The hardware must use a WiFi adapter. Devices connected through an IP network must already have a WiFi connection and valid credentials. The assistant can therefore only change existing credentials. TP devices, however, can always be adjusted over the bus.
IP devices without WiFi credentials must initially be set up in another way. This depends on both the device software and the hardware used. Setup via terminal (USB) should always work. On the console, enter `wifi SSID PSK`.
On Arduino-Pico-based devices, WiFi credentials can usually also be transferred over USB. Connect the device to the computer and start transfer mode by double-clicking the Prog button. Then copy a file named "WIFI.TXT" to the removable drive. The first line must contain the SSID and the second line the PSK. Afterwards, exit the mode with another double-click and restart the device.
@@ -0,0 +1,5 @@
### ドキュメント
完全なアプリケーション説明は次の場所で確認できます:https://github.com/openknx/OFM-Network/blob/v1/doc/Applikationsbeschreibung-Netzwerk.md
ネットワーク設定では、デバイスの IP アドレスを調整できるだけでなく、複数のサービスを有効または無効にできます。これには、現在時刻を取得する NTP クライアント、OpenKNX デバイスを自動検出する mDNS、ネットワーク経由でデバイスファームウェアを更新する機能が含まれます。
@@ -0,0 +1,3 @@
### Web サーバー
将来的にここで Web サーバーを有効化し、ブラウザーから開けるようにできます。この機能は現在まだ統合されておらず、現時点ではプレースホルダーです。
@@ -0,0 +1,3 @@
### ホスト名
ホスト名はシリアル番号から自動生成されます(OpenKNX-XXXXXXXX)。通常は変更不要です。カスタムホスト名を使用する場合、24 文字を超えてはいけません。使用できるのは文字、数字、ハイフンのみです。ホスト名は文字で始まり、ハイフンで終わってはいけません。
@@ -0,0 +1,5 @@
### IP アドレス
この入力フォームでは、IP アドレスを DHCP サーバーから動的に割り当てるか、手動で設定するかを選択できます。手動設定では、IP アドレスに加えてサブネットマスク、デフォルトゲートウェイ(ルーター)、ネームサーバー(DNS)が必要です。
**注意:** DHCP 設定を維持し、固定 IP アドレスはルーター側で割り当てることを推奨します。ネットワーク設定は、デバイスがプログラム済みの状態でのみ有効です。たとえば更新後は、再度プログラムされるまで DHCP モードに戻る場合があります。
@@ -0,0 +1,5 @@
### LAN モード
LAN インターフェイスの希望するモードを選択します。10 MBit/s モードは消費電力を抑えるために使用できます。
**注意:** 2.5 GBit/s 以上の速度に対応する新しいスイッチでは、通常 10 MBit/s モードが用意されていません。その場合、接続できない可能性があります。ただし、このモードをサポートするデバイスもあるため、試す価値はあります。
@@ -0,0 +1,3 @@
### mDNS
mDNS サービスは "Hostname.local" を解決し、後でローカルネットワーク内の OpenKNX デバイスを自動検出するためにも使用できます。
@@ -0,0 +1,7 @@
### NTP クライアント
NTP クライアントを有効にすると、デバイスはバスからではなく、時刻サーバーから周期的に現在時刻を取得できます。必要に応じて、現在時刻をバスへ送信することもできます。
従来の、バスから時刻を取得するための設定や通信オブジェクトは使用されなくなります。代わりに、時刻、日付、またはその両方を結合してバスに提供する 3 つの新しい通信オブジェクトが利用できます。
現在時刻を取得する時刻サーバー(NTP サーバー)も変更できます。通常は変更不要です。既定のサーバー pool.ntp.org は信頼性が高く広く使われており、多数の時刻サーバーへのエイリアスとして機能します。
@@ -0,0 +1,7 @@
### OTA 更新
KNX や USB 接続を使用せずに、ファームウェアを直接更新できます。
* **プログラミングモード時:** 更新するには、事前にデバイスをプログラミングモードにする必要があります。たとえば PROG ボタンを押します。
* **常に有効:** デバイスの常時更新モードを有効にします。デバイスを取り違えやすくなるため、このモードは推奨されません。
* **無効:** ネットワーク経由の更新を無効にします。
@@ -0,0 +1,3 @@
### Web サーバー
将来的にここで Web サーバーを設定し、ブラウザーから開けるようにできます。この機能は現在まだ統合されておらず、現時点ではプレースホルダーです。
@@ -0,0 +1,7 @@
### WiFi アシスタント
この WiFi アシスタントは、WiFi 認証情報をデバイスへ転送します。使用するハードウェアには WiFi アダプターが必要です。IP ネットワーク経由で接続されるデバイスは、すでに WiFi 接続と有効な認証情報を持っている必要があります。そのため、このアシスタントで変更できるのは既存の認証情報のみです。一方、TP デバイスは常にバス経由で調整できます。
WiFi 認証情報を持たない IP デバイスは、最初に別の方法でセットアップする必要があります。これはデバイスソフトウェアと使用するハードウェアの両方に依存します。ターミナル(USB)経由の設定は通常常に機能します。コンソールで `wifi SSID PSK` と入力してください。
Arduino-Pico ベースのデバイスでは、通常 USB 経由でも WiFi 認証情報を転送できます。デバイスをコンピューターに接続し、Prog ボタンをダブルクリックして転送モードを開始します。その後、"WIFI.TXT" という名前のファイルをリムーバブルドライブにコピーします。1 行目に SSID、2 行目に PSK を記載してください。最後にもう一度ダブルクリックしてモードを終了し、デバイスを再起動します。
@@ -0,0 +1,5 @@
### 文档
完整的应用说明可在以下地址查看:https://github.com/openknx/OFM-Network/blob/v1/doc/Applikationsbeschreibung-Netzwerk.md
网络设置不仅可用于调整设备的 IP 地址,还可启用或停用多种服务。其中包括用于获取当前时间的 NTP 客户端、用于自动发现 OpenKNX 设备的 mDNS,以及通过网络更新设备固件的功能。
@@ -0,0 +1,3 @@
### Web 服务器
将来可在此启用 Web 服务器,并通过浏览器访问。此功能目前尚未集成,当前仅作为占位项。
@@ -0,0 +1,3 @@
### 主机名
主机名会根据序列号自动生成(OpenKNX-XXXXXXXX),通常不需要调整。如果需要自定义,长度不得超过 24 个字符,并且只能包含字母、数字和连字符。主机名必须以字母开头,且不能以连字符结尾。
@@ -0,0 +1,5 @@
### IP 地址
在此表单中,你可以选择 IP 地址由 DHCP 服务器动态分配,或手动指定。手动配置时,除 IP 地址外,还需要子网掩码、默认网关(路由器)和名称服务器(DNS)。
**注意:** 建议保留 DHCP 设置,并在路由器中直接为设备分配固定 IP 地址。网络设置仅在设备已编程的状态下生效。例如更新后,设备可能会恢复到 DHCP 模式,直到再次编程。
@@ -0,0 +1,5 @@
### LAN 模式
选择 LAN 接口所需的模式。10 MBit/s 模式可用于降低功耗。
**注意:** 较新的交换机,特别是 2.5 GBit/s 及以上速度的交换机,通常不再提供 10 MBit/s 模式。在这种情况下可能无法建立连接。但仍值得尝试,因为部分设备支持该模式。
@@ -0,0 +1,3 @@
### mDNS
mDNS 服务可解析 "Hostname.local",之后也可用于在本地网络中自动发现 OpenKNX 设备。
@@ -0,0 +1,7 @@
### NTP 客户端
启用 NTP 客户端后,设备可以周期性地从时间服务器获取当前时间,而不是从总线获取。如有需要,设备也可以将当前时间发送到总线。
此前用于从总线获取时间的设置和通信对象将不再使用。取而代之的是三个新的通信对象,可在总线上提供时间、日期,或时间日期组合。
也可以调整用于获取当前时间的时间服务器(NTP 服务器)。通常不需要修改,因为预设服务器 pool.ntp.org 可靠且使用广泛。该服务器是大量时间服务器的别名。
@@ -0,0 +1,7 @@
### OTA 更新
允许在不使用 KNX 或 USB 连接的情况下直接更新固件。
* **编程模式下:** 更新前必须先将设备置于编程模式,例如按下 PROG 按钮。
* **始终启用:** 为设备启用永久更新模式。不建议使用该模式,因为容易混淆不同设备。
* **已禁用:** 禁用通过网络进行更新。
@@ -0,0 +1,3 @@
### Web 服务器
将来可在此配置 Web 服务器,并通过浏览器访问。此功能目前尚未集成,当前仅作为占位项。

Some files were not shown because too many files have changed in this diff Show More