diff --git a/README.md b/README.md index 49ee906..a8ecd38 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,12 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway. - `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_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, and a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, Wi-Fi STA lifecycle, ESP-Touch smartconfig credential provisioning, the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`, ESP-NOW setup ingress for Lua-compatible `connReq`/`connAck`/`echo`/`cmd`/`data`/`uart` packets, native raw DALI frame forwarding back to connected setup peers, and BOOT-button Wi-Fi credential clearing. Startup behavior is configured in `main/Kconfig.projbuild`: BLE is enabled by default, Wi-Fi STA, smartconfig, and ESP-NOW setup mode are disabled by default, and the built-in USB Serial/JTAG interface stays in debug mode unless the optional USB setup bridge mode is selected. Runtime settings and internal scene/group data are cached in RAM after load, skip unchanged flash writes, and batch Wi-Fi credential commits to reduce flash stalls on ESP32-S3 boards where flash and PSRAM share the SPI bus. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY. The checked-in `sdkconfig` is aligned with the app's custom 16 MB partition table so the Wi-Fi/BLE/network-enabled image fits the OTA app slots. +The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port with raw receive fan-out, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app also includes a `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications, incoming `FFF1`/`FFF2`/`FFF3` writes, and native raw DALI frame notifications into the matching raw channel, a `gateway_network` service that provides the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, 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. ## Modbus diff --git a/apps/gateway/main/CMakeLists.txt b/apps/gateway/main/CMakeLists.txt index 01b22d4..be19177 100644 --- a/apps/gateway/main/CMakeLists.txt +++ b/apps/gateway/main/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( SRCS "app_main.cpp" - REQUIRES gateway_core gateway_controller gateway_network gateway_bridge gateway_cache dali_domain gateway_runtime gateway_ble gateway_usb_setup log + REQUIRES gateway_core gateway_controller gateway_network gateway_bridge gateway_cache dali_domain gateway_runtime gateway_ble gateway_usb_setup gateway_485_control log ) set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) \ No newline at end of file diff --git a/apps/gateway/main/Kconfig.projbuild b/apps/gateway/main/Kconfig.projbuild index 17a5a14..9757916 100644 --- a/apps/gateway/main/Kconfig.projbuild +++ b/apps/gateway/main/Kconfig.projbuild @@ -579,6 +579,72 @@ config GATEWAY_USB_SETUP_READ_TIMEOUT_MS range 1 1000 default 20 +config GATEWAY_485_CONTROL_ENABLED + bool "Enable UART0 Lua control bridge" + default n + help + Claims UART0 for the Lua-compatible framed gateway control channel at boot. + This requires moving the ESP-IDF console away from UART0 and prevents Modbus + serial from using UART0 at runtime. + +config GATEWAY_485_CONTROL_BAUDRATE + int "UART0 control baudrate" + depends on GATEWAY_485_CONTROL_ENABLED + range 1200 921600 + default 9600 + +config GATEWAY_485_CONTROL_TX_PIN + int "UART0 control TX pin" + depends on GATEWAY_485_CONTROL_ENABLED + range -1 48 + default -1 + help + Leave at -1 to keep the current UART0 TX routing. + +config GATEWAY_485_CONTROL_RX_PIN + int "UART0 control RX pin" + depends on GATEWAY_485_CONTROL_ENABLED + range -1 48 + default -1 + help + Leave at -1 to keep the current UART0 RX routing. + +config GATEWAY_485_CONTROL_RX_BUFFER + int "UART0 control RX buffer bytes" + depends on GATEWAY_485_CONTROL_ENABLED + range 64 4096 + default 256 + +config GATEWAY_485_CONTROL_TX_BUFFER + int "UART0 control TX buffer bytes" + depends on GATEWAY_485_CONTROL_ENABLED + range 64 4096 + default 256 + +config GATEWAY_485_CONTROL_READ_TIMEOUT_MS + int "UART0 control read timeout ms" + depends on GATEWAY_485_CONTROL_ENABLED + range 1 1000 + default 20 + +config GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS + int "UART0 control write timeout ms" + depends on GATEWAY_485_CONTROL_ENABLED + range 1 1000 + default 20 + +config GATEWAY_485_CONTROL_TASK_STACK_SIZE + int "UART0 control task stack bytes" + depends on GATEWAY_485_CONTROL_ENABLED + range 2048 16384 + default 4096 + +config GATEWAY_485_CONTROL_TASK_PRIORITY + int "UART0 control task priority" + depends on GATEWAY_485_CONTROL_ENABLED + range 1 10 + default 4 + endmenu menu "Gateway Network Services" diff --git a/apps/gateway/main/app_main.cpp b/apps/gateway/main/app_main.cpp index 258581f..d0ed13e 100644 --- a/apps/gateway/main/app_main.cpp +++ b/apps/gateway/main/app_main.cpp @@ -6,6 +6,7 @@ #include "gateway_core.hpp" #include "gateway_network.hpp" #include "gateway_runtime.hpp" +#include "gateway_485_control.hpp" #include "gateway_usb_setup.hpp" #include "esp_log.h" @@ -55,6 +56,42 @@ #define CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS 20 #endif +#ifndef CONFIG_GATEWAY_485_CONTROL_BAUDRATE +#define CONFIG_GATEWAY_485_CONTROL_BAUDRATE 9600 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_TX_PIN +#define CONFIG_GATEWAY_485_CONTROL_TX_PIN -1 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_RX_PIN +#define CONFIG_GATEWAY_485_CONTROL_RX_PIN -1 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_RX_BUFFER +#define CONFIG_GATEWAY_485_CONTROL_RX_BUFFER 256 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_TX_BUFFER +#define CONFIG_GATEWAY_485_CONTROL_TX_BUFFER 256 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS +#define CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS 20 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS +#define CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS 20 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE +#define CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE 4096 +#endif + +#ifndef CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY +#define CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY 4 +#endif + #ifndef CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC #define CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC 60 #endif @@ -257,6 +294,20 @@ constexpr bool kModbusAllowUart0 = true; constexpr bool kModbusAllowUart0 = false; #endif +#ifdef CONFIG_GATEWAY_485_CONTROL_ENABLED +constexpr bool k485ControlEnabled = true; +#else +constexpr bool k485ControlEnabled = false; +#endif + +#if defined(CONFIG_ESP_CONSOLE_UART) && defined(CONFIG_ESP_CONSOLE_UART_NUM) && CONFIG_ESP_CONSOLE_UART_NUM == 0 +constexpr bool kConsoleOnUart0 = true; +#elif defined(CONFIG_CONSOLE_UART) && defined(CONFIG_CONSOLE_UART_NUM) && CONFIG_CONSOLE_UART_NUM == 0 +constexpr bool kConsoleOnUart0 = true; +#else +constexpr bool kConsoleOnUart0 = false; +#endif + #ifdef CONFIG_GATEWAY_MODBUS_SERIAL_RS485_ENABLED constexpr bool kModbusSerialRs485Enabled = true; #else @@ -270,6 +321,7 @@ std::unique_ptr s_controller; std::unique_ptr s_bridge; std::unique_ptr s_network; std::unique_ptr s_ble_bridge; +std::unique_ptr s_uart0_control_bridge; std::unique_ptr s_usb_setup_bridge; [[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) { @@ -289,6 +341,11 @@ struct ChannelBindingConfig { }; bool ValidateChannelBindings() { + if (k485ControlEnabled && kConsoleOnUart0) { + ESP_LOGE(kTag, "485 control bridge requires moving the ESP-IDF console off UART0"); + return false; + } + ChannelBindingConfig channels[CONFIG_GATEWAY_CHANNEL_COUNT] = {}; #if CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE @@ -375,6 +432,10 @@ bool ValidateChannelBindings() { if (kModbusBridgeSupported && kModbusDefaultSerialTransport) { const int modbus_uart = CONFIG_GATEWAY_MODBUS_SERIAL_UART_PORT; + if (k485ControlEnabled && modbus_uart == 0) { + ESP_LOGE(kTag, "Modbus serial UART0 conflicts with the UART0 control bridge"); + return false; + } if (modbus_uart == 0 && !kModbusAllowUart0) { ESP_LOGE(kTag, "Modbus serial is configured on UART0, but UART0 is reserved for console"); return false; @@ -429,10 +490,10 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain, channel1.query_timeout_ms = static_cast(CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS); channel1.name = runtime.gatewayName(channel1.gateway_id); - esp_err_t err = dali_domain.bindSerialBus(channel1); - LogBindError("channel1 serial DALI", err); - if (err != ESP_OK) { - return err; + esp_err_t err1 = dali_domain.bindSerialBus(channel1); + LogBindError("channel1 serial DALI", err1); + if (err1 != ESP_OK) { + return err1; } #endif @@ -446,10 +507,10 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain, channel2.rx_pin = static_cast(CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN); channel2.baudrate = static_cast(CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE); channel2.name = runtime.gatewayName(channel2.gateway_id); - esp_err_t err = dali_domain.bindHardwareBus(channel2); - LogBindError("channel2 native DALI", err); - if (err != ESP_OK) { - return err; + esp_err_t err2 = dali_domain.bindHardwareBus(channel2); + LogBindError("channel2 native DALI", err2); + if (err2 != ESP_OK) { + return err2; } #elif CONFIG_GATEWAY_CHANNEL2_PHY_UART1 || CONFIG_GATEWAY_CHANNEL2_PHY_UART2 gateway::DaliSerialBusConfig channel2{}; @@ -468,10 +529,10 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain, channel2.query_timeout_ms = static_cast(CONFIG_GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS); channel2.name = runtime.gatewayName(channel2.gateway_id); - esp_err_t err = dali_domain.bindSerialBus(channel2); - LogBindError("channel2 serial DALI", err); - if (err != ESP_OK) { - return err; + esp_err_t err2 = dali_domain.bindSerialBus(channel2); + LogBindError("channel2 serial DALI", err2); + if (err2 != ESP_OK) { + return err2; } #endif #endif @@ -535,7 +596,27 @@ extern "C" void app_main(void) { s_controller = std::make_unique(*s_runtime, *s_dali_domain, *s_cache, controller_config); - ESP_ERROR_CHECK(s_controller->start()); + if (k485ControlEnabled) { + gateway::Gateway485ControlBridgeConfig gateway485_config; + gateway485_config.enabled = true; + gateway485_config.tx_pin = CONFIG_GATEWAY_485_CONTROL_TX_PIN; + gateway485_config.rx_pin = CONFIG_GATEWAY_485_CONTROL_RX_PIN; + gateway485_config.baudrate = static_cast(CONFIG_GATEWAY_485_CONTROL_BAUDRATE); + gateway485_config.rx_buffer_size = static_cast(CONFIG_GATEWAY_485_CONTROL_RX_BUFFER); + gateway485_config.tx_buffer_size = static_cast(CONFIG_GATEWAY_485_CONTROL_TX_BUFFER); + gateway485_config.read_timeout_ms = + static_cast(CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS); + gateway485_config.write_timeout_ms = + static_cast(CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS); + gateway485_config.task_stack_size = + static_cast(CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE); + gateway485_config.task_priority = + static_cast(CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY); + s_uart0_control_bridge = std::make_unique(*s_controller, + gateway485_config); + ESP_ERROR_CHECK(s_uart0_control_bridge->start()); + } + ESP_ERROR_CHECK(s_controller->start()); if (kBridgeSupported) { gateway::GatewayBridgeServiceConfig bridge_config; @@ -552,8 +633,8 @@ extern "C" void app_main(void) { static_cast(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE); bridge_config.modbus_task_priority = static_cast(CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY); - bridge_config.allow_modbus_uart0 = kModbusAllowUart0; - if (!kModbusAllowUart0) { + bridge_config.allow_modbus_uart0 = kModbusAllowUart0 && !k485ControlEnabled; + if (!bridge_config.allow_modbus_uart0) { bridge_config.reserved_uart_ports.push_back(0); } #if CONFIG_GATEWAY_CHANNEL1_PHY_UART1 diff --git a/apps/gateway/partitions-4M-single.csv b/apps/gateway/partitions-4M-single.csv new file mode 100644 index 0000000..437498a --- /dev/null +++ b/apps/gateway/partitions-4M-single.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +otadata, data, ota, 0xf000, 0x2000, +phy_init, data, phy, 0x11000, 0x1000, +factory, app, factory, 0x20000, 0x200000, +storage, data, spiffs, 0x220000, 0x180000, diff --git a/apps/gateway/sdkconfig b/apps/gateway/sdkconfig index 64f12ca..c804336 100644 --- a/apps/gateway/sdkconfig +++ b/apps/gateway/sdkconfig @@ -562,13 +562,13 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_FLASHFREQ="80m" # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y # CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="16MB" +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set CONFIG_ESPTOOLPY_BEFORE_RESET=y # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set @@ -587,8 +587,8 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4M-single.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-4M-single.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -602,20 +602,32 @@ CONFIG_GATEWAY_CHANNEL_COUNT=2 # Gateway Channel 1 # CONFIG_GATEWAY_CHANNEL1_GW_ID=3 -CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED=y +# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set # CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE is not set -# CONFIG_GATEWAY_CHANNEL1_PHY_UART1 is not set +CONFIG_GATEWAY_CHANNEL1_PHY_UART1=y # CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set +CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_PIN=1 +CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_PIN=2 +CONFIG_GATEWAY_CHANNEL1_SERIAL_BAUDRATE=9600 +CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS=100 # end of Gateway Channel 1 # # Gateway Channel 2 # CONFIG_GATEWAY_CHANNEL2_GW_ID=4 -CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED=y +# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set # CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE is not set # CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set -# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set +CONFIG_GATEWAY_CHANNEL2_PHY_UART2=y +CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_PIN=6 +CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_PIN=7 +CONFIG_GATEWAY_CHANNEL2_SERIAL_BAUDRATE=9600 +CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS=100 # end of Gateway Channel 2 # @@ -662,6 +674,16 @@ 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 # end of Gateway Startup Services # @@ -722,6 +744,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 @@ -748,6 +771,7 @@ CONFIG_BT_CONTROLLER_ENABLED=y # General # CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set # CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y @@ -1500,8 +1524,8 @@ CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4 # # Sleep Config # -# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y @@ -1640,7 +1664,40 @@ CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y # # ESP PSRAM # -# CONFIG_SPIRAM is not set +CONFIG_SPIRAM=y + +# +# SPI RAM config +# +CONFIG_SPIRAM_MODE_QUAD=y +# CONFIG_SPIRAM_MODE_OCT is not set +CONFIG_SPIRAM_TYPE_AUTO=y +# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set +CONFIG_SPIRAM_CLK_IO=30 +CONFIG_SPIRAM_CS_IO=26 +# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set +# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set +# CONFIG_SPIRAM_RODATA is not set +# CONFIG_SPIRAM_SPEED_120M is not set +CONFIG_SPIRAM_SPEED_80M=y +# CONFIG_SPIRAM_SPEED_40M is not set +CONFIG_SPIRAM_SPEED=80 +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set +# end of SPI RAM config # end of ESP PSRAM # @@ -1731,18 +1788,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 @@ -1895,6 +1949,7 @@ CONFIG_FATFS_CODEPAGE=437 CONFIG_FATFS_FS_LOCK=0 CONFIG_FATFS_TIMEOUT_MS=10000 CONFIG_FATFS_PER_FILE_CACHE=y +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y # CONFIG_FATFS_USE_FASTSEEK is not set CONFIG_FATFS_USE_STRFUNC_NONE=y # CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set @@ -1972,6 +2027,7 @@ CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y # # Extra # +CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y # end of Extra CONFIG_FREERTOS_PORT=y @@ -2256,6 +2312,7 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y # mbedTLS # CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set # CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set # CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y @@ -2412,12 +2469,15 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y CONFIG_LIBC_ASSERT_BUFFER_SIZE=200 # end of LibC +CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y + # # NVS # # CONFIG_NVS_ENCRYPTION is not set # CONFIG_NVS_ASSERT_ERROR_CHECK is not set # CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set # end of NVS # @@ -2761,6 +2821,7 @@ CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y # CONFIG_BLUEDROID_ENABLED is not set CONFIG_NIMBLE_ENABLED=y CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set # CONFIG_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set CONFIG_NIMBLE_PINNED_TO_CORE=0 CONFIG_NIMBLE_PINNED_TO_CORE_0=y @@ -2809,7 +2870,6 @@ CONFIG_POST_EVENTS_FROM_IRAM_ISR=y CONFIG_GDBSTUB_SUPPORT_TASKS=y CONFIG_GDBSTUB_MAX_TASKS=32 # CONFIG_OTA_ALLOW_HTTP is not set -# CONFIG_ESP_SYSTEM_PD_FLASH is not set CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y @@ -2845,7 +2905,9 @@ CONFIG_ESP32_PHY_MAX_TX_POWER=20 # CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y -# CONFIG_ESP32S3_SPIRAM_SUPPORT is not set +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 # CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y # CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set @@ -2853,13 +2915,11 @@ CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160 CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 CONFIG_MAIN_TASK_STACK_SIZE=3584 -CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_DEFAULT is not set # CONFIG_CONSOLE_UART_CUSTOM is not set # CONFIG_CONSOLE_UART_NONE is not set # CONFIG_ESP_CONSOLE_UART_NONE is not set -CONFIG_CONSOLE_UART=y -CONFIG_CONSOLE_UART_NUM=0 -CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_CONSOLE_UART_NUM=-1 CONFIG_INT_WDT=y CONFIG_INT_WDT_TIMEOUT_MS=300 CONFIG_INT_WDT_CHECK_CPU1=y @@ -2912,6 +2972,7 @@ CONFIG_TIMER_TASK_PRIORITY=1 CONFIG_TIMER_TASK_STACK_DEPTH=2048 CONFIG_TIMER_QUEUE_LENGTH=10 # CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y # CONFIG_HAL_ASSERTION_SILIENT is not set # CONFIG_L2_TO_L3_COPY is not set CONFIG_ESP_GRATUITOUS_ARP=y diff --git a/apps/gateway/sdkconfig.old b/apps/gateway/sdkconfig.old index 83d59d4..8dd48a0 100644 --- a/apps/gateway/sdkconfig.old +++ b/apps/gateway/sdkconfig.old @@ -562,13 +562,13 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESPTOOLPY_FLASHFREQ="80m" # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y # CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="16MB" +CONFIG_ESPTOOLPY_FLASHSIZE="4MB" # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set CONFIG_ESPTOOLPY_BEFORE_RESET=y # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set @@ -587,8 +587,8 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # CONFIG_PARTITION_TABLE_TWO_OTA is not set # CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4M-single.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-4M-single.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -596,7 +596,108 @@ CONFIG_PARTITION_TABLE_MD5=y # # Gateway App # +CONFIG_GATEWAY_CHANNEL_COUNT=2 + +# +# Gateway Channel 1 +# +CONFIG_GATEWAY_CHANNEL1_GW_ID=3 +# CONFIG_GATEWAY_CHANNEL1_PHY_DISABLED is not set +# CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE is not set +CONFIG_GATEWAY_CHANNEL1_PHY_UART1=y +# CONFIG_GATEWAY_CHANNEL1_PHY_UART2 is not set +CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_PIN=1 +CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_PIN=2 +CONFIG_GATEWAY_CHANNEL1_SERIAL_BAUDRATE=9600 +CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS=100 +# end of Gateway Channel 1 + +# +# Gateway Channel 2 +# +CONFIG_GATEWAY_CHANNEL2_GW_ID=4 +# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set +# CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE is not set +# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set +CONFIG_GATEWAY_CHANNEL2_PHY_UART2=y +CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_PIN=6 +CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_PIN=7 +CONFIG_GATEWAY_CHANNEL2_SERIAL_BAUDRATE=9600 +CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_BUFFER=512 +CONFIG_GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS=100 +# end of Gateway Channel 2 + +# +# Gateway Cache +# +CONFIG_GATEWAY_CACHE_SUPPORTED=y +CONFIG_GATEWAY_CACHE_START_ENABLED=y +CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y +# CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set +CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=5000 +CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y +# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set +# end of Gateway Cache + # CONFIG_GATEWAY_ENABLE_DALI_BUS is not set + +# +# Gateway Startup Services +# +CONFIG_GATEWAY_BLE_SUPPORTED=y +CONFIG_GATEWAY_START_BLE_ENABLED=y +CONFIG_GATEWAY_WIFI_SUPPORTED=y +# CONFIG_GATEWAY_START_WIFI_STA_ENABLED is not set +CONFIG_GATEWAY_ESPNOW_SETUP_SUPPORTED=y +CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y +# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set +# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set +CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60 +CONFIG_GATEWAY_BRIDGE_SUPPORTED=y +CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y +# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set +CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_TCP=y +# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU is not set +# CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII is not set +CONFIG_GATEWAY_MODBUS_TCP_PORT=1502 +CONFIG_GATEWAY_MODBUS_UNIT_ID=1 +CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y +# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set +CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y +# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set +CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 +CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4 +CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192 +CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5 +CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y +# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set +CONFIG_GATEWAY_485_CONTROL_ENABLED=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 +# end of Gateway Startup Services + +# +# Gateway Network Services +# +CONFIG_GATEWAY_NETWORK_HTTP_ENABLED=y +CONFIG_GATEWAY_NETWORK_HTTP_PORT=80 +CONFIG_GATEWAY_NETWORK_UDP_ROUTER_ENABLED=y +CONFIG_GATEWAY_NETWORK_UDP_PORT=2020 +CONFIG_GATEWAY_STATUS_LED_GPIO=-1 +CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0 +CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y +CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS=3000 +# end of Gateway Network Services # end of Gateway App # @@ -669,6 +770,7 @@ CONFIG_BT_CONTROLLER_ENABLED=y # General # CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_INTERNAL=y +# CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL is not set # CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_DEFAULT is not set CONFIG_BT_NIMBLE_PINNED_TO_CORE=0 CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=y @@ -1421,8 +1523,8 @@ CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4 # # Sleep Config # -# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y @@ -1561,7 +1663,40 @@ CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y # # ESP PSRAM # -# CONFIG_SPIRAM is not set +CONFIG_SPIRAM=y + +# +# SPI RAM config +# +CONFIG_SPIRAM_MODE_QUAD=y +# CONFIG_SPIRAM_MODE_OCT is not set +CONFIG_SPIRAM_TYPE_AUTO=y +# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set +# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set +CONFIG_SPIRAM_CLK_IO=30 +CONFIG_SPIRAM_CS_IO=26 +# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set +# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set +# CONFIG_SPIRAM_RODATA is not set +# CONFIG_SPIRAM_SPEED_120M is not set +CONFIG_SPIRAM_SPEED_80M=y +# CONFIG_SPIRAM_SPEED_40M is not set +CONFIG_SPIRAM_SPEED=80 +CONFIG_SPIRAM_BOOT_HW_INIT=y +CONFIG_SPIRAM_BOOT_INIT=y +CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y +# CONFIG_SPIRAM_USE_MEMMAP is not set +# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_MEMTEST=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 +# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set +# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set +# end of SPI RAM config # end of ESP PSRAM # @@ -1816,6 +1951,7 @@ CONFIG_FATFS_CODEPAGE=437 CONFIG_FATFS_FS_LOCK=0 CONFIG_FATFS_TIMEOUT_MS=10000 CONFIG_FATFS_PER_FILE_CACHE=y +CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y # CONFIG_FATFS_USE_FASTSEEK is not set CONFIG_FATFS_USE_STRFUNC_NONE=y # CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set @@ -1893,6 +2029,7 @@ CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y # # Extra # +CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y # end of Extra CONFIG_FREERTOS_PORT=y @@ -2177,6 +2314,7 @@ CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y # mbedTLS # CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set # CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set # CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y @@ -2333,12 +2471,15 @@ CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y CONFIG_LIBC_ASSERT_BUFFER_SIZE=200 # end of LibC +CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y + # # NVS # # CONFIG_NVS_ENCRYPTION is not set # CONFIG_NVS_ASSERT_ERROR_CHECK is not set # CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set # end of NVS # diff --git a/components/gateway_485_control/CMakeLists.txt b/components/gateway_485_control/CMakeLists.txt new file mode 100644 index 0000000..e26f701 --- /dev/null +++ b/components/gateway_485_control/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS "src/gateway_485_control.cpp" + INCLUDE_DIRS "include" + REQUIRES esp_driver_uart freertos gateway_controller log +) + +set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17) \ No newline at end of file diff --git a/components/gateway_485_control/include/gateway_485_control.hpp b/components/gateway_485_control/include/gateway_485_control.hpp new file mode 100644 index 0000000..35ced48 --- /dev/null +++ b/components/gateway_485_control/include/gateway_485_control.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +namespace gateway { + +class GatewayController; + +struct Gateway485ControlBridgeConfig { + bool enabled{false}; + int tx_pin{-1}; + int rx_pin{-1}; + uint32_t baudrate{9600}; + size_t rx_buffer_size{256}; + size_t tx_buffer_size{256}; + uint32_t read_timeout_ms{20}; + uint32_t write_timeout_ms{20}; + uint32_t task_stack_size{4096}; + UBaseType_t task_priority{4}; +}; + +class Gateway485ControlBridge { + public: + Gateway485ControlBridge(GatewayController& controller, + Gateway485ControlBridgeConfig config = {}); + + esp_err_t start(); + + private: + static void TaskEntry(void* arg); + void taskLoop(); + void handleBytes(const uint8_t* data, size_t len); + void handleGatewayNotification(const std::vector& frame); + + GatewayController& controller_; + Gateway485ControlBridgeConfig config_; + TaskHandle_t task_handle_{nullptr}; + bool started_{false}; +}; + +} // namespace gateway \ No newline at end of file diff --git a/components/gateway_485_control/src/gateway_485_control.cpp b/components/gateway_485_control/src/gateway_485_control.cpp new file mode 100644 index 0000000..8abe0c7 --- /dev/null +++ b/components/gateway_485_control/src/gateway_485_control.cpp @@ -0,0 +1,134 @@ +#include "gateway_485_control.hpp" + +#include "gateway_controller.hpp" + +#include "driver/uart.h" +#include "esp_log.h" + +#include + +namespace gateway { + +namespace { +constexpr const char* kTag = "gateway_485"; +constexpr uart_port_t kControlUart = UART_NUM_0; +constexpr size_t kCommandFrameMinLen = 7; + +int EffectivePin(int pin) { + return pin >= 0 ? pin : UART_PIN_NO_CHANGE; +} +} // namespace + +Gateway485ControlBridge::Gateway485ControlBridge(GatewayController& controller, + Gateway485ControlBridgeConfig config) + : controller_(controller), config_(config) {} + +esp_err_t Gateway485ControlBridge::start() { + if (started_) { + return ESP_OK; + } + if (!config_.enabled) { + ESP_LOGI(kTag, "UART0 control bridge disabled"); + return ESP_OK; + } + if (uart_is_driver_installed(kControlUart)) { + ESP_LOGE(kTag, "UART0 driver already installed; move console or other users off UART0"); + return ESP_ERR_INVALID_STATE; + } + + uart_config_t uart_config{}; + uart_config.baud_rate = static_cast(config_.baudrate); + uart_config.data_bits = UART_DATA_8_BITS; + uart_config.parity = UART_PARITY_DISABLE; + uart_config.stop_bits = UART_STOP_BITS_1; + uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; + uart_config.source_clk = UART_SCLK_DEFAULT; + + esp_err_t err = uart_param_config(kControlUart, &uart_config); + if (err != ESP_OK) { + ESP_LOGE(kTag, "failed to configure UART0: %s", esp_err_to_name(err)); + return err; + } + err = uart_set_pin(kControlUart, EffectivePin(config_.tx_pin), EffectivePin(config_.rx_pin), + UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + if (err != ESP_OK) { + ESP_LOGE(kTag, "failed to set UART0 pins: %s", esp_err_to_name(err)); + return err; + } + err = uart_driver_install(kControlUart, static_cast(config_.rx_buffer_size), + static_cast(config_.tx_buffer_size), 0, nullptr, 0); + if (err != ESP_OK) { + ESP_LOGE(kTag, "failed to install UART0 driver: %s", esp_err_to_name(err)); + return err; + } + + controller_.addNotificationSink( + [this](const std::vector& frame) { handleGatewayNotification(frame); }); + + const BaseType_t created = xTaskCreate(&Gateway485ControlBridge::TaskEntry, + "gateway_485_ctrl", + static_cast(config_.task_stack_size), this, + config_.task_priority, &task_handle_); + if (created != pdPASS) { + uart_driver_delete(kControlUart); + task_handle_ = nullptr; + ESP_LOGE(kTag, "failed to create 485 control task"); + return ESP_ERR_NO_MEM; + } + + started_ = true; + ESP_LOGI(kTag, "485 control bridge started baud=%lu", static_cast(config_.baudrate)); + return ESP_OK; +} + +void Gateway485ControlBridge::TaskEntry(void* arg) { + static_cast(arg)->taskLoop(); +} + +void Gateway485ControlBridge::taskLoop() { + std::vector read_buffer(std::max(config_.rx_buffer_size, 64)); + std::vector pending; + pending.reserve(std::max(config_.rx_buffer_size, 64)); + const TickType_t timeout = pdMS_TO_TICKS(config_.read_timeout_ms); + + while (true) { + const int read_len = uart_read_bytes(kControlUart, read_buffer.data(), read_buffer.size(), timeout); + if (read_len > 0) { + pending.insert(pending.end(), read_buffer.begin(), read_buffer.begin() + read_len); + continue; + } + if (!pending.empty()) { + handleBytes(pending.data(), pending.size()); + pending.clear(); + } + } +} + +void Gateway485ControlBridge::handleBytes(const uint8_t* data, size_t len) { + if (data == nullptr || len < kCommandFrameMinLen) { + return; + } + if (data[0] != 0x28 || data[1] != 0x01) { + ESP_LOGD(kTag, "ignored non-gateway UART0 burst len=%u", static_cast(len)); + return; + } + + controller_.enqueueCommandFrame(std::vector(data, data + len)); +} + +void Gateway485ControlBridge::handleGatewayNotification(const std::vector& frame) { + if (!started_ || frame.empty()) { + return; + } + + const int written = uart_write_bytes(kControlUart, frame.data(), frame.size()); + if (written < 0 || static_cast(written) != frame.size()) { + ESP_LOGW(kTag, "failed to write UART0 notification len=%u", static_cast(frame.size())); + return; + } + if (uart_wait_tx_done(kControlUart, pdMS_TO_TICKS(config_.write_timeout_ms)) != ESP_OK) { + ESP_LOGW(kTag, "timed out flushing UART0 notification len=%u", static_cast(frame.size())); + } +} + +} // namespace gateway \ No newline at end of file diff --git a/components/gateway_bridge/src/gateway_bridge.cpp b/components/gateway_bridge/src/gateway_bridge.cpp index da95372..d93898a 100644 --- a/components/gateway_bridge/src/gateway_bridge.cpp +++ b/components/gateway_bridge/src/gateway_bridge.cpp @@ -257,9 +257,13 @@ bool SnapshotHasDeviceType(const DaliDomainSnapshot& snapshot, int device_type) } std::optional SnapshotBoolValue(const DaliDomainSnapshot& snapshot, - const std::string& key) { - const auto found = snapshot.bools.find(key); - return found == snapshot.bools.end() ? std::nullopt : std::optional(found->second); + std::string_view key) { + for (const auto& entry : snapshot.bools) { + if (std::string_view(entry.first) == key) { + return entry.second; + } + } + return std::nullopt; } std::optional SnapshotIntValue(const DaliDomainSnapshot& snapshot, @@ -1258,11 +1262,13 @@ struct GatewayBridgeService::ChannelRuntime { } std::optional diagnosticSnapshotLocked(int short_address, - const std::string& kind) { + std::string_view kind) { if (!ValidShortAddress(short_address) || kind.empty()) { return std::nullopt; } - const std::string key = kind + ":" + std::to_string(short_address); + std::string key(kind.data(), kind.size()); + key += ":"; + key += std::to_string(short_address); const TickType_t now = xTaskGetTickCount(); const auto cached = diagnostic_snapshot_cache.find(key); if (cached != diagnostic_snapshot_cache.end() && @@ -1291,8 +1297,8 @@ struct GatewayBridgeService::ChannelRuntime { return snapshot; } - std::optional readSnapshotBoolLocked(int short_address, const std::string& kind, - const std::string& bool_key) { + std::optional readSnapshotBoolLocked(int short_address, std::string_view kind, + std::string_view bool_key) { const auto snapshot = diagnosticSnapshotLocked(short_address, kind); if (!snapshot.has_value()) { return std::nullopt; @@ -1629,7 +1635,7 @@ struct GatewayBridgeService::ChannelRuntime { return point; } - bool shouldPublishGeneratedBacnetPointLocked(const GatewayModbusPointBinding& point) { + bool shouldPublishGeneratedBacnetPointLocked(const GatewayModbusPoint& point) { if (!point.generated || point.space != GatewayModbusSpace::kDiscreteInput || point.access != GatewayModbusAccess::kReadOnly || !ValidShortAddress(point.short_address)) { @@ -1651,27 +1657,38 @@ struct GatewayBridgeService::ChannelRuntime { if (modbus == nullptr) { return bindings; } - for (const auto& point : modbus->describePoints()) { - if (!shouldPublishGeneratedBacnetPointLocked(point)) { + std::vector generated_points; + generated_points.reserve(192); + for (const auto& inventory_entry : discovery_inventory) { + if (!ValidShortAddress(inventory_entry.first)) { continue; } - const auto* discovery = findDiscoveryEntryLocked(point.short_address); - const auto object_instance = generatedBacnetBinaryInputInstance(point.address); - if (discovery == nullptr || !object_instance.has_value()) { - continue; + generated_points.clear(); + modbus->appendGeneratedPointsForShortAddress( + static_cast(inventory_entry.first), &generated_points); + for (const auto& point : generated_points) { + if (!shouldPublishGeneratedBacnetPointLocked(point)) { + continue; + } + const auto* discovery = findDiscoveryEntryLocked(point.short_address); + const auto object_instance = generatedBacnetBinaryInputInstance(point.address); + if (discovery == nullptr || !object_instance.has_value()) { + continue; + } + const auto binding = modbus->describePoint(point); + const bool out_of_service = !discovery->online; + bindings.push_back(GatewayBacnetObjectBinding{channel.gateway_id, + binding.id, + binding.name, + BridgeObjectType::binaryInput, + object_instance.value(), + "presentValue", + out_of_service, + out_of_service + ? kBacnetReliabilityCommunicationFailure + : kBacnetReliabilityNoFaultDetected, + true}); } - const bool out_of_service = !discovery->online; - bindings.push_back(GatewayBacnetObjectBinding{channel.gateway_id, - point.id, - point.name, - BridgeObjectType::binaryInput, - object_instance.value(), - "presentValue", - out_of_service, - out_of_service - ? kBacnetReliabilityCommunicationFailure - : kBacnetReliabilityNoFaultDetected, - true}); } return bindings; } @@ -2135,47 +2152,59 @@ struct GatewayBridgeService::ChannelRuntime { cJSON_AddItemToArray(bindings, item); } if (modbus != nullptr) { - for (const auto& point : modbus->describePoints()) { - if (!shouldPublishGeneratedBacnetPointLocked(point)) { + std::vector generated_points; + generated_points.reserve(192); + for (const auto& inventory_entry : discovery_inventory) { + if (!ValidShortAddress(inventory_entry.first)) { continue; } - const auto object_instance = generatedBacnetBinaryInputInstance(point.address); - const auto* discovery = findDiscoveryEntryLocked(point.short_address); - if (!object_instance.has_value() || discovery == nullptr) { - continue; + generated_points.clear(); + modbus->appendGeneratedPointsForShortAddress( + static_cast(inventory_entry.first), &generated_points); + for (const auto& point : generated_points) { + if (!shouldPublishGeneratedBacnetPointLocked(point)) { + continue; + } + const auto object_instance = generatedBacnetBinaryInputInstance(point.address); + const auto* discovery = findDiscoveryEntryLocked(point.short_address); + if (!object_instance.has_value() || discovery == nullptr) { + continue; + } + cJSON* item = cJSON_CreateObject(); + if (item == nullptr) { + continue; + } + const auto binding = modbus->describePoint(point); + cJSON_AddStringToObject(item, "model", binding.id.c_str()); + cJSON_AddStringToObject(item, "name", binding.name.c_str()); + cJSON_AddStringToObject(item, "objectType", "binaryInput"); + cJSON_AddNumberToObject(item, "objectInstance", object_instance.value()); + cJSON_AddStringToObject(item, "property", "presentValue"); + cJSON_AddBoolToObject(item, "generated", true); + cJSON_AddStringToObject(item, "generatedKind", + GatewayModbusGeneratedKindToString(binding.generated_kind)); + cJSON_AddNumberToObject(item, "shortAddress", binding.short_address); + if (!binding.diagnostic_snapshot.empty()) { + cJSON_AddStringToObject(item, "diagnosticSnapshot", + binding.diagnostic_snapshot.c_str()); + } + if (!binding.diagnostic_bool.empty()) { + cJSON_AddStringToObject(item, "diagnosticBool", binding.diagnostic_bool.c_str()); + } + if (binding.diagnostic_device_type >= 0) { + cJSON_AddNumberToObject(item, "diagnosticDeviceType", + binding.diagnostic_device_type); + } + const bool out_of_service = !discovery->online; + cJSON_AddBoolToObject(item, "outOfService", out_of_service); + cJSON_AddStringToObject(item, "reliability", + BacnetReliabilityToString(out_of_service + ? kBacnetReliabilityCommunicationFailure + : kBacnetReliabilityNoFaultDetected)); + cJSON_AddStringToObject(item, "inventoryState", + DiscoveryStateString(discovery->online)); + cJSON_AddItemToArray(bindings, item); } - cJSON* item = cJSON_CreateObject(); - if (item == nullptr) { - continue; - } - cJSON_AddStringToObject(item, "model", point.id.c_str()); - cJSON_AddStringToObject(item, "name", point.name.c_str()); - cJSON_AddStringToObject(item, "objectType", "binaryInput"); - cJSON_AddNumberToObject(item, "objectInstance", object_instance.value()); - cJSON_AddStringToObject(item, "property", "presentValue"); - cJSON_AddBoolToObject(item, "generated", true); - cJSON_AddStringToObject(item, "generatedKind", - GatewayModbusGeneratedKindToString(point.generated_kind)); - cJSON_AddNumberToObject(item, "shortAddress", point.short_address); - if (!point.diagnostic_snapshot.empty()) { - cJSON_AddStringToObject(item, "diagnosticSnapshot", - point.diagnostic_snapshot.c_str()); - } - if (!point.diagnostic_bool.empty()) { - cJSON_AddStringToObject(item, "diagnosticBool", point.diagnostic_bool.c_str()); - } - if (point.diagnostic_device_type >= 0) { - cJSON_AddNumberToObject(item, "diagnosticDeviceType", - point.diagnostic_device_type); - } - const bool out_of_service = !discovery->online; - cJSON_AddBoolToObject(item, "outOfService", out_of_service); - cJSON_AddStringToObject(item, "reliability", - BacnetReliabilityToString(out_of_service - ? kBacnetReliabilityCommunicationFailure - : kBacnetReliabilityNoFaultDetected)); - cJSON_AddStringToObject(item, "inventoryState", DiscoveryStateString(discovery->online)); - cJSON_AddItemToArray(bindings, item); } } } diff --git a/components/gateway_modbus/include/gateway_modbus.hpp b/components/gateway_modbus/include/gateway_modbus.hpp index 181df71..13817a0 100644 --- a/components/gateway_modbus/include/gateway_modbus.hpp +++ b/components/gateway_modbus/include/gateway_modbus.hpp @@ -119,12 +119,14 @@ struct GatewayModbusPoint { std::string name; bool generated{false}; GatewayModbusGeneratedKind generated_kind{GatewayModbusGeneratedKind::kNone}; + const char* generated_suffix{"point"}; + const char* generated_name{"point"}; int short_address{-1}; std::string model_id; BridgeOperation operation{BridgeOperation::unknown}; std::optional bit_index; - std::string diagnostic_snapshot; - std::string diagnostic_bool; + const char* diagnostic_snapshot{""}; + const char* diagnostic_bool{""}; int diagnostic_device_type{-1}; }; @@ -165,8 +167,12 @@ class GatewayModbusBridge { void rebuildMap(); std::optional findPoint(GatewayModbusSpace space, uint16_t address) const; + GatewayModbusPointBinding describePoint(const GatewayModbusPoint& point) const; + void appendGeneratedPointsForShortAddress(uint8_t short_address, + std::vector* points) const; std::vector describePoints() const; std::vector describeHoldingRegisters() const; + const std::vector& points() const; DaliBridgeResult readModelPoint(const GatewayModbusPoint& point) const; DaliBridgeResult writeRegisterPoint(const GatewayModbusPoint& point, uint16_t value) const; diff --git a/components/gateway_modbus/src/gateway_modbus.cpp b/components/gateway_modbus/src/gateway_modbus.cpp index dc3dbe0..d2bed1a 100644 --- a/components/gateway_modbus/src/gateway_modbus.cpp +++ b/components/gateway_modbus/src/gateway_modbus.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include namespace gateway { @@ -283,6 +282,16 @@ constexpr GeneratedDiagnosticBitSpec kGeneratedDiagnosticBitsTail[] = { {119, 1, "dt1", "controlGearFailure", "dt1_control_gear_failure", "DT1 control gear failure"}, }; +constexpr size_t kGeneratedDiagnosticBitCount = + sizeof(kGeneratedDiagnosticBits) / sizeof(kGeneratedDiagnosticBits[0]) + + sizeof(kGeneratedDiagnosticBitsTail) / sizeof(kGeneratedDiagnosticBitsTail[0]); +constexpr size_t kGeneratedPointsPerShort = kGeneratedCoils.size() + + kGeneratedDiscreteInputs.size() + + kGeneratedHoldingRegisters.size() + + kGeneratedInputRegisters.size() + + kGeneratedDiagnosticBitCount; +constexpr size_t kGeneratedPointCount = kShortAddressCount * kGeneratedPointsPerShort; + uint16_t baseForSpace(GatewayModbusSpace space) { switch (space) { case GatewayModbusSpace::kCoil: @@ -338,62 +347,165 @@ std::string generatedName(uint8_t short_address, const char* name) { return buffer; } -void addGeneratedPoint(std::map* points, uint8_t short_address, - const GeneratedPointSpec& spec) { - if (points == nullptr) { - return; +const char* literalOrEmpty(const char* value) { + return value == nullptr ? "" : value; +} + +PointKey keyForPoint(const GatewayModbusPoint& point) { + return PointKey{point.space, point.address}; +} + +bool pointLess(const GatewayModbusPoint& lhs, const GatewayModbusPoint& rhs) { + return keyForPoint(lhs) < keyForPoint(rhs); +} + +bool pointKeyEqual(const GatewayModbusPoint& lhs, const GatewayModbusPoint& rhs) { + return lhs.space == rhs.space && lhs.address == rhs.address; +} + +bool pointKeyEqual(const GatewayModbusPoint& point, const PointKey& key) { + return point.space == key.space && point.address == key.address; +} + +std::vector::const_iterator findStoredPoint( + const std::vector& points, PointKey key) { + const auto found = std::lower_bound( + points.begin(), points.end(), key, + [](const GatewayModbusPoint& point, const PointKey& value) { + return keyForPoint(point) < value; + }); + if (found == points.end() || !pointKeyEqual(*found, key)) { + return points.end(); } + return found; +} + +GatewayModbusPoint makeGeneratedPoint(uint8_t short_address, + const GeneratedPointSpec& spec) { const uint16_t address = static_cast(baseForSpace(spec.space) + short_address * kShortStride + spec.offset); GatewayModbusPoint point; point.space = spec.space; point.access = spec.access; point.address = address; - point.id = generatedId(short_address, spec.suffix); - point.name = generatedName(short_address, spec.name); point.generated = true; point.generated_kind = spec.kind; + point.generated_suffix = spec.suffix == nullptr ? "point" : spec.suffix; + point.generated_name = spec.name == nullptr ? "point" : spec.name; point.short_address = short_address; - (*points)[PointKey{spec.space, address}] = std::move(point); + return point; } -void addGeneratedDiagnosticPoint(std::map* points, - uint8_t short_address, - const GeneratedDiagnosticBitSpec& spec) { - if (points == nullptr) { - return; - } +GatewayModbusPoint makeGeneratedDiagnosticPoint(uint8_t short_address, + const GeneratedDiagnosticBitSpec& spec) { const uint16_t address = static_cast(kDiagnosticDiscreteInputBase + short_address * kDiagnosticStride + spec.offset); GatewayModbusPoint point; point.space = GatewayModbusSpace::kDiscreteInput; point.access = GatewayModbusAccess::kReadOnly; point.address = address; - point.id = generatedId(short_address, spec.suffix); - point.name = generatedName(short_address, spec.name); point.generated = true; point.generated_kind = GatewayModbusGeneratedKind::kShortDiagnosticBit; + point.generated_suffix = spec.suffix == nullptr ? "point" : spec.suffix; + point.generated_name = spec.name == nullptr ? "point" : spec.name; point.short_address = short_address; - point.diagnostic_snapshot = spec.snapshot == nullptr ? "" : spec.snapshot; - point.diagnostic_bool = spec.bool_key == nullptr ? "" : spec.bool_key; + point.diagnostic_snapshot = literalOrEmpty(spec.snapshot); + point.diagnostic_bool = literalOrEmpty(spec.bool_key); point.diagnostic_device_type = spec.device_type; - (*points)[PointKey{point.space, address}] = std::move(point); + return point; +} + +void appendIfNotOverridden(const std::vector& stored_points, + std::vector* points, + GatewayModbusPoint point) { + if (points == nullptr) { + return; + } + if (findStoredPoint(stored_points, keyForPoint(point)) != stored_points.end()) { + return; + } + points->push_back(std::move(point)); +} + +std::optional generatedPointForAddress(GatewayModbusSpace space, + uint16_t address) { + if (space == GatewayModbusSpace::kDiscreteInput && + address >= kDiagnosticDiscreteInputBase) { + const uint16_t relative = static_cast(address - kDiagnosticDiscreteInputBase); + const uint8_t short_address = static_cast(relative / kDiagnosticStride); + const uint16_t offset = static_cast(relative % kDiagnosticStride); + if (short_address >= kShortAddressCount) { + return std::nullopt; + } + for (const auto& spec : kGeneratedDiagnosticBits) { + if (spec.offset == offset) { + return makeGeneratedDiagnosticPoint(short_address, spec); + } + } + for (const auto& spec : kGeneratedDiagnosticBitsTail) { + if (spec.offset == offset) { + return makeGeneratedDiagnosticPoint(short_address, spec); + } + } + return std::nullopt; + } + + const uint16_t base = baseForSpace(space); + if (address < base) { + return std::nullopt; + } + const uint16_t relative = static_cast(address - base); + const uint8_t short_address = static_cast(relative / kShortStride); + const uint16_t offset = static_cast(relative % kShortStride); + if (short_address >= kShortAddressCount) { + return std::nullopt; + } + + const auto find_regular_point = [short_address, offset](const auto& specs) + -> std::optional { + for (const auto& spec : specs) { + if (spec.offset == offset) { + return makeGeneratedPoint(short_address, spec); + } + } + return std::nullopt; + }; + + switch (space) { + case GatewayModbusSpace::kCoil: + return find_regular_point(kGeneratedCoils); + case GatewayModbusSpace::kDiscreteInput: + return find_regular_point(kGeneratedDiscreteInputs); + case GatewayModbusSpace::kHoldingRegister: + return find_regular_point(kGeneratedHoldingRegisters); + case GatewayModbusSpace::kInputRegister: + return find_regular_point(kGeneratedInputRegisters); + } + return std::nullopt; } GatewayModbusPointBinding toBinding(const GatewayModbusPoint& point) { - return GatewayModbusPointBinding{point.model_id, - point.space, - point.address, - point.id, - point.name, - point.generated, - point.generated_kind, - point.short_address, - point.access, - point.bit_index, - point.diagnostic_snapshot, - point.diagnostic_bool, - point.diagnostic_device_type}; + GatewayModbusPointBinding binding; + binding.model_id = point.model_id; + binding.space = point.space; + binding.address = point.address; + if (point.generated) { + const auto short_address = static_cast(point.short_address < 0 ? 0 : point.short_address); + binding.id = generatedId(short_address, point.generated_suffix); + binding.name = generatedName(short_address, point.generated_name); + } else { + binding.id = point.id; + binding.name = point.name; + } + binding.generated = point.generated; + binding.generated_kind = point.generated_kind; + binding.short_address = point.short_address; + binding.access = point.access; + binding.bit_index = point.bit_index; + binding.diagnostic_snapshot = literalOrEmpty(point.diagnostic_snapshot); + binding.diagnostic_bool = literalOrEmpty(point.diagnostic_bool); + binding.diagnostic_device_type = point.diagnostic_device_type; + return binding; } int clampedInt(const DaliValue::Object& json, const std::string& key, int fallback, @@ -657,29 +769,11 @@ void GatewayModbusBridge::setConfig(const GatewayModbusConfig& config) { config_ const GatewayModbusConfig& GatewayModbusBridge::config() const { return config_; } void GatewayModbusBridge::rebuildMap() { - std::map next; - for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) { - for (const auto& spec : kGeneratedCoils) { - addGeneratedPoint(&next, short_address, spec); - } - for (const auto& spec : kGeneratedDiscreteInputs) { - addGeneratedPoint(&next, short_address, spec); - } - for (const auto& spec : kGeneratedHoldingRegisters) { - addGeneratedPoint(&next, short_address, spec); - } - for (const auto& spec : kGeneratedInputRegisters) { - addGeneratedPoint(&next, short_address, spec); - } - for (const auto& spec : kGeneratedDiagnosticBits) { - addGeneratedDiagnosticPoint(&next, short_address, spec); - } - for (const auto& spec : kGeneratedDiagnosticBitsTail) { - addGeneratedDiagnosticPoint(&next, short_address, spec); - } - } + auto models = engine_.listModels(); + points_.clear(); + points_.reserve(models.size()); - for (const auto& model : engine_.listModels()) { + for (const auto& model : models) { if (model.protocol != BridgeProtocolKind::modbus || !model.external.registerAddress.has_value()) { continue; } @@ -701,46 +795,117 @@ void GatewayModbusBridge::rebuildMap() { if (model.dali.kind == BridgeDaliTargetKind::shortAddress && model.dali.shortAddress.has_value()) { point.short_address = model.dali.shortAddress.value(); } - next[PointKey{point.space, point.address}] = std::move(point); + points_.push_back(std::move(point)); } - points_.clear(); - points_.reserve(next.size()); - for (auto& entry : next) { - points_.push_back(std::move(entry.second)); + std::stable_sort(points_.begin(), points_.end(), pointLess); + auto write = points_.begin(); + for (auto read = points_.begin(); read != points_.end();) { + auto next = read + 1; + while (next != points_.end() && pointKeyEqual(*read, *next)) { + ++next; + } + if (write != next - 1) { + *write = std::move(*(next - 1)); + } + ++write; + read = next; } + points_.erase(write, points_.end()); } std::optional GatewayModbusBridge::findPoint(GatewayModbusSpace space, uint16_t address) const { - const auto found = std::find_if(points_.begin(), points_.end(), [space, address](const auto& point) { - return point.space == space && point.address == address; - }); - if (found == points_.end()) { - return std::nullopt; + const PointKey key{space, address}; + const auto found = findStoredPoint(points_, key); + if (found != points_.end()) { + return *found; + } + return generatedPointForAddress(space, address); +} + +GatewayModbusPointBinding GatewayModbusBridge::describePoint( + const GatewayModbusPoint& point) const { + return toBinding(point); +} + +void GatewayModbusBridge::appendGeneratedPointsForShortAddress( + uint8_t short_address, std::vector* points) const { + if (points == nullptr || short_address >= kShortAddressCount) { + return; + } + for (const auto& spec : kGeneratedCoils) { + appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec)); + } + for (const auto& spec : kGeneratedDiscreteInputs) { + appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec)); + } + for (const auto& spec : kGeneratedHoldingRegisters) { + appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec)); + } + for (const auto& spec : kGeneratedInputRegisters) { + appendIfNotOverridden(points_, points, makeGeneratedPoint(short_address, spec)); + } + for (const auto& spec : kGeneratedDiagnosticBits) { + appendIfNotOverridden(points_, points, makeGeneratedDiagnosticPoint(short_address, spec)); + } + for (const auto& spec : kGeneratedDiagnosticBitsTail) { + appendIfNotOverridden(points_, points, makeGeneratedDiagnosticPoint(short_address, spec)); } - return *found; } std::vector GatewayModbusBridge::describePoints() const { std::vector bindings; - bindings.reserve(points_.size()); + bindings.reserve(kGeneratedPointCount + points_.size()); + std::vector generated_points; + generated_points.reserve(kGeneratedPointsPerShort); + for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) { + generated_points.clear(); + appendGeneratedPointsForShortAddress(short_address, &generated_points); + for (const auto& point : generated_points) { + bindings.push_back(toBinding(point)); + } + } for (const auto& point : points_) { bindings.push_back(toBinding(point)); } + std::sort(bindings.begin(), bindings.end(), [](const auto& lhs, const auto& rhs) { + if (lhs.space != rhs.space) { + return static_cast(lhs.space) < static_cast(rhs.space); + } + return lhs.address < rhs.address; + }); return bindings; } std::vector GatewayModbusBridge::describeHoldingRegisters() const { std::vector bindings; + std::vector generated_points; + generated_points.reserve(kGeneratedPointsPerShort); + for (uint8_t short_address = 0; short_address < kShortAddressCount; ++short_address) { + generated_points.clear(); + appendGeneratedPointsForShortAddress(short_address, &generated_points); + for (const auto& point : generated_points) { + if (point.space == GatewayModbusSpace::kHoldingRegister) { + bindings.push_back(toBinding(point)); + } + } + } for (const auto& point : points_) { if (point.space == GatewayModbusSpace::kHoldingRegister) { bindings.push_back(toBinding(point)); } } + std::sort(bindings.begin(), bindings.end(), [](const auto& lhs, const auto& rhs) { + return lhs.address < rhs.address; + }); return bindings; } +const std::vector& GatewayModbusBridge::points() const { + return points_; +} + DaliBridgeResult GatewayModbusBridge::readModelPoint(const GatewayModbusPoint& point) const { return executeModelPoint(point, std::nullopt); }