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
@@ -14,10 +14,15 @@ namespace gateway::openknx {
class EspIdfPlatform : public Platform {
public:
using OutboundCemiFrameCallback = bool (*)(CemiFrame& frame, void* context);
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
const char* nvs_namespace = "openknx");
~EspIdfPlatform() override;
void outboundCemiFrameCallback(OutboundCemiFrameCallback callback, void* context);
bool handleOutboundCemiFrame(CemiFrame& frame) override;
void networkInterface(esp_netif_t* netif);
esp_netif_t* networkInterface() const;
@@ -54,6 +59,8 @@ class EspIdfPlatform : public Platform {
std::vector<uint8_t> eeprom_;
std::string nvs_namespace_;
bool eeprom_loaded_{false};
OutboundCemiFrameCallback outbound_cemi_frame_callback_{nullptr};
void* outbound_cemi_frame_context_{nullptr};
};
} // namespace gateway::openknx
@@ -17,6 +17,8 @@ namespace gateway::openknx {
class EtsDeviceRuntime {
public:
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,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response)>;
@@ -31,12 +33,19 @@ class EtsDeviceRuntime {
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
FunctionPropertyHandler state_handler);
void setGroupWriteHandler(GroupWriteHandler handler);
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();
private:
static bool HandleOutboundCemiFrame(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,
uint8_t length, uint8_t* data,
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* result_data, uint8_t& result_length);
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
bool shouldConsumeBusFrame(CemiFrame& frame) const;
std::string nvs_namespace_;
EspIdfPlatform platform_;
Bau07B0 device_;
CemiFrameSender sender_;
GroupWriteHandler group_write_handler_;
FunctionPropertyHandler command_handler_;
FunctionPropertyHandler state_handler_;
};
@@ -51,6 +51,19 @@ EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface,
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; }
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
@@ -1,6 +1,7 @@
#include "openknx_idf/ets_device_runtime.h"
#include "knx/cemi_server.h"
#include "knx/secure_application_layer.h"
#include "knx/property.h"
#include <algorithm>
@@ -52,6 +53,7 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
: nvs_namespace_(std::move(nvs_namespace)),
platform_(nullptr, nvs_namespace_.c_str()),
device_(platform_) {
platform_.outboundCemiFrameCallback(&EtsDeviceRuntime::HandleOutboundCemiFrame, this);
ApplyReg1DaliIdentity(device_, platform_);
if (IsUsableIndividualAddress(fallback_individual_address)) {
device_.deviceObject().individualAddress(fallback_individual_address);
@@ -67,9 +69,16 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
}
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
#ifdef USE_DATASECURE
device_.secureGroupWriteCallback(&EtsDeviceRuntime::HandleSecureGroupWrite, this);
#endif
}
EtsDeviceRuntime::~EtsDeviceRuntime() {
platform_.outboundCemiFrameCallback(nullptr, nullptr);
#ifdef USE_DATASECURE
device_.secureGroupWriteCallback(nullptr, nullptr);
#endif
device_.functionPropertyCallback(nullptr);
device_.functionPropertyStateCallback(nullptr);
if (auto* server = device_.getCemiServer()) {
@@ -126,6 +135,10 @@ void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler comma
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,
CemiFrameSender sender) {
auto* server = device_.getCemiServer();
@@ -146,8 +159,58 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
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(); }
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) {
auto* self = static_cast<EtsDeviceRuntime*>(context);
if (self == nullptr || !self->sender_) {
@@ -156,6 +219,15 @@ void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
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,
uint8_t length, uint8_t* data,
uint8_t* result_data,
@@ -217,11 +289,27 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
case M_FuncPropStateRead_req:
return true;
case L_data_req:
return frame.addressType() == IndividualAddress &&
frame.destinationAddress() == individualAddress();
if (frame.addressType() == IndividualAddress &&
frame.destinationAddress() == individualAddress()) {
return true;
}
#ifdef USE_DATASECURE
return frame.addressType() == GroupAddress && frame.apdu().type() == SecureService;
#else
return false;
#endif
default:
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