Refactor GatewayController to integrate DaliGateway types

- Updated GatewayController to use DaliGateway types instead of GatewayCache types for channel flags, targets, and states.
- Modified methods to reflect changes in data structures, including flag checks and state management.
- Adjusted cache handling to utilize DaliGatewayCache for address and group management.
- Ensured compatibility with existing functionality while enhancing clarity and maintainability of the code.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-08-01 01:19:13 +08:00
parent 886bda43a4
commit 555c2ec5bd
10 changed files with 279 additions and 1439 deletions
+20 -12
View File
@@ -12,7 +12,8 @@ update as the project evolves:
- `gateway_core/`: boot profile and top-level role bootstrap.
- `dali/`: vendored ESP-IDF DALI HAL/backend reused from LuatOS, including native raw receive fan-out.
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
- `gateway_cache/`: ESP-IDF NVS adapter for the portable `dali_cpp` cache,
plus gateway-internal scene/group definitions.
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
- `openknx_idf/`: ESP-IDF port layer for the OpenKNX `gateway/knx` and `gateway/tpuart` submodules, including NVS-backed OpenKNX memory, development KNX security storage, ETS cEMI programming support, UDP multicast/unicast plumbing, and a native TP-UART interface without the Arduino framework.
- `gateway_modbus/`: gateway-owned Modbus TCP/RTU/ASCII config, generated DALI point tables, and provisioned Modbus model override dispatch.
@@ -29,18 +30,25 @@ update as the project evolves:
## Gateway DALI cache
The gateway owns the shared DALI cache for multi-user deployments. App, BLE,
IP, Modbus, BACnet, KNX, and local control paths should treat the gateway cache
as the shared read surface instead of maintaining separate app-side device-state
caches. Transparent/setup raw forwarding paths remain bypass-oriented, but raw
DALI bus observation still feeds the passive decoder when frames are visible to
the gateway.
`dali_cpp::DaliGatewayCache` owns the shared DALI cache and is the authoritative
read surface for App, BLE, IP, Modbus, BACnet, KNX, and local control paths. It
interprets direct, group, broadcast, scene, DTR, and settings commands; owns
presence and reconciliation state; versions persisted address snapshots; and
emits platform-neutral `DaliGatewayStatusUpdate` callbacks.
`gateway_cache` stores internal scene/group data and per-short-address DALI
state. Device settings, group masks, scene levels, known flags, and the last
runtime status are batched to NVS using `GATEWAY_CACHE_FLUSH_INTERVAL_MS`, which
defaults to 10000 ms. Runtime status loaded from disk is marked stale until the
gateway observes a bus command or the background refresher verifies it again.
The ESP-IDF `gateway_cache` component no longer contains DALI cache behavior.
It binds the portable cache persistence callbacks to NVS and continues to own
only gateway-internal scene/group definitions. Device settings, group masks,
scene levels, known flags, and the last runtime status are batched through the
portable cache using `GATEWAY_CACHE_FLUSH_INTERVAL_MS`, which defaults to
10000 ms. Runtime status loaded from disk is marked stale until a bus command
or the background refresher verifies it again.
Protocol adapters register for semantic status updates instead of decoding raw
DALI commands independently. The current KNX adapter maps the callback target,
status, and affected short-address list into its group-object status updates.
Transparent/setup raw forwarding paths remain bypass-oriented, while visible
raw DALI bus traffic still feeds the portable cache.
`GATEWAY_CACHE_REFRESH_INTERVAL_MS` defaults to 120000 ms. When nonzero, the
controller maintenance loop refreshes direct short-address actual levels one
+12 -11
View File
@@ -511,11 +511,11 @@ constexpr bool kCacheFullStateMirrorEnabled = false;
#endif
#ifdef CONFIG_GATEWAY_CACHE_LOCAL_GATEWAY_FIRST
constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
gateway::GatewayCachePriorityMode::kLocalGatewayFirst;
constexpr DaliGatewayCachePriorityMode kCachePriorityMode =
DaliGatewayCachePriorityMode::localGatewayFirst;
#else
constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
gateway::GatewayCachePriorityMode::kOutsideBusFirst;
constexpr DaliGatewayCachePriorityMode kCachePriorityMode =
DaliGatewayCachePriorityMode::outsideBusFirst;
#endif
#ifdef CONFIG_GATEWAY_DALI_103_APPLICATION_CONTROLLER_ENABLED
@@ -1097,14 +1097,15 @@ extern "C" void app_main(void) {
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.refresh_interval_ms =
static_cast<uint32_t>(CONFIG_GATEWAY_CACHE_REFRESH_INTERVAL_MS);
cache_config.default_priority_mode = kCachePriorityMode;
s_cache = std::make_unique<gateway::GatewayCache>(cache_config);
const bool reconciliation_enabled =
cache_config.cache_enabled && kCacheReconciliationEnabled;
s_dali_domain->gatewayCache().configure(
DaliGatewayCacheConfig{cache_config.cache_enabled, reconciliation_enabled,
reconciliation_enabled && kCacheFullStateMirrorEnabled,
kCachePriorityMode});
s_cache = std::make_unique<gateway::GatewayCache>(s_dali_domain->gatewayCache(),
cache_config);
ESP_ERROR_CHECK(s_cache->start());
gateway::GatewayControllerConfig controller_config;
@@ -12,6 +12,7 @@
#include <vector>
#include "esp_err.h"
#include "dali_gateway.hpp"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
@@ -137,6 +138,8 @@ class DaliDomainService {
void setApplicationControllerIdentitySink(ApplicationControllerIdentitySink sink);
void setApplicationControllerShortAddressProvider(ApplicationControllerShortAddressProvider provider);
void setApplicationControllerShortAddressSink(ApplicationControllerShortAddressSink sink);
DaliGatewayCache& gatewayCache();
const DaliGatewayCache& gatewayCache() const;
bool resetBus(uint8_t gateway_id) const;
bool pulseBusLow(uint8_t gateway_id, uint32_t duration_ms) const;
@@ -263,6 +266,7 @@ class DaliDomainService {
ApplicationControllerIdentitySink application_controller_identity_sink_;
ApplicationControllerShortAddressProvider application_controller_short_address_provider_;
ApplicationControllerShortAddressSink application_controller_short_address_sink_;
DaliGatewayCache gateway_cache_;
QueueHandle_t raw_frame_dispatch_queue_{nullptr};
TaskHandle_t raw_frame_dispatch_task_handle_{nullptr};
TaskHandle_t raw_frame_task_handle_{nullptr};
@@ -481,6 +481,14 @@ DaliDomainService::~DaliDomainService() {
}
}
DaliGatewayCache& DaliDomainService::gatewayCache() {
return gateway_cache_;
}
const DaliGatewayCache& DaliDomainService::gatewayCache() const {
return gateway_cache_;
}
bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) {
if (!hooks.send) {
return false;
@@ -10,6 +10,7 @@
#include <string_view>
#include <vector>
#include "dali_gateway.hpp"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -19,7 +20,6 @@
namespace gateway {
class DaliDomainService;
struct DaliRawFrame;
class GatewayCache;
struct GatewayBridgeHttpResponse {
@@ -86,7 +86,7 @@ class GatewayBridgeService {
size_t len);
DaliBridgeResult routeKnxGroupObjectWrite(uint16_t group_object_number,
const uint8_t* data, size_t len);
void handleDaliRawFrame(const DaliRawFrame& frame);
void handleDaliStatusUpdate(const DaliGatewayStatusUpdate& update);
void collectUsedRuntimeResources(uint8_t except_gateway_id,
std::set<uint16_t>* modbus_tcp_ports,
std::set<uint16_t>* knx_udp_ports,
+62 -115
View File
@@ -103,11 +103,6 @@ struct BridgeDiscoveryEntry {
DaliDomainSnapshot discovery;
};
struct DaliKnxStatusUpdate {
GatewayKnxDaliTarget target;
uint8_t actual_level{0};
};
using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>;
extern "C" uint8_t knx_platform_copy_security_failures(uint8_t* counters, size_t countersLen,
@@ -472,54 +467,6 @@ bool ValidDaliAddress(int address) {
return address >= 0 && address <= 127;
}
std::optional<GatewayKnxDaliTarget> DecodeKnxDaliTarget(uint8_t raw_addr) {
if (raw_addr <= 0x7F) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
static_cast<int>(raw_addr >> 1)};
}
if (raw_addr >= kDaliGroupRawMin && raw_addr <= kDaliGroupRawMax) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
static_cast<int>((raw_addr - kDaliGroupRawMin) >> 1)};
}
if (raw_addr == 0xFE || raw_addr == 0xFF) {
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kBroadcast, 127};
}
return std::nullopt;
}
std::optional<DaliKnxStatusUpdate> DecodeDaliKnxStatusUpdate(const DaliRawFrame& frame) {
if (frame.data.size() != 2 && frame.data.size() != 3) {
return std::nullopt;
}
uint8_t raw_addr = 0;
uint8_t command = 0;
if (frame.data.size() == 2) {
raw_addr = frame.data[0];
command = frame.data[1];
if (raw_addr == 0xBE) {
return std::nullopt;
}
} else {
raw_addr = frame.data[1];
command = frame.data[2];
}
auto target = DecodeKnxDaliTarget(raw_addr);
if (!target.has_value()) {
return std::nullopt;
}
if ((raw_addr & 0x01U) == 0) {
if (command > 254) {
return std::nullopt;
}
return DaliKnxStatusUpdate{*target, command};
}
if (command == kDaliCmdOff || command == kDaliCmdRecallMax) {
return DaliKnxStatusUpdate{*target,
static_cast<uint8_t>(command == kDaliCmdOff ? 0 : 254)};
}
return std::nullopt;
}
bool ValidShortAddress(int address) {
return address >= 0 && address <= kMaxDaliShortAddress;
}
@@ -1583,6 +1530,7 @@ struct GatewayBridgeService::ChannelRuntime {
GatewayBridgeServiceConfig service_config)
: service(service),
domain(domain),
dali_cache(domain.gatewayCache()),
cache(cache),
channel(std::move(channel)),
service_config(service_config),
@@ -1603,6 +1551,7 @@ struct GatewayBridgeService::ChannelRuntime {
GatewayBridgeService& service;
DaliDomainService& domain;
DaliGatewayCache& dali_cache;
GatewayCache& cache;
DaliChannelInfo channel;
GatewayBridgeServiceConfig service_config;
@@ -3141,7 +3090,7 @@ struct GatewayBridgeService::ChannelRuntime {
}
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
const auto state = cache.daliAddressState(channel.gateway_id,
const auto state = dali_cache.addressState(channel.gateway_id,
static_cast<uint8_t>(point.short_address));
switch (point.generated_kind) {
case GatewayModbusGeneratedKind::kShortDiscovered:
@@ -3159,11 +3108,11 @@ struct GatewayBridgeService::ChannelRuntime {
case GatewayModbusGeneratedKind::kShortSupportsDt8:
return discovery != nullptr && SnapshotHasDeviceType(discovery->discovery, 8);
case GatewayModbusGeneratedKind::kShortGroupMaskKnown:
return state.group_mask_known;
return state.groupMaskKnown;
case GatewayModbusGeneratedKind::kShortActualLevelKnown:
return state.status.actual_level.has_value();
return state.status.actualLevel.has_value();
case GatewayModbusGeneratedKind::kShortSceneKnown:
return state.status.scene_id.has_value();
return state.status.sceneID.has_value();
case GatewayModbusGeneratedKind::kShortSettingsKnown:
return state.settings.anyKnown();
case GatewayModbusGeneratedKind::kShortControlGearPresent:
@@ -3195,7 +3144,7 @@ struct GatewayBridgeService::ChannelRuntime {
}
const auto* discovery = findDiscoveryEntryLocked(point.short_address);
const auto state = cache.daliAddressState(channel.gateway_id,
const auto state = dali_cache.addressState(channel.gateway_id,
static_cast<uint8_t>(point.short_address));
switch (point.generated_kind) {
case GatewayModbusGeneratedKind::kShortInventoryState:
@@ -3216,12 +3165,12 @@ struct GatewayBridgeService::ChannelRuntime {
return discovery == nullptr ? kModbusUnknownRegister : DeviceTypeMask(discovery->discovery);
case GatewayModbusGeneratedKind::kShortBrightness:
case GatewayModbusGeneratedKind::kShortActualLevel:
return state.status.actual_level.has_value()
? static_cast<uint16_t>(state.status.actual_level.value())
return state.status.actualLevel.has_value()
? static_cast<uint16_t>(state.status.actualLevel.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortSceneId:
return state.status.scene_id.has_value()
? static_cast<uint16_t>(state.status.scene_id.value())
return state.status.sceneID.has_value()
? static_cast<uint16_t>(state.status.sceneID.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortRawStatus: {
const auto snapshot = diagnosticSnapshotLocked(point.short_address, "base_status");
@@ -3233,30 +3182,30 @@ struct GatewayBridgeService::ChannelRuntime {
return kModbusUnknownRegister;
}
case GatewayModbusGeneratedKind::kShortGroupMask:
return state.group_mask_known ? state.group_mask : kModbusUnknownRegister;
return state.groupMaskKnown ? state.groupMask : kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
return state.settings.power_on_level.has_value()
? static_cast<uint16_t>(state.settings.power_on_level.value())
return state.settings.powerOnLevel.has_value()
? static_cast<uint16_t>(state.settings.powerOnLevel.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortSystemFailureLevel:
return state.settings.system_failure_level.has_value()
? static_cast<uint16_t>(state.settings.system_failure_level.value())
return state.settings.systemFailureLevel.has_value()
? static_cast<uint16_t>(state.settings.systemFailureLevel.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortMinLevel:
return state.settings.min_level.has_value()
? static_cast<uint16_t>(state.settings.min_level.value())
return state.settings.minLevel.has_value()
? static_cast<uint16_t>(state.settings.minLevel.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortMaxLevel:
return state.settings.max_level.has_value()
? static_cast<uint16_t>(state.settings.max_level.value())
return state.settings.maxLevel.has_value()
? static_cast<uint16_t>(state.settings.maxLevel.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortFadeTime:
return state.settings.fade_time.has_value()
? static_cast<uint16_t>(state.settings.fade_time.value())
return state.settings.fadeTime.has_value()
? static_cast<uint16_t>(state.settings.fadeTime.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortFadeRate:
return state.settings.fade_rate.has_value()
? static_cast<uint16_t>(state.settings.fade_rate.value())
return state.settings.fadeRate.has_value()
? static_cast<uint16_t>(state.settings.fadeRate.value())
: kModbusUnknownRegister;
case GatewayModbusGeneratedKind::kShortColorTemperature:
return kModbusUnknownRegister;
@@ -3297,7 +3246,7 @@ struct GatewayBridgeService::ChannelRuntime {
return false;
}
if (sent) {
cache.mirrorDaliCommand(channel.gateway_id, raw_command_address, mirrored_command);
dali_cache.mirrorForwardFrame(channel.gateway_id, raw_command_address, mirrored_command);
}
return sent;
}
@@ -3315,7 +3264,7 @@ struct GatewayBridgeService::ChannelRuntime {
domain.markHostCommandFrame(channel.gateway_id, RawArcAddressFromDec(point.short_address),
static_cast<uint8_t>(value));
if (domain.setBright(channel.gateway_id, point.short_address, value)) {
cache.mirrorDaliCommand(channel.gateway_id, RawArcAddressFromDec(point.short_address),
dali_cache.mirrorForwardFrame(channel.gateway_id, RawArcAddressFromDec(point.short_address),
static_cast<uint8_t>(value));
return true;
}
@@ -3326,7 +3275,7 @@ struct GatewayBridgeService::ChannelRuntime {
case GatewayModbusGeneratedKind::kShortGroupMask:
domain.markHostActivity(channel.gateway_id);
if (domain.applyGroupMask(channel.gateway_id, point.short_address, value)) {
cache.setDaliGroupMask(channel.gateway_id, static_cast<uint8_t>(point.short_address),
dali_cache.setGroupMask(channel.gateway_id, static_cast<uint8_t>(point.short_address),
value);
return true;
}
@@ -3340,40 +3289,40 @@ struct GatewayBridgeService::ChannelRuntime {
if (value > 255) {
return false;
}
auto current = cache.daliAddressState(channel.gateway_id,
auto current = dali_cache.addressState(channel.gateway_id,
static_cast<uint8_t>(point.short_address)).settings;
switch (point.generated_kind) {
case GatewayModbusGeneratedKind::kShortPowerOnLevel:
current.power_on_level = static_cast<uint8_t>(value);
current.powerOnLevel = static_cast<uint8_t>(value);
break;
case GatewayModbusGeneratedKind::kShortSystemFailureLevel:
current.system_failure_level = static_cast<uint8_t>(value);
current.systemFailureLevel = static_cast<uint8_t>(value);
break;
case GatewayModbusGeneratedKind::kShortMinLevel:
current.min_level = static_cast<uint8_t>(value);
current.minLevel = static_cast<uint8_t>(value);
break;
case GatewayModbusGeneratedKind::kShortMaxLevel:
current.max_level = static_cast<uint8_t>(value);
current.maxLevel = static_cast<uint8_t>(value);
break;
case GatewayModbusGeneratedKind::kShortFadeTime:
current.fade_time = static_cast<uint8_t>(value);
current.fadeTime = static_cast<uint8_t>(value);
break;
case GatewayModbusGeneratedKind::kShortFadeRate:
current.fade_rate = static_cast<uint8_t>(value);
current.fadeRate = static_cast<uint8_t>(value);
break;
default:
break;
}
DaliAddressSettingsSnapshot domain_settings;
domain_settings.power_on_level = current.power_on_level;
domain_settings.system_failure_level = current.system_failure_level;
domain_settings.min_level = current.min_level;
domain_settings.max_level = current.max_level;
domain_settings.fade_time = current.fade_time;
domain_settings.fade_rate = current.fade_rate;
domain_settings.power_on_level = current.powerOnLevel;
domain_settings.system_failure_level = current.systemFailureLevel;
domain_settings.min_level = current.minLevel;
domain_settings.max_level = current.maxLevel;
domain_settings.fade_time = current.fadeTime;
domain_settings.fade_rate = current.fadeRate;
domain.markHostActivity(channel.gateway_id);
if (domain.applyAddressSettings(channel.gateway_id, point.short_address, domain_settings)) {
cache.setDaliSettings(channel.gateway_id, static_cast<uint8_t>(point.short_address),
dali_cache.setSettings(channel.gateway_id, static_cast<uint8_t>(point.short_address),
current);
return true;
}
@@ -4318,7 +4267,9 @@ GatewayBridgeService::GatewayBridgeService(DaliDomainService& dali_domain,
GatewayBridgeServiceConfig config)
: dali_domain_(dali_domain), cache_(cache), config_(config) {}
GatewayBridgeService::~GatewayBridgeService() = default;
GatewayBridgeService::~GatewayBridgeService() {
dali_domain_.gatewayCache().setStatusUpdateCallback({});
}
esp_err_t GatewayBridgeService::start() {
ConfigureDaliCppLogging();
@@ -4343,8 +4294,8 @@ esp_err_t GatewayBridgeService::start() {
runtimes_.push_back(std::move(runtime));
}
dali_domain_.addRawFrameSink(
[this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
dali_domain_.gatewayCache().setStatusUpdateCallback(
[this](const DaliGatewayStatusUpdate& update) { handleDaliStatusUpdate(update); });
std::set<int> used_serial_uarts;
if (config_.modbus_enabled && config_.modbus_startup_enabled) {
@@ -4583,14 +4534,13 @@ DaliBridgeResult GatewayBridgeService::routeKnxGroupObjectWrite(uint16_t group_o
return runtime->knx->handleGroupObjectWrite(group_object_number, data, len);
}
void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
const auto update = DecodeDaliKnxStatusUpdate(frame);
if (!update.has_value()) {
void GatewayBridgeService::handleDaliStatusUpdate(const DaliGatewayStatusUpdate& update) {
if (!update.status.actualLevel.has_value()) {
return;
}
auto* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
: selectKnxEndpointRuntime();
if (owner == nullptr || owner->channel.gateway_id != frame.gateway_id) {
if (owner == nullptr || owner->channel.gateway_id != update.channel) {
return;
}
LockGuard guard(owner->lock);
@@ -4600,32 +4550,29 @@ void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
auto publish_target = [&](GatewayKnxDaliTargetKind kind, int address) {
owner->knx_router->publishDaliStatus(GatewayKnxDaliTarget{kind, address},
update->actual_level);
*update.status.actualLevel);
};
publish_target(update->target.kind, update->target.address);
GatewayKnxDaliTargetKind target_kind = GatewayKnxDaliTargetKind::kShortAddress;
int target_address = update.target.value;
if (update.target.kind == DaliGatewayTargetKind::group) {
target_kind = GatewayKnxDaliTargetKind::kGroup;
} else if (update.target.kind == DaliGatewayTargetKind::broadcast) {
target_kind = GatewayKnxDaliTargetKind::kBroadcast;
target_address = 127;
}
publish_target(target_kind, target_address);
if (update->target.kind == GatewayKnxDaliTargetKind::kGroup &&
update->target.address >= 0 && update->target.address < 16) {
const uint16_t group_bit = static_cast<uint16_t>(1U << update->target.address);
for (int short_address = 0; short_address <= kMaxDaliShortAddress; ++short_address) {
const auto state = owner->cache.daliAddressState(frame.gateway_id,
static_cast<uint8_t>(short_address));
if (!state.group_mask_known || (state.group_mask & group_bit) == 0) {
continue;
}
if (update.target.kind != DaliGatewayTargetKind::shortAddress) {
for (const uint8_t short_address : update.affectedShortAddresses) {
publish_target(GatewayKnxDaliTargetKind::kShortAddress, short_address);
}
return;
}
if (update->target.kind == GatewayKnxDaliTargetKind::kBroadcast) {
if (update.target.kind == DaliGatewayTargetKind::broadcast) {
for (int group = 0; group < 16; ++group) {
publish_target(GatewayKnxDaliTargetKind::kGroup, group);
}
for (int short_address = 0; short_address <= kMaxDaliShortAddress; ++short_address) {
publish_target(GatewayKnxDaliTargetKind::kShortAddress, short_address);
}
}
}
@@ -12,88 +12,17 @@
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "nvs.h"
#include "dali_gateway.hpp"
class DaliGatewayCache;
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{10000};
uint32_t refresh_interval_ms{120000};
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,
};
enum class GatewayCacheDaliPresence : uint8_t {
kUnknown = 0,
kOnline = 1,
kOffline = 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};
bool stale{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 {
@@ -118,11 +47,10 @@ class GatewayCache {
using SceneStore = std::array<SceneEntry, 16>;
using GroupStore = std::array<GroupEntry, 16>;
explicit GatewayCache(GatewayCacheConfig config = {});
GatewayCache(DaliGatewayCache& dali_cache, 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);
@@ -143,48 +71,10 @@ class GatewayCache {
bool deleteGroup(uint8_t gateway_id, uint8_t group_id);
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);
GatewayCacheDaliPresence daliAddressPresence(uint8_t gateway_id, uint8_t short_address);
void markDaliAddressPresence(uint8_t gateway_id, uint8_t short_address,
GatewayCacheDaliPresence presence);
std::optional<GatewayCacheDaliTarget> decodeDaliTarget(uint8_t raw_addr);
std::vector<uint8_t> reconciliationAddresses(
uint8_t gateway_id, std::optional<GatewayCacheDaliTarget> target);
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 setDaliActualLevel(uint8_t gateway_id, uint8_t short_address,
std::optional<uint8_t> level);
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 setCacheEnabled(bool enabled);
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();
@@ -193,63 +83,22 @@ class GatewayCache {
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 persistDaliAddressStateLocked(uint8_t gateway_id, uint8_t short_address,
const GatewayCacheDaliAddressState& state);
bool commitStorageLocked();
bool shouldTrackUpdateFlagsLocked() const;
uint32_t nextDaliRuntimeRevisionLocked();
void markDaliAddressPresenceLocked(uint8_t gateway_id, uint8_t short_address,
GatewayCacheDaliPresence presence);
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);
void loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups);
void loadDaliStateStoreLocked(uint8_t gateway_id,
std::array<GatewayCacheDaliAddressState, 64>& states);
std::string readStringLocked(std::string_view key);
bool writeStringLocked(std::string_view key, std::string_view value);
bool eraseKeyLocked(std::string_view key);
void syncDaliStatesFromCoreLocked(uint8_t gateway_id);
DaliGatewayCache& dali_cache_;
GatewayCacheConfig config_;
GatewayCachePriorityMode priority_mode_;
DaliGatewayCache dali_gateway_cache_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t lock_{nullptr};
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<GatewayCacheDaliPresence, 64>> dali_presence_;
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
@@ -10,6 +10,7 @@
#include <string_view>
#include <vector>
#include "dali_gateway.hpp"
#include "gateway_cache.hpp"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
@@ -148,9 +149,9 @@ class GatewayController {
kSettings = 3,
};
GatewayCacheChannelFlags flags{};
DaliGatewayChannelFlags flags{};
Phase phase{Phase::kReloadFlags};
std::optional<GatewayCacheDaliTarget> target;
std::optional<DaliGatewayTarget> target;
std::vector<uint8_t> addresses;
size_t address_index{0};
uint8_t scene_id{0};
@@ -210,7 +211,7 @@ class GatewayController {
void runOperationTask(GatewayOperationTaskContext* context);
void dispatchCommand(const std::vector<uint8_t>& command);
void scheduleReconciliation(uint8_t gateway_id,
std::optional<GatewayCacheDaliTarget> target = std::nullopt);
std::optional<DaliGatewayTarget> target = std::nullopt);
bool hasPendingReconciliation() const;
bool cacheRefreshEnabled() const;
bool cacheMaintenanceSnoozed(uint8_t gateway_id) const;
@@ -303,6 +304,7 @@ class GatewayController {
GatewayRuntime& runtime_;
DaliDomainService& dali_domain_;
DaliGatewayCache& dali_cache_;
GatewayCache& cache_;
GatewayBridgeService* bridge_service_{nullptr};
GatewayControllerConfig config_;
@@ -206,12 +206,12 @@ class LockGuard {
SemaphoreHandle_t lock_;
};
bool AnyFlagSet(const GatewayCacheChannelFlags& flags) {
return flags.need_update_group || flags.need_update_scene || flags.need_update_settings;
bool AnyFlagSet(const DaliGatewayChannelFlags& flags) {
return flags.needUpdateGroup || flags.needUpdateScene || flags.needUpdateSettings;
}
bool SameTarget(const std::optional<GatewayCacheDaliTarget>& lhs,
const std::optional<GatewayCacheDaliTarget>& rhs) {
bool SameTarget(const std::optional<DaliGatewayTarget>& lhs,
const std::optional<DaliGatewayTarget>& rhs) {
if (lhs.has_value() != rhs.has_value()) {
return false;
}
@@ -453,15 +453,15 @@ uint8_t CacheByte(std::optional<uint8_t> value) {
return value.value_or(0xFF);
}
uint16_t CacheRuntimeFlags(const GatewayCacheDaliRuntimeStatus& status) {
uint16_t CacheRuntimeFlags(const DaliGatewayRuntimeStatus& status) {
uint16_t flags = 0;
if (status.actual_level.has_value()) {
if (status.actualLevel.has_value()) {
flags |= kCacheFlagActualKnown;
}
if (status.scene_id.has_value()) {
if (status.sceneID.has_value()) {
flags |= kCacheFlagSceneKnown;
}
if (status.use_min_level) {
if (status.useMinLevel) {
flags |= kCacheFlagUseMinLevel;
}
if (status.stale) {
@@ -470,36 +470,36 @@ uint16_t CacheRuntimeFlags(const GatewayCacheDaliRuntimeStatus& status) {
return flags;
}
uint16_t CacheAddressFlags(const GatewayCacheDaliAddressState& state) {
uint16_t CacheAddressFlags(const DaliGatewayAddressState& state) {
uint16_t flags = CacheRuntimeFlags(state.status);
if (state.group_mask_known) {
if (state.groupMaskKnown) {
flags |= kCacheFlagGroupMaskKnown;
}
if (state.settings.power_on_level.has_value()) {
if (state.settings.powerOnLevel.has_value()) {
flags |= kCacheFlagPowerOnKnown;
}
if (state.settings.system_failure_level.has_value()) {
if (state.settings.systemFailureLevel.has_value()) {
flags |= kCacheFlagSystemFailureKnown;
}
if (state.settings.min_level.has_value()) {
if (state.settings.minLevel.has_value()) {
flags |= kCacheFlagMinKnown;
}
if (state.settings.max_level.has_value()) {
if (state.settings.maxLevel.has_value()) {
flags |= kCacheFlagMaxKnown;
}
if (state.settings.fade_time.has_value()) {
if (state.settings.fadeTime.has_value()) {
flags |= kCacheFlagFadeTimeKnown;
}
if (state.settings.fade_rate.has_value()) {
if (state.settings.fadeRate.has_value()) {
flags |= kCacheFlagFadeRateKnown;
}
return flags;
}
uint16_t CacheSceneKnownMask(const GatewayCacheDaliAddressState& state) {
uint16_t CacheSceneKnownMask(const DaliGatewayAddressState& state) {
uint16_t mask = 0;
for (size_t index = 0; index < state.scene_levels.size(); ++index) {
if (state.scene_levels[index].has_value()) {
for (size_t index = 0; index < state.sceneLevels.size(); ++index) {
if (state.sceneLevels[index].has_value()) {
mask |= static_cast<uint16_t>(1U << index);
}
}
@@ -546,6 +546,7 @@ GatewayController::GatewayController(GatewayRuntime& runtime, DaliDomainService&
GatewayCache& cache, GatewayControllerConfig config)
: runtime_(runtime),
dali_domain_(dali_domain),
dali_cache_(dali_domain.gatewayCache()),
cache_(cache),
config_(config),
maintenance_lock_(xSemaphoreCreateMutex()),
@@ -577,7 +578,7 @@ esp_err_t GatewayController::start() {
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
for (const auto& channel : dali_domain_.channelInfo()) {
cache_.preloadChannel(channel.gateway_id);
dali_cache_.preloadChannel(channel.gateway_id);
dali_domain_.resetBus(channel.gateway_id);
publishPayload(channel.gateway_id, {0x02, channel.gateway_id, 0x88});
}
@@ -746,19 +747,23 @@ bool GatewayController::setBleEnabled(bool enabled) {
}
bool GatewayController::cacheEnabled() const {
return cache_.cacheEnabled();
return dali_cache_.enabled();
}
bool GatewayController::setCacheEnabled(bool enabled) {
if (!config_.cache_supported || !runtime_.setCacheEnabled(enabled)) {
return false;
}
if (cache_.setCacheEnabled(enabled)) {
if (!enabled && !cache_.setCacheEnabled(false)) {
runtime_.setCacheEnabled(dali_cache_.enabled());
return false;
}
if (dali_cache_.setEnabled(enabled) && (enabled ? cache_.setCacheEnabled(true) : true)) {
return true;
}
// Keep the persisted setting aligned with the live cache when the worker
// cannot be started or stopped.
runtime_.setCacheEnabled(cache_.cacheEnabled());
runtime_.setCacheEnabled(dali_cache_.enabled());
return false;
}
@@ -880,7 +885,7 @@ bool GatewayController::setChannelGatewayId(uint8_t channel_index, uint8_t gatew
dali_domain_.updateChannelGatewayId(channel_index, current_gateway_id.value());
return false;
}
cache_.preloadChannel(gateway_id);
dali_cache_.preloadChannel(gateway_id);
{
LockGuard guard(maintenance_lock_);
reconciliation_jobs_.erase(current_gateway_id.value());
@@ -952,13 +957,13 @@ void GatewayController::taskLoop() {
}
void GatewayController::scheduleReconciliation(uint8_t gateway_id,
std::optional<GatewayCacheDaliTarget> target) {
if (!cache_.reconciliationEnabled()) {
std::optional<DaliGatewayTarget> target) {
if (!dali_cache_.reconciliationEnabled()) {
return;
}
auto flags = cache_.pendingChannelFlags(gateway_id);
if (cache_.fullStateMirrorEnabled() && AnyFlagSet(flags)) {
auto flags = dali_cache_.pendingChannelFlags(gateway_id);
if (dali_cache_.fullStateMirrorEnabled() && AnyFlagSet(flags)) {
flags = {true, true, true};
}
if (!AnyFlagSet(flags)) {
@@ -990,7 +995,7 @@ bool GatewayController::hasPendingReconciliation() const {
}
bool GatewayController::cacheRefreshEnabled() const {
return config_.cache_supported && cache_.cacheEnabled() &&
return config_.cache_supported && dali_cache_.enabled() &&
config_.cache_refresh_interval_ms > 0;
}
@@ -1000,7 +1005,7 @@ bool GatewayController::cacheMaintenanceSnoozed(uint8_t gateway_id) const {
}
bool GatewayController::runMaintenanceStep() {
if (cache_.reconciliationEnabled()) {
if (dali_cache_.reconciliationEnabled()) {
bool has_job = false;
uint8_t gateway_id = 0;
ReconciliationJob job;
@@ -1045,24 +1050,24 @@ bool GatewayController::runMaintenanceStep() {
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 = dali_cache_.pendingChannelFlags(gateway_id);
if (dali_cache_.fullStateMirrorEnabled() && AnyFlagSet(job.flags)) {
job.flags = {true, true, true};
}
if (!AnyFlagSet(job.flags)) {
return false;
}
job.addresses = cache_.reconciliationAddresses(gateway_id, job.target);
job.addresses = dali_cache_.reconciliationAddresses(gateway_id, job.target);
job.address_index = 0;
job.scene_id = 0;
if (job.addresses.empty()) {
cache_.clearChannelFlagsIfMatched(gateway_id, job.flags);
dali_cache_.clearChannelFlagsIfMatched(gateway_id, job.flags);
return false;
}
if (job.flags.need_update_group) {
if (job.flags.needUpdateGroup) {
job.phase = ReconciliationJob::Phase::kGroups;
} else if (job.flags.need_update_scene) {
} else if (job.flags.needUpdateScene) {
job.phase = ReconciliationJob::Phase::kScenes;
} else {
job.phase = ReconciliationJob::Phase::kSettings;
@@ -1074,11 +1079,11 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
reconcileGroupStep(gateway_id, job.addresses[job.address_index++]);
if (job.address_index >= job.addresses.size()) {
job.address_index = 0;
if (job.flags.need_update_scene) {
if (job.flags.needUpdateScene) {
job.phase = ReconciliationJob::Phase::kScenes;
} else if (job.flags.need_update_settings) {
} else if (job.flags.needUpdateSettings) {
job.phase = ReconciliationJob::Phase::kSettings;
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
} else if (!dali_cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
job.phase = ReconciliationJob::Phase::kReloadFlags;
} else {
return false;
@@ -1087,8 +1092,8 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
return true;
case ReconciliationJob::Phase::kScenes: {
const uint8_t short_address = job.addresses[job.address_index];
if (cache_.daliAddressPresence(gateway_id, short_address) ==
GatewayCacheDaliPresence::kOffline) {
if (dali_cache_.addressPresence(gateway_id, short_address) ==
DaliGatewayPresence::offline) {
job.scene_id = 0;
++job.address_index;
} else {
@@ -1101,9 +1106,9 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
}
if (job.address_index >= job.addresses.size()) {
job.address_index = 0;
if (job.flags.need_update_settings) {
if (job.flags.needUpdateSettings) {
job.phase = ReconciliationJob::Phase::kSettings;
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
} else if (!dali_cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
job.phase = ReconciliationJob::Phase::kReloadFlags;
} else {
return false;
@@ -1113,13 +1118,13 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
}
case ReconciliationJob::Phase::kSettings: {
const uint8_t short_address = job.addresses[job.address_index++];
if (cache_.daliAddressPresence(gateway_id, short_address) !=
GatewayCacheDaliPresence::kOffline) {
if (dali_cache_.addressPresence(gateway_id, short_address) !=
DaliGatewayPresence::offline) {
reconcileSettingsStep(gateway_id, short_address);
}
if (job.address_index >= job.addresses.size()) {
job.address_index = 0;
if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
if (!dali_cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
job.phase = ReconciliationJob::Phase::kReloadFlags;
} else {
return false;
@@ -1170,8 +1175,8 @@ bool GatewayController::runCacheRefreshStep() {
}
};
if (cache_.daliAddressPresence(channel.gateway_id, job.short_address) ==
GatewayCacheDaliPresence::kOffline) {
if (dali_cache_.addressPresence(channel.gateway_id, job.short_address) ==
DaliGatewayPresence::offline) {
advance_job();
return true;
}
@@ -1179,11 +1184,11 @@ bool GatewayController::runCacheRefreshStep() {
maintenance_activity_gateway_.store(channel.gateway_id);
const auto actual_level = dali_domain_.queryActualLevel(channel.gateway_id, job.short_address);
maintenance_activity_gateway_.store(-1);
cache_.markDaliAddressPresence(channel.gateway_id, job.short_address,
dali_cache_.markAddressPresence(channel.gateway_id, job.short_address,
actual_level.has_value()
? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliActualLevel(channel.gateway_id, job.short_address, actual_level);
? DaliGatewayPresence::online
: DaliGatewayPresence::offline);
dali_cache_.setActualLevel(channel.gateway_id, job.short_address, actual_level);
advance_job();
return true;
}
@@ -1192,19 +1197,19 @@ bool GatewayController::runCacheRefreshStep() {
}
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);
const auto policy = dali_cache_.priorityMode();
const auto state = dali_cache_.addressState(gateway_id, short_address);
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst && state.group_mask_known) {
if (policy == DaliGatewayCachePriorityMode::localGatewayFirst && state.groupMaskKnown) {
maintenance_activity_gateway_.store(gateway_id);
const bool applied = dali_domain_.applyGroupMask(gateway_id, short_address, state.group_mask);
const bool applied = dali_domain_.applyGroupMask(gateway_id, short_address, state.groupMask);
maintenance_activity_gateway_.store(-1);
const auto verified_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
cache_.markDaliAddressPresence(gateway_id, short_address,
dali_cache_.markAddressPresence(gateway_id, short_address,
verified_mask.has_value()
? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliGroupMask(gateway_id, short_address, verified_mask);
? DaliGatewayPresence::online
: DaliGatewayPresence::offline);
dali_cache_.setGroupMask(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);
}
@@ -1212,62 +1217,62 @@ void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_add
}
const auto group_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
cache_.markDaliAddressPresence(gateway_id, short_address,
group_mask.has_value() ? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliGroupMask(gateway_id, short_address, group_mask);
dali_cache_.markAddressPresence(gateway_id, short_address,
group_mask.has_value() ? DaliGatewayPresence::online
: DaliGatewayPresence::offline);
dali_cache_.setGroupMask(gateway_id, short_address, group_mask);
}
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);
const auto policy = dali_cache_.priorityMode();
const auto state = dali_cache_.addressState(gateway_id, short_address);
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst &&
state.scene_levels[scene_id].has_value()) {
if (policy == DaliGatewayCachePriorityMode::localGatewayFirst &&
state.sceneLevels[scene_id].has_value()) {
maintenance_activity_gateway_.store(gateway_id);
dali_domain_.applySceneLevel(gateway_id, short_address, scene_id, state.scene_levels[scene_id]);
dali_domain_.applySceneLevel(gateway_id, short_address, scene_id, state.sceneLevels[scene_id]);
maintenance_activity_gateway_.store(-1);
}
const auto level = dali_domain_.querySceneLevel(gateway_id, short_address, scene_id);
cache_.markDaliAddressPresence(gateway_id, short_address,
level.has_value() ? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
cache_.setDaliSceneLevel(gateway_id, short_address, scene_id, level);
dali_cache_.markAddressPresence(gateway_id, short_address,
level.has_value() ? DaliGatewayPresence::online
: DaliGatewayPresence::offline);
dali_cache_.setSceneLevel(gateway_id, short_address, scene_id, level);
}
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);
const auto policy = dali_cache_.priorityMode();
const auto state = dali_cache_.addressState(gateway_id, short_address);
if (policy == GatewayCachePriorityMode::kLocalGatewayFirst && state.settings.anyKnown()) {
if (policy == DaliGatewayCachePriorityMode::localGatewayFirst && 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,
state.settings.powerOnLevel,
state.settings.systemFailureLevel,
state.settings.minLevel,
state.settings.maxLevel,
state.settings.fadeTime,
state.settings.fadeRate,
});
maintenance_activity_gateway_.store(-1);
}
const auto settings = dali_domain_.queryAddressSettings(gateway_id, short_address);
cache_.markDaliAddressPresence(gateway_id, short_address,
settings.has_value() ? GatewayCacheDaliPresence::kOnline
: GatewayCacheDaliPresence::kOffline);
dali_cache_.markAddressPresence(gateway_id, short_address,
settings.has_value() ? DaliGatewayPresence::online
: DaliGatewayPresence::offline);
if (settings.has_value()) {
cache_.setDaliSettings(gateway_id, short_address,
GatewayCacheDaliSettingsSnapshot{settings->power_on_level,
dali_cache_.setSettings(gateway_id, short_address,
DaliGatewaySettingsSnapshot{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);
dali_cache_.setSettings(gateway_id, short_address, std::nullopt);
}
}
@@ -1932,12 +1937,13 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
dali_domain_.hasRecentHostActivity(frame.gateway_id, config_.cache_host_echo_ms);
const bool local_activity = maintenance_activity || runtime_.hasActiveCommand(frame.gateway_id) ||
host_echo_activity || dali_domain_.isAllocAddr(frame.gateway_id);
const bool flagged = cache_.observeDaliCommand(frame.gateway_id, addr, data,
local_activity
? GatewayCacheRawFrameOrigin::kLocalGateway
: GatewayCacheRawFrameOrigin::kOutsideBus);
const bool flagged =
local_activity
? (dali_cache_.mirrorForwardFrame(frame.gateway_id, addr, data), false)
: dali_cache_.observeForwardFrame(frame.gateway_id, addr, data,
DaliGatewayFrameOrigin::outsideBus);
if (flagged) {
scheduleReconciliation(frame.gateway_id, cache_.decodeDaliTarget(addr));
scheduleReconciliation(frame.gateway_id, dali_cache_.decodeTarget(addr));
}
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) || maintenance_activity ||
@@ -1952,7 +1958,7 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
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);
dali_cache_.mirrorForwardFrame(gateway_id, raw_addr, command);
}
return sent;
}
@@ -1961,7 +1967,7 @@ 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);
dali_cache_.mirrorForwardFrame(gateway_id, raw_addr, command);
}
return sent;
}
@@ -1971,7 +1977,7 @@ bool GatewayController::setBrightAndMirror(uint8_t gateway_id, int dec_address,
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, level);
const bool sent = dali_domain_.setBright(gateway_id, dec_address, level);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, raw_addr, level);
dali_cache_.mirrorForwardFrame(gateway_id, raw_addr, level);
}
return sent;
}
@@ -1981,7 +1987,7 @@ bool GatewayController::offAndMirror(uint8_t gateway_id, int dec_address) {
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, kDaliCmdOff);
const bool sent = dali_domain_.off(gateway_id, dec_address);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, raw_addr, kDaliCmdOff);
dali_cache_.mirrorForwardFrame(gateway_id, raw_addr, kDaliCmdOff);
}
return sent;
}
@@ -1991,7 +1997,7 @@ bool GatewayController::onAndMirror(uint8_t gateway_id, int dec_address) {
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, kDaliCmdRecallMax);
const bool sent = dali_domain_.on(gateway_id, dec_address);
if (sent) {
cache_.mirrorDaliCommand(gateway_id, raw_addr, kDaliCmdRecallMax);
dali_cache_.mirrorForwardFrame(gateway_id, raw_addr, kDaliCmdRecallMax);
}
return sent;
}
@@ -2311,7 +2317,7 @@ void GatewayController::handleGatewaySerialCommand(uint8_t channel_number,
{channel_number, new_gateway_id});
return;
}
cache_.preloadChannel(new_gateway_id);
dali_cache_.preloadChannel(new_gateway_id);
reconciliation_jobs_.erase(old_gateway_id);
cache_refresh_jobs_.erase(old_gateway_id);
refreshRuntimeGatewayNames();
@@ -3393,11 +3399,11 @@ void GatewayController::handleGatewayCacheCommand(uint8_t gateway_id,
const std::vector<uint8_t>& command) {
const uint8_t op = command.size() > 4 ? command[4] : kGatewayCacheOpSummary;
const uint8_t arg = command.size() > 5 ? command[5] : 0;
const bool enabled = config_.cache_supported && cache_.cacheEnabled();
const bool enabled = config_.cache_supported && dali_cache_.enabled();
if (op == kGatewayCacheOpSummary) {
const uint8_t flags = static_cast<uint8_t>((config_.cache_supported ? 0x01 : 0x00) |
(cache_.cacheEnabled() ? 0x02 : 0x00) |
(dali_cache_.enabled() ? 0x02 : 0x00) |
(cacheRefreshEnabled() ? 0x04 : 0x00));
std::vector<uint8_t> payload{kGatewayCacheOpcode,
gateway_id,
@@ -3423,21 +3429,21 @@ void GatewayController::handleGatewayCacheCommand(uint8_t gateway_id,
kGatewayCacheStatusInvalidArgument, arg});
return;
}
const auto state = cache_.daliAddressState(gateway_id, arg);
const auto state = dali_cache_.addressState(gateway_id, arg);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
AppendLe16(payload, CacheAddressFlags(state));
payload.push_back(CacheByte(state.status.actual_level));
payload.push_back(CacheByte(state.status.scene_id));
AppendLe16(payload, state.group_mask_known ? state.group_mask : 0);
payload.push_back(CacheByte(state.settings.power_on_level));
payload.push_back(CacheByte(state.settings.system_failure_level));
payload.push_back(CacheByte(state.settings.min_level));
payload.push_back(CacheByte(state.settings.max_level));
payload.push_back(CacheByte(state.settings.fade_time));
payload.push_back(CacheByte(state.settings.fade_rate));
payload.push_back(CacheByte(state.status.actualLevel));
payload.push_back(CacheByte(state.status.sceneID));
AppendLe16(payload, state.groupMaskKnown ? state.groupMask : 0);
payload.push_back(CacheByte(state.settings.powerOnLevel));
payload.push_back(CacheByte(state.settings.systemFailureLevel));
payload.push_back(CacheByte(state.settings.minLevel));
payload.push_back(CacheByte(state.settings.maxLevel));
payload.push_back(CacheByte(state.settings.fadeTime));
payload.push_back(CacheByte(state.settings.fadeRate));
AppendLe32(payload, state.status.revision);
AppendLe16(payload, CacheSceneKnownMask(state));
for (const auto& level : state.scene_levels) {
for (const auto& level : state.sceneLevels) {
payload.push_back(CacheByte(level));
}
publishPayload(gateway_id, payload);
@@ -3450,22 +3456,22 @@ void GatewayController::handleGatewayCacheCommand(uint8_t gateway_id,
kGatewayCacheStatusInvalidArgument, arg});
return;
}
const auto status = cache_.daliGroupStatus(gateway_id, arg);
const auto status = dali_cache_.groupStatus(gateway_id, arg);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
AppendLe16(payload, CacheRuntimeFlags(status));
payload.push_back(CacheByte(status.actual_level));
payload.push_back(CacheByte(status.scene_id));
payload.push_back(CacheByte(status.actualLevel));
payload.push_back(CacheByte(status.sceneID));
AppendLe32(payload, status.revision);
publishPayload(gateway_id, payload);
return;
}
if (op == kGatewayCacheOpBroadcast) {
const auto status = cache_.daliBroadcastStatus(gateway_id);
const auto status = dali_cache_.broadcastStatus(gateway_id);
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, 0};
AppendLe16(payload, CacheRuntimeFlags(status));
payload.push_back(CacheByte(status.actual_level));
payload.push_back(CacheByte(status.scene_id));
payload.push_back(CacheByte(status.actualLevel));
payload.push_back(CacheByte(status.sceneID));
AppendLe32(payload, status.revision);
publishPayload(gateway_id, payload);
return;