feat(gateway): integrate bridge service into gateway controller for transport handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
Reference in New Issue
Block a user