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
+51
View File
@@ -1010,6 +1010,57 @@ config GATEWAY_START_CLOUD_BRIDGE_ENABLED
help help
Starts configured MQTT cloud bridges at boot when broker URI and device id are present. Starts configured MQTT cloud bridges at boot when broker URI and device id are present.
config GATEWAY_CLOUD_TOPIC_PREFIX
string "Default MQTT cloud topic prefix"
depends on GATEWAY_CLOUD_BRIDGE_SUPPORTED
default "devices"
help
Topic namespace used for canonical MQTT cloud bridge traffic.
choice GATEWAY_CLOUD_CEMI_TRANSPORT
prompt "Default KNX cEMI cloud transport"
depends on GATEWAY_CLOUD_BRIDGE_SUPPORTED
default GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT
help
Selects the default transport for cloud KNX cEMI proxy envelopes.
config GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT
bool "MQTT topics only"
config GATEWAY_CLOUD_CEMI_TRANSPORT_LTE_UART
bool "LTE UART transparent bridge only"
config GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT_AND_LTE_UART
bool "MQTT topics and LTE UART"
endchoice
config GATEWAY_CLOUD_LTE_UART_PORT
int "LTE UART port for cEMI transparent bridge"
depends on GATEWAY_CLOUD_BRIDGE_SUPPORTED
range -1 2
default -1
help
UART index used by a transparent 4G LTE module. Use -1 to leave it disabled.
config GATEWAY_CLOUD_LTE_UART_TX_PIN
int "LTE UART TX pin"
depends on GATEWAY_CLOUD_BRIDGE_SUPPORTED
range -1 48
default -1
config GATEWAY_CLOUD_LTE_UART_RX_PIN
int "LTE UART RX pin"
depends on GATEWAY_CLOUD_BRIDGE_SUPPORTED
range -1 48
default -1
config GATEWAY_CLOUD_LTE_UART_BAUDRATE
int "LTE UART baudrate"
depends on GATEWAY_CLOUD_BRIDGE_SUPPORTED
range 1200 921600
default 115200
config GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE config GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE
int "Modbus bridge task stack bytes" int "Modbus bridge task stack bytes"
depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED depends on GATEWAY_MODBUS_BRIDGE_SUPPORTED
+19 -2
View File
@@ -696,7 +696,7 @@ CONFIG_GATEWAY_KNX_INSTANCE_COUNT=1
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
# CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED=y
CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS=y CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS=y
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID=0x01e5 CONFIG_GATEWAY_KNX_OEM_MANUFACTURER_ID=0x01e5
@@ -712,7 +712,16 @@ CONFIG_GATEWAY_KNX_UDP_PORT=3671
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12" CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS=65281 CONFIG_GATEWAY_KNX_IP_INTERFACE_INDIVIDUAL_ADDRESS=65281
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=65534 CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=65534
# CONFIG_GATEWAY_KNX_OAM_ROUTER_SUPPORTED is not set CONFIG_GATEWAY_KNX_OAM_ROUTER_SUPPORTED=y
CONFIG_GATEWAY_KNX_OAM_ROUTER_ENABLED=y
CONFIG_GATEWAY_KNX_OAM_ROUTER_OEM_MANUFACTURER_ID=0x00FA
CONFIG_GATEWAY_KNX_OAM_ROUTER_HARDWARE_ID=0x0001
CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_NUMBER=0xA11F
CONFIG_GATEWAY_KNX_OAM_ROUTER_APPLICATION_VERSION=0x07
CONFIG_GATEWAY_KNX_OAM_ROUTER_INDIVIDUAL_ADDRESS=65282
CONFIG_GATEWAY_KNX_OAM_ROUTER_TUNNEL_ADDRESS_BASE=65296
CONFIG_GATEWAY_KNX_OAM_PROGRAMMING_BUTTON_GPIO=-1
CONFIG_GATEWAY_KNX_OAM_PROGRAMMING_LED_GPIO=-1
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO=0 CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_GPIO=0
CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW=y CONFIG_GATEWAY_KNX_PROGRAMMING_BUTTON_ACTIVE_LOW=y
CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO=10 CONFIG_GATEWAY_KNX_PROGRAMMING_LED_GPIO=10
@@ -730,6 +739,14 @@ CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX="devices"
CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT=y
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_LTE_UART is not set
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT_AND_LTE_UART is not set
CONFIG_GATEWAY_CLOUD_LTE_UART_PORT=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE=115200
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4 CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192 CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
+32 -3
View File
@@ -596,7 +596,11 @@ CONFIG_PARTITION_TABLE_MD5=y
# #
# Gateway App # Gateway App
# #
CONFIG_GATEWAY_CHANNEL_COUNT=1
#
# DALI Settings
#
CONFIG_GATEWAY_CHANNEL_COUNT=2
# #
# Gateway Channel 1 # Gateway Channel 1
@@ -615,6 +619,15 @@ CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
# #
# Gateway Channel 2 # Gateway Channel 2
# #
CONFIG_GATEWAY_CHANNEL2_GW_ID=4
# CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED is not set
CONFIG_GATEWAY_CHANNEL2_PHY_NATIVE=y
# CONFIG_GATEWAY_CHANNEL2_PHY_UART1 is not set
# CONFIG_GATEWAY_CHANNEL2_PHY_UART2 is not set
CONFIG_GATEWAY_CHANNEL2_NATIVE_BUS_ID=1
CONFIG_GATEWAY_CHANNEL2_NATIVE_TX_PIN=4
CONFIG_GATEWAY_CHANNEL2_NATIVE_RX_PIN=3
CONFIG_GATEWAY_CHANNEL2_NATIVE_BAUDRATE=1200
# end of Gateway Channel 2 # end of Gateway Channel 2
# #
@@ -624,13 +637,14 @@ CONFIG_GATEWAY_CACHE_SUPPORTED=y
CONFIG_GATEWAY_CACHE_START_ENABLED=y CONFIG_GATEWAY_CACHE_START_ENABLED=y
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR=y CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR=y
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=10000 CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=600000
CONFIG_GATEWAY_CACHE_REFRESH_INTERVAL_MS=120000 CONFIG_GATEWAY_CACHE_REFRESH_INTERVAL_MS=120000
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set # CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
# end of Gateway Cache # end of Gateway Cache
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set # CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
# end of DALI Settings
# #
# Gateway Startup Services # Gateway Startup Services
@@ -674,6 +688,11 @@ CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
CONFIG_GATEWAY_MODBUS_UNIT_ID=1 CONFIG_GATEWAY_MODBUS_UNIT_ID=1
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
#
# KNX Settings
#
CONFIG_GATEWAY_KNX_INSTANCE_COUNT=1
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
@@ -707,8 +726,18 @@ CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y
CONFIG_GATEWAY_KNX_TP_FULL_IP_FORWARD=y CONFIG_GATEWAY_KNX_TP_FULL_IP_FORWARD=y
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288 CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5 CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
# end of KNX Settings
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX="devices"
CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT=y
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_LTE_UART is not set
# CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT_AND_LTE_UART is not set
CONFIG_GATEWAY_CLOUD_LTE_UART_PORT=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN=-1
CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE=115200
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4 CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_PRIORITY=4
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192 CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
@@ -1652,7 +1681,7 @@ CONFIG_ESP_NETIF_TCPIP_LWIP=y
# CONFIG_ESP_NETIF_LOOPBACK is not set # CONFIG_ESP_NETIF_LOOPBACK is not set
CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y
# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS=y
# CONFIG_ESP_NETIF_L2_TAP is not set # CONFIG_ESP_NETIF_L2_TAP is not set
# CONFIG_ESP_NETIF_BRIDGE_EN is not set # CONFIG_ESP_NETIF_BRIDGE_EN is not set
# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set # CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
@@ -1226,6 +1226,28 @@ std::optional<GatewayBridgeStoredConfig> GatewayBridgeStoredConfigFromJson(std::
GatewayCloudConfig GatewayCloudConfigFromJson(cJSON* root) { GatewayCloudConfig GatewayCloudConfigFromJson(cJSON* root) {
GatewayCloudConfig config; GatewayCloudConfig config;
#if defined(CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX)
config.topicPrefix = CONFIG_GATEWAY_CLOUD_TOPIC_PREFIX;
#endif
#if defined(CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_LTE_UART)
config.cemiTransport = "lte_uart";
config.lteUartEnabled = true;
#elif defined(CONFIG_GATEWAY_CLOUD_CEMI_TRANSPORT_MQTT_AND_LTE_UART)
config.cemiTransport = "mqtt_lte_uart";
config.lteUartEnabled = true;
#endif
#if defined(CONFIG_GATEWAY_CLOUD_LTE_UART_PORT)
config.lteUartPort = CONFIG_GATEWAY_CLOUD_LTE_UART_PORT;
#endif
#if defined(CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN)
config.lteTxPin = CONFIG_GATEWAY_CLOUD_LTE_UART_TX_PIN;
#endif
#if defined(CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN)
config.lteRxPin = CONFIG_GATEWAY_CLOUD_LTE_UART_RX_PIN;
#endif
#if defined(CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE)
config.lteBaudrate = CONFIG_GATEWAY_CLOUD_LTE_UART_BAUDRATE;
#endif
if (const char* value = JsonString(root, "brokerURI")) { if (const char* value = JsonString(root, "brokerURI")) {
config.brokerURI = value; config.brokerURI = value;
} }
@@ -1241,6 +1263,28 @@ GatewayCloudConfig GatewayCloudConfigFromJson(cJSON* root) {
if (const char* value = JsonString(root, "topicPrefix")) { if (const char* value = JsonString(root, "topicPrefix")) {
config.topicPrefix = value; config.topicPrefix = value;
} }
if (const char* value = JsonString(root, "cemiTransport")) {
config.cemiTransport = value;
}
config.lteUartEnabled = JsonBool(root, "lteUartEnabled", config.lteUartEnabled);
if (const auto value = JsonInt(root, "lteUartPort")) {
config.lteUartPort = value.value();
}
if (const auto value = JsonInt(root, "lteTxPin")) {
config.lteTxPin = value.value();
}
if (const auto value = JsonInt(root, "lteRxPin")) {
config.lteRxPin = value.value();
}
if (const auto value = JsonInt(root, "lteBaudrate")) {
config.lteBaudrate = value.value();
}
if (const auto value = JsonInt(root, "lteRxBufferSize")) {
config.lteRxBufferSize = value.value();
}
if (const auto value = JsonInt(root, "lteTxBufferSize")) {
config.lteTxBufferSize = value.value();
}
if (const auto qos = JsonInt(root, "qos")) { if (const auto qos = JsonInt(root, "qos")) {
config.qos = qos.value(); config.qos = qos.value();
} }
@@ -1257,6 +1301,14 @@ cJSON* GatewayCloudConfigToCjson(const GatewayCloudConfig& config) {
cJSON_AddStringToObject(root, "username", config.username.c_str()); cJSON_AddStringToObject(root, "username", config.username.c_str());
cJSON_AddStringToObject(root, "password", config.password.c_str()); cJSON_AddStringToObject(root, "password", config.password.c_str());
cJSON_AddStringToObject(root, "topicPrefix", config.topicPrefix.c_str()); cJSON_AddStringToObject(root, "topicPrefix", config.topicPrefix.c_str());
cJSON_AddStringToObject(root, "cemiTransport", config.cemiTransport.c_str());
cJSON_AddBoolToObject(root, "lteUartEnabled", config.lteUartEnabled);
cJSON_AddNumberToObject(root, "lteUartPort", config.lteUartPort);
cJSON_AddNumberToObject(root, "lteTxPin", config.lteTxPin);
cJSON_AddNumberToObject(root, "lteRxPin", config.lteRxPin);
cJSON_AddNumberToObject(root, "lteBaudrate", config.lteBaudrate);
cJSON_AddNumberToObject(root, "lteRxBufferSize", config.lteRxBufferSize);
cJSON_AddNumberToObject(root, "lteTxBufferSize", config.lteTxBufferSize);
cJSON_AddNumberToObject(root, "qos", config.qos); cJSON_AddNumberToObject(root, "qos", config.qos);
return root; return root;
} }
@@ -1832,6 +1884,22 @@ struct GatewayBridgeService::ChannelRuntime {
for (const auto& model : bridge_config.models) { for (const auto& model : bridge_config.models) {
cloud->bridge().upsertModel(model); cloud->bridge().upsertModel(model);
} }
wireCloudCemiProxyLocked();
}
void wireCloudCemiProxyLocked() {
if (knx_router != nullptr) {
knx_router->setCloudCemiPublisher([this](const uint8_t* data, size_t len) {
if (cloud_started && cloud != nullptr) {
cloud->publishCemiFrame(data, len);
}
});
}
if (cloud != nullptr) {
cloud->setCemiDownlinkHandler([this](const uint8_t* data, size_t len) {
return knx_router != nullptr && knx_router->injectCloudCemiFrame(data, len);
});
}
} }
esp_err_t saveBridgeConfig(std::string_view json) { esp_err_t saveBridgeConfig(std::string_view json) {
@@ -2454,8 +2522,12 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON* cloud_remote_json = cJSON_CreateObject(); cJSON* cloud_remote_json = cJSON_CreateObject();
if (cloud_remote_json != nullptr) { if (cloud_remote_json != nullptr) {
const auto& cloud_remote = effective_knx->oam_router.cloud_remote; const auto& cloud_remote = effective_knx->oam_router.cloud_remote;
const auto cloud_stats = knx_router == nullptr
? GatewayKnxTpIpRouter::CloudCemiStats{}
: knx_router->cloudCemiStats();
cJSON_AddBoolToObject(cloud_remote_json, "prepared", true); cJSON_AddBoolToObject(cloud_remote_json, "prepared", true);
cJSON_AddBoolToObject(cloud_remote_json, "enabled", cloud_remote.enabled); cJSON_AddBoolToObject(cloud_remote_json, "enabled", cloud_remote.enabled);
cJSON_AddBoolToObject(cloud_remote_json, "running", cloud_stats.enabled);
cJSON_AddStringToObject(cloud_remote_json, "mode", cloud_remote.mode.c_str()); cJSON_AddStringToObject(cloud_remote_json, "mode", cloud_remote.mode.c_str());
cJSON_AddBoolToObject(cloud_remote_json, "requireSecureTunnel", cJSON_AddBoolToObject(cloud_remote_json, "requireSecureTunnel",
cloud_remote.require_secure_tunnel); cloud_remote.require_secure_tunnel);
@@ -2467,6 +2539,10 @@ struct GatewayBridgeService::ChannelRuntime {
!cloud_remote.mqtt_topic_prefix.empty()); !cloud_remote.mqtt_topic_prefix.empty());
cJSON_AddBoolToObject(cloud_remote_json, "authTokenRefConfigured", cJSON_AddBoolToObject(cloud_remote_json, "authTokenRefConfigured",
!cloud_remote.auth_token_ref.empty()); !cloud_remote.auth_token_ref.empty());
cJSON_AddNumberToObject(cloud_remote_json, "uplinkFrames",
static_cast<double>(cloud_stats.uplink_frames));
cJSON_AddNumberToObject(cloud_remote_json, "downlinkFrames",
static_cast<double>(cloud_stats.downlink_frames));
cJSON_AddItemToObject(knx_json, "cloudKnxRemoteAccess", cloud_remote_json); cJSON_AddItemToObject(knx_json, "cloudKnxRemoteAccess", cloud_remote_json);
} }
cJSON* serial_json = cJSON_CreateObject(); cJSON* serial_json = cJSON_CreateObject();
@@ -2564,9 +2640,16 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON_AddBoolToObject(cloud_json, "configured", cloud_config_loaded); cJSON_AddBoolToObject(cloud_json, "configured", cloud_config_loaded);
cJSON_AddBoolToObject(cloud_json, "started", cloud_started); cJSON_AddBoolToObject(cloud_json, "started", cloud_started);
cJSON_AddBoolToObject(cloud_json, "connected", cloud != nullptr && cloud->isConnected()); cJSON_AddBoolToObject(cloud_json, "connected", cloud != nullptr && cloud->isConnected());
cJSON_AddBoolToObject(cloud_json, "lteUartActive",
cloud != nullptr && cloud->lteUartActive());
if (cloud_config.has_value()) { if (cloud_config.has_value()) {
cJSON_AddStringToObject(cloud_json, "deviceID", cloud_config->deviceID.c_str()); cJSON_AddStringToObject(cloud_json, "deviceID", cloud_config->deviceID.c_str());
cJSON_AddStringToObject(cloud_json, "topicPrefix", cloud_config->topicPrefix.c_str()); cJSON_AddStringToObject(cloud_json, "topicPrefix", cloud_config->topicPrefix.c_str());
cJSON_AddStringToObject(cloud_json, "cemiTransport",
cloud_config->cemiTransport.c_str());
cJSON_AddBoolToObject(cloud_json, "lteUartEnabled",
cloud_config->lteUartEnabled);
cJSON_AddNumberToObject(cloud_json, "lteUartPort", cloud_config->lteUartPort);
} }
cJSON_AddItemToObject(root, "cloud", cloud_json); cJSON_AddItemToObject(root, "cloud", cloud_json);
} }
@@ -2968,6 +3051,7 @@ struct GatewayBridgeService::ChannelRuntime {
cJSON_AddBoolToObject(root, "configured", cloud_config_loaded); cJSON_AddBoolToObject(root, "configured", cloud_config_loaded);
cJSON_AddBoolToObject(root, "started", cloud_started); cJSON_AddBoolToObject(root, "started", cloud_started);
cJSON_AddBoolToObject(root, "connected", cloud != nullptr && cloud->isConnected()); cJSON_AddBoolToObject(root, "connected", cloud != nullptr && cloud->isConnected());
cJSON_AddBoolToObject(root, "lteUartActive", cloud != nullptr && cloud->lteUartActive());
if (cloud_config.has_value()) { if (cloud_config.has_value()) {
cJSON_AddItemToObject(root, "config", GatewayCloudConfigToCjson(cloud_config.value())); cJSON_AddItemToObject(root, "config", GatewayCloudConfigToCjson(cloud_config.value()));
} }
@@ -268,6 +268,13 @@ class GatewayKnxTpIpRouter {
const uint8_t* data, const uint8_t* data,
size_t len)>; size_t len)>;
using RoutingSequenceStoreHandler = std::function<void(uint64_t sequence)>; 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, GatewayKnxTpIpRouter(GatewayKnxBridge& bridge,
std::string openknx_namespace = "openknx"); std::string openknx_namespace = "openknx");
@@ -279,7 +286,10 @@ class GatewayKnxTpIpRouter {
void setGroupObjectWriteHandler(GroupObjectWriteHandler handler); void setGroupObjectWriteHandler(GroupObjectWriteHandler handler);
void setOamIpSecureCredentials(const GatewayKnxIpSecureCredentialMaterial& credentials); void setOamIpSecureCredentials(const GatewayKnxIpSecureCredentialMaterial& credentials);
void setOamIpSecureRoutingSequenceStoreHandler(RoutingSequenceStoreHandler handler); void setOamIpSecureRoutingSequenceStoreHandler(RoutingSequenceStoreHandler handler);
void setCloudCemiPublisher(CloudCemiPublisher publisher);
const GatewayKnxConfig& config() const; const GatewayKnxConfig& config() const;
bool injectCloudCemiFrame(const uint8_t* data, size_t len);
CloudCemiStats cloudCemiStats() const;
bool tpUartOnline() const; bool tpUartOnline() const;
bool programmingMode(); bool programmingMode();
esp_err_t setProgrammingMode(bool enabled); esp_err_t setProgrammingMode(bool enabled);
@@ -458,6 +468,7 @@ class GatewayKnxTpIpRouter {
size_t suppress_routing_echo_len = 0); size_t suppress_routing_echo_len = 0);
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len); bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
bool transmitOpenKnxTpFrame(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); void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
bool routeOpenKnxGroupWrite(const uint8_t* data, size_t len, const char* context); 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); bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
@@ -476,6 +487,7 @@ class GatewayKnxTpIpRouter {
GroupWriteHandler group_write_handler_; GroupWriteHandler group_write_handler_;
GroupObjectWriteHandler group_object_write_handler_; GroupObjectWriteHandler group_object_write_handler_;
RoutingSequenceStoreHandler routing_sequence_store_handler_; RoutingSequenceStoreHandler routing_sequence_store_handler_;
CloudCemiPublisher cloud_cemi_publisher_;
std::string openknx_namespace_; std::string openknx_namespace_;
GatewayKnxConfig config_; GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_; std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
@@ -505,6 +517,8 @@ class GatewayKnxTpIpRouter {
bool tp_uart_online_{false}; bool tp_uart_online_{false};
bool commissioning_only_{false}; bool commissioning_only_{false};
std::atomic_bool openknx_configured_{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_button_last_pressed_{false};
bool programming_led_state_{false}; bool programming_led_state_{false};
TickType_t programming_button_last_toggle_tick_{0}; TickType_t programming_button_last_toggle_tick_{0};
@@ -46,8 +46,27 @@ void GatewayKnxTpIpRouter::setOamIpSecureRoutingSequenceStoreHandler(
routing_sequence_store_handler_ = std::move(handler); 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_; } 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::tpUartOnline() const { return tp_uart_online_; }
bool GatewayKnxTpIpRouter::programmingMode() { bool GatewayKnxTpIpRouter::programmingMode() {
@@ -328,6 +347,7 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
return result; return result;
}); });
ets_device_->setBusFrameSender([this](const uint8_t* data, size_t len) { ets_device_->setBusFrameSender([this](const uint8_t* data, size_t len) {
publishCloudCemiFrame(data, len);
sendTunnelIndication(data, len); sendTunnelIndication(data, len);
sendRoutingIndication(data, len); sendRoutingIndication(data, len);
}); });
@@ -51,6 +51,7 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
if (response == nullptr || response_len == 0) { if (response == nullptr || response_len == 0) {
return; return;
} }
publishCloudCemiFrame(response, response_len);
const bool routing_context = const bool routing_context =
response_client == nullptr && response_service == kServiceRoutingIndication; response_client == nullptr && response_service == kServiceRoutingIndication;
const auto message_code = CemiMessageCode(response, response_len); 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) { if (response == nullptr || response_len == 0) {
return; return;
} }
publishCloudCemiFrame(response, response_len);
const bool routing_context = const bool routing_context =
response_client == nullptr && response_service == kServiceRoutingIndication; response_client == nullptr && response_service == kServiceRoutingIndication;
const auto message_code = CemiMessageCode(response, response_len); 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) { bool GatewayKnxTpIpRouter::handleOpenKnxBusFrame(const uint8_t* data, size_t len) {
SemaphoreGuard guard(openknx_lock_); bool consumed = false;
if (ets_device_ == nullptr) { {
return 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; 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, bool GatewayKnxTpIpRouter::routeOpenKnxGroupWrite(const uint8_t* data, size_t len,
const char* context) { const char* context) {
const auto decoded = DecodeOpenKnxGroupWrite(data, len); const auto decoded = DecodeOpenKnxGroupWrite(data, len);