Compare commits
4 Commits
1b8753636f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 626f86ec4e | |||
| 36d10702da | |||
| d231460612 | |||
| 70367f53ca |
@@ -1,3 +1,4 @@
|
||||
**/build/
|
||||
build/
|
||||
**/managed_components/
|
||||
.DS_Store
|
||||
|
||||
@@ -5,3 +5,11 @@
|
||||
path = knx
|
||||
url = https://git.tonycloud.org/knx/knx.git
|
||||
branch = v1
|
||||
[submodule "knx_dali_gw"]
|
||||
path = knx_dali_gw
|
||||
url = https://git.tonycloud.org/knx/GW-REG1-Dali.git
|
||||
branch = tonycloud-dev
|
||||
[submodule "tpuart"]
|
||||
path = tpuart
|
||||
url = https://git.tonycloud.org/knx/tpuart.git
|
||||
branch = main
|
||||
|
||||
@@ -12,18 +12,19 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
||||
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
|
||||
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
|
||||
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
|
||||
- `openknx_idf/`: ESP-IDF port layer for the OpenKNX `gateway/knx` and `gateway/tpuart` submodules, including NVS-backed OpenKNX memory, ETS cEMI programming support, UDP multicast/unicast plumbing, and a native TP-UART interface without the Arduino framework.
|
||||
- `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, and BOOT-button Wi-Fi reset 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.
|
||||
|
||||
## Current status
|
||||
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing, and an optional `gateway_485_control` bridge that claims UART0 for Lua-compatible framed command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE is enabled by default, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, W5500 SPI Ethernet with DHCP, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing, and an optional `gateway_485_control` bridge that claims UART0 for Lua-compatible framed command ingress plus `0x22` notification egress when the console is moved off UART0. Startup behavior is configured in `main/Kconfig.projbuild`: BLE and wired Ethernet are enabled by default, W5500 initialization and startup probe failures are ignored by default for boards without populated Ethernet hardware by fully disabling Ethernet for that boot, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected, and the UART0 control bridge stays disabled unless the deployment explicitly repurposes UART0 away from the ESP-IDF console. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots.
|
||||
|
||||
## Modbus
|
||||
|
||||
|
||||
@@ -380,6 +380,109 @@ config GATEWAY_SMARTCONFIG_TIMEOUT_SEC
|
||||
help
|
||||
Timeout passed to ESP-IDF smartconfig before provisioning restarts internally.
|
||||
|
||||
config GATEWAY_ETHERNET_SUPPORTED
|
||||
bool "Wired Ethernet gateway transport is supported"
|
||||
default y
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_W5500
|
||||
help
|
||||
Enables the ESP-IDF Ethernet driver path for wired gateway networking. The
|
||||
native gateway currently provisions a W5500 SPI Ethernet controller and
|
||||
exposes the same HTTP, UDP, KNXnet/IP, BACnet/IP, Modbus TCP, and cloud
|
||||
services over the wired netif.
|
||||
|
||||
config GATEWAY_START_ETHERNET_ENABLED
|
||||
bool "Start wired Ethernet at startup"
|
||||
depends on GATEWAY_ETHERNET_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Starts the configured W5500 Ethernet netif during boot and uses DHCP for
|
||||
address assignment. Disable this when the board is built without Ethernet
|
||||
hardware even though the firmware keeps Ethernet support compiled in.
|
||||
|
||||
config GATEWAY_ETHERNET_IGNORE_INIT_FAILURE
|
||||
bool "Ignore wired Ethernet init failures"
|
||||
depends on GATEWAY_START_ETHERNET_ENABLED
|
||||
default y
|
||||
help
|
||||
Continues booting if the W5500 Ethernet controller is missing, held in
|
||||
reset, miswired, or otherwise fails ESP-IDF Ethernet driver startup.
|
||||
Disable this for strict hardware bring-up where Ethernet failure should
|
||||
abort application startup.
|
||||
|
||||
menu "Gateway Wired Ethernet"
|
||||
depends on GATEWAY_ETHERNET_SUPPORTED
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_SPI_HOST
|
||||
int "W5500 SPI host number"
|
||||
range 1 2
|
||||
default 1
|
||||
help
|
||||
SPI host used for the W5500 Ethernet controller. On ESP32-S3, host 1 maps
|
||||
to SPI2 and host 2 maps to SPI3; do not use host 0 because it is reserved
|
||||
by flash/PSRAM.
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_SCLK_GPIO
|
||||
int "W5500 SPI SCLK GPIO"
|
||||
range 0 48
|
||||
default 14
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_MOSI_GPIO
|
||||
int "W5500 SPI MOSI GPIO"
|
||||
range 0 48
|
||||
default 13
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_MISO_GPIO
|
||||
int "W5500 SPI MISO GPIO"
|
||||
range 0 48
|
||||
default 12
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_CS_GPIO
|
||||
int "W5500 SPI CS GPIO"
|
||||
range 0 48
|
||||
default 15
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_INT_GPIO
|
||||
int "W5500 interrupt GPIO"
|
||||
range -1 48
|
||||
default 4
|
||||
help
|
||||
W5500 interrupt pin. Set to -1 to disable interrupt mode and poll RX
|
||||
status periodically.
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS
|
||||
int "W5500 polling period ms"
|
||||
range 0 1000
|
||||
default 0
|
||||
help
|
||||
Polling interval used when the W5500 interrupt pin is disabled. A value of
|
||||
0 keeps interrupt mode when an interrupt GPIO is configured; if the
|
||||
interrupt GPIO is -1, the gateway falls back to 100 ms polling.
|
||||
|
||||
config GATEWAY_ETHERNET_W5500_CLOCK_MHZ
|
||||
int "W5500 SPI clock MHz"
|
||||
range 5 80
|
||||
default 36
|
||||
|
||||
config GATEWAY_ETHERNET_PHY_RESET_GPIO
|
||||
int "Ethernet PHY reset GPIO"
|
||||
range -1 48
|
||||
default 5
|
||||
help
|
||||
GPIO used to reset the W5500 PHY. Set to -1 to disable hardware reset.
|
||||
|
||||
config GATEWAY_ETHERNET_PHY_ADDR
|
||||
int "Ethernet PHY address"
|
||||
range 0 31
|
||||
default 1
|
||||
|
||||
config GATEWAY_ETHERNET_RX_TASK_STACK_SIZE
|
||||
int "Ethernet RX task stack bytes"
|
||||
range 2048 8192
|
||||
default 3072
|
||||
|
||||
endmenu
|
||||
|
||||
config GATEWAY_BRIDGE_SUPPORTED
|
||||
bool "dali_cpp bridge runtime is supported"
|
||||
default y
|
||||
@@ -411,7 +514,7 @@ choice GATEWAY_MODBUS_DEFAULT_TRANSPORT
|
||||
|
||||
config GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP
|
||||
bool "TCP server"
|
||||
depends on GATEWAY_WIFI_SUPPORTED
|
||||
depends on GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED
|
||||
|
||||
config GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU
|
||||
bool "RTU server on UART"
|
||||
@@ -488,7 +591,7 @@ config GATEWAY_MODBUS_SERIAL_RS485_DE_PIN
|
||||
|
||||
config GATEWAY_BACNET_BRIDGE_SUPPORTED
|
||||
bool "BACnet/IP bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||
default n
|
||||
help
|
||||
Enables BACnet bridge configuration, binding discovery, and the bacnet-stack BACnet/IP server adapter.
|
||||
@@ -503,7 +606,7 @@ config GATEWAY_START_BACNET_BRIDGE_ENABLED
|
||||
|
||||
config GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||
bool "KNX to DALI bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||
default n
|
||||
help
|
||||
Enables the gateway-owned KNX group-address router and KNXnet/IP TP/IP
|
||||
@@ -595,7 +698,7 @@ config GATEWAY_BRIDGE_KNX_TASK_PRIORITY
|
||||
|
||||
config GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
||||
bool "MQTT cloud bridge is supported"
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && GATEWAY_WIFI_SUPPORTED
|
||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||
default y
|
||||
help
|
||||
Enables per-channel DaliCloudBridge provisioning and MQTT downlink execution.
|
||||
|
||||
@@ -96,6 +96,54 @@
|
||||
#define CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC 60
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE
|
||||
#define CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO 14
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO 13
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO 12
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO 15
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO 4
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ
|
||||
#define CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ 36
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO
|
||||
#define CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO 5
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_PHY_ADDR
|
||||
#define CONFIG_GATEWAY_ETHERNET_PHY_ADDR 1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE 3072
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE 6144
|
||||
#endif
|
||||
@@ -199,12 +247,24 @@ constexpr bool kWifiSupported = true;
|
||||
constexpr bool kWifiSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_ETHERNET_SUPPORTED
|
||||
constexpr bool kEthernetSupported = true;
|
||||
#else
|
||||
constexpr bool kEthernetSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_WIFI_STA_ENABLED
|
||||
constexpr bool kWifiStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kWifiStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_ETHERNET_ENABLED
|
||||
constexpr bool kEthernetStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kEthernetStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_BLE_SUPPORTED
|
||||
constexpr bool kBleSupported = true;
|
||||
#else
|
||||
@@ -642,7 +702,7 @@ extern "C" void app_main(void) {
|
||||
"gateway",
|
||||
kWifiSupported,
|
||||
kBleSupported,
|
||||
true,
|
||||
kEthernetSupported,
|
||||
kEspnowSetupSupported,
|
||||
kUsbSetupStartupEnabled,
|
||||
};
|
||||
@@ -678,10 +738,11 @@ extern "C" void app_main(void) {
|
||||
ESP_ERROR_CHECK(s_cache->start());
|
||||
|
||||
gateway::GatewayControllerConfig controller_config;
|
||||
const bool network_transport_supported = profile.enable_wifi || profile.enable_eth;
|
||||
controller_config.setup_supported = true;
|
||||
controller_config.ble_supported = profile.enable_ble;
|
||||
controller_config.wifi_supported = profile.enable_wifi;
|
||||
controller_config.ip_router_supported = profile.enable_wifi || profile.enable_eth;
|
||||
controller_config.ip_router_supported = network_transport_supported;
|
||||
controller_config.internal_scene_supported = true;
|
||||
controller_config.internal_group_supported = true;
|
||||
|
||||
@@ -715,14 +776,14 @@ extern "C" void app_main(void) {
|
||||
bridge_config.bridge_enabled = true;
|
||||
bridge_config.modbus_enabled = kModbusBridgeSupported;
|
||||
bridge_config.modbus_startup_enabled = kModbusBridgeSupported && kModbusBridgeStartupEnabled;
|
||||
bridge_config.bacnet_enabled = profile.enable_wifi && kBacnetBridgeSupported;
|
||||
bridge_config.bacnet_startup_enabled = profile.enable_wifi && kBacnetBridgeSupported &&
|
||||
bridge_config.bacnet_enabled = network_transport_supported && kBacnetBridgeSupported;
|
||||
bridge_config.bacnet_startup_enabled = network_transport_supported && kBacnetBridgeSupported &&
|
||||
kBacnetBridgeStartupEnabled;
|
||||
bridge_config.knx_enabled = profile.enable_wifi && kKnxBridgeSupported;
|
||||
bridge_config.knx_startup_enabled = profile.enable_wifi && kKnxBridgeSupported &&
|
||||
bridge_config.knx_enabled = network_transport_supported && kKnxBridgeSupported;
|
||||
bridge_config.knx_startup_enabled = network_transport_supported && kKnxBridgeSupported &&
|
||||
kKnxBridgeStartupEnabled;
|
||||
bridge_config.cloud_enabled = profile.enable_wifi && kCloudBridgeSupported;
|
||||
bridge_config.cloud_startup_enabled = profile.enable_wifi && kCloudBridgeSupported &&
|
||||
bridge_config.cloud_enabled = network_transport_supported && kCloudBridgeSupported;
|
||||
bridge_config.cloud_startup_enabled = network_transport_supported && kCloudBridgeSupported &&
|
||||
kCloudBridgeStartupEnabled;
|
||||
bridge_config.modbus_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE);
|
||||
@@ -796,6 +857,12 @@ extern "C" void app_main(void) {
|
||||
if (profile.enable_wifi || profile.enable_eth) {
|
||||
gateway::GatewayNetworkServiceConfig network_config;
|
||||
network_config.wifi_enabled = profile.enable_wifi && kWifiStartupEnabled;
|
||||
network_config.ethernet_enabled = profile.enable_eth && kEthernetStartupEnabled;
|
||||
#if CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE
|
||||
network_config.ethernet_ignore_init_failure = true;
|
||||
#else
|
||||
network_config.ethernet_ignore_init_failure = false;
|
||||
#endif
|
||||
network_config.espnow_setup_enabled = profile.enable_espnow;
|
||||
network_config.espnow_setup_startup_enabled =
|
||||
profile.enable_espnow && kEspnowSetupStartupEnabled;
|
||||
@@ -815,6 +882,20 @@ extern "C" void app_main(void) {
|
||||
#endif
|
||||
network_config.http_port = static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_HTTP_PORT);
|
||||
network_config.udp_port = static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_UDP_PORT);
|
||||
network_config.ethernet_spi_host = CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST;
|
||||
network_config.ethernet_spi_sclk_gpio = CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO;
|
||||
network_config.ethernet_spi_mosi_gpio = CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO;
|
||||
network_config.ethernet_spi_miso_gpio = CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO;
|
||||
network_config.ethernet_spi_cs_gpio = CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO;
|
||||
network_config.ethernet_spi_int_gpio = CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO;
|
||||
network_config.ethernet_poll_period_ms =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS);
|
||||
network_config.ethernet_spi_clock_mhz =
|
||||
static_cast<uint8_t>(CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ);
|
||||
network_config.ethernet_phy_reset_gpio = CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO;
|
||||
network_config.ethernet_phy_addr = CONFIG_GATEWAY_ETHERNET_PHY_ADDR;
|
||||
network_config.ethernet_rx_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE);
|
||||
network_config.status_led_gpio = CONFIG_GATEWAY_STATUS_LED_GPIO;
|
||||
network_config.boot_button_gpio = CONFIG_GATEWAY_BOOT_BUTTON_GPIO;
|
||||
network_config.boot_button_long_press_ms = CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS;
|
||||
|
||||
+35
-11
@@ -656,6 +656,26 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
|
||||
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
|
||||
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
||||
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
||||
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||
|
||||
#
|
||||
# Gateway Wired Ethernet
|
||||
#
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=1
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=14
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=13
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=12
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=15
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=4
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0
|
||||
CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=36
|
||||
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=5
|
||||
CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
|
||||
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=3072
|
||||
# end of Gateway Wired Ethernet
|
||||
|
||||
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
|
||||
@@ -666,7 +686,20 @@ CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
|
||||
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
|
||||
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||
# CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED is not set
|
||||
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
|
||||
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_UDP_PORT=3671
|
||||
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
|
||||
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=4353
|
||||
CONFIG_GATEWAY_KNX_TP_UART_PORT=0
|
||||
CONFIG_GATEWAY_KNX_TP_TX_PIN=-1
|
||||
CONFIG_GATEWAY_KNX_TP_RX_PIN=-1
|
||||
CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200
|
||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=8192
|
||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
|
||||
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
|
||||
@@ -675,16 +708,7 @@ CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
|
||||
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
|
||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||
CONFIG_GATEWAY_485_CONTROL_ENABLED=y
|
||||
CONFIG_GATEWAY_485_CONTROL_BAUDRATE=9600
|
||||
CONFIG_GATEWAY_485_CONTROL_TX_PIN=-1
|
||||
CONFIG_GATEWAY_485_CONTROL_RX_PIN=-1
|
||||
CONFIG_GATEWAY_485_CONTROL_RX_BUFFER=256
|
||||
CONFIG_GATEWAY_485_CONTROL_TX_BUFFER=256
|
||||
CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS=20
|
||||
CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS=20
|
||||
CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE=4096
|
||||
CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY=4
|
||||
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
|
||||
# end of Gateway Startup Services
|
||||
|
||||
#
|
||||
|
||||
@@ -12,4 +12,7 @@ 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
|
||||
@@ -666,6 +666,7 @@ CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
|
||||
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
|
||||
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||
# CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED is not set
|
||||
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
||||
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
|
||||
@@ -744,6 +745,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
|
||||
@@ -1787,18 +1789,15 @@ CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
|
||||
# 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
|
||||
|
||||
@@ -10,6 +10,7 @@ set(GATEWAY_BRIDGE_REQUIRES
|
||||
log
|
||||
lwip
|
||||
nvs_flash
|
||||
openknx_idf
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "gateway_knx.hpp"
|
||||
#include "gateway_modbus.hpp"
|
||||
#include "gateway_provisioning.hpp"
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "driver/uart.h"
|
||||
@@ -1206,6 +1207,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return "dali_cloud_" + std::to_string(channel.gateway_id);
|
||||
}
|
||||
|
||||
std::string openKnxNamespace() const {
|
||||
return "openknx_" + std::to_string(channel.gateway_id);
|
||||
}
|
||||
|
||||
esp_err_t start() {
|
||||
comm = std::make_unique<DaliComm>(
|
||||
[this](const uint8_t* data, size_t len) {
|
||||
@@ -1254,6 +1259,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
engine->upsertModel(model);
|
||||
}
|
||||
|
||||
refreshOpenKnxEtsAssociationsLocked();
|
||||
|
||||
modbus = std::make_unique<GatewayModbusBridge>(*engine);
|
||||
if (modbus_config.has_value()) {
|
||||
modbus->setConfig(modbus_config.value());
|
||||
@@ -1269,7 +1276,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return result;
|
||||
}
|
||||
return knx->handleCemiFrame(data, len);
|
||||
});
|
||||
},
|
||||
openKnxNamespace());
|
||||
if (knx_config.has_value()) {
|
||||
knx->setConfig(knx_config.value());
|
||||
knx_router->setConfig(knx_config.value());
|
||||
@@ -1292,6 +1300,36 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
diagnostic_snapshot_cache.clear();
|
||||
}
|
||||
|
||||
void refreshOpenKnxEtsAssociationsLocked() {
|
||||
if (!service_config.knx_enabled) {
|
||||
return;
|
||||
}
|
||||
const auto active_config = activeKnxConfigLocked();
|
||||
if (!active_config.has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto snapshot = openknx::LoadEtsMemorySnapshot(openKnxNamespace());
|
||||
const bool has_downloaded_address = snapshot.individual_address != 0 &&
|
||||
snapshot.individual_address != 0xffff;
|
||||
if (!snapshot.configured && !has_downloaded_address && snapshot.associations.empty()) {
|
||||
return;
|
||||
}
|
||||
GatewayKnxConfig updated = active_config.value();
|
||||
if (has_downloaded_address) {
|
||||
updated.individual_address = snapshot.individual_address;
|
||||
}
|
||||
updated.ets_associations.clear();
|
||||
updated.ets_associations.reserve(snapshot.associations.size());
|
||||
for (const auto& association : snapshot.associations) {
|
||||
updated.ets_associations.push_back(GatewayKnxEtsAssociation{
|
||||
association.group_address, association.group_object_number});
|
||||
}
|
||||
knx_config = std::move(updated);
|
||||
ESP_LOGI(kTag, "gateway=%u loaded OpenKNX ETS address=0x%04x associations=%u from NVS namespace %s",
|
||||
channel.gateway_id, snapshot.individual_address,
|
||||
static_cast<unsigned>(snapshot.associations.size()), openKnxNamespace().c_str());
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
|
||||
std::string_view kind) {
|
||||
if (!ValidShortAddress(short_address) || kind.empty()) {
|
||||
@@ -2004,13 +2042,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
knx_last_error.empty() ? router_error.c_str()
|
||||
: knx_last_error.c_str());
|
||||
if (effective_knx.has_value()) {
|
||||
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled",
|
||||
effective_knx->dali_router_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled",
|
||||
effective_knx->ip_router_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", effective_knx->dali_router_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled", effective_knx->ip_router_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "tunnelEnabled", effective_knx->tunnel_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "multicastEnabled",
|
||||
effective_knx->multicast_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "multicastEnabled", effective_knx->multicast_enabled);
|
||||
cJSON_AddBoolToObject(knx_json, "etsDatabaseEnabled", effective_knx->ets_database_enabled);
|
||||
cJSON_AddNumberToObject(knx_json, "etsBindingCount",
|
||||
knx == nullptr ? 0 : knx->etsBindingCount());
|
||||
cJSON_AddStringToObject(knx_json, "mappingMode",
|
||||
GatewayKnxMappingModeToString(effective_knx->mapping_mode));
|
||||
cJSON_AddNumberToObject(knx_json, "mainGroup", effective_knx->main_group);
|
||||
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
|
||||
cJSON_AddStringToObject(knx_json, "multicastAddress",
|
||||
@@ -2262,6 +2302,17 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
cJSON_AddNumberToObject(item, "mainGroup", binding.main_group);
|
||||
cJSON_AddNumberToObject(item, "middleGroup", binding.middle_group);
|
||||
cJSON_AddNumberToObject(item, "subGroup", binding.sub_group);
|
||||
cJSON_AddStringToObject(item, "mappingMode",
|
||||
GatewayKnxMappingModeToString(binding.mapping_mode));
|
||||
if (binding.group_object_number >= 0) {
|
||||
cJSON_AddNumberToObject(item, "objectNumber", binding.group_object_number);
|
||||
}
|
||||
if (binding.channel_index >= 0) {
|
||||
cJSON_AddNumberToObject(item, "channelIndex", binding.channel_index);
|
||||
}
|
||||
if (!binding.object_role.empty()) {
|
||||
cJSON_AddStringToObject(item, "objectRole", binding.object_role.c_str());
|
||||
}
|
||||
cJSON_AddStringToObject(item, "name", binding.name.c_str());
|
||||
cJSON_AddStringToObject(item, "datapointType", binding.datapoint_type.c_str());
|
||||
cJSON_AddStringToObject(item, "dataType",
|
||||
@@ -2894,22 +2945,28 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
std::set<uint16_t>* used_ports = nullptr,
|
||||
std::set<int>* used_uarts = nullptr) {
|
||||
LockGuard guard(lock);
|
||||
GatewayKnxConfig merged_config = config;
|
||||
const auto previous_knx = activeKnxConfigLocked();
|
||||
if (merged_config.ets_associations.empty() && previous_knx.has_value() &&
|
||||
!previous_knx->ets_associations.empty()) {
|
||||
merged_config.ets_associations = previous_knx->ets_associations;
|
||||
}
|
||||
std::string validation_error;
|
||||
const esp_err_t validation_err = validateKnxConfigLocked(
|
||||
config, activeModbusConfigLocked(), &validation_error);
|
||||
merged_config, activeModbusConfigLocked(), &validation_error);
|
||||
if (validation_err != ESP_OK) {
|
||||
knx_last_error = validation_error;
|
||||
return validation_err;
|
||||
}
|
||||
const bool restart_router = knx_started || (knx_router != nullptr && knx_router->started());
|
||||
if (restart_router && config.ip_router_enabled && used_ports != nullptr &&
|
||||
used_ports->find(config.udp_port) != used_ports->end()) {
|
||||
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(config.udp_port);
|
||||
if (restart_router && merged_config.ip_router_enabled && used_ports != nullptr &&
|
||||
used_ports->find(merged_config.udp_port) != used_ports->end()) {
|
||||
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(merged_config.udp_port);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (restart_router && config.ip_router_enabled && used_uarts != nullptr &&
|
||||
used_uarts->find(config.tp_uart.uart_port) != used_uarts->end()) {
|
||||
knx_last_error = "KNX TP-UART UART" + std::to_string(config.tp_uart.uart_port) +
|
||||
if (restart_router && merged_config.ip_router_enabled && used_uarts != nullptr &&
|
||||
used_uarts->find(merged_config.tp_uart.uart_port) != used_uarts->end()) {
|
||||
knx_last_error = "KNX TP-UART UART" + std::to_string(merged_config.tp_uart.uart_port) +
|
||||
" is already used by another runtime";
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
@@ -2920,18 +2977,18 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
BridgeProvisioningStore store(bridgeNamespace());
|
||||
const esp_err_t err = store.saveObject(
|
||||
kBridgeConfigKey,
|
||||
GatewayBridgeStoredConfigToValue(bridge_config, modbus_config, config,
|
||||
GatewayBridgeStoredConfigToValue(bridge_config, modbus_config, merged_config,
|
||||
bacnet_server_config));
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
knx_config = config;
|
||||
knx_config = merged_config;
|
||||
bridge_config_loaded = true;
|
||||
if (knx != nullptr) {
|
||||
knx->setConfig(config);
|
||||
knx->setConfig(merged_config);
|
||||
}
|
||||
if (knx_router != nullptr) {
|
||||
knx_router->setConfig(config);
|
||||
knx_router->setConfig(merged_config);
|
||||
}
|
||||
if (restart_router) {
|
||||
return startKnx(used_ports, used_uarts);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_knx.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_cpp esp_driver_uart freertos log lwip
|
||||
REQUIRES dali_cpp esp_driver_uart freertos log lwip openknx_idf
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -12,12 +12,18 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
namespace openknx {
|
||||
class EtsDeviceRuntime;
|
||||
}
|
||||
|
||||
constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671;
|
||||
constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12";
|
||||
constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200;
|
||||
@@ -32,15 +38,30 @@ struct GatewayKnxTpUartConfig {
|
||||
uint32_t read_timeout_ms{20};
|
||||
};
|
||||
|
||||
enum class GatewayKnxMappingMode : uint8_t {
|
||||
kFormula = 0,
|
||||
kGwReg1Direct = 1,
|
||||
kManual = 2,
|
||||
kEtsDatabase = 3,
|
||||
};
|
||||
|
||||
struct GatewayKnxEtsAssociation {
|
||||
uint16_t group_address{0};
|
||||
uint16_t group_object_number{0};
|
||||
};
|
||||
|
||||
struct GatewayKnxConfig {
|
||||
bool dali_router_enabled{true};
|
||||
bool ip_router_enabled{false};
|
||||
bool tunnel_enabled{true};
|
||||
bool multicast_enabled{true};
|
||||
bool ets_database_enabled{true};
|
||||
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
|
||||
uint8_t main_group{0};
|
||||
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
||||
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
||||
uint16_t individual_address{0x1101};
|
||||
std::vector<GatewayKnxEtsAssociation> ets_associations;
|
||||
GatewayKnxTpUartConfig tp_uart;
|
||||
};
|
||||
|
||||
@@ -69,16 +90,29 @@ struct GatewayKnxDaliBinding {
|
||||
uint8_t main_group{0};
|
||||
uint8_t middle_group{0};
|
||||
uint8_t sub_group{0};
|
||||
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
|
||||
int group_object_number{-1};
|
||||
int channel_index{-1};
|
||||
std::string address;
|
||||
std::string name;
|
||||
std::string object_role;
|
||||
std::string datapoint_type;
|
||||
GatewayKnxDaliDataType data_type{GatewayKnxDaliDataType::kUnknown};
|
||||
GatewayKnxDaliTarget target;
|
||||
};
|
||||
|
||||
struct GatewayKnxCommissioningBallast {
|
||||
uint8_t high{0};
|
||||
uint8_t middle{0};
|
||||
uint8_t low{0};
|
||||
uint8_t short_address{0xff};
|
||||
};
|
||||
|
||||
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
||||
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
||||
|
||||
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(
|
||||
@@ -94,27 +128,66 @@ class GatewayKnxBridge {
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
const GatewayKnxConfig& config() const;
|
||||
size_t etsBindingCount() const;
|
||||
|
||||
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
|
||||
DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len);
|
||||
DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
size_t len);
|
||||
bool handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
|
||||
private:
|
||||
DaliBridgeResult executeForDecodedWrite(uint16_t group_address,
|
||||
GatewayKnxDaliDataType data_type,
|
||||
GatewayKnxDaliTarget target,
|
||||
const uint8_t* data, size_t len);
|
||||
DaliBridgeResult executeEtsBindings(uint16_t group_address,
|
||||
const std::vector<GatewayKnxDaliBinding>& bindings,
|
||||
const uint8_t* data, size_t len);
|
||||
void rebuildEtsBindings();
|
||||
|
||||
bool handleReg1TypeCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1ScanCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1AssignCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1EvgWriteCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1EvgReadCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1SetSceneCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1GetSceneCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1IdentifyCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1ScanState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1AssignState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1FoundEvgsState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
|
||||
DaliBridgeEngine& engine_;
|
||||
GatewayKnxConfig config_;
|
||||
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
|
||||
bool commissioning_scan_done_{true};
|
||||
bool commissioning_assign_done_{true};
|
||||
std::vector<GatewayKnxCommissioningBallast> commissioning_found_ballasts_;
|
||||
};
|
||||
|
||||
class GatewayKnxTpIpRouter {
|
||||
public:
|
||||
using CemiFrameHandler = std::function<DaliBridgeResult(const uint8_t* data, size_t len)>;
|
||||
|
||||
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler);
|
||||
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler,
|
||||
std::string openknx_namespace = "openknx");
|
||||
~GatewayKnxTpIpRouter();
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
@@ -151,6 +224,10 @@ class GatewayKnxTpIpRouter {
|
||||
void sendConnectResponse(uint8_t channel_id, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendRoutingIndication(const uint8_t* data, size_t len);
|
||||
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len);
|
||||
void syncOpenKnxConfigFromDevice();
|
||||
uint16_t effectiveIndividualAddress() const;
|
||||
uint16_t effectiveTunnelAddress() const;
|
||||
void pollTpUart();
|
||||
void handleTpUartControlByte(uint8_t byte);
|
||||
void handleTpTelegram(const uint8_t* data, size_t len);
|
||||
@@ -158,7 +235,9 @@ class GatewayKnxTpIpRouter {
|
||||
|
||||
GatewayKnxBridge& bridge_;
|
||||
CemiFrameHandler handler_;
|
||||
std::string openknx_namespace_;
|
||||
GatewayKnxConfig config_;
|
||||
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
std::atomic_bool stop_requested_{false};
|
||||
std::atomic_bool started_{false};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_network.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_domain esp_event esp_http_server esp_netif esp_wifi freertos gateway_bridge gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
REQUIRES dali_domain esp_driver_gpio esp_driver_spi esp_eth esp_event esp_http_server esp_hw_support esp_netif esp_wifi freertos gateway_bridge gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_eth.h"
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_eth_netif_glue.h"
|
||||
#include "esp_eth_phy.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_now.h"
|
||||
@@ -26,6 +30,8 @@ struct DaliRawFrame;
|
||||
|
||||
struct GatewayNetworkServiceConfig {
|
||||
bool wifi_enabled{true};
|
||||
bool ethernet_enabled{false};
|
||||
bool ethernet_ignore_init_failure{false};
|
||||
bool espnow_setup_enabled{true};
|
||||
bool espnow_setup_startup_enabled{false};
|
||||
bool smartconfig_enabled{true};
|
||||
@@ -35,6 +41,17 @@ struct GatewayNetworkServiceConfig {
|
||||
bool udp_enabled{true};
|
||||
uint16_t http_port{80};
|
||||
uint16_t udp_port{2020};
|
||||
int ethernet_spi_host{1};
|
||||
int ethernet_spi_sclk_gpio{14};
|
||||
int ethernet_spi_mosi_gpio{13};
|
||||
int ethernet_spi_miso_gpio{12};
|
||||
int ethernet_spi_cs_gpio{15};
|
||||
int ethernet_spi_int_gpio{4};
|
||||
uint32_t ethernet_poll_period_ms{0};
|
||||
uint8_t ethernet_spi_clock_mhz{36};
|
||||
int ethernet_phy_reset_gpio{5};
|
||||
int ethernet_phy_addr{1};
|
||||
uint32_t ethernet_rx_task_stack_size{3072};
|
||||
int status_led_gpio{-1};
|
||||
bool status_led_active_high{true};
|
||||
int boot_button_gpio{-1};
|
||||
@@ -66,12 +83,17 @@ class GatewayNetworkService {
|
||||
static esp_err_t HandleLedOnGet(httpd_req_t* req);
|
||||
static esp_err_t HandleLedOffGet(httpd_req_t* req);
|
||||
static esp_err_t HandleJqJsGet(httpd_req_t* req);
|
||||
static void HandleEthernetEvent(void* arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data);
|
||||
static void HandleWifiEvent(void* arg, esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data);
|
||||
static void HandleEspNowReceive(const esp_now_recv_info_t* info, const uint8_t* data,
|
||||
int data_len);
|
||||
|
||||
esp_err_t ensureNetworkStack();
|
||||
esp_err_t startEthernet();
|
||||
esp_err_t probeEthernetStartup();
|
||||
void stopEthernet();
|
||||
esp_err_t startWifi();
|
||||
esp_err_t startSetupAp();
|
||||
esp_err_t startSmartconfig();
|
||||
@@ -89,6 +111,7 @@ class GatewayNetworkService {
|
||||
void bootButtonTaskLoop();
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
void handleWifiControl(uint8_t mode);
|
||||
void handleEthernetEvent(esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
void handleWifiEvent(esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
void handleEspNowReceive(const esp_now_recv_info_t* info, const uint8_t* data, int data_len);
|
||||
void handleSetupUartFrame(int setup_id, const std::vector<uint8_t>& frame);
|
||||
@@ -106,8 +129,15 @@ class GatewayNetworkService {
|
||||
GatewayBridgeService* bridge_service_{nullptr};
|
||||
bool started_{false};
|
||||
httpd_handle_t http_server_{nullptr};
|
||||
esp_netif_t* eth_netif_{nullptr};
|
||||
esp_netif_t* wifi_sta_netif_{nullptr};
|
||||
esp_netif_t* wifi_ap_netif_{nullptr};
|
||||
esp_eth_handle_t eth_handle_{nullptr};
|
||||
esp_eth_mac_t* eth_mac_{nullptr};
|
||||
esp_eth_phy_t* eth_phy_{nullptr};
|
||||
esp_eth_netif_glue_handle_t eth_glue_{nullptr};
|
||||
bool ethernet_started_{false};
|
||||
bool ethernet_event_handlers_registered_{false};
|
||||
bool wifi_started_{false};
|
||||
bool wifi_event_handlers_registered_{false};
|
||||
bool setup_ap_started_{false};
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_eth_mac_spi.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_ip_addr.h"
|
||||
#include "esp_smartconfig.h"
|
||||
@@ -268,6 +272,19 @@ esp_err_t GatewayNetworkService::start() {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (config_.ethernet_enabled) {
|
||||
err = startEthernet();
|
||||
if (err != ESP_OK) {
|
||||
if (config_.ethernet_ignore_init_failure) {
|
||||
ESP_LOGW(kTag, "Ethernet init failed; Ethernet is disabled for this boot: %s",
|
||||
esp_err_to_name(err));
|
||||
config_.ethernet_enabled = false;
|
||||
} else {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.espnow_setup_startup_enabled) {
|
||||
err = startSetupAp();
|
||||
if (err != ESP_OK) {
|
||||
@@ -320,7 +337,8 @@ esp_err_t GatewayNetworkService::start() {
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "network service started http=%d udp=%d", config_.http_enabled,
|
||||
ESP_LOGI(kTag, "network service started eth=%d wifi=%d http=%d udp=%d",
|
||||
config_.ethernet_enabled, config_.wifi_enabled, config_.http_enabled,
|
||||
config_.udp_enabled);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -341,6 +359,234 @@ esp_err_t GatewayNetworkService::ensureNetworkStack() {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startEthernet() {
|
||||
if (ethernet_started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_ETH_SPI_ETHERNET_W5500
|
||||
if (eth_netif_ == nullptr) {
|
||||
esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_ETH();
|
||||
eth_netif_ = esp_netif_new(&netif_config);
|
||||
if (eth_netif_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create Ethernet netif");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ethernet_event_handlers_registered_) {
|
||||
esp_err_t err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID,
|
||||
&GatewayNetworkService::HandleEthernetEvent, this);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to register Ethernet event handler: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
err = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP,
|
||||
&GatewayNetworkService::HandleEthernetEvent, this);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to register Ethernet IP event handler: %s", esp_err_to_name(err));
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
||||
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
ethernet_event_handlers_registered_ = true;
|
||||
}
|
||||
|
||||
if (config_.ethernet_spi_int_gpio >= 0) {
|
||||
esp_err_t err = gpio_install_isr_service(0);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to install GPIO ISR service for Ethernet: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
const auto spi_host = static_cast<spi_host_device_t>(config_.ethernet_spi_host);
|
||||
spi_bus_config_t bus_config = {};
|
||||
bus_config.miso_io_num = config_.ethernet_spi_miso_gpio;
|
||||
bus_config.mosi_io_num = config_.ethernet_spi_mosi_gpio;
|
||||
bus_config.sclk_io_num = config_.ethernet_spi_sclk_gpio;
|
||||
bus_config.quadwp_io_num = -1;
|
||||
bus_config.quadhd_io_num = -1;
|
||||
|
||||
esp_err_t err = spi_bus_initialize(spi_host, &bus_config, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(kTag, "failed to initialize Ethernet SPI host %d: %s", config_.ethernet_spi_host,
|
||||
esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
spi_device_interface_config_t spi_device_config = {};
|
||||
spi_device_config.mode = 0;
|
||||
spi_device_config.clock_speed_hz = static_cast<int>(config_.ethernet_spi_clock_mhz) * 1000 * 1000;
|
||||
spi_device_config.spics_io_num = config_.ethernet_spi_cs_gpio;
|
||||
spi_device_config.queue_size = 20;
|
||||
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
mac_config.rx_task_stack_size = config_.ethernet_rx_task_stack_size;
|
||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||
phy_config.phy_addr = config_.ethernet_phy_addr;
|
||||
phy_config.reset_gpio_num = config_.ethernet_phy_reset_gpio;
|
||||
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_host, &spi_device_config);
|
||||
w5500_config.int_gpio_num = config_.ethernet_spi_int_gpio;
|
||||
w5500_config.poll_period_ms = config_.ethernet_poll_period_ms;
|
||||
if (w5500_config.int_gpio_num < 0 && w5500_config.poll_period_ms == 0) {
|
||||
w5500_config.poll_period_ms = 100;
|
||||
}
|
||||
|
||||
eth_mac_ = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
if (eth_mac_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create W5500 Ethernet MAC");
|
||||
stopEthernet();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
eth_phy_ = esp_eth_phy_new_w5500(&phy_config);
|
||||
if (eth_phy_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create W5500 Ethernet PHY");
|
||||
stopEthernet();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(eth_mac_, eth_phy_);
|
||||
err = esp_eth_driver_install(ð_config, ð_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to install Ethernet driver: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
uint8_t eth_mac[6] = {};
|
||||
err = esp_read_mac(eth_mac, ESP_MAC_ETH);
|
||||
if (err == ESP_OK) {
|
||||
err = esp_eth_ioctl(eth_handle_, ETH_CMD_S_MAC_ADDR, eth_mac);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to set Ethernet MAC address: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
EthernetInfo info;
|
||||
info.mac = MacToHex(eth_mac);
|
||||
runtime_.setEthernetInfo(std::move(info));
|
||||
} else {
|
||||
ESP_LOGW(kTag, "failed to read Ethernet MAC address: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
eth_glue_ = esp_eth_new_netif_glue(eth_handle_);
|
||||
if (eth_glue_ == nullptr) {
|
||||
ESP_LOGE(kTag, "failed to create Ethernet netif glue");
|
||||
stopEthernet();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
err = esp_netif_attach(eth_netif_, eth_glue_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to attach Ethernet netif: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
err = esp_eth_start(eth_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to start Ethernet: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
ethernet_started_ = true;
|
||||
err = probeEthernetStartup();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "Ethernet startup probe failed: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag,
|
||||
"Ethernet W5500 started spi_host=%d sclk=%d mosi=%d miso=%d cs=%d int=%d reset=%d",
|
||||
config_.ethernet_spi_host, config_.ethernet_spi_sclk_gpio,
|
||||
config_.ethernet_spi_mosi_gpio, config_.ethernet_spi_miso_gpio,
|
||||
config_.ethernet_spi_cs_gpio, config_.ethernet_spi_int_gpio,
|
||||
config_.ethernet_phy_reset_gpio);
|
||||
return ESP_OK;
|
||||
#else
|
||||
ESP_LOGW(kTag, "Ethernet requested but W5500 support is not enabled in esp-eth");
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::probeEthernetStartup() {
|
||||
if (eth_handle_ == nullptr || !ethernet_started_) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_eth_stop(eth_handle_);
|
||||
ethernet_started_ = false;
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = esp_eth_start(eth_handle_);
|
||||
if (err == ESP_OK) {
|
||||
ethernet_started_ = true;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void GatewayNetworkService::stopEthernet() {
|
||||
if (ethernet_event_handlers_registered_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
||||
IP_EVENT, IP_EVENT_ETH_GOT_IP, &GatewayNetworkService::HandleEthernetEvent));
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_event_handler_unregister(
|
||||
ETH_EVENT, ESP_EVENT_ANY_ID, &GatewayNetworkService::HandleEthernetEvent));
|
||||
ethernet_event_handlers_registered_ = false;
|
||||
}
|
||||
|
||||
if (eth_handle_ != nullptr) {
|
||||
const esp_err_t stop_err = esp_eth_stop(eth_handle_);
|
||||
if (stop_err != ESP_OK && stop_err != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGW(kTag, "failed to stop Ethernet during disable: %s", esp_err_to_name(stop_err));
|
||||
}
|
||||
ethernet_started_ = false;
|
||||
}
|
||||
|
||||
if (eth_glue_ != nullptr) {
|
||||
const esp_err_t glue_err = esp_eth_del_netif_glue(eth_glue_);
|
||||
if (glue_err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to delete Ethernet netif glue: %s", esp_err_to_name(glue_err));
|
||||
} else {
|
||||
eth_glue_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (eth_handle_ != nullptr) {
|
||||
const esp_err_t uninstall_err = esp_eth_driver_uninstall(eth_handle_);
|
||||
if (uninstall_err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to uninstall Ethernet driver: %s", esp_err_to_name(uninstall_err));
|
||||
} else {
|
||||
eth_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (eth_phy_ != nullptr && eth_handle_ == nullptr) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_phy_->del(eth_phy_));
|
||||
eth_phy_ = nullptr;
|
||||
}
|
||||
if (eth_mac_ != nullptr && eth_handle_ == nullptr) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(eth_mac_->del(eth_mac_));
|
||||
eth_mac_ = nullptr;
|
||||
}
|
||||
|
||||
if (eth_netif_ != nullptr && eth_glue_ == nullptr) {
|
||||
esp_netif_destroy(eth_netif_);
|
||||
eth_netif_ = nullptr;
|
||||
}
|
||||
|
||||
runtime_.clearEthernetInfo();
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startWifi() {
|
||||
if (wifi_started_) {
|
||||
return ESP_OK;
|
||||
@@ -778,6 +1024,69 @@ void GatewayNetworkService::HandleEspNowReceive(const esp_now_recv_info_t* info,
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::HandleEthernetEvent(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(arg);
|
||||
if (service != nullptr) {
|
||||
service->handleEthernetEvent(event_base, event_id, event_data);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleEthernetEvent(esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data) {
|
||||
if (event_base == ETH_EVENT) {
|
||||
esp_eth_handle_t handle = eth_handle_;
|
||||
if (event_data != nullptr) {
|
||||
handle = *static_cast<esp_eth_handle_t*>(event_data);
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_CONNECTED) {
|
||||
uint8_t mac[6] = {};
|
||||
if (handle != nullptr && esp_eth_ioctl(handle, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
|
||||
const std::string mac_hex = MacToHex(mac);
|
||||
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
|
||||
info.mac = mac_hex;
|
||||
runtime_.setEthernetInfo(std::move(info));
|
||||
ESP_LOGI(kTag, "Ethernet link up mac=%s", mac_hex.c_str());
|
||||
} else {
|
||||
ESP_LOGI(kTag, "Ethernet link up");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_DISCONNECTED) {
|
||||
runtime_.clearEthernetIp();
|
||||
ESP_LOGI(kTag, "Ethernet link down");
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_START) {
|
||||
ESP_LOGI(kTag, "Ethernet driver started");
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_id == ETHERNET_EVENT_STOP) {
|
||||
runtime_.clearEthernetIp();
|
||||
ESP_LOGI(kTag, "Ethernet driver stopped");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event_base == IP_EVENT && event_id == IP_EVENT_ETH_GOT_IP && event_data != nullptr) {
|
||||
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
|
||||
char ip[16] = {0};
|
||||
esp_ip4addr_ntoa(&event->ip_info.ip, ip, sizeof(ip));
|
||||
EthernetInfo info = runtime_.deviceInfo().eth.value_or(EthernetInfo{});
|
||||
uint8_t mac[6] = {};
|
||||
if (eth_handle_ != nullptr && esp_eth_ioctl(eth_handle_, ETH_CMD_G_MAC_ADDR, mac) == ESP_OK) {
|
||||
info.mac = MacToHex(mac);
|
||||
}
|
||||
info.ip = ip;
|
||||
runtime_.setEthernetInfo(std::move(info));
|
||||
ESP_LOGI(kTag, "Ethernet got IP %s", ip);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data) {
|
||||
if (!config_.wifi_enabled) {
|
||||
@@ -1168,6 +1477,15 @@ std::string GatewayNetworkService::deviceInfoJson() const {
|
||||
}
|
||||
}
|
||||
|
||||
if (info.eth.has_value()) {
|
||||
cJSON* eth = cJSON_CreateObject();
|
||||
if (eth != nullptr) {
|
||||
cJSON_AddStringToObject(eth, "mac", info.eth->mac.c_str());
|
||||
cJSON_AddStringToObject(eth, "IP", info.eth->ip.c_str());
|
||||
cJSON_AddItemToObject(root, "ethInfo", eth);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string rendered = PrintJson(root);
|
||||
cJSON_Delete(root);
|
||||
return rendered;
|
||||
|
||||
@@ -30,6 +30,11 @@ struct WirelessInfo {
|
||||
std::string ip;
|
||||
};
|
||||
|
||||
struct EthernetInfo {
|
||||
std::string mac;
|
||||
std::string ip;
|
||||
};
|
||||
|
||||
struct GatewayRuntimeConfig {
|
||||
std::string_view project_name;
|
||||
std::string_view version;
|
||||
@@ -47,6 +52,7 @@ struct GatewayDeviceInfo {
|
||||
size_t dali_gateway_count{0};
|
||||
bool ble_enabled{false};
|
||||
std::optional<WirelessInfo> wlan;
|
||||
std::optional<EthernetInfo> eth;
|
||||
};
|
||||
|
||||
class GatewaySettingsStore {
|
||||
@@ -119,6 +125,9 @@ class GatewayRuntime {
|
||||
void setGatewayCount(size_t gateway_count);
|
||||
void setWirelessInfo(WirelessInfo info);
|
||||
bool clearWirelessInfo();
|
||||
void setEthernetInfo(EthernetInfo info);
|
||||
void clearEthernetInfo();
|
||||
void clearEthernetIp();
|
||||
void setCommandAddressResolver(std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver);
|
||||
|
||||
GatewayDeviceInfo deviceInfo() const;
|
||||
@@ -159,6 +168,7 @@ class GatewayRuntime {
|
||||
CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone};
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> command_address_resolver_;
|
||||
std::optional<WirelessInfo> wireless_info_;
|
||||
std::optional<EthernetInfo> ethernet_info_;
|
||||
SemaphoreHandle_t command_lock_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -450,6 +450,23 @@ bool GatewayRuntime::clearWirelessInfo() {
|
||||
return settings_.clearWifiCredentials();
|
||||
}
|
||||
|
||||
void GatewayRuntime::setEthernetInfo(EthernetInfo info) {
|
||||
LockGuard guard(command_lock_);
|
||||
ethernet_info_ = std::move(info);
|
||||
}
|
||||
|
||||
void GatewayRuntime::clearEthernetInfo() {
|
||||
LockGuard guard(command_lock_);
|
||||
ethernet_info_.reset();
|
||||
}
|
||||
|
||||
void GatewayRuntime::clearEthernetIp() {
|
||||
LockGuard guard(command_lock_);
|
||||
if (ethernet_info_.has_value()) {
|
||||
ethernet_info_->ip.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayRuntime::setCommandAddressResolver(
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
|
||||
LockGuard guard(command_lock_);
|
||||
@@ -470,6 +487,7 @@ GatewayDeviceInfo GatewayRuntime::deviceInfo() const {
|
||||
info.dali_gateway_count = gateway_count_;
|
||||
info.ble_enabled = ble_enabled_;
|
||||
info.wlan = wireless_info_;
|
||||
info.eth = ethernet_info_;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
set(OPENKNX_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../knx")
|
||||
set(TPUART_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../tpuart")
|
||||
|
||||
if(NOT EXISTS "${OPENKNX_ROOT}/src/knx/platform.h")
|
||||
message(FATAL_ERROR "OpenKNX submodule is missing at ${OPENKNX_ROOT}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${TPUART_ROOT}/src/TPUart/DataLinkLayer.h")
|
||||
message(FATAL_ERROR "TPUart submodule is missing at ${TPUART_ROOT}")
|
||||
endif()
|
||||
|
||||
file(GLOB OPENKNX_SRCS
|
||||
"${OPENKNX_ROOT}/src/knx/*.cpp"
|
||||
)
|
||||
|
||||
set(TPUART_SRCS
|
||||
"${TPUART_ROOT}/src/TPUart/DataLinkLayer.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/Receiver.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/RepetitionFilter.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/RingBuffer.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/SearchBuffer.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/Statistics.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/SystemState.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart/Transmitter.cpp"
|
||||
"${TPUART_ROOT}/src/TPUart.cpp"
|
||||
)
|
||||
|
||||
idf_component_register(
|
||||
SRCS
|
||||
"src/arduino_compat.cpp"
|
||||
"src/esp_idf_platform.cpp"
|
||||
"src/ets_device_runtime.cpp"
|
||||
"src/ets_memory_loader.cpp"
|
||||
"src/tpuart_uart_interface.cpp"
|
||||
${OPENKNX_SRCS}
|
||||
${TPUART_SRCS}
|
||||
INCLUDE_DIRS
|
||||
"include"
|
||||
"${OPENKNX_ROOT}/src"
|
||||
"${TPUART_ROOT}/src"
|
||||
REQUIRES
|
||||
esp_driver_gpio
|
||||
esp_driver_uart
|
||||
esp_netif
|
||||
esp_timer
|
||||
esp_wifi
|
||||
freertos
|
||||
log
|
||||
lwip
|
||||
nvs_flash
|
||||
)
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB} PUBLIC
|
||||
MASK_VERSION=0x07B0
|
||||
KNX_FLASH_SIZE=4096
|
||||
KNX_NO_AUTOMATIC_GLOBAL_INSTANCE
|
||||
KNX_NO_SPI
|
||||
USE_CEMI_SERVER
|
||||
)
|
||||
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||
-Wno-unused-parameter
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef DEC
|
||||
#define DEC 10
|
||||
#endif
|
||||
|
||||
#ifndef HEX
|
||||
#define HEX 16
|
||||
#endif
|
||||
|
||||
#ifndef INPUT
|
||||
#define INPUT 0x0
|
||||
#endif
|
||||
|
||||
#ifndef OUTPUT
|
||||
#define OUTPUT 0x1
|
||||
#endif
|
||||
|
||||
#ifndef INPUT_PULLUP
|
||||
#define INPUT_PULLUP 0x2
|
||||
#endif
|
||||
|
||||
#ifndef INPUT_PULLDOWN
|
||||
#define INPUT_PULLDOWN 0x3
|
||||
#endif
|
||||
|
||||
#ifndef LOW
|
||||
#define LOW 0x0
|
||||
#endif
|
||||
|
||||
#ifndef HIGH
|
||||
#define HIGH 0x1
|
||||
#endif
|
||||
|
||||
#ifndef CHANGE
|
||||
#define CHANGE 2
|
||||
#endif
|
||||
|
||||
#ifndef FALLING
|
||||
#define FALLING 3
|
||||
#endif
|
||||
|
||||
#ifndef RISING
|
||||
#define RISING 4
|
||||
#endif
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
uint32_t millis();
|
||||
uint32_t micros();
|
||||
void delay(uint32_t millis);
|
||||
void delayMicroseconds(unsigned int howLong);
|
||||
void pinMode(uint32_t pin, uint32_t mode);
|
||||
void digitalWrite(uint32_t pin, uint32_t value);
|
||||
uint32_t digitalRead(uint32_t pin);
|
||||
typedef void (*voidFuncPtr)(void);
|
||||
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode);
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "knx/platform.h"
|
||||
|
||||
#include "esp_netif.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
class EspIdfPlatform : public Platform {
|
||||
public:
|
||||
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
|
||||
const char* nvs_namespace = "openknx");
|
||||
~EspIdfPlatform() override;
|
||||
|
||||
void networkInterface(esp_netif_t* netif);
|
||||
esp_netif_t* networkInterface() const;
|
||||
|
||||
uint32_t currentIpAddress() override;
|
||||
uint32_t currentSubnetMask() override;
|
||||
uint32_t currentDefaultGateway() override;
|
||||
void macAddress(uint8_t* data) override;
|
||||
uint32_t uniqueSerialNumber() override;
|
||||
|
||||
void restart() override;
|
||||
void fatalError() override;
|
||||
|
||||
void setupMultiCast(uint32_t addr, uint16_t port) override;
|
||||
void closeMultiCast() override;
|
||||
bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override;
|
||||
int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override;
|
||||
int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr,
|
||||
uint16_t& src_port) override;
|
||||
bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer,
|
||||
uint16_t len) override;
|
||||
|
||||
uint8_t* getEepromBuffer(uint32_t size) override;
|
||||
void commitToEeprom() override;
|
||||
|
||||
private:
|
||||
esp_netif_t* effectiveNetif() const;
|
||||
void loadEeprom(size_t size);
|
||||
|
||||
esp_netif_t* netif_{nullptr};
|
||||
int udp_sock_{-1};
|
||||
sockaddr_in multicast_remote_{};
|
||||
sockaddr_in last_remote_{};
|
||||
bool has_last_remote_{false};
|
||||
std::vector<uint8_t> eeprom_;
|
||||
std::string nvs_namespace_;
|
||||
bool eeprom_loaded_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx/cemi_frame.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
class EtsDeviceRuntime {
|
||||
public:
|
||||
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
||||
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
|
||||
const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response)>;
|
||||
|
||||
EtsDeviceRuntime(std::string nvs_namespace, uint16_t fallback_individual_address);
|
||||
~EtsDeviceRuntime();
|
||||
|
||||
uint16_t individualAddress() const;
|
||||
uint16_t tunnelClientAddress() const;
|
||||
bool configured() const;
|
||||
EtsMemorySnapshot snapshot() const;
|
||||
|
||||
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||
FunctionPropertyHandler state_handler);
|
||||
|
||||
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
|
||||
void loop();
|
||||
|
||||
private:
|
||||
static void EmitTunnelFrame(CemiFrame& frame, void* context);
|
||||
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
static bool HandleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
static uint16_t DefaultTunnelClientAddress(uint16_t individual_address);
|
||||
static bool DispatchFunctionProperty(FunctionPropertyHandler* handler, uint8_t object_index,
|
||||
uint8_t property_id, uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length);
|
||||
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
|
||||
|
||||
std::string nvs_namespace_;
|
||||
EspIdfPlatform platform_;
|
||||
Bau07B0 device_;
|
||||
CemiFrameSender sender_;
|
||||
FunctionPropertyHandler command_handler_;
|
||||
FunctionPropertyHandler state_handler_;
|
||||
};
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
struct EtsAssociation {
|
||||
uint16_t group_address{0};
|
||||
uint16_t group_object_number{0};
|
||||
};
|
||||
|
||||
struct EtsMemorySnapshot {
|
||||
bool configured{false};
|
||||
uint16_t individual_address{0};
|
||||
std::vector<EtsAssociation> associations;
|
||||
};
|
||||
|
||||
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace);
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
#include "openknx_idf/ets_device_runtime.h"
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
#include "openknx_idf/tpuart_uart_interface.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx_facade.h"
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
using DaliGatewayDevice = KnxFacade<EspIdfPlatform, Bau07B0>;
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "TPUart/Interface/Abstract.h"
|
||||
|
||||
#include "driver/uart.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace gateway::openknx {
|
||||
|
||||
class TpuartUartInterface : public TPUart::Interface::Abstract {
|
||||
public:
|
||||
TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
|
||||
size_t rx_buffer_size = 512, size_t tx_buffer_size = 512);
|
||||
~TpuartUartInterface();
|
||||
|
||||
void begin(int baud) override;
|
||||
void end() override;
|
||||
bool available() override;
|
||||
bool availableForWrite() override;
|
||||
bool write(char value) override;
|
||||
int read() override;
|
||||
bool overflow() override;
|
||||
void flush() override;
|
||||
bool hasCallback() override;
|
||||
void registerCallback(std::function<bool()> callback) override;
|
||||
|
||||
private:
|
||||
uart_port_t uart_port_;
|
||||
int tx_pin_;
|
||||
int rx_pin_;
|
||||
size_t rx_buffer_size_;
|
||||
size_t tx_buffer_size_;
|
||||
std::atomic_bool overflow_{false};
|
||||
std::function<bool()> callback_;
|
||||
};
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,180 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_timer.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
|
||||
std::array<voidFuncPtr, GPIO_NUM_MAX> g_gpio_callbacks{};
|
||||
bool g_isr_service_installed = false;
|
||||
|
||||
void IRAM_ATTR gpioIsrThunk(void* arg) {
|
||||
const auto pin = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(arg));
|
||||
if (pin < g_gpio_callbacks.size() && g_gpio_callbacks[pin] != nullptr) {
|
||||
g_gpio_callbacks[pin]();
|
||||
}
|
||||
}
|
||||
|
||||
gpio_int_type_t toGpioInterrupt(uint32_t mode) {
|
||||
switch (mode) {
|
||||
case RISING:
|
||||
return GPIO_INTR_POSEDGE;
|
||||
case FALLING:
|
||||
return GPIO_INTR_NEGEDGE;
|
||||
case CHANGE:
|
||||
return GPIO_INTR_ANYEDGE;
|
||||
default:
|
||||
return GPIO_INTR_DISABLE;
|
||||
}
|
||||
}
|
||||
|
||||
void printUnsigned(unsigned long long value, int base) {
|
||||
if (base == HEX) {
|
||||
std::printf("%llX", value);
|
||||
} else {
|
||||
std::printf("%llu", value);
|
||||
}
|
||||
}
|
||||
|
||||
void printSigned(long long value, int base) {
|
||||
if (base == HEX) {
|
||||
std::printf("%llX", static_cast<unsigned long long>(value));
|
||||
} else {
|
||||
std::printf("%lld", value);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t millis() { return static_cast<uint32_t>(esp_timer_get_time() / 1000ULL); }
|
||||
|
||||
uint32_t micros() { return static_cast<uint32_t>(esp_timer_get_time()); }
|
||||
|
||||
void delay(uint32_t millis) { vTaskDelay(pdMS_TO_TICKS(millis)); }
|
||||
|
||||
void delayMicroseconds(unsigned int howLong) { esp_rom_delay_us(howLong); }
|
||||
|
||||
void pinMode(uint32_t pin, uint32_t mode) {
|
||||
if (pin >= GPIO_NUM_MAX) {
|
||||
return;
|
||||
}
|
||||
gpio_config_t config{};
|
||||
config.pin_bit_mask = 1ULL << pin;
|
||||
config.mode = mode == OUTPUT ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
|
||||
config.pull_up_en = mode == INPUT_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
config.pull_down_en = mode == INPUT_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||
config.intr_type = GPIO_INTR_DISABLE;
|
||||
gpio_config(&config);
|
||||
}
|
||||
|
||||
void digitalWrite(uint32_t pin, uint32_t value) {
|
||||
if (pin < GPIO_NUM_MAX) {
|
||||
gpio_set_level(static_cast<gpio_num_t>(pin), value == LOW ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t digitalRead(uint32_t pin) {
|
||||
if (pin >= GPIO_NUM_MAX) {
|
||||
return LOW;
|
||||
}
|
||||
return gpio_get_level(static_cast<gpio_num_t>(pin)) == 0 ? LOW : HIGH;
|
||||
}
|
||||
|
||||
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) {
|
||||
if (pin >= GPIO_NUM_MAX) {
|
||||
return;
|
||||
}
|
||||
if (!g_isr_service_installed) {
|
||||
const esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||
g_isr_service_installed = err == ESP_OK || err == ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (!g_isr_service_installed) {
|
||||
return;
|
||||
}
|
||||
gpio_set_intr_type(static_cast<gpio_num_t>(pin), toGpioInterrupt(mode));
|
||||
gpio_isr_handler_remove(static_cast<gpio_num_t>(pin));
|
||||
g_gpio_callbacks[pin] = callback;
|
||||
if (callback != nullptr) {
|
||||
gpio_isr_handler_add(static_cast<gpio_num_t>(pin), gpioIsrThunk,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(pin)));
|
||||
}
|
||||
}
|
||||
|
||||
void print(const char value[]) { std::printf("%s", value == nullptr ? "" : value); }
|
||||
|
||||
void print(char value) { std::printf("%c", value); }
|
||||
|
||||
void print(unsigned char value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(int value, int base) { printSigned(value, base); }
|
||||
|
||||
void print(unsigned int value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(long value, int base) { printSigned(value, base); }
|
||||
|
||||
void print(unsigned long value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(long long value, int base) { printSigned(value, base); }
|
||||
|
||||
void print(unsigned long long value, int base) { printUnsigned(value, base); }
|
||||
|
||||
void print(double value) { std::printf("%f", value); }
|
||||
|
||||
void println(const char value[]) {
|
||||
print(value);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(char value) {
|
||||
print(value);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned char value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(int value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned int value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(long long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(unsigned long long value, int base) {
|
||||
print(value, base);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(double value) {
|
||||
print(value);
|
||||
std::printf("\n");
|
||||
}
|
||||
|
||||
void println(void) { std::printf("\n"); }
|
||||
@@ -0,0 +1,273 @@
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "lwip/inet.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
constexpr const char* kTag = "openknx_idf";
|
||||
constexpr const char* kEepromKey = "eeprom";
|
||||
|
||||
esp_netif_t* findDefaultNetif() {
|
||||
if (auto* sta = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF")) {
|
||||
return sta;
|
||||
}
|
||||
if (auto* eth = esp_netif_get_handle_from_ifkey("ETH_DEF")) {
|
||||
return eth;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ensureNvsReady() {
|
||||
const esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
if (nvs_flash_erase() != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
return nvs_flash_init() == ESP_OK;
|
||||
}
|
||||
return err == ESP_OK || err == ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface,
|
||||
const char* nvs_namespace)
|
||||
: nvs_namespace_(nvs_namespace == nullptr ? "openknx" : nvs_namespace) {
|
||||
this->interface(interface);
|
||||
}
|
||||
|
||||
EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); }
|
||||
|
||||
void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
|
||||
|
||||
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
|
||||
|
||||
esp_netif_t* EspIdfPlatform::effectiveNetif() const {
|
||||
return netif_ == nullptr ? findDefaultNetif() : netif_;
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::currentIpAddress() {
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
esp_netif_t* netif = effectiveNetif();
|
||||
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
return ip_info.ip.addr;
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::currentSubnetMask() {
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
esp_netif_t* netif = effectiveNetif();
|
||||
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
return ip_info.netmask.addr;
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::currentDefaultGateway() {
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
esp_netif_t* netif = effectiveNetif();
|
||||
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
return ip_info.gw.addr;
|
||||
}
|
||||
|
||||
void EspIdfPlatform::macAddress(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (esp_read_mac(data, ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||
std::memset(data, 0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t EspIdfPlatform::uniqueSerialNumber() {
|
||||
uint8_t mac[6]{};
|
||||
macAddress(mac);
|
||||
return (static_cast<uint32_t>(mac[0]) << 24) | (static_cast<uint32_t>(mac[1]) << 16) |
|
||||
(static_cast<uint32_t>(mac[4]) << 8) | mac[5];
|
||||
}
|
||||
|
||||
void EspIdfPlatform::restart() { esp_restart(); }
|
||||
|
||||
void EspIdfPlatform::fatalError() {
|
||||
ESP_LOGE(kTag, "OpenKNX fatal error");
|
||||
while (true) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||
closeMultiCast();
|
||||
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (udp_sock_ < 0) {
|
||||
ESP_LOGE(kTag, "failed to create UDP socket: errno=%d", errno);
|
||||
return;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
setsockopt(udp_sock_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
|
||||
sockaddr_in bind_addr{};
|
||||
bind_addr.sin_family = AF_INET;
|
||||
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
bind_addr.sin_port = htons(port);
|
||||
if (bind(udp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
||||
ESP_LOGE(kTag, "failed to bind UDP socket: errno=%d", errno);
|
||||
closeMultiCast();
|
||||
return;
|
||||
}
|
||||
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 1000;
|
||||
setsockopt(udp_sock_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
|
||||
ip_mreq mreq{};
|
||||
mreq.imr_multiaddr.s_addr = htonl(addr);
|
||||
mreq.imr_interface.s_addr = currentIpAddress();
|
||||
if (setsockopt(udp_sock_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
||||
ESP_LOGW(kTag, "failed to join KNX multicast group: errno=%d", errno);
|
||||
}
|
||||
|
||||
uint8_t loop = 0;
|
||||
setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
|
||||
|
||||
multicast_remote_ = {};
|
||||
multicast_remote_.sin_family = AF_INET;
|
||||
multicast_remote_.sin_addr.s_addr = htonl(addr);
|
||||
multicast_remote_.sin_port = htons(port);
|
||||
}
|
||||
|
||||
void EspIdfPlatform::closeMultiCast() {
|
||||
if (udp_sock_ >= 0) {
|
||||
shutdown(udp_sock_, SHUT_RDWR);
|
||||
close(udp_sock_);
|
||||
udp_sock_ = -1;
|
||||
}
|
||||
has_last_remote_ = false;
|
||||
}
|
||||
|
||||
bool EspIdfPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) {
|
||||
if (udp_sock_ < 0 || buffer == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
const int sent = sendto(udp_sock_, buffer, len, 0, reinterpret_cast<sockaddr*>(&multicast_remote_),
|
||||
sizeof(multicast_remote_));
|
||||
return sent == len;
|
||||
}
|
||||
|
||||
int EspIdfPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) {
|
||||
uint32_t src_addr = 0;
|
||||
uint16_t src_port = 0;
|
||||
return readBytesMultiCast(buffer, maxLen, src_addr, src_port);
|
||||
}
|
||||
|
||||
int EspIdfPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr,
|
||||
uint16_t& src_port) {
|
||||
if (udp_sock_ < 0 || buffer == nullptr || maxLen == 0) {
|
||||
return 0;
|
||||
}
|
||||
sockaddr_in remote{};
|
||||
socklen_t remote_len = sizeof(remote);
|
||||
const int len = recvfrom(udp_sock_, buffer, maxLen, 0, reinterpret_cast<sockaddr*>(&remote),
|
||||
&remote_len);
|
||||
if (len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
last_remote_ = remote;
|
||||
has_last_remote_ = true;
|
||||
src_addr = ntohl(remote.sin_addr.s_addr);
|
||||
src_port = ntohs(remote.sin_port);
|
||||
return len;
|
||||
}
|
||||
|
||||
bool EspIdfPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer,
|
||||
uint16_t len) {
|
||||
if (udp_sock_ < 0 || buffer == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
sockaddr_in remote{};
|
||||
if (addr == 0 && port == 0 && has_last_remote_) {
|
||||
remote = last_remote_;
|
||||
} else {
|
||||
remote.sin_family = AF_INET;
|
||||
remote.sin_addr.s_addr = htonl(addr);
|
||||
remote.sin_port = htons(port);
|
||||
}
|
||||
const int sent = sendto(udp_sock_, buffer, len, 0, reinterpret_cast<sockaddr*>(&remote),
|
||||
sizeof(remote));
|
||||
return sent == len;
|
||||
}
|
||||
|
||||
void EspIdfPlatform::loadEeprom(size_t size) {
|
||||
if (eeprom_loaded_ && eeprom_.size() == size) {
|
||||
return;
|
||||
}
|
||||
eeprom_.assign(size, 0xff);
|
||||
eeprom_loaded_ = true;
|
||||
|
||||
if (!ensureNvsReady()) {
|
||||
ESP_LOGW(kTag, "NVS is not ready for OpenKNX EEPROM load");
|
||||
return;
|
||||
}
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
if (nvs_open(nvs_namespace_.c_str(), NVS_READONLY, &handle) != ESP_OK) {
|
||||
return;
|
||||
}
|
||||
size_t stored_size = 0;
|
||||
if (nvs_get_blob(handle, kEepromKey, nullptr, &stored_size) == ESP_OK && stored_size > 0) {
|
||||
std::vector<uint8_t> stored(stored_size);
|
||||
if (nvs_get_blob(handle, kEepromKey, stored.data(), &stored_size) == ESP_OK) {
|
||||
std::memcpy(eeprom_.data(), stored.data(), std::min(eeprom_.size(), stored.size()));
|
||||
}
|
||||
}
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
uint8_t* EspIdfPlatform::getEepromBuffer(uint32_t size) {
|
||||
loadEeprom(size);
|
||||
return eeprom_.data();
|
||||
}
|
||||
|
||||
void EspIdfPlatform::commitToEeprom() {
|
||||
if (eeprom_.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!ensureNvsReady()) {
|
||||
ESP_LOGW(kTag, "NVS is not ready for OpenKNX EEPROM commit");
|
||||
return;
|
||||
}
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t err = nvs_open(nvs_namespace_.c_str(), NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to open OpenKNX NVS namespace: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
err = nvs_set_blob(handle, kEepromKey, eeprom_.data(), eeprom_.size());
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(handle);
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to commit OpenKNX EEPROM: %s", esp_err_to_name(err));
|
||||
}
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "openknx_idf/ets_device_runtime.h"
|
||||
|
||||
#include "knx/cemi_server.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
thread_local EtsDeviceRuntime* active_function_property_runtime = nullptr;
|
||||
|
||||
class ActiveFunctionPropertyRuntimeScope {
|
||||
public:
|
||||
explicit ActiveFunctionPropertyRuntimeScope(EtsDeviceRuntime* runtime)
|
||||
: previous_(active_function_property_runtime) {
|
||||
active_function_property_runtime = runtime;
|
||||
}
|
||||
|
||||
~ActiveFunctionPropertyRuntimeScope() { active_function_property_runtime = previous_; }
|
||||
|
||||
private:
|
||||
EtsDeviceRuntime* previous_;
|
||||
};
|
||||
|
||||
constexpr uint16_t kInvalidIndividualAddress = 0xffff;
|
||||
constexpr uint16_t kReg1DaliManufacturerId = 0x00a4;
|
||||
constexpr uint8_t kReg1DaliApplicationNumber = 0x01;
|
||||
constexpr uint8_t kReg1DaliApplicationVersion = 0x05;
|
||||
constexpr uint8_t kReg1DaliOrderNumber[10] = {'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
|
||||
|
||||
bool IsUsableIndividualAddress(uint16_t address) {
|
||||
return address != 0 && address != kInvalidIndividualAddress;
|
||||
}
|
||||
|
||||
void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
|
||||
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||
device.deviceObject().orderNumber(kReg1DaliOrderNumber);
|
||||
const uint8_t program_version[5] = {0x00, 0xa4, 0x00, kReg1DaliApplicationNumber,
|
||||
kReg1DaliApplicationVersion};
|
||||
device.parameters().property(PID_PROG_VERSION)->write(program_version);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
uint16_t fallback_individual_address)
|
||||
: nvs_namespace_(std::move(nvs_namespace)),
|
||||
platform_(nullptr, nvs_namespace_.c_str()),
|
||||
device_(platform_) {
|
||||
ApplyReg1DaliIdentity(device_, platform_);
|
||||
if (IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
device_.readMemory();
|
||||
if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
|
||||
IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
if (auto* server = device_.getCemiServer()) {
|
||||
server->clientAddress(DefaultTunnelClientAddress(device_.deviceObject().individualAddress()));
|
||||
server->tunnelFrameCallback(&EtsDeviceRuntime::EmitTunnelFrame, this);
|
||||
}
|
||||
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
|
||||
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
|
||||
}
|
||||
|
||||
EtsDeviceRuntime::~EtsDeviceRuntime() {
|
||||
device_.functionPropertyCallback(nullptr);
|
||||
device_.functionPropertyStateCallback(nullptr);
|
||||
if (auto* server = device_.getCemiServer()) {
|
||||
server->tunnelFrameCallback(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::individualAddress() const {
|
||||
return const_cast<Bau07B0&>(device_).deviceObject().individualAddress();
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::tunnelClientAddress() const {
|
||||
if (auto* server = const_cast<Bau07B0&>(device_).getCemiServer()) {
|
||||
return server->clientAddress();
|
||||
}
|
||||
return DefaultTunnelClientAddress(individualAddress());
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::configured() const { return const_cast<Bau07B0&>(device_).configured(); }
|
||||
|
||||
EtsMemorySnapshot EtsDeviceRuntime::snapshot() const {
|
||||
EtsMemorySnapshot out;
|
||||
auto& device = const_cast<Bau07B0&>(device_);
|
||||
out.configured = device.configured();
|
||||
out.individual_address = device.deviceObject().individualAddress();
|
||||
device.forEachEtsAssociation(
|
||||
[](uint16_t group_address, uint16_t group_object_number, void* context) {
|
||||
auto* associations = static_cast<std::vector<EtsAssociation>*>(context);
|
||||
if (associations != nullptr) {
|
||||
associations->push_back(EtsAssociation{group_address, group_object_number});
|
||||
}
|
||||
},
|
||||
&out.associations);
|
||||
std::sort(out.associations.begin(), out.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
if (lhs.group_address != rhs.group_address) {
|
||||
return lhs.group_address < rhs.group_address;
|
||||
}
|
||||
return lhs.group_object_number < rhs.group_object_number;
|
||||
});
|
||||
out.associations.erase(
|
||||
std::unique(out.associations.begin(), out.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
return lhs.group_address == rhs.group_address &&
|
||||
lhs.group_object_number == rhs.group_object_number;
|
||||
}),
|
||||
out.associations.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||
FunctionPropertyHandler state_handler) {
|
||||
command_handler_ = std::move(command_handler);
|
||||
state_handler_ = std::move(state_handler);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender) {
|
||||
auto* server = device_.getCemiServer();
|
||||
if (server == nullptr || data == nullptr || len < 2) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> frame_data(data, data + len);
|
||||
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
||||
const bool consumed = shouldConsumeTunnelFrame(frame);
|
||||
if (!consumed) {
|
||||
return false;
|
||||
}
|
||||
sender_ = std::move(sender);
|
||||
ActiveFunctionPropertyRuntimeScope callback_scope(this);
|
||||
server->frameReceived(frame);
|
||||
loop();
|
||||
sender_ = nullptr;
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::loop() { device_.loop(); }
|
||||
|
||||
void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||
if (self == nullptr || !self->sender_) {
|
||||
return;
|
||||
}
|
||||
self->sender_(frame.data(), frame.dataLength());
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data,
|
||||
uint8_t& result_length) {
|
||||
if (active_function_property_runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return DispatchFunctionProperty(&active_function_property_runtime->command_handler_, object_index,
|
||||
property_id, length, data, result_data, result_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::HandleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data,
|
||||
uint8_t& result_length) {
|
||||
if (active_function_property_runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return DispatchFunctionProperty(&active_function_property_runtime->state_handler_, object_index,
|
||||
property_id, length, data, result_data, result_length);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::DispatchFunctionProperty(FunctionPropertyHandler* handler,
|
||||
uint8_t object_index, uint8_t property_id,
|
||||
uint8_t length, uint8_t* data,
|
||||
uint8_t* result_data, uint8_t& result_length) {
|
||||
if (handler == nullptr || !*handler || result_data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> response;
|
||||
if (!(*handler)(object_index, property_id, data, length, &response)) {
|
||||
return false;
|
||||
}
|
||||
result_length = static_cast<uint8_t>(std::min<size_t>(response.size(), result_length));
|
||||
if (result_length > 0) {
|
||||
std::copy_n(response.begin(), result_length, result_data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t EtsDeviceRuntime::DefaultTunnelClientAddress(uint16_t individual_address) {
|
||||
if (!IsUsableIndividualAddress(individual_address)) {
|
||||
return 0x1101;
|
||||
}
|
||||
const uint16_t line_base = individual_address & 0xff00;
|
||||
uint16_t device = static_cast<uint16_t>((individual_address & 0x00ff) + 1);
|
||||
if (device == 0 || device > 0xff) {
|
||||
device = 1;
|
||||
}
|
||||
return static_cast<uint16_t>(line_base | device);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
switch (frame.messageCode()) {
|
||||
case M_PropRead_req:
|
||||
case M_PropWrite_req:
|
||||
case M_Reset_req:
|
||||
case M_FuncPropCommand_req:
|
||||
case M_FuncPropStateRead_req:
|
||||
return true;
|
||||
case L_data_req:
|
||||
return frame.addressType() == IndividualAddress &&
|
||||
frame.destinationAddress() == individualAddress();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,80 @@
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
|
||||
#include "openknx_idf/esp_idf_platform.h"
|
||||
|
||||
#include "knx/bau07B0.h"
|
||||
#include "knx/property.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
void CollectAssociation(uint16_t group_address, uint16_t group_object_number,
|
||||
void* context) {
|
||||
auto* associations = static_cast<std::vector<EtsAssociation>*>(context);
|
||||
if (associations == nullptr) {
|
||||
return;
|
||||
}
|
||||
associations->push_back(EtsAssociation{group_address, group_object_number});
|
||||
}
|
||||
|
||||
bool IsErasedMemory(const uint8_t* data, size_t size) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return std::all_of(data, data + size, [](uint8_t value) { return value == 0xff; });
|
||||
}
|
||||
|
||||
constexpr uint16_t kReg1DaliManufacturerId = 0x00a4;
|
||||
constexpr uint8_t kReg1DaliApplicationNumber = 0x01;
|
||||
constexpr uint8_t kReg1DaliApplicationVersion = 0x05;
|
||||
constexpr uint8_t kReg1DaliOrderNumber[10] = {'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
|
||||
|
||||
void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
|
||||
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||
device.deviceObject().orderNumber(kReg1DaliOrderNumber);
|
||||
const uint8_t program_version[5] = {0x00, 0xa4, 0x00, kReg1DaliApplicationNumber,
|
||||
kReg1DaliApplicationVersion};
|
||||
device.parameters().property(PID_PROG_VERSION)->write(program_version);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace) {
|
||||
EspIdfPlatform platform(nullptr, nvs_namespace.c_str());
|
||||
EtsMemorySnapshot snapshot;
|
||||
const uint8_t* memory = platform.getNonVolatileMemoryStart();
|
||||
const size_t memory_size = platform.getNonVolatileMemorySize();
|
||||
if (memory == nullptr || memory_size == 0 || IsErasedMemory(memory, memory_size)) {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
auto device = std::make_unique<Bau07B0>(platform);
|
||||
ApplyReg1DaliIdentity(*device, platform);
|
||||
device->readMemory();
|
||||
|
||||
snapshot.configured = device->configured();
|
||||
snapshot.individual_address = device->deviceObject().individualAddress();
|
||||
device->forEachEtsAssociation(CollectAssociation, &snapshot.associations);
|
||||
std::sort(snapshot.associations.begin(), snapshot.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
if (lhs.group_address != rhs.group_address) {
|
||||
return lhs.group_address < rhs.group_address;
|
||||
}
|
||||
return lhs.group_object_number < rhs.group_object_number;
|
||||
});
|
||||
snapshot.associations.erase(
|
||||
std::unique(snapshot.associations.begin(), snapshot.associations.end(),
|
||||
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||
return lhs.group_address == rhs.group_address &&
|
||||
lhs.group_object_number == rhs.group_object_number;
|
||||
}),
|
||||
snapshot.associations.end());
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
@@ -0,0 +1,114 @@
|
||||
#include "openknx_idf/tpuart_uart_interface.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace gateway::openknx {
|
||||
namespace {
|
||||
|
||||
constexpr const char* kTag = "openknx_tpuart";
|
||||
|
||||
} // namespace
|
||||
|
||||
TpuartUartInterface::TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
|
||||
size_t rx_buffer_size, size_t tx_buffer_size)
|
||||
: uart_port_(uart_port),
|
||||
tx_pin_(tx_pin),
|
||||
rx_pin_(rx_pin),
|
||||
rx_buffer_size_(rx_buffer_size),
|
||||
tx_buffer_size_(tx_buffer_size) {}
|
||||
|
||||
TpuartUartInterface::~TpuartUartInterface() { end(); }
|
||||
|
||||
void TpuartUartInterface::begin(int baud) {
|
||||
if (_running) {
|
||||
end();
|
||||
}
|
||||
|
||||
uart_config_t config{};
|
||||
config.baud_rate = baud;
|
||||
config.data_bits = UART_DATA_8_BITS;
|
||||
config.parity = UART_PARITY_EVEN;
|
||||
config.stop_bits = UART_STOP_BITS_1;
|
||||
config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
config.source_clk = UART_SCLK_DEFAULT;
|
||||
|
||||
esp_err_t err = uart_param_config(uart_port_, &config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure UART%d: %s", uart_port_, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_set_pin(uart_port_, tx_pin_ < 0 ? UART_PIN_NO_CHANGE : tx_pin_,
|
||||
rx_pin_ < 0 ? UART_PIN_NO_CHANGE : rx_pin_, UART_PIN_NO_CHANGE,
|
||||
UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to route UART%d pins: %s", uart_port_, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = uart_driver_install(uart_port_, rx_buffer_size_, tx_buffer_size_, 0, nullptr, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to install UART%d driver: %s", uart_port_, esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
uart_set_rx_full_threshold(uart_port_, 1);
|
||||
_running = true;
|
||||
}
|
||||
|
||||
void TpuartUartInterface::end() {
|
||||
if (!_running) {
|
||||
return;
|
||||
}
|
||||
_running = false;
|
||||
uart_driver_delete(uart_port_);
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::available() {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
size_t len = 0;
|
||||
return uart_get_buffered_data_len(uart_port_, &len) == ESP_OK && len > 0;
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::availableForWrite() {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
size_t len = 0;
|
||||
return uart_get_tx_buffer_free_size(uart_port_, &len) == ESP_OK && len > 0;
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::write(char value) {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
return uart_write_bytes(uart_port_, &value, 1) == 1;
|
||||
}
|
||||
|
||||
int TpuartUartInterface::read() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
uint8_t value = 0;
|
||||
return uart_read_bytes(uart_port_, &value, 1, 0) == 1 ? value : -1;
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::overflow() { return overflow_.exchange(false); }
|
||||
|
||||
void TpuartUartInterface::flush() {
|
||||
if (_running) {
|
||||
uart_flush(uart_port_);
|
||||
}
|
||||
}
|
||||
|
||||
bool TpuartUartInterface::hasCallback() { return false; }
|
||||
|
||||
void TpuartUartInterface::registerCallback(std::function<bool()> callback) {
|
||||
callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
} // namespace gateway::openknx
|
||||
+1
-1
Submodule knx updated: 7124a6435d...339d8472e7
Submodule
+1
Submodule knx_dali_gw added at 6064d84520
Submodule
+1
Submodule tpuart added at f8c01e6a32
Reference in New Issue
Block a user