feat(gateway): add cloud integration for KNX and DALI with configurable transport options

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-27 15:20:29 +08:00
parent 5622e6ba81
commit 7a820e700c
7 changed files with 242 additions and 10 deletions
@@ -268,6 +268,13 @@ class GatewayKnxTpIpRouter {
const uint8_t* data,
size_t len)>;
using RoutingSequenceStoreHandler = std::function<void(uint64_t sequence)>;
using CloudCemiPublisher = std::function<void(const uint8_t* data, size_t len)>;
struct CloudCemiStats {
bool enabled{false};
uint64_t uplink_frames{0};
uint64_t downlink_frames{0};
};
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge,
std::string openknx_namespace = "openknx");
@@ -279,7 +286,10 @@ class GatewayKnxTpIpRouter {
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
void setOamIpSecureCredentials(const GatewayKnxIpSecureCredentialMaterial& credentials);
void setOamIpSecureRoutingSequenceStoreHandler(RoutingSequenceStoreHandler handler);
void setCloudCemiPublisher(CloudCemiPublisher publisher);
const GatewayKnxConfig& config() const;
bool injectCloudCemiFrame(const uint8_t* data, size_t len);
CloudCemiStats cloudCemiStats() const;
bool tpUartOnline() const;
bool programmingMode();
esp_err_t setProgrammingMode(bool enabled);
@@ -458,6 +468,7 @@ class GatewayKnxTpIpRouter {
size_t suppress_routing_echo_len = 0);
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
bool transmitOpenKnxTpFrame(const uint8_t* data, size_t len);
void publishCloudCemiFrame(const uint8_t* data, size_t len);
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
bool routeOpenKnxGroupWrite(const uint8_t* data, size_t len, const char* context);
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
@@ -476,6 +487,7 @@ class GatewayKnxTpIpRouter {
GroupWriteHandler group_write_handler_;
GroupObjectWriteHandler group_object_write_handler_;
RoutingSequenceStoreHandler routing_sequence_store_handler_;
CloudCemiPublisher cloud_cemi_publisher_;
std::string openknx_namespace_;
GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
@@ -505,6 +517,8 @@ class GatewayKnxTpIpRouter {
bool tp_uart_online_{false};
bool commissioning_only_{false};
std::atomic_bool openknx_configured_{false};
std::atomic<uint64_t> cloud_cemi_uplink_frames_{0};
std::atomic<uint64_t> cloud_cemi_downlink_frames_{0};
bool programming_button_last_pressed_{false};
bool programming_led_state_{false};
TickType_t programming_button_last_toggle_tick_{0};
@@ -46,8 +46,27 @@ void GatewayKnxTpIpRouter::setOamIpSecureRoutingSequenceStoreHandler(
routing_sequence_store_handler_ = std::move(handler);
}
void GatewayKnxTpIpRouter::setCloudCemiPublisher(CloudCemiPublisher publisher) {
cloud_cemi_publisher_ = std::move(publisher);
}
const GatewayKnxConfig& GatewayKnxTpIpRouter::config() const { return config_; }
bool GatewayKnxTpIpRouter::injectCloudCemiFrame(const uint8_t* data, size_t len) {
if (data == nullptr || len == 0 || !config_.oam_router.cloud_remote.enabled) {
return false;
}
cloud_cemi_downlink_frames_.fetch_add(1, std::memory_order_relaxed);
return handleOpenKnxTunnelFrame(data, len, nullptr, kServiceTunnellingRequest);
}
GatewayKnxTpIpRouter::CloudCemiStats GatewayKnxTpIpRouter::cloudCemiStats() const {
return CloudCemiStats{
config_.oam_router.cloud_remote.enabled,
cloud_cemi_uplink_frames_.load(std::memory_order_relaxed),
cloud_cemi_downlink_frames_.load(std::memory_order_relaxed)};
}
bool GatewayKnxTpIpRouter::tpUartOnline() const { return tp_uart_online_; }
bool GatewayKnxTpIpRouter::programmingMode() {
@@ -328,6 +347,7 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
return result;
});
ets_device_->setBusFrameSender([this](const uint8_t* data, size_t len) {
publishCloudCemiFrame(data, len);
sendTunnelIndication(data, len);
sendRoutingIndication(data, len);
});
@@ -51,6 +51,7 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
if (response == nullptr || response_len == 0) {
return;
}
publishCloudCemiFrame(response, response_len);
const bool routing_context =
response_client == nullptr && response_service == kServiceRoutingIndication;
const auto message_code = CemiMessageCode(response, response_len);
@@ -128,6 +129,7 @@ bool GatewayKnxTpIpRouter::handleOamRouterTunnelFrame(const uint8_t* data, size_
if (response == nullptr || response_len == 0) {
return;
}
publishCloudCemiFrame(response, response_len);
const bool routing_context =
response_client == nullptr && response_service == kServiceRoutingIndication;
const auto message_code = CemiMessageCode(response, response_len);
@@ -191,15 +193,30 @@ bool GatewayKnxTpIpRouter::transmitOpenKnxTpFrame(const uint8_t* data, size_t le
}
bool GatewayKnxTpIpRouter::handleOpenKnxBusFrame(const uint8_t* data, size_t len) {
SemaphoreGuard guard(openknx_lock_);
if (ets_device_ == nullptr) {
return false;
bool consumed = false;
{
SemaphoreGuard guard(openknx_lock_);
if (ets_device_ == nullptr) {
return false;
}
consumed = ets_device_->handleBusFrame(data, len);
syncOpenKnxConfigFromDevice();
}
if (consumed) {
publishCloudCemiFrame(data, len);
}
const bool consumed = ets_device_->handleBusFrame(data, len);
syncOpenKnxConfigFromDevice();
return consumed;
}
void GatewayKnxTpIpRouter::publishCloudCemiFrame(const uint8_t* data, size_t len) {
if (data == nullptr || len == 0 || !config_.oam_router.cloud_remote.enabled ||
!cloud_cemi_publisher_) {
return;
}
cloud_cemi_uplink_frames_.fetch_add(1, std::memory_order_relaxed);
cloud_cemi_publisher_(data, len);
}
bool GatewayKnxTpIpRouter::routeOpenKnxGroupWrite(const uint8_t* data, size_t len,
const char* context) {
const auto decoded = DecodeOpenKnxGroupWrite(data, len);