feat: add KNX gateway snapshot and command transaction handling
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -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 =
|
||||
|
||||
Reference in New Issue
Block a user