feat: add KNX gateway snapshot and command transaction handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -43,6 +43,8 @@ struct GatewayBridgeServiceConfig {
|
||||
UBaseType_t knx_task_priority{5};
|
||||
std::optional<GatewayKnxConfig> default_knx_config;
|
||||
std::function<std::string()> gateway_device_name_provider;
|
||||
GatewayKnxGatewaySnapshotProvider knx_gateway_snapshot_provider;
|
||||
GatewayKnxGatewayCommandTransactor knx_gateway_command_transactor;
|
||||
};
|
||||
|
||||
struct GatewayBridgeHttpResponse {
|
||||
|
||||
@@ -1715,6 +1715,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
|
||||
knx = std::make_unique<GatewayKnxBridge>(*engine);
|
||||
knx->setGatewayControllerCallbacks(service_config.knx_gateway_snapshot_provider,
|
||||
service_config.knx_gateway_command_transactor);
|
||||
knx_router = std::make_unique<GatewayKnxTpIpRouter>(*knx, openKnxNamespace());
|
||||
knx_router->setGroupWriteHandler(
|
||||
[this](uint16_t group_address, const uint8_t* data, size_t len) {
|
||||
|
||||
@@ -65,6 +65,19 @@ struct GatewayControllerSnapshot {
|
||||
std::vector<GatewayChannelSnapshot> channels;
|
||||
};
|
||||
|
||||
enum class GatewayCommandTransactionStatus : uint8_t {
|
||||
kOk = 0,
|
||||
kInvalidFrame = 1,
|
||||
kQueueRejected = 2,
|
||||
kTimeout = 3,
|
||||
kNoResponse = 4,
|
||||
};
|
||||
|
||||
struct GatewayCommandTransactionResult {
|
||||
GatewayCommandTransactionStatus status{GatewayCommandTransactionStatus::kNoResponse};
|
||||
std::vector<uint8_t> frames;
|
||||
};
|
||||
|
||||
class GatewayController {
|
||||
public:
|
||||
using NotificationSink = std::function<void(const std::vector<uint8_t>& frame)>;
|
||||
@@ -79,6 +92,11 @@ class GatewayController {
|
||||
|
||||
esp_err_t start();
|
||||
bool enqueueCommandFrame(const std::vector<uint8_t>& frame);
|
||||
GatewayCommandTransactionResult transactCommandFrame(
|
||||
const std::vector<uint8_t>& frame,
|
||||
uint32_t timeout_ms = 700,
|
||||
uint32_t idle_ms = 30,
|
||||
size_t max_response_bytes = 2048);
|
||||
void addNotificationSink(NotificationSink sink);
|
||||
void addBleStateSink(BleStateSink sink);
|
||||
void addWifiStateSink(WifiStateSink sink);
|
||||
@@ -122,6 +140,15 @@ class GatewayController {
|
||||
uint8_t short_address{0};
|
||||
};
|
||||
|
||||
struct TransactionWaiter {
|
||||
uint8_t gateway_id{0};
|
||||
uint8_t opcode{0};
|
||||
SemaphoreHandle_t signal{nullptr};
|
||||
std::vector<uint8_t> frames;
|
||||
size_t max_response_bytes{0};
|
||||
bool overflow{false};
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void dispatchCommand(const std::vector<uint8_t>& command);
|
||||
@@ -144,6 +171,9 @@ class GatewayController {
|
||||
void refreshRuntimeGatewayNames();
|
||||
void publishPayload(uint8_t gateway_id, const std::vector<uint8_t>& payload);
|
||||
void publishFrame(const std::vector<uint8_t>& frame);
|
||||
bool transactionFrameMatches(const TransactionWaiter& waiter,
|
||||
const std::vector<uint8_t>& frame) const;
|
||||
void captureTransactionFrame(const std::vector<uint8_t>& frame);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
bool handleApplicationControllerFrame(const DaliRawFrame& frame);
|
||||
std::optional<uint8_t> applicationControllerResponse(uint8_t gateway_id, uint8_t first,
|
||||
@@ -203,11 +233,13 @@ class GatewayController {
|
||||
GatewayControllerConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
SemaphoreHandle_t maintenance_lock_{nullptr};
|
||||
SemaphoreHandle_t transaction_lock_{nullptr};
|
||||
std::vector<NotificationSink> notification_sinks_;
|
||||
std::vector<BleStateSink> ble_state_sinks_;
|
||||
std::vector<WifiStateSink> wifi_state_sinks_;
|
||||
std::vector<GatewayNameSink> gateway_name_sinks_;
|
||||
std::map<uint16_t, BridgeTransportRequestState> bridge_transport_requests_;
|
||||
std::vector<TransactionWaiter*> transaction_waiters_;
|
||||
std::map<uint8_t, ReconciliationJob> reconciliation_jobs_;
|
||||
std::map<uint8_t, CacheRefreshJob> cache_refresh_jobs_;
|
||||
std::atomic<int> maintenance_activity_gateway_{-1};
|
||||
|
||||
@@ -282,9 +282,14 @@ GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService&
|
||||
dali_domain_(dali_domain),
|
||||
cache_(cache),
|
||||
config_(config),
|
||||
maintenance_lock_(xSemaphoreCreateMutex()) {}
|
||||
maintenance_lock_(xSemaphoreCreateMutex()),
|
||||
transaction_lock_(xSemaphoreCreateMutex()) {}
|
||||
|
||||
GatewayController::~GatewayController() {
|
||||
if (transaction_lock_ != nullptr) {
|
||||
vSemaphoreDelete(transaction_lock_);
|
||||
transaction_lock_ = nullptr;
|
||||
}
|
||||
if (maintenance_lock_ != nullptr) {
|
||||
vSemaphoreDelete(maintenance_lock_);
|
||||
maintenance_lock_ = nullptr;
|
||||
@@ -341,6 +346,74 @@ bool GatewayController::enqueueCommandFrame(const std::vector<uint8_t>& frame) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GatewayCommandTransactionResult GatewayController::transactCommandFrame(
|
||||
const std::vector<uint8_t>& frame,
|
||||
uint32_t timeout_ms,
|
||||
uint32_t idle_ms,
|
||||
size_t max_response_bytes) {
|
||||
GatewayCommandTransactionResult result;
|
||||
if (!GatewayRuntime::isGatewayCommandFrame(frame) || !GatewayRuntime::hasValidChecksum(frame) ||
|
||||
frame.size() < 5) {
|
||||
result.status = GatewayCommandTransactionStatus::kInvalidFrame;
|
||||
return result;
|
||||
}
|
||||
|
||||
TransactionWaiter waiter;
|
||||
waiter.gateway_id = frame[2];
|
||||
waiter.opcode = frame[3];
|
||||
waiter.max_response_bytes = max_response_bytes;
|
||||
waiter.signal = xSemaphoreCreateBinary();
|
||||
if (waiter.signal == nullptr) {
|
||||
result.status = GatewayCommandTransactionStatus::kQueueRejected;
|
||||
return result;
|
||||
}
|
||||
|
||||
{
|
||||
LockGuard guard(transaction_lock_);
|
||||
transaction_waiters_.push_back(&waiter);
|
||||
}
|
||||
|
||||
const bool enqueued = enqueueCommandFrame(frame);
|
||||
if (!enqueued) {
|
||||
{
|
||||
LockGuard guard(transaction_lock_);
|
||||
transaction_waiters_.erase(std::remove(transaction_waiters_.begin(),
|
||||
transaction_waiters_.end(), &waiter),
|
||||
transaction_waiters_.end());
|
||||
}
|
||||
vSemaphoreDelete(waiter.signal);
|
||||
result.status = GatewayCommandTransactionStatus::kQueueRejected;
|
||||
return result;
|
||||
}
|
||||
|
||||
const TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
|
||||
const BaseType_t first = xSemaphoreTake(waiter.signal, timeout_ticks);
|
||||
if (first == pdTRUE && idle_ms > 0) {
|
||||
while (xSemaphoreTake(waiter.signal, pdMS_TO_TICKS(idle_ms)) == pdTRUE) {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
LockGuard guard(transaction_lock_);
|
||||
transaction_waiters_.erase(std::remove(transaction_waiters_.begin(),
|
||||
transaction_waiters_.end(), &waiter),
|
||||
transaction_waiters_.end());
|
||||
result.frames = waiter.frames;
|
||||
}
|
||||
vSemaphoreDelete(waiter.signal);
|
||||
|
||||
if (waiter.overflow) {
|
||||
result.status = GatewayCommandTransactionStatus::kQueueRejected;
|
||||
} else if (!result.frames.empty()) {
|
||||
result.status = GatewayCommandTransactionStatus::kOk;
|
||||
} else if (first == pdTRUE) {
|
||||
result.status = GatewayCommandTransactionStatus::kNoResponse;
|
||||
} else {
|
||||
result.status = GatewayCommandTransactionStatus::kTimeout;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void GatewayController::addNotificationSink(NotificationSink sink) {
|
||||
if (sink) {
|
||||
notification_sinks_.push_back(std::move(sink));
|
||||
@@ -1109,11 +1182,65 @@ void GatewayController::publishBridgeTransportResponse(uint8_t gateway_id, uint8
|
||||
}
|
||||
|
||||
void GatewayController::publishFrame(const std::vector<uint8_t>& frame) {
|
||||
captureTransactionFrame(frame);
|
||||
for (const auto& sink : notification_sinks_) {
|
||||
sink(frame);
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayController::transactionFrameMatches(const TransactionWaiter& waiter,
|
||||
const std::vector<uint8_t>& frame) const {
|
||||
if (frame.size() < 3 || frame[0] != 0x22) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t response_opcode = frame[1];
|
||||
const uint8_t response_gateway = frame.size() > 2 ? frame[2] : 0;
|
||||
const bool gateway_matches = response_gateway == waiter.gateway_id ||
|
||||
waiter.gateway_id == 0 ||
|
||||
response_gateway == 0xff;
|
||||
switch (waiter.opcode) {
|
||||
case 0x06:
|
||||
return response_opcode == 0x03 && gateway_matches;
|
||||
case 0x09:
|
||||
return (response_opcode == 0x09 && waiter.gateway_id == 0) ||
|
||||
((response_opcode == 0x03 || response_opcode == 0x04) && gateway_matches);
|
||||
case 0x14:
|
||||
return (response_opcode == 0x03 || response_opcode == 0x04) && gateway_matches;
|
||||
case kDali103QueryOpcode:
|
||||
return (response_opcode == kDali103QueryResponseOpcode ||
|
||||
response_opcode == kDali103NoResponseOpcode) &&
|
||||
gateway_matches;
|
||||
case 0x05:
|
||||
case 0x0A:
|
||||
case 0x30:
|
||||
case kGatewayCacheOpcode:
|
||||
case 0xA0:
|
||||
case 0xA2:
|
||||
return response_opcode == waiter.opcode && gateway_matches;
|
||||
case kBridgeTransportRequestOpcode:
|
||||
return response_opcode == kBridgeTransportResponseOpcode && gateway_matches;
|
||||
default:
|
||||
return response_opcode == waiter.opcode && gateway_matches;
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::captureTransactionFrame(const std::vector<uint8_t>& frame) {
|
||||
LockGuard guard(transaction_lock_);
|
||||
for (auto* waiter : transaction_waiters_) {
|
||||
if (waiter == nullptr || waiter->signal == nullptr ||
|
||||
!transactionFrameMatches(*waiter, frame)) {
|
||||
continue;
|
||||
}
|
||||
if (waiter->frames.size() + frame.size() > waiter->max_response_bytes) {
|
||||
waiter->overflow = true;
|
||||
xSemaphoreGive(waiter->signal);
|
||||
continue;
|
||||
}
|
||||
waiter->frames.insert(waiter->frames.end(), frame.begin(), frame.end());
|
||||
xSemaphoreGive(waiter->signal);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<uint8_t> GatewayController::applicationControllerResponse(
|
||||
uint8_t gateway_id, uint8_t first, uint8_t instance, uint8_t opcode) const {
|
||||
const uint8_t gateway_short =
|
||||
|
||||
@@ -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