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:
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user