Add EtsDeviceRuntime class for handling KNX device runtime operations

- Introduced EtsDeviceRuntime class to manage device runtime functionalities including handling tunnel frames and function property commands.
- Added support for individual address management and memory snapshot retrieval.
- Updated EtsMemorySnapshot structure to include individual address.
- Implemented identity application for DALI devices in the memory loader.
- Enhanced CMakeLists.txt to include new source files and compile definitions.
- Updated header files to include new dependencies and declarations.
- Refactored existing memory loading logic to accommodate new device runtime features.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-12 05:19:14 +08:00
parent d231460612
commit 36d10702da
14 changed files with 1034 additions and 41 deletions
+618 -6
View File
@@ -1,9 +1,11 @@
#include "gateway_knx.hpp"
#include "dali_define.hpp"
#include "driver/uart.h"
#include "esp_log.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include "openknx_idf/ets_device_runtime.h"
#include <algorithm>
#include <array>
@@ -62,6 +64,20 @@ constexpr uint16_t kGwReg1AppKoBroadcastDimm = 2;
constexpr uint8_t kGwReg1KoSwitch = 0;
constexpr uint8_t kGwReg1KoDimmAbsolute = 3;
constexpr uint8_t kGwReg1KoColor = 6;
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
constexpr uint8_t kReg1FunctionType = 2;
constexpr uint8_t kReg1FunctionScan = 3;
constexpr uint8_t kReg1FunctionAssign = 4;
constexpr uint8_t kReg1FunctionEvgWrite = 10;
constexpr uint8_t kReg1FunctionEvgRead = 11;
constexpr uint8_t kReg1FunctionSetScene = 12;
constexpr uint8_t kReg1FunctionGetScene = 13;
constexpr uint8_t kReg1FunctionIdentify = 14;
constexpr uint8_t kReg1DeviceTypeDt8 = 8;
constexpr uint8_t kReg1ColorTypeTw = 1;
constexpr uint8_t kDaliDeviceTypeNone = 0xfe;
constexpr uint8_t kDaliDeviceTypeMultiple = 0xff;
struct DecodedGroupWrite {
uint16_t group_address{0};
@@ -293,6 +309,88 @@ std::optional<DecodedGroupWrite> DecodeCemiGroupWrite(const uint8_t* data, size_
return out;
}
uint8_t Reg1PercentToArc(uint8_t value) {
if (value == 0 || value == 0xff) {
return value;
}
const double arc = ((253.0 / 3.0) * (std::log10(static_cast<double>(value)) + 1.0)) + 1.0;
return static_cast<uint8_t>(std::clamp(static_cast<int>(arc + 0.5), 0, 254));
}
uint8_t Reg1ArcToPercent(uint8_t value) {
if (value == 0 || value == 0xff) {
return value;
}
const double percent = std::pow(10.0, ((static_cast<double>(value) - 1.0) / (253.0 / 3.0)) - 1.0);
return static_cast<uint8_t>(std::clamp(static_cast<int>(percent + 0.5), 0, 100));
}
GatewayKnxDaliTarget Reg1SceneTarget(uint8_t encoded_target) {
if ((encoded_target & 0x80) != 0) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
static_cast<int>(encoded_target & 0x0f)};
}
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
static_cast<int>(encoded_target & 0x3f)};
}
DaliBridgeRequest FunctionRequest(const char* sequence, BridgeOperation operation) {
DaliBridgeRequest request;
request.sequence = sequence == nullptr ? "knx-function-property" : sequence;
request.operation = operation;
return request;
}
void ApplyTargetToRequest(const GatewayKnxDaliTarget& target, DaliBridgeRequest* request) {
if (request == nullptr) {
return;
}
switch (target.kind) {
case GatewayKnxDaliTargetKind::kBroadcast:
request->metadata["broadcast"] = true;
break;
case GatewayKnxDaliTargetKind::kShortAddress:
request->shortAddress = target.address;
break;
case GatewayKnxDaliTargetKind::kGroup:
request->metadata["group"] = target.address;
break;
case GatewayKnxDaliTargetKind::kNone:
default:
break;
}
}
DaliBridgeResult ExecuteRaw(DaliBridgeEngine& engine, BridgeOperation operation, uint8_t addr,
uint8_t cmd, const char* sequence) {
DaliBridgeRequest request = FunctionRequest(sequence, operation);
request.rawAddress = addr;
request.rawCommand = cmd;
return engine.execute(request);
}
std::optional<int> QueryShort(DaliBridgeEngine& engine, uint8_t short_address, uint8_t command,
const char* sequence) {
const auto result = ExecuteRaw(engine, BridgeOperation::query, DaliComm::toCmdAddr(short_address),
command, sequence);
if (!result.ok || !result.data.has_value()) {
return std::nullopt;
}
return result.data.value();
}
bool SendRaw(DaliBridgeEngine& engine, uint8_t addr, uint8_t cmd, const char* sequence) {
return ExecuteRaw(engine, BridgeOperation::send, addr, cmd, sequence).ok;
}
bool SendRawExt(DaliBridgeEngine& engine, uint8_t addr, uint8_t cmd, const char* sequence) {
return ExecuteRaw(engine, BridgeOperation::sendExt, addr, cmd, sequence).ok;
}
std::optional<int> MetadataInt(const DaliBridgeResult& result, const std::string& key) {
return getObjectInt(result.metadata, key);
}
DaliBridgeRequest RequestForTarget(uint16_t group_address,
const GatewayKnxDaliTarget& target,
BridgeOperation operation) {
@@ -923,6 +1021,421 @@ DaliBridgeResult GatewayKnxBridge::handleGroupWrite(uint16_t group_address, cons
return executeForDecodedWrite(group_address, data_type.value(), target.value(), data, len);
}
bool GatewayKnxBridge::handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (object_index != kReg1DaliFunctionObjectIndex || property_id != kReg1DaliFunctionPropertyId ||
data == nullptr || len == 0 || response == nullptr) {
return false;
}
switch (data[0]) {
case kReg1FunctionType:
return handleReg1TypeCommand(data, len, response);
case kReg1FunctionScan:
return handleReg1ScanCommand(data, len, response);
case kReg1FunctionAssign:
return handleReg1AssignCommand(data, len, response);
case kReg1FunctionEvgWrite:
return handleReg1EvgWriteCommand(data, len, response);
case kReg1FunctionEvgRead:
return handleReg1EvgReadCommand(data, len, response);
case kReg1FunctionSetScene:
return handleReg1SetSceneCommand(data, len, response);
case kReg1FunctionGetScene:
return handleReg1GetSceneCommand(data, len, response);
case kReg1FunctionIdentify:
return handleReg1IdentifyCommand(data, len, response);
default:
return false;
}
}
bool GatewayKnxBridge::handleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (object_index != kReg1DaliFunctionObjectIndex || property_id != kReg1DaliFunctionPropertyId ||
data == nullptr || len == 0 || response == nullptr) {
return false;
}
switch (data[0]) {
case kReg1FunctionScan:
case 5:
return handleReg1ScanState(data, len, response);
case kReg1FunctionAssign:
return handleReg1AssignState(data, len, response);
case 7:
return handleReg1FoundEvgsState(data, len, response);
default:
return false;
}
}
bool GatewayKnxBridge::handleReg1TypeCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 2 || response == nullptr) {
return false;
}
const uint8_t short_address = data[1];
const auto type_response = QueryShort(engine_, short_address, DALI_CMD_QUERY_DEVICE_TYPE,
"knx-function-type");
if (!type_response.has_value()) {
*response = {0x01};
return true;
}
uint8_t device_type = static_cast<uint8_t>(type_response.value());
if (device_type == kDaliDeviceTypeMultiple) {
for (int index = 0; index < 16; ++index) {
const auto next_type = QueryShort(engine_, short_address, DALI_CMD_QUERY_NEXT_DEVICE_TYPE,
"knx-function-next-device-type");
if (!next_type.has_value()) {
*response = {0x01};
return true;
}
if (next_type.value() == kDaliDeviceTypeNone) {
break;
}
if (next_type.value() < 20) {
device_type = static_cast<uint8_t>(next_type.value());
}
}
}
*response = {0x00, device_type};
if (device_type == kReg1DeviceTypeDt8) {
if (!SendRaw(engine_, DALI_CMD_SPECIAL_DT_SELECT, kReg1DeviceTypeDt8,
"knx-function-dt8-select")) {
*response = {0x02};
return true;
}
const auto color_features = QueryShort(engine_, short_address, DALI_CMD_QUERY_COLOR_TYPE,
"knx-function-color-type");
if (!color_features.has_value()) {
*response = {0x02};
return true;
}
response->push_back(static_cast<uint8_t>(color_features.value()));
}
return true;
}
bool GatewayKnxBridge::handleReg1ScanCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 5 || response == nullptr) {
return false;
}
commissioning_scan_done_ = false;
commissioning_found_ballasts_.clear();
const bool delete_all = data[3] == 1;
const bool assign = data[4] == 1;
if (assign || delete_all) {
DaliBridgeRequest allocate = FunctionRequest(
"knx-function-scan-allocate",
delete_all ? BridgeOperation::resetAndAllocateShortAddresses
: BridgeOperation::allocateAllShortAddresses);
allocate.value = DaliValue::Object{{"start", 0}, {"removeAddrFirst", delete_all}};
engine_.execute(allocate);
}
DaliBridgeRequest search = FunctionRequest("knx-function-scan-search", BridgeOperation::searchAddressRange);
search.value = DaliValue::Object{{"start", 0}, {"end", 63}};
const auto search_result = engine_.execute(search);
if (search_result.ok) {
if (const auto* addresses_value = getObjectValue(search_result.metadata, "addresses")) {
if (const auto* addresses = addresses_value->asArray()) {
for (const auto& address_value : *addresses) {
const auto short_address = address_value.asInt();
if (!short_address.has_value() || short_address.value() < 0 || short_address.value() > 63) {
continue;
}
GatewayKnxCommissioningBallast ballast;
ballast.short_address = static_cast<uint8_t>(short_address.value());
ballast.high = static_cast<uint8_t>(
QueryShort(engine_, ballast.short_address, DALI_CMD_QUERY_RANDOM_ADDRESS_H,
"knx-function-scan-rand-h")
.value_or(0));
ballast.middle = static_cast<uint8_t>(
QueryShort(engine_, ballast.short_address, DALI_CMD_QUERY_RANDOM_ADDRESS_M,
"knx-function-scan-rand-m")
.value_or(0));
ballast.low = static_cast<uint8_t>(
QueryShort(engine_, ballast.short_address, DALI_CMD_QUERY_RANDOM_ADDRESS_L,
"knx-function-scan-rand-l")
.value_or(0));
commissioning_found_ballasts_.push_back(ballast);
}
}
}
}
commissioning_scan_done_ = true;
response->clear();
return true;
}
bool GatewayKnxBridge::handleReg1AssignCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 5 || response == nullptr) {
return false;
}
commissioning_assign_done_ = false;
const uint8_t short_address = data[1] == 99 ? 0xff : data[1];
const bool ok = SendRawExt(engine_, DALI_CMD_SPECIAL_INITIALIZE, 0x00,
"knx-function-assign-init") &&
SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRH, data[2],
"knx-function-assign-search-h") &&
SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRM, data[3],
"knx-function-assign-search-m") &&
SendRaw(engine_, DALI_CMD_SPECIAL_SEARCHADDRL, data[4],
"knx-function-assign-search-l") &&
SendRaw(engine_, DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS,
short_address == 0xff ? 0xff : DaliComm::toCmdAddr(short_address),
"knx-function-assign-program") &&
SendRaw(engine_, DALI_CMD_SPECIAL_TERMINATE, 0x00,
"knx-function-assign-terminate");
commissioning_assign_done_ = true;
if (!ok) {
ESP_LOGW(kTag, "REG1-Dali assign command failed while programming short address %u",
short_address);
}
response->clear();
return true;
}
bool GatewayKnxBridge::handleReg1EvgWriteCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 10 || response == nullptr) {
return false;
}
const uint8_t short_address = data[1];
DaliBridgeRequest settings = FunctionRequest("knx-function-evg-write-settings",
BridgeOperation::setAddressSettings);
settings.shortAddress = short_address;
settings.value = DaliValue::Object{
{"minLevel", Reg1PercentToArc(data[2])},
{"maxLevel", Reg1PercentToArc(data[3])},
{"powerOnLevel", Reg1PercentToArc(data[4])},
{"systemFailureLevel", Reg1PercentToArc(data[5])},
{"fadeTime", static_cast<int>((data[6] >> 4) & 0x0f)},
{"fadeRate", static_cast<int>(data[6] & 0x0f)},
};
const bool settings_ok = engine_.execute(settings).ok;
DaliBridgeRequest groups = FunctionRequest("knx-function-evg-write-groups",
BridgeOperation::setGroupMask);
groups.shortAddress = short_address;
groups.value = static_cast<int>(static_cast<uint16_t>(data[8]) |
(static_cast<uint16_t>(data[9]) << 8));
const bool groups_ok = engine_.execute(groups).ok;
if (!settings_ok || !groups_ok) {
ESP_LOGW(kTag, "REG1-Dali EVG write command failed for short address %u", short_address);
}
response->clear();
return true;
}
bool GatewayKnxBridge::handleReg1EvgReadCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 2 || response == nullptr) {
return false;
}
const uint8_t short_address = data[1];
response->assign(12, 0x00);
(*response)[0] = 0x00;
uint8_t error_byte = 0;
DaliBridgeRequest settings = FunctionRequest("knx-function-evg-read-settings",
BridgeOperation::getAddressSettings);
settings.shortAddress = short_address;
const auto settings_result = engine_.execute(settings);
const auto set_level = [&](size_t index, const char* key, uint8_t error_mask) {
const auto value = MetadataInt(settings_result, key);
if (!settings_result.ok || !value.has_value()) {
error_byte |= error_mask;
(*response)[index] = 0xff;
return;
}
(*response)[index] = Reg1ArcToPercent(static_cast<uint8_t>(std::clamp(value.value(), 0, 255)));
};
set_level(1, "minLevel", 0b00000001);
set_level(2, "maxLevel", 0b00000010);
set_level(3, "powerOnLevel", 0b00000100);
set_level(4, "systemFailureLevel", 0b00001000);
const auto fade_time = MetadataInt(settings_result, "fadeTime");
const auto fade_rate = MetadataInt(settings_result, "fadeRate");
if (!settings_result.ok || !fade_time.has_value() || !fade_rate.has_value()) {
error_byte |= 0b00010000;
(*response)[5] = 0xff;
} else {
(*response)[5] = static_cast<uint8_t>(((fade_rate.value() & 0x0f) << 4) |
(fade_time.value() & 0x0f));
}
DaliBridgeRequest groups = FunctionRequest("knx-function-evg-read-groups", BridgeOperation::getGroupMask);
groups.shortAddress = short_address;
const auto groups_result = engine_.execute(groups);
if (!groups_result.ok || !groups_result.data.has_value()) {
error_byte |= 0b11000000;
} else {
const uint16_t mask = static_cast<uint16_t>(groups_result.data.value());
(*response)[7] = static_cast<uint8_t>(mask & 0xff);
(*response)[8] = static_cast<uint8_t>((mask >> 8) & 0xff);
}
(*response)[9] = error_byte;
return true;
}
bool GatewayKnxBridge::handleReg1SetSceneCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 10 || response == nullptr) {
return false;
}
const GatewayKnxDaliTarget target = Reg1SceneTarget(data[1]);
const uint8_t scene = data[2] & 0x0f;
const bool enabled = data[3] != 0;
DaliBridgeRequest request = FunctionRequest(
enabled ? "knx-function-set-scene" : "knx-function-remove-scene",
enabled ? (data[4] == kReg1DeviceTypeDt8 ? BridgeOperation::storeDt8SceneSnapshot
: BridgeOperation::setSceneLevel)
: BridgeOperation::removeSceneLevel);
ApplyTargetToRequest(target, &request);
DaliValue::Object value{{"scene", static_cast<int>(scene)}};
if (enabled) {
value["brightness"] = static_cast<int>(Reg1PercentToArc(data[6]));
if (data[4] == kReg1DeviceTypeDt8) {
if (data[5] == kReg1ColorTypeTw) {
const uint16_t kelvin = ReadBe16(data + 7);
value["colorMode"] = "color_temperature";
value["colorTemperature"] = static_cast<int>(kelvin);
} else {
value["colorMode"] = "rgb";
value["r"] = static_cast<int>(data[7]);
value["g"] = static_cast<int>(data[8]);
value["b"] = static_cast<int>(data[9]);
}
}
}
request.value = std::move(value);
const auto result = engine_.execute(request);
if (!result.ok) {
ESP_LOGW(kTag, "REG1-Dali set scene command failed for scene %u", scene);
}
response->clear();
return true;
}
bool GatewayKnxBridge::handleReg1GetSceneCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 5 || response == nullptr) {
return false;
}
const uint8_t short_address = data[1];
const uint8_t scene = data[2] & 0x0f;
DaliBridgeRequest request = FunctionRequest("knx-function-get-scene", BridgeOperation::getSceneLevel);
request.shortAddress = short_address;
request.value = DaliValue::Object{{"scene", static_cast<int>(scene)}};
const auto result = engine_.execute(request);
if (!result.ok || !result.data.has_value()) {
*response = {0xff};
return true;
}
const uint8_t raw_level = static_cast<uint8_t>(std::clamp(result.data.value(), 0, 255));
*response = {static_cast<uint8_t>(raw_level == 0xff ? 0xff : Reg1ArcToPercent(raw_level))};
if (raw_level != 0xff && data[3] == kReg1DeviceTypeDt8) {
if (data[4] == kReg1ColorTypeTw) {
response->resize(3, 0);
SendRaw(engine_, DALI_CMD_SPECIAL_SET_DTR0, 0xe2, "knx-function-get-scene-ct-selector");
SendRaw(engine_, DALI_CMD_SPECIAL_DT_SELECT, kReg1DeviceTypeDt8,
"knx-function-get-scene-ct-dt-select");
const uint16_t mirek = static_cast<uint16_t>(
(QueryShort(engine_, short_address, DALI_CMD_QUERY_COLOR_VALUE,
"knx-function-get-scene-mirek-h")
.value_or(0)
<< 8) |
QueryShort(engine_, short_address, DALI_CMD_QUERY_CONTENT_DTR,
"knx-function-get-scene-mirek-l")
.value_or(0));
const uint16_t kelvin = mirek == 0 ? 0 : static_cast<uint16_t>(1000000U / mirek);
(*response)[1] = static_cast<uint8_t>((kelvin >> 8) & 0xff);
(*response)[2] = static_cast<uint8_t>(kelvin & 0xff);
} else {
response->resize(4, 0);
const std::array<uint8_t, 3> selectors{0xe9, 0xea, 0xeb};
for (size_t index = 0; index < selectors.size(); ++index) {
SendRaw(engine_, DALI_CMD_SPECIAL_SET_DTR0, selectors[index],
"knx-function-get-scene-rgb-selector");
SendRaw(engine_, DALI_CMD_SPECIAL_DT_SELECT, kReg1DeviceTypeDt8,
"knx-function-get-scene-rgb-dt-select");
(*response)[index + 1] = static_cast<uint8_t>(
QueryShort(engine_, short_address, DALI_CMD_QUERY_COLOR_VALUE,
"knx-function-get-scene-rgb-value")
.value_or(0));
}
}
}
return true;
}
bool GatewayKnxBridge::handleReg1IdentifyCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 2 || response == nullptr) {
return false;
}
DaliBridgeRequest off = FunctionRequest("knx-function-identify-broadcast-off", BridgeOperation::off);
off.metadata["broadcast"] = true;
engine_.execute(off);
DaliBridgeRequest identify = FunctionRequest("knx-function-identify-recall-max",
BridgeOperation::recallMaxLevel);
identify.shortAddress = data[1];
engine_.execute(identify);
response->clear();
return true;
}
bool GatewayKnxBridge::handleReg1ScanState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 1 || response == nullptr) {
return false;
}
response->clear();
response->push_back(commissioning_scan_done_ ? 1 : 0);
if (data[0] == kReg1FunctionScan) {
response->push_back(static_cast<uint8_t>(
std::min<size_t>(commissioning_found_ballasts_.size(), 0xff)));
}
return true;
}
bool GatewayKnxBridge::handleReg1AssignState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 1 || response == nullptr) {
return false;
}
*response = {static_cast<uint8_t>(commissioning_assign_done_ ? 1 : 0)};
return true;
}
bool GatewayKnxBridge::handleReg1FoundEvgsState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
if (len < 2 || response == nullptr) {
return false;
}
if (data[1] == 254) {
commissioning_found_ballasts_.clear();
response->clear();
return true;
}
const size_t index = data[1];
response->clear();
response->push_back(index < commissioning_found_ballasts_.size() ? 1 : 0);
if (index < commissioning_found_ballasts_.size()) {
const auto& ballast = commissioning_found_ballasts_[index];
response->push_back(ballast.high);
response->push_back(ballast.middle);
response->push_back(ballast.low);
response->push_back(ballast.short_address);
}
return true;
}
DaliBridgeResult GatewayKnxBridge::executeEtsBindings(
uint16_t group_address, const std::vector<GatewayKnxDaliBinding>& bindings,
const uint8_t* data, size_t len) {
@@ -1011,8 +1524,11 @@ DaliBridgeResult GatewayKnxBridge::executeForDecodedWrite(uint16_t group_address
}
}
GatewayKnxTpIpRouter::GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler)
: bridge_(bridge), handler_(std::move(handler)) {}
GatewayKnxTpIpRouter::GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler,
std::string openknx_namespace)
: bridge_(bridge),
handler_(std::move(handler)),
openknx_namespace_(std::move(openknx_namespace)) {}
GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() { stop(); }
@@ -1032,7 +1548,19 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
if (!configureSocket()) {
return ESP_FAIL;
}
ets_device_ = std::make_unique<openknx::EtsDeviceRuntime>(openknx_namespace_,
config_.individual_address);
ets_device_->setFunctionPropertyHandlers(
[this](uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
return bridge_.handleFunctionPropertyCommand(object_index, property_id, data, len, response);
},
[this](uint8_t object_index, uint8_t property_id, const uint8_t* data, size_t len,
std::vector<uint8_t>* response) {
return bridge_.handleFunctionPropertyState(object_index, property_id, data, len, response);
});
if (!configureTpUart()) {
ets_device_.reset();
closeSockets();
return ESP_FAIL;
}
@@ -1075,6 +1603,9 @@ void GatewayKnxTpIpRouter::taskLoop() {
reinterpret_cast<sockaddr*>(&remote), &remote_len);
if (received <= 0) {
pollTpUart();
if (ets_device_ != nullptr) {
ets_device_->loop();
}
if (!stop_requested_) {
vTaskDelay(pdMS_TO_TICKS(10));
}
@@ -1082,12 +1613,16 @@ void GatewayKnxTpIpRouter::taskLoop() {
}
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
pollTpUart();
if (ets_device_ != nullptr) {
ets_device_->loop();
}
}
finishTask();
}
void GatewayKnxTpIpRouter::finishTask() {
closeSockets();
ets_device_.reset();
started_ = false;
task_handle_ = nullptr;
vTaskDelete(nullptr);
@@ -1209,8 +1744,8 @@ bool GatewayKnxTpIpRouter::initializeTpUart() {
saw_reset = true;
const std::array<uint8_t, 3> set_address{
kTpUartSetAddressRequest,
static_cast<uint8_t>((config_.individual_address >> 8) & 0xff),
static_cast<uint8_t>(config_.individual_address & 0xff),
static_cast<uint8_t>((effectiveIndividualAddress() >> 8) & 0xff),
static_cast<uint8_t>(effectiveIndividualAddress() & 0xff),
};
uart_write_bytes(uart_port, set_address.data(), set_address.size());
const uint8_t state_request = kTpUartStateRequest;
@@ -1296,6 +1831,10 @@ 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 consumed_by_openknx = handleOpenKnxTunnelFrame(cemi, cemi_len);
if (consumed_by_openknx) {
return;
}
const DaliBridgeResult result = handler_(cemi, cemi_len);
if (!result.ok && !result.error.empty()) {
ESP_LOGD(kTag, "KNX tunnel frame not routed to DALI: %s", result.error.c_str());
@@ -1400,8 +1939,8 @@ void GatewayKnxTpIpRouter::sendConnectResponse(uint8_t channel_id, uint8_t statu
body.insert(body.end(), data_endpoint.begin(), data_endpoint.end());
body.push_back(0x04);
body.push_back(kKnxConnectionTypeTunnel);
body.push_back(static_cast<uint8_t>((config_.individual_address >> 8) & 0xff));
body.push_back(static_cast<uint8_t>(config_.individual_address & 0xff));
body.push_back(static_cast<uint8_t>((effectiveTunnelAddress() >> 8) & 0xff));
body.push_back(static_cast<uint8_t>(effectiveTunnelAddress() & 0xff));
const auto packet = KnxNetIpPacket(kServiceConnectResponse, body);
SendAll(udp_sock_, packet.data(), packet.size(), remote);
}
@@ -1419,6 +1958,79 @@ void GatewayKnxTpIpRouter::sendRoutingIndication(const uint8_t* data, size_t len
SendAll(udp_sock_, packet.data(), packet.size(), remote);
}
bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t len) {
if (ets_device_ == nullptr) {
return false;
}
const bool consumed = ets_device_->handleTunnelFrame(
data, len, [this](const uint8_t* response, size_t response_len) {
sendTunnelIndication(response, response_len);
});
syncOpenKnxConfigFromDevice();
return consumed;
}
void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
if (ets_device_ == nullptr) {
return;
}
const auto snapshot = ets_device_->snapshot();
bool changed = false;
GatewayKnxConfig updated = config_;
if (snapshot.individual_address != 0 && snapshot.individual_address != 0xffff &&
snapshot.individual_address != updated.individual_address) {
updated.individual_address = snapshot.individual_address;
changed = true;
}
if (snapshot.configured || !snapshot.associations.empty()) {
std::vector<GatewayKnxEtsAssociation> associations;
associations.reserve(snapshot.associations.size());
for (const auto& association : snapshot.associations) {
associations.push_back(GatewayKnxEtsAssociation{association.group_address,
association.group_object_number});
}
if (associations.size() != updated.ets_associations.size() ||
!std::equal(associations.begin(), associations.end(), updated.ets_associations.begin(),
[](const GatewayKnxEtsAssociation& lhs,
const GatewayKnxEtsAssociation& rhs) {
return lhs.group_address == rhs.group_address &&
lhs.group_object_number == rhs.group_object_number;
})) {
updated.ets_associations = std::move(associations);
changed = true;
}
}
if (!changed) {
return;
}
config_ = updated;
bridge_.setConfig(config_);
}
uint16_t GatewayKnxTpIpRouter::effectiveIndividualAddress() const {
if (ets_device_ != nullptr) {
const uint16_t address = ets_device_->individualAddress();
if (address != 0 && address != 0xffff) {
return address;
}
}
return config_.individual_address;
}
uint16_t GatewayKnxTpIpRouter::effectiveTunnelAddress() const {
if (ets_device_ != nullptr) {
const uint16_t address = ets_device_->tunnelClientAddress();
if (address != 0 && address != 0xffff) {
return address;
}
}
uint16_t device = static_cast<uint16_t>((config_.individual_address & 0x00ff) + 1);
if (device == 0 || device > 0xff) {
device = 1;
}
return static_cast<uint16_t>((config_.individual_address & 0xff00) | device);
}
void GatewayKnxTpIpRouter::pollTpUart() {
if (tp_uart_port_ < 0) {
return;