feat: Add gateway group management and TCP control support
- Introduced gateway group functionality in GatewayController, allowing for grouping of gateways. - Added methods to retrieve and set gateway groups for channels in GatewayRuntime and GatewaySettingsStore. - Implemented TCP control task in GatewayNetworkService to handle control commands over TCP. - Enhanced JSON handling for control frames, including discovery and configuration commands. - Updated Ethernet configuration management to support new settings for gateway groups. - Improved snapshot and reporting mechanisms to include gateway group information. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -65,6 +65,8 @@ report. It returns the enabled DALI channel ids so clients do not need to probe
|
||||
every possible gateway id one by one. For native C++ gateways, channel number is
|
||||
the fixed 1-based Kconfig slot (`1` to `16`) and channel id is the persisted,
|
||||
Lua-compatible gateway id used by normal `0x28 0x01 ...` command frames.
|
||||
Physical gateway ids are reserved to `0..127`. Raw control transports reserve
|
||||
`128..254` for gateway group targets and `255` for all-gateway fan-out.
|
||||
|
||||
Gateway opcode `0x0B` is the serial-scoped channel command. The serial is the
|
||||
last three bytes of the ESP base MAC. Operation `0x00` reports serial plus
|
||||
@@ -74,6 +76,28 @@ Operation `0x03` wraps an existing gateway command and dispatches it by fixed
|
||||
channel number after the serial matches, so BLE/Wi-Fi configuration and DALI
|
||||
send/query commands can target a channel even when its variable channel id is
|
||||
unknown.
|
||||
Operations `0x04` and `0x05` get and set a channel gateway group id. Group `0`
|
||||
means no group membership; group ids `128..254` can be used as raw-control
|
||||
targets. Gateway opcode `0x6A` exposes the same group setting by physical
|
||||
gateway id: operation `0x00` reads the group and operation `0x01` writes group
|
||||
`0` or `128..254`.
|
||||
|
||||
## Ethernet control
|
||||
|
||||
The network service accepts Lua-style raw gateway frames on UDP port `2020` and,
|
||||
when enabled, TCP port `2020`. It also accepts JSON control frames on the same
|
||||
ports:
|
||||
|
||||
- `{"type":"discover"}` returns `discoverResp` with device identity, UDP/TCP
|
||||
control ports, Ethernet status/config, and channel gateway/group data.
|
||||
- `{"type":"dali","data":"<hex gateway frame>"}` enqueues a raw gateway command.
|
||||
- `{"type":"setconfig","data":{"ip":"...","mask":"...","gw":"...","dns":"..."}}`
|
||||
stores and applies static Ethernet IPv4 settings. `ip` empty or `0.0.0.0`
|
||||
clears static config and returns Ethernet to DHCP.
|
||||
|
||||
Raw UDP, raw TCP, and UART0 session control all expand gateway target `255` to
|
||||
all physical gateway ids and targets `128..254` to channels whose configured
|
||||
gateway group matches the target.
|
||||
|
||||
## Gateway operation protocol
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ menu "Gateway Channel 1"
|
||||
|
||||
config GATEWAY_CHANNEL1_GW_ID
|
||||
int "Gateway id"
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 3
|
||||
help
|
||||
Lua-compatible gateway id for the first DALI channel.
|
||||
@@ -121,7 +121,7 @@ menu "Gateway Channel 2"
|
||||
config GATEWAY_CHANNEL2_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 2
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 4
|
||||
help
|
||||
Lua-compatible gateway id for the second DALI channel.
|
||||
@@ -230,7 +230,7 @@ menu "Gateway Channel 3"
|
||||
config GATEWAY_CHANNEL3_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 3
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 5
|
||||
|
||||
choice GATEWAY_CHANNEL3_PHY
|
||||
@@ -319,7 +319,7 @@ menu "Gateway Channel 4"
|
||||
config GATEWAY_CHANNEL4_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 4
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 6
|
||||
|
||||
choice GATEWAY_CHANNEL4_PHY
|
||||
@@ -408,7 +408,7 @@ menu "Gateway Channel 5"
|
||||
config GATEWAY_CHANNEL5_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 5
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 7
|
||||
|
||||
choice GATEWAY_CHANNEL5_PHY
|
||||
@@ -497,7 +497,7 @@ menu "Gateway Channel 6"
|
||||
config GATEWAY_CHANNEL6_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 6
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 8
|
||||
|
||||
choice GATEWAY_CHANNEL6_PHY
|
||||
@@ -586,7 +586,7 @@ menu "Gateway Channel 7"
|
||||
config GATEWAY_CHANNEL7_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 7
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 9
|
||||
|
||||
choice GATEWAY_CHANNEL7_PHY
|
||||
@@ -675,7 +675,7 @@ menu "Gateway Channel 8"
|
||||
config GATEWAY_CHANNEL8_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 8
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 10
|
||||
|
||||
choice GATEWAY_CHANNEL8_PHY
|
||||
@@ -764,7 +764,7 @@ menu "Gateway Channel 9"
|
||||
config GATEWAY_CHANNEL9_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 9
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 11
|
||||
|
||||
choice GATEWAY_CHANNEL9_PHY
|
||||
@@ -853,7 +853,7 @@ menu "Gateway Channel 10"
|
||||
config GATEWAY_CHANNEL10_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 10
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 12
|
||||
|
||||
choice GATEWAY_CHANNEL10_PHY
|
||||
@@ -942,7 +942,7 @@ menu "Gateway Channel 11"
|
||||
config GATEWAY_CHANNEL11_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 11
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 13
|
||||
|
||||
choice GATEWAY_CHANNEL11_PHY
|
||||
@@ -1031,7 +1031,7 @@ menu "Gateway Channel 12"
|
||||
config GATEWAY_CHANNEL12_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 12
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 14
|
||||
|
||||
choice GATEWAY_CHANNEL12_PHY
|
||||
@@ -1120,7 +1120,7 @@ menu "Gateway Channel 13"
|
||||
config GATEWAY_CHANNEL13_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 13
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 15
|
||||
|
||||
choice GATEWAY_CHANNEL13_PHY
|
||||
@@ -1209,7 +1209,7 @@ menu "Gateway Channel 14"
|
||||
config GATEWAY_CHANNEL14_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 14
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 16
|
||||
|
||||
choice GATEWAY_CHANNEL14_PHY
|
||||
@@ -1298,7 +1298,7 @@ menu "Gateway Channel 15"
|
||||
config GATEWAY_CHANNEL15_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 15
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 17
|
||||
|
||||
choice GATEWAY_CHANNEL15_PHY
|
||||
@@ -1387,7 +1387,7 @@ menu "Gateway Channel 16"
|
||||
config GATEWAY_CHANNEL16_GW_ID
|
||||
int "Gateway id"
|
||||
depends on GATEWAY_CHANNEL_COUNT >= 16
|
||||
range 0 255
|
||||
range 0 127
|
||||
default 18
|
||||
|
||||
choice GATEWAY_CHANNEL16_PHY
|
||||
@@ -2502,6 +2502,32 @@ config GATEWAY_NETWORK_UDP_PORT
|
||||
range 1 65535
|
||||
default 2020
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
bool "Enable TCP raw gateway control"
|
||||
default y
|
||||
help
|
||||
Enables a TCP listener for Lua-compatible raw gateway frames and JSON
|
||||
Ethernet control frames. This mirrors the UDP control path and supports
|
||||
gateway group and all-gateway target expansion.
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_PORT
|
||||
int "TCP raw gateway control port"
|
||||
depends on GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
range 1 65535
|
||||
default 2020
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE
|
||||
int "TCP control task stack bytes"
|
||||
depends on GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
range 2048 16384
|
||||
default 4096
|
||||
|
||||
config GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY
|
||||
int "TCP control task priority"
|
||||
depends on GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
range 1 10
|
||||
default 4
|
||||
|
||||
config GATEWAY_STATUS_LED_GPIO
|
||||
int "Status LED GPIO"
|
||||
range -1 48
|
||||
@@ -2555,4 +2581,4 @@ config GATEWAY_BUTTON_TASK_STACK_SIZE
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
||||
endmenu
|
||||
|
||||
@@ -30,6 +30,18 @@
|
||||
#define CONFIG_GATEWAY_NETWORK_UDP_PORT 2020
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT
|
||||
#define CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT 2020
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE
|
||||
#define CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE 4096
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY
|
||||
#define CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY 4
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_GATEWAY_STATUS_LED_GPIO
|
||||
#define CONFIG_GATEWAY_STATUS_LED_GPIO -1
|
||||
#endif
|
||||
@@ -905,6 +917,11 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain,
|
||||
}
|
||||
const uint8_t gateway_id =
|
||||
runtime.gatewayIdForChannel(channel.channel_index, channel.gateway_id);
|
||||
if (gateway_id >= 128) {
|
||||
ESP_LOGE(kTag, "gateway id %u for channel %u is reserved for gateway groups",
|
||||
gateway_id, channel.channel_index + 1);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (used_gateway_ids[gateway_id]) {
|
||||
ESP_LOGE(kTag, "duplicate runtime gateway id configured: %u", gateway_id);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
@@ -1255,9 +1272,20 @@ extern "C" void app_main(void) {
|
||||
network_config.udp_enabled = true;
|
||||
#else
|
||||
network_config.udp_enabled = false;
|
||||
#endif
|
||||
#ifdef CONFIG_GATEWAY_NETWORK_TCP_CONTROL_ENABLED
|
||||
network_config.tcp_control_enabled = true;
|
||||
#else
|
||||
network_config.tcp_control_enabled = false;
|
||||
#endif
|
||||
network_config.http_port = static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_HTTP_PORT);
|
||||
network_config.udp_port = static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_UDP_PORT);
|
||||
network_config.tcp_control_port =
|
||||
static_cast<uint16_t>(CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT);
|
||||
network_config.tcp_control_task_stack_size =
|
||||
static_cast<uint32_t>(CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE);
|
||||
network_config.tcp_control_task_priority =
|
||||
static_cast<UBaseType_t>(CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY);
|
||||
network_config.ethernet_spi_host = CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST;
|
||||
network_config.ethernet_spi_sclk_gpio = CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO;
|
||||
network_config.ethernet_spi_mosi_gpio = CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO;
|
||||
|
||||
@@ -858,6 +858,10 @@ CONFIG_GATEWAY_NETWORK_HTTP_ENABLED=y
|
||||
CONFIG_GATEWAY_NETWORK_HTTP_PORT=80
|
||||
CONFIG_GATEWAY_NETWORK_UDP_ROUTER_ENABLED=y
|
||||
CONFIG_GATEWAY_NETWORK_UDP_PORT=2020
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_ENABLED=y
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_PORT=2020
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_STACK_SIZE=4096
|
||||
CONFIG_GATEWAY_NETWORK_TCP_CONTROL_TASK_PRIORITY=4
|
||||
CONFIG_GATEWAY_STATUS_LED_GPIO=-1
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_GPIO=0
|
||||
CONFIG_GATEWAY_BOOT_BUTTON_ACTIVE_LOW=y
|
||||
|
||||
@@ -10,11 +10,10 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "gateway_controller.hpp"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class GatewayController;
|
||||
|
||||
struct Gateway485ControlBridgeConfig {
|
||||
bool enabled{false};
|
||||
int tx_pin{-1};
|
||||
@@ -69,8 +68,10 @@ class Gateway485ControlBridge {
|
||||
uint8_t hash,
|
||||
SessionStatus status,
|
||||
const std::vector<uint8_t>& result);
|
||||
GatewayCommandTransactionResult transactRoutedCommand(const std::vector<uint8_t>& command);
|
||||
bool shouldSuppressNotification(const std::vector<uint8_t>& frame);
|
||||
bool responseMatchesActiveSession(const std::vector<uint8_t>& frame) const;
|
||||
bool activeSessionTargetMatches(uint8_t response_gateway) const;
|
||||
|
||||
GatewayController& controller_;
|
||||
Gateway485ControlBridgeConfig config_;
|
||||
|
||||
@@ -28,6 +28,7 @@ constexpr uint8_t kDali103NoResponseOpcode = 0x64;
|
||||
constexpr uint8_t kRawReportLeaseOpcode = 0x66;
|
||||
constexpr uint8_t kGatewayOperationOpcode = 0x67;
|
||||
constexpr uint8_t kGatewayOperationResultOpcode = 0x68;
|
||||
constexpr uint8_t kGatewayGroupOpcode = 0x6A;
|
||||
constexpr uint8_t kGatewayCacheOpcode = 0x39;
|
||||
constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0;
|
||||
constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1;
|
||||
@@ -215,9 +216,7 @@ bool Gateway485ControlBridge::handleSessionBytes(const uint8_t* data, size_t len
|
||||
active_session_->status = SessionStatus::kInProcess;
|
||||
}
|
||||
|
||||
const auto result = controller_.transactCommandFrame(
|
||||
command, kSessionTransactionTimeoutMs, kSessionTransactionIdleMs,
|
||||
kSessionMaxResponseBytes, true);
|
||||
const auto result = transactRoutedCommand(command);
|
||||
|
||||
{
|
||||
LockGuard guard(session_lock_);
|
||||
@@ -325,11 +324,63 @@ void Gateway485ControlBridge::writeSessionStatus(uint8_t gateway_id,
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCommandTransactionResult Gateway485ControlBridge::transactRoutedCommand(
|
||||
const std::vector<uint8_t>& command) {
|
||||
GatewayCommandTransactionResult aggregate;
|
||||
if (command.size() < kCommandFrameMinLen) {
|
||||
aggregate.status = GatewayCommandTransactionStatus::kInvalidFrame;
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
const auto targets = controller_.gatewayIdsForControlTarget(command[2]);
|
||||
if (targets.empty() || (targets.size() == 1 && targets.front() == command[2])) {
|
||||
return controller_.transactCommandFrame(command, kSessionTransactionTimeoutMs,
|
||||
kSessionTransactionIdleMs,
|
||||
kSessionMaxResponseBytes, true);
|
||||
}
|
||||
|
||||
aggregate.status = GatewayCommandTransactionStatus::kNoResponse;
|
||||
for (const auto gateway_id : targets) {
|
||||
std::vector<uint8_t> routed(command.begin(), command.end() - 1);
|
||||
routed[2] = gateway_id;
|
||||
const auto result = controller_.transactCommandFrame(
|
||||
GatewayRuntime::checksum(std::move(routed)), kSessionTransactionTimeoutMs,
|
||||
kSessionTransactionIdleMs, kSessionMaxResponseBytes, true);
|
||||
if (result.status == GatewayCommandTransactionStatus::kOk) {
|
||||
aggregate.status = GatewayCommandTransactionStatus::kOk;
|
||||
} else if (aggregate.status != GatewayCommandTransactionStatus::kOk) {
|
||||
aggregate.status = result.status;
|
||||
}
|
||||
if (!result.frames.empty()) {
|
||||
const size_t remaining =
|
||||
kSessionMaxResponseBytes > aggregate.frames.size()
|
||||
? kSessionMaxResponseBytes - aggregate.frames.size()
|
||||
: 0;
|
||||
aggregate.frames.insert(aggregate.frames.end(), result.frames.begin(),
|
||||
result.frames.begin() +
|
||||
std::min(remaining, result.frames.size()));
|
||||
}
|
||||
}
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
bool Gateway485ControlBridge::shouldSuppressNotification(const std::vector<uint8_t>& frame) {
|
||||
LockGuard guard(session_lock_);
|
||||
return responseMatchesActiveSession(frame);
|
||||
}
|
||||
|
||||
bool Gateway485ControlBridge::activeSessionTargetMatches(uint8_t response_gateway) const {
|
||||
if (!active_session_.has_value()) {
|
||||
return false;
|
||||
}
|
||||
const auto targets = controller_.gatewayIdsForControlTarget(active_session_->gateway_id);
|
||||
if (targets.empty()) {
|
||||
return response_gateway == active_session_->gateway_id ||
|
||||
active_session_->gateway_id == 0 || response_gateway == 0xff;
|
||||
}
|
||||
return std::find(targets.begin(), targets.end(), response_gateway) != targets.end();
|
||||
}
|
||||
|
||||
bool Gateway485ControlBridge::responseMatchesActiveSession(
|
||||
const std::vector<uint8_t>& frame) const {
|
||||
if (!active_session_.has_value() || frame.size() < 3 || frame[0] != 0x22) {
|
||||
@@ -338,9 +389,7 @@ bool Gateway485ControlBridge::responseMatchesActiveSession(
|
||||
const auto& session = *active_session_;
|
||||
const uint8_t response_opcode = frame[1];
|
||||
const uint8_t response_gateway = frame[2];
|
||||
const bool gateway_matches = response_gateway == session.gateway_id ||
|
||||
session.gateway_id == 0 ||
|
||||
response_gateway == 0xff;
|
||||
const bool gateway_matches = activeSessionTargetMatches(response_gateway);
|
||||
switch (session.opcode) {
|
||||
case 0x06:
|
||||
return response_opcode == 0x03 && gateway_matches;
|
||||
@@ -365,6 +414,8 @@ bool Gateway485ControlBridge::responseMatchesActiveSession(
|
||||
return (response_opcode == kGatewayOperationOpcode ||
|
||||
response_opcode == kGatewayOperationResultOpcode) &&
|
||||
gateway_matches;
|
||||
case kGatewayGroupOpcode:
|
||||
return response_opcode == kGatewayGroupOpcode && gateway_matches;
|
||||
case kBridgeTransportRequestOpcode:
|
||||
return response_opcode == kBridgeTransportResponseOpcode && gateway_matches;
|
||||
default:
|
||||
|
||||
@@ -44,6 +44,7 @@ struct GatewayControllerConfig {
|
||||
struct GatewayChannelSnapshot {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
uint8_t gateway_group{0};
|
||||
std::string name;
|
||||
std::string phy;
|
||||
uint8_t scene_mask_low{0};
|
||||
@@ -119,6 +120,7 @@ class GatewayController {
|
||||
bool ipRouterEnabled() const;
|
||||
bool rawReportingEnabled(uint8_t gateway_id) const;
|
||||
GatewayControllerSnapshot snapshot();
|
||||
std::vector<uint8_t> gatewayIdsForControlTarget(uint8_t target) const;
|
||||
|
||||
struct ParsedTlv {
|
||||
uint8_t type{0};
|
||||
@@ -212,6 +214,8 @@ class GatewayController {
|
||||
bool hasGateway(uint8_t gateway_id) const;
|
||||
std::vector<uint8_t> gatewayIds() const;
|
||||
std::optional<uint8_t> gatewayIdForChannelNumber(uint8_t channel_number) const;
|
||||
std::optional<uint8_t> channelNumberForGatewayId(uint8_t gateway_id) const;
|
||||
uint8_t gatewayGroupForGatewayId(uint8_t gateway_id) const;
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
void refreshRuntimeGatewayNames();
|
||||
void publishPayload(uint8_t gateway_id, const std::vector<uint8_t>& payload);
|
||||
@@ -266,6 +270,7 @@ class GatewayController {
|
||||
|
||||
void handleGatewayNameCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleGatewaySerialCommand(uint8_t channel_number, const std::vector<uint8_t>& command);
|
||||
void handleGatewayGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void publishGatewaySerialReport();
|
||||
void publishGatewaySerialResponse(uint8_t status, uint8_t op,
|
||||
const std::vector<uint8_t>& data);
|
||||
|
||||
@@ -35,10 +35,21 @@ constexpr uint8_t kGatewaySerialOpReport = 0x00;
|
||||
constexpr uint8_t kGatewaySerialOpGetChannelId = 0x01;
|
||||
constexpr uint8_t kGatewaySerialOpSetChannelId = 0x02;
|
||||
constexpr uint8_t kGatewaySerialOpDispatchByChannelNumber = 0x03;
|
||||
constexpr uint8_t kGatewaySerialOpGetGatewayGroup = 0x04;
|
||||
constexpr uint8_t kGatewaySerialOpSetGatewayGroup = 0x05;
|
||||
constexpr uint8_t kGatewaySerialStatusOk = 0x00;
|
||||
constexpr uint8_t kGatewaySerialStatusInvalidArgument = 0x02;
|
||||
constexpr uint8_t kGatewaySerialStatusDuplicateChannelId = 0x03;
|
||||
constexpr uint8_t kGatewaySerialStatusStorageError = 0x04;
|
||||
constexpr uint8_t kGatewayGroupOpcode = 0x6A;
|
||||
constexpr uint8_t kGatewayGroupOpGet = 0x00;
|
||||
constexpr uint8_t kGatewayGroupOpSet = 0x01;
|
||||
constexpr uint8_t kGatewayGroupStatusOk = 0x00;
|
||||
constexpr uint8_t kGatewayGroupStatusInvalidArgument = 0x02;
|
||||
constexpr uint8_t kGatewayGroupStatusStorageError = 0x04;
|
||||
constexpr uint8_t kGatewayGroupMin = 128;
|
||||
constexpr uint8_t kGatewayGroupMax = 254;
|
||||
constexpr uint8_t kGatewayTargetAll = 255;
|
||||
constexpr uint8_t kDali103SendOpcode = 0x60;
|
||||
constexpr uint8_t kDali103SendTwiceOpcode = 0x61;
|
||||
constexpr uint8_t kDali103QueryOpcode = 0x62;
|
||||
@@ -758,6 +769,7 @@ GatewayControllerSnapshot GatewayController::snapshot() {
|
||||
GatewayChannelSnapshot channel_snapshot;
|
||||
channel_snapshot.channel_index = channel.channel_index;
|
||||
channel_snapshot.gateway_id = channel.gateway_id;
|
||||
channel_snapshot.gateway_group = runtime_.gatewayGroupForChannel(channel.channel_index);
|
||||
channel_snapshot.name = channel.name;
|
||||
channel_snapshot.phy = PhyKindToString(channel.phy_kind);
|
||||
channel_snapshot.scene_mask_low = scene_low;
|
||||
@@ -785,6 +797,33 @@ GatewayControllerSnapshot GatewayController::snapshot() {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GatewayController::gatewayIdsForControlTarget(uint8_t target) const {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
std::vector<uint8_t> ids;
|
||||
ids.reserve(channels.size());
|
||||
if (target == kGatewayTargetAll) {
|
||||
for (const auto& channel : channels) {
|
||||
ids.push_back(channel.gateway_id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
if (target >= kGatewayGroupMin && target <= kGatewayGroupMax) {
|
||||
for (const auto& channel : channels) {
|
||||
if (runtime_.gatewayGroupForChannel(channel.channel_index) == target) {
|
||||
ids.push_back(channel.gateway_id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
if (target < kGatewayGroupMin &&
|
||||
std::any_of(channels.begin(), channels.end(), [target](const auto& channel) {
|
||||
return channel.gateway_id == target;
|
||||
})) {
|
||||
ids.push_back(target);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void GatewayController::TaskEntry(void* arg) {
|
||||
static_cast<GatewayController*>(arg)->taskLoop();
|
||||
}
|
||||
@@ -1167,6 +1206,7 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
}
|
||||
if (!chip_level_command && operationActive(gateway_id) &&
|
||||
opcode != kRawReportLeaseOpcode && opcode != kGatewayOperationOpcode &&
|
||||
opcode != kGatewayGroupOpcode &&
|
||||
!(opcode == 0x30 && (addr == 0 || addr == 3))) {
|
||||
ESP_LOGW(kTag, "gateway=%u busy with operation, opcode=0x%02x rejected", gateway_id,
|
||||
opcode);
|
||||
@@ -1395,6 +1435,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
case kGatewayOperationOpcode:
|
||||
handleGatewayOperationCommand(gateway_id, command);
|
||||
break;
|
||||
case kGatewayGroupOpcode:
|
||||
handleGatewayGroupCommand(gateway_id, command);
|
||||
break;
|
||||
case kGatewayCacheOpcode:
|
||||
handleGatewayCacheCommand(gateway_id, command);
|
||||
break;
|
||||
@@ -1447,6 +1490,30 @@ std::optional<uint8_t> GatewayController::gatewayIdForChannelNumber(
|
||||
return it->gateway_id;
|
||||
}
|
||||
|
||||
std::optional<uint8_t> GatewayController::channelNumberForGatewayId(uint8_t gateway_id) const {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
const auto it =
|
||||
std::find_if(channels.begin(), channels.end(), [gateway_id](const auto& channel) {
|
||||
return channel.gateway_id == gateway_id;
|
||||
});
|
||||
if (it == channels.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<uint8_t>(it->channel_index + 1);
|
||||
}
|
||||
|
||||
uint8_t GatewayController::gatewayGroupForGatewayId(uint8_t gateway_id) const {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
const auto it =
|
||||
std::find_if(channels.begin(), channels.end(), [gateway_id](const auto& channel) {
|
||||
return channel.gateway_id == gateway_id;
|
||||
});
|
||||
if (it == channels.end()) {
|
||||
return 0;
|
||||
}
|
||||
return runtime_.gatewayGroupForChannel(it->channel_index);
|
||||
}
|
||||
|
||||
std::string GatewayController::gatewayName(uint8_t gateway_id) const {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
const auto it = std::find_if(channels.begin(), channels.end(), [gateway_id](const auto& channel) {
|
||||
@@ -2292,6 +2359,11 @@ void GatewayController::handleGatewaySerialCommand(uint8_t channel_number,
|
||||
return;
|
||||
}
|
||||
const uint8_t new_gateway_id = command[8];
|
||||
if (new_gateway_id >= kGatewayGroupMin) {
|
||||
publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op,
|
||||
{channel_number, new_gateway_id});
|
||||
return;
|
||||
}
|
||||
const uint8_t channel_index = static_cast<uint8_t>(channel_number - 1);
|
||||
const uint8_t old_gateway_id = gateway_id.value();
|
||||
if (!dali_domain_.updateChannelGatewayId(channel_index, new_gateway_id)) {
|
||||
@@ -2313,6 +2385,33 @@ void GatewayController::handleGatewaySerialCommand(uint8_t channel_number,
|
||||
{channel_number, new_gateway_id});
|
||||
return;
|
||||
}
|
||||
case kGatewaySerialOpGetGatewayGroup:
|
||||
publishGatewaySerialResponse(kGatewaySerialStatusOk, op,
|
||||
{channel_number, runtime_.gatewayGroupForChannel(
|
||||
static_cast<uint8_t>(channel_number - 1))});
|
||||
return;
|
||||
case kGatewaySerialOpSetGatewayGroup: {
|
||||
if (command.size() < 10) {
|
||||
publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op, {channel_number});
|
||||
return;
|
||||
}
|
||||
const uint8_t gateway_group = command[8];
|
||||
if (gateway_group != 0 && (gateway_group < kGatewayGroupMin ||
|
||||
gateway_group > kGatewayGroupMax)) {
|
||||
publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op,
|
||||
{channel_number, gateway_group});
|
||||
return;
|
||||
}
|
||||
const uint8_t channel_index = static_cast<uint8_t>(channel_number - 1);
|
||||
if (!runtime_.setGatewayGroupForChannel(channel_index, gateway_group)) {
|
||||
publishGatewaySerialResponse(kGatewaySerialStatusStorageError, op,
|
||||
{channel_number, gateway_group});
|
||||
return;
|
||||
}
|
||||
publishGatewaySerialResponse(kGatewaySerialStatusOk, op,
|
||||
{channel_number, gateway_group});
|
||||
return;
|
||||
}
|
||||
case kGatewaySerialOpDispatchByChannelNumber: {
|
||||
if (command.size() < 12) {
|
||||
publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op, {channel_number});
|
||||
@@ -2335,6 +2434,45 @@ void GatewayController::handleGatewaySerialCommand(uint8_t channel_number,
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::handleGatewayGroupCommand(uint8_t gateway_id,
|
||||
const std::vector<uint8_t>& command) {
|
||||
const uint8_t op = command.size() > 4 ? command[4] : kGatewayGroupOpGet;
|
||||
const auto channel_number = channelNumberForGatewayId(gateway_id);
|
||||
if (!channel_number.has_value()) {
|
||||
publishPayload(gateway_id, {kGatewayGroupOpcode, gateway_id,
|
||||
kGatewayGroupStatusInvalidArgument, op, 0x00});
|
||||
return;
|
||||
}
|
||||
const uint8_t channel_index = static_cast<uint8_t>(channel_number.value() - 1);
|
||||
|
||||
if (op == kGatewayGroupOpGet) {
|
||||
publishPayload(gateway_id, {kGatewayGroupOpcode, gateway_id, kGatewayGroupStatusOk, op,
|
||||
runtime_.gatewayGroupForChannel(channel_index)});
|
||||
return;
|
||||
}
|
||||
|
||||
if (op != kGatewayGroupOpSet || command.size() < 7) {
|
||||
publishPayload(gateway_id, {kGatewayGroupOpcode, gateway_id,
|
||||
kGatewayGroupStatusInvalidArgument, op, 0x00});
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t gateway_group = command[5];
|
||||
if (gateway_group != 0 &&
|
||||
(gateway_group < kGatewayGroupMin || gateway_group > kGatewayGroupMax)) {
|
||||
publishPayload(gateway_id, {kGatewayGroupOpcode, gateway_id,
|
||||
kGatewayGroupStatusInvalidArgument, op, gateway_group});
|
||||
return;
|
||||
}
|
||||
if (!runtime_.setGatewayGroupForChannel(channel_index, gateway_group)) {
|
||||
publishPayload(gateway_id, {kGatewayGroupOpcode, gateway_id,
|
||||
kGatewayGroupStatusStorageError, op, gateway_group});
|
||||
return;
|
||||
}
|
||||
publishPayload(gateway_id,
|
||||
{kGatewayGroupOpcode, gateway_id, kGatewayGroupStatusOk, op, gateway_group});
|
||||
}
|
||||
|
||||
void GatewayController::handleGatewayIdentityCommand(uint8_t gateway_id, uint8_t op) {
|
||||
std::string value;
|
||||
if (op == 0x00) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cJSON.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_eth.h"
|
||||
@@ -39,8 +40,10 @@ struct GatewayNetworkServiceConfig {
|
||||
uint8_t smartconfig_timeout_sec{60};
|
||||
bool http_enabled{true};
|
||||
bool udp_enabled{true};
|
||||
bool tcp_control_enabled{true};
|
||||
uint16_t http_port{80};
|
||||
uint16_t udp_port{2020};
|
||||
uint16_t tcp_control_port{2020};
|
||||
int ethernet_spi_host{1};
|
||||
int ethernet_spi_sclk_gpio{14};
|
||||
int ethernet_spi_mosi_gpio{13};
|
||||
@@ -63,6 +66,8 @@ struct GatewayNetworkServiceConfig {
|
||||
UBaseType_t boot_button_task_priority{2};
|
||||
uint32_t udp_task_stack_size{4096};
|
||||
UBaseType_t udp_task_priority{4};
|
||||
uint32_t tcp_control_task_stack_size{4096};
|
||||
UBaseType_t tcp_control_task_priority{4};
|
||||
};
|
||||
|
||||
class GatewayNetworkService {
|
||||
@@ -76,6 +81,7 @@ class GatewayNetworkService {
|
||||
|
||||
private:
|
||||
static void UdpTaskEntry(void* arg);
|
||||
static void TcpControlTaskEntry(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);
|
||||
@@ -107,10 +113,18 @@ class GatewayNetworkService {
|
||||
esp_err_t configureStatusLed();
|
||||
esp_err_t startHttpServer();
|
||||
esp_err_t startUdpTask();
|
||||
esp_err_t startTcpControlTask();
|
||||
esp_err_t configureBootButton();
|
||||
esp_err_t startBootButtonTask();
|
||||
void udpTaskLoop();
|
||||
void tcpControlTaskLoop();
|
||||
void bootButtonTaskLoop();
|
||||
void handleNetworkControlBytes(const uint8_t* data, size_t len);
|
||||
std::optional<std::string> handleJsonControlFrame(const uint8_t* data, size_t len);
|
||||
bool enqueueControlFrameForTargets(const std::vector<uint8_t>& frame);
|
||||
std::string discoveryResponseJson() const;
|
||||
std::string setEthernetConfigFromJson(cJSON* data);
|
||||
esp_err_t applyEthernetConfig();
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
void handleWifiControl(uint8_t mode);
|
||||
void handleEthernetEvent(esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
@@ -151,11 +165,15 @@ class GatewayNetworkService {
|
||||
std::array<uint8_t, 6> espnow_peer_{};
|
||||
TaskHandle_t boot_button_task_handle_{nullptr};
|
||||
TaskHandle_t udp_task_handle_{nullptr};
|
||||
TaskHandle_t tcp_control_task_handle_{nullptr};
|
||||
int udp_socket_{-1};
|
||||
int tcp_control_socket_{-1};
|
||||
int tcp_control_client_socket_{-1};
|
||||
SemaphoreHandle_t udp_lock_{nullptr};
|
||||
SemaphoreHandle_t tcp_control_lock_{nullptr};
|
||||
bool has_udp_remote_{false};
|
||||
sockaddr_storage udp_remote_addr_{};
|
||||
socklen_t udp_remote_addr_len_{0};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "lwip/inet.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
@@ -35,6 +36,7 @@ namespace {
|
||||
constexpr const char* kTag = "gateway_network";
|
||||
constexpr const char* kSetupApSsid = "LAMMIN_Gateway";
|
||||
constexpr size_t kUdpBufferSize = 256;
|
||||
constexpr size_t kTcpControlBufferSize = 512;
|
||||
constexpr uint8_t kEspNowBroadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
GatewayNetworkService* s_espnow_service = nullptr;
|
||||
@@ -146,6 +148,17 @@ std::string BytesToHex(const std::vector<uint8_t>& bytes) {
|
||||
return out;
|
||||
}
|
||||
|
||||
bool IsLikelyJson(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
size_t index = 0;
|
||||
while (index < len && std::isspace(static_cast<unsigned char>(data[index]))) {
|
||||
++index;
|
||||
}
|
||||
return index < len && data[index] == '{';
|
||||
}
|
||||
|
||||
std::string PrintJson(cJSON* node) {
|
||||
if (node == nullptr) {
|
||||
return {};
|
||||
@@ -160,6 +173,32 @@ std::string PrintJson(cJSON* node) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string JsonResponse(std::string_view type, std::string_view status) {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (root == nullptr) {
|
||||
return {};
|
||||
}
|
||||
cJSON_AddStringToObject(root, "type", std::string(type).c_str());
|
||||
cJSON_AddStringToObject(root, "status", std::string(status).c_str());
|
||||
cJSON_AddStringToObject(root, "data", std::string(status).c_str());
|
||||
const std::string rendered = PrintJson(root);
|
||||
cJSON_Delete(root);
|
||||
return rendered;
|
||||
}
|
||||
|
||||
bool ParseIpv4(std::string_view value, esp_ip4_addr_t* out) {
|
||||
if (out == nullptr || value.empty()) {
|
||||
return false;
|
||||
}
|
||||
const std::string text(value);
|
||||
const uint32_t address = esp_ip4addr_aton(text.c_str());
|
||||
if (address == 0 && text != "0.0.0.0") {
|
||||
return false;
|
||||
}
|
||||
out->addr = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
esp_err_t ReadRequestBody(httpd_req_t* req, std::string& body) {
|
||||
body.clear();
|
||||
if (req == nullptr) {
|
||||
@@ -260,7 +299,8 @@ GatewayNetworkService::GatewayNetworkService(GatewayController& controller,
|
||||
GatewayNetworkServiceConfig config,
|
||||
GatewayBridgeService* bridge_service)
|
||||
: controller_(controller), runtime_(runtime), dali_domain_(dali_domain), config_(config),
|
||||
bridge_service_(bridge_service), udp_lock_(xSemaphoreCreateMutex()) {}
|
||||
bridge_service_(bridge_service), udp_lock_(xSemaphoreCreateMutex()),
|
||||
tcp_control_lock_(xSemaphoreCreateMutex()) {}
|
||||
|
||||
esp_err_t GatewayNetworkService::start() {
|
||||
if (started_) {
|
||||
@@ -331,15 +371,22 @@ esp_err_t GatewayNetworkService::start() {
|
||||
}
|
||||
}
|
||||
|
||||
if (config_.tcp_control_enabled) {
|
||||
err = startTcpControlTask();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
err = startBootButtonTask();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "network service started eth=%d wifi=%d http=%d udp=%d",
|
||||
ESP_LOGI(kTag, "network service started eth=%d wifi=%d http=%d udp=%d tcp=%d",
|
||||
config_.ethernet_enabled, config_.wifi_enabled, config_.http_enabled,
|
||||
config_.udp_enabled);
|
||||
config_.udp_enabled, config_.tcp_control_enabled);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -489,6 +536,13 @@ esp_err_t GatewayNetworkService::startEthernet() {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = applyEthernetConfig();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to apply Ethernet config: %s", esp_err_to_name(err));
|
||||
stopEthernet();
|
||||
return err;
|
||||
}
|
||||
|
||||
err = esp_eth_start(eth_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to start Ethernet: %s", esp_err_to_name(err));
|
||||
@@ -999,6 +1053,24 @@ esp_err_t GatewayNetworkService::startUdpTask() {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startTcpControlTask() {
|
||||
if (tcp_control_task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const BaseType_t created =
|
||||
xTaskCreate(&GatewayNetworkService::TcpControlTaskEntry, "gateway_tcp_ctrl",
|
||||
config_.tcp_control_task_stack_size, this,
|
||||
config_.tcp_control_task_priority, &tcp_control_task_handle_);
|
||||
if (created != pdPASS) {
|
||||
tcp_control_task_handle_ = nullptr;
|
||||
ESP_LOGE(kTag, "failed to create TCP control task");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startBootButtonTask() {
|
||||
if ((config_.boot_button_gpio < 0 && config_.setup_ap_button_gpio < 0) ||
|
||||
boot_button_task_handle_ != nullptr) {
|
||||
@@ -1021,6 +1093,10 @@ void GatewayNetworkService::UdpTaskEntry(void* arg) {
|
||||
static_cast<GatewayNetworkService*>(arg)->udpTaskLoop();
|
||||
}
|
||||
|
||||
void GatewayNetworkService::TcpControlTaskEntry(void* arg) {
|
||||
static_cast<GatewayNetworkService*>(arg)->tcpControlTaskLoop();
|
||||
}
|
||||
|
||||
void GatewayNetworkService::BootButtonTaskEntry(void* arg) {
|
||||
static_cast<GatewayNetworkService*>(arg)->bootButtonTaskLoop();
|
||||
}
|
||||
@@ -1359,7 +1435,84 @@ void GatewayNetworkService::udpTaskLoop() {
|
||||
has_udp_remote_ = true;
|
||||
}
|
||||
|
||||
controller_.enqueueCommandFrame(std::vector<uint8_t>(buffer, buffer + read_len));
|
||||
const auto response =
|
||||
handleJsonControlFrame(buffer, static_cast<size_t>(read_len));
|
||||
if (response.has_value()) {
|
||||
sendto(udp_socket_, response->data(), response->size(), 0,
|
||||
reinterpret_cast<const sockaddr*>(&remote_addr), remote_addr_len);
|
||||
continue;
|
||||
}
|
||||
|
||||
handleNetworkControlBytes(buffer, static_cast<size_t>(read_len));
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::tcpControlTaskLoop() {
|
||||
tcp_control_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
|
||||
if (tcp_control_socket_ < 0) {
|
||||
ESP_LOGE(kTag, "failed to create TCP control socket");
|
||||
tcp_control_task_handle_ = nullptr;
|
||||
vTaskDelete(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
setsockopt(tcp_control_socket_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
|
||||
sockaddr_in local_addr = {};
|
||||
local_addr.sin_family = AF_INET;
|
||||
local_addr.sin_port = htons(config_.tcp_control_port);
|
||||
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (bind(tcp_control_socket_, reinterpret_cast<sockaddr*>(&local_addr),
|
||||
sizeof(local_addr)) != 0 ||
|
||||
listen(tcp_control_socket_, 2) != 0) {
|
||||
ESP_LOGE(kTag, "failed to listen on TCP control port %u", config_.tcp_control_port);
|
||||
close(tcp_control_socket_);
|
||||
tcp_control_socket_ = -1;
|
||||
tcp_control_task_handle_ = nullptr;
|
||||
vTaskDelete(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag, "TCP control listening on port %u", config_.tcp_control_port);
|
||||
while (true) {
|
||||
sockaddr_storage remote_addr = {};
|
||||
socklen_t remote_addr_len = sizeof(remote_addr);
|
||||
const int client =
|
||||
accept(tcp_control_socket_, reinterpret_cast<sockaddr*>(&remote_addr), &remote_addr_len);
|
||||
if (client < 0) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
LockGuard guard(tcp_control_lock_);
|
||||
if (tcp_control_client_socket_ >= 0) {
|
||||
close(tcp_control_client_socket_);
|
||||
}
|
||||
tcp_control_client_socket_ = client;
|
||||
}
|
||||
|
||||
uint8_t buffer[kTcpControlBufferSize] = {0};
|
||||
while (true) {
|
||||
const int read_len = recv(client, buffer, sizeof(buffer), 0);
|
||||
if (read_len <= 0) {
|
||||
break;
|
||||
}
|
||||
const auto response =
|
||||
handleJsonControlFrame(buffer, static_cast<size_t>(read_len));
|
||||
if (response.has_value()) {
|
||||
send(client, response->data(), response->size(), 0);
|
||||
continue;
|
||||
}
|
||||
handleNetworkControlBytes(buffer, static_cast<size_t>(read_len));
|
||||
}
|
||||
|
||||
{
|
||||
LockGuard guard(tcp_control_lock_);
|
||||
if (tcp_control_client_socket_ == client) {
|
||||
tcp_control_client_socket_ = -1;
|
||||
}
|
||||
}
|
||||
close(client);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1454,23 +1607,110 @@ void GatewayNetworkService::bootButtonTaskLoop() {
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
||||
if (!config_.udp_enabled || udp_socket_ < 0 || frame.empty()) {
|
||||
if (frame.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sockaddr_storage remote_addr = {};
|
||||
socklen_t remote_addr_len = 0;
|
||||
{
|
||||
LockGuard guard(udp_lock_);
|
||||
if (!has_udp_remote_) {
|
||||
return;
|
||||
if (config_.udp_enabled && udp_socket_ >= 0) {
|
||||
sockaddr_storage remote_addr = {};
|
||||
socklen_t remote_addr_len = 0;
|
||||
{
|
||||
LockGuard guard(udp_lock_);
|
||||
if (has_udp_remote_) {
|
||||
remote_addr = udp_remote_addr_;
|
||||
remote_addr_len = udp_remote_addr_len_;
|
||||
}
|
||||
}
|
||||
if (remote_addr_len > 0) {
|
||||
sendto(udp_socket_, frame.data(), frame.size(), 0,
|
||||
reinterpret_cast<const sockaddr*>(&remote_addr), remote_addr_len);
|
||||
}
|
||||
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);
|
||||
if (config_.tcp_control_enabled) {
|
||||
int client = -1;
|
||||
{
|
||||
LockGuard guard(tcp_control_lock_);
|
||||
client = tcp_control_client_socket_;
|
||||
}
|
||||
if (client >= 0) {
|
||||
send(client, frame.data(), frame.size(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleNetworkControlBytes(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
return;
|
||||
}
|
||||
enqueueControlFrameForTargets(std::vector<uint8_t>(data, data + len));
|
||||
}
|
||||
|
||||
bool GatewayNetworkService::enqueueControlFrameForTargets(const std::vector<uint8_t>& frame) {
|
||||
if (!GatewayRuntime::isGatewayCommandFrame(frame) || frame.size() < 7) {
|
||||
return controller_.enqueueCommandFrame(frame);
|
||||
}
|
||||
const uint8_t target = frame[2];
|
||||
const auto gateways = controller_.gatewayIdsForControlTarget(target);
|
||||
if (gateways.empty() || (gateways.size() == 1 && gateways.front() == target)) {
|
||||
return controller_.enqueueCommandFrame(frame);
|
||||
}
|
||||
|
||||
bool any_enqueued = false;
|
||||
for (const auto gateway_id : gateways) {
|
||||
std::vector<uint8_t> routed(frame.begin(), frame.end() - 1);
|
||||
routed[2] = gateway_id;
|
||||
any_enqueued = controller_.enqueueCommandFrame(GatewayRuntime::checksum(std::move(routed))) ||
|
||||
any_enqueued;
|
||||
}
|
||||
return any_enqueued;
|
||||
}
|
||||
|
||||
std::optional<std::string> GatewayNetworkService::handleJsonControlFrame(const uint8_t* data,
|
||||
size_t len) {
|
||||
if (!IsLikelyJson(data, len)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
cJSON* root = cJSON_ParseWithLength(reinterpret_cast<const char*>(data), len);
|
||||
if (root == nullptr) {
|
||||
return JsonResponse("error", "json");
|
||||
}
|
||||
const char* type = JsonString(root, "type");
|
||||
if (type == nullptr) {
|
||||
cJSON_Delete(root);
|
||||
return JsonResponse("error", "missing_type");
|
||||
}
|
||||
|
||||
if (std::strcmp(type, "discover") == 0 || std::strcmp(type, "info") == 0) {
|
||||
const std::string response = discoveryResponseJson();
|
||||
cJSON_Delete(root);
|
||||
return response;
|
||||
}
|
||||
|
||||
if (std::strcmp(type, "dali") == 0) {
|
||||
cJSON* payload = cJSON_GetObjectItemCaseSensitive(root, "data");
|
||||
std::vector<uint8_t> frame;
|
||||
if (cJSON_IsString(payload) && payload->valuestring != nullptr) {
|
||||
frame = BytesFromJsonString(payload->valuestring);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
if (frame.empty()) {
|
||||
return JsonResponse("daliResp", "invalid");
|
||||
}
|
||||
const bool accepted = enqueueControlFrameForTargets(frame);
|
||||
return JsonResponse("daliResp", accepted ? "ok" : "rejected");
|
||||
}
|
||||
|
||||
if (std::strcmp(type, "setconfig") == 0) {
|
||||
cJSON* payload = cJSON_GetObjectItemCaseSensitive(root, "data");
|
||||
const std::string response = setEthernetConfigFromJson(payload);
|
||||
cJSON_Delete(root);
|
||||
return response;
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
return JsonResponse("error", "unsupported");
|
||||
}
|
||||
|
||||
void GatewayNetworkService::handleWifiControl(uint8_t mode) {
|
||||
@@ -1550,6 +1790,12 @@ std::string GatewayNetworkService::deviceInfoJson() const {
|
||||
if (eth != nullptr) {
|
||||
cJSON_AddStringToObject(eth, "mac", info.eth->mac.c_str());
|
||||
cJSON_AddStringToObject(eth, "IP", info.eth->ip.c_str());
|
||||
if (const auto eth_config = runtime_.ethernetConfig()) {
|
||||
cJSON_AddStringToObject(eth, "configIp", eth_config->ip.c_str());
|
||||
cJSON_AddStringToObject(eth, "configMask", eth_config->mask.c_str());
|
||||
cJSON_AddStringToObject(eth, "configGw", eth_config->gateway.c_str());
|
||||
cJSON_AddStringToObject(eth, "configDns", eth_config->dns.c_str());
|
||||
}
|
||||
cJSON_AddItemToObject(root, "ethInfo", eth);
|
||||
}
|
||||
}
|
||||
@@ -1571,6 +1817,139 @@ std::string GatewayNetworkService::deviceInfoDoubleEncodedJson() const {
|
||||
return rendered;
|
||||
}
|
||||
|
||||
std::string GatewayNetworkService::discoveryResponseJson() const {
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
if (root == nullptr) {
|
||||
return {};
|
||||
}
|
||||
cJSON_AddStringToObject(root, "type", "discoverResp");
|
||||
cJSON_AddStringToObject(root, "status", "ok");
|
||||
cJSON_AddNumberToObject(root, "udpPort", config_.udp_port);
|
||||
cJSON_AddNumberToObject(root, "tcpPort", config_.tcp_control_port);
|
||||
cJSON_AddBoolToObject(root, "udpControl", config_.udp_enabled);
|
||||
cJSON_AddBoolToObject(root, "tcpControl", config_.tcp_control_enabled);
|
||||
|
||||
const auto info = runtime_.deviceInfo();
|
||||
cJSON_AddStringToObject(root, "serialId", info.serial_id.c_str());
|
||||
cJSON_AddStringToObject(root, "project", info.project.c_str());
|
||||
cJSON_AddStringToObject(root, "version", info.version.c_str());
|
||||
cJSON_AddStringToObject(root, "deviceName", runtime_.deviceName().c_str());
|
||||
if (info.eth.has_value()) {
|
||||
cJSON_AddStringToObject(root, "ethMac", info.eth->mac.c_str());
|
||||
cJSON_AddStringToObject(root, "ethIp", info.eth->ip.c_str());
|
||||
}
|
||||
if (const auto eth_config = runtime_.ethernetConfig()) {
|
||||
cJSON* config = cJSON_CreateObject();
|
||||
if (config != nullptr) {
|
||||
cJSON_AddStringToObject(config, "ip", eth_config->ip.c_str());
|
||||
cJSON_AddStringToObject(config, "mask", eth_config->mask.c_str());
|
||||
cJSON_AddStringToObject(config, "gw", eth_config->gateway.c_str());
|
||||
cJSON_AddStringToObject(config, "dns", eth_config->dns.c_str());
|
||||
cJSON_AddItemToObject(root, "ethConfig", config);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* channels = cJSON_CreateArray();
|
||||
if (channels != nullptr) {
|
||||
for (const auto& channel : controller_.snapshot().channels) {
|
||||
cJSON* item = cJSON_CreateObject();
|
||||
if (item == nullptr) {
|
||||
continue;
|
||||
}
|
||||
cJSON_AddNumberToObject(item, "channel", channel.channel_index + 1);
|
||||
cJSON_AddNumberToObject(item, "gw", channel.gateway_id);
|
||||
cJSON_AddNumberToObject(item, "gatewayGroup", channel.gateway_group);
|
||||
cJSON_AddStringToObject(item, "name", channel.name.c_str());
|
||||
cJSON_AddStringToObject(item, "phy", channel.phy.c_str());
|
||||
cJSON_AddItemToArray(channels, item);
|
||||
}
|
||||
cJSON_AddItemToObject(root, "channels", channels);
|
||||
}
|
||||
|
||||
const std::string rendered = PrintJson(root);
|
||||
cJSON_Delete(root);
|
||||
return rendered;
|
||||
}
|
||||
|
||||
std::string GatewayNetworkService::setEthernetConfigFromJson(cJSON* data) {
|
||||
if (!cJSON_IsObject(data)) {
|
||||
return JsonResponse("setconfigResp", "invalid");
|
||||
}
|
||||
|
||||
EthernetConfig config;
|
||||
if (const char* value = JsonString(data, "ip")) {
|
||||
config.ip = value;
|
||||
}
|
||||
if (const char* value = JsonString(data, "mask")) {
|
||||
config.mask = value;
|
||||
}
|
||||
if (const char* value = JsonString(data, "gw")) {
|
||||
config.gateway = value;
|
||||
}
|
||||
if (const char* value = JsonString(data, "gateway")) {
|
||||
config.gateway = value;
|
||||
}
|
||||
if (const char* value = JsonString(data, "dns")) {
|
||||
config.dns = value;
|
||||
}
|
||||
|
||||
const bool dhcp = config.ip.empty() || config.ip == "0.0.0.0";
|
||||
if (!dhcp) {
|
||||
esp_ip4_addr_t parsed = {};
|
||||
if (!ParseIpv4(config.ip, &parsed) || !ParseIpv4(config.mask, &parsed) ||
|
||||
!ParseIpv4(config.gateway, &parsed) ||
|
||||
(!config.dns.empty() && !ParseIpv4(config.dns, &parsed))) {
|
||||
return JsonResponse("setconfigResp", "invalid");
|
||||
}
|
||||
}
|
||||
|
||||
const bool stored = dhcp ? runtime_.clearEthernetConfig() : runtime_.setEthernetConfig(config);
|
||||
if (!stored) {
|
||||
return JsonResponse("setconfigResp", "storage_error");
|
||||
}
|
||||
if (applyEthernetConfig() != ESP_OK) {
|
||||
return JsonResponse("setconfigResp", "apply_error");
|
||||
}
|
||||
return JsonResponse("setconfigResp", "ok");
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::applyEthernetConfig() {
|
||||
if (eth_netif_ == nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const auto config = runtime_.ethernetConfig();
|
||||
if (!config.has_value() || config->ip.empty() || config->ip == "0.0.0.0") {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcpc_start(eth_netif_));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t ip_info = {};
|
||||
if (!ParseIpv4(config->ip, &ip_info.ip) || !ParseIpv4(config->mask, &ip_info.netmask) ||
|
||||
!ParseIpv4(config->gateway, &ip_info.gw)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcpc_stop(eth_netif_));
|
||||
esp_err_t err = esp_netif_set_ip_info(eth_netif_, &ip_info);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
if (!config->dns.empty()) {
|
||||
esp_netif_dns_info_t dns = {};
|
||||
if (!ParseIpv4(config->dns, &dns.ip.u_addr.ip4)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
dns.ip.type = ESP_IPADDR_TYPE_V4;
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_set_dns_info(eth_netif_,
|
||||
ESP_NETIF_DNS_MAIN, &dns));
|
||||
}
|
||||
ESP_LOGI(kTag, "Ethernet static config applied ip=%s mask=%s gw=%s dns=%s",
|
||||
config->ip.c_str(), config->mask.c_str(), config->gateway.c_str(),
|
||||
config->dns.c_str());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
std::string GatewayNetworkService::gatewaySnapshotJson() {
|
||||
const auto snapshot = controller_.snapshot();
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
@@ -1601,6 +1980,7 @@ std::string GatewayNetworkService::gatewaySnapshotJson() {
|
||||
}
|
||||
cJSON_AddNumberToObject(item, "channel", channel.channel_index + 1);
|
||||
cJSON_AddNumberToObject(item, "gw", channel.gateway_id);
|
||||
cJSON_AddNumberToObject(item, "gatewayGroup", channel.gateway_group);
|
||||
cJSON_AddStringToObject(item, "name", channel.name.c_str());
|
||||
cJSON_AddStringToObject(item, "phy", channel.phy.c_str());
|
||||
cJSON_AddNumberToObject(item, "sceneMaskLow", channel.scene_mask_low);
|
||||
|
||||
@@ -35,6 +35,13 @@ struct EthernetInfo {
|
||||
std::string ip;
|
||||
};
|
||||
|
||||
struct EthernetConfig {
|
||||
std::string ip;
|
||||
std::string mask;
|
||||
std::string gateway;
|
||||
std::string dns;
|
||||
};
|
||||
|
||||
struct GatewayRuntimeConfig {
|
||||
std::string_view project_name;
|
||||
std::string_view version;
|
||||
@@ -73,6 +80,9 @@ class GatewaySettingsStore {
|
||||
std::optional<std::string> getWifiPassword() const;
|
||||
bool setWifiCredentials(std::string_view ssid, std::string_view password);
|
||||
bool clearWifiCredentials();
|
||||
std::optional<EthernetConfig> getEthernetConfig() const;
|
||||
bool setEthernetConfig(const EthernetConfig& config);
|
||||
bool clearEthernetConfig();
|
||||
|
||||
std::string getDeviceName(std::string_view fallback) const;
|
||||
bool setDeviceName(std::string_view name);
|
||||
@@ -80,12 +90,15 @@ class GatewaySettingsStore {
|
||||
bool setGatewayName(uint8_t gateway_id, std::string_view name);
|
||||
uint8_t getChannelGatewayId(uint8_t channel_index, uint8_t fallback) const;
|
||||
bool setChannelGatewayId(uint8_t channel_index, uint8_t gateway_id);
|
||||
uint8_t getChannelGatewayGroup(uint8_t channel_index, uint8_t fallback) const;
|
||||
bool setChannelGatewayGroup(uint8_t channel_index, uint8_t gateway_group);
|
||||
|
||||
private:
|
||||
std::optional<std::string> readString(std::string_view key) const;
|
||||
bool writeString(std::string_view key, std::string_view value);
|
||||
std::string makeGatewayNameKey(uint8_t gateway_id) const;
|
||||
std::string makeChannelGatewayIdKey(uint8_t channel_index) const;
|
||||
std::string makeChannelGatewayGroupKey(uint8_t channel_index) const;
|
||||
|
||||
mutable nvs_handle_t handle_{0};
|
||||
};
|
||||
@@ -133,6 +146,9 @@ class GatewayRuntime {
|
||||
void setEthernetInfo(EthernetInfo info);
|
||||
void clearEthernetInfo();
|
||||
void clearEthernetIp();
|
||||
std::optional<EthernetConfig> ethernetConfig() const;
|
||||
bool setEthernetConfig(const EthernetConfig& config);
|
||||
bool clearEthernetConfig();
|
||||
void setCommandAddressResolver(std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver);
|
||||
|
||||
GatewayDeviceInfo deviceInfo() const;
|
||||
@@ -142,6 +158,8 @@ class GatewayRuntime {
|
||||
bool setCacheEnabled(bool enabled);
|
||||
uint8_t gatewayIdForChannel(uint8_t channel_index, uint8_t fallback) const;
|
||||
bool setGatewayIdForChannel(uint8_t channel_index, uint8_t gateway_id);
|
||||
uint8_t gatewayGroupForChannel(uint8_t channel_index, uint8_t fallback = 0) const;
|
||||
bool setGatewayGroupForChannel(uint8_t channel_index, uint8_t gateway_group);
|
||||
std::string deviceName() const;
|
||||
bool setDeviceName(std::string_view name);
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
@@ -175,6 +193,7 @@ class GatewayRuntime {
|
||||
mutable std::optional<std::string> device_name_;
|
||||
mutable std::map<uint8_t, std::string> gateway_names_;
|
||||
mutable std::map<uint8_t, uint8_t> channel_gateway_ids_;
|
||||
mutable std::map<uint8_t, uint8_t> channel_gateway_groups_;
|
||||
size_t gateway_count_{0};
|
||||
bool ble_enabled_{false};
|
||||
bool cache_enabled_{true};
|
||||
|
||||
@@ -22,6 +22,10 @@ constexpr const char* kCacheEnabledKey = "cache_enabled";
|
||||
constexpr const char* kDeviceNameKey = "device_name";
|
||||
constexpr const char* kWifiSsidKey = "wifi_ssid";
|
||||
constexpr const char* kWifiPasswordKey = "wifi_passwd";
|
||||
constexpr const char* kEthIpKey = "eth_ip";
|
||||
constexpr const char* kEthMaskKey = "eth_mask";
|
||||
constexpr const char* kEthGatewayKey = "eth_gw";
|
||||
constexpr const char* kEthDnsKey = "eth_dns";
|
||||
constexpr size_t kMaxGatewayNameBytes = 32;
|
||||
constexpr uint8_t kCommandFramePrefix0 = 0x28;
|
||||
constexpr uint8_t kCommandFramePrefix1 = 0x01;
|
||||
@@ -175,6 +179,54 @@ bool GatewaySettingsStore::clearWifiCredentials() {
|
||||
return ssid_err == ESP_OK && password_err == ESP_OK && nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<EthernetConfig> GatewaySettingsStore::getEthernetConfig() const {
|
||||
EthernetConfig config;
|
||||
if (const auto value = readString(kEthIpKey)) {
|
||||
config.ip = *value;
|
||||
}
|
||||
if (const auto value = readString(kEthMaskKey)) {
|
||||
config.mask = *value;
|
||||
}
|
||||
if (const auto value = readString(kEthGatewayKey)) {
|
||||
config.gateway = *value;
|
||||
}
|
||||
if (const auto value = readString(kEthDnsKey)) {
|
||||
config.dns = *value;
|
||||
}
|
||||
if (config.ip.empty() && config.mask.empty() && config.gateway.empty() &&
|
||||
config.dns.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setEthernetConfig(const EthernetConfig& config) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const esp_err_t ip_err = nvs_set_str(handle_, kEthIpKey, config.ip.c_str());
|
||||
const esp_err_t mask_err = nvs_set_str(handle_, kEthMaskKey, config.mask.c_str());
|
||||
const esp_err_t gw_err = nvs_set_str(handle_, kEthGatewayKey, config.gateway.c_str());
|
||||
const esp_err_t dns_err = nvs_set_str(handle_, kEthDnsKey, config.dns.c_str());
|
||||
return ip_err == ESP_OK && mask_err == ESP_OK && gw_err == ESP_OK && dns_err == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::clearEthernetConfig() {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto erase = [this](const char* key) {
|
||||
const esp_err_t err = nvs_erase_key(handle_, key);
|
||||
return err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND;
|
||||
};
|
||||
const bool ok = erase(kEthIpKey) && erase(kEthMaskKey) && erase(kEthGatewayKey) &&
|
||||
erase(kEthDnsKey);
|
||||
return ok && nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::getDeviceName(std::string_view fallback) const {
|
||||
const auto value = readString(kDeviceNameKey);
|
||||
if (!value.has_value() || value->empty()) {
|
||||
@@ -228,6 +280,31 @@ bool GatewaySettingsStore::setChannelGatewayId(uint8_t channel_index,
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t GatewaySettingsStore::getChannelGatewayGroup(uint8_t channel_index,
|
||||
uint8_t fallback) const {
|
||||
if (handle_ == 0) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
uint8_t gateway_group = fallback;
|
||||
if (nvs_get_u8(handle_, makeChannelGatewayGroupKey(channel_index).c_str(),
|
||||
&gateway_group) != ESP_OK) {
|
||||
return fallback;
|
||||
}
|
||||
return gateway_group;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setChannelGatewayGroup(uint8_t channel_index,
|
||||
uint8_t gateway_group) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nvs_set_u8(handle_, makeChannelGatewayGroupKey(channel_index).c_str(),
|
||||
gateway_group) == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<std::string> GatewaySettingsStore::readString(std::string_view key) const {
|
||||
if (handle_ == 0) {
|
||||
return std::nullopt;
|
||||
@@ -268,6 +345,12 @@ std::string GatewaySettingsStore::makeChannelGatewayIdKey(uint8_t channel_index)
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
std::string GatewaySettingsStore::makeChannelGatewayGroupKey(uint8_t channel_index) const {
|
||||
char key[24] = {0};
|
||||
std::snprintf(key, sizeof(key), "dali_ch_grp_%u", channel_index);
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
GatewayRuntime::GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
|
||||
DaliDomainService* dali_domain)
|
||||
: profile_(profile),
|
||||
@@ -371,6 +454,7 @@ GatewayRuntime::CommandPriority GatewayRuntime::classifyCommandPriority(
|
||||
opcode == 0x08 || opcode == 0x10 || opcode == 0x11 || opcode == 0x12 || opcode == 0x13 ||
|
||||
opcode == 0x0B || opcode == 0x17 || opcode == 0x18 || opcode == 0x37 || opcode == 0x38 ||
|
||||
opcode == 0x60 || opcode == 0x61 || opcode == 0x62 || opcode == 0x66 || opcode == 0x67 ||
|
||||
opcode == 0x6A ||
|
||||
(opcode == 0x30 && addr == 0)) {
|
||||
return CommandPriority::kControl;
|
||||
}
|
||||
@@ -514,6 +598,18 @@ void GatewayRuntime::clearEthernetIp() {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<EthernetConfig> GatewayRuntime::ethernetConfig() const {
|
||||
return settings_.getEthernetConfig();
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setEthernetConfig(const EthernetConfig& config) {
|
||||
return settings_.setEthernetConfig(config);
|
||||
}
|
||||
|
||||
bool GatewayRuntime::clearEthernetConfig() {
|
||||
return settings_.clearEthernetConfig();
|
||||
}
|
||||
|
||||
void GatewayRuntime::setCommandAddressResolver(
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver) {
|
||||
LockGuard guard(command_lock_);
|
||||
@@ -599,6 +695,27 @@ bool GatewayRuntime::setGatewayIdForChannel(uint8_t channel_index, uint8_t gatew
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t GatewayRuntime::gatewayGroupForChannel(uint8_t channel_index, uint8_t fallback) const {
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = channel_gateway_groups_.find(channel_index);
|
||||
if (cached != channel_gateway_groups_.end()) {
|
||||
return cached->second;
|
||||
}
|
||||
const uint8_t gateway_group = settings_.getChannelGatewayGroup(channel_index, fallback);
|
||||
channel_gateway_groups_[channel_index] = gateway_group;
|
||||
return gateway_group;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setGatewayGroupForChannel(uint8_t channel_index, uint8_t gateway_group) {
|
||||
if (!settings_.setChannelGatewayGroup(channel_index, gateway_group)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LockGuard guard(command_lock_);
|
||||
channel_gateway_groups_[channel_index] = gateway_group;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::deviceName() const {
|
||||
LockGuard guard(command_lock_);
|
||||
if (device_name_.has_value()) {
|
||||
|
||||
Reference in New Issue
Block a user