feat: Add DALI raw frame handling and USB setup bridge
- Introduced DaliRawFrame structure to encapsulate raw frame data. - Enhanced DaliDomainService to manage raw frame sinks and processing. - Implemented raw frame task for asynchronous handling of incoming DALI frames. - Integrated raw frame handling in GatewayBleBridge and GatewayNetworkService. - Added GatewayUsbSetupBridge to facilitate USB Serial/JTAG communication with DALI. - Configured ESP-NOW for wireless communication and setup management. - Updated GatewayRuntime to support clearing wireless credentials on boot button long press. - Enhanced CMakeLists to include new components and dependencies. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -5,16 +5,17 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
||||
## Layout
|
||||
|
||||
- `apps/`: standard ESP-IDF applications for each firmware role.
|
||||
- `apps/gateway/main/Kconfig.projbuild`: project-visible gateway-role settings such as per-channel native/serial PHY selection, gateway ids, and pin mapping.
|
||||
- `apps/gateway/main/Kconfig.projbuild`: project-visible gateway-role settings such as per-channel native/serial PHY selection, gateway ids, pin mapping, and startup transport policy.
|
||||
- `components/`: reusable components shared by all gateway applications.
|
||||
- `gateway_core/`: boot profile and top-level role bootstrap.
|
||||
- `dali/`: vendored ESP-IDF DALI HAL/backend reused from LuatOS.
|
||||
- `dali_domain/`: native DALI domain facade over `dali_cpp`.
|
||||
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`.
|
||||
- `dali/`: vendored ESP-IDF DALI HAL/backend reused from LuatOS, including native raw receive fan-out.
|
||||
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
|
||||
- `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, and setup AP mode 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, 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_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, 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 plus incoming `FFF1`/`FFF2`/`FFF3` writes into the native controller and DALI domain, and a `gateway_network` service that starts the native HTTP `/info`, `GET`/`POST /dali/cmd`, `/led/1`, `/led/0`, `/jq.js`, UDP control-plane router on port `2020`, Wi-Fi STA startup from persisted credentials, and the Lua-style `LAMMIN_Gateway` setup AP on `192.168.3.1`. 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, 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, 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 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. 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.
|
||||
@@ -1,6 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "app_main.cpp"
|
||||
REQUIRES gateway_core gateway_controller gateway_network dali_domain gateway_runtime gateway_ble log
|
||||
REQUIRES gateway_core gateway_controller gateway_network dali_domain gateway_runtime gateway_ble gateway_usb_setup log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -258,6 +258,90 @@ config GATEWAY_DALI_BAUDRATE
|
||||
help
|
||||
Runtime baudrate used when initializing the local DALI bus.
|
||||
|
||||
menu "Gateway Startup Services"
|
||||
|
||||
config GATEWAY_BLE_SUPPORTED
|
||||
bool "BLE gateway transport is supported"
|
||||
default y
|
||||
help
|
||||
Builds and starts the BLE gateway bridge. Runtime BLE enable state is controlled separately.
|
||||
|
||||
config GATEWAY_START_BLE_ENABLED
|
||||
bool "Enable BLE at startup"
|
||||
depends on GATEWAY_BLE_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Default runtime BLE state when no persisted BLE setting exists. Lua gateway behavior starts BLE by default.
|
||||
|
||||
config GATEWAY_WIFI_SUPPORTED
|
||||
bool "Wi-Fi gateway transport is supported"
|
||||
default y
|
||||
help
|
||||
Keeps Wi-Fi control, HTTP/UDP networking, setup AP, and ESP-NOW setup paths available.
|
||||
|
||||
config GATEWAY_START_WIFI_STA_ENABLED
|
||||
bool "Start Wi-Fi STA at startup"
|
||||
depends on GATEWAY_WIFI_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Connect to persisted Wi-Fi credentials at boot. Disabled by default so wireless stays off until commanded.
|
||||
|
||||
config GATEWAY_ESPNOW_SETUP_SUPPORTED
|
||||
bool "ESP-NOW setup transport is supported"
|
||||
depends on GATEWAY_WIFI_SUPPORTED
|
||||
default y
|
||||
help
|
||||
Enables ESP-NOW setup ingress when setup AP mode is entered.
|
||||
|
||||
config GATEWAY_START_ESPNOW_SETUP_ENABLED
|
||||
bool "Enter ESP-NOW setup mode at startup"
|
||||
depends on GATEWAY_ESPNOW_SETUP_SUPPORTED
|
||||
default n
|
||||
help
|
||||
Starts the setup AP and ESP-NOW setup ingress immediately at boot. Disabled by default.
|
||||
|
||||
choice GATEWAY_USB_STARTUP_MODE
|
||||
prompt "USB Serial/JTAG startup mode"
|
||||
default GATEWAY_USB_STARTUP_DEBUG_JTAG
|
||||
help
|
||||
Select whether the built-in USB Serial/JTAG interface remains available for debug or is claimed by the setup bridge.
|
||||
|
||||
config GATEWAY_USB_STARTUP_DEBUG_JTAG
|
||||
bool "USB Serial/JTAG debug interface"
|
||||
|
||||
config GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
bool "USB Serial/JTAG setup bridge"
|
||||
|
||||
endchoice
|
||||
|
||||
config GATEWAY_USB_SETUP_CHANNEL_INDEX
|
||||
int "USB setup DALI channel index"
|
||||
depends on GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
range 0 1
|
||||
default 0
|
||||
help
|
||||
Native zero-based DALI channel used for short raw USB setup frames on the single USB stream.
|
||||
|
||||
config GATEWAY_USB_SETUP_RX_BUFFER
|
||||
int "USB setup RX buffer bytes"
|
||||
depends on GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
range 64 4096
|
||||
default 256
|
||||
|
||||
config GATEWAY_USB_SETUP_TX_BUFFER
|
||||
int "USB setup TX buffer bytes"
|
||||
depends on GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
range 64 4096
|
||||
default 256
|
||||
|
||||
config GATEWAY_USB_SETUP_READ_TIMEOUT_MS
|
||||
int "USB setup read timeout ms"
|
||||
depends on GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
range 1 1000
|
||||
default 20
|
||||
|
||||
endmenu
|
||||
|
||||
menu "Gateway Network Services"
|
||||
|
||||
config GATEWAY_NETWORK_HTTP_ENABLED
|
||||
@@ -296,6 +380,24 @@ config GATEWAY_STATUS_LED_ACTIVE_HIGH
|
||||
depends on GATEWAY_STATUS_LED_GPIO >= 0
|
||||
default y
|
||||
|
||||
config GATEWAY_BOOT_BUTTON_GPIO
|
||||
int "BOOT button GPIO"
|
||||
range -1 48
|
||||
default 0
|
||||
help
|
||||
GPIO used for Lua-compatible setup entry and Wi-Fi credential clearing. Set to -1 to disable.
|
||||
|
||||
config GATEWAY_BOOT_BUTTON_ACTIVE_LOW
|
||||
bool "BOOT button is active low"
|
||||
depends on GATEWAY_BOOT_BUTTON_GPIO >= 0
|
||||
default y
|
||||
|
||||
config GATEWAY_BOOT_BUTTON_LONG_PRESS_MS
|
||||
int "BOOT button long press ms"
|
||||
depends on GATEWAY_BOOT_BUTTON_GPIO >= 0
|
||||
range 500 10000
|
||||
default 3000
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "gateway_core.hpp"
|
||||
#include "gateway_network.hpp"
|
||||
#include "gateway_runtime.hpp"
|
||||
#include "gateway_usb_setup.hpp"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
@@ -28,16 +29,83 @@
|
||||
#define CONFIG_GATEWAY_STATUS_LED_GPIO -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BOOT_BUTTON_GPIO
|
||||
#define CONFIG_GATEWAY_BOOT_BUTTON_GPIO -1
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS
|
||||
#define CONFIG_GATEWAY_BOOT_BUTTON_LONG_PRESS_MS 3000
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX
|
||||
#define CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX 0
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_USB_SETUP_RX_BUFFER
|
||||
#define CONFIG_GATEWAY_USB_SETUP_RX_BUFFER 256
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_USB_SETUP_TX_BUFFER
|
||||
#define CONFIG_GATEWAY_USB_SETUP_TX_BUFFER 256
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS
|
||||
#define CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS 20
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr const char* kProjectName = "DALI_485_Gateway";
|
||||
constexpr const char* kProjectVersion = "0.1.0";
|
||||
constexpr const char* kTag = "gateway_main";
|
||||
|
||||
#ifdef CONFIG_GATEWAY_WIFI_SUPPORTED
|
||||
constexpr bool kWifiSupported = true;
|
||||
#else
|
||||
constexpr bool kWifiSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_WIFI_STA_ENABLED
|
||||
constexpr bool kWifiStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kWifiStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_BLE_SUPPORTED
|
||||
constexpr bool kBleSupported = true;
|
||||
#else
|
||||
constexpr bool kBleSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_BLE_ENABLED
|
||||
constexpr bool kBleStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kBleStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_ESPNOW_SETUP_SUPPORTED
|
||||
constexpr bool kEspnowSetupSupported = true;
|
||||
#else
|
||||
constexpr bool kEspnowSetupSupported = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED
|
||||
constexpr bool kEspnowSetupStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kEspnowSetupStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL
|
||||
constexpr bool kUsbSetupStartupEnabled = true;
|
||||
#else
|
||||
constexpr bool kUsbSetupStartupEnabled = false;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<gateway::DaliDomainService> s_dali_domain;
|
||||
std::unique_ptr<gateway::GatewayRuntime> s_runtime;
|
||||
std::unique_ptr<gateway::GatewayController> s_controller;
|
||||
std::unique_ptr<gateway::GatewayNetworkService> s_network;
|
||||
std::unique_ptr<gateway::GatewayBleBridge> s_ble_bridge;
|
||||
std::unique_ptr<gateway::GatewayUsbSetupBridge> s_usb_setup_bridge;
|
||||
|
||||
[[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) {
|
||||
if (err != ESP_OK) {
|
||||
@@ -239,11 +307,11 @@ extern "C" void app_main(void) {
|
||||
const gateway::BootProfile profile{
|
||||
gateway::AppRole::kGateway,
|
||||
"gateway",
|
||||
kWifiSupported,
|
||||
kBleSupported,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
kEspnowSetupSupported,
|
||||
kUsbSetupStartupEnabled,
|
||||
};
|
||||
|
||||
gateway::GatewayCore core(profile);
|
||||
@@ -258,6 +326,7 @@ extern "C" void app_main(void) {
|
||||
kProjectName,
|
||||
kProjectVersion,
|
||||
gateway::ReadRuntimeSerialId(),
|
||||
kBleStartupEnabled,
|
||||
},
|
||||
s_dali_domain.get());
|
||||
ESP_ERROR_CHECK(s_runtime->start());
|
||||
@@ -278,7 +347,10 @@ extern "C" void app_main(void) {
|
||||
|
||||
if (profile.enable_wifi || profile.enable_eth) {
|
||||
gateway::GatewayNetworkServiceConfig network_config;
|
||||
network_config.wifi_enabled = profile.enable_wifi;
|
||||
network_config.wifi_enabled = profile.enable_wifi && kWifiStartupEnabled;
|
||||
network_config.espnow_setup_enabled = profile.enable_espnow;
|
||||
network_config.espnow_setup_startup_enabled =
|
||||
profile.enable_espnow && kEspnowSetupStartupEnabled;
|
||||
#ifdef CONFIG_GATEWAY_NETWORK_HTTP_ENABLED
|
||||
network_config.http_enabled = true;
|
||||
#else
|
||||
@@ -292,13 +364,20 @@ extern "C" void app_main(void) {
|
||||
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.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;
|
||||
#ifdef CONFIG_GATEWAY_STATUS_LED_ACTIVE_HIGH
|
||||
network_config.status_led_active_high = true;
|
||||
#else
|
||||
network_config.status_led_active_high = false;
|
||||
#endif
|
||||
#ifdef CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW
|
||||
network_config.boot_button_active_low = true;
|
||||
#else
|
||||
network_config.boot_button_active_low = false;
|
||||
#endif
|
||||
s_network = std::make_unique<gateway::GatewayNetworkService>(*s_controller, *s_runtime,
|
||||
network_config);
|
||||
*s_dali_domain, network_config);
|
||||
ESP_ERROR_CHECK(s_network->start());
|
||||
}
|
||||
|
||||
@@ -308,6 +387,19 @@ extern "C" void app_main(void) {
|
||||
ESP_ERROR_CHECK(s_ble_bridge->start());
|
||||
}
|
||||
|
||||
if (profile.enable_usb) {
|
||||
gateway::GatewayUsbSetupBridgeConfig usb_config;
|
||||
usb_config.enabled = true;
|
||||
usb_config.channel_index = static_cast<uint8_t>(CONFIG_GATEWAY_USB_SETUP_CHANNEL_INDEX);
|
||||
usb_config.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_USB_SETUP_RX_BUFFER);
|
||||
usb_config.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_USB_SETUP_TX_BUFFER);
|
||||
usb_config.read_timeout_ms = static_cast<uint32_t>(CONFIG_GATEWAY_USB_SETUP_READ_TIMEOUT_MS);
|
||||
s_usb_setup_bridge = std::make_unique<gateway::GatewayUsbSetupBridge>(*s_controller,
|
||||
*s_dali_domain,
|
||||
usb_config);
|
||||
ESP_ERROR_CHECK(s_usb_setup_bridge->start());
|
||||
}
|
||||
|
||||
const auto device_info = s_runtime->deviceInfo();
|
||||
std::printf("gateway_main: dali domain implementation=%s bound=%d channels=%u\n",
|
||||
s_dali_domain->implementationName(), s_dali_domain->isBound(),
|
||||
|
||||
@@ -620,6 +620,19 @@ CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED=y
|
||||
|
||||
# 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_START_ESPNOW_SETUP_ENABLED is not set
|
||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||
# end of Gateway Startup Services
|
||||
|
||||
#
|
||||
# Gateway Network Services
|
||||
#
|
||||
@@ -628,6 +641,9 @@ 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
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ QueueHandle_t dali_send_queue; // [Dali_msg_t] from other tasks to dali t
|
||||
QueueHandle_t dali_send_reply_queue; // [Dali_msg_t] from dali task to other tasks
|
||||
QueueHandle_t dali_receive_queue; // alias to bus 0 for compatibility
|
||||
QueueHandle_t dali_receive_queues[DALI_PHY_COUNT];
|
||||
QueueHandle_t dali_raw_receive_queue; // [Dali_msg_t] non-consuming receive fan-out
|
||||
|
||||
// internal queues for debug data
|
||||
struct Dali_rx_dbg_data {
|
||||
@@ -250,6 +251,19 @@ static inline bool bus_valid(uint8_t bus_id) {
|
||||
return bus_id < DALI_PHY_COUNT && s_bus[bus_id].inited;
|
||||
}
|
||||
|
||||
static inline void publish_rx_frame_from_isr(Dali_msg_t *msg, QueueHandle_t queue, BaseType_t *yield)
|
||||
{
|
||||
if (msg == NULL) {
|
||||
return;
|
||||
}
|
||||
if (queue) {
|
||||
xQueueSendToBackFromISR(queue, msg, yield);
|
||||
}
|
||||
if (dali_raw_receive_queue) {
|
||||
xQueueSendToBackFromISR(dali_raw_receive_queue, msg, yield);
|
||||
}
|
||||
}
|
||||
|
||||
// GPIO ISR handler
|
||||
// define rx_gpio_isr_handler on any edge
|
||||
static void IRAM_ATTR rx_gpio_isr_handler(void* arg)
|
||||
@@ -524,9 +538,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
// rx_data.status = DALI_FRAME_ERROR; // should be set inside ISR
|
||||
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
|
||||
// rx_data.data[0] = 0xAA; // debug
|
||||
if (bus->rx_queue) {
|
||||
xQueueSendToBackFromISR(bus->rx_queue, &bus->rx_data, &yield); // send data to queue
|
||||
}
|
||||
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
|
||||
}
|
||||
}
|
||||
else if(bus->rx_state == RX_STATE_DATA || bus->rx_state == RX_STATE_STOP) {
|
||||
@@ -537,9 +549,7 @@ static bool IRAM_ATTR handle_bus_timer(dali_bus_ctx_t *bus, uint64_t time_now)
|
||||
bus->rx_data.status = DALI_FRAME_OK; // frame is OK
|
||||
bus->rx_data.length = bus->rx_data_bit_counter; // set length of data
|
||||
// rx_data.data[0] = 0xBB; // debug
|
||||
if (bus->rx_queue) {
|
||||
xQueueSendToBackFromISR(bus->rx_queue, &bus->rx_data, &yield); // send data to queue
|
||||
}
|
||||
publish_rx_frame_from_isr(&bus->rx_data, bus->rx_queue, &yield); // send data to queue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -617,6 +627,9 @@ static void ensure_common_queues(void)
|
||||
if (!rx_dbg_queue) {
|
||||
rx_dbg_queue = xQueueCreate(CONFIG_DALI_DEBUG_QUEUE_LEN, sizeof(Dali_rx_dbg_data_t));
|
||||
}
|
||||
if (!dali_raw_receive_queue) {
|
||||
dali_raw_receive_queue = xQueueCreate(CONFIG_DALI_RX_QUEUE_LEN, sizeof(Dali_msg_t));
|
||||
}
|
||||
#if CONFIG_DALI_ENABLE_DEBUG_TASK
|
||||
if (!s_debug_task_created) {
|
||||
xTaskCreate(debug_task, "debug_task", CONFIG_DALI_DEBUG_TASK_STACK_SIZE, NULL, CONFIG_DALI_DEBUG_TASK_PRIORITY, NULL); // at low priority !!!
|
||||
@@ -931,4 +944,10 @@ esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
QueueHandle_t dali_hal_raw_receive_queue(void)
|
||||
{
|
||||
ensure_common_queues();
|
||||
return dali_raw_receive_queue;
|
||||
}
|
||||
|
||||
#endif // CONFIG_IDF_TARGET
|
||||
|
||||
@@ -204,6 +204,7 @@ extern QueueHandle_t dali_send_queue;
|
||||
extern QueueHandle_t dali_send_reply_queue;
|
||||
extern QueueHandle_t dali_receive_queue;
|
||||
extern QueueHandle_t dali_receive_queues[DALI_PHY_COUNT];
|
||||
extern QueueHandle_t dali_raw_receive_queue;
|
||||
|
||||
extern uint8_t rx_debug_enabled; // 1 - enable debug for received messages, timing, etc
|
||||
|
||||
@@ -224,5 +225,6 @@ esp_err_t dali_hal_set_baudrate(uint32_t baudrate);
|
||||
uint32_t dali_hal_get_baudrate(void);
|
||||
size_t dali_hal_get_inited_buses(uint8_t *ids, size_t max_ids);
|
||||
esp_err_t dali_hal_get_bus_info(uint8_t bus_id, dali_hal_bus_info_t *info);
|
||||
QueueHandle_t dali_hal_raw_receive_queue(void);
|
||||
void dali_task(void *pvParameters);
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
class Dali;
|
||||
class DaliComm;
|
||||
@@ -65,6 +68,13 @@ struct DaliChannelInfo {
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct DaliRawFrame {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
class DaliDomainService {
|
||||
public:
|
||||
DaliDomainService();
|
||||
@@ -78,6 +88,7 @@ class DaliDomainService {
|
||||
const char* implementationName() const;
|
||||
size_t channelCount() const;
|
||||
std::vector<DaliChannelInfo> channelInfo() const;
|
||||
void addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink);
|
||||
|
||||
bool resetBus(uint8_t gateway_id) const;
|
||||
bool writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const;
|
||||
@@ -108,9 +119,17 @@ class DaliDomainService {
|
||||
DaliChannel* findChannelByGateway(uint8_t gateway_id);
|
||||
const DaliChannel* findChannelByGateway(uint8_t gateway_id) const;
|
||||
DaliChannel* findChannelByIndex(uint8_t channel_index);
|
||||
const DaliChannel* findChannelByHardwareBus(uint8_t bus_id) const;
|
||||
bool hasSerialPort(int uart_port) const;
|
||||
esp_err_t startRawFrameTask();
|
||||
static void RawFrameTaskEntry(void* arg);
|
||||
void rawFrameTaskLoop();
|
||||
void notifyRawFrameSinks(const DaliRawFrame& frame);
|
||||
|
||||
std::vector<std::unique_ptr<DaliChannel>> channels_;
|
||||
std::vector<std::function<void(const DaliRawFrame& frame)>> raw_frame_sinks_;
|
||||
SemaphoreHandle_t raw_frame_sink_lock_{nullptr};
|
||||
TaskHandle_t raw_frame_task_handle_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -112,7 +112,8 @@ struct DaliDomainService::DaliChannel {
|
||||
std::optional<DaliSerialBusConfig> serial_bus;
|
||||
};
|
||||
|
||||
DaliDomainService::DaliDomainService() = default;
|
||||
DaliDomainService::DaliDomainService()
|
||||
: raw_frame_sink_lock_(xSemaphoreCreateMutex()) {}
|
||||
|
||||
DaliDomainService::~DaliDomainService() = default;
|
||||
|
||||
@@ -172,6 +173,10 @@ esp_err_t DaliDomainService::bindHardwareBus(const DaliHardwareBusConfig& config
|
||||
channel->phy_kind = DaliPhyKind::kNativeHardware;
|
||||
channel->hardware_bus = config;
|
||||
}
|
||||
err = startRawFrameTask();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to start raw frame task: %s", esp_err_to_name(err));
|
||||
}
|
||||
ESP_LOGI(kTag, "bound channel=%u gateway=%u hardware bus=%u tx=%u rx=%u baudrate=%lu",
|
||||
config.channel_index, config.gateway_id, config.bus_id, config.tx_pin, config.rx_pin,
|
||||
static_cast<unsigned long>(config.baudrate));
|
||||
@@ -290,6 +295,19 @@ std::vector<DaliChannelInfo> DaliDomainService::channelInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
void DaliDomainService::addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink) {
|
||||
if (!sink) {
|
||||
return;
|
||||
}
|
||||
if (raw_frame_sink_lock_ != nullptr) {
|
||||
xSemaphoreTake(raw_frame_sink_lock_, portMAX_DELAY);
|
||||
}
|
||||
raw_frame_sinks_.push_back(std::move(sink));
|
||||
if (raw_frame_sink_lock_ != nullptr) {
|
||||
xSemaphoreGive(raw_frame_sink_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
bool DaliDomainService::resetBus(uint8_t gateway_id) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->comm != nullptr && channel->comm->resetBus();
|
||||
@@ -446,6 +464,81 @@ DaliDomainService::DaliChannel* DaliDomainService::findChannelByIndex(uint8_t ch
|
||||
return it == channels_.end() ? nullptr : it->get();
|
||||
}
|
||||
|
||||
const DaliDomainService::DaliChannel* DaliDomainService::findChannelByHardwareBus(
|
||||
uint8_t bus_id) const {
|
||||
const auto it = std::find_if(channels_.begin(), channels_.end(), [bus_id](const auto& channel) {
|
||||
return channel->hardware_bus.has_value() && channel->hardware_bus->bus_id == bus_id;
|
||||
});
|
||||
return it == channels_.end() ? nullptr : it->get();
|
||||
}
|
||||
|
||||
esp_err_t DaliDomainService::startRawFrameTask() {
|
||||
if (raw_frame_task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
QueueHandle_t queue = dali_hal_raw_receive_queue();
|
||||
if (queue == nullptr) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
const BaseType_t created = xTaskCreate(&DaliDomainService::RawFrameTaskEntry,
|
||||
"dali_raw_rx", 4096, this, 4,
|
||||
&raw_frame_task_handle_);
|
||||
if (created != pdPASS) {
|
||||
raw_frame_task_handle_ = nullptr;
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void DaliDomainService::RawFrameTaskEntry(void* arg) {
|
||||
static_cast<DaliDomainService*>(arg)->rawFrameTaskLoop();
|
||||
}
|
||||
|
||||
void DaliDomainService::rawFrameTaskLoop() {
|
||||
QueueHandle_t queue = dali_hal_raw_receive_queue();
|
||||
Dali_msg_t message = {};
|
||||
while (true) {
|
||||
if (queue == nullptr) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
queue = dali_hal_raw_receive_queue();
|
||||
continue;
|
||||
}
|
||||
if (xQueueReceive(queue, &message, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
if (message.status != DALI_FRAME_OK) {
|
||||
continue;
|
||||
}
|
||||
const auto* channel = findChannelByHardwareBus(message.id);
|
||||
if (channel == nullptr) {
|
||||
continue;
|
||||
}
|
||||
size_t byte_count = (static_cast<size_t>(message.length) + 7U) / 8U;
|
||||
if (byte_count > DALI_MAX_BYTES) {
|
||||
byte_count = DALI_MAX_BYTES;
|
||||
}
|
||||
DaliRawFrame frame;
|
||||
frame.channel_index = channel->config.channel_index;
|
||||
frame.gateway_id = channel->config.gateway_id;
|
||||
frame.phy_kind = channel->phy_kind;
|
||||
frame.data.assign(message.data, message.data + byte_count);
|
||||
notifyRawFrameSinks(frame);
|
||||
}
|
||||
}
|
||||
|
||||
void DaliDomainService::notifyRawFrameSinks(const DaliRawFrame& frame) {
|
||||
if (raw_frame_sink_lock_ != nullptr) {
|
||||
xSemaphoreTake(raw_frame_sink_lock_, portMAX_DELAY);
|
||||
}
|
||||
auto sinks = raw_frame_sinks_;
|
||||
if (raw_frame_sink_lock_ != nullptr) {
|
||||
xSemaphoreGive(raw_frame_sink_lock_);
|
||||
}
|
||||
for (const auto& sink : sinks) {
|
||||
sink(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool DaliDomainService::hasSerialPort(int uart_port) const {
|
||||
return std::any_of(channels_.begin(), channels_.end(), [uart_port](const auto& channel) {
|
||||
return channel->serial_bus.has_value() && channel->serial_bus->uart_port == uart_port;
|
||||
|
||||
@@ -14,6 +14,7 @@ struct ble_gatt_access_ctxt;
|
||||
namespace gateway {
|
||||
|
||||
class DaliDomainService;
|
||||
struct DaliRawFrame;
|
||||
class GatewayController;
|
||||
class GatewayRuntime;
|
||||
|
||||
@@ -38,6 +39,7 @@ class GatewayBleBridge {
|
||||
void stopAdvertising();
|
||||
void notifyCharacteristic(size_t index, const std::vector<uint8_t>& payload);
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
void handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload);
|
||||
void handleGatewayWrite(const std::vector<uint8_t>& payload);
|
||||
std::string resolvedDeviceName() const;
|
||||
|
||||
@@ -177,6 +177,7 @@ esp_err_t GatewayBleBridge::start() {
|
||||
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
|
||||
controller_.addBleStateSink([this](bool enabled) { setEnabled(enabled); });
|
||||
controller_.addGatewayNameSink([this](uint8_t) { refreshDeviceName(); });
|
||||
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
|
||||
|
||||
const esp_err_t err = initNimble();
|
||||
if (err != ESP_OK) {
|
||||
@@ -372,6 +373,13 @@ void GatewayBleBridge::handleGatewayNotification(const std::vector<uint8_t>& fra
|
||||
last_notify_at_us_ = now;
|
||||
}
|
||||
|
||||
void GatewayBleBridge::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
if (!enabled_ || conn_handle_ == kInvalidConnectionHandle || frame.data.empty()) {
|
||||
return;
|
||||
}
|
||||
notifyCharacteristic(frame.channel_index, frame.data);
|
||||
}
|
||||
|
||||
void GatewayBleBridge::handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload) {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
const auto channel_it = std::find_if(channels.begin(), channels.end(),
|
||||
|
||||
@@ -320,6 +320,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
if (config_.ble_supported) {
|
||||
feature |= 0x02;
|
||||
}
|
||||
if (config_.wifi_supported) {
|
||||
feature |= 0x04;
|
||||
}
|
||||
if (config_.ip_router_supported && ip_router_enabled_) {
|
||||
feature |= 0x08;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_network.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES esp_event esp_http_server esp_netif esp_wifi freertos gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
REQUIRES dali_domain esp_event esp_http_server esp_netif esp_wifi freertos gateway_controller gateway_runtime log lwip espressif__cjson
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -8,6 +9,7 @@
|
||||
#include "esp_event.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_now.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -16,16 +18,25 @@
|
||||
namespace gateway {
|
||||
|
||||
class GatewayController;
|
||||
class DaliDomainService;
|
||||
struct DaliRawFrame;
|
||||
class GatewayRuntime;
|
||||
|
||||
struct GatewayNetworkServiceConfig {
|
||||
bool wifi_enabled{true};
|
||||
bool espnow_setup_enabled{true};
|
||||
bool espnow_setup_startup_enabled{false};
|
||||
bool http_enabled{true};
|
||||
bool udp_enabled{true};
|
||||
uint16_t http_port{80};
|
||||
uint16_t udp_port{2020};
|
||||
int status_led_gpio{-1};
|
||||
bool status_led_active_high{true};
|
||||
int boot_button_gpio{-1};
|
||||
bool boot_button_active_low{true};
|
||||
uint32_t boot_button_long_press_ms{3000};
|
||||
uint32_t boot_button_task_stack_size{2048};
|
||||
UBaseType_t boot_button_task_priority{2};
|
||||
uint32_t udp_task_stack_size{4096};
|
||||
UBaseType_t udp_task_priority{4};
|
||||
};
|
||||
@@ -33,12 +44,13 @@ struct GatewayNetworkServiceConfig {
|
||||
class GatewayNetworkService {
|
||||
public:
|
||||
GatewayNetworkService(GatewayController& controller, GatewayRuntime& runtime,
|
||||
GatewayNetworkServiceConfig config = {});
|
||||
DaliDomainService& dali_domain, GatewayNetworkServiceConfig config = {});
|
||||
|
||||
esp_err_t start();
|
||||
|
||||
private:
|
||||
static void UdpTaskEntry(void* arg);
|
||||
static void BootButtonTaskEntry(void* arg);
|
||||
static esp_err_t HandleInfoGet(httpd_req_t* req);
|
||||
static esp_err_t HandleCommandGet(httpd_req_t* req);
|
||||
static esp_err_t HandleCommandPost(httpd_req_t* req);
|
||||
@@ -47,17 +59,29 @@ class GatewayNetworkService {
|
||||
static esp_err_t HandleJqJsGet(httpd_req_t* req);
|
||||
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 startWifi();
|
||||
esp_err_t startSetupAp();
|
||||
esp_err_t startEspNow();
|
||||
void stopEspNow();
|
||||
esp_err_t addEspNowPeer(const uint8_t* mac, bool broadcast = false);
|
||||
esp_err_t sendEspNowJson(const uint8_t* mac, const std::string& payload);
|
||||
esp_err_t configureStatusLed();
|
||||
esp_err_t startHttpServer();
|
||||
esp_err_t startUdpTask();
|
||||
esp_err_t configureBootButton();
|
||||
esp_err_t startBootButtonTask();
|
||||
void udpTaskLoop();
|
||||
void bootButtonTaskLoop();
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
void handleWifiControl(uint8_t mode);
|
||||
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);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
std::string deviceInfoJson() const;
|
||||
std::string deviceInfoDoubleEncodedJson() const;
|
||||
std::string gatewaySnapshotJson();
|
||||
@@ -65,6 +89,7 @@ class GatewayNetworkService {
|
||||
|
||||
GatewayController& controller_;
|
||||
GatewayRuntime& runtime_;
|
||||
DaliDomainService& dali_domain_;
|
||||
GatewayNetworkServiceConfig config_;
|
||||
bool started_{false};
|
||||
httpd_handle_t http_server_{nullptr};
|
||||
@@ -72,6 +97,10 @@ class GatewayNetworkService {
|
||||
esp_netif_t* wifi_ap_netif_{nullptr};
|
||||
bool wifi_started_{false};
|
||||
bool setup_ap_started_{false};
|
||||
bool espnow_started_{false};
|
||||
bool espnow_connected_{false};
|
||||
std::array<uint8_t, 6> espnow_peer_{};
|
||||
TaskHandle_t boot_button_task_handle_{nullptr};
|
||||
TaskHandle_t udp_task_handle_{nullptr};
|
||||
int udp_socket_{-1};
|
||||
SemaphoreHandle_t udp_lock_{nullptr};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "gateway_network.hpp"
|
||||
|
||||
#include "dali_domain.hpp"
|
||||
#include "gateway_controller.hpp"
|
||||
#include "gateway_runtime.hpp"
|
||||
|
||||
@@ -9,6 +10,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_netif_ip_addr.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "lwip/inet.h"
|
||||
|
||||
@@ -26,6 +28,9 @@ namespace {
|
||||
constexpr const char* kTag = "gateway_network";
|
||||
constexpr const char* kSetupApSsid = "LAMMIN_Gateway";
|
||||
constexpr size_t kUdpBufferSize = 256;
|
||||
constexpr uint8_t kEspNowBroadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
GatewayNetworkService* s_espnow_service = nullptr;
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
@@ -86,6 +91,43 @@ std::string MacToHex(const uint8_t mac[6]) {
|
||||
return std::string(out);
|
||||
}
|
||||
|
||||
std::string LocalMacHex(wifi_interface_t interface) {
|
||||
uint8_t mac[6] = {};
|
||||
if (esp_wifi_get_mac(interface, mac) != ESP_OK) {
|
||||
return {};
|
||||
}
|
||||
return MacToHex(mac);
|
||||
}
|
||||
|
||||
const char* JsonString(cJSON* parent, const char* name) {
|
||||
cJSON* item = cJSON_GetObjectItem(parent, name);
|
||||
return cJSON_IsString(item) ? item->valuestring : nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BytesFromJsonString(const char* value) {
|
||||
if (value == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string_view text(value);
|
||||
auto decoded = DecodeHex(text);
|
||||
if (!decoded.empty() || text.empty()) {
|
||||
return decoded;
|
||||
}
|
||||
return std::vector<uint8_t>(text.begin(), text.end());
|
||||
}
|
||||
|
||||
std::string BytesToHex(const std::vector<uint8_t>& bytes) {
|
||||
static constexpr char kHex[] = "0123456789ABCDEF";
|
||||
std::string out;
|
||||
out.reserve(bytes.size() * 2);
|
||||
for (uint8_t byte : bytes) {
|
||||
out.push_back(kHex[(byte >> 4) & 0x0F]);
|
||||
out.push_back(kHex[byte & 0x0F]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string PrintJson(cJSON* node) {
|
||||
if (node == nullptr) {
|
||||
return {};
|
||||
@@ -132,8 +174,9 @@ esp_err_t RegisterUri(httpd_handle_t server, const char* uri, httpd_method_t met
|
||||
|
||||
GatewayNetworkService::GatewayNetworkService(GatewayController& controller,
|
||||
GatewayRuntime& runtime,
|
||||
DaliDomainService& dali_domain,
|
||||
GatewayNetworkServiceConfig config)
|
||||
: controller_(controller), runtime_(runtime), config_(config),
|
||||
: controller_(controller), runtime_(runtime), dali_domain_(dali_domain), config_(config),
|
||||
udp_lock_(xSemaphoreCreateMutex()) {}
|
||||
|
||||
esp_err_t GatewayNetworkService::start() {
|
||||
@@ -146,7 +189,12 @@ esp_err_t GatewayNetworkService::start() {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (config_.wifi_enabled) {
|
||||
if (config_.espnow_setup_startup_enabled) {
|
||||
err = startSetupAp();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
} else if (config_.wifi_enabled) {
|
||||
err = startWifi();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
@@ -158,9 +206,15 @@ esp_err_t GatewayNetworkService::start() {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = configureBootButton();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
controller_.addNotificationSink(
|
||||
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
|
||||
controller_.addWifiStateSink([this](uint8_t mode) { handleWifiControl(mode); });
|
||||
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
|
||||
|
||||
if (config_.http_enabled) {
|
||||
err = startHttpServer();
|
||||
@@ -176,6 +230,11 @@ esp_err_t GatewayNetworkService::start() {
|
||||
}
|
||||
}
|
||||
|
||||
err = startBootButtonTask();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "network service started http=%d udp=%d", config_.http_enabled,
|
||||
config_.udp_enabled);
|
||||
@@ -202,6 +261,7 @@ esp_err_t GatewayNetworkService::startWifi() {
|
||||
if (wifi_started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
stopEspNow();
|
||||
setup_ap_started_ = false;
|
||||
|
||||
if (wifi_sta_netif_ == nullptr) {
|
||||
@@ -299,6 +359,7 @@ esp_err_t GatewayNetworkService::startSetupAp() {
|
||||
}
|
||||
|
||||
if (wifi_started_) {
|
||||
stopEspNow();
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
}
|
||||
@@ -327,12 +388,106 @@ esp_err_t GatewayNetworkService::startSetupAp() {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (config_.espnow_setup_enabled) {
|
||||
err = startEspNow();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "setup AP started without ESP-NOW: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
wifi_started_ = true;
|
||||
setup_ap_started_ = true;
|
||||
ESP_LOGI(kTag, "setup AP started ssid=%s ip=192.168.3.1", kSetupApSsid);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startEspNow() {
|
||||
if (espnow_started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_now_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to init ESP-NOW: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
s_espnow_service = this;
|
||||
err = esp_now_register_recv_cb(&GatewayNetworkService::HandleEspNowReceive);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to register ESP-NOW RX callback: %s", esp_err_to_name(err));
|
||||
esp_now_deinit();
|
||||
s_espnow_service = nullptr;
|
||||
return err;
|
||||
}
|
||||
|
||||
err = addEspNowPeer(kEspNowBroadcastMac, true);
|
||||
if (err != ESP_OK) {
|
||||
esp_now_unregister_recv_cb();
|
||||
esp_now_deinit();
|
||||
s_espnow_service = nullptr;
|
||||
return err;
|
||||
}
|
||||
|
||||
espnow_connected_ = false;
|
||||
espnow_peer_.fill(0);
|
||||
espnow_started_ = true;
|
||||
ESP_LOGI(kTag, "ESP-NOW setup ingress started local_mac=%s", LocalMacHex(WIFI_IF_AP).c_str());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void GatewayNetworkService::stopEspNow() {
|
||||
if (!espnow_started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_now_unregister_recv_cb();
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_now_deinit());
|
||||
if (s_espnow_service == this) {
|
||||
s_espnow_service = nullptr;
|
||||
}
|
||||
espnow_connected_ = false;
|
||||
espnow_started_ = false;
|
||||
espnow_peer_.fill(0);
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::addEspNowPeer(const uint8_t* mac, bool broadcast) {
|
||||
if (mac == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (esp_now_is_peer_exist(mac)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_now_peer_info_t peer = {};
|
||||
std::memcpy(peer.peer_addr, mac, sizeof(peer.peer_addr));
|
||||
peer.channel = 0;
|
||||
peer.ifidx = setup_ap_started_ || broadcast ? WIFI_IF_AP : WIFI_IF_STA;
|
||||
peer.encrypt = false;
|
||||
esp_err_t err = esp_now_add_peer(&peer);
|
||||
if (err == ESP_ERR_ESPNOW_EXIST) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to add ESP-NOW peer %s: %s", MacToHex(mac).c_str(),
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::sendEspNowJson(const uint8_t* mac, const std::string& payload) {
|
||||
if (mac == nullptr || payload.empty()) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_err_t err = addEspNowPeer(mac, std::memcmp(mac, kEspNowBroadcastMac, 6) == 0);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
return esp_now_send(mac, reinterpret_cast<const uint8_t*>(payload.data()), payload.size());
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::configureStatusLed() {
|
||||
if (config_.status_led_gpio < 0) {
|
||||
return ESP_OK;
|
||||
@@ -354,6 +509,25 @@ esp_err_t GatewayNetworkService::configureStatusLed() {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::configureBootButton() {
|
||||
if (config_.boot_button_gpio < 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gpio_config_t io_config = {};
|
||||
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.boot_button_gpio);
|
||||
io_config.mode = GPIO_MODE_INPUT;
|
||||
io_config.pull_up_en = config_.boot_button_active_low ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
io_config.pull_down_en = config_.boot_button_active_low ? GPIO_PULLDOWN_DISABLE : GPIO_PULLDOWN_ENABLE;
|
||||
io_config.intr_type = GPIO_INTR_DISABLE;
|
||||
const esp_err_t err = gpio_config(&io_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure boot button GPIO%d: %s", config_.boot_button_gpio,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startHttpServer() {
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = config_.http_port;
|
||||
@@ -409,10 +583,31 @@ esp_err_t GatewayNetworkService::startUdpTask() {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startBootButtonTask() {
|
||||
if (config_.boot_button_gpio < 0 || boot_button_task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const BaseType_t created =
|
||||
xTaskCreate(&GatewayNetworkService::BootButtonTaskEntry, "gateway_boot_btn",
|
||||
config_.boot_button_task_stack_size, this, config_.boot_button_task_priority,
|
||||
&boot_button_task_handle_);
|
||||
if (created != pdPASS) {
|
||||
boot_button_task_handle_ = nullptr;
|
||||
ESP_LOGE(kTag, "failed to create boot button task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void GatewayNetworkService::UdpTaskEntry(void* arg) {
|
||||
static_cast<GatewayNetworkService*>(arg)->udpTaskLoop();
|
||||
}
|
||||
|
||||
void GatewayNetworkService::BootButtonTaskEntry(void* arg) {
|
||||
static_cast<GatewayNetworkService*>(arg)->bootButtonTaskLoop();
|
||||
}
|
||||
|
||||
void GatewayNetworkService::HandleWifiEvent(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(arg);
|
||||
@@ -421,6 +616,13 @@ void GatewayNetworkService::HandleWifiEvent(void* arg, esp_event_base_t event_ba
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::HandleEspNowReceive(const esp_now_recv_info_t* info,
|
||||
const uint8_t* data, int data_len) {
|
||||
if (s_espnow_service != nullptr) {
|
||||
s_espnow_service->handleEspNowReceive(info, data, data_len);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t event_id,
|
||||
void* event_data) {
|
||||
if (!config_.wifi_enabled) {
|
||||
@@ -459,6 +661,139 @@ void GatewayNetworkService::handleWifiEvent(esp_event_base_t event_base, int32_t
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleEspNowReceive(const esp_now_recv_info_t* info,
|
||||
const uint8_t* data, int data_len) {
|
||||
if (!espnow_started_ || info == nullptr || info->src_addr == nullptr || data == nullptr ||
|
||||
data_len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string payload(reinterpret_cast<const char*>(data),
|
||||
static_cast<size_t>(data_len));
|
||||
cJSON* root = cJSON_Parse(payload.c_str());
|
||||
if (root == nullptr) {
|
||||
ESP_LOGW(kTag, "ignored non-JSON ESP-NOW setup packet len=%d", data_len);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* type = JsonString(root, "type");
|
||||
if (type == nullptr) {
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
addEspNowPeer(info->src_addr);
|
||||
const std::string local_mac = LocalMacHex(WIFI_IF_AP);
|
||||
|
||||
if (std::strcmp(type, "connReq") == 0) {
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response != nullptr) {
|
||||
cJSON_AddStringToObject(response, "type", "connRsp");
|
||||
cJSON_AddStringToObject(response, "data", "");
|
||||
cJSON_AddStringToObject(response, "dst", MacToHex(info->src_addr).c_str());
|
||||
cJSON_AddStringToObject(response, "src", local_mac.c_str());
|
||||
cJSON_AddStringToObject(response, "pmk", "");
|
||||
const std::string rendered = PrintJson(response);
|
||||
sendEspNowJson(kEspNowBroadcastMac, rendered);
|
||||
cJSON_Delete(response);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(type, "connAck") == 0) {
|
||||
if (!espnow_connected_) {
|
||||
std::memcpy(espnow_peer_.data(), info->src_addr, espnow_peer_.size());
|
||||
espnow_connected_ = true;
|
||||
ESP_LOGI(kTag, "ESP-NOW setup peer connected mac=%s", MacToHex(info->src_addr).c_str());
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(type, "echo") == 0) {
|
||||
if (espnow_connected_) {
|
||||
cJSON* response = cJSON_CreateObject();
|
||||
if (response != nullptr) {
|
||||
cJSON_AddStringToObject(response, "type", "echoRsp");
|
||||
cJSON_AddStringToObject(response, "data", "");
|
||||
const std::string rendered = PrintJson(response);
|
||||
sendEspNowJson(info->src_addr, rendered);
|
||||
cJSON_Delete(response);
|
||||
}
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(type, "cmd") == 0 || std::strcmp(type, "data") == 0) {
|
||||
const auto frame = BytesFromJsonString(JsonString(root, "data"));
|
||||
if (!frame.empty()) {
|
||||
controller_.enqueueCommandFrame(frame);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::strcmp(type, "uart") == 0) {
|
||||
cJSON* num = cJSON_GetObjectItem(root, "num");
|
||||
const auto frame = BytesFromJsonString(JsonString(root, "data"));
|
||||
if (cJSON_IsNumber(num) && !frame.empty()) {
|
||||
handleSetupUartFrame(num->valueint, frame);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleSetupUartFrame(int setup_id,
|
||||
const std::vector<uint8_t>& frame) {
|
||||
if (frame.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.size() >= 7) {
|
||||
controller_.enqueueCommandFrame(frame);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t channel_index = static_cast<uint8_t>(setup_id - 3);
|
||||
for (const auto& channel : dali_domain_.channelInfo()) {
|
||||
if (channel.channel_index == channel_index) {
|
||||
if (!dali_domain_.writeBridgeFrame(channel.gateway_id, frame.data(), frame.size())) {
|
||||
ESP_LOGW(kTag, "failed to forward ESP-NOW setup UART%d frame to gateway=%u", setup_id,
|
||||
channel.gateway_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGW(kTag, "ignored setup UART%d frame for unbound DALI channel", setup_id);
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
if (!espnow_started_ || !espnow_connected_ || frame.data.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cJSON* payload = cJSON_CreateObject();
|
||||
if (payload == nullptr) {
|
||||
return;
|
||||
}
|
||||
cJSON_AddStringToObject(payload, "type", "uart");
|
||||
cJSON_AddNumberToObject(payload, "num", static_cast<double>(frame.channel_index + 3));
|
||||
const std::string data_hex = BytesToHex(frame.data);
|
||||
cJSON_AddStringToObject(payload, "data", data_hex.c_str());
|
||||
|
||||
const std::string rendered = PrintJson(payload);
|
||||
cJSON_Delete(payload);
|
||||
if (!rendered.empty()) {
|
||||
sendEspNowJson(espnow_peer_.data(), rendered);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::udpTaskLoop() {
|
||||
udp_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (udp_socket_ < 0) {
|
||||
@@ -504,6 +839,47 @@ void GatewayNetworkService::udpTaskLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::bootButtonTaskLoop() {
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
const TickType_t poll_ticks = pdMS_TO_TICKS(100);
|
||||
const uint32_t long_press_ms = std::max<uint32_t>(config_.boot_button_long_press_ms, 100);
|
||||
|
||||
auto is_pressed = [this]() {
|
||||
const int level = gpio_get_level(static_cast<gpio_num_t>(config_.boot_button_gpio));
|
||||
return config_.boot_button_active_low ? level == 0 : level != 0;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
if (!is_pressed()) {
|
||||
vTaskDelay(poll_ticks);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t pressed_ms = 0;
|
||||
while (is_pressed()) {
|
||||
vTaskDelay(poll_ticks);
|
||||
pressed_ms += 100;
|
||||
}
|
||||
|
||||
if (pressed_ms >= long_press_ms) {
|
||||
ESP_LOGW(kTag, "BOOT long press clears Wi-Fi credentials and restarts");
|
||||
runtime_.clearWirelessInfo();
|
||||
stopEspNow();
|
||||
if (wifi_started_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
esp_restart();
|
||||
} else {
|
||||
ESP_LOGI(kTag, "BOOT short press enters setup AP mode");
|
||||
handleWifiControl(101);
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
||||
if (!config_.udp_enabled || udp_socket_ < 0 || frame.empty()) {
|
||||
return;
|
||||
@@ -527,6 +903,7 @@ void GatewayNetworkService::handleGatewayNotification(const std::vector<uint8_t>
|
||||
void GatewayNetworkService::handleWifiControl(uint8_t mode) {
|
||||
if (mode == 0) {
|
||||
config_.wifi_enabled = false;
|
||||
stopEspNow();
|
||||
if (wifi_started_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
@@ -549,6 +926,7 @@ void GatewayNetworkService::handleWifiControl(uint8_t mode) {
|
||||
if (mode == 1) {
|
||||
config_.wifi_enabled = true;
|
||||
if (setup_ap_started_) {
|
||||
stopEspNow();
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
wifi_started_ = false;
|
||||
setup_ap_started_ = false;
|
||||
|
||||
@@ -33,6 +33,7 @@ struct GatewayRuntimeConfig {
|
||||
std::string_view project_name;
|
||||
std::string_view version;
|
||||
std::string serial_id;
|
||||
bool default_ble_enabled{true};
|
||||
size_t command_queue_capacity{16};
|
||||
};
|
||||
|
||||
@@ -60,6 +61,7 @@ class GatewaySettingsStore {
|
||||
std::optional<std::string> getWifiSsid() const;
|
||||
std::optional<std::string> getWifiPassword() const;
|
||||
bool setWifiCredentials(std::string_view ssid, std::string_view password);
|
||||
bool clearWifiCredentials();
|
||||
|
||||
std::string getGatewayName(uint8_t gateway_id, std::string_view fallback) const;
|
||||
bool setGatewayName(uint8_t gateway_id, std::string_view name);
|
||||
@@ -99,6 +101,7 @@ class GatewayRuntime {
|
||||
|
||||
void setGatewayCount(size_t gateway_count);
|
||||
void setWirelessInfo(WirelessInfo info);
|
||||
bool clearWirelessInfo();
|
||||
void setCommandAddressResolver(std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver);
|
||||
|
||||
GatewayDeviceInfo deviceInfo() const;
|
||||
|
||||
@@ -122,6 +122,22 @@ bool GatewaySettingsStore::setWifiCredentials(std::string_view ssid,
|
||||
return writeString(kWifiSsidKey, ssid) && writeString(kWifiPasswordKey, password);
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::clearWifiCredentials() {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ssid_err = nvs_erase_key(handle_, kWifiSsidKey);
|
||||
esp_err_t password_err = nvs_erase_key(handle_, kWifiPasswordKey);
|
||||
if (ssid_err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
ssid_err = ESP_OK;
|
||||
}
|
||||
if (password_err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
password_err = ESP_OK;
|
||||
}
|
||||
return ssid_err == ESP_OK && password_err == ESP_OK && nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::getGatewayName(uint8_t gateway_id,
|
||||
std::string_view fallback) const {
|
||||
const auto value = readString(makeGatewayNameKey(gateway_id));
|
||||
@@ -193,7 +209,7 @@ esp_err_t GatewayRuntime::start() {
|
||||
return err;
|
||||
}
|
||||
|
||||
ble_enabled_ = settings_.getBleEnabled(profile_.enable_ble);
|
||||
ble_enabled_ = settings_.getBleEnabled(config_.default_ble_enabled);
|
||||
|
||||
if (!wireless_info_.has_value()) {
|
||||
WirelessInfo info;
|
||||
@@ -331,6 +347,14 @@ void GatewayRuntime::setWirelessInfo(WirelessInfo info) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayRuntime::clearWirelessInfo() {
|
||||
{
|
||||
LockGuard guard(command_lock_);
|
||||
wireless_info_.reset();
|
||||
}
|
||||
return settings_.clearWifiCredentials();
|
||||
}
|
||||
|
||||
void GatewayRuntime::setCommandAddressResolver(
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
|
||||
LockGuard guard(command_lock_);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_usb_setup.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_domain esp_driver_usb_serial_jtag freertos gateway_controller log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "dali_domain.hpp"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class GatewayController;
|
||||
|
||||
struct GatewayUsbSetupBridgeConfig {
|
||||
bool enabled{false};
|
||||
uint8_t channel_index{0};
|
||||
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 GatewayUsbSetupBridge {
|
||||
public:
|
||||
GatewayUsbSetupBridge(GatewayController& controller, DaliDomainService& dali_domain,
|
||||
GatewayUsbSetupBridgeConfig config = {});
|
||||
|
||||
esp_err_t start();
|
||||
|
||||
private:
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void handleBytes(const uint8_t* data, size_t len);
|
||||
void handleRawFrame(const DaliRawFrame& frame);
|
||||
uint8_t setupGatewayId() const;
|
||||
|
||||
GatewayController& controller_;
|
||||
DaliDomainService& dali_domain_;
|
||||
GatewayUsbSetupBridgeConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
bool started_{false};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "gateway_usb_setup.hpp"
|
||||
|
||||
#include "gateway_controller.hpp"
|
||||
|
||||
#include "driver/usb_serial_jtag.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace gateway {
|
||||
|
||||
namespace {
|
||||
constexpr const char* kTag = "gateway_usb";
|
||||
constexpr size_t kCommandFrameMinLen = 7;
|
||||
}
|
||||
|
||||
GatewayUsbSetupBridge::GatewayUsbSetupBridge(GatewayController& controller,
|
||||
DaliDomainService& dali_domain,
|
||||
GatewayUsbSetupBridgeConfig config)
|
||||
: controller_(controller), dali_domain_(dali_domain), config_(config) {}
|
||||
|
||||
esp_err_t GatewayUsbSetupBridge::start() {
|
||||
if (started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (!config_.enabled) {
|
||||
ESP_LOGI(kTag, "USB Serial/JTAG setup bridge disabled; USB remains available for debug");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
if (!usb_serial_jtag_is_driver_installed()) {
|
||||
usb_serial_jtag_driver_config_t driver_config = {};
|
||||
driver_config.rx_buffer_size = static_cast<int>(config_.rx_buffer_size);
|
||||
driver_config.tx_buffer_size = static_cast<int>(config_.tx_buffer_size);
|
||||
esp_err_t err = usb_serial_jtag_driver_install(&driver_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to install USB Serial/JTAG driver: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleRawFrame(frame); });
|
||||
|
||||
const BaseType_t ok = xTaskCreate(&GatewayUsbSetupBridge::TaskEntry, "gateway_usb_setup",
|
||||
static_cast<uint32_t>(config_.task_stack_size), this,
|
||||
config_.task_priority, &task_handle_);
|
||||
if (ok != pdPASS) {
|
||||
ESP_LOGE(kTag, "failed to create USB setup task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "USB Serial/JTAG setup bridge started channel=%u", config_.channel_index);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void GatewayUsbSetupBridge::TaskEntry(void* arg) {
|
||||
auto* self = static_cast<GatewayUsbSetupBridge*>(arg);
|
||||
self->taskLoop();
|
||||
}
|
||||
|
||||
void GatewayUsbSetupBridge::taskLoop() {
|
||||
std::vector<uint8_t> buffer(std::max<size_t>(config_.rx_buffer_size, 64));
|
||||
const TickType_t timeout = pdMS_TO_TICKS(config_.read_timeout_ms);
|
||||
|
||||
while (true) {
|
||||
const int read_len = usb_serial_jtag_read_bytes(buffer.data(), buffer.size(), timeout);
|
||||
if (read_len > 0) {
|
||||
handleBytes(buffer.data(), static_cast<size_t>(read_len));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayUsbSetupBridge::handleBytes(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (len >= kCommandFrameMinLen) {
|
||||
controller_.enqueueCommandFrame(std::vector<uint8_t>(data, data + len));
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t gateway_id = setupGatewayId();
|
||||
if (!dali_domain_.writeBridgeFrame(gateway_id, data, len)) {
|
||||
ESP_LOGW(kTag, "failed to write USB raw setup frame channel=%u len=%u", config_.channel_index,
|
||||
static_cast<unsigned>(len));
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayUsbSetupBridge::handleRawFrame(const DaliRawFrame& frame) {
|
||||
if (!config_.enabled || frame.channel_index != config_.channel_index || frame.data.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int written = usb_serial_jtag_write_bytes(frame.data.data(), frame.data.size(),
|
||||
pdMS_TO_TICKS(config_.write_timeout_ms));
|
||||
if (written < 0 || static_cast<size_t>(written) != frame.data.size()) {
|
||||
ESP_LOGW(kTag, "failed to forward USB raw setup frame channel=%u len=%u", frame.channel_index,
|
||||
static_cast<unsigned>(frame.data.size()));
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t GatewayUsbSetupBridge::setupGatewayId() const {
|
||||
for (const auto& channel : dali_domain_.channelInfo()) {
|
||||
if (channel.channel_index == config_.channel_index) {
|
||||
return channel.gateway_id;
|
||||
}
|
||||
}
|
||||
return config_.channel_index;
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
Reference in New Issue
Block a user