feat(gateway): implement reconciliation mechanism and command prioritization
- Introduced a reconciliation job structure to manage the reconciliation process for gateway channels. - Added methods to schedule and run reconciliation steps, including group, scene, and settings reconciliation. - Implemented a locking mechanism to ensure thread safety during reconciliation operations. - Enhanced command handling in GatewayRuntime to classify commands by priority (control, normal, maintenance). - Updated command enqueueing and processing to respect command priorities, ensuring maintenance commands are handled appropriately. - Added configuration options for enabling/disabling cache functionality in GatewayRuntime. - Improved logging to include cache status during runtime initialization. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -35,6 +35,7 @@ struct GatewayRuntimeConfig {
|
||||
std::string_view version;
|
||||
std::string serial_id;
|
||||
bool default_ble_enabled{true};
|
||||
bool default_cache_enabled{true};
|
||||
size_t command_queue_capacity{16};
|
||||
};
|
||||
|
||||
@@ -59,6 +60,9 @@ class GatewaySettingsStore {
|
||||
bool getBleEnabled(bool default_value = false) const;
|
||||
bool setBleEnabled(bool enabled);
|
||||
|
||||
bool getCacheEnabled(bool default_value = true) const;
|
||||
bool setCacheEnabled(bool enabled);
|
||||
|
||||
std::optional<std::string> getWifiSsid() const;
|
||||
std::optional<std::string> getWifiPassword() const;
|
||||
bool setWifiCredentials(std::string_view ssid, std::string_view password);
|
||||
@@ -83,6 +87,12 @@ class GatewayRuntime {
|
||||
kQueueFull,
|
||||
};
|
||||
|
||||
enum class CommandPriority : uint8_t {
|
||||
kControl = 0,
|
||||
kNormal = 1,
|
||||
kMaintenance = 2,
|
||||
};
|
||||
|
||||
GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
|
||||
DaliDomainService* dali_domain);
|
||||
~GatewayRuntime();
|
||||
@@ -93,11 +103,16 @@ class GatewayRuntime {
|
||||
static bool hasValidChecksum(const std::vector<uint8_t>& frame);
|
||||
static bool isGatewayCommandFrame(const std::vector<uint8_t>& frame);
|
||||
static std::vector<uint8_t> buildNotificationFrame(const std::vector<uint8_t>& payload);
|
||||
static CommandPriority classifyCommandPriority(const std::vector<uint8_t>& command);
|
||||
|
||||
bool enqueueCommand(std::vector<uint8_t> command);
|
||||
bool enqueueCommand(std::vector<uint8_t> command,
|
||||
CommandPriority priority = CommandPriority::kNormal);
|
||||
std::optional<std::vector<uint8_t>> popNextCommand();
|
||||
void completeCurrentCommand();
|
||||
bool hasPendingQueryCommand(const std::vector<uint8_t>& command) const;
|
||||
bool hasPendingControlCommand(uint8_t gateway_id) const;
|
||||
bool shouldYieldMaintenance(uint8_t gateway_id) const;
|
||||
bool hasActiveCommand(uint8_t gateway_id) const;
|
||||
bool hasActiveQueryCommand(uint8_t gateway_id) const;
|
||||
CommandDropReason lastEnqueueDropReason() const;
|
||||
|
||||
@@ -109,6 +124,8 @@ class GatewayRuntime {
|
||||
GatewayDeviceInfo deviceInfo() const;
|
||||
bool bleEnabled() const;
|
||||
bool setBleEnabled(bool enabled);
|
||||
bool cacheEnabled() const;
|
||||
bool setCacheEnabled(bool enabled);
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
bool setGatewayName(uint8_t gateway_id, std::string_view name);
|
||||
std::string gatewaySerialHex(uint8_t gateway_id) const;
|
||||
@@ -118,6 +135,9 @@ class GatewayRuntime {
|
||||
|
||||
private:
|
||||
bool isQueryCommand(const std::vector<uint8_t>& command) const;
|
||||
size_t pendingCommandCountLocked() const;
|
||||
std::deque<std::vector<uint8_t>>& queueForPriorityLocked(CommandPriority priority);
|
||||
const std::deque<std::vector<uint8_t>>& queueForPriorityLocked(CommandPriority priority) const;
|
||||
std::optional<std::string> queryCommandKey(const std::vector<uint8_t>& command) const;
|
||||
std::string defaultGatewayName(uint8_t gateway_id) const;
|
||||
std::vector<uint8_t> serialBytes() const;
|
||||
@@ -128,10 +148,14 @@ class GatewayRuntime {
|
||||
DaliDomainService* dali_domain_;
|
||||
GatewaySettingsStore settings_;
|
||||
std::optional<std::vector<uint8_t>> current_command_;
|
||||
std::deque<std::vector<uint8_t>> pending_commands_;
|
||||
CommandPriority current_command_priority_{CommandPriority::kNormal};
|
||||
std::deque<std::vector<uint8_t>> control_commands_;
|
||||
std::deque<std::vector<uint8_t>> normal_commands_;
|
||||
std::deque<std::vector<uint8_t>> maintenance_commands_;
|
||||
mutable std::map<uint8_t, std::string> gateway_names_;
|
||||
size_t gateway_count_{0};
|
||||
bool ble_enabled_{false};
|
||||
bool cache_enabled_{true};
|
||||
CommandDropReason last_enqueue_drop_reason_{CommandDropReason::kNone};
|
||||
std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> command_address_resolver_;
|
||||
std::optional<WirelessInfo> wireless_info_;
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace {
|
||||
constexpr const char* kTag = "gateway_runtime";
|
||||
constexpr const char* kNamespace = "gateway_rt";
|
||||
constexpr const char* kBleEnabledKey = "ble_enabled";
|
||||
constexpr const char* kCacheEnabledKey = "cache_enabled";
|
||||
constexpr const char* kWifiSsidKey = "wifi_ssid";
|
||||
constexpr const char* kWifiPasswordKey = "wifi_passwd";
|
||||
constexpr size_t kMaxGatewayNameBytes = 32;
|
||||
@@ -109,6 +110,28 @@ bool GatewaySettingsStore::setBleEnabled(bool enabled) {
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::getCacheEnabled(bool default_value) const {
|
||||
if (handle_ == 0) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
uint8_t enabled = default_value ? 1 : 0;
|
||||
if (nvs_get_u8(handle_, kCacheEnabledKey, &enabled) != ESP_OK) {
|
||||
return default_value;
|
||||
}
|
||||
|
||||
return enabled != 0;
|
||||
}
|
||||
|
||||
bool GatewaySettingsStore::setCacheEnabled(bool enabled) {
|
||||
if (handle_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return nvs_set_u8(handle_, kCacheEnabledKey, enabled ? 1 : 0) == ESP_OK &&
|
||||
nvs_commit(handle_) == ESP_OK;
|
||||
}
|
||||
|
||||
std::optional<std::string> GatewaySettingsStore::getWifiSsid() const {
|
||||
return readString(kWifiSsidKey);
|
||||
}
|
||||
@@ -222,6 +245,7 @@ esp_err_t GatewayRuntime::start() {
|
||||
}
|
||||
|
||||
ble_enabled_ = settings_.getBleEnabled(config_.default_ble_enabled);
|
||||
cache_enabled_ = settings_.getCacheEnabled(config_.default_cache_enabled);
|
||||
|
||||
if (!wireless_info_.has_value()) {
|
||||
WirelessInfo info;
|
||||
@@ -239,10 +263,10 @@ esp_err_t GatewayRuntime::start() {
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag,
|
||||
"runtime project=%.*s version=%.*s serial=%s ble=%d dali_bound=%d",
|
||||
"runtime project=%.*s version=%.*s serial=%s ble=%d cache=%d dali_bound=%d",
|
||||
static_cast<int>(config_.project_name.size()), config_.project_name.data(),
|
||||
static_cast<int>(config_.version.size()), config_.version.data(),
|
||||
config_.serial_id.c_str(), ble_enabled_,
|
||||
config_.serial_id.c_str(), ble_enabled_, cache_enabled_,
|
||||
dali_domain_ != nullptr && dali_domain_->isBound());
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -283,7 +307,30 @@ std::vector<uint8_t> GatewayRuntime::buildNotificationFrame(const std::vector<ui
|
||||
return checksum(std::move(frame));
|
||||
}
|
||||
|
||||
bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
|
||||
GatewayRuntime::CommandPriority GatewayRuntime::classifyCommandPriority(
|
||||
const std::vector<uint8_t>& command) {
|
||||
if (command.size() < 5 || !isGatewayCommandFrame(command)) {
|
||||
return CommandPriority::kNormal;
|
||||
}
|
||||
|
||||
const uint8_t opcode = command[3];
|
||||
const uint8_t addr = command[4];
|
||||
if (opcode == 0x30 && (addr == 1 || addr == 2)) {
|
||||
return CommandPriority::kMaintenance;
|
||||
}
|
||||
if (opcode == 0x32) {
|
||||
return CommandPriority::kMaintenance;
|
||||
}
|
||||
if (opcode == 0x00 || opcode == 0x01 || opcode == 0x03 || opcode == 0x04 || opcode == 0x07 ||
|
||||
opcode == 0x08 || opcode == 0x10 || opcode == 0x11 || opcode == 0x12 || opcode == 0x13 ||
|
||||
opcode == 0x17 || opcode == 0x18 || opcode == 0x37 || opcode == 0x38 ||
|
||||
(opcode == 0x30 && addr == 0)) {
|
||||
return CommandPriority::kControl;
|
||||
}
|
||||
return CommandPriority::kNormal;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command, CommandPriority priority) {
|
||||
LockGuard guard(command_lock_);
|
||||
last_enqueue_drop_reason_ = CommandDropReason::kNone;
|
||||
if (isQueryCommand(command) && hasPendingQueryCommand(command)) {
|
||||
@@ -291,25 +338,30 @@ bool GatewayRuntime::enqueueCommand(std::vector<uint8_t> command) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pending_commands_.size() >= config_.command_queue_capacity) {
|
||||
if (pendingCommandCountLocked() >= config_.command_queue_capacity) {
|
||||
last_enqueue_drop_reason_ = CommandDropReason::kQueueFull;
|
||||
return false;
|
||||
}
|
||||
|
||||
pending_commands_.push_back(std::move(command));
|
||||
queueForPriorityLocked(priority).push_back(std::move(command));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> GatewayRuntime::popNextCommand() {
|
||||
LockGuard guard(command_lock_);
|
||||
if (pending_commands_.empty()) {
|
||||
current_command_.reset();
|
||||
return std::nullopt;
|
||||
for (const auto priority : {CommandPriority::kControl, CommandPriority::kNormal,
|
||||
CommandPriority::kMaintenance}) {
|
||||
auto& queue = queueForPriorityLocked(priority);
|
||||
if (!queue.empty()) {
|
||||
current_command_ = std::move(queue.front());
|
||||
current_command_priority_ = priority;
|
||||
queue.pop_front();
|
||||
return current_command_;
|
||||
}
|
||||
}
|
||||
|
||||
current_command_ = std::move(pending_commands_.front());
|
||||
pending_commands_.pop_front();
|
||||
return current_command_;
|
||||
current_command_.reset();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void GatewayRuntime::completeCurrentCommand() {
|
||||
@@ -328,10 +380,29 @@ bool GatewayRuntime::hasPendingQueryCommand(const std::vector<uint8_t>& command)
|
||||
return true;
|
||||
}
|
||||
|
||||
return std::any_of(pending_commands_.begin(), pending_commands_.end(),
|
||||
[&](const std::vector<uint8_t>& pending) {
|
||||
return queryCommandKey(pending) == command_key;
|
||||
});
|
||||
const auto matches = [&](const std::vector<uint8_t>& pending) {
|
||||
return queryCommandKey(pending) == command_key;
|
||||
};
|
||||
return std::any_of(control_commands_.begin(), control_commands_.end(), matches) ||
|
||||
std::any_of(normal_commands_.begin(), normal_commands_.end(), matches) ||
|
||||
std::any_of(maintenance_commands_.begin(), maintenance_commands_.end(), matches);
|
||||
}
|
||||
|
||||
bool GatewayRuntime::hasPendingControlCommand(uint8_t gateway_id) const {
|
||||
LockGuard guard(command_lock_);
|
||||
return std::any_of(control_commands_.begin(), control_commands_.end(), [gateway_id](const auto& command) {
|
||||
return command.size() > 2 && command[2] == gateway_id;
|
||||
});
|
||||
}
|
||||
|
||||
bool GatewayRuntime::shouldYieldMaintenance(uint8_t gateway_id) const {
|
||||
return hasPendingControlCommand(gateway_id);
|
||||
}
|
||||
|
||||
bool GatewayRuntime::hasActiveCommand(uint8_t gateway_id) const {
|
||||
LockGuard guard(command_lock_);
|
||||
return current_command_.has_value() && current_command_->size() > 2 &&
|
||||
(*current_command_)[2] == gateway_id;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::hasActiveQueryCommand(uint8_t gateway_id) const {
|
||||
@@ -422,6 +493,26 @@ bool GatewayRuntime::setBleEnabled(bool enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::cacheEnabled() const {
|
||||
LockGuard guard(command_lock_);
|
||||
return cache_enabled_;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setCacheEnabled(bool enabled) {
|
||||
{
|
||||
LockGuard guard(command_lock_);
|
||||
if (cache_enabled_ == enabled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!settings_.setCacheEnabled(enabled)) {
|
||||
return false;
|
||||
}
|
||||
LockGuard guard(command_lock_);
|
||||
cache_enabled_ = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const {
|
||||
LockGuard guard(command_lock_);
|
||||
const auto cached = gateway_names_.find(gateway_id);
|
||||
@@ -492,6 +583,36 @@ bool GatewayRuntime::isQueryCommand(const std::vector<uint8_t>& command) const {
|
||||
command[3] <= 0x16;
|
||||
}
|
||||
|
||||
size_t GatewayRuntime::pendingCommandCountLocked() const {
|
||||
return control_commands_.size() + normal_commands_.size() + maintenance_commands_.size();
|
||||
}
|
||||
|
||||
std::deque<std::vector<uint8_t>>& GatewayRuntime::queueForPriorityLocked(
|
||||
CommandPriority priority) {
|
||||
switch (priority) {
|
||||
case CommandPriority::kControl:
|
||||
return control_commands_;
|
||||
case CommandPriority::kMaintenance:
|
||||
return maintenance_commands_;
|
||||
case CommandPriority::kNormal:
|
||||
default:
|
||||
return normal_commands_;
|
||||
}
|
||||
}
|
||||
|
||||
const std::deque<std::vector<uint8_t>>& GatewayRuntime::queueForPriorityLocked(
|
||||
CommandPriority priority) const {
|
||||
switch (priority) {
|
||||
case CommandPriority::kControl:
|
||||
return control_commands_;
|
||||
case CommandPriority::kMaintenance:
|
||||
return maintenance_commands_;
|
||||
case CommandPriority::kNormal:
|
||||
default:
|
||||
return normal_commands_;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> GatewayRuntime::queryCommandKey(
|
||||
const std::vector<uint8_t>& command) const {
|
||||
if (!isQueryCommand(command)) {
|
||||
|
||||
Reference in New Issue
Block a user