feat(gateway): enhance MAC DALI command handling and increase UDP buffer size

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-08-01 17:00:22 +08:00
parent 555c2ec5bd
commit da67d9ff32
2 changed files with 82 additions and 4 deletions
+4
View File
@@ -100,6 +100,10 @@ ports:
- `{"type":"discover"}` returns `discoverResp` with device identity, UDP/TCP - `{"type":"discover"}` returns `discoverResp` with device identity, UDP/TCP
control ports, Ethernet status/config, and channel gateway/group data. control ports, Ethernet status/config, and channel gateway/group data.
- `{"type":"dali","data":"<hex gateway frame>"}` enqueues a raw gateway command. - `{"type":"dali","data":"<hex gateway frame>"}` enqueues a raw gateway command.
- `{"type":"macDali","mac":"<ethernet MAC or 3-byte serial>","data":"<hex gateway frame>"}`
enqueues a raw command only on the matching gateway. The gateway broadcasts
command responses back to the sender's UDP port, allowing local-link control
when its IPv4 address is unknown or configured for the wrong subnet.
- `{"type":"setconfig","data":{"ip":"...","mask":"...","gw":"...","dns":"..."}}` - `{"type":"setconfig","data":{"ip":"...","mask":"...","gw":"...","dns":"..."}}`
stores and applies static Ethernet IPv4 settings. `ip` empty or `0.0.0.0` stores and applies static Ethernet IPv4 settings. `ip` empty or `0.0.0.0`
clears static config and returns Ethernet to DHCP. clears static config and returns Ethernet to DHCP.
@@ -37,7 +37,7 @@ namespace {
constexpr const char* kTag = "gateway_network"; constexpr const char* kTag = "gateway_network";
constexpr const char* kSetupApSsid = "LAMMIN_Gateway"; constexpr const char* kSetupApSsid = "LAMMIN_Gateway";
constexpr size_t kUdpBufferSize = 256; constexpr size_t kUdpBufferSize = 1024;
constexpr size_t kTcpControlBufferSize = 512; constexpr size_t kTcpControlBufferSize = 512;
constexpr uint8_t kEspNowBroadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; constexpr uint8_t kEspNowBroadcastMac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
@@ -202,6 +202,56 @@ bool IsLikelyJson(const uint8_t* data, size_t len) {
return index < len && data[index] == '{'; return index < len && data[index] == '{';
} }
std::string NormalizeNetworkIdentity(std::string_view value) {
std::string normalized;
normalized.reserve(value.size());
for (const unsigned char character : value) {
if (std::isxdigit(character) != 0) {
normalized.push_back(static_cast<char>(std::tolower(character)));
}
}
return normalized;
}
bool MatchesNetworkIdentity(const GatewayDeviceInfo& info, std::string_view value) {
const std::string target = NormalizeNetworkIdentity(value);
if (target.size() != 6 && target.size() != 12) {
return false;
}
const auto matches = [&target](std::string_view candidate) {
const std::string normalized = NormalizeNetworkIdentity(candidate);
return normalized == target ||
(target.size() == 6 && normalized.size() >= 6 &&
normalized.compare(normalized.size() - 6, 6, target) == 0);
};
return matches(info.serial_id) ||
(info.eth.has_value() && matches(info.eth->mac)) ||
(info.wlan.has_value() && matches(info.wlan->mac));
}
enum class MacControlDisposition { kNotMacControl, kMatches, kOtherGateway };
MacControlDisposition InspectMacControl(const uint8_t* data, size_t len,
const GatewayDeviceInfo& info) {
if (!IsLikelyJson(data, len)) {
return MacControlDisposition::kNotMacControl;
}
cJSON* root = cJSON_ParseWithLength(reinterpret_cast<const char*>(data), len);
if (root == nullptr) {
return MacControlDisposition::kNotMacControl;
}
const char* type = JsonString(root, "type");
const char* mac = JsonString(root, "mac");
const bool is_mac_control = type != nullptr && std::strcmp(type, "macDali") == 0;
const bool matches = is_mac_control && mac != nullptr && MatchesNetworkIdentity(info, mac);
cJSON_Delete(root);
if (!is_mac_control) {
return MacControlDisposition::kNotMacControl;
}
return matches ? MacControlDisposition::kMatches
: MacControlDisposition::kOtherGateway;
}
std::string PrintJson(cJSON* node) { std::string PrintJson(cJSON* node) {
if (node == nullptr) { if (node == nullptr) {
return {}; return {};
@@ -1480,6 +1530,9 @@ void GatewayNetworkService::udpTaskLoop() {
return; return;
} }
int broadcast = 1;
setsockopt(udp_socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
sockaddr_in local_addr = {}; sockaddr_in local_addr = {};
local_addr.sin_family = AF_INET; local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(config_.udp_port); local_addr.sin_port = htons(config_.udp_port);
@@ -1505,6 +1558,17 @@ void GatewayNetworkService::udpTaskLoop() {
continue; continue;
} }
const auto mac_control = InspectMacControl(
buffer, static_cast<size_t>(read_len), runtime_.deviceInfo());
if (mac_control == MacControlDisposition::kOtherGateway) {
continue;
}
if (mac_control == MacControlDisposition::kMatches &&
remote_addr.ss_family == AF_INET) {
auto* remote_v4 = reinterpret_cast<sockaddr_in*>(&remote_addr);
remote_v4->sin_addr.s_addr = htonl(INADDR_BROADCAST);
}
{ {
LockGuard guard(udp_lock_); LockGuard guard(udp_lock_);
udp_remote_addr_ = remote_addr; udp_remote_addr_ = remote_addr;
@@ -1765,7 +1829,15 @@ std::optional<std::string> GatewayNetworkService::handleJsonControlFrame(const u
return response; return response;
} }
if (std::strcmp(type, "dali") == 0) { if (std::strcmp(type, "dali") == 0 || std::strcmp(type, "macDali") == 0) {
const bool mac_dali = std::strcmp(type, "macDali") == 0;
if (mac_dali) {
const char* mac = JsonString(root, "mac");
if (mac == nullptr || !MatchesNetworkIdentity(runtime_.deviceInfo(), mac)) {
cJSON_Delete(root);
return JsonResponse("macDaliResp", "not_target");
}
}
cJSON* payload = cJSON_GetObjectItemCaseSensitive(root, "data"); cJSON* payload = cJSON_GetObjectItemCaseSensitive(root, "data");
std::vector<uint8_t> frame; std::vector<uint8_t> frame;
if (cJSON_IsString(payload) && payload->valuestring != nullptr) { if (cJSON_IsString(payload) && payload->valuestring != nullptr) {
@@ -1773,10 +1845,12 @@ std::optional<std::string> GatewayNetworkService::handleJsonControlFrame(const u
} }
cJSON_Delete(root); cJSON_Delete(root);
if (frame.empty()) { if (frame.empty()) {
return JsonResponse("daliResp", "invalid"); return JsonResponse(mac_dali ? "macDaliResp" : "daliResp",
"invalid");
} }
const bool accepted = enqueueControlFrameForTargets(frame); const bool accepted = enqueueControlFrameForTargets(frame);
return JsonResponse("daliResp", accepted ? "ok" : "rejected"); return JsonResponse(mac_dali ? "macDaliResp" : "daliResp",
accepted ? "ok" : "rejected");
} }
if (std::strcmp(type, "setconfig") == 0) { if (std::strcmp(type, "setconfig") == 0) {