feat(gateway): add extended function property handlers and diagnostics support for OpenKNX
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "tpuart_uart_interface.h"
|
||||
|
||||
#include "knx/cemi_frame.h"
|
||||
#include "knx/interface_object.h"
|
||||
#include "knx/knx_ip_connect_request.h"
|
||||
#include "knx/knx_ip_connect_response.h"
|
||||
#include "knx/knx_ip_config_request.h"
|
||||
@@ -140,12 +141,22 @@ constexpr uint8_t kReg1DeviceTypeDt8 = 8;
|
||||
constexpr uint8_t kReg1ColorTypeTw = 1;
|
||||
constexpr uint8_t kDaliDeviceTypeNone = 0xfe;
|
||||
constexpr uint8_t kDaliDeviceTypeMultiple = 0xff;
|
||||
constexpr uint16_t kGroupObjectTableObjectType = OT_GRP_OBJ_TABLE;
|
||||
constexpr uint8_t kPidGoDiagnostics = 0x42;
|
||||
constexpr uint8_t kGoDiagnosticsReservedByte = 0x00;
|
||||
constexpr uint8_t kGoDiagnosticsGroupWriteService = 0x01;
|
||||
|
||||
struct DecodedGroupWrite {
|
||||
uint16_t group_address{0};
|
||||
std::vector<uint8_t> data;
|
||||
};
|
||||
|
||||
struct DecodedGoDiagnosticsGroupWrite {
|
||||
uint16_t group_address{0};
|
||||
const uint8_t* payload{nullptr};
|
||||
size_t payload_len{0};
|
||||
};
|
||||
|
||||
struct KnxNetifInfo {
|
||||
const char* key{nullptr};
|
||||
esp_netif_t* netif{nullptr};
|
||||
@@ -550,6 +561,26 @@ std::optional<DecodedGroupWrite> DecodeOpenKnxGroupWrite(const uint8_t* data, si
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<DecodedGoDiagnosticsGroupWrite> DecodeGoDiagnosticsGroupWrite(
|
||||
const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len < 5) {
|
||||
return std::nullopt;
|
||||
}
|
||||
if (data[0] != kGoDiagnosticsReservedByte || data[1] != kGoDiagnosticsGroupWriteService) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const size_t encoded_length = data[2];
|
||||
if (encoded_length < 2 || len != encoded_length + 3) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
DecodedGoDiagnosticsGroupWrite out;
|
||||
out.group_address = ReadBe16(data + 3);
|
||||
out.payload = data + 5;
|
||||
out.payload_len = encoded_length - 2;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool IsOpenKnxGroupValueWrite(const uint8_t* data, size_t len) {
|
||||
return DecodeOpenKnxGroupWrite(data, len).has_value();
|
||||
}
|
||||
@@ -705,6 +736,27 @@ std::optional<int> MetadataInt(const DaliBridgeResult& result, const std::string
|
||||
return getObjectInt(result.metadata, key);
|
||||
}
|
||||
|
||||
uint8_t GoDiagnosticsReturnCode(const DaliBridgeResult& result) {
|
||||
if (result.ok) {
|
||||
return ReturnCodes::Success;
|
||||
}
|
||||
if (getObjectBool(result.metadata, "ignored").value_or(false)) {
|
||||
return ReturnCodes::TemporarilyNotAvailable;
|
||||
}
|
||||
|
||||
const std::string& error = result.error;
|
||||
if (error.find("unmapped") != std::string::npos ||
|
||||
error.find("does not match gateway config") != std::string::npos) {
|
||||
return ReturnCodes::AddressVoid;
|
||||
}
|
||||
if (error.find("disabled") != std::string::npos ||
|
||||
error.find("commissioning-only") != std::string::npos ||
|
||||
error.find("not configured") != std::string::npos) {
|
||||
return ReturnCodes::TemporarilyNotAvailable;
|
||||
}
|
||||
return ReturnCodes::GenericError;
|
||||
}
|
||||
|
||||
std::string HexBytes(const uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len == 0) {
|
||||
return {};
|
||||
@@ -2498,6 +2550,17 @@ esp_err_t GatewayKnxTpIpRouter::initializeRuntime() {
|
||||
return bridge_.handleFunctionPropertyState(object_index, property_id, data, len,
|
||||
response);
|
||||
});
|
||||
ets_device_->setFunctionPropertyExtHandlers(
|
||||
[this](uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
||||
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
||||
return handleFunctionPropertyExtCommand(object_type, object_instance, property_id,
|
||||
data, len, response);
|
||||
},
|
||||
[this](uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
||||
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
||||
return handleFunctionPropertyExtState(object_type, object_instance, property_id,
|
||||
data, len, response);
|
||||
});
|
||||
ets_device_->setGroupWriteHandler(
|
||||
[this](uint16_t group_address, const uint8_t* data, size_t len) {
|
||||
if (!shouldRouteDaliApplicationFrames()) {
|
||||
@@ -4266,6 +4329,93 @@ bool GatewayKnxTpIpRouter::routeOpenKnxGroupWrite(const uint8_t* data, size_t le
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::handleFunctionPropertyExtCommand(
|
||||
uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
||||
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
||||
if (response == nullptr || object_type != kGroupObjectTableObjectType ||
|
||||
property_id != kPidGoDiagnostics) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto decoded = DecodeGoDiagnosticsGroupWrite(data, len);
|
||||
if (!decoded.has_value()) {
|
||||
const std::string payload = HexBytes(data, len);
|
||||
ESP_LOGW(kTag,
|
||||
"OpenKNX GO diagnostics write malformed objType=0x%04X objInst=%u property=0x%02X len=%u payload=%s",
|
||||
static_cast<unsigned>(object_type), static_cast<unsigned>(object_instance),
|
||||
static_cast<unsigned>(property_id), static_cast<unsigned>(len), payload.c_str());
|
||||
*response = {ReturnCodes::DataVoid};
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string group_address_text =
|
||||
GatewayKnxGroupAddressString(decoded->group_address);
|
||||
const std::string payload = HexBytes(decoded->payload, decoded->payload_len);
|
||||
ESP_LOGI(kTag,
|
||||
"OpenKNX GO diagnostics group write ga=0x%04X (%s) len=%u payload=%s",
|
||||
static_cast<unsigned>(decoded->group_address), group_address_text.c_str(),
|
||||
static_cast<unsigned>(decoded->payload_len), payload.c_str());
|
||||
|
||||
if (!shouldRouteDaliApplicationFrames()) {
|
||||
ESP_LOGW(kTag,
|
||||
"OpenKNX GO diagnostics group write ga=0x%04X (%s) blocked by commissioning-only routing state",
|
||||
static_cast<unsigned>(decoded->group_address), group_address_text.c_str());
|
||||
*response = {ReturnCodes::TemporarilyNotAvailable};
|
||||
return true;
|
||||
}
|
||||
|
||||
const DaliBridgeResult result =
|
||||
group_write_handler_ ? group_write_handler_(decoded->group_address, decoded->payload,
|
||||
decoded->payload_len)
|
||||
: bridge_.handleGroupWrite(decoded->group_address,
|
||||
decoded->payload,
|
||||
decoded->payload_len);
|
||||
const uint8_t return_code = GoDiagnosticsReturnCode(result);
|
||||
if (return_code == ReturnCodes::AddressVoid) {
|
||||
ESP_LOGW(kTag,
|
||||
"OpenKNX GO diagnostics group write ga=0x%04X (%s) returning AddressVoid: %s",
|
||||
static_cast<unsigned>(decoded->group_address), group_address_text.c_str(),
|
||||
result.error.empty() ? "unmapped KNX group address"
|
||||
: result.error.c_str());
|
||||
} else if (!result.ok) {
|
||||
ESP_LOGW(kTag,
|
||||
"OpenKNX GO diagnostics group write ga=0x%04X (%s) failed rc=0x%02X: %s",
|
||||
static_cast<unsigned>(decoded->group_address), group_address_text.c_str(),
|
||||
static_cast<unsigned>(return_code),
|
||||
result.error.empty() ? "command routing failed" : result.error.c_str());
|
||||
}
|
||||
response->assign(1, return_code);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::handleFunctionPropertyExtState(
|
||||
uint16_t object_type, uint8_t object_instance, uint8_t property_id,
|
||||
const uint8_t* data, size_t len, std::vector<uint8_t>* response) {
|
||||
if (response == nullptr || object_type != kGroupObjectTableObjectType ||
|
||||
property_id != kPidGoDiagnostics) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto decoded = DecodeGoDiagnosticsGroupWrite(data, len);
|
||||
if (!decoded.has_value()) {
|
||||
const std::string payload = HexBytes(data, len);
|
||||
ESP_LOGW(kTag,
|
||||
"OpenKNX GO diagnostics state request malformed objType=0x%04X objInst=%u property=0x%02X len=%u payload=%s",
|
||||
static_cast<unsigned>(object_type), static_cast<unsigned>(object_instance),
|
||||
static_cast<unsigned>(property_id), static_cast<unsigned>(len), payload.c_str());
|
||||
*response = {ReturnCodes::DataVoid};
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string group_address_text =
|
||||
GatewayKnxGroupAddressString(decoded->group_address);
|
||||
ESP_LOGW(kTag,
|
||||
"OpenKNX GO diagnostics state request unsupported ga=0x%04X (%s)",
|
||||
static_cast<unsigned>(decoded->group_address), group_address_text.c_str());
|
||||
*response = {ReturnCodes::InvalidCommand};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayKnxTpIpRouter::emitOpenKnxGroupValue(uint16_t group_object_number,
|
||||
const uint8_t* data, size_t len) {
|
||||
SemaphoreGuard guard(openknx_lock_);
|
||||
|
||||
Reference in New Issue
Block a user