feat(gateway): add GatewayNetworkService and enhance runtime channel handling
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -12,8 +12,9 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
|||||||
- `dali_domain/`: native DALI domain facade over `dali_cpp`.
|
- `dali_domain/`: native DALI domain facade over `dali_cpp`.
|
||||||
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`.
|
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`.
|
||||||
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
|
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
|
||||||
|
- `gateway_network/`: initial HTTP `/info` and `/dali/cmd` plus UDP port `2020` control-plane ingress for the native gateway.
|
||||||
- `gateway_runtime/`: persistent runtime state, command queueing, and device info services.
|
- `gateway_runtime/`: persistent runtime state, command queueing, and device info services.
|
||||||
|
|
||||||
## Current status
|
## Current status
|
||||||
|
|
||||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port, 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 now also includes an initial `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. 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 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 now also includes an initial `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 an initial `gateway_network` service that starts the native HTTP `/info` and `POST /dali/cmd` surfaces plus the UDP control-plane router on port `2020`. 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.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "app_main.cpp"
|
SRCS "app_main.cpp"
|
||||||
REQUIRES gateway_core gateway_controller dali_domain gateway_runtime gateway_ble log
|
REQUIRES gateway_core gateway_controller gateway_network dali_domain gateway_runtime gateway_ble log
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||||
@@ -98,6 +98,18 @@ config GATEWAY_CHANNEL1_SERIAL_RX_BUFFER
|
|||||||
range 128 4096
|
range 128 4096
|
||||||
default 512
|
default 512
|
||||||
|
|
||||||
|
config GATEWAY_CHANNEL1_SERIAL_TX_BUFFER
|
||||||
|
int "Serial PHY TX buffer bytes"
|
||||||
|
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
|
||||||
|
range 0 4096
|
||||||
|
default 512
|
||||||
|
|
||||||
|
config GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS
|
||||||
|
int "Serial PHY query timeout ms"
|
||||||
|
depends on GATEWAY_CHANNEL1_PHY_UART1 || GATEWAY_CHANNEL1_PHY_UART2
|
||||||
|
range 10 5000
|
||||||
|
default 500
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
menu "Gateway Channel 2"
|
menu "Gateway Channel 2"
|
||||||
@@ -193,6 +205,18 @@ config GATEWAY_CHANNEL2_SERIAL_RX_BUFFER
|
|||||||
range 128 4096
|
range 128 4096
|
||||||
default 512
|
default 512
|
||||||
|
|
||||||
|
config GATEWAY_CHANNEL2_SERIAL_TX_BUFFER
|
||||||
|
int "Serial PHY TX buffer bytes"
|
||||||
|
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
|
||||||
|
range 0 4096
|
||||||
|
default 512
|
||||||
|
|
||||||
|
config GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS
|
||||||
|
int "Serial PHY query timeout ms"
|
||||||
|
depends on GATEWAY_CHANNEL_COUNT >= 2 && (GATEWAY_CHANNEL2_PHY_UART1 || GATEWAY_CHANNEL2_PHY_UART2)
|
||||||
|
range 10 5000
|
||||||
|
default 500
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
config GATEWAY_ENABLE_DALI_BUS
|
config GATEWAY_ENABLE_DALI_BUS
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
#include "gateway_ble.hpp"
|
#include "gateway_ble.hpp"
|
||||||
#include "gateway_controller.hpp"
|
#include "gateway_controller.hpp"
|
||||||
#include "gateway_core.hpp"
|
#include "gateway_core.hpp"
|
||||||
|
#include "gateway_network.hpp"
|
||||||
#include "gateway_runtime.hpp"
|
#include "gateway_runtime.hpp"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#ifndef CONFIG_GATEWAY_CHANNEL_COUNT
|
#ifndef CONFIG_GATEWAY_CHANNEL_COUNT
|
||||||
@@ -16,10 +19,12 @@
|
|||||||
namespace {
|
namespace {
|
||||||
constexpr const char* kProjectName = "DALI_485_Gateway";
|
constexpr const char* kProjectName = "DALI_485_Gateway";
|
||||||
constexpr const char* kProjectVersion = "0.1.0";
|
constexpr const char* kProjectVersion = "0.1.0";
|
||||||
|
constexpr const char* kTag = "gateway_main";
|
||||||
|
|
||||||
std::unique_ptr<gateway::DaliDomainService> s_dali_domain;
|
std::unique_ptr<gateway::DaliDomainService> s_dali_domain;
|
||||||
std::unique_ptr<gateway::GatewayRuntime> s_runtime;
|
std::unique_ptr<gateway::GatewayRuntime> s_runtime;
|
||||||
std::unique_ptr<gateway::GatewayController> s_controller;
|
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::GatewayBleBridge> s_ble_bridge;
|
||||||
|
|
||||||
[[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) {
|
[[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) {
|
||||||
@@ -28,8 +33,111 @@ std::unique_ptr<gateway::GatewayBleBridge> s_ble_bridge;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
struct ChannelBindingConfig {
|
||||||
const gateway::GatewayRuntime& runtime) {
|
bool enabled{false};
|
||||||
|
bool native_phy{false};
|
||||||
|
bool serial_phy{false};
|
||||||
|
uint8_t gateway_id{0};
|
||||||
|
uint8_t native_bus_id{0};
|
||||||
|
uint32_t native_baudrate{0};
|
||||||
|
int uart_port{-1};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ValidateChannelBindings() {
|
||||||
|
ChannelBindingConfig channels[CONFIG_GATEWAY_CHANNEL_COUNT] = {};
|
||||||
|
|
||||||
|
#if CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE
|
||||||
|
channels[0].enabled = true;
|
||||||
|
channels[0].native_phy = true;
|
||||||
|
channels[0].gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_GW_ID);
|
||||||
|
channels[0].native_bus_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_BUS_ID);
|
||||||
|
channels[0].native_baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE);
|
||||||
|
#elif CONFIG_GATEWAY_CHANNEL1_PHY_UART1
|
||||||
|
channels[0].enabled = true;
|
||||||
|
channels[0].serial_phy = true;
|
||||||
|
channels[0].gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_GW_ID);
|
||||||
|
channels[0].uart_port = 1;
|
||||||
|
#elif CONFIG_GATEWAY_CHANNEL1_PHY_UART2
|
||||||
|
channels[0].enabled = true;
|
||||||
|
channels[0].serial_phy = true;
|
||||||
|
channels[0].gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_GW_ID);
|
||||||
|
channels[0].uart_port = 2;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_GATEWAY_CHANNEL_COUNT >= 2
|
||||||
|
#if CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE
|
||||||
|
channels[1].enabled = true;
|
||||||
|
channels[1].native_phy = true;
|
||||||
|
channels[1].gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_GW_ID);
|
||||||
|
channels[1].native_bus_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_BUS_ID);
|
||||||
|
channels[1].native_baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE);
|
||||||
|
#elif CONFIG_GATEWAY_CHANNEL2_PHY_UART1
|
||||||
|
channels[1].enabled = true;
|
||||||
|
channels[1].serial_phy = true;
|
||||||
|
channels[1].gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_GW_ID);
|
||||||
|
channels[1].uart_port = 1;
|
||||||
|
#elif CONFIG_GATEWAY_CHANNEL2_PHY_UART2
|
||||||
|
channels[1].enabled = true;
|
||||||
|
channels[1].serial_phy = true;
|
||||||
|
channels[1].gateway_id = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_GW_ID);
|
||||||
|
channels[1].uart_port = 2;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool any_enabled = false;
|
||||||
|
bool saw_native = false;
|
||||||
|
uint32_t native_baudrate = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < CONFIG_GATEWAY_CHANNEL_COUNT; ++i) {
|
||||||
|
if (!channels[i].enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
any_enabled = true;
|
||||||
|
for (int j = i + 1; j < CONFIG_GATEWAY_CHANNEL_COUNT; ++j) {
|
||||||
|
if (!channels[j].enabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (channels[i].gateway_id == channels[j].gateway_id) {
|
||||||
|
ESP_LOGE(kTag, "duplicate gateway ids configured: %u", channels[i].gateway_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (channels[i].serial_phy && channels[j].serial_phy &&
|
||||||
|
channels[i].uart_port == channels[j].uart_port) {
|
||||||
|
ESP_LOGE(kTag, "duplicate serial PHY UART%d configured for multiple channels",
|
||||||
|
channels[i].uart_port);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (channels[i].native_phy && channels[j].native_phy &&
|
||||||
|
channels[i].native_bus_id == channels[j].native_bus_id) {
|
||||||
|
ESP_LOGE(kTag, "duplicate native DALI bus ids configured: %u",
|
||||||
|
channels[i].native_bus_id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (channels[i].native_phy) {
|
||||||
|
if (!saw_native) {
|
||||||
|
saw_native = true;
|
||||||
|
native_baudrate = channels[i].native_baudrate;
|
||||||
|
} else if (native_baudrate != channels[i].native_baudrate) {
|
||||||
|
ESP_LOGE(kTag,
|
||||||
|
"mixed native PHY baudrates are not supported by the shared DALI HAL: %lu vs %lu",
|
||||||
|
static_cast<unsigned long>(native_baudrate),
|
||||||
|
static_cast<unsigned long>(channels[i].native_baudrate));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!any_enabled) {
|
||||||
|
ESP_LOGE(kTag, "no DALI PHY is configured; enable at least one native or serial channel");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||||
|
const gateway::GatewayRuntime& runtime) {
|
||||||
#if CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE
|
#if CONFIG_GATEWAY_CHANNEL1_PHY_NATIVE
|
||||||
gateway::DaliHardwareBusConfig channel1{};
|
gateway::DaliHardwareBusConfig channel1{};
|
||||||
channel1.channel_index = 0;
|
channel1.channel_index = 0;
|
||||||
@@ -39,7 +147,11 @@ void BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
|||||||
channel1.rx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN);
|
channel1.rx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_RX_PIN);
|
||||||
channel1.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE);
|
channel1.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE);
|
||||||
channel1.name = runtime.gatewayName(channel1.gateway_id);
|
channel1.name = runtime.gatewayName(channel1.gateway_id);
|
||||||
LogBindError("channel1 native DALI", dali_domain.bindHardwareBus(channel1));
|
esp_err_t err = dali_domain.bindHardwareBus(channel1);
|
||||||
|
LogBindError("channel1 native DALI", err);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
#elif CONFIG_GATEWAY_CHANNEL1_PHY_UART1 || CONFIG_GATEWAY_CHANNEL1_PHY_UART2
|
#elif CONFIG_GATEWAY_CHANNEL1_PHY_UART1 || CONFIG_GATEWAY_CHANNEL1_PHY_UART2
|
||||||
gateway::DaliSerialBusConfig channel1{};
|
gateway::DaliSerialBusConfig channel1{};
|
||||||
channel1.channel_index = 0;
|
channel1.channel_index = 0;
|
||||||
@@ -53,9 +165,15 @@ void BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
|||||||
channel1.rx_pin = CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_PIN;
|
channel1.rx_pin = CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_PIN;
|
||||||
channel1.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_BAUDRATE);
|
channel1.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_BAUDRATE);
|
||||||
channel1.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER);
|
channel1.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER);
|
||||||
channel1.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_RX_BUFFER);
|
channel1.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_TX_BUFFER);
|
||||||
|
channel1.query_timeout_ms =
|
||||||
|
static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL1_SERIAL_QUERY_TIMEOUT_MS);
|
||||||
channel1.name = runtime.gatewayName(channel1.gateway_id);
|
channel1.name = runtime.gatewayName(channel1.gateway_id);
|
||||||
LogBindError("channel1 serial DALI", dali_domain.bindSerialBus(channel1));
|
esp_err_t err = dali_domain.bindSerialBus(channel1);
|
||||||
|
LogBindError("channel1 serial DALI", err);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if CONFIG_GATEWAY_CHANNEL_COUNT >= 2
|
#if CONFIG_GATEWAY_CHANNEL_COUNT >= 2
|
||||||
@@ -68,7 +186,11 @@ void BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
|||||||
channel2.rx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN);
|
channel2.rx_pin = static_cast<uint8_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN);
|
||||||
channel2.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE);
|
channel2.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE);
|
||||||
channel2.name = runtime.gatewayName(channel2.gateway_id);
|
channel2.name = runtime.gatewayName(channel2.gateway_id);
|
||||||
LogBindError("channel2 native DALI", dali_domain.bindHardwareBus(channel2));
|
esp_err_t err = dali_domain.bindHardwareBus(channel2);
|
||||||
|
LogBindError("channel2 native DALI", err);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
#elif CONFIG_GATEWAY_CHANNEL2_PHY_UART1 || CONFIG_GATEWAY_CHANNEL2_PHY_UART2
|
#elif CONFIG_GATEWAY_CHANNEL2_PHY_UART1 || CONFIG_GATEWAY_CHANNEL2_PHY_UART2
|
||||||
gateway::DaliSerialBusConfig channel2{};
|
gateway::DaliSerialBusConfig channel2{};
|
||||||
channel2.channel_index = 1;
|
channel2.channel_index = 1;
|
||||||
@@ -82,11 +204,19 @@ void BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
|||||||
channel2.rx_pin = CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_PIN;
|
channel2.rx_pin = CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_PIN;
|
||||||
channel2.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_BAUDRATE);
|
channel2.baudrate = static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_BAUDRATE);
|
||||||
channel2.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER);
|
channel2.rx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER);
|
||||||
channel2.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_RX_BUFFER);
|
channel2.tx_buffer_size = static_cast<size_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_TX_BUFFER);
|
||||||
|
channel2.query_timeout_ms =
|
||||||
|
static_cast<uint32_t>(CONFIG_GATEWAY_CHANNEL2_SERIAL_QUERY_TIMEOUT_MS);
|
||||||
channel2.name = runtime.gatewayName(channel2.gateway_id);
|
channel2.name = runtime.gatewayName(channel2.gateway_id);
|
||||||
LogBindError("channel2 serial DALI", dali_domain.bindSerialBus(channel2));
|
esp_err_t err = dali_domain.bindSerialBus(channel2);
|
||||||
|
LogBindError("channel2 serial DALI", err);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -107,6 +237,8 @@ extern "C" void app_main(void) {
|
|||||||
gateway::GatewayCore core(profile);
|
gateway::GatewayCore core(profile);
|
||||||
core.start();
|
core.start();
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(ValidateChannelBindings() ? ESP_OK : ESP_ERR_INVALID_STATE);
|
||||||
|
|
||||||
s_dali_domain = std::make_unique<gateway::DaliDomainService>();
|
s_dali_domain = std::make_unique<gateway::DaliDomainService>();
|
||||||
s_runtime = std::make_unique<gateway::GatewayRuntime>(
|
s_runtime = std::make_unique<gateway::GatewayRuntime>(
|
||||||
profile,
|
profile,
|
||||||
@@ -118,7 +250,7 @@ extern "C" void app_main(void) {
|
|||||||
s_dali_domain.get());
|
s_dali_domain.get());
|
||||||
ESP_ERROR_CHECK(s_runtime->start());
|
ESP_ERROR_CHECK(s_runtime->start());
|
||||||
s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT);
|
s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT);
|
||||||
BindConfiguredChannels(*s_dali_domain, *s_runtime);
|
ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime));
|
||||||
|
|
||||||
gateway::GatewayControllerConfig controller_config;
|
gateway::GatewayControllerConfig controller_config;
|
||||||
controller_config.setup_supported = true;
|
controller_config.setup_supported = true;
|
||||||
@@ -132,6 +264,15 @@ extern "C" void app_main(void) {
|
|||||||
controller_config);
|
controller_config);
|
||||||
ESP_ERROR_CHECK(s_controller->start());
|
ESP_ERROR_CHECK(s_controller->start());
|
||||||
|
|
||||||
|
if (profile.enable_wifi || profile.enable_eth) {
|
||||||
|
gateway::GatewayNetworkServiceConfig network_config;
|
||||||
|
network_config.http_enabled = true;
|
||||||
|
network_config.udp_enabled = true;
|
||||||
|
s_network = std::make_unique<gateway::GatewayNetworkService>(*s_controller, *s_runtime,
|
||||||
|
network_config);
|
||||||
|
ESP_ERROR_CHECK(s_network->start());
|
||||||
|
}
|
||||||
|
|
||||||
if (profile.enable_ble) {
|
if (profile.enable_ble) {
|
||||||
s_ble_bridge = std::make_unique<gateway::GatewayBleBridge>(*s_controller, *s_runtime,
|
s_ble_bridge = std::make_unique<gateway::GatewayBleBridge>(*s_controller, *s_runtime,
|
||||||
*s_dali_domain);
|
*s_dali_domain);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "src/gateway_network.cpp"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES esp_event esp_http_server esp_netif freertos gateway_controller gateway_runtime log lwip espressif__cjson
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_http_server.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
|
||||||
|
namespace gateway {
|
||||||
|
|
||||||
|
class GatewayController;
|
||||||
|
class GatewayRuntime;
|
||||||
|
|
||||||
|
struct GatewayNetworkServiceConfig {
|
||||||
|
bool http_enabled{true};
|
||||||
|
bool udp_enabled{true};
|
||||||
|
uint16_t http_port{80};
|
||||||
|
uint16_t udp_port{2020};
|
||||||
|
uint32_t udp_task_stack_size{4096};
|
||||||
|
UBaseType_t udp_task_priority{4};
|
||||||
|
};
|
||||||
|
|
||||||
|
class GatewayNetworkService {
|
||||||
|
public:
|
||||||
|
GatewayNetworkService(GatewayController& controller, GatewayRuntime& runtime,
|
||||||
|
GatewayNetworkServiceConfig config = {});
|
||||||
|
|
||||||
|
esp_err_t start();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void UdpTaskEntry(void* arg);
|
||||||
|
static esp_err_t HandleInfoGet(httpd_req_t* req);
|
||||||
|
static esp_err_t HandleCommandPost(httpd_req_t* req);
|
||||||
|
|
||||||
|
esp_err_t ensureNetworkStack();
|
||||||
|
esp_err_t startHttpServer();
|
||||||
|
esp_err_t startUdpTask();
|
||||||
|
void udpTaskLoop();
|
||||||
|
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||||
|
std::string deviceInfoJson() const;
|
||||||
|
std::string deviceInfoDoubleEncodedJson() const;
|
||||||
|
|
||||||
|
GatewayController& controller_;
|
||||||
|
GatewayRuntime& runtime_;
|
||||||
|
GatewayNetworkServiceConfig config_;
|
||||||
|
bool started_{false};
|
||||||
|
httpd_handle_t http_server_{nullptr};
|
||||||
|
TaskHandle_t udp_task_handle_{nullptr};
|
||||||
|
int udp_socket_{-1};
|
||||||
|
SemaphoreHandle_t udp_lock_{nullptr};
|
||||||
|
bool has_udp_remote_{false};
|
||||||
|
sockaddr_storage udp_remote_addr_{};
|
||||||
|
socklen_t udp_remote_addr_len_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gateway
|
||||||
@@ -0,0 +1,379 @@
|
|||||||
|
#include "gateway_network.hpp"
|
||||||
|
|
||||||
|
#include "gateway_controller.hpp"
|
||||||
|
#include "gateway_runtime.hpp"
|
||||||
|
|
||||||
|
#include "cJSON.h"
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_netif.h"
|
||||||
|
#include "lwip/inet.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace gateway {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kTag = "gateway_network";
|
||||||
|
constexpr size_t kUdpBufferSize = 256;
|
||||||
|
|
||||||
|
class LockGuard {
|
||||||
|
public:
|
||||||
|
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
|
||||||
|
if (lock_ != nullptr) {
|
||||||
|
xSemaphoreTake(lock_, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~LockGuard() {
|
||||||
|
if (lock_ != nullptr) {
|
||||||
|
xSemaphoreGive(lock_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t lock_;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool HexValue(char ch, uint8_t& value) {
|
||||||
|
if (ch >= '0' && ch <= '9') {
|
||||||
|
value = static_cast<uint8_t>(ch - '0');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ch >= 'a' && ch <= 'f') {
|
||||||
|
value = static_cast<uint8_t>(10 + ch - 'a');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ch >= 'A' && ch <= 'F') {
|
||||||
|
value = static_cast<uint8_t>(10 + ch - 'A');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> DecodeHex(std::string_view hex) {
|
||||||
|
if ((hex.size() & 1U) != 0U) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
bytes.reserve(hex.size() / 2);
|
||||||
|
for (size_t i = 0; i < hex.size(); i += 2) {
|
||||||
|
uint8_t high = 0;
|
||||||
|
uint8_t low = 0;
|
||||||
|
if (!HexValue(hex[i], high) || !HexValue(hex[i + 1], low)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
bytes.push_back(static_cast<uint8_t>((high << 4) | low));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PrintJson(cJSON* node) {
|
||||||
|
if (node == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
char* rendered = cJSON_PrintUnformatted(node);
|
||||||
|
if (rendered == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::string out(rendered);
|
||||||
|
cJSON_free(rendered);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t ReadRequestBody(httpd_req_t* req, std::string& body) {
|
||||||
|
body.clear();
|
||||||
|
if (req == nullptr) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.resize(static_cast<size_t>(req->content_len));
|
||||||
|
int received = 0;
|
||||||
|
while (received < req->content_len) {
|
||||||
|
const int ret = httpd_req_recv(req, body.data() + received, req->content_len - received);
|
||||||
|
if (ret <= 0) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
received += ret;
|
||||||
|
}
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
GatewayNetworkService::GatewayNetworkService(GatewayController& controller,
|
||||||
|
GatewayRuntime& runtime,
|
||||||
|
GatewayNetworkServiceConfig config)
|
||||||
|
: controller_(controller), runtime_(runtime), config_(config),
|
||||||
|
udp_lock_(xSemaphoreCreateMutex()) {}
|
||||||
|
|
||||||
|
esp_err_t GatewayNetworkService::start() {
|
||||||
|
if (started_) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t err = ensureNetworkStack();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller_.addNotificationSink(
|
||||||
|
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
|
||||||
|
|
||||||
|
if (config_.http_enabled) {
|
||||||
|
err = startHttpServer();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config_.udp_enabled) {
|
||||||
|
err = startUdpTask();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
started_ = true;
|
||||||
|
ESP_LOGI(kTag, "network service started http=%d udp=%d", config_.http_enabled,
|
||||||
|
config_.udp_enabled);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t GatewayNetworkService::ensureNetworkStack() {
|
||||||
|
esp_err_t err = esp_netif_init();
|
||||||
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||||
|
ESP_LOGE(kTag, "failed to init esp_netif: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = esp_event_loop_create_default();
|
||||||
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
||||||
|
ESP_LOGE(kTag, "failed to create default event loop: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t GatewayNetworkService::startHttpServer() {
|
||||||
|
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||||
|
config.server_port = config_.http_port;
|
||||||
|
config.lru_purge_enable = true;
|
||||||
|
|
||||||
|
esp_err_t err = httpd_start(&http_server_, &config);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "failed to start HTTP server: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_uri_t info_uri = {};
|
||||||
|
info_uri.uri = "/info";
|
||||||
|
info_uri.method = HTTP_GET;
|
||||||
|
info_uri.handler = &GatewayNetworkService::HandleInfoGet;
|
||||||
|
info_uri.user_ctx = this;
|
||||||
|
err = httpd_register_uri_handler(http_server_, &info_uri);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "failed to register /info handler: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpd_uri_t command_uri = {};
|
||||||
|
command_uri.uri = "/dali/cmd";
|
||||||
|
command_uri.method = HTTP_POST;
|
||||||
|
command_uri.handler = &GatewayNetworkService::HandleCommandPost;
|
||||||
|
command_uri.user_ctx = this;
|
||||||
|
err = httpd_register_uri_handler(http_server_, &command_uri);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "failed to register /dali/cmd handler: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t GatewayNetworkService::startUdpTask() {
|
||||||
|
if (udp_task_handle_ != nullptr) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BaseType_t created =
|
||||||
|
xTaskCreate(&GatewayNetworkService::UdpTaskEntry, "gateway_udp",
|
||||||
|
config_.udp_task_stack_size, this, config_.udp_task_priority,
|
||||||
|
&udp_task_handle_);
|
||||||
|
if (created != pdPASS) {
|
||||||
|
udp_task_handle_ = nullptr;
|
||||||
|
ESP_LOGE(kTag, "failed to create UDP task");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayNetworkService::UdpTaskEntry(void* arg) {
|
||||||
|
static_cast<GatewayNetworkService*>(arg)->udpTaskLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayNetworkService::udpTaskLoop() {
|
||||||
|
udp_socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||||
|
if (udp_socket_ < 0) {
|
||||||
|
ESP_LOGE(kTag, "failed to create UDP socket");
|
||||||
|
udp_task_handle_ = nullptr;
|
||||||
|
vTaskDelete(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in local_addr = {};
|
||||||
|
local_addr.sin_family = AF_INET;
|
||||||
|
local_addr.sin_port = htons(config_.udp_port);
|
||||||
|
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
if (bind(udp_socket_, reinterpret_cast<sockaddr*>(&local_addr), sizeof(local_addr)) != 0) {
|
||||||
|
ESP_LOGE(kTag, "failed to bind UDP socket on port %u", config_.udp_port);
|
||||||
|
close(udp_socket_);
|
||||||
|
udp_socket_ = -1;
|
||||||
|
udp_task_handle_ = nullptr;
|
||||||
|
vTaskDelete(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(kTag, "UDP router listening on port %u", config_.udp_port);
|
||||||
|
while (true) {
|
||||||
|
uint8_t buffer[kUdpBufferSize] = {0};
|
||||||
|
sockaddr_storage remote_addr = {};
|
||||||
|
socklen_t remote_addr_len = sizeof(remote_addr);
|
||||||
|
const int read_len = recvfrom(udp_socket_, buffer, sizeof(buffer), 0,
|
||||||
|
reinterpret_cast<sockaddr*>(&remote_addr),
|
||||||
|
&remote_addr_len);
|
||||||
|
if (read_len <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LockGuard guard(udp_lock_);
|
||||||
|
udp_remote_addr_ = remote_addr;
|
||||||
|
udp_remote_addr_len_ = remote_addr_len;
|
||||||
|
has_udp_remote_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller_.enqueueCommandFrame(std::vector<uint8_t>(buffer, buffer + read_len));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayNetworkService::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
||||||
|
if (!config_.udp_enabled || udp_socket_ < 0 || frame.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_storage remote_addr = {};
|
||||||
|
socklen_t remote_addr_len = 0;
|
||||||
|
{
|
||||||
|
LockGuard guard(udp_lock_);
|
||||||
|
if (!has_udp_remote_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
remote_addr = udp_remote_addr_;
|
||||||
|
remote_addr_len = udp_remote_addr_len_;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendto(udp_socket_, frame.data(), frame.size(), 0,
|
||||||
|
reinterpret_cast<const sockaddr*>(&remote_addr), remote_addr_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GatewayNetworkService::deviceInfoJson() const {
|
||||||
|
const auto info = runtime_.deviceInfo();
|
||||||
|
cJSON* root = cJSON_CreateObject();
|
||||||
|
if (root == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON_AddStringToObject(root, "serialId", info.serial_id.c_str());
|
||||||
|
cJSON_AddStringToObject(root, "type", info.type.c_str());
|
||||||
|
cJSON_AddStringToObject(root, "project", info.project.c_str());
|
||||||
|
cJSON_AddStringToObject(root, "version", info.version.c_str());
|
||||||
|
|
||||||
|
cJSON* gateway_info = cJSON_CreateObject();
|
||||||
|
if (gateway_info != nullptr) {
|
||||||
|
cJSON_AddNumberToObject(gateway_info, "count",
|
||||||
|
static_cast<double>(info.dali_gateway_count));
|
||||||
|
cJSON_AddItemToObject(root, "daliGatewayInfo", gateway_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.wlan.has_value()) {
|
||||||
|
cJSON* wlan = cJSON_CreateObject();
|
||||||
|
if (wlan != nullptr) {
|
||||||
|
cJSON_AddStringToObject(wlan, "mac", info.wlan->mac.c_str());
|
||||||
|
cJSON_AddStringToObject(wlan, "IP", info.wlan->ip.c_str());
|
||||||
|
cJSON_AddStringToObject(wlan, "ssid", info.wlan->ssid.c_str());
|
||||||
|
cJSON_AddStringToObject(wlan, "passwd", info.wlan->password.c_str());
|
||||||
|
cJSON_AddItemToObject(root, "wlanInfo", wlan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string rendered = PrintJson(root);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GatewayNetworkService::deviceInfoDoubleEncodedJson() const {
|
||||||
|
const std::string inner = deviceInfoJson();
|
||||||
|
cJSON* outer = cJSON_CreateString(inner.c_str());
|
||||||
|
if (outer == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string rendered = PrintJson(outer);
|
||||||
|
cJSON_Delete(outer);
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t GatewayNetworkService::HandleInfoGet(httpd_req_t* req) {
|
||||||
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
||||||
|
if (service == nullptr) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string payload = service->deviceInfoDoubleEncodedJson();
|
||||||
|
httpd_resp_set_type(req, "application/json");
|
||||||
|
return httpd_resp_send(req, payload.data(), payload.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t GatewayNetworkService::HandleCommandPost(httpd_req_t* req) {
|
||||||
|
auto* service = static_cast<GatewayNetworkService*>(req->user_ctx);
|
||||||
|
if (service == nullptr) {
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string body;
|
||||||
|
if (ReadRequestBody(req, body) != ESP_OK) {
|
||||||
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request");
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON* root = cJSON_ParseWithLength(body.c_str(), body.size());
|
||||||
|
if (root == nullptr) {
|
||||||
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request: json decode error");
|
||||||
|
}
|
||||||
|
|
||||||
|
const cJSON* command = cJSON_GetObjectItemCaseSensitive(root, "command");
|
||||||
|
if (!cJSON_IsString(command) || command->valuestring == nullptr) {
|
||||||
|
cJSON_Delete(root);
|
||||||
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request: missing command");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto frame = DecodeHex(command->valuestring);
|
||||||
|
cJSON_Delete(root);
|
||||||
|
if (frame.empty()) {
|
||||||
|
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Bad Request: invalid command hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
service->controller_.enqueueCommandFrame(frame);
|
||||||
|
httpd_resp_set_type(req, "text/plain");
|
||||||
|
return httpd_resp_sendstr(req, "ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gateway
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
#include "gateway_core.hpp"
|
#include "gateway_core.hpp"
|
||||||
#include "nvs.h"
|
#include "nvs.h"
|
||||||
|
|
||||||
@@ -80,6 +82,7 @@ class GatewayRuntime {
|
|||||||
|
|
||||||
GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
|
GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
|
||||||
DaliDomainService* dali_domain);
|
DaliDomainService* dali_domain);
|
||||||
|
~GatewayRuntime();
|
||||||
|
|
||||||
esp_err_t start();
|
esp_err_t start();
|
||||||
|
|
||||||
@@ -126,6 +129,7 @@ class GatewayRuntime {
|
|||||||
CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone};
|
CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone};
|
||||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> command_address_resolver_;
|
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> command_address_resolver_;
|
||||||
std::optional<WirelessInfo> wireless_info_;
|
std::optional<WirelessInfo> wireless_info_;
|
||||||
|
SemaphoreHandle_t command_lock_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gateway
|
} // namespace gateway
|
||||||
@@ -25,6 +25,24 @@ constexpr uint8_t kCommandFramePrefix0 = 0x28;
|
|||||||
constexpr uint8_t kCommandFramePrefix1 = 0x01;
|
constexpr uint8_t kCommandFramePrefix1 = 0x01;
|
||||||
constexpr uint8_t kNotifyFramePrefix = 0x22;
|
constexpr uint8_t kNotifyFramePrefix = 0x22;
|
||||||
|
|
||||||
|
class LockGuard {
|
||||||
|
public:
|
||||||
|
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
|
||||||
|
if (lock_ != nullptr) {
|
||||||
|
xSemaphoreTakeRecursive(lock_, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~LockGuard() {
|
||||||
|
if (lock_ != nullptr) {
|
||||||
|
xSemaphoreGiveRecursive(lock_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t lock_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
esp_err_t InitializeRuntimeNvs() {
|
esp_err_t InitializeRuntimeNvs() {
|
||||||
@@ -158,7 +176,15 @@ GatewayRuntime::GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
|
|||||||
: profile_(profile),
|
: profile_(profile),
|
||||||
config_(std::move(config)),
|
config_(std::move(config)),
|
||||||
dali_domain_(dali_domain),
|
dali_domain_(dali_domain),
|
||||||
command_address_resolver_([](uint8_t, uint8_t raw_addr) { return raw_addr; }) {}
|
command_address_resolver_([](uint8_t, uint8_t raw_addr) { return raw_addr; }),
|
||||||
|
command_lock_(xSemaphoreCreateRecursiveMutex()) {}
|
||||||
|
|
||||||
|
GatewayRuntime::~GatewayRuntime() {
|
||||||
|
if (command_lock_ != nullptr) {
|
||||||
|
vSemaphoreDelete(command_lock_);
|
||||||
|
command_lock_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t GatewayRuntime::start() {
|
esp_err_t GatewayRuntime::start() {
|
||||||
const esp_err_t err = settings_.open();
|
const esp_err_t err = settings_.open();
|
||||||
@@ -230,6 +256,7 @@ std::vector<uint8_t> GatewayRuntime::buildNotificationFrame(const std::vector<ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
|
bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
|
||||||
|
LockGuard guard(command_lock_);
|
||||||
last_enqueue_drop_reason_ = CommandDropReason::kNone;
|
last_enqueue_drop_reason_ = CommandDropReason::kNone;
|
||||||
if (isQueryCommand(command) && hasPendingQueryCommand(command)) {
|
if (isQueryCommand(command) && hasPendingQueryCommand(command)) {
|
||||||
last_enqueue_drop_reason_ = CommandDropReason::kDuplicate;
|
last_enqueue_drop_reason_ = CommandDropReason::kDuplicate;
|
||||||
@@ -246,6 +273,7 @@ bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::vector<uint8_t>> GatewayRuntime::popNextCommand() {
|
std::optional<std::vector<uint8_t>> GatewayRuntime::popNextCommand() {
|
||||||
|
LockGuard guard(command_lock_);
|
||||||
if (pending_commands_.empty()) {
|
if (pending_commands_.empty()) {
|
||||||
current_command_.reset();
|
current_command_.reset();
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@@ -257,10 +285,12 @@ std::optional<std::vector<uint8_t>> GatewayRuntime::popNextCommand() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GatewayRuntime::completeCurrentCommand() {
|
void GatewayRuntime::completeCurrentCommand() {
|
||||||
|
LockGuard guard(command_lock_);
|
||||||
current_command_.reset();
|
current_command_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayRuntime::hasPendingQueryCommand(const std::vector<uint8_t>& command) const {
|
bool GatewayRuntime::hasPendingQueryCommand(const std::vector<uint8_t>& command) const {
|
||||||
|
LockGuard guard(command_lock_);
|
||||||
const auto command_key = queryCommandKey(command);
|
const auto command_key = queryCommandKey(command);
|
||||||
if (!command_key.has_value()) {
|
if (!command_key.has_value()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -277,6 +307,7 @@ bool GatewayRuntime::hasPendingQueryCommand(const std::vector<uint8_t>& command)
|
|||||||
}
|
}
|
||||||
|
|
||||||
GatewayRuntime::CommandDropReason GatewayRuntime::lastEnqueueDropReason() const {
|
GatewayRuntime::CommandDropReason GatewayRuntime::lastEnqueueDropReason() const {
|
||||||
|
LockGuard guard(command_lock_);
|
||||||
return last_enqueue_drop_reason_;
|
return last_enqueue_drop_reason_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,6 +324,7 @@ void GatewayRuntime::setWirelessInfo(WirelessInfo info) {
|
|||||||
|
|
||||||
void GatewayRuntime::setCommandAddressResolver(
|
void GatewayRuntime::setCommandAddressResolver(
|
||||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
|
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
|
||||||
|
LockGuard guard(command_lock_);
|
||||||
if (resolver) {
|
if (resolver) {
|
||||||
command_address_resolver_ = std::move(resolver);
|
command_address_resolver_ = std::move(resolver);
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user