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:
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user