Compare commits

..

2 Commits

Author SHA1 Message Date
Tony fa2eae87cf feat(gateway): enhance DALI command handling with mirroring and new target types 2026-05-02 03:19:02 +08:00
Tony 639fdd860e 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>
2026-05-02 03:04:06 +08:00
11 changed files with 1745 additions and 46 deletions
+32
View File
@@ -221,8 +221,39 @@ endmenu
menu "Gateway Cache"
config GATEWAY_CACHE_SUPPORTED
bool "Gateway cache support"
default y
help
Enables the gateway cache facade. When disabled, internal scene and group
commands still persist through direct NVS writes.
config GATEWAY_CACHE_START_ENABLED
bool "Start gateway cache in deferred mode"
depends on GATEWAY_CACHE_SUPPORTED
default y
help
Starts the deferred RAM cache and background flush task at boot. Disable
this to keep scene and group persistence in direct-NVS mode.
config GATEWAY_CACHE_RECONCILIATION_ENABLED
bool "Enable cache reconciliation tracking"
depends on GATEWAY_CACHE_SUPPORTED && GATEWAY_CACHE_START_ENABLED
default y
help
Tracks outside DALI bus mutations as update-needed flags for resumable
reconciliation work.
config GATEWAY_CACHE_FULL_STATE_MIRROR
bool "Enable full DALI state mirror"
depends on GATEWAY_CACHE_RECONCILIATION_ENABLED
default n
help
Enables the heavier full bus-state mirror path for future reconciliation.
config GATEWAY_CACHE_FLUSH_INTERVAL_MS
int "Cache flush interval ms"
depends on GATEWAY_CACHE_SUPPORTED && GATEWAY_CACHE_START_ENABLED
range 100 600000
default 5000
help
@@ -230,6 +261,7 @@ config GATEWAY_CACHE_FLUSH_INTERVAL_MS
choice GATEWAY_CACHE_CONFLICT_PRIORITY
prompt "Cache conflict priority default"
depends on GATEWAY_CACHE_RECONCILIATION_ENABLED
default GATEWAY_CACHE_OUTSIDE_BUS_FIRST
help
Default source of truth to use when future cache reconciliation detects
+29
View File
@@ -180,6 +180,30 @@ constexpr bool kCloudBridgeStartupEnabled = true;
constexpr bool kCloudBridgeStartupEnabled = false;
#endif
#ifdef CONFIG_GATEWAY_CACHE_SUPPORTED
constexpr bool kCacheSupported = true;
#else
constexpr bool kCacheSupported = false;
#endif
#ifdef CONFIG_GATEWAY_CACHE_START_ENABLED
constexpr bool kCacheStartupEnabled = true;
#else
constexpr bool kCacheStartupEnabled = false;
#endif
#ifdef CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED
constexpr bool kCacheReconciliationEnabled = true;
#else
constexpr bool kCacheReconciliationEnabled = false;
#endif
#ifdef CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR
constexpr bool kCacheFullStateMirrorEnabled = true;
#else
constexpr bool kCacheFullStateMirrorEnabled = false;
#endif
#ifdef CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST
constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
gateway::GatewayCachePriorityMode::kLocalGatewayFirst;
@@ -417,6 +441,7 @@ extern "C" void app_main(void) {
kProjectVersion,
gateway::ReadRuntimeSerialId(),
kBleStartupEnabled,
kCacheSupported && kCacheStartupEnabled,
},
s_dali_domain.get());
ESP_ERROR_CHECK(s_runtime->start());
@@ -424,6 +449,10 @@ extern "C" void app_main(void) {
ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime));
gateway::GatewayCacheConfig cache_config;
cache_config.cache_enabled = kCacheSupported && kCacheStartupEnabled && s_runtime->cacheEnabled();
cache_config.reconciliation_enabled = cache_config.cache_enabled && kCacheReconciliationEnabled;
cache_config.full_state_mirror_enabled = cache_config.reconciliation_enabled &&
kCacheFullStateMirrorEnabled;
cache_config.flush_interval_ms = static_cast<uint32_t>(CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS);
cache_config.default_priority_mode = kCachePriorityMode;
s_cache = std::make_unique<gateway::GatewayCache>(cache_config);
+4
View File
@@ -621,6 +621,10 @@ CONFIG_GATEWAY_CHANNEL2_PHY_DISABLED=y
#
# Gateway Cache
#
CONFIG_GATEWAY_CACHE_SUPPORTED=y
CONFIG_GATEWAY_CACHE_START_ENABLED=y
CONFIG_GATEWAY_CACHE_RECONCILIATION_ENABLED=y
# CONFIG_GATEWAY_CACHE_FULL_STATE_MIRROR is not set
CONFIG_GATEWAY_CACHE_FLUSH_INTERVAL_MS=5000
CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
# CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST is not set
@@ -2,6 +2,7 @@
#include <cstddef>
#include <cstdint>
#include <array>
#include <functional>
#include <map>
#include <memory>
@@ -93,6 +94,21 @@ struct DaliDomainSnapshot {
std::map<std::string, std::vector<double>> number_arrays;
};
struct DaliAddressSettingsSnapshot {
std::optional<uint8_t> power_on_level;
std::optional<uint8_t> system_failure_level;
std::optional<uint8_t> min_level;
std::optional<uint8_t> max_level;
std::optional<uint8_t> fade_time;
std::optional<uint8_t> fade_rate;
bool anyKnown() const {
return power_on_level.has_value() || system_failure_level.has_value() ||
min_level.has_value() || max_level.has_value() || fade_time.has_value() ||
fade_rate.has_value();
}
};
class DaliDomainService {
public:
DaliDomainService();
@@ -141,6 +157,15 @@ class DaliDomainService {
bool on(uint8_t gateway_id, int short_address) const;
bool off(uint8_t gateway_id, int short_address) const;
bool off(int short_address) const;
std::optional<uint16_t> queryGroupMask(uint8_t gateway_id, int short_address) const;
std::optional<uint8_t> querySceneLevel(uint8_t gateway_id, int short_address, int scene) const;
std::optional<DaliAddressSettingsSnapshot> queryAddressSettings(uint8_t gateway_id,
int short_address) const;
bool applyGroupMask(uint8_t gateway_id, int short_address, uint16_t group_mask) const;
bool applySceneLevel(uint8_t gateway_id, int short_address, int scene,
std::optional<uint8_t> level) const;
bool applyAddressSettings(uint8_t gateway_id, int short_address,
const DaliAddressSettingsSnapshot& settings) const;
bool updateChannelName(uint8_t gateway_id, std::string_view name);
bool allocateAllAddr(uint8_t gateway_id, int start_address = 0) const;
void stopAllocAddr(uint8_t gateway_id) const;
+119
View File
@@ -779,6 +779,125 @@ bool DaliDomainService::off(int short_address) const {
return off(channels_.front()->config.gateway_id, short_address);
}
std::optional<uint16_t> DaliDomainService::queryGroupMask(uint8_t gateway_id,
int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
const auto group_mask = channel->dali->base.getGroup(short_address);
if (!group_mask.has_value()) {
return std::nullopt;
}
return static_cast<uint16_t>(*group_mask);
}
std::optional<uint8_t> DaliDomainService::querySceneLevel(uint8_t gateway_id, int short_address,
int scene) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
const auto level = channel->dali->base.getScene(short_address, scene);
if (!level.has_value()) {
return std::nullopt;
}
return static_cast<uint8_t>(*level);
}
std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettings(
uint8_t gateway_id, int short_address) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return std::nullopt;
}
DaliAddressSettingsSnapshot settings{};
if (const auto value = channel->dali->base.getPowerOnLevel(short_address); value.has_value()) {
settings.power_on_level = static_cast<uint8_t>(*value);
}
if (const auto value = channel->dali->base.getSystemFailureLevel(short_address);
value.has_value()) {
settings.system_failure_level = static_cast<uint8_t>(*value);
}
if (const auto value = channel->dali->base.getMinLevel(short_address); value.has_value()) {
settings.min_level = static_cast<uint8_t>(*value);
}
if (const auto value = channel->dali->base.getMaxLevel(short_address); value.has_value()) {
settings.max_level = static_cast<uint8_t>(*value);
}
if (const auto value = channel->dali->base.getFadeTime(short_address); value.has_value()) {
settings.fade_time = static_cast<uint8_t>(*value);
}
if (const auto value = channel->dali->base.getFadeRate(short_address); value.has_value()) {
settings.fade_rate = static_cast<uint8_t>(*value);
}
if (!settings.anyKnown()) {
return std::nullopt;
}
return settings;
}
bool DaliDomainService::applyGroupMask(uint8_t gateway_id, int short_address,
uint16_t group_mask) const {
const auto* channel = findChannelByGateway(gateway_id);
return channel != nullptr && channel->dali != nullptr &&
channel->dali->base.setGroup(short_address, group_mask);
}
bool DaliDomainService::applySceneLevel(uint8_t gateway_id, int short_address, int scene,
std::optional<uint8_t> level) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr || !level.has_value()) {
return false;
}
if (*level == 255U) {
return channel->dali->base.removeScene(short_address, scene);
}
return channel->dali->base.setDTR(*level) &&
channel->dali->base.storeDTRAsSceneBright(short_address, scene);
}
bool DaliDomainService::applyAddressSettings(uint8_t gateway_id, int short_address,
const DaliAddressSettingsSnapshot& settings) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->dali == nullptr) {
return false;
}
bool ok = true;
if (settings.power_on_level.has_value()) {
ok = ok && channel->dali->base.setPowerOnLevel(short_address, *settings.power_on_level);
}
if (settings.system_failure_level.has_value()) {
ok = ok &&
channel->dali->base.setSystemFailureLevel(short_address, *settings.system_failure_level);
}
if (settings.min_level.has_value()) {
ok = ok && channel->dali->base.setMinLevel(short_address, *settings.min_level);
}
if (settings.max_level.has_value()) {
ok = ok && channel->dali->base.setMaxLevel(short_address, *settings.max_level);
}
if (settings.fade_time.has_value()) {
ok = ok && channel->dali->base.setFadeTime(short_address, *settings.fade_time);
}
if (settings.fade_rate.has_value()) {
ok = ok && channel->dali->base.setFadeRate(short_address, *settings.fade_rate);
}
return ok;
}
bool DaliDomainService::updateChannelName(uint8_t gateway_id, std::string_view name) {
auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr) {
@@ -3,6 +3,7 @@
#include <array>
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include "esp_err.h"
@@ -20,18 +21,71 @@ enum class GatewayCachePriorityMode : uint8_t {
struct GatewayCacheConfig {
std::string storage_namespace{"gateway_rt"};
bool cache_enabled{true};
bool reconciliation_enabled{true};
bool full_state_mirror_enabled{false};
uint32_t flush_interval_ms{5000};
uint32_t task_stack_size{4096};
UBaseType_t task_priority{3};
GatewayCachePriorityMode default_priority_mode{GatewayCachePriorityMode::kOutsideBusFirst};
};
enum class GatewayCacheRawFrameOrigin : uint8_t {
kLocalGateway = 0,
kOutsideBus = 1,
};
enum class GatewayCacheDaliTargetKind : uint8_t {
kShortAddress = 0,
kGroup = 1,
kBroadcast = 2,
};
struct GatewayCacheDaliTarget {
GatewayCacheDaliTargetKind kind{GatewayCacheDaliTargetKind::kShortAddress};
uint8_t value{0};
};
struct GatewayCacheChannelFlags {
bool need_update_group{false};
bool need_update_scene{false};
bool need_update_settings{false};
};
struct GatewayCacheDaliSettingsSnapshot {
std::optional<uint8_t> power_on_level;
std::optional<uint8_t> system_failure_level;
std::optional<uint8_t> min_level;
std::optional<uint8_t> max_level;
std::optional<uint8_t> fade_time;
std::optional<uint8_t> fade_rate;
bool anyKnown() const {
return power_on_level.has_value() || system_failure_level.has_value() ||
min_level.has_value() || max_level.has_value() || fade_time.has_value() ||
fade_rate.has_value();
}
};
struct GatewayCacheDaliRuntimeStatus {
std::optional<uint8_t> actual_level;
std::optional<uint8_t> scene_id;
bool use_min_level{false};
uint32_t revision{0};
bool anyKnown() const {
return actual_level.has_value() || scene_id.has_value() || use_min_level;
}
};
struct GatewayCacheDaliAddressState {
bool group_mask_known{false};
uint16_t group_mask{0};
std::array<std::optional<uint8_t>, 16> scene_levels{};
GatewayCacheDaliSettingsSnapshot settings;
GatewayCacheDaliRuntimeStatus status;
};
class GatewayCache {
public:
struct SceneEntry {
@@ -80,20 +134,73 @@ class GatewayCache {
std::pair<uint8_t, uint8_t> groupMask(uint8_t gateway_id);
GatewayCacheChannelFlags channelFlags(uint8_t gateway_id);
GatewayCacheChannelFlags pendingChannelFlags(uint8_t gateway_id);
GatewayCacheDaliAddressState daliAddressState(uint8_t gateway_id, uint8_t short_address);
GatewayCacheDaliRuntimeStatus daliGroupStatus(uint8_t gateway_id, uint8_t group_id);
GatewayCacheDaliRuntimeStatus daliBroadcastStatus(uint8_t gateway_id);
bool setDaliGroupMask(uint8_t gateway_id, uint8_t short_address,
std::optional<uint16_t> group_mask);
bool setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id,
std::optional<uint8_t> level);
bool setDaliSettings(uint8_t gateway_id, uint8_t short_address,
std::optional<GatewayCacheDaliSettingsSnapshot> settings);
bool clearChannelFlagsIfMatched(uint8_t gateway_id, const GatewayCacheChannelFlags& flags);
void markGroupUpdateNeeded(uint8_t gateway_id, bool needed = true);
void markSceneUpdateNeeded(uint8_t gateway_id, bool needed = true);
void markSettingsUpdateNeeded(uint8_t gateway_id, bool needed = true);
bool cacheEnabled() const;
bool reconciliationEnabled() const;
bool fullStateMirrorEnabled() const;
bool mirrorDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
bool observeDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command,
GatewayCacheRawFrameOrigin origin);
GatewayCachePriorityMode priorityMode();
void setPriorityMode(GatewayCachePriorityMode mode);
private:
struct DtrState {
std::optional<uint8_t> dtr0;
std::optional<uint8_t> dtr1;
std::optional<uint8_t> dtr2;
};
static void TaskEntry(void* arg);
void taskLoop();
bool flushDirty();
bool openStorageLocked();
void closeStorageLocked();
bool persistSceneLocked(uint8_t gateway_id, uint8_t scene_id, const SceneEntry& scene);
bool persistGroupLocked(uint8_t gateway_id, uint8_t group_id, const GroupEntry& group);
bool commitStorageLocked();
bool shouldTrackUpdateFlagsLocked() const;
uint32_t nextDaliRuntimeRevisionLocked();
bool mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
void clearDaliTargetStateLocked(uint8_t gateway_id, const GatewayCacheDaliTarget& target,
uint32_t revision);
void applyDaliTargetRuntimeStatusLocked(uint8_t gateway_id,
const GatewayCacheDaliTarget& target,
const GatewayCacheDaliRuntimeStatus& status);
void applyDaliRuntimeStatusToAddressLocked(GatewayCacheDaliAddressState& state,
const GatewayCacheDaliRuntimeStatus& status);
void applyDaliTargetGroupMutationLocked(uint8_t gateway_id,
const GatewayCacheDaliTarget& target, uint8_t group_id,
bool add_to_group);
void applyDaliTargetSceneLevelLocked(uint8_t gateway_id,
const GatewayCacheDaliTarget& target, uint8_t scene_id,
std::optional<uint8_t> level);
void applyDaliTargetSettingsLocked(uint8_t gateway_id,
const GatewayCacheDaliTarget& target, uint8_t command,
uint8_t value);
void refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id,
GatewayCacheDaliAddressState& state);
GatewayCacheDaliAddressState& ensureDaliAddressStateLocked(uint8_t gateway_id,
uint8_t short_address);
GatewayCacheDaliRuntimeStatus& ensureDaliGroupStatusLocked(uint8_t gateway_id,
uint8_t group_id);
GatewayCacheDaliRuntimeStatus& ensureDaliBroadcastStatusLocked(uint8_t gateway_id);
SceneStore& ensureSceneStoreLocked(uint8_t gateway_id);
GroupStore& ensureGroupStoreLocked(uint8_t gateway_id);
void loadSceneStoreLocked(uint8_t gateway_id, SceneStore& scenes);
@@ -109,7 +216,12 @@ class GatewayCache {
nvs_handle_t storage_{0};
std::map<uint8_t, SceneStore> scenes_;
std::map<uint8_t, GroupStore> groups_;
std::map<uint8_t, std::array<GatewayCacheDaliAddressState, 64>> dali_states_;
std::map<uint8_t, std::array<GatewayCacheDaliRuntimeStatus, 16>> dali_group_status_;
std::map<uint8_t, GatewayCacheDaliRuntimeStatus> dali_broadcast_status_;
std::map<uint8_t, DtrState> dtr_states_;
std::map<uint8_t, GatewayCacheChannelFlags> channel_flags_;
uint32_t dali_runtime_revision_{0};
bool dirty_{false};
};
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,10 @@
#pragma once
#include <array>
#include <atomic>
#include <cstdint>
#include <functional>
#include <map>
#include <string>
#include <string_view>
#include <vector>
@@ -10,6 +12,7 @@
#include "gateway_cache.hpp"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
namespace gateway {
@@ -83,9 +86,30 @@ class GatewayController {
GatewayControllerSnapshot snapshot();
private:
struct ReconciliationJob {
enum class Phase : uint8_t {
kReloadFlags = 0,
kGroups = 1,
kScenes = 2,
kSettings = 3,
};
GatewayCacheChannelFlags flags{};
Phase phase{Phase::kReloadFlags};
uint8_t short_address{0};
uint8_t scene_id{0};
};
static void TaskEntry(void* arg);
void taskLoop();
void dispatchCommand(const std::vector<uint8_t>& command);
void scheduleReconciliation(uint8_t gateway_id);
bool hasPendingReconciliation() const;
bool runMaintenanceStep();
bool runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job);
void reconcileGroupStep(uint8_t gateway_id, uint8_t short_address);
void reconcileSceneStep(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id);
void reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address);
bool hasGateway(uint8_t gateway_id) const;
std::vector<uint8_t> gatewayIds() const;
@@ -95,12 +119,19 @@ class GatewayController {
void publishFrame(const std::vector<uint8_t>& frame);
void handleDaliRawFrame(const DaliRawFrame& frame);
bool sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
bool sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
bool setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level);
bool offAndMirror(uint8_t gateway_id, int dec_address);
bool onAndMirror(uint8_t gateway_id, int dec_address);
uint8_t resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr);
static uint8_t normalizeGroupTargetType(uint8_t target_type);
static uint8_t normalizeGroupTargetValue(uint8_t target_type, uint8_t target_value);
static uint8_t internalGroupRawTargetAddress(uint8_t target_type, uint8_t target_value,
uint8_t raw_addr);
static int internalGroupDecTargetAddress(uint8_t target_type, uint8_t target_value);
static uint8_t rawArcAddressFromDec(int dec_address);
static uint8_t rawCommandAddressFromDec(int dec_address);
static int shortAddressFromRaw(uint8_t raw_addr);
static int reverseInRange(int value, int min_value, int max_value);
@@ -131,10 +162,13 @@ class GatewayController {
GatewayCache& cache_;
GatewayControllerConfig config_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t maintenance_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<uint8_t, ReconciliationJob> reconciliation_jobs_;
std::atomic<int> maintenance_activity_gateway_{-1};
bool setup_mode_{false};
bool wireless_setup_mode_{false};
bool ble_enabled_{false};
@@ -16,6 +16,33 @@ namespace {
constexpr const char* kTag = "gateway_controller";
constexpr size_t kMaxNameBytes = 32;
constexpr uint8_t kDaliShortAddressCount = 64;
constexpr uint8_t kDaliSceneCount = 16;
constexpr uint8_t kDaliCmdOff = 0x00;
constexpr uint8_t kDaliCmdRecallMax = 0x05;
constexpr TickType_t kMaintenancePollTicks = pdMS_TO_TICKS(20);
class LockGuard {
public:
explicit LockGuard(SemaphoreHandle_t lock) : lock_(lock) {
if (lock_ != nullptr) {
xSemaphoreTake(lock_, portMAX_DELAY);
}
}
~LockGuard() {
if (lock_ != nullptr) {
xSemaphoreGive(lock_);
}
}
private:
SemaphoreHandle_t lock_;
};
bool AnyFlagSet(const GatewayCacheChannelFlags& flags) {
return flags.need_update_group || flags.need_update_scene || flags.need_update_settings;
}
std::string NormalizeName(std::string_view name) {
std::string normalized(name);
@@ -71,9 +98,18 @@ const char* PhyKindToString(DaliPhyKind phy_kind) {
GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain,
GatewayCache& cache, GatewayControllerConfig config)
: runtime_(runtime), dali_domain_(dali_domain), cache_(cache), config_(config) {}
: runtime_(runtime),
dali_domain_(dali_domain),
cache_(cache),
config_(config),
maintenance_lock_(xSemaphoreCreateMutex()) {}
GatewayController::~GatewayController() = default;
GatewayController::~GatewayController() {
if (maintenance_lock_ != nullptr) {
vSemaphoreDelete(maintenance_lock_);
maintenance_lock_ = nullptr;
}
}
esp_err_t GatewayController::start() {
const auto device_info = runtime_.deviceInfo();
@@ -112,7 +148,7 @@ bool GatewayController::enqueueCommandFrame(const std::vector<uint8_t>& frame) {
ESP_LOGW(kTag, "dropped invalid command frame len=%u", static_cast<unsigned>(frame.size()));
return false;
}
if (!runtime_.enqueueCommand(frame)) {
if (!runtime_.enqueueCommand(frame, GatewayRuntime::classifyCommandPriority(frame))) {
if (runtime_.lastEnqueueDropReason() != GatewayRuntime::CommandDropReason::kDuplicate) {
ESP_LOGW(kTag, "dropped command frame reason=%d",
static_cast<int>(runtime_.lastEnqueueDropReason()));
@@ -210,15 +246,223 @@ void GatewayController::TaskEntry(void* arg) {
void GatewayController::taskLoop() {
while (true) {
bool drained = false;
while (auto command = runtime_.popNextCommand()) {
drained = true;
bool worked = false;
if (auto command = runtime_.popNextCommand()) {
worked = true;
dispatchCommand(*command);
runtime_.completeCurrentCommand();
}
if (!drained) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (!worked) {
worked = runMaintenanceStep();
}
if (!worked) {
ulTaskNotifyTake(pdTRUE, hasPendingReconciliation() ? kMaintenancePollTicks : portMAX_DELAY);
}
}
}
void GatewayController::scheduleReconciliation(uint8_t gateway_id) {
if (!cache_.reconciliationEnabled()) {
return;
}
auto flags = cache_.pendingChannelFlags(gateway_id);
if (cache_.fullStateMirrorEnabled() && AnyFlagSet(flags)) {
flags = {true, true, true};
}
if (!AnyFlagSet(flags)) {
return;
}
{
LockGuard guard(maintenance_lock_);
reconciliation_jobs_.try_emplace(gateway_id);
}
if (task_handle_ != nullptr) {
xTaskNotifyGive(task_handle_);
}
}
bool GatewayController::hasPendingReconciliation() const {
LockGuard guard(maintenance_lock_);
return !reconciliation_jobs_.empty();
}
bool GatewayController::runMaintenanceStep() {
if (!cache_.reconciliationEnabled()) {
return false;
}
uint8_t gateway_id = 0;
ReconciliationJob job;
{
LockGuard guard(maintenance_lock_);
if (reconciliation_jobs_.empty()) {
return false;
}
const auto it = reconciliation_jobs_.begin();
gateway_id = it->first;
job = it->second;
}
if (runtime_.shouldYieldMaintenance(gateway_id)) {
return false;
}
const bool keep_job = runReconciliationStep(gateway_id, job);
{
LockGuard guard(maintenance_lock_);
auto it = reconciliation_jobs_.find(gateway_id);
if (it == reconciliation_jobs_.end()) {
return true;
}
if (keep_job) {
it->second = job;
} else {
reconciliation_jobs_.erase(it);
}
}
return true;
}
bool GatewayController::runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job) {
if (job.phase == ReconciliationJob::Phase::kReloadFlags) {
job.flags = cache_.pendingChannelFlags(gateway_id);
if (cache_.fullStateMirrorEnabled() && AnyFlagSet(job.flags)) {
job.flags = {true, true, true};
}
if (!AnyFlagSet(job.flags)) {
return false;
}
job.short_address = 0;
job.scene_id = 0;
if (job.flags.need_update_group) {
job.phase = ReconciliationJob::Phase::kGroups;
} else if (job.flags.need_update_scene) {
job.phase = ReconciliationJob::Phase::kScenes;
} else {
job.phase = ReconciliationJob::Phase::kSettings;
}
}
switch (job.phase) {
case ReconciliationJob::Phase::kGroups:
reconcileGroupStep(gateway_id, job.short_address++);
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
if (job.flags.need_update_scene) {
job.phase = ReconciliationJob::Phase::kScenes;
} else if (job.flags.need_update_settings) {
job.phase = ReconciliationJob::Phase::kSettings;
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
job.phase = ReconciliationJob::Phase::kReloadFlags;
} else {
return false;
}
}
return true;
case ReconciliationJob::Phase::kScenes:
reconcileSceneStep(gateway_id, job.short_address, job.scene_id);
++job.scene_id;
if (job.scene_id >= kDaliSceneCount) {
job.scene_id = 0;
++job.short_address;
}
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
if (job.flags.need_update_settings) {
job.phase = ReconciliationJob::Phase::kSettings;
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
job.phase = ReconciliationJob::Phase::kReloadFlags;
} else {
return false;
}
}
return true;
case ReconciliationJob::Phase::kSettings:
reconcileSettingsStep(gateway_id, job.short_address++);
if (job.short_address >= kDaliShortAddressCount) {
job.short_address = 0;
if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
job.phase = ReconciliationJob::Phase::kReloadFlags;
} else {
return false;
}
}
return true;
case ReconciliationJob::Phase::kReloadFlags:
default:
return true;
}
}
void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_address) {
const auto policy = cache_.priorityMode();
const auto state = cache_.daliAddressState(gateway_id, short_address);
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst && state.group_mask_known) {
maintenance_activity_gateway_.store(gateway_id);
const bool applied = dali_domain_.applyGroupMask(gateway_id, short_address, state.group_mask);
maintenance_activity_gateway_.store(-1);
const auto verified_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
cache_.setDaliGroupMask(gateway_id, short_address, verified_mask);
if (!applied && verified_mask.has_value()) {
ESP_LOGW(kTag, "group reconcile fallback gateway=%u short=%u", gateway_id, short_address);
}
return;
}
cache_.setDaliGroupMask(gateway_id, short_address,
dali_domain_.queryGroupMask(gateway_id, short_address));
}
void GatewayController::reconcileSceneStep(uint8_t gateway_id, uint8_t short_address,
uint8_t scene_id) {
const auto policy = cache_.priorityMode();
const auto state = cache_.daliAddressState(gateway_id, short_address);
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst &&
state.scene_levels[scene_id].has_value()) {
maintenance_activity_gateway_.store(gateway_id);
dali_domain_.applySceneLevel(gateway_id, short_address, scene_id, state.scene_levels[scene_id]);
maintenance_activity_gateway_.store(-1);
}
cache_.setDaliSceneLevel(gateway_id, short_address, scene_id,
dali_domain_.querySceneLevel(gateway_id, short_address, scene_id));
}
void GatewayController::reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address) {
const auto policy = cache_.priorityMode();
const auto state = cache_.daliAddressState(gateway_id, short_address);
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst && state.settings.anyKnown()) {
maintenance_activity_gateway_.store(gateway_id);
dali_domain_.applyAddressSettings(gateway_id, short_address, {
state.settings.power_on_level,
state.settings.system_failure_level,
state.settings.min_level,
state.settings.max_level,
state.settings.fade_time,
state.settings.fade_rate,
});
maintenance_activity_gateway_.store(-1);
}
const auto settings = dali_domain_.queryAddressSettings(gateway_id, short_address);
if (settings.has_value()) {
cache_.setDaliSettings(gateway_id, short_address,
GatewayCacheDaliSettingsSnapshot{settings->power_on_level,
settings->system_failure_level,
settings->min_level,
settings->max_level,
settings->fade_time,
settings->fade_rate});
} else {
cache_.setDaliSettings(gateway_id, short_address, std::nullopt);
}
}
@@ -316,7 +560,7 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
}
case 0x07:
case 0x08:
dali_domain_.sendRaw(gateway_id, addr, data);
sendRawAndMirror(gateway_id, addr, data);
break;
case 0x09: {
const auto ids = gatewayIds();
@@ -333,20 +577,20 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
break;
case 0x10:
case 0x11:
dali_domain_.sendRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
sendRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
break;
case 0x12:
if (addr == 0xff && data >= 0x10 && data <= 0x1f) {
const uint8_t scene_id = static_cast<uint8_t>(data - 0x10);
if (!executeScene(gateway_id, shortAddressFromRaw(addr), scene_id)) {
dali_domain_.sendRaw(gateway_id, addr, data);
sendRawAndMirror(gateway_id, addr, data);
}
} else {
dali_domain_.sendRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
sendRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
}
break;
case 0x13:
dali_domain_.sendExtRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
sendExtRawAndMirror(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
break;
case 0x14: {
const auto result = dali_domain_.queryRaw(gateway_id, resolveInternalGroupRawAddress(gateway_id, addr), data);
@@ -403,7 +647,7 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
const uint8_t b = command[7];
const int target = shortAddressFromRaw(resolveInternalGroupRawAddress(gateway_id, addr));
if (r == 0 && g == 0 && b == 0) {
dali_domain_.off(gateway_id, target);
offAndMirror(gateway_id, target);
} else {
dali_domain_.setColourRGB(gateway_id, target, r, g, b);
}
@@ -480,10 +724,6 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
if (frame.data.size() != 2 && frame.data.size() != 3) {
return;
}
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) ||
runtime_.hasActiveQueryCommand(frame.gateway_id)) {
return;
}
uint8_t addr = 0;
uint8_t data = 0;
@@ -498,9 +738,67 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
data = frame.data[2];
}
const bool maintenance_activity = maintenance_activity_gateway_.load() == frame.gateway_id;
const bool local_activity = maintenance_activity || runtime_.hasActiveCommand(frame.gateway_id) ||
dali_domain_.isAllocAddr(frame.gateway_id);
const bool flagged = cache_.observeDaliCommand(frame.gateway_id, addr, data,
local_activity
? GatewayCacheRawFrameOrigin::kLocalGateway
: GatewayCacheRawFrameOrigin::kOutsideBus);
if (flagged) {
scheduleReconciliation(frame.gateway_id);
}
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) || maintenance_activity ||
runtime_.hasActiveQueryCommand(frame.gateway_id)) {
return;
}
publishPayload(frame.gateway_id, {0x01, frame.gateway_id, addr, data});
}
bool GatewayController::sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) {
const bool sent = dali_domain_.sendRaw(gateway_id, raw_addr, command);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, raw_addr, command);
}
return sent;
}
bool GatewayController::sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr,
uint8_t command) {
const bool sent = dali_domain_.sendExtRaw(gateway_id, raw_addr, command);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, raw_addr, command);
}
return sent;
}
bool GatewayController::setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level) {
const bool sent = dali_domain_.setBright(gateway_id, dec_address, level);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, rawArcAddressFromDec(dec_address), level);
}
return sent;
}
bool GatewayController::offAndMirror(uint8_t gateway_id, int dec_address) {
const bool sent = dali_domain_.off(gateway_id, dec_address);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address), kDaliCmdOff);
}
return sent;
}
bool GatewayController::onAndMirror(uint8_t gateway_id, int dec_address) {
const bool sent = dali_domain_.on(gateway_id, dec_address);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address),
kDaliCmdRecallMax);
}
return sent;
}
uint8_t GatewayController::resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr) {
if (raw_addr < 0x80 || raw_addr > 0x9f) {
return raw_addr;
@@ -554,6 +852,26 @@ int GatewayController::internalGroupDecTargetAddress(uint8_t target_type, uint8_
return 127;
}
uint8_t GatewayController::rawArcAddressFromDec(int dec_address) {
if (dec_address >= 0 && dec_address < 64) {
return static_cast<uint8_t>(dec_address * 2);
}
if (dec_address >= 64 && dec_address < 80) {
return static_cast<uint8_t>(0x80 + (dec_address - 64) * 2);
}
return 0xfe;
}
uint8_t GatewayController::rawCommandAddressFromDec(int dec_address) {
if (dec_address >= 0 && dec_address < 64) {
return static_cast<uint8_t>(dec_address * 2 + 1);
}
if (dec_address >= 64 && dec_address < 80) {
return static_cast<uint8_t>(0x80 + (dec_address - 64) * 2 + 1);
}
return 0xff;
}
int GatewayController::shortAddressFromRaw(uint8_t raw_addr) {
return raw_addr / 2;
}
@@ -605,9 +923,9 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint
return false;
}
if (scene_data.brightness <= 0) {
dali_domain_.off(gateway_id, short_address);
offAndMirror(gateway_id, short_address);
} else {
dali_domain_.setBright(gateway_id, short_address, scene_data.brightness);
setBrightAndMirror(gateway_id, short_address, scene_data.brightness);
}
if (scene_data.color_mode == 0) {
int kelvin = scene_data.data1 * 256 + scene_data.data2;
@@ -623,7 +941,7 @@ bool GatewayController::executeScene(uint8_t gateway_id, int short_address, uint
dali_domain_.setColourRGB(gateway_id, short_address, scene_data.data1, scene_data.data2,
scene_data.data3);
} else if (scene_data.brightness <= 0) {
dali_domain_.off(gateway_id, short_address);
offAndMirror(gateway_id, short_address);
}
}
return true;
@@ -658,9 +976,9 @@ bool GatewayController::executeGroup(uint8_t gateway_id, uint8_t group_id) {
if (!group_data.enabled) {
return false;
}
return dali_domain_.on(gateway_id,
internalGroupDecTargetAddress(group_data.target_type,
group_data.target_value));
return onAndMirror(gateway_id,
internalGroupDecTargetAddress(group_data.target_type,
group_data.target_value));
}
void GatewayController::handleGatewayNameCommand(uint8_t gateway_id,
@@ -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)) {