feat(gateway): enhance KNX support with DALI integration and configuration updates

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-12 20:34:33 +08:00
parent e58115d303
commit de0edd5ad9
11 changed files with 395 additions and 30 deletions
+3 -3
View File
@@ -1075,12 +1075,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set # CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11 CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
+39 -11
View File
@@ -656,6 +656,26 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set # CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set # CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60 CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
#
# Gateway Wired Ethernet
#
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=1
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=14
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=13
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=12
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=15
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=4
CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0
CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=36
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=5
CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=3072
# end of Gateway Wired Ethernet
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set # CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
@@ -666,7 +686,24 @@ 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
# CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED is not set CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
# CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set
# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
CONFIG_GATEWAY_KNX_UDP_PORT=3671
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=4353
CONFIG_GATEWAY_KNX_TP_UART_PORT=0
CONFIG_GATEWAY_KNX_TP_TX_PIN=-1
CONFIG_GATEWAY_KNX_TP_RX_PIN=-1
CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=8192
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_BRIDGE_MODBUS_TASK_STACK_SIZE=6144 CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
@@ -675,16 +712,7 @@ CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5 CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set # CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
CONFIG_GATEWAY_485_CONTROL_ENABLED=y # CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
CONFIG_GATEWAY_485_CONTROL_BAUDRATE=9600
CONFIG_GATEWAY_485_CONTROL_TX_PIN=-1
CONFIG_GATEWAY_485_CONTROL_RX_PIN=-1
CONFIG_GATEWAY_485_CONTROL_RX_BUFFER=256
CONFIG_GATEWAY_485_CONTROL_TX_BUFFER=256
CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS=20
CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS=20
CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE=4096
CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY=4
# end of Gateway Startup Services # end of Gateway Startup Services
# #
@@ -16,6 +16,7 @@
namespace gateway { namespace gateway {
class DaliDomainService; class DaliDomainService;
struct DaliRawFrame;
class GatewayCache; class GatewayCache;
struct GatewayBridgeServiceConfig { struct GatewayBridgeServiceConfig {
@@ -65,6 +66,7 @@ class GatewayBridgeService {
ChannelRuntime* findRuntime(uint8_t gateway_id); ChannelRuntime* findRuntime(uint8_t gateway_id);
const ChannelRuntime* findRuntime(uint8_t gateway_id) const; const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
void handleDaliRawFrame(const DaliRawFrame& frame);
void collectUsedRuntimeResources(uint8_t except_gateway_id, void collectUsedRuntimeResources(uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports, std::set<uint16_t>* modbus_tcp_ports,
std::set<uint16_t>* knx_udp_ports, std::set<uint16_t>* knx_udp_ports,
@@ -57,6 +57,10 @@ constexpr uint32_t kBacnetMaxObjectInstance = 4194303;
constexpr uint32_t kBacnetReliabilityNoFaultDetected = 0; constexpr uint32_t kBacnetReliabilityNoFaultDetected = 0;
constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12; constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
constexpr const char* kModbusManagementPrefix = "@DALIGW"; constexpr const char* kModbusManagementPrefix = "@DALIGW";
constexpr uint8_t kDaliGroupRawMin = 0x80;
constexpr uint8_t kDaliGroupRawMax = 0x9F;
constexpr uint8_t kDaliCmdOff = 0x00;
constexpr uint8_t kDaliCmdRecallMax = 0x05;
struct GatewayBridgeStoredConfig { struct GatewayBridgeStoredConfig {
BridgeRuntimeConfig bridge; BridgeRuntimeConfig bridge;
@@ -71,6 +75,11 @@ struct BridgeDiscoveryEntry {
DaliDomainSnapshot discovery; DaliDomainSnapshot discovery;
}; };
struct DaliKnxStatusUpdate {
GatewayKnxDaliTarget target;
uint8_t actual_level{0};
};
using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>; using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>;
class LockGuard { class LockGuard {
@@ -220,6 +229,51 @@ bool ValidDaliAddress(int address) {
return address >= 0 && address <= 127; return address >= 0 && address <= 127;
} }
std::optional<GatewayKnxDaliTarget> DecodeKnxDaliTarget(uint8_t raw_addr) {
if (raw_addr <= 0x7F) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
static_cast<int>(raw_addr >> 1)};
}
if (raw_addr >= kDaliGroupRawMin && raw_addr <= kDaliGroupRawMax) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
static_cast<int>((raw_addr - kDaliGroupRawMin) >> 1)};
}
return std::nullopt;
}
std::optional<DaliKnxStatusUpdate> DecodeDaliKnxStatusUpdate(const DaliRawFrame& frame) {
if (frame.data.size() != 2 && frame.data.size() != 3) {
return std::nullopt;
}
uint8_t raw_addr = 0;
uint8_t command = 0;
if (frame.data.size() == 2) {
raw_addr = frame.data[0];
command = frame.data[1];
if (raw_addr == 0xBE) {
return std::nullopt;
}
} else {
raw_addr = frame.data[1];
command = frame.data[2];
}
auto target = DecodeKnxDaliTarget(raw_addr);
if (!target.has_value()) {
return std::nullopt;
}
if ((raw_addr & 0x01U) == 0) {
if (command > 254) {
return std::nullopt;
}
return DaliKnxStatusUpdate{*target, command};
}
if (command == kDaliCmdOff || command == kDaliCmdRecallMax) {
return DaliKnxStatusUpdate{*target,
static_cast<uint8_t>(command == kDaliCmdOff ? 0 : 254)};
}
return std::nullopt;
}
bool ValidShortAddress(int address) { bool ValidShortAddress(int address) {
return address >= 0 && address <= kMaxDaliShortAddress; return address >= 0 && address <= kMaxDaliShortAddress;
} }
@@ -3652,6 +3706,9 @@ esp_err_t GatewayBridgeService::start() {
runtimes_.push_back(std::move(runtime)); runtimes_.push_back(std::move(runtime));
} }
dali_domain_.addRawFrameSink(
[this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
std::set<int> used_serial_uarts; std::set<int> used_serial_uarts;
if (config_.modbus_enabled && config_.modbus_startup_enabled) { if (config_.modbus_enabled && config_.modbus_startup_enabled) {
std::set<uint16_t> used_modbus_ports; std::set<uint16_t> used_modbus_ports;
@@ -3708,6 +3765,22 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime(
return nullptr; return nullptr;
} }
void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
const auto update = DecodeDaliKnxStatusUpdate(frame);
if (!update.has_value()) {
return;
}
auto* runtime = findRuntime(frame.gateway_id);
if (runtime == nullptr) {
return;
}
LockGuard guard(runtime->lock);
if (!runtime->knx_started || runtime->knx_router == nullptr) {
return;
}
runtime->knx_router->publishDaliStatus(update->target, update->actual_level);
}
void GatewayBridgeService::collectUsedRuntimeResources( void GatewayBridgeService::collectUsedRuntimeResources(
uint8_t except_gateway_id, uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports, std::set<uint16_t>* modbus_tcp_ports,
@@ -5,6 +5,7 @@
#include "esp_err.h" #include "esp_err.h"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "lwip/sockets.h" #include "lwip/sockets.h"
@@ -197,6 +198,7 @@ class GatewayKnxTpIpRouter {
esp_err_t stop(); esp_err_t stop();
bool started() const; bool started() const;
const std::string& lastError() const; const std::string& lastError() const;
bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level);
private: private:
static void TaskEntry(void* arg); static void TaskEntry(void* arg);
@@ -225,6 +227,8 @@ class GatewayKnxTpIpRouter {
const ::sockaddr_in& remote); const ::sockaddr_in& remote);
void sendRoutingIndication(const uint8_t* data, size_t len); void sendRoutingIndication(const uint8_t* data, size_t len);
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len); bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len);
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
void syncOpenKnxConfigFromDevice(); void syncOpenKnxConfigFromDevice();
uint16_t effectiveIndividualAddress() const; uint16_t effectiveIndividualAddress() const;
uint16_t effectiveTunnelAddress() const; uint16_t effectiveTunnelAddress() const;
@@ -239,6 +243,7 @@ class GatewayKnxTpIpRouter {
GatewayKnxConfig config_; GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_; std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
TaskHandle_t task_handle_{nullptr}; TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t openknx_lock_{nullptr};
std::atomic_bool stop_requested_{false}; std::atomic_bool stop_requested_{false};
std::atomic_bool started_{false}; std::atomic_bool started_{false};
int udp_sock_{-1}; int udp_sock_{-1};
+151 -13
View File
@@ -64,6 +64,8 @@ constexpr uint16_t kGwReg1AppKoBroadcastDimm = 2;
constexpr uint8_t kGwReg1KoSwitch = 0; constexpr uint8_t kGwReg1KoSwitch = 0;
constexpr uint8_t kGwReg1KoDimmAbsolute = 3; constexpr uint8_t kGwReg1KoDimmAbsolute = 3;
constexpr uint8_t kGwReg1KoColor = 6; constexpr uint8_t kGwReg1KoColor = 6;
constexpr uint8_t kGwReg1KoSwitchState = 1;
constexpr uint8_t kGwReg1KoDimmState = 4;
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160; constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
constexpr uint8_t kReg1DaliFunctionPropertyId = 1; constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
constexpr uint8_t kReg1FunctionType = 2; constexpr uint8_t kReg1FunctionType = 2;
@@ -84,6 +86,31 @@ struct DecodedGroupWrite {
std::vector<uint8_t> data; std::vector<uint8_t> data;
}; };
class SemaphoreGuard {
public:
explicit SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore_(semaphore) {
if (semaphore_ != nullptr) {
xSemaphoreTake(semaphore_, portMAX_DELAY);
locked_ = true;
}
}
~SemaphoreGuard() {
if (locked_) {
xSemaphoreGive(semaphore_);
}
}
private:
SemaphoreHandle_t semaphore_{nullptr};
bool locked_{false};
};
uint8_t DaliArcLevelToDpt5(uint8_t actual_level) {
return static_cast<uint8_t>(
std::clamp<int>(static_cast<int>(std::lround(actual_level * 255.0 / 254.0)), 0, 255));
}
uint16_t ReadBe16(const uint8_t* data) { uint16_t ReadBe16(const uint8_t* data) {
return static_cast<uint16_t>((static_cast<uint16_t>(data[0]) << 8) | data[1]); return static_cast<uint16_t>((static_cast<uint16_t>(data[0]) << 8) | data[1]);
} }
@@ -309,6 +336,22 @@ std::optional<DecodedGroupWrite> DecodeCemiGroupWrite(const uint8_t* data, size_
return out; return out;
} }
bool IsCemiGroupFrame(const uint8_t* data, size_t len) {
if (data == nullptr || len < 10) {
return false;
}
const uint8_t message_code = data[0];
if (message_code != kCemiLDataReq && message_code != kCemiLDataInd &&
message_code != kCemiLDataCon) {
return false;
}
const size_t base = 2U + data[1];
if (len < base + 8U) {
return false;
}
return (data[base + 1] & 0x80) != 0;
}
uint8_t Reg1PercentToArc(uint8_t value) { uint8_t Reg1PercentToArc(uint8_t value) {
if (value == 0 || value == 0xff) { if (value == 0 || value == 0xff) {
return value; return value;
@@ -1528,9 +1571,17 @@ GatewayKnxTpIpRouter::GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHa
std::string openknx_namespace) std::string openknx_namespace)
: bridge_(bridge), : bridge_(bridge),
handler_(std::move(handler)), handler_(std::move(handler)),
openknx_namespace_(std::move(openknx_namespace)) {} openknx_namespace_(std::move(openknx_namespace)) {
openknx_lock_ = xSemaphoreCreateMutex();
}
GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() { stop(); } GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() {
stop();
if (openknx_lock_ != nullptr) {
vSemaphoreDelete(openknx_lock_);
openknx_lock_ = nullptr;
}
}
void GatewayKnxTpIpRouter::setConfig(const GatewayKnxConfig& config) { config_ = config; } void GatewayKnxTpIpRouter::setConfig(const GatewayKnxConfig& config) { config_ = config; }
@@ -1559,6 +1610,13 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
std::vector<uint8_t>* response) { std::vector<uint8_t>* response) {
return bridge_.handleFunctionPropertyState(object_index, property_id, data, len, response); return bridge_.handleFunctionPropertyState(object_index, property_id, data, len, response);
}); });
ets_device_->setGroupWriteHandler(
[this](uint16_t group_address, const uint8_t* data, size_t len) {
const DaliBridgeResult result = bridge_.handleGroupWrite(group_address, data, len);
if (!result.ok && !result.error.empty()) {
ESP_LOGD(kTag, "secure KNX group write not routed to DALI: %s", result.error.c_str());
}
});
if (!configureTpUart()) { if (!configureTpUart()) {
ets_device_.reset(); ets_device_.reset();
closeSockets(); closeSockets();
@@ -1590,6 +1648,40 @@ bool GatewayKnxTpIpRouter::started() const { return started_; }
const std::string& GatewayKnxTpIpRouter::lastError() const { return last_error_; } const std::string& GatewayKnxTpIpRouter::lastError() const { return last_error_; }
bool GatewayKnxTpIpRouter::publishDaliStatus(const GatewayKnxDaliTarget& target,
uint8_t actual_level) {
if (!started_ || !config_.ip_router_enabled) {
return false;
}
uint16_t switch_object = 0;
uint16_t dimm_object = 0;
if (target.kind == GatewayKnxDaliTargetKind::kShortAddress) {
if (target.address < 0 || target.address > 63) {
return false;
}
const uint16_t base = kGwReg1AdrKoOffset +
kGwReg1AdrKoBlockSize * static_cast<uint16_t>(target.address);
switch_object = base + kGwReg1KoSwitchState;
dimm_object = base + kGwReg1KoDimmState;
} else if (target.kind == GatewayKnxDaliTargetKind::kGroup) {
if (target.address < 0 || target.address > 15) {
return false;
}
const uint16_t base = kGwReg1GrpKoOffset +
kGwReg1GrpKoBlockSize * static_cast<uint16_t>(target.address);
switch_object = base + kGwReg1KoSwitchState;
dimm_object = base + kGwReg1KoDimmState;
} else {
return false;
}
const uint8_t switch_value = actual_level > 0 ? 1 : 0;
const uint8_t dimm_value = DaliArcLevelToDpt5(actual_level);
bool emitted = emitOpenKnxGroupValue(switch_object, &switch_value, 1);
emitted = emitOpenKnxGroupValue(dimm_object, &dimm_value, 1) || emitted;
return emitted;
}
void GatewayKnxTpIpRouter::TaskEntry(void* arg) { void GatewayKnxTpIpRouter::TaskEntry(void* arg) {
static_cast<GatewayKnxTpIpRouter*>(arg)->taskLoop(); static_cast<GatewayKnxTpIpRouter*>(arg)->taskLoop();
} }
@@ -1603,8 +1695,11 @@ void GatewayKnxTpIpRouter::taskLoop() {
reinterpret_cast<sockaddr*>(&remote), &remote_len); reinterpret_cast<sockaddr*>(&remote), &remote_len);
if (received <= 0) { if (received <= 0) {
pollTpUart(); pollTpUart();
if (ets_device_ != nullptr) { {
ets_device_->loop(); SemaphoreGuard guard(openknx_lock_);
if (ets_device_ != nullptr) {
ets_device_->loop();
}
} }
if (!stop_requested_) { if (!stop_requested_) {
vTaskDelay(pdMS_TO_TICKS(10)); vTaskDelay(pdMS_TO_TICKS(10));
@@ -1613,8 +1708,11 @@ void GatewayKnxTpIpRouter::taskLoop() {
} }
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote); handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
pollTpUart(); pollTpUart();
if (ets_device_ != nullptr) { {
ets_device_->loop(); SemaphoreGuard guard(openknx_lock_);
if (ets_device_ != nullptr) {
ets_device_->loop();
}
} }
} }
finishTask(); finishTask();
@@ -1622,7 +1720,10 @@ void GatewayKnxTpIpRouter::taskLoop() {
void GatewayKnxTpIpRouter::finishTask() { void GatewayKnxTpIpRouter::finishTask() {
closeSockets(); closeSockets();
ets_device_.reset(); {
SemaphoreGuard guard(openknx_lock_);
ets_device_.reset();
}
started_ = false; started_ = false;
task_handle_ = nullptr; task_handle_ = nullptr;
vTaskDelete(nullptr); vTaskDelete(nullptr);
@@ -1805,9 +1906,12 @@ void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* body, size_t l
if (body == nullptr || len == 0) { if (body == nullptr || len == 0) {
return; return;
} }
const DaliBridgeResult result = handler_(body, len); const bool consumed_by_openknx = handleOpenKnxBusFrame(body, len);
if (!result.ok && !result.error.empty()) { if (!consumed_by_openknx) {
ESP_LOGD(kTag, "KNX routing indication ignored: %s", result.error.c_str()); const DaliBridgeResult result = handler_(body, len);
if (!result.ok && !result.error.empty()) {
ESP_LOGD(kTag, "KNX routing indication ignored: %s", result.error.c_str());
}
} }
forwardCemiToTp(body, len); forwardCemiToTp(body, len);
} }
@@ -1831,8 +1935,12 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* body, size_t l
sendTunnellingAck(channel_id, sequence, kKnxNoError, remote); sendTunnellingAck(channel_id, sequence, kKnxNoError, remote);
const uint8_t* cemi = body + 4; const uint8_t* cemi = body + 4;
const size_t cemi_len = len - 4; const size_t cemi_len = len - 4;
const bool group_frame = IsCemiGroupFrame(cemi, cemi_len);
const bool consumed_by_openknx = handleOpenKnxTunnelFrame(cemi, cemi_len); const bool consumed_by_openknx = handleOpenKnxTunnelFrame(cemi, cemi_len);
if (consumed_by_openknx) { if (consumed_by_openknx) {
if (group_frame) {
forwardCemiToTp(cemi, cemi_len);
}
return; return;
} }
const DaliBridgeResult result = handler_(cemi, cemi_len); const DaliBridgeResult result = handler_(cemi, cemi_len);
@@ -1959,6 +2067,7 @@ void GatewayKnxTpIpRouter::sendRoutingIndication(const uint8_t* data, size_t len
} }
bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t len) { bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t len) {
SemaphoreGuard guard(openknx_lock_);
if (ets_device_ == nullptr) { if (ets_device_ == nullptr) {
return false; return false;
} }
@@ -1970,6 +2079,32 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
return consumed; return consumed;
} }
bool GatewayKnxTpIpRouter::handleOpenKnxBusFrame(const uint8_t* data, size_t len) {
SemaphoreGuard guard(openknx_lock_);
if (ets_device_ == nullptr) {
return false;
}
const bool consumed = ets_device_->handleBusFrame(data, len);
syncOpenKnxConfigFromDevice();
return consumed;
}
bool GatewayKnxTpIpRouter::emitOpenKnxGroupValue(uint16_t group_object_number,
const uint8_t* data, size_t len) {
SemaphoreGuard guard(openknx_lock_);
if (ets_device_ == nullptr) {
return false;
}
const bool emitted = ets_device_->emitGroupValue(
group_object_number, data, len, [this](const uint8_t* frame_data, size_t frame_len) {
sendRoutingIndication(frame_data, frame_len);
sendTunnelIndication(frame_data, frame_len);
forwardCemiToTp(frame_data, frame_len);
});
syncOpenKnxConfigFromDevice();
return emitted;
}
void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() { void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
if (ets_device_ == nullptr) { if (ets_device_ == nullptr) {
return; return;
@@ -2122,9 +2257,12 @@ void GatewayKnxTpIpRouter::handleTpTelegram(const uint8_t* data, size_t len) {
if (!cemi.has_value()) { if (!cemi.has_value()) {
return; return;
} }
const DaliBridgeResult result = handler_(cemi->data(), cemi->size()); const bool consumed_by_openknx = handleOpenKnxBusFrame(cemi->data(), cemi->size());
if (!result.ok && !result.error.empty()) { if (!consumed_by_openknx) {
ESP_LOGD(kTag, "KNX TP frame not routed to DALI: %s", result.error.c_str()); const DaliBridgeResult result = handler_(cemi->data(), cemi->size());
if (!result.ok && !result.error.empty()) {
ESP_LOGD(kTag, "KNX TP frame not routed to DALI: %s", result.error.c_str());
}
} }
sendTunnelIndication(cemi->data(), cemi->size()); sendTunnelIndication(cemi->data(), cemi->size());
sendRoutingIndication(cemi->data(), cemi->size()); sendRoutingIndication(cemi->data(), cemi->size());
@@ -14,10 +14,15 @@ namespace gateway::openknx {
class EspIdfPlatform : public Platform { class EspIdfPlatform : public Platform {
public: public:
using OutboundCemiFrameCallback = bool (*)(CemiFrame& frame, void* context);
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr, explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
const char* nvs_namespace = "openknx"); const char* nvs_namespace = "openknx");
~EspIdfPlatform() override; ~EspIdfPlatform() override;
void outboundCemiFrameCallback(OutboundCemiFrameCallback callback, void* context);
bool handleOutboundCemiFrame(CemiFrame& frame) override;
void networkInterface(esp_netif_t* netif); void networkInterface(esp_netif_t* netif);
esp_netif_t* networkInterface() const; esp_netif_t* networkInterface() const;
@@ -54,6 +59,8 @@ class EspIdfPlatform : public Platform {
std::vector<uint8_t> eeprom_; std::vector<uint8_t> eeprom_;
std::string nvs_namespace_; std::string nvs_namespace_;
bool eeprom_loaded_{false}; bool eeprom_loaded_{false};
OutboundCemiFrameCallback outbound_cemi_frame_callback_{nullptr};
void* outbound_cemi_frame_context_{nullptr};
}; };
} // namespace gateway::openknx } // namespace gateway::openknx
@@ -17,6 +17,8 @@ namespace gateway::openknx {
class EtsDeviceRuntime { class EtsDeviceRuntime {
public: public:
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>; using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
size_t len)>;
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id, using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len, const uint8_t* data, size_t len,
std::vector<uint8_t>* response)>; std::vector<uint8_t>* response)>;
@@ -31,12 +33,19 @@ class EtsDeviceRuntime {
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler, void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
FunctionPropertyHandler state_handler); FunctionPropertyHandler state_handler);
void setGroupWriteHandler(GroupWriteHandler handler);
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender); bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
bool handleBusFrame(const uint8_t* data, size_t len);
bool emitGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len,
CemiFrameSender sender);
void loop(); void loop();
private: private:
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
static void EmitTunnelFrame(CemiFrame& frame, void* context); static void EmitTunnelFrame(CemiFrame& frame, void* context);
static void HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
uint8_t data_length, void* context);
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id, static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length); uint8_t* result_data, uint8_t& result_length);
@@ -48,11 +57,13 @@ class EtsDeviceRuntime {
uint8_t property_id, uint8_t length, uint8_t* data, uint8_t property_id, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length); uint8_t* result_data, uint8_t& result_length);
bool shouldConsumeTunnelFrame(CemiFrame& frame) const; bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
bool shouldConsumeBusFrame(CemiFrame& frame) const;
std::string nvs_namespace_; std::string nvs_namespace_;
EspIdfPlatform platform_; EspIdfPlatform platform_;
Bau07B0 device_; Bau07B0 device_;
CemiFrameSender sender_; CemiFrameSender sender_;
GroupWriteHandler group_write_handler_;
FunctionPropertyHandler command_handler_; FunctionPropertyHandler command_handler_;
FunctionPropertyHandler state_handler_; FunctionPropertyHandler state_handler_;
}; };
@@ -51,6 +51,19 @@ EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface,
EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); } EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); }
void EspIdfPlatform::outboundCemiFrameCallback(OutboundCemiFrameCallback callback,
void* context) {
outbound_cemi_frame_callback_ = callback;
outbound_cemi_frame_context_ = context;
}
bool EspIdfPlatform::handleOutboundCemiFrame(CemiFrame& frame) {
if (outbound_cemi_frame_callback_ == nullptr) {
return false;
}
return outbound_cemi_frame_callback_(frame, outbound_cemi_frame_context_);
}
void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; } void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; } esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
@@ -1,6 +1,7 @@
#include "openknx_idf/ets_device_runtime.h" #include "openknx_idf/ets_device_runtime.h"
#include "knx/cemi_server.h" #include "knx/cemi_server.h"
#include "knx/secure_application_layer.h"
#include "knx/property.h" #include "knx/property.h"
#include <algorithm> #include <algorithm>
@@ -52,6 +53,7 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
: nvs_namespace_(std::move(nvs_namespace)), : nvs_namespace_(std::move(nvs_namespace)),
platform_(nullptr, nvs_namespace_.c_str()), platform_(nullptr, nvs_namespace_.c_str()),
device_(platform_) { device_(platform_) {
platform_.outboundCemiFrameCallback(&EtsDeviceRuntime::HandleOutboundCemiFrame, this);
ApplyReg1DaliIdentity(device_, platform_); ApplyReg1DaliIdentity(device_, platform_);
if (IsUsableIndividualAddress(fallback_individual_address)) { if (IsUsableIndividualAddress(fallback_individual_address)) {
device_.deviceObject().individualAddress(fallback_individual_address); device_.deviceObject().individualAddress(fallback_individual_address);
@@ -67,9 +69,16 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
} }
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand); device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState); device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
#ifdef USE_DATASECURE
device_.secureGroupWriteCallback(&EtsDeviceRuntime::HandleSecureGroupWrite, this);
#endif
} }
EtsDeviceRuntime::~EtsDeviceRuntime() { EtsDeviceRuntime::~EtsDeviceRuntime() {
platform_.outboundCemiFrameCallback(nullptr, nullptr);
#ifdef USE_DATASECURE
device_.secureGroupWriteCallback(nullptr, nullptr);
#endif
device_.functionPropertyCallback(nullptr); device_.functionPropertyCallback(nullptr);
device_.functionPropertyStateCallback(nullptr); device_.functionPropertyStateCallback(nullptr);
if (auto* server = device_.getCemiServer()) { if (auto* server = device_.getCemiServer()) {
@@ -126,6 +135,10 @@ void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler comma
state_handler_ = std::move(state_handler); state_handler_ = std::move(state_handler);
} }
void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) {
group_write_handler_ = std::move(handler);
}
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len, bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
CemiFrameSender sender) { CemiFrameSender sender) {
auto* server = device_.getCemiServer(); auto* server = device_.getCemiServer();
@@ -146,8 +159,58 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
return consumed; return consumed;
} }
bool EtsDeviceRuntime::handleBusFrame(const uint8_t* data, size_t len) {
auto* data_link_layer = device_.getDataLinkLayer();
if (data_link_layer == nullptr || data == nullptr || len < 2) {
return false;
}
std::vector<uint8_t> frame_data(data, data + len);
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
const bool consumed = shouldConsumeBusFrame(frame);
if (!consumed) {
return false;
}
data_link_layer->externalFrameReceived(frame);
loop();
return consumed;
}
bool EtsDeviceRuntime::emitGroupValue(uint16_t group_object_number, const uint8_t* data,
size_t len, CemiFrameSender sender) {
if (group_object_number == 0 || data == nullptr || !sender || !device_.configured()) {
return false;
}
auto& table = device_.groupObjectTable();
if (group_object_number > table.entryCount()) {
return false;
}
auto& group_object = table.get(group_object_number);
if (len != group_object.valueSize() || group_object.valueRef() == nullptr) {
return false;
}
if (group_object.sizeInTelegram() == 0) {
group_object.valueRef()[0] = data[0] & 0x01;
} else {
std::copy_n(data, len, group_object.valueRef());
}
sender_ = std::move(sender);
group_object.objectWritten();
loop();
sender_ = nullptr;
return true;
}
void EtsDeviceRuntime::loop() { device_.loop(); } void EtsDeviceRuntime::loop() { device_.loop(); }
bool EtsDeviceRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) {
auto* self = static_cast<EtsDeviceRuntime*>(context);
if (self == nullptr || !self->sender_) {
return false;
}
self->sender_(frame.data(), frame.dataLength());
return true;
}
void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) { void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
auto* self = static_cast<EtsDeviceRuntime*>(context); auto* self = static_cast<EtsDeviceRuntime*>(context);
if (self == nullptr || !self->sender_) { if (self == nullptr || !self->sender_) {
@@ -156,6 +219,15 @@ void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
self->sender_(frame.data(), frame.dataLength()); self->sender_(frame.data(), frame.dataLength());
} }
void EtsDeviceRuntime::HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
uint8_t data_length, void* context) {
auto* self = static_cast<EtsDeviceRuntime*>(context);
if (self == nullptr || !self->group_write_handler_) {
return;
}
self->group_write_handler_(group_address, data, data_length);
}
bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id, bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data, uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t* result_data,
@@ -217,11 +289,27 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
case M_FuncPropStateRead_req: case M_FuncPropStateRead_req:
return true; return true;
case L_data_req: case L_data_req:
return frame.addressType() == IndividualAddress && if (frame.addressType() == IndividualAddress &&
frame.destinationAddress() == individualAddress(); frame.destinationAddress() == individualAddress()) {
return true;
}
#ifdef USE_DATASECURE
return frame.addressType() == GroupAddress && frame.apdu().type() == SecureService;
#else
return false;
#endif
default: default:
return false; return false;
} }
} }
bool EtsDeviceRuntime::shouldConsumeBusFrame(CemiFrame& frame) const {
#ifdef USE_DATASECURE
return frame.messageCode() == L_data_ind && frame.addressType() == GroupAddress &&
frame.apdu().type() == SecureService;
#else
return false;
#endif
}
} // namespace gateway::openknx } // namespace gateway::openknx
+1 -1
Submodule knx updated: 5da3b1f30e...1549366447