feat(gateway): integrate DALI controller and enhance network service

- Updated `CMakeLists.txt` to require `dali_cpp` for the gateway cache component.
- Enhanced `GatewayCache` class to include DALI state synchronization and management, including new methods for handling DALI address states, settings, and runtime statuses.
- Removed legacy application controller handling from `GatewayController`, simplifying the frame handling logic.
- Introduced `identify` method in `GatewayNetworkService` for Part 103 identity indication, along with task management for LED signaling.
- Added methods in `GatewaySettingsStore` and `GatewayRuntime` for managing application controller short addresses, improving configuration handling.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-07-29 06:05:27 +08:00
parent 1e8bda56e0
commit 886bda43a4
14 changed files with 443 additions and 407 deletions
+14
View File
@@ -1471,6 +1471,20 @@ config GATEWAY_CHANNEL16_SERIAL_QUERY_TIMEOUT_MS
endmenu
menu "DALI Part 103 Application Controller"
config GATEWAY_DALI_103_APPLICATION_CONTROLLER_ENABLED
bool "Enable the gateway as a DALI Part 103 application controller"
default n
help
Makes each configured DALI channel expose the gateway as a logical
Part 103 control device. The dali_cpp application layer answers the
standard device queries, supports short-address commissioning, and
emits an identity action for the gateway adapter. The ESP-IDF domain
owns repeat-command timing and backward-frame transmission.
endmenu
menu "Gateway Cache"
config GATEWAY_CACHE_SUPPORTED
+35 -1
View File
@@ -18,6 +18,7 @@
#include <cstdio>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@@ -517,6 +518,12 @@ constexpr gateway::GatewayCachePriorityMode kCachePriorityMode =
gateway::GatewayCachePriorityMode::kOutsideBusFirst;
#endif
#ifdef CONFIG_GATEWAY_DALI_103_APPLICATION_CONTROLLER_ENABLED
constexpr bool kDali103ApplicationControllerEnabled = true;
#else
constexpr bool kDali103ApplicationControllerEnabled = false;
#endif
#if defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_RTU) || \
defined(CONFIG_GATEWAY_MODBUS_DEFAULT_TRANSPORT_ASCII)
constexpr bool kModbusDefaultSerialTransport = true;
@@ -1016,6 +1023,17 @@ extern "C" void app_main(void) {
ESP_ERROR_CHECK(ValidateChannelBindings() ? ESP_OK : ESP_ERR_INVALID_STATE);
s_dali_domain = std::make_unique<gateway::DaliDomainService>();
s_dali_domain->setApplicationControllerEnabled(kDali103ApplicationControllerEnabled);
s_dali_domain->setApplicationControllerIdentitySink([](uint8_t gateway_id) {
// dali_cpp only emits a semantic identity action. This gateway adapter
// turns it into a physical status-LED indication when the network service
// is running; another host can bind the same action to its own hardware.
if (s_network != nullptr) {
s_network->identify();
} else {
ESP_LOGI(kTag, "Part 103 identity command executed for gateway channel=%u", gateway_id);
}
});
s_runtime = std::make_unique<gateway::GatewayRuntime>(
profile,
gateway::GatewayRuntimeConfig{
@@ -1026,9 +1044,25 @@ extern "C" void app_main(void) {
kCacheSupported && kCacheStartupEnabled,
kUsbStartupDefaultMode,
kUsbStartupDefaultChannelIndex,
},
},
s_dali_domain.get());
ESP_ERROR_CHECK(s_runtime->start());
s_dali_domain->setApplicationControllerShortAddressProvider(
[](uint8_t channel_index, uint8_t gateway_id) {
return s_runtime == nullptr
? std::optional<uint8_t>(static_cast<uint8_t>(gateway_id & 0x3fU))
: s_runtime->applicationControllerShortAddressForChannel(channel_index,
gateway_id & 0x3fU);
});
s_dali_domain->setApplicationControllerShortAddressSink(
[](uint8_t channel_index, std::optional<uint8_t> short_address) {
if (s_runtime == nullptr ||
!s_runtime->setApplicationControllerShortAddressForChannel(channel_index,
short_address)) {
ESP_LOGE(kTag, "failed to persist Part 103 short address for channel=%u",
channel_index);
}
});
ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime));
s_runtime->setGatewayCount(s_dali_domain->channelCount());
+6
View File
@@ -691,6 +691,12 @@ CONFIG_GATEWAY_CHANNEL1_NATIVE_BAUDRATE=1200
#
# end of Gateway Channel 16
#
# DALI Part 103 Application Controller
#
# CONFIG_GATEWAY_DALI_103_APPLICATION_CONTROLLER_ENABLED is not set
# end of DALI Part 103 Application Controller
#
# Gateway Cache
#
@@ -77,6 +77,7 @@ struct DaliRawFrame {
uint8_t gateway_id{0};
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
std::vector<uint8_t> data;
bool double_send_confirmed{false};
};
enum class DaliDt8SceneColorMode {
@@ -115,6 +116,11 @@ struct DaliAddressSettingsSnapshot {
class DaliDomainService {
public:
using ApplicationControllerIdentitySink = std::function<void(uint8_t gateway_id)>;
using ApplicationControllerShortAddressProvider =
std::function<std::optional<uint8_t>(uint8_t channel_index, uint8_t gateway_id)>;
using ApplicationControllerShortAddressSink =
std::function<void(uint8_t channel_index, std::optional<uint8_t> short_address)>;
DaliDomainService();
~DaliDomainService();
@@ -127,6 +133,10 @@ class DaliDomainService {
size_t channelCount() const;
std::vector<DaliChannelInfo> channelInfo() const;
void addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink);
void setApplicationControllerEnabled(bool enabled);
void setApplicationControllerIdentitySink(ApplicationControllerIdentitySink sink);
void setApplicationControllerShortAddressProvider(ApplicationControllerShortAddressProvider provider);
void setApplicationControllerShortAddressSink(ApplicationControllerShortAddressSink sink);
bool resetBus(uint8_t gateway_id) const;
bool pulseBusLow(uint8_t gateway_id, uint32_t duration_ms) const;
@@ -215,6 +225,11 @@ class DaliDomainService {
TickType_t tick{0};
bool valid{false};
};
struct RecentForwardFrame {
std::array<uint8_t, 3> data{};
TickType_t tick{0};
bool valid{false};
};
DaliChannel* findChannelByGateway(uint8_t gateway_id);
const DaliChannel* findChannelByGateway(uint8_t gateway_id) const;
@@ -243,6 +258,11 @@ class DaliDomainService {
mutable SemaphoreHandle_t host_activity_lock_{nullptr};
mutable std::map<uint8_t, TickType_t> last_host_activity_ticks_;
mutable std::map<uint8_t, RecentHostCommandFrame> recent_host_command_frames_;
std::map<uint8_t, RecentForwardFrame> recent_forward_frames_;
bool application_controller_enabled_{false};
ApplicationControllerIdentitySink application_controller_identity_sink_;
ApplicationControllerShortAddressProvider application_controller_short_address_provider_;
ApplicationControllerShortAddressSink application_controller_short_address_sink_;
QueueHandle_t raw_frame_dispatch_queue_{nullptr};
TaskHandle_t raw_frame_dispatch_task_handle_{nullptr};
TaskHandle_t raw_frame_task_handle_{nullptr};
@@ -6,6 +6,7 @@
#include "dali.h"
#include "dali_hal.h"
#include "dali.hpp"
#include "dali_gateway.hpp"
#include "driver/uart.h"
#include "freertos/queue.h"
@@ -42,6 +43,7 @@ constexpr uint32_t kRawFrameRxTaskStackSize = CONFIG_DALI_DOMAIN_RAW_RX_TASK_STA
constexpr uint32_t kRawFrameDispatchTaskStackSize =
CONFIG_DALI_DOMAIN_RAW_DISPATCH_TASK_STACK_SIZE;
constexpr uint32_t kHardwareQueryRawPostSuppressMs = 10;
constexpr uint32_t kDaliDoubleSendWindowMs = 100;
constexpr uint8_t kControlDeviceSendOpcode = 0x60;
constexpr uint8_t kControlDeviceSendTwiceOpcode = 0x61;
constexpr uint8_t kControlDeviceQueryOpcode = 0x62;
@@ -439,8 +441,24 @@ struct DaliDomainService::DaliChannel {
std::optional<DaliSerialBusConfig> serial_bus;
QueueHandle_t serial_rx_queue{nullptr};
TaskHandle_t serial_rx_task_handle{nullptr};
std::unique_ptr<DaliApplicationController> application_controller;
};
std::optional<uint8_t> ApplicationControllerShortAddress(
const DaliDomainService::ApplicationControllerShortAddressProvider& provider,
const DaliChannelConfig& channel) {
if (provider) {
const auto short_address = provider(channel.channel_index, channel.gateway_id);
if (short_address.has_value() && short_address.value() <= 63) {
return short_address;
}
if (!short_address.has_value()) {
return std::nullopt;
}
}
return static_cast<uint8_t>(channel.gateway_id & 0x3fU);
}
DaliDomainService::DaliDomainService()
: raw_frame_sink_lock_(xSemaphoreCreateMutex()),
bus_activity_lock_(xSemaphoreCreateMutex()),
@@ -475,6 +493,13 @@ bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTrans
channel->comm = std::make_unique<DaliComm>(channel->hooks.send, channel->hooks.read,
channel->hooks.transact, channel->hooks.delay);
channel->dali = std::make_unique<Dali>(*channel->comm, config.gateway_id, config.name);
if (application_controller_enabled_) {
DaliApplicationControllerConfig application_config;
application_config.shortAddress =
ApplicationControllerShortAddress(application_controller_short_address_provider_, config);
channel->application_controller =
std::make_unique<DaliApplicationController>(application_config);
}
auto* existing = findChannelByIndex(config.channel_index);
if (existing != nullptr) {
@@ -702,6 +727,37 @@ void DaliDomainService::addRawFrameSink(std::function<void(const DaliRawFrame& f
}
}
void DaliDomainService::setApplicationControllerEnabled(bool enabled) {
application_controller_enabled_ = enabled;
for (auto& channel : channels_) {
if (!enabled) {
channel->application_controller.reset();
continue;
}
if (channel->application_controller != nullptr) {
continue;
}
DaliApplicationControllerConfig config;
config.shortAddress = ApplicationControllerShortAddress(
application_controller_short_address_provider_, channel->config);
channel->application_controller = std::make_unique<DaliApplicationController>(config);
}
}
void DaliDomainService::setApplicationControllerIdentitySink(ApplicationControllerIdentitySink sink) {
application_controller_identity_sink_ = std::move(sink);
}
void DaliDomainService::setApplicationControllerShortAddressProvider(
ApplicationControllerShortAddressProvider provider) {
application_controller_short_address_provider_ = std::move(provider);
}
void DaliDomainService::setApplicationControllerShortAddressSink(
ApplicationControllerShortAddressSink sink) {
application_controller_short_address_sink_ = std::move(sink);
}
bool DaliDomainService::resetBus(uint8_t gateway_id) const {
const auto* channel = findChannelByGateway(gateway_id);
if (channel == nullptr || channel->comm == nullptr) {
@@ -1873,6 +1929,37 @@ void DaliDomainService::rawFrameDispatchTaskLoop() {
frame.gateway_id = event.gateway_id;
frame.phy_kind = event.phy_kind;
frame.data.assign(event.data, event.data + event.len);
if (event.len == 3) {
const TickType_t now = xTaskGetTickCount();
auto& recent = recent_forward_frames_[event.gateway_id];
const bool same = recent.valid && recent.data[0] == event.data[0] &&
recent.data[1] == event.data[1] && recent.data[2] == event.data[2];
frame.double_send_confirmed =
same && (now - recent.tick) <= pdMS_TO_TICKS(kDaliDoubleSendWindowMs);
recent.data = {event.data[0], event.data[1], event.data[2]};
recent.tick = now;
recent.valid = true;
}
if (auto* channel = findChannelByGateway(frame.gateway_id);
channel != nullptr && channel->application_controller != nullptr && frame.data.size() == 3) {
const std::array<uint8_t, 3> forward{frame.data[0], frame.data[1], frame.data[2]};
const auto result = channel->application_controller->handleForwardFrame(
forward, frame.double_send_confirmed);
if (result.backwardFrame.has_value()) {
sendBackwardFrame(frame.gateway_id, result.backwardFrame.value());
}
if (result.identifyRequested && application_controller_identity_sink_) {
application_controller_identity_sink_(frame.gateway_id);
}
if (result.shortAddressChanged) {
ESP_LOGI(TAG, "Part 103 application controller gateway=%u short-address=%d",
frame.gateway_id, result.shortAddress.has_value() ? result.shortAddress.value() : -1);
if (application_controller_short_address_sink_) {
application_controller_short_address_sink_(channel->config.channel_index,
result.shortAddress);
}
}
}
notifyRawFrameSinks(frame);
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
idf_component_register(
SRCS "src/gateway_cache.cpp"
INCLUDE_DIRS "include"
REQUIRES freertos log nvs_flash
REQUIRES dali_cpp freertos log nvs_flash
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -12,6 +12,7 @@
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "nvs.h"
#include "dali_gateway.hpp"
namespace gateway {
@@ -232,9 +233,11 @@ class GatewayCache {
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);
GatewayCacheConfig config_;
GatewayCachePriorityMode priority_mode_;
DaliGatewayCache dali_gateway_cache_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t lock_{nullptr};
nvs_handle_t storage_{0};
+167 -238
View File
@@ -115,14 +115,102 @@ bool IsDefaultGroup(const GatewayCache::GroupEntry& group) {
return !group.enabled && group.target_type == 2 && group.target_value == 0;
}
bool SameFlags(const GatewayCacheChannelFlags& lhs, const GatewayCacheChannelFlags& rhs) {
return lhs.need_update_group == rhs.need_update_group &&
lhs.need_update_scene == rhs.need_update_scene &&
lhs.need_update_settings == rhs.need_update_settings;
DaliGatewayCachePriorityMode ToDaliCppPriorityMode(GatewayCachePriorityMode mode) {
return mode == GatewayCachePriorityMode::kLocalGatewayFirst
? DaliGatewayCachePriorityMode::localGatewayFirst
: DaliGatewayCachePriorityMode::outsideBusFirst;
}
bool AnyFlagSet(const GatewayCacheChannelFlags& flags) {
return flags.need_update_group || flags.need_update_scene || flags.need_update_settings;
GatewayCacheDaliPresence FromDaliCppPresence(DaliGatewayPresence presence) {
switch (presence) {
case DaliGatewayPresence::online:
return GatewayCacheDaliPresence::kOnline;
case DaliGatewayPresence::offline:
return GatewayCacheDaliPresence::kOffline;
case DaliGatewayPresence::unknown:
default:
return GatewayCacheDaliPresence::kUnknown;
}
}
DaliGatewayPresence ToDaliCppPresence(GatewayCacheDaliPresence presence) {
switch (presence) {
case GatewayCacheDaliPresence::kOnline:
return DaliGatewayPresence::online;
case GatewayCacheDaliPresence::kOffline:
return DaliGatewayPresence::offline;
case GatewayCacheDaliPresence::kUnknown:
default:
return DaliGatewayPresence::unknown;
}
}
GatewayCacheDaliRuntimeStatus FromDaliCppStatus(const DaliGatewayRuntimeStatus& value) {
return {value.actualLevel, value.sceneID, value.useMinLevel, value.stale, value.revision};
}
DaliGatewayRuntimeStatus ToDaliCppStatus(const GatewayCacheDaliRuntimeStatus& value) {
return {value.actual_level, value.scene_id, value.use_min_level, value.stale, value.revision};
}
GatewayCacheDaliSettingsSnapshot FromDaliCppSettings(const DaliGatewaySettingsSnapshot& value) {
return {value.powerOnLevel, value.systemFailureLevel, value.minLevel, value.maxLevel,
value.fadeTime, value.fadeRate};
}
DaliGatewaySettingsSnapshot ToDaliCppSettings(const GatewayCacheDaliSettingsSnapshot& value) {
return {value.power_on_level, value.system_failure_level, value.min_level, value.max_level,
value.fade_time, value.fade_rate};
}
GatewayCacheDaliAddressState FromDaliCppAddressState(const DaliGatewayAddressState& value) {
GatewayCacheDaliAddressState result;
result.group_mask_known = value.groupMaskKnown;
result.group_mask = value.groupMask;
result.scene_levels = value.sceneLevels;
result.settings = FromDaliCppSettings(value.settings);
result.status = FromDaliCppStatus(value.status);
return result;
}
DaliGatewayAddressState ToDaliCppAddressState(const GatewayCacheDaliAddressState& value) {
DaliGatewayAddressState result;
result.groupMaskKnown = value.group_mask_known;
result.groupMask = value.group_mask;
result.sceneLevels = value.scene_levels;
result.settings = ToDaliCppSettings(value.settings);
result.status = ToDaliCppStatus(value.status);
return result;
}
GatewayCacheChannelFlags FromDaliCppFlags(const DaliGatewayChannelFlags& value) {
return {value.needUpdateGroup, value.needUpdateScene, value.needUpdateSettings};
}
DaliGatewayChannelFlags ToDaliCppFlags(const GatewayCacheChannelFlags& value) {
return {value.need_update_group, value.need_update_scene, value.need_update_settings};
}
std::optional<DaliGatewayTarget> ToDaliCppTarget(std::optional<GatewayCacheDaliTarget> target) {
if (!target.has_value()) return std::nullopt;
DaliGatewayTargetKind kind = DaliGatewayTargetKind::shortAddress;
if (target->kind == GatewayCacheDaliTargetKind::kGroup) {
kind = DaliGatewayTargetKind::group;
} else if (target->kind == GatewayCacheDaliTargetKind::kBroadcast) {
kind = DaliGatewayTargetKind::broadcast;
}
return DaliGatewayTarget{kind, target->value};
}
std::optional<GatewayCacheDaliTarget> FromDaliCppTarget(std::optional<DaliGatewayTarget> target) {
if (!target.has_value()) return std::nullopt;
GatewayCacheDaliTargetKind kind = GatewayCacheDaliTargetKind::kShortAddress;
if (target->kind == DaliGatewayTargetKind::group) {
kind = GatewayCacheDaliTargetKind::kGroup;
} else if (target->kind == DaliGatewayTargetKind::broadcast) {
kind = GatewayCacheDaliTargetKind::kBroadcast;
}
return GatewayCacheDaliTarget{kind, target->value};
}
std::optional<GatewayCacheDaliTarget> DecodeDaliTarget(uint8_t raw_addr) {
@@ -140,24 +228,6 @@ std::optional<GatewayCacheDaliTarget> DecodeDaliTarget(uint8_t raw_addr) {
return std::nullopt;
}
bool ShouldMirrorObservedMutation(GatewayCacheRawFrameOrigin origin,
GatewayCachePriorityMode priority_mode) {
return origin == GatewayCacheRawFrameOrigin::kLocalGateway ||
priority_mode == GatewayCachePriorityMode::kOutsideBusFirst;
}
bool ShouldAlwaysMirrorObservedStatus(uint8_t raw_addr, uint8_t command) {
if (!DecodeDaliTarget(raw_addr).has_value()) {
return false;
}
if ((raw_addr & 0x01) == 0) {
return command <= 254;
}
return command == kDaliCmdOff || command == kDaliCmdRecallMax ||
command == kDaliCmdRecallMin ||
(command >= kDaliCmdGoToSceneMin && command <= kDaliCmdGoToSceneMax);
}
void ClearDaliState(GatewayCacheDaliAddressState& state) {
state.group_mask_known = false;
state.group_mask = 0;
@@ -192,39 +262,6 @@ void ApplyObservedSettingsValue(GatewayCacheDaliSettingsSnapshot& settings, uint
}
}
GatewayCacheChannelFlags ClassifyDaliMutation(uint8_t raw_addr, uint8_t command) {
GatewayCacheChannelFlags flags;
if (raw_addr == kDaliCmdSpecialProgramShortAddress) {
flags.need_update_settings = true;
return flags;
}
const bool special_command = raw_addr >= 0xA1 && raw_addr <= 0xC5 && (raw_addr & 0x01) != 0;
if (special_command || (raw_addr & 0x01) == 0) {
return flags;
}
if (command == kDaliCmdReset) {
flags.need_update_group = true;
flags.need_update_scene = true;
flags.need_update_settings = true;
} else if (command >= kDaliCmdStoreDtrAsMaxLevel && command <= kDaliCmdStoreDtrAsFadeRate) {
flags.need_update_settings = true;
} else if (command >= kDaliCmdSetSceneMin && command <= kDaliCmdRemoveSceneMax) {
flags.need_update_scene = true;
} else if (command >= kDaliCmdAddToGroupMin && command <= kDaliCmdRemoveFromGroupMax) {
flags.need_update_group = true;
} else if (command == kDaliCmdStoreDtrAsShortAddress) {
flags.need_update_settings = true;
} else if (command == kDaliCmdDt8StoreDtrAsColorX || command == kDaliCmdDt8StoreDtrAsColorY ||
(command >= kDaliCmdDt8StorePrimaryMin &&
command <= kDaliCmdDt8StartAutoCalibration)) {
flags.need_update_settings = true;
}
return flags;
}
std::string BuildScenePayload(const GatewayCache::SceneEntry& scene) {
char payload[32] = {0};
std::snprintf(payload, sizeof(payload), "%u,%u,%u,%u,%u,%u", scene.enabled ? 1 : 0,
@@ -377,6 +414,10 @@ void ApplyDaliStatePayload(std::string_view raw, GatewayCacheDaliAddressState& s
GatewayCache::GatewayCache(GatewayCacheConfig config)
: config_(std::move(config)),
priority_mode_(config_.default_priority_mode),
dali_gateway_cache_(DaliGatewayCacheConfig{config_.cache_enabled,
config_.reconciliation_enabled,
config_.full_state_mirror_enabled,
ToDaliCppPriorityMode(config_.default_priority_mode)}),
lock_(xSemaphoreCreateRecursiveMutex()) {}
GatewayCache::~GatewayCache() {
@@ -446,6 +487,11 @@ void GatewayCache::preloadChannel(uint8_t gateway_id) {
if (inserted) {
loadDaliStateStoreLocked(gateway_id, it->second);
}
std::array<DaliGatewayAddressState, 64> states;
for (size_t index = 0; index < states.size(); ++index) {
states[index] = ToDaliCppAddressState(it->second[index]);
}
dali_gateway_cache_.restoreAddressStates(gateway_id, states);
}
GatewayCache::SceneStore GatewayCache::scenes(uint8_t gateway_id) {
@@ -739,225 +785,115 @@ std::pair<uint8_t, uint8_t> GatewayCache::groupMask(uint8_t gateway_id) {
GatewayCacheChannelFlags GatewayCache::channelFlags(uint8_t gateway_id) {
LockGuard guard(lock_);
if (!shouldTrackUpdateFlagsLocked()) {
return {};
}
return channel_flags_[gateway_id];
return FromDaliCppFlags(dali_gateway_cache_.channelFlags(gateway_id));
}
GatewayCacheChannelFlags GatewayCache::pendingChannelFlags(uint8_t gateway_id) {
LockGuard guard(lock_);
return shouldTrackUpdateFlagsLocked() ? channel_flags_[gateway_id] : GatewayCacheChannelFlags{};
return FromDaliCppFlags(dali_gateway_cache_.pendingChannelFlags(gateway_id));
}
GatewayCacheDaliAddressState GatewayCache::daliAddressState(uint8_t gateway_id,
uint8_t short_address) {
LockGuard guard(lock_);
if (short_address >= 64) {
return {};
}
return ensureDaliAddressStateLocked(gateway_id, short_address);
if (short_address >= 64) return {};
return FromDaliCppAddressState(dali_gateway_cache_.addressState(gateway_id, short_address));
}
GatewayCacheDaliPresence GatewayCache::daliAddressPresence(uint8_t gateway_id,
uint8_t short_address) {
LockGuard guard(lock_);
if (short_address >= 64) {
return GatewayCacheDaliPresence::kUnknown;
}
if (const auto it = dali_presence_.find(gateway_id); it != dali_presence_.end()) {
return it->second[short_address];
}
return GatewayCacheDaliPresence::kUnknown;
return short_address >= 64 ? GatewayCacheDaliPresence::kUnknown
: FromDaliCppPresence(dali_gateway_cache_.addressPresence(gateway_id,
short_address));
}
void GatewayCache::markDaliAddressPresence(uint8_t gateway_id, uint8_t short_address,
GatewayCacheDaliPresence presence) {
LockGuard guard(lock_);
markDaliAddressPresenceLocked(gateway_id, short_address, presence);
dali_gateway_cache_.markAddressPresence(gateway_id, short_address, ToDaliCppPresence(presence));
}
std::optional<GatewayCacheDaliTarget> GatewayCache::decodeDaliTarget(uint8_t raw_addr) {
return DecodeDaliTarget(raw_addr);
return FromDaliCppTarget(DaliGatewayCache::decodeTarget(raw_addr));
}
std::vector<uint8_t> GatewayCache::reconciliationAddresses(
uint8_t gateway_id, std::optional<GatewayCacheDaliTarget> target) {
LockGuard guard(lock_);
std::vector<uint8_t> addresses;
auto presence = [&](uint8_t short_address) {
if (const auto it = dali_presence_.find(gateway_id); it != dali_presence_.end()) {
return it->second[short_address];
}
return GatewayCacheDaliPresence::kUnknown;
};
auto add_if_known_online = [&](uint8_t short_address) {
if (short_address < 64 && presence(short_address) == GatewayCacheDaliPresence::kOnline) {
addresses.push_back(short_address);
}
};
if (!target.has_value()) {
for (uint8_t short_address = 0; short_address < 64; ++short_address) {
add_if_known_online(short_address);
}
return addresses;
}
switch (target->kind) {
case GatewayCacheDaliTargetKind::kShortAddress:
if (target->value < 64 && presence(target->value) != GatewayCacheDaliPresence::kOffline) {
addresses.push_back(target->value);
}
break;
case GatewayCacheDaliTargetKind::kGroup: {
if (target->value >= 16) {
break;
}
const uint16_t bit = static_cast<uint16_t>(1U << target->value);
auto [states_it, inserted] = dali_states_.try_emplace(gateway_id);
if (inserted) {
loadDaliStateStoreLocked(gateway_id, states_it->second);
}
for (uint8_t short_address = 0; short_address < states_it->second.size(); ++short_address) {
const auto& state = states_it->second[short_address];
if (state.group_mask_known && (state.group_mask & bit) != 0) {
add_if_known_online(short_address);
}
}
break;
}
case GatewayCacheDaliTargetKind::kBroadcast:
for (uint8_t short_address = 0; short_address < 64; ++short_address) {
add_if_known_online(short_address);
}
break;
default:
break;
}
return addresses;
return dali_gateway_cache_.reconciliationAddresses(gateway_id, ToDaliCppTarget(target));
}
GatewayCacheDaliRuntimeStatus GatewayCache::daliGroupStatus(uint8_t gateway_id,
uint8_t group_id) {
LockGuard guard(lock_);
if (group_id >= 16) {
return {};
}
return ensureDaliGroupStatusLocked(gateway_id, group_id);
return group_id >= 16 ? GatewayCacheDaliRuntimeStatus{}
: FromDaliCppStatus(dali_gateway_cache_.groupStatus(gateway_id, group_id));
}
GatewayCacheDaliRuntimeStatus GatewayCache::daliBroadcastStatus(uint8_t gateway_id) {
LockGuard guard(lock_);
return ensureDaliBroadcastStatusLocked(gateway_id);
return FromDaliCppStatus(dali_gateway_cache_.broadcastStatus(gateway_id));
}
bool GatewayCache::setDaliGroupMask(uint8_t gateway_id, uint8_t short_address,
std::optional<uint16_t> group_mask) {
LockGuard guard(lock_);
if (short_address >= 64) {
return false;
}
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
state.group_mask_known = group_mask.has_value();
state.group_mask = group_mask.value_or(0);
refreshDaliAddressAggregateStatusLocked(gateway_id, state);
dirty_ = true;
return true;
const bool changed = dali_gateway_cache_.setGroupMask(gateway_id, short_address, group_mask);
if (changed) { syncDaliStatesFromCoreLocked(gateway_id); dirty_ = true; }
return changed;
}
bool GatewayCache::setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id,
std::optional<uint8_t> level) {
LockGuard guard(lock_);
if (short_address >= 64 || scene_id >= 16) {
return false;
}
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
state.scene_levels[scene_id] = level;
dirty_ = true;
return true;
const bool changed = dali_gateway_cache_.setSceneLevel(gateway_id, short_address, scene_id, level);
if (changed) { syncDaliStatesFromCoreLocked(gateway_id); dirty_ = true; }
return changed;
}
bool GatewayCache::setDaliSettings(uint8_t gateway_id, uint8_t short_address,
std::optional<GatewayCacheDaliSettingsSnapshot> settings) {
LockGuard guard(lock_);
if (short_address >= 64) {
return false;
}
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
state.settings = settings.value_or(GatewayCacheDaliSettingsSnapshot{});
dirty_ = true;
return true;
const auto core_settings = settings.has_value()
? std::optional<DaliGatewaySettingsSnapshot>(ToDaliCppSettings(*settings))
: std::nullopt;
const bool changed = dali_gateway_cache_.setSettings(gateway_id, short_address, core_settings);
if (changed) { syncDaliStatesFromCoreLocked(gateway_id); dirty_ = true; }
return changed;
}
bool GatewayCache::setDaliActualLevel(uint8_t gateway_id, uint8_t short_address,
std::optional<uint8_t> level) {
LockGuard guard(lock_);
if (short_address >= 64) {
return false;
}
GatewayCacheDaliRuntimeStatus status;
status.actual_level = level;
status.revision = nextDaliRuntimeRevisionLocked();
status.stale = false;
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
state.status.scene_id.reset();
state.status.use_min_level = false;
applyDaliRuntimeStatusToAddressLocked(state, status);
if (!level.has_value()) {
state.status.actual_level.reset();
state.status.revision = status.revision;
state.status.stale = false;
}
dirty_ = true;
return true;
const bool changed = dali_gateway_cache_.setActualLevel(gateway_id, short_address, level);
if (changed) { syncDaliStatesFromCoreLocked(gateway_id); dirty_ = true; }
return changed;
}
bool GatewayCache::clearChannelFlagsIfMatched(uint8_t gateway_id,
const GatewayCacheChannelFlags& flags) {
LockGuard guard(lock_);
if (!shouldTrackUpdateFlagsLocked()) {
return true;
}
auto& current = channel_flags_[gateway_id];
if (!SameFlags(current, flags)) {
return false;
}
current = {};
return true;
return dali_gateway_cache_.clearChannelFlagsIfMatched(gateway_id, ToDaliCppFlags(flags));
}
void GatewayCache::markGroupUpdateNeeded(uint8_t gateway_id, bool needed) {
LockGuard guard(lock_);
if (!shouldTrackUpdateFlagsLocked()) {
return;
}
channel_flags_[gateway_id].need_update_group = needed;
dali_gateway_cache_.markGroupUpdateNeeded(gateway_id, needed);
}
void GatewayCache::markSceneUpdateNeeded(uint8_t gateway_id, bool needed) {
LockGuard guard(lock_);
if (!shouldTrackUpdateFlagsLocked()) {
return;
}
channel_flags_[gateway_id].need_update_scene = needed;
dali_gateway_cache_.markSceneUpdateNeeded(gateway_id, needed);
}
void GatewayCache::markSettingsUpdateNeeded(uint8_t gateway_id, bool needed) {
LockGuard guard(lock_);
if (!shouldTrackUpdateFlagsLocked()) {
return;
}
channel_flags_[gateway_id].need_update_settings = needed;
dali_gateway_cache_.markSettingsUpdateNeeded(gateway_id, needed);
}
bool GatewayCache::cacheEnabled() const {
return config_.cache_enabled;
return dali_gateway_cache_.enabled();
}
bool GatewayCache::setCacheEnabled(bool enabled) {
@@ -975,80 +911,73 @@ bool GatewayCache::setCacheEnabled(bool enabled) {
}
LockGuard guard(lock_);
config_.cache_enabled = false;
dali_gateway_cache_.setEnabled(false);
return true;
}
{
LockGuard guard(lock_);
config_.cache_enabled = true;
dali_gateway_cache_.setEnabled(true);
}
return start() == ESP_OK;
}
bool GatewayCache::reconciliationEnabled() const {
return config_.cache_enabled && config_.reconciliation_enabled;
return dali_gateway_cache_.reconciliationEnabled();
}
bool GatewayCache::fullStateMirrorEnabled() const {
return reconciliationEnabled() && config_.full_state_mirror_enabled;
return dali_gateway_cache_.fullStateMirrorEnabled();
}
bool GatewayCache::mirrorDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) {
LockGuard guard(lock_);
if (!config_.cache_enabled) {
return false;
const bool changed = dali_gateway_cache_.mirrorForwardFrame(gateway_id, raw_addr, command);
if (changed) {
syncDaliStatesFromCoreLocked(gateway_id);
dirty_ = true;
}
return mirrorDaliCommandLocked(gateway_id, raw_addr, command);
return changed;
}
bool GatewayCache::observeDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint8_t command,
GatewayCacheRawFrameOrigin origin) {
LockGuard guard(lock_);
if (!config_.cache_enabled) {
return false;
bool changed = false;
if (origin == GatewayCacheRawFrameOrigin::kOutsideBus) {
changed = dali_gateway_cache_.observeForwardFrame(gateway_id, raw_addr, command,
DaliGatewayFrameOrigin::outsideBus);
} else {
dali_gateway_cache_.mirrorForwardFrame(gateway_id, raw_addr, command);
}
if (ShouldAlwaysMirrorObservedStatus(raw_addr, command) ||
ShouldMirrorObservedMutation(origin, priority_mode_)) {
mirrorDaliCommandLocked(gateway_id, raw_addr, command);
}
if (!shouldTrackUpdateFlagsLocked()) {
return false;
}
const auto detected = ClassifyDaliMutation(raw_addr, command);
if (!AnyFlagSet(detected)) {
return false;
}
if (origin != GatewayCacheRawFrameOrigin::kOutsideBus) {
return false;
}
auto& current = channel_flags_[gateway_id];
const bool changed = (!current.need_update_group && detected.need_update_group) ||
(!current.need_update_scene && detected.need_update_scene) ||
(!current.need_update_settings && detected.need_update_settings);
current.need_update_group = current.need_update_group || detected.need_update_group;
current.need_update_scene = current.need_update_scene || detected.need_update_scene;
current.need_update_settings = current.need_update_settings || detected.need_update_settings;
if (changed) {
ESP_LOGI(kTag, "outside DALI mutation gateway=%u addr=0x%02x cmd=0x%02x flags g=%d s=%d cfg=%d",
gateway_id, raw_addr, command, current.need_update_group, current.need_update_scene,
current.need_update_settings);
if (dali_gateway_cache_.enabled()) {
syncDaliStatesFromCoreLocked(gateway_id);
dirty_ = true;
}
return changed;
}
GatewayCachePriorityMode GatewayCache::priorityMode() {
LockGuard guard(lock_);
return priority_mode_;
return dali_gateway_cache_.priorityMode() == DaliGatewayCachePriorityMode::localGatewayFirst
? GatewayCachePriorityMode::kLocalGatewayFirst
: GatewayCachePriorityMode::kOutsideBusFirst;
}
void GatewayCache::setPriorityMode(GatewayCachePriorityMode mode) {
LockGuard guard(lock_);
priority_mode_ = mode;
dali_gateway_cache_.setPriorityMode(ToDaliCppPriorityMode(mode));
}
void GatewayCache::syncDaliStatesFromCoreLocked(uint8_t gateway_id) {
const auto states = dali_gateway_cache_.addressStates(gateway_id);
auto& legacy = dali_states_[gateway_id];
for (size_t index = 0; index < states.size(); ++index) {
legacy[index] = FromDaliCppAddressState(states[index]);
dali_runtime_revision_ = std::max(dali_runtime_revision_, legacy[index].status.revision);
}
}
uint32_t GatewayCache::nextDaliRuntimeRevisionLocked() {
@@ -242,9 +242,6 @@ class GatewayController {
void captureTransactionFrame(const std::vector<uint8_t>& frame);
void captureTransactionCompletion(const std::vector<uint8_t>& command);
void handleDaliRawFrame(const DaliRawFrame& frame);
bool handleApplicationControllerFrame(const DaliRawFrame& frame);
std::optional<uint8_t> applicationControllerResponse(uint8_t gateway_id, uint8_t first,
uint8_t instance, uint8_t opcode) const;
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);
@@ -329,14 +326,6 @@ class GatewayController {
bool ble_enabled_{false};
bool wifi_enabled_{false};
bool ip_router_enabled_{true};
bool application_controller_enabled_{true};
bool application_controller_power_cycle_notification_{true};
bool application_controller_power_cycle_seen_{true};
bool application_controller_reset_state_{false};
uint8_t application_controller_operating_mode_{0};
uint8_t application_controller_dtr0_{0};
uint8_t application_controller_dtr1_{0};
uint8_t application_controller_dtr2_{0};
};
} // namespace gateway
@@ -1815,160 +1815,6 @@ void GatewayController::captureTransactionCompletion(const std::vector<uint8_t>&
}
}
std::optional<uint8_t> GatewayController::applicationControllerResponse(
uint8_t gateway_id, uint8_t first, uint8_t instance, uint8_t opcode) const {
const uint8_t gateway_short =
static_cast<uint8_t>(((gateway_id & 0x3F) << 1) | 0x01);
const bool addressed_to_gateway = first == 0xFF || first == gateway_short;
if (!addressed_to_gateway || instance != 0xFE) {
return std::nullopt;
}
switch (opcode) {
case 0x30: {
uint8_t status = 0;
if (application_controller_enabled_) {
status |= 0x08;
}
if (application_controller_power_cycle_seen_) {
status |= 0x20;
}
if (application_controller_reset_state_) {
status |= 0x40;
}
return status;
}
case 0x31:
case 0x32:
case 0x33:
return 0x00;
case 0x34:
return 0x02;
case 0x35:
return 0x00;
case 0x36:
return application_controller_dtr0_;
case 0x37:
return application_controller_dtr1_;
case 0x38:
return application_controller_dtr2_;
case 0x39:
case 0x3A:
case 0x3B:
return 0x00;
case 0x3C:
return 0xFF;
case 0x3D:
return static_cast<uint8_t>(application_controller_enabled_ ? 1 : 0);
case 0x3E:
return application_controller_operating_mode_;
case 0x3F:
case 0x40:
case 0x41:
case 0x42:
case 0x43:
case 0x44:
return 0x00;
case 0x45:
return static_cast<uint8_t>(application_controller_power_cycle_notification_ ? 1 : 0);
case 0x46:
return 0x01;
case 0x47:
return 0x01;
case 0x48:
return static_cast<uint8_t>(application_controller_reset_state_ ? 1 : 0);
default:
return std::nullopt;
}
}
bool GatewayController::handleApplicationControllerFrame(const DaliRawFrame& frame) {
if (frame.data.size() != 3) {
return false;
}
const uint8_t first = frame.data[0];
const uint8_t instance = frame.data[1];
const uint8_t opcode = frame.data[2];
if (first == 0xC1) {
switch (instance) {
case 0x30:
application_controller_dtr0_ = opcode;
break;
case 0x31:
application_controller_dtr1_ = opcode;
break;
case 0x32:
application_controller_dtr2_ = opcode;
break;
default:
break;
}
return false;
}
if ((first & 0x01) == 0) {
return false;
}
const uint8_t gateway_short =
static_cast<uint8_t>(((frame.gateway_id & 0x3F) << 1) | 0x01);
if (first != 0xFF && first != gateway_short) {
return false;
}
if (instance != 0xFE) {
return false;
}
switch (opcode) {
case 0x01:
application_controller_power_cycle_seen_ = false;
application_controller_reset_state_ = false;
break;
case 0x10:
application_controller_enabled_ = true;
application_controller_power_cycle_notification_ = true;
application_controller_reset_state_ = true;
application_controller_operating_mode_ = 0;
application_controller_dtr0_ = 0;
application_controller_dtr1_ = 0;
application_controller_dtr2_ = 0;
break;
case 0x16:
application_controller_enabled_ = true;
application_controller_reset_state_ = false;
break;
case 0x17:
application_controller_enabled_ = false;
application_controller_reset_state_ = false;
break;
case 0x18:
application_controller_operating_mode_ = application_controller_dtr0_;
application_controller_reset_state_ = false;
break;
case 0x1F:
application_controller_power_cycle_notification_ = true;
application_controller_reset_state_ = false;
break;
case 0x20:
application_controller_power_cycle_notification_ = false;
application_controller_reset_state_ = false;
break;
case 0x21:
application_controller_reset_state_ = false;
break;
default:
break;
}
const auto response = applicationControllerResponse(frame.gateway_id, first, instance, opcode);
if (!response.has_value()) {
return false;
}
return dali_domain_.sendBackwardFrame(frame.gateway_id, response.value());
}
void GatewayController::handleBridgeTransportCommand(uint8_t gateway_id,
const std::vector<uint8_t>& command) {
const uint8_t version = command.size() > 4 ? command[4] : kBridgeTransportVersion;
@@ -2054,7 +1900,6 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
if (frame.data.size() == 3 &&
(((frame.data[0] & 0x01) != 0) || frame.data[0] == 0xC1)) {
handleApplicationControllerFrame(frame);
const bool maintenance_activity = maintenance_activity_gateway_.load() == frame.gateway_id;
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) || maintenance_activity ||
runtime_.hasActiveQueryCommand(frame.gateway_id) ||
@@ -89,6 +89,9 @@ class GatewayNetworkService {
GatewayBridgeService* bridge_service = nullptr);
esp_err_t start();
// Physical adapter for the Part 103 IDENTIFY DEVICE action. The DALI core
// remains hardware-independent and invokes this only through the app wiring.
void identify();
/// Stable runtime settings surface shared by local HTTP, the bridge API, and
/// cloud provisioning. The payload is a schema-versioned JSON document.
@@ -100,6 +103,7 @@ class GatewayNetworkService {
static void UdpTaskEntry(void* arg);
static void TcpControlTaskEntry(void* arg);
static void BootButtonTaskEntry(void* arg);
static void IdentifyTaskEntry(void* arg);
static esp_err_t HandleInfoGet(httpd_req_t* req);
static esp_err_t HandleCommandGet(httpd_req_t* req);
static esp_err_t HandleCommandPost(httpd_req_t* req);
@@ -136,6 +140,7 @@ class GatewayNetworkService {
void udpTaskLoop();
void tcpControlTaskLoop();
void bootButtonTaskLoop();
void identifyTaskLoop();
void handleNetworkControlBytes(const uint8_t* data, size_t len);
std::optional<std::string> handleJsonControlFrame(const uint8_t* data, size_t len);
bool enqueueControlFrameForTargets(const std::vector<uint8_t>& frame);
@@ -184,6 +189,7 @@ class GatewayNetworkService {
bool espnow_connected_{false};
std::array<uint8_t, 6> espnow_peer_{};
TaskHandle_t boot_button_task_handle_{nullptr};
TaskHandle_t identify_task_handle_{nullptr};
TaskHandle_t udp_task_handle_{nullptr};
TaskHandle_t tcp_control_task_handle_{nullptr};
int udp_socket_{-1};
@@ -433,6 +433,23 @@ esp_err_t GatewayNetworkService::start() {
return ESP_OK;
}
void GatewayNetworkService::identify() {
if (config_.status_led_gpio < 0) {
ESP_LOGI(kTag, "Part 103 identity requested; no status LED is configured");
return;
}
if (identify_task_handle_ != nullptr) {
return;
}
const BaseType_t created = xTaskCreate(&GatewayNetworkService::IdentifyTaskEntry,
"gateway_identify", 2048, this, 2,
&identify_task_handle_);
if (created != pdPASS) {
identify_task_handle_ = nullptr;
ESP_LOGW(kTag, "failed to start Part 103 identity indicator task");
}
}
esp_err_t GatewayNetworkService::ensureNetworkStack() {
esp_err_t err = esp_netif_init();
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
@@ -1144,6 +1161,23 @@ void GatewayNetworkService::BootButtonTaskEntry(void* arg) {
static_cast<GatewayNetworkService*>(arg)->bootButtonTaskLoop();
}
void GatewayNetworkService::IdentifyTaskEntry(void* arg) {
static_cast<GatewayNetworkService*>(arg)->identifyTaskLoop();
}
void GatewayNetworkService::identifyTaskLoop() {
// Five short flashes provide the physical IDENTIFY DEVICE indication without
// tying the generic DALI layer to an ESP timer or a GPIO implementation.
for (int flash = 0; flash < 5; ++flash) {
setStatusLed(true);
vTaskDelay(pdMS_TO_TICKS(200));
setStatusLed(false);
vTaskDelay(pdMS_TO_TICKS(200));
}
identify_task_handle_ = nullptr;
vTaskDelete(nullptr);
}
void GatewayNetworkService::HandleWifiEvent(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data) {
auto* service = static_cast<GatewayNetworkService*>(arg);
@@ -111,6 +111,10 @@ class GatewaySettingsStore {
bool setChannelGatewayId(uint8_t channel_index, uint8_t gateway_id);
uint8_t getChannelGatewayGroup(uint8_t channel_index, uint8_t fallback) const;
bool setChannelGatewayGroup(uint8_t channel_index, uint8_t gateway_group);
std::optional<uint8_t> getApplicationControllerShortAddress(uint8_t channel_index,
uint8_t fallback) const;
bool setApplicationControllerShortAddress(uint8_t channel_index,
std::optional<uint8_t> short_address);
private:
std::optional<std::string> readString(std::string_view key) const;
@@ -118,6 +122,7 @@ class GatewaySettingsStore {
std::string makeGatewayNameKey(uint8_t gateway_id) const;
std::string makeChannelGatewayIdKey(uint8_t channel_index) const;
std::string makeChannelGatewayGroupKey(uint8_t channel_index) const;
std::string makeApplicationControllerShortAddressKey(uint8_t channel_index) const;
mutable nvs_handle_t handle_{0};
};
@@ -183,6 +188,10 @@ class GatewayRuntime {
bool setGatewayIdForChannel(uint8_t channel_index, uint8_t gateway_id);
uint8_t gatewayGroupForChannel(uint8_t channel_index, uint8_t fallback = 0) const;
bool setGatewayGroupForChannel(uint8_t channel_index, uint8_t gateway_group);
std::optional<uint8_t> applicationControllerShortAddressForChannel(uint8_t channel_index,
uint8_t fallback) const;
bool setApplicationControllerShortAddressForChannel(
uint8_t channel_index, std::optional<uint8_t> short_address);
std::string deviceName() const;
bool setDeviceName(std::string_view name);
std::string gatewayName(uint8_t gateway_id) const;
@@ -217,6 +226,7 @@ class GatewayRuntime {
mutable std::map<uint8_t, std::string> gateway_names_;
mutable std::map<uint8_t, uint8_t> channel_gateway_ids_;
mutable std::map<uint8_t, uint8_t> channel_gateway_groups_;
mutable std::map<uint8_t, std::optional<uint8_t>> application_controller_short_addresses_;
size_t gateway_count_{0};
bool ble_enabled_{false};
bool cache_enabled_{true};
@@ -372,6 +372,31 @@ bool GatewaySettingsStore::setChannelGatewayGroup(uint8_t channel_index,
nvs_commit(handle_) == ESP_OK;
}
std::optional<uint8_t> GatewaySettingsStore::getApplicationControllerShortAddress(
uint8_t channel_index, uint8_t fallback) const {
if (handle_ == 0) {
return fallback & 0x3fU;
}
uint8_t short_address = fallback & 0x3fU;
if (nvs_get_u8(handle_, makeApplicationControllerShortAddressKey(channel_index).c_str(),
&short_address) != ESP_OK) {
return fallback & 0x3fU;
}
return short_address <= 63 ? std::optional<uint8_t>(short_address) : std::nullopt;
}
bool GatewaySettingsStore::setApplicationControllerShortAddress(
uint8_t channel_index, std::optional<uint8_t> short_address) {
if (handle_ == 0 || (short_address.has_value() && short_address.value() > 63)) {
return false;
}
const uint8_t stored = short_address.value_or(0xffU);
return nvs_set_u8(handle_, makeApplicationControllerShortAddressKey(channel_index).c_str(),
stored) == ESP_OK &&
nvs_commit(handle_) == ESP_OK;
}
std::optional<std::string> GatewaySettingsStore::readString(std::string_view key) const {
if (handle_ == 0) {
return std::nullopt;
@@ -418,6 +443,13 @@ std::string GatewaySettingsStore::makeChannelGatewayGroupKey(uint8_t channel_ind
return std::string(key);
}
std::string GatewaySettingsStore::makeApplicationControllerShortAddressKey(
uint8_t channel_index) const {
char key[24] = {0};
std::snprintf(key, sizeof(key), "dali_cd_sa_%u", channel_index);
return std::string(key);
}
GatewayRuntime::GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config,
DaliDomainService* dali_domain)
: profile_(profile),
@@ -847,6 +879,33 @@ bool GatewayRuntime::setGatewayGroupForChannel(uint8_t channel_index, uint8_t ga
return true;
}
std::optional<uint8_t> GatewayRuntime::applicationControllerShortAddressForChannel(
uint8_t channel_index, uint8_t fallback) const {
LockGuard guard(command_lock_);
const auto cached = application_controller_short_addresses_.find(channel_index);
if (cached != application_controller_short_addresses_.end()) {
return cached->second;
}
const auto short_address =
settings_.getApplicationControllerShortAddress(channel_index, fallback);
application_controller_short_addresses_[channel_index] = short_address;
return short_address;
}
bool GatewayRuntime::setApplicationControllerShortAddressForChannel(
uint8_t channel_index, std::optional<uint8_t> short_address) {
if (short_address.has_value() && short_address.value() > 63) {
return false;
}
if (!settings_.setApplicationControllerShortAddress(channel_index, short_address)) {
return false;
}
LockGuard guard(command_lock_);
application_controller_short_addresses_[channel_index] = short_address;
return true;
}
std::string GatewayRuntime::deviceName() const {
LockGuard guard(command_lock_);
if (device_name_.has_value()) {