#pragma once #include #include #include #include #include #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "nvs.h" namespace gateway { enum class GatewayCachePriorityMode : uint8_t { kOutsideBusFirst = 0, kLocalGatewayFirst = 1, }; 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 power_on_level; std::optional system_failure_level; std::optional min_level; std::optional max_level; std::optional fade_time; std::optional 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 actual_level; std::optional 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, 16> scene_levels{}; GatewayCacheDaliSettingsSnapshot settings; GatewayCacheDaliRuntimeStatus status; }; class GatewayCache { public: struct SceneEntry { bool enabled{false}; uint8_t brightness{254}; uint8_t color_mode{2}; uint8_t data1{0}; uint8_t data2{0}; uint8_t data3{0}; std::string name; }; struct GroupEntry { bool enabled{false}; uint8_t target_type{2}; uint8_t target_value{0}; std::string name; }; using SceneStore = std::array; using GroupStore = std::array; explicit GatewayCache(GatewayCacheConfig config = {}); ~GatewayCache(); esp_err_t start(); void preloadChannel(uint8_t gateway_id); SceneStore scenes(uint8_t gateway_id); GroupStore groups(uint8_t gateway_id); SceneEntry scene(uint8_t gateway_id, uint8_t scene_id); GroupEntry group(uint8_t gateway_id, uint8_t group_id); bool setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled); bool setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness, uint8_t color_mode, uint8_t data1, uint8_t data2, uint8_t data3); bool setSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name); bool deleteScene(uint8_t gateway_id, uint8_t scene_id); std::pair sceneMask(uint8_t gateway_id); bool setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool enabled); bool setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type, uint8_t target_value); bool setGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name); bool deleteGroup(uint8_t gateway_id, uint8_t group_id); std::pair 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 group_mask); bool setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id, std::optional level); bool setDaliSettings(uint8_t gateway_id, uint8_t short_address, std::optional 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 dtr0; std::optional dtr1; std::optional 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 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); void loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups); std::string readStringLocked(std::string_view key); bool writeStringLocked(std::string_view key, std::string_view value); bool eraseKeyLocked(std::string_view key); GatewayCacheConfig config_; GatewayCachePriorityMode priority_mode_; TaskHandle_t task_handle_{nullptr}; SemaphoreHandle_t lock_{nullptr}; nvs_handle_t storage_{0}; std::map scenes_; std::map groups_; std::map> dali_states_; std::map> dali_group_status_; std::map dali_broadcast_status_; std::map dtr_states_; std::map channel_flags_; uint32_t dali_runtime_revision_{0}; bool dirty_{false}; }; } // namespace gateway