feat: add KNX gateway snapshot and command transaction handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -169,6 +169,32 @@ struct GatewayKnxReg1ScanOptions {
|
||||
bool assign{false};
|
||||
};
|
||||
|
||||
struct GatewayKnxGatewayChannelSnapshot {
|
||||
uint8_t gateway_id{0};
|
||||
};
|
||||
|
||||
struct GatewayKnxGatewaySnapshot {
|
||||
std::vector<GatewayKnxGatewayChannelSnapshot> channels;
|
||||
};
|
||||
|
||||
enum class GatewayKnxGatewayTransactionStatus : uint8_t {
|
||||
kOk = 0,
|
||||
kInvalidFrame = 1,
|
||||
kQueueRejected = 2,
|
||||
kTimeout = 3,
|
||||
kNoResponse = 4,
|
||||
};
|
||||
|
||||
struct GatewayKnxGatewayTransactionResult {
|
||||
GatewayKnxGatewayTransactionStatus status{GatewayKnxGatewayTransactionStatus::kNoResponse};
|
||||
std::vector<uint8_t> frames;
|
||||
};
|
||||
|
||||
using GatewayKnxGatewaySnapshotProvider = std::function<GatewayKnxGatewaySnapshot()>;
|
||||
using GatewayKnxGatewayCommandTransactor =
|
||||
std::function<GatewayKnxGatewayTransactionResult(const std::vector<uint8_t>& frame,
|
||||
uint32_t timeout_ms)>;
|
||||
|
||||
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
||||
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
||||
std::optional<GatewayKnxOamRouterConfig> GatewayKnxOamRouterConfigFromValue(
|
||||
@@ -194,6 +220,8 @@ class GatewayKnxBridge {
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
void setRuntimeContext(const openknx::EtsDeviceRuntime* runtime);
|
||||
void setGatewayControllerCallbacks(GatewayKnxGatewaySnapshotProvider snapshot_provider,
|
||||
GatewayKnxGatewayCommandTransactor command_transactor);
|
||||
const GatewayKnxConfig& config() const;
|
||||
size_t etsBindingCount() const;
|
||||
|
||||
@@ -211,6 +239,13 @@ class GatewayKnxBridge {
|
||||
std::vector<uint8_t>* response);
|
||||
|
||||
private:
|
||||
struct GatewayTransferState {
|
||||
uint16_t total_length{0};
|
||||
uint8_t total_chunks{0};
|
||||
std::map<uint8_t, std::vector<uint8_t>> request_chunks;
|
||||
std::vector<uint8_t> response_bytes;
|
||||
};
|
||||
|
||||
DaliBridgeResult executeForDecodedWrite(uint16_t group_address,
|
||||
GatewayKnxDaliDataType data_type,
|
||||
GatewayKnxDaliTarget target,
|
||||
@@ -244,13 +279,29 @@ class GatewayKnxBridge {
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleReg1FoundEvgsState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleDalimasterDiscoveryState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleDalimasterTransferCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool handleDalimasterTransferState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response);
|
||||
bool executeDalimasterTransfer(uint16_t transaction_id, uint32_t timeout_ms,
|
||||
std::vector<uint8_t>* response);
|
||||
bool appendDalimasterTransferResponse(uint8_t transfer_status,
|
||||
uint16_t transaction_id,
|
||||
const std::vector<uint8_t>& response_bytes,
|
||||
uint8_t chunk_index,
|
||||
std::vector<uint8_t>* response) const;
|
||||
static void CommissioningScanTaskEntry(void* arg);
|
||||
void runCommissioningScanTask();
|
||||
|
||||
DaliBridgeEngine& engine_;
|
||||
GatewayKnxGatewaySnapshotProvider gateway_snapshot_provider_;
|
||||
GatewayKnxGatewayCommandTransactor gateway_command_transactor_;
|
||||
GatewayKnxConfig config_;
|
||||
const openknx::EtsDeviceRuntime* runtime_{nullptr};
|
||||
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
|
||||
std::map<uint16_t, GatewayTransferState> gateway_transfer_states_;
|
||||
SemaphoreHandle_t commissioning_lock_{nullptr};
|
||||
TaskHandle_t commissioning_scan_task_{nullptr};
|
||||
std::atomic_bool commissioning_scan_cancel_requested_{false};
|
||||
@@ -536,4 +587,4 @@ class GatewayKnxTpIpRouter {
|
||||
std::string last_error_;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace gateway
|
||||
|
||||
@@ -418,6 +418,13 @@ void GatewayKnxBridge::setRuntimeContext(const openknx::EtsDeviceRuntime* runtim
|
||||
runtime_ = runtime;
|
||||
}
|
||||
|
||||
void GatewayKnxBridge::setGatewayControllerCallbacks(
|
||||
GatewayKnxGatewaySnapshotProvider snapshot_provider,
|
||||
GatewayKnxGatewayCommandTransactor command_transactor) {
|
||||
gateway_snapshot_provider_ = std::move(snapshot_provider);
|
||||
gateway_command_transactor_ = std::move(command_transactor);
|
||||
}
|
||||
|
||||
const GatewayKnxConfig& GatewayKnxBridge::config() const { return config_; }
|
||||
|
||||
size_t GatewayKnxBridge::etsBindingCount() const {
|
||||
@@ -615,8 +622,14 @@ DaliBridgeResult GatewayKnxBridge::handleGroupObjectWrite(uint16_t group_object_
|
||||
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) {
|
||||
if (object_index != kReg1DaliFunctionObjectIndex || data == nullptr || len == 0 ||
|
||||
response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (property_id == kDalimasterTransferFunctionPropertyId) {
|
||||
return handleDalimasterTransferCommand(data, len, response);
|
||||
}
|
||||
if (property_id != kReg1DaliFunctionPropertyId) {
|
||||
return false;
|
||||
}
|
||||
switch (data[0]) {
|
||||
@@ -644,8 +657,16 @@ bool GatewayKnxBridge::handleFunctionPropertyCommand(uint8_t object_index, uint8
|
||||
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) {
|
||||
if (object_index != kReg1DaliFunctionObjectIndex || response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (property_id == kDalimasterDiscoveryFunctionPropertyId) {
|
||||
return handleDalimasterDiscoveryState(data, len, response);
|
||||
}
|
||||
if (property_id == kDalimasterTransferFunctionPropertyId) {
|
||||
return handleDalimasterTransferState(data, len, response);
|
||||
}
|
||||
if (property_id != kReg1DaliFunctionPropertyId || data == nullptr || len == 0) {
|
||||
return false;
|
||||
}
|
||||
switch (data[0]) {
|
||||
@@ -661,6 +682,238 @@ bool GatewayKnxBridge::handleFunctionPropertyState(uint8_t object_index, uint8_t
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleDalimasterDiscoveryState(const uint8_t*, size_t,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
response->clear();
|
||||
response->reserve(32);
|
||||
response->push_back(0x00);
|
||||
response->push_back('D');
|
||||
response->push_back('M');
|
||||
response->push_back('K');
|
||||
response->push_back('G');
|
||||
response->push_back(0x01);
|
||||
response->push_back(kDalimasterTransferChunkBytes);
|
||||
uint16_t feature_flags = 0x0100 | 0x0080;
|
||||
if (config_.tunnel_enabled) {
|
||||
feature_flags |= 0x0001;
|
||||
}
|
||||
if (config_.multicast_enabled) {
|
||||
feature_flags |= 0x0002;
|
||||
}
|
||||
if (config_.oam_router.secure_tunnel_enabled) {
|
||||
feature_flags |= 0x0004;
|
||||
}
|
||||
if (config_.oam_router.secure_routing_enabled) {
|
||||
feature_flags |= 0x0008;
|
||||
}
|
||||
response->push_back(static_cast<uint8_t>(feature_flags & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((feature_flags >> 8) & 0xff));
|
||||
response->push_back(kReg1DaliFunctionObjectIndex);
|
||||
response->push_back(kDalimasterDiscoveryFunctionPropertyId);
|
||||
response->push_back(kDalimasterTransferFunctionPropertyId);
|
||||
const uint16_t individual_address =
|
||||
runtime_ != nullptr ? runtime_->individualAddress() : config_.individual_address;
|
||||
const uint16_t tunnel_address =
|
||||
runtime_ != nullptr ? runtime_->tunnelClientAddress() : config_.ip_interface_individual_address;
|
||||
response->push_back(static_cast<uint8_t>((individual_address >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(individual_address & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((tunnel_address >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(tunnel_address & 0xff));
|
||||
|
||||
const auto snapshot = gateway_snapshot_provider_ ? gateway_snapshot_provider_()
|
||||
: GatewayKnxGatewaySnapshot{};
|
||||
const auto channel_count = std::min<size_t>(snapshot.channels.size(), 32);
|
||||
response->push_back(static_cast<uint8_t>(channel_count));
|
||||
for (size_t index = 0; index < channel_count; ++index) {
|
||||
response->push_back(snapshot.channels[index].gateway_id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleDalimasterTransferCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (data == nullptr || len == 0 || response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto read_le16 = [](const uint8_t* value) -> uint16_t {
|
||||
return static_cast<uint16_t>(value[0] | (static_cast<uint16_t>(value[1]) << 8));
|
||||
};
|
||||
const uint8_t op = data[0];
|
||||
if (op == kDalimasterTransferOpSingle) {
|
||||
if (len < 6) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
const uint16_t transaction_id = read_le16(data + 1);
|
||||
const uint32_t timeout_ms = read_le16(data + 3);
|
||||
gateway_transfer_states_[transaction_id] = GatewayTransferState{};
|
||||
gateway_transfer_states_[transaction_id].request_chunks[0] =
|
||||
std::vector<uint8_t>(data + 5, data + len);
|
||||
gateway_transfer_states_[transaction_id].total_length =
|
||||
static_cast<uint16_t>(len - 5);
|
||||
gateway_transfer_states_[transaction_id].total_chunks = 1;
|
||||
return executeDalimasterTransfer(transaction_id, timeout_ms, response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpBegin) {
|
||||
if (len < 6) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
const uint16_t transaction_id = read_le16(data + 1);
|
||||
GatewayTransferState state;
|
||||
state.total_length = read_le16(data + 3);
|
||||
state.total_chunks = data[5];
|
||||
gateway_transfer_states_[transaction_id] = std::move(state);
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id, {}, 0,
|
||||
response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpChunk) {
|
||||
if (len < 6) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
const uint16_t transaction_id = read_le16(data + 1);
|
||||
const uint8_t chunk_index = data[3];
|
||||
const uint8_t chunk_len = data[4];
|
||||
if (len < static_cast<size_t>(5 + chunk_len) ||
|
||||
chunk_len > kDalimasterTransferChunkBytes ||
|
||||
gateway_transfer_states_.find(transaction_id) == gateway_transfer_states_.end()) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, chunk_index, response);
|
||||
}
|
||||
gateway_transfer_states_[transaction_id].request_chunks[chunk_index] =
|
||||
std::vector<uint8_t>(data + 5, data + 5 + chunk_len);
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id, {},
|
||||
chunk_index, response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpExecute) {
|
||||
if (len < 5) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
return executeDalimasterTransfer(read_le16(data + 1), read_le16(data + 3), response);
|
||||
}
|
||||
if (op == kDalimasterTransferOpReadResponse) {
|
||||
return handleDalimasterTransferState(data, len, response);
|
||||
}
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame, 0, {}, 0,
|
||||
response);
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleDalimasterTransferState(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (data == nullptr || len < 4 || response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const uint16_t transaction_id =
|
||||
static_cast<uint16_t>(data[1] | (static_cast<uint16_t>(data[2]) << 8));
|
||||
const uint8_t chunk_index = data[3];
|
||||
const auto it = gateway_transfer_states_.find(transaction_id);
|
||||
if (it == gateway_transfer_states_.end()) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, chunk_index, response);
|
||||
}
|
||||
const auto response_bytes = it->second.response_bytes;
|
||||
const bool ok = appendDalimasterTransferResponse(kDalimasterTransferStatusOk, transaction_id,
|
||||
response_bytes, chunk_index, response);
|
||||
const size_t total_chunks =
|
||||
std::max<size_t>(1, (response_bytes.size() + kDalimasterTransferChunkBytes - 1) /
|
||||
kDalimasterTransferChunkBytes);
|
||||
if (chunk_index + 1 >= total_chunks) {
|
||||
gateway_transfer_states_.erase(it);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::executeDalimasterTransfer(uint16_t transaction_id, uint32_t timeout_ms,
|
||||
std::vector<uint8_t>* response) {
|
||||
const auto it = gateway_transfer_states_.find(transaction_id);
|
||||
if (it == gateway_transfer_states_.end() || response == nullptr) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
std::vector<uint8_t> frame;
|
||||
frame.reserve(it->second.total_length);
|
||||
for (uint8_t index = 0; index < it->second.total_chunks; ++index) {
|
||||
const auto chunk = it->second.request_chunks.find(index);
|
||||
if (chunk == it->second.request_chunks.end()) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
frame.insert(frame.end(), chunk->second.begin(), chunk->second.end());
|
||||
}
|
||||
if (it->second.total_length != frame.size() || frame.size() < 4 ||
|
||||
frame[0] != 0x28 || frame[1] != 0x01) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusInvalidFrame,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
if (!gateway_command_transactor_) {
|
||||
return appendDalimasterTransferResponse(kDalimasterTransferStatusNoController,
|
||||
transaction_id, {}, 0, response);
|
||||
}
|
||||
const auto transaction =
|
||||
gateway_command_transactor_(frame, timeout_ms == 0 ? 700 : timeout_ms);
|
||||
uint8_t status = kDalimasterTransferStatusOk;
|
||||
switch (transaction.status) {
|
||||
case GatewayKnxGatewayTransactionStatus::kOk:
|
||||
status = kDalimasterTransferStatusOk;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kInvalidFrame:
|
||||
status = kDalimasterTransferStatusInvalidFrame;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kQueueRejected:
|
||||
status = kDalimasterTransferStatusQueueRejected;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kTimeout:
|
||||
status = kDalimasterTransferStatusTimeout;
|
||||
break;
|
||||
case GatewayKnxGatewayTransactionStatus::kNoResponse:
|
||||
status = kDalimasterTransferStatusNoResponse;
|
||||
break;
|
||||
}
|
||||
it->second.response_bytes = transaction.frames;
|
||||
return appendDalimasterTransferResponse(status, transaction_id, it->second.response_bytes, 0,
|
||||
response);
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::appendDalimasterTransferResponse(
|
||||
uint8_t transfer_status, uint16_t transaction_id, const std::vector<uint8_t>& response_bytes,
|
||||
uint8_t chunk_index, std::vector<uint8_t>* response) const {
|
||||
if (response == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const size_t total_chunks =
|
||||
std::max<size_t>(1, (response_bytes.size() + kDalimasterTransferChunkBytes - 1) /
|
||||
kDalimasterTransferChunkBytes);
|
||||
if (chunk_index >= total_chunks) {
|
||||
response->assign({0x00, kDalimasterTransferStatusInvalidFrame,
|
||||
static_cast<uint8_t>(transaction_id & 0xff),
|
||||
static_cast<uint8_t>((transaction_id >> 8) & 0xff), 0x00, 0x00,
|
||||
static_cast<uint8_t>(total_chunks & 0xff), chunk_index, 0x00});
|
||||
return true;
|
||||
}
|
||||
const size_t start = static_cast<size_t>(chunk_index) * kDalimasterTransferChunkBytes;
|
||||
const size_t chunk_len =
|
||||
std::min<size_t>(kDalimasterTransferChunkBytes, response_bytes.size() - start);
|
||||
response->clear();
|
||||
response->reserve(9 + chunk_len);
|
||||
response->push_back(0x00);
|
||||
response->push_back(transfer_status);
|
||||
response->push_back(static_cast<uint8_t>(transaction_id & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((transaction_id >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(response_bytes.size() & 0xff));
|
||||
response->push_back(static_cast<uint8_t>((response_bytes.size() >> 8) & 0xff));
|
||||
response->push_back(static_cast<uint8_t>(total_chunks & 0xff));
|
||||
response->push_back(chunk_index);
|
||||
response->push_back(static_cast<uint8_t>(chunk_len & 0xff));
|
||||
response->insert(response->end(), response_bytes.begin() + start,
|
||||
response_bytes.begin() + start + chunk_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayKnxBridge::handleReg1TypeCommand(const uint8_t* data, size_t len,
|
||||
std::vector<uint8_t>* response) {
|
||||
if (len < 2 || response == nullptr) {
|
||||
|
||||
@@ -138,6 +138,20 @@ constexpr uint8_t kDaliCmdOnStepUp = 0x08;
|
||||
constexpr uint8_t kDaliCmdStopFade = 0xff;
|
||||
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
|
||||
constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
|
||||
constexpr uint8_t kDalimasterDiscoveryFunctionPropertyId = 210;
|
||||
constexpr uint8_t kDalimasterTransferFunctionPropertyId = 211;
|
||||
constexpr uint8_t kDalimasterTransferChunkBytes = 192;
|
||||
constexpr uint8_t kDalimasterTransferOpSingle = 0x01;
|
||||
constexpr uint8_t kDalimasterTransferOpBegin = 0x02;
|
||||
constexpr uint8_t kDalimasterTransferOpChunk = 0x03;
|
||||
constexpr uint8_t kDalimasterTransferOpExecute = 0x04;
|
||||
constexpr uint8_t kDalimasterTransferOpReadResponse = 0x05;
|
||||
constexpr uint8_t kDalimasterTransferStatusOk = 0x00;
|
||||
constexpr uint8_t kDalimasterTransferStatusInvalidFrame = 0x01;
|
||||
constexpr uint8_t kDalimasterTransferStatusNoController = 0x02;
|
||||
constexpr uint8_t kDalimasterTransferStatusQueueRejected = 0x03;
|
||||
constexpr uint8_t kDalimasterTransferStatusTimeout = 0x04;
|
||||
constexpr uint8_t kDalimasterTransferStatusNoResponse = 0x05;
|
||||
constexpr uint8_t kReg1FunctionType = 2;
|
||||
constexpr uint8_t kReg1FunctionScan = 3;
|
||||
constexpr uint8_t kReg1FunctionAssign = 4;
|
||||
|
||||
Reference in New Issue
Block a user