feat(gateway): enhance KNX support with DALI integration and configuration updates
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -64,6 +64,8 @@ constexpr uint16_t kGwReg1AppKoBroadcastDimm = 2;
|
||||
constexpr uint8_t kGwReg1KoSwitch = 0;
|
||||
constexpr uint8_t kGwReg1KoDimmAbsolute = 3;
|
||||
constexpr uint8_t kGwReg1KoColor = 6;
|
||||
constexpr uint8_t kGwReg1KoSwitchState = 1;
|
||||
constexpr uint8_t kGwReg1KoDimmState = 4;
|
||||
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
|
||||
constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
|
||||
constexpr uint8_t kReg1FunctionType = 2;
|
||||
@@ -84,6 +86,31 @@ struct DecodedGroupWrite {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (value == 0 || value == 0xff) {
|
||||
return value;
|
||||
@@ -1528,9 +1571,17 @@ GatewayKnxTpIpRouter::GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHa
|
||||
std::string openknx_namespace)
|
||||
: bridge_(bridge),
|
||||
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; }
|
||||
|
||||
@@ -1559,6 +1610,13 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
|
||||
std::vector<uint8_t>* 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()) {
|
||||
ets_device_.reset();
|
||||
closeSockets();
|
||||
@@ -1590,6 +1648,40 @@ bool GatewayKnxTpIpRouter::started() const { return started_; }
|
||||
|
||||
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) {
|
||||
static_cast<GatewayKnxTpIpRouter*>(arg)->taskLoop();
|
||||
}
|
||||
@@ -1603,8 +1695,11 @@ void GatewayKnxTpIpRouter::taskLoop() {
|
||||
reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
||||
if (received <= 0) {
|
||||
pollTpUart();
|
||||
if (ets_device_ != nullptr) {
|
||||
ets_device_->loop();
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ != nullptr) {
|
||||
ets_device_->loop();
|
||||
}
|
||||
}
|
||||
if (!stop_requested_) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
@@ -1613,8 +1708,11 @@ void GatewayKnxTpIpRouter::taskLoop() {
|
||||
}
|
||||
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
|
||||
pollTpUart();
|
||||
if (ets_device_ != nullptr) {
|
||||
ets_device_->loop();
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ != nullptr) {
|
||||
ets_device_->loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
finishTask();
|
||||
@@ -1622,7 +1720,10 @@ void GatewayKnxTpIpRouter::taskLoop() {
|
||||
|
||||
void GatewayKnxTpIpRouter::finishTask() {
|
||||
closeSockets();
|
||||
ets_device_.reset();
|
||||
{
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
ets_device_.reset();
|
||||
}
|
||||
started_ = false;
|
||||
task_handle_ = nullptr;
|
||||
vTaskDelete(nullptr);
|
||||
@@ -1805,9 +1906,12 @@ void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* body, size_t l
|
||||
if (body == nullptr || len == 0) {
|
||||
return;
|
||||
}
|
||||
const DaliBridgeResult result = handler_(body, len);
|
||||
if (!result.ok && !result.error.empty()) {
|
||||
ESP_LOGD(kTag, "KNX routing indication ignored: %s", result.error.c_str());
|
||||
const bool consumed_by_openknx = handleOpenKnxBusFrame(body, len);
|
||||
if (!consumed_by_openknx) {
|
||||
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);
|
||||
}
|
||||
@@ -1831,8 +1935,12 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* body, size_t l
|
||||
sendTunnellingAck(channel_id, sequence, kKnxNoError, remote);
|
||||
const uint8_t* cemi = body + 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);
|
||||
if (consumed_by_openknx) {
|
||||
if (group_frame) {
|
||||
forwardCemiToTp(cemi, cemi_len);
|
||||
}
|
||||
return;
|
||||
}
|
||||
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) {
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
if (ets_device_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -1970,6 +2079,32 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
||||
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() {
|
||||
if (ets_device_ == nullptr) {
|
||||
return;
|
||||
@@ -2122,9 +2257,12 @@ void GatewayKnxTpIpRouter::handleTpTelegram(const uint8_t* data, size_t len) {
|
||||
if (!cemi.has_value()) {
|
||||
return;
|
||||
}
|
||||
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());
|
||||
const bool consumed_by_openknx = handleOpenKnxBusFrame(cemi->data(), cemi->size());
|
||||
if (!consumed_by_openknx) {
|
||||
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());
|
||||
sendRoutingIndication(cemi->data(), cemi->size());
|
||||
|
||||
Reference in New Issue
Block a user