feat(gateway): integrate bridge service into gateway controller for transport handling

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-19 05:52:36 +08:00
parent 3bc5355041
commit 226855362b
6 changed files with 391 additions and 112 deletions
@@ -61,6 +61,7 @@ class GatewayBridgeService {
const std::string& query = {});
GatewayBridgeHttpResponse handlePost(const std::string& action, int gateway_id,
const std::string& body);
std::string handleTransportRequest(uint8_t gateway_id, std::string_view request);
private:
struct ChannelRuntime;
+244 -111
View File
@@ -257,6 +257,66 @@ std::optional<uint8_t> JsonGatewayId(const cJSON* root) {
return static_cast<uint8_t>(gateway);
}
int BridgeTransportStatusCode(esp_err_t err) {
if (err == ESP_OK) {
return 200;
}
if (err == ESP_ERR_INVALID_ARG) {
return 400;
}
if (err == ESP_ERR_NOT_FOUND) {
return 404;
}
return 500;
}
std::string BuildBridgeTransportEnvelope(const GatewayBridgeHttpResponse& response) {
cJSON* root = cJSON_CreateObject();
if (root == nullptr) {
return "{}";
}
cJSON_AddNumberToObject(root, "statusCode",
static_cast<double>(BridgeTransportStatusCode(response.err)));
if (response.err == ESP_OK) {
cJSON* data = response.body.empty()
? cJSON_CreateObject()
: cJSON_ParseWithLength(response.body.data(), response.body.size());
if (data == nullptr) {
cJSON_AddNumberToObject(root, "statusCode", 500);
cJSON_AddStringToObject(root, "error", "bridge response is not valid JSON");
cJSON_AddStringToObject(root, "message", "bridge response is not valid JSON");
} else {
cJSON_AddItemToObject(root, "data", data);
}
const std::string body = PrintJson(root);
cJSON_Delete(root);
return body;
}
const char* message = "bridge request failed";
cJSON* details = response.body.empty()
? nullptr
: cJSON_ParseWithLength(response.body.data(), response.body.size());
if (details != nullptr && cJSON_IsObject(details)) {
if (const char* error = JsonString(details, "error")) {
message = error;
}
cJSON_AddItemToObject(root, "details", details);
details = nullptr;
} else {
cJSON_Delete(details);
if (!response.body.empty()) {
message = response.body.c_str();
}
}
cJSON_AddStringToObject(root, "error", message);
cJSON_AddStringToObject(root, "message", message);
const std::string body = PrintJson(root);
cJSON_Delete(root);
return body;
}
std::string QueryValue(std::string_view query, std::string_view key) {
if (query.empty() || key.empty()) {
return {};
@@ -2131,6 +2191,125 @@ struct GatewayBridgeService::ChannelRuntime {
return JsonOk(BridgeResultToCjson(result));
}
cJSON* knxStatusCjson() const {
cJSON* knx_json = cJSON_CreateObject();
if (knx_json == nullptr) {
return nullptr;
}
auto* endpoint_runtime = service.knx_endpoint_runtime_;
if (endpoint_runtime == nullptr) {
endpoint_runtime = const_cast<GatewayBridgeService&>(service).selectKnxEndpointRuntime();
}
bool programming_mode = false;
bool programming_control_available = false;
int endpoint_owner_gateway_id = -1;
if (endpoint_runtime != nullptr) {
LockGuard owner_guard(endpoint_runtime->lock);
endpoint_owner_gateway_id = endpoint_runtime->channel.gateway_id;
programming_control_available = endpoint_runtime->knx_router != nullptr &&
endpoint_runtime->knx_router->started();
if (programming_control_available) {
programming_mode = endpoint_runtime->knx_router->programmingMode();
}
}
const auto effective_knx =
knx_config.has_value() ? knx_config : service_config.default_knx_config;
cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled);
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
cJSON_AddBoolToObject(knx_json, "started", knx_started);
cJSON_AddBoolToObject(knx_json, "routerReady", knx_router != nullptr && knx_router->started());
cJSON_AddBoolToObject(knx_json, "programmingMode", programming_mode);
cJSON_AddBoolToObject(knx_json, "programmingControlAvailable",
programming_control_available);
cJSON_AddBoolToObject(knx_json, "endpointOwner",
endpoint_owner_gateway_id == channel.gateway_id);
if (endpoint_owner_gateway_id >= 0) {
cJSON_AddNumberToObject(knx_json, "endpointOwnerGatewayId",
endpoint_owner_gateway_id);
}
const std::string router_error = knx_router == nullptr ? "" : knx_router->lastError();
cJSON_AddStringToObject(knx_json, "lastError",
knx_last_error.empty() ? router_error.c_str()
: knx_last_error.c_str());
cJSON* security_json = cJSON_CreateObject();
if (security_json != nullptr) {
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
cJSON_AddBoolToObject(security_json, "dataSecureCompiled", true);
#else
cJSON_AddBoolToObject(security_json, "dataSecureCompiled", false);
#endif
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", true);
#else
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", false);
#endif
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
cJSON_AddBoolToObject(security_json, "knxnetIpSecureServicesRecognized", true);
#else
cJSON_AddBoolToObject(security_json, "knxnetIpSecureServicesRecognized", false);
#endif
cJSON_AddBoolToObject(security_json, "knxnetIpSecureImplemented", false);
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
cJSON_AddBoolToObject(security_json, "developmentEndpointsEnabled", true);
#else
cJSON_AddBoolToObject(security_json, "developmentEndpointsEnabled", false);
#endif
#if defined(CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS)
cJSON_AddBoolToObject(security_json, "plainNvsStorage", true);
cJSON_AddStringToObject(security_json, "storage", "plain_nvs_development");
#else
cJSON_AddBoolToObject(security_json, "plainNvsStorage", false);
cJSON_AddStringToObject(security_json, "storage", "none");
#endif
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
const auto fdsk_info = openknx::LoadFactoryFdskInfo();
cJSON* fdsk_json = FactoryFdskInfoToCjson(fdsk_info, true);
if (fdsk_json != nullptr) {
cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json);
}
cJSON* certificate_json =
FactoryCertificateToCjson(openknx::BuildFactoryCertificatePayload(), false);
if (certificate_json != nullptr) {
cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json);
}
cJSON* failures_json = SecurityFailuresToCjson();
if (failures_json != nullptr) {
cJSON_AddItemToObject(security_json, "failures", failures_json);
}
#endif
cJSON_AddItemToObject(knx_json, "security", security_json);
}
if (effective_knx.has_value()) {
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", effective_knx->dali_router_enabled);
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled", effective_knx->ip_router_enabled);
cJSON_AddBoolToObject(knx_json, "tunnelEnabled", effective_knx->tunnel_enabled);
cJSON_AddBoolToObject(knx_json, "multicastEnabled", effective_knx->multicast_enabled);
cJSON_AddBoolToObject(knx_json, "etsDatabaseEnabled", effective_knx->ets_database_enabled);
cJSON_AddNumberToObject(knx_json, "etsBindingCount",
knx == nullptr ? 0 : knx->etsBindingCount());
cJSON_AddStringToObject(knx_json, "mappingMode",
GatewayKnxMappingModeToString(effective_knx->mapping_mode));
cJSON_AddNumberToObject(knx_json, "mainGroup", effective_knx->main_group);
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
cJSON_AddStringToObject(knx_json, "multicastAddress",
effective_knx->multicast_address.c_str());
cJSON_AddNumberToObject(knx_json, "ipInterfaceIndividualAddress",
effective_knx->ip_interface_individual_address);
cJSON_AddNumberToObject(knx_json, "individualAddress",
effective_knx->individual_address);
cJSON* serial_json = cJSON_CreateObject();
if (serial_json != nullptr) {
cJSON_AddNumberToObject(serial_json, "uartPort", effective_knx->tp_uart.uart_port);
cJSON_AddNumberToObject(serial_json, "txPin", effective_knx->tp_uart.tx_pin);
cJSON_AddNumberToObject(serial_json, "rxPin", effective_knx->tp_uart.rx_pin);
cJSON_AddNumberToObject(serial_json, "baudrate", effective_knx->tp_uart.baudrate);
cJSON_AddBoolToObject(serial_json, "nineBitMode", effective_knx->tp_uart.nine_bit_mode);
cJSON_AddItemToObject(knx_json, "tpUart", serial_json);
}
}
return knx_json;
}
cJSON* statusCjson() const {
cJSON* root = cJSON_CreateObject();
if (root == nullptr) {
@@ -2202,118 +2381,8 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON_AddItemToObject(root, "bacnet", bacnet_json);
}
cJSON* knx_json = cJSON_CreateObject();
cJSON* knx_json = knxStatusCjson();
if (knx_json != nullptr) {
auto* endpoint_runtime = service.knx_endpoint_runtime_;
if (endpoint_runtime == nullptr) {
endpoint_runtime = const_cast<GatewayBridgeService&>(service).selectKnxEndpointRuntime();
}
bool programming_mode = false;
bool programming_control_available = false;
int endpoint_owner_gateway_id = -1;
if (endpoint_runtime != nullptr) {
LockGuard owner_guard(endpoint_runtime->lock);
endpoint_owner_gateway_id = endpoint_runtime->channel.gateway_id;
programming_control_available = endpoint_runtime->knx_router != nullptr &&
endpoint_runtime->knx_router->started();
if (programming_control_available) {
programming_mode = endpoint_runtime->knx_router->programmingMode();
}
}
const auto effective_knx = knx_config.has_value() ? knx_config : service_config.default_knx_config;
cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled);
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
cJSON_AddBoolToObject(knx_json, "started", knx_started);
cJSON_AddBoolToObject(knx_json, "routerReady", knx_router != nullptr && knx_router->started());
cJSON_AddBoolToObject(knx_json, "programmingMode", programming_mode);
cJSON_AddBoolToObject(knx_json, "programmingControlAvailable",
programming_control_available);
cJSON_AddBoolToObject(knx_json, "endpointOwner",
endpoint_owner_gateway_id == channel.gateway_id);
if (endpoint_owner_gateway_id >= 0) {
cJSON_AddNumberToObject(knx_json, "endpointOwnerGatewayId",
endpoint_owner_gateway_id);
}
const std::string router_error = knx_router == nullptr ? "" : knx_router->lastError();
cJSON_AddStringToObject(knx_json, "lastError",
knx_last_error.empty() ? router_error.c_str()
: knx_last_error.c_str());
cJSON* security_json = cJSON_CreateObject();
if (security_json != nullptr) {
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
cJSON_AddBoolToObject(security_json, "dataSecureCompiled", true);
#else
cJSON_AddBoolToObject(security_json, "dataSecureCompiled", false);
#endif
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", true);
#else
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", false);
#endif
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
cJSON_AddBoolToObject(security_json, "knxnetIpSecureServicesRecognized", true);
#else
cJSON_AddBoolToObject(security_json, "knxnetIpSecureServicesRecognized", false);
#endif
cJSON_AddBoolToObject(security_json, "knxnetIpSecureImplemented", false);
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
cJSON_AddBoolToObject(security_json, "developmentEndpointsEnabled", true);
#else
cJSON_AddBoolToObject(security_json, "developmentEndpointsEnabled", false);
#endif
#if defined(CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS)
cJSON_AddBoolToObject(security_json, "plainNvsStorage", true);
cJSON_AddStringToObject(security_json, "storage", "plain_nvs_development");
#else
cJSON_AddBoolToObject(security_json, "plainNvsStorage", false);
cJSON_AddStringToObject(security_json, "storage", "none");
#endif
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
const auto fdsk_info = openknx::LoadFactoryFdskInfo();
cJSON* fdsk_json = FactoryFdskInfoToCjson(fdsk_info, false);
if (fdsk_json != nullptr) {
cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json);
}
cJSON* certificate_json = FactoryCertificateToCjson(
openknx::BuildFactoryCertificatePayload(), false);
if (certificate_json != nullptr) {
cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json);
}
cJSON* failures_json = SecurityFailuresToCjson();
if (failures_json != nullptr) {
cJSON_AddItemToObject(security_json, "failures", failures_json);
}
#endif
cJSON_AddItemToObject(knx_json, "security", security_json);
}
if (effective_knx.has_value()) {
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", effective_knx->dali_router_enabled);
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled", effective_knx->ip_router_enabled);
cJSON_AddBoolToObject(knx_json, "tunnelEnabled", effective_knx->tunnel_enabled);
cJSON_AddBoolToObject(knx_json, "multicastEnabled", effective_knx->multicast_enabled);
cJSON_AddBoolToObject(knx_json, "etsDatabaseEnabled", effective_knx->ets_database_enabled);
cJSON_AddNumberToObject(knx_json, "etsBindingCount",
knx == nullptr ? 0 : knx->etsBindingCount());
cJSON_AddStringToObject(knx_json, "mappingMode",
GatewayKnxMappingModeToString(effective_knx->mapping_mode));
cJSON_AddNumberToObject(knx_json, "mainGroup", effective_knx->main_group);
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
cJSON_AddStringToObject(knx_json, "multicastAddress",
effective_knx->multicast_address.c_str());
cJSON_AddNumberToObject(knx_json, "ipInterfaceIndividualAddress",
effective_knx->ip_interface_individual_address);
cJSON_AddNumberToObject(knx_json, "individualAddress",
effective_knx->individual_address);
cJSON* serial_json = cJSON_CreateObject();
if (serial_json != nullptr) {
cJSON_AddNumberToObject(serial_json, "uartPort", effective_knx->tp_uart.uart_port);
cJSON_AddNumberToObject(serial_json, "txPin", effective_knx->tp_uart.tx_pin);
cJSON_AddNumberToObject(serial_json, "rxPin", effective_knx->tp_uart.rx_pin);
cJSON_AddNumberToObject(serial_json, "baudrate", effective_knx->tp_uart.baudrate);
cJSON_AddBoolToObject(serial_json, "nineBitMode", effective_knx->tp_uart.nine_bit_mode);
cJSON_AddItemToObject(knx_json, "tpUart", serial_json);
}
}
cJSON_AddItemToObject(root, "knx", knx_json);
}
@@ -2333,6 +2402,21 @@ struct GatewayBridgeService::ChannelRuntime {
return root;
}
GatewayBridgeHttpResponse knxStatusJson() const {
cJSON* root = cJSON_CreateObject();
if (root == nullptr) {
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON");
}
cJSON_AddNumberToObject(root, "gatewayId", channel.gateway_id);
cJSON* knx_json = knxStatusCjson();
if (knx_json == nullptr) {
cJSON_Delete(root);
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON");
}
cJSON_AddItemToObject(root, "knx", knx_json);
return JsonOk(root);
}
GatewayBridgeHttpResponse configJson() const {
return GatewayBridgeHttpResponse{ESP_OK,
GatewayBridgeStoredConfigToJson(bridge_config, modbus_config,
@@ -4199,6 +4283,9 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
if (action == "status") {
return JsonOk(runtime->statusCjson());
}
if (action == "knx_status") {
return runtime->knxStatusJson();
}
if (action == "config") {
return runtime->configJson();
}
@@ -4718,4 +4805,50 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
return ErrorResponse(ESP_ERR_INVALID_ARG, "unknown bridge POST action");
}
std::string GatewayBridgeService::handleTransportRequest(uint8_t gateway_id,
std::string_view request) {
cJSON* root = request.empty() ? nullptr : cJSON_ParseWithLength(request.data(), request.size());
if (root == nullptr || !cJSON_IsObject(root)) {
cJSON_Delete(root);
return BuildBridgeTransportEnvelope(
ErrorResponse(ESP_ERR_INVALID_ARG, "invalid bridge request JSON"));
}
const char* action_raw = JsonString(root, "action");
const char* method_raw = JsonString(root, "method");
if (action_raw == nullptr || method_raw == nullptr) {
cJSON_Delete(root);
return BuildBridgeTransportEnvelope(
ErrorResponse(ESP_ERR_INVALID_ARG, "bridge request requires action and method"));
}
const auto request_gateway_id = JsonGatewayId(root);
if (request_gateway_id.has_value() && request_gateway_id.value() != gateway_id) {
cJSON_Delete(root);
return BuildBridgeTransportEnvelope(
ErrorResponse(ESP_ERR_INVALID_ARG, "bridge request gateway id mismatch"));
}
std::string method(method_raw);
std::transform(method.begin(), method.end(), method.begin(),
[](unsigned char ch) { return static_cast<char>(std::toupper(ch)); });
GatewayBridgeHttpResponse response;
if (method == "GET") {
const char* query = JsonString(root, "query");
response = handleGet(action_raw, gateway_id,
query == nullptr ? std::string() : std::string(query));
} else if (method == "POST") {
const cJSON* body_node = cJSON_GetObjectItemCaseSensitive(root, "body");
response = handlePost(action_raw, gateway_id,
body_node == nullptr ? std::string("{}")
: PrintJson(const_cast<cJSON*>(body_node)));
} else {
response = ErrorResponse(ESP_ERR_INVALID_ARG, "unsupported bridge request method");
}
cJSON_Delete(root);
return BuildBridgeTransportEnvelope(response);
}
} // namespace gateway
+1 -1
View File
@@ -1,7 +1,7 @@
idf_component_register(
SRCS "src/gateway_controller.cpp"
INCLUDE_DIRS "include"
REQUIRES dali_domain gateway_runtime gateway_cache freertos log
REQUIRES dali_domain gateway_runtime gateway_cache gateway_bridge freertos log
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -19,6 +19,7 @@ namespace gateway {
class DaliDomainService;
struct DaliRawFrame;
class GatewayBridgeService;
class GatewayRuntime;
struct GatewayControllerConfig {
@@ -76,6 +77,7 @@ class GatewayController {
void addBleStateSink(BleStateSink sink);
void addWifiStateSink(WifiStateSink sink);
void addGatewayNameSink(GatewayNameSink sink);
void setBridgeService(GatewayBridgeService* bridge_service);
bool setupMode() const;
bool wirelessSetupMode() const;
@@ -100,6 +102,13 @@ class GatewayController {
uint8_t scene_id{0};
};
struct BridgeTransportRequestState {
uint8_t version{0};
uint16_t payload_length{0};
uint8_t total_chunks{0};
std::map<uint8_t, std::vector<uint8_t>> chunks;
};
static void TaskEntry(void* arg);
void taskLoop();
void dispatchCommand(const std::vector<uint8_t>& command);
@@ -156,10 +165,14 @@ class GatewayController {
void handleAllocationCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleInternalSceneCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleInternalGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void handleBridgeTransportCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
void publishBridgeTransportResponse(uint8_t gateway_id, uint8_t version, uint8_t sequence,
std::string_view response);
GatewayRuntime& runtime_;
DaliDomainService& dali_domain_;
GatewayCache& cache_;
GatewayBridgeService* bridge_service_{nullptr};
GatewayControllerConfig config_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t maintenance_lock_{nullptr};
@@ -167,6 +180,7 @@ class GatewayController {
std::vector<BleStateSink> ble_state_sinks_;
std::vector<WifiStateSink> wifi_state_sinks_;
std::vector<GatewayNameSink> gateway_name_sinks_;
std::map<uint16_t, BridgeTransportRequestState> bridge_transport_requests_;
std::map<uint8_t, ReconciliationJob> reconciliation_jobs_;
std::atomic<int> maintenance_activity_gateway_{-1};
bool setup_mode_{false};
@@ -3,6 +3,7 @@
#include "dali_domain.hpp"
#include "esp_log.h"
#include "esp_system.h"
#include "gateway_bridge.hpp"
#include "gateway_runtime.hpp"
#include <algorithm>
@@ -21,6 +22,16 @@ constexpr uint8_t kDaliSceneCount = 16;
constexpr uint8_t kDaliCmdOff = 0x00;
constexpr uint8_t kDaliCmdRecallMax = 0x05;
constexpr TickType_t kMaintenancePollTicks = pdMS_TO_TICKS(20);
constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0;
constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1;
constexpr uint8_t kBridgeTransportVersion = 1;
constexpr size_t kBridgeTransportMaxChunkBytes = 120;
constexpr const char* kBridgeTransportInvalidFrameResponse =
"{\"statusCode\":400,\"error\":\"invalid bridge transport frame\","
"\"message\":\"invalid bridge transport frame\"}";
constexpr const char* kBridgeTransportUnavailableResponse =
"{\"statusCode\":500,\"error\":\"bridge service is not enabled\","
"\"message\":\"bridge service is not enabled\"}";
class LockGuard {
public:
@@ -73,6 +84,10 @@ void AppendStringBytes(std::vector<uint8_t>& out, std::string_view value) {
}
}
uint16_t BridgeTransportRequestKey(uint8_t gateway_id, uint8_t sequence) {
return static_cast<uint16_t>((static_cast<uint16_t>(gateway_id) << 8) | sequence);
}
void AppendPaddedName(std::vector<uint8_t>& out, std::string_view name) {
const auto normalized = NormalizeName(name);
out.push_back(static_cast<uint8_t>(normalized.size()));
@@ -185,6 +200,10 @@ void GatewayController::addGatewayNameSink(GatewayNameSink sink) {
}
}
void GatewayController::setBridgeService(GatewayBridgeService* bridge_service) {
bridge_service_ = bridge_service;
}
bool GatewayController::setupMode() const {
return setup_mode_;
}
@@ -659,6 +678,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
case 0xA2:
handleInternalGroupCommand(gateway_id, command);
break;
case kBridgeTransportRequestOpcode:
handleBridgeTransportCommand(gateway_id, command);
break;
default:
ESP_LOGW(kTag, "unhandled opcode=0x%02x gateway=%u", opcode, gateway_id);
break;
@@ -714,12 +736,120 @@ void GatewayController::publishPayload(uint8_t, const std::vector<uint8_t>& payl
publishFrame(GatewayRuntime::buildNotificationFrame(payload));
}
void GatewayController::publishBridgeTransportResponse(uint8_t gateway_id, uint8_t version,
uint8_t sequence,
std::string_view response) {
const size_t total_chunks =
std::max<size_t>(1, (response.size() + kBridgeTransportMaxChunkBytes - 1) /
kBridgeTransportMaxChunkBytes);
for (size_t index = 0; index < total_chunks; ++index) {
const size_t start = index * kBridgeTransportMaxChunkBytes;
const size_t chunk_length =
std::min(kBridgeTransportMaxChunkBytes, response.size() - start);
std::vector<uint8_t> payload{
kBridgeTransportResponseOpcode,
gateway_id,
version,
sequence,
static_cast<uint8_t>(total_chunks),
static_cast<uint8_t>(index),
static_cast<uint8_t>(response.size() & 0xFF),
static_cast<uint8_t>((response.size() >> 8) & 0xFF),
static_cast<uint8_t>(chunk_length & 0xFF),
static_cast<uint8_t>((chunk_length >> 8) & 0xFF),
};
payload.reserve(payload.size() + chunk_length);
for (size_t offset = 0; offset < chunk_length; ++offset) {
payload.push_back(static_cast<uint8_t>(response[start + offset]));
}
publishPayload(gateway_id, payload);
}
}
void GatewayController::publishFrame(const std::vector<uint8_t>& frame) {
for (const auto& sink : notification_sinks_) {
sink(frame);
}
}
void GatewayController::handleBridgeTransportCommand(uint8_t gateway_id,
const std::vector<uint8_t>& command) {
const uint8_t version = command.size() > 4 ? command[4] : kBridgeTransportVersion;
const uint8_t sequence = command.size() > 5 ? command[5] : 0;
const uint16_t request_key = BridgeTransportRequestKey(gateway_id, sequence);
if (command.size() < 11) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
const uint8_t total_chunks = command[6];
const uint8_t chunk_index = command[7];
const uint16_t payload_length =
static_cast<uint16_t>(command[8] | (static_cast<uint16_t>(command[9]) << 8));
if (version != kBridgeTransportVersion || total_chunks == 0 || chunk_index >= total_chunks) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
auto& state = bridge_transport_requests_[request_key];
if (chunk_index == 0 || state.version != version || state.payload_length != payload_length ||
state.total_chunks != total_chunks) {
state = BridgeTransportRequestState{};
state.version = version;
state.payload_length = payload_length;
state.total_chunks = total_chunks;
}
const size_t payload_start = 10;
const size_t payload_end = command.size() - 1;
if (payload_end < payload_start) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
if (state.chunks.find(chunk_index) == state.chunks.end()) {
state.chunks[chunk_index] =
std::vector<uint8_t>(command.begin() + payload_start, command.begin() + payload_end);
}
if (state.chunks.size() < total_chunks) {
return;
}
std::vector<uint8_t> request_bytes;
request_bytes.reserve(payload_length);
for (uint8_t index = 0; index < total_chunks; ++index) {
const auto it = state.chunks.find(index);
if (it == state.chunks.end()) {
bridge_transport_requests_.erase(request_key);
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
request_bytes.insert(request_bytes.end(), it->second.begin(), it->second.end());
}
bridge_transport_requests_.erase(request_key);
if (request_bytes.size() != payload_length) {
publishBridgeTransportResponse(gateway_id, version, sequence,
kBridgeTransportInvalidFrameResponse);
return;
}
const std::string response =
bridge_service_ == nullptr
? std::string(kBridgeTransportUnavailableResponse)
: bridge_service_->handleTransportRequest(
gateway_id,
std::string_view(reinterpret_cast<const char*>(request_bytes.data()),
request_bytes.size()));
publishBridgeTransportResponse(gateway_id, version, sequence, response);
}
void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
if (frame.data.size() != 2 && frame.data.size() != 3) {
return;