feat(gateway): enhance DALI host activity tracking and presence management
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"idf.currentSetup": "/Users/tonylu/.espressif/v6.0/esp-idf"
|
||||
}
|
||||
@@ -692,6 +692,7 @@ CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||
#
|
||||
# KNX Settings
|
||||
#
|
||||
CONFIG_GATEWAY_KNX_INSTANCE_COUNT=1
|
||||
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
|
||||
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
|
||||
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||
|
||||
@@ -127,6 +127,11 @@ class DaliDomainService {
|
||||
|
||||
bool resetBus(uint8_t gateway_id) const;
|
||||
bool isBusIdle(uint8_t gateway_id, uint32_t quiet_ms) const;
|
||||
void markHostActivity(uint8_t gateway_id) const;
|
||||
void markHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
bool hasRecentHostActivity(uint8_t gateway_id, uint32_t window_ms) const;
|
||||
bool matchesRecentHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr, uint8_t command,
|
||||
uint32_t window_ms) const;
|
||||
bool writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const;
|
||||
std::vector<uint8_t> transactBridgeFrame(uint8_t gateway_id, const uint8_t* data,
|
||||
size_t len) const;
|
||||
@@ -184,6 +189,12 @@ class DaliDomainService {
|
||||
|
||||
private:
|
||||
struct DaliChannel;
|
||||
struct RecentHostCommandFrame {
|
||||
uint8_t raw_addr{0};
|
||||
uint8_t command{0};
|
||||
TickType_t tick{0};
|
||||
bool valid{false};
|
||||
};
|
||||
|
||||
DaliChannel* findChannelByGateway(uint8_t gateway_id);
|
||||
const DaliChannel* findChannelByGateway(uint8_t gateway_id) const;
|
||||
@@ -204,6 +215,9 @@ class DaliDomainService {
|
||||
SemaphoreHandle_t raw_frame_sink_lock_{nullptr};
|
||||
mutable SemaphoreHandle_t bus_activity_lock_{nullptr};
|
||||
mutable std::map<uint8_t, TickType_t> last_bus_activity_ticks_;
|
||||
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_;
|
||||
TaskHandle_t raw_frame_task_handle_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -315,7 +315,8 @@ struct DaliDomainService::DaliChannel {
|
||||
|
||||
DaliDomainService::DaliDomainService()
|
||||
: raw_frame_sink_lock_(xSemaphoreCreateMutex()),
|
||||
bus_activity_lock_(xSemaphoreCreateMutex()) {
|
||||
bus_activity_lock_(xSemaphoreCreateMutex()),
|
||||
host_activity_lock_(xSemaphoreCreateMutex()) {
|
||||
esp_log_level_set(TAG, (esp_log_level_t)CONFIG_DALI_LOG_LEVEL);
|
||||
}
|
||||
|
||||
@@ -328,6 +329,10 @@ DaliDomainService::~DaliDomainService() {
|
||||
vSemaphoreDelete(bus_activity_lock_);
|
||||
bus_activity_lock_ = nullptr;
|
||||
}
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
vSemaphoreDelete(host_activity_lock_);
|
||||
host_activity_lock_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) {
|
||||
@@ -568,11 +573,85 @@ bool DaliDomainService::isBusIdle(uint8_t gateway_id, uint32_t quiet_ms) const {
|
||||
return (xTaskGetTickCount() - last_activity) >= pdMS_TO_TICKS(quiet_ms);
|
||||
}
|
||||
|
||||
void DaliDomainService::markHostActivity(uint8_t gateway_id) const {
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
|
||||
}
|
||||
last_host_activity_ticks_[gateway_id] = xTaskGetTickCount();
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreGive(host_activity_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
void DaliDomainService::markHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr,
|
||||
uint8_t command) const {
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
|
||||
}
|
||||
last_host_activity_ticks_[gateway_id] = now;
|
||||
recent_host_command_frames_[gateway_id] = RecentHostCommandFrame{raw_addr, command, now, true};
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreGive(host_activity_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
bool DaliDomainService::hasRecentHostActivity(uint8_t gateway_id, uint32_t window_ms) const {
|
||||
if (window_ms == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TickType_t last_activity = 0;
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
|
||||
}
|
||||
if (const auto it = last_host_activity_ticks_.find(gateway_id);
|
||||
it != last_host_activity_ticks_.end()) {
|
||||
last_activity = it->second;
|
||||
}
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreGive(host_activity_lock_);
|
||||
}
|
||||
if (last_activity == 0) {
|
||||
return false;
|
||||
}
|
||||
return (xTaskGetTickCount() - last_activity) < pdMS_TO_TICKS(window_ms);
|
||||
}
|
||||
|
||||
bool DaliDomainService::matchesRecentHostCommandFrame(uint8_t gateway_id, uint8_t raw_addr,
|
||||
uint8_t command,
|
||||
uint32_t window_ms) const {
|
||||
if (window_ms == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RecentHostCommandFrame frame;
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreTake(host_activity_lock_, portMAX_DELAY);
|
||||
}
|
||||
if (const auto it = recent_host_command_frames_.find(gateway_id);
|
||||
it != recent_host_command_frames_.end()) {
|
||||
frame = it->second;
|
||||
}
|
||||
if (host_activity_lock_ != nullptr) {
|
||||
xSemaphoreGive(host_activity_lock_);
|
||||
}
|
||||
if (!frame.valid || frame.raw_addr != raw_addr || frame.command != command) {
|
||||
return false;
|
||||
}
|
||||
return (xTaskGetTickCount() - frame.tick) < pdMS_TO_TICKS(window_ms);
|
||||
}
|
||||
|
||||
bool DaliDomainService::writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || !channel->hooks.send) {
|
||||
return false;
|
||||
}
|
||||
if (data != nullptr && len == 3 && (data[0] == 0x10 || data[0] == 0x11)) {
|
||||
markHostCommandFrame(gateway_id, data[1], data[2]);
|
||||
} else {
|
||||
markHostActivity(gateway_id);
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->hooks.send(data, len);
|
||||
}
|
||||
@@ -584,6 +663,11 @@ std::vector<uint8_t> DaliDomainService::transactBridgeFrame(uint8_t gateway_id,
|
||||
if (channel == nullptr || !channel->hooks.transact) {
|
||||
return {};
|
||||
}
|
||||
if (data != nullptr && len == 3 && (data[0] == 0x10 || data[0] == 0x11 || data[0] == 0x12)) {
|
||||
markHostCommandFrame(gateway_id, data[1], data[2]);
|
||||
} else {
|
||||
markHostActivity(gateway_id);
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->hooks.transact(data, len);
|
||||
}
|
||||
@@ -593,6 +677,7 @@ bool DaliDomainService::sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t co
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markHostCommandFrame(gateway_id, raw_addr, command);
|
||||
markBusActivity(gateway_id);
|
||||
return channel->comm->sendRawNew(raw_addr, command);
|
||||
}
|
||||
@@ -602,6 +687,7 @@ bool DaliDomainService::sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markHostCommandFrame(gateway_id, raw_addr, command);
|
||||
markBusActivity(gateway_id);
|
||||
return channel->comm->sendExtRawNew(raw_addr, command);
|
||||
}
|
||||
@@ -612,6 +698,7 @@ std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t r
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markHostCommandFrame(gateway_id, raw_addr, command);
|
||||
markBusActivity(gateway_id);
|
||||
return channel->comm->queryRawNew(raw_addr, command);
|
||||
}
|
||||
@@ -683,6 +770,23 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt1Snapshot(uint8_t gateway
|
||||
PutOptionalInt(snapshot, "emergencyModeRaw", detailed->emergencyMode);
|
||||
PutOptionalInt(snapshot, "featuresRaw", detailed->feature);
|
||||
PutOptionalInt(snapshot, "deviceStatusRaw", detailed->deviceStatus);
|
||||
PutOptionalInt(snapshot, "batteryChargeLevel", detailed->batteryChargeLevel);
|
||||
PutOptionalInt(snapshot, "functionTestDelayTime", detailed->functionTestDelayTime);
|
||||
PutOptionalInt(snapshot, "durationTestDelayTime", detailed->durationTestDelayTime);
|
||||
PutOptionalInt(snapshot, "functionTestIntervalDays", detailed->functionTestIntervalDays);
|
||||
PutOptionalInt(snapshot, "durationTestIntervalWeeks", detailed->durationTestIntervalWeeks);
|
||||
PutOptionalInt(snapshot, "testExecutionTimeoutDays", detailed->testExecutionTimeoutDays);
|
||||
PutOptionalInt(snapshot, "prolongTimeHalfMinutes", detailed->prolongTimeHalfMinutes);
|
||||
PutOptionalInt(snapshot, "durationTestResultMinutes", detailed->durationTestResultMinutes);
|
||||
PutOptionalInt(snapshot, "lampEmergencyTimeHours", detailed->lampEmergencyTimeHours);
|
||||
PutOptionalInt(snapshot, "lampTotalOperationTimeHours", detailed->lampTotalOperationTimeHours);
|
||||
PutOptionalInt(snapshot, "emergencyLevel", detailed->emergencyLevel);
|
||||
PutOptionalInt(snapshot, "emergencyMinLevel", detailed->emergencyMinLevel);
|
||||
PutOptionalInt(snapshot, "emergencyMaxLevel", detailed->emergencyMaxLevel);
|
||||
PutOptionalInt(snapshot, "ratedDurationMinutes", detailed->ratedDurationMinutes);
|
||||
PutOptionalInt(snapshot, "extendedVersion", detailed->extendedVersion);
|
||||
PutOptionalInt(snapshot, "physicalMinLevel", detailed->physicalMinLevel);
|
||||
PutOptionalInt(snapshot, "emergencyDeviceTypeCode", detailed->emergencyDeviceTypeCode);
|
||||
|
||||
snapshot.bools["testInProgress"] = detailed->testInProgress;
|
||||
snapshot.bools["lampFailure"] = detailed->lampFailure;
|
||||
@@ -736,6 +840,8 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt1Snapshot(uint8_t gateway
|
||||
snapshot.bools["hardwiredInhibitSupported"] = features.hardwiredInhibitSupported();
|
||||
snapshot.bools["physicalSelectionSupported"] = features.physicalSelectionSupported();
|
||||
snapshot.bools["relightInRestModeSupported"] = features.relightInRestModeSupported();
|
||||
snapshot.ints["derivedEmergencyDeviceTypeCode"] =
|
||||
features.emergencyDeviceTypeCode(detailed->physicalMinLevel);
|
||||
}
|
||||
if (detailed->deviceStatus.has_value()) {
|
||||
const DaliDT1DeviceStatus status(detailed->deviceStatus.value());
|
||||
@@ -1208,9 +1314,11 @@ std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettin
|
||||
|
||||
DaliAddressSettingsSnapshot settings{};
|
||||
|
||||
if (const auto value = channel->dali->base.getPowerOnLevel(short_address); value.has_value()) {
|
||||
settings.power_on_level = static_cast<uint8_t>(*value);
|
||||
const auto power_on_level = channel->dali->base.getPowerOnLevel(short_address);
|
||||
if (!power_on_level.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
settings.power_on_level = static_cast<uint8_t>(*power_on_level);
|
||||
if (const auto value = channel->dali->base.getSystemFailureLevel(short_address);
|
||||
value.has_value()) {
|
||||
settings.system_failure_level = static_cast<uint8_t>(*value);
|
||||
|
||||
@@ -587,9 +587,39 @@ bool OperationRequiresDt1(BridgeOperation operation) {
|
||||
case BridgeOperation::getEmergencyLevel:
|
||||
case BridgeOperation::getEmergencyStatus:
|
||||
case BridgeOperation::getEmergencyFailureStatus:
|
||||
case BridgeOperation::getDt1Snapshot:
|
||||
case BridgeOperation::startEmergencyFunctionTest:
|
||||
case BridgeOperation::stopEmergencyTest:
|
||||
case BridgeOperation::startEmergencyDurationTest:
|
||||
case BridgeOperation::dt1Rest:
|
||||
case BridgeOperation::dt1Inhibit:
|
||||
case BridgeOperation::dt1RelightResetInhibit:
|
||||
case BridgeOperation::dt1StartIdentification:
|
||||
case BridgeOperation::dt1ResetFunctionTestDoneFlag:
|
||||
case BridgeOperation::dt1ResetDurationTestDoneFlag:
|
||||
case BridgeOperation::dt1ResetLampTime:
|
||||
case BridgeOperation::dt1StoreEmergencyLevel:
|
||||
case BridgeOperation::dt1StoreTestDelayTime:
|
||||
case BridgeOperation::dt1StoreFunctionTestInterval:
|
||||
case BridgeOperation::dt1StoreDurationTestInterval:
|
||||
case BridgeOperation::dt1StoreTestExecutionTimeout:
|
||||
case BridgeOperation::dt1StoreProlongTime:
|
||||
case BridgeOperation::dt1PerformDtrSelectedFunction:
|
||||
case BridgeOperation::dt1GetBatteryCharge:
|
||||
case BridgeOperation::dt1GetFunctionTestDelayTime:
|
||||
case BridgeOperation::dt1GetDurationTestDelayTime:
|
||||
case BridgeOperation::dt1GetFunctionTestInterval:
|
||||
case BridgeOperation::dt1GetDurationTestInterval:
|
||||
case BridgeOperation::dt1GetTestExecutionTimeout:
|
||||
case BridgeOperation::dt1GetProlongTime:
|
||||
case BridgeOperation::dt1GetDurationTestResult:
|
||||
case BridgeOperation::dt1GetLampEmergencyTime:
|
||||
case BridgeOperation::dt1GetLampTotalOperationTime:
|
||||
case BridgeOperation::dt1GetEmergencyMinLevel:
|
||||
case BridgeOperation::dt1GetEmergencyMaxLevel:
|
||||
case BridgeOperation::dt1GetRatedDuration:
|
||||
case BridgeOperation::dt1GetExtendedVersion:
|
||||
case BridgeOperation::dt1GetEmergencyDeviceType:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -617,6 +647,22 @@ bool BridgeOperationReadable(BridgeOperation operation) {
|
||||
case BridgeOperation::getEmergencyLevel:
|
||||
case BridgeOperation::getEmergencyStatus:
|
||||
case BridgeOperation::getEmergencyFailureStatus:
|
||||
case BridgeOperation::getDt1Snapshot:
|
||||
case BridgeOperation::dt1GetBatteryCharge:
|
||||
case BridgeOperation::dt1GetFunctionTestDelayTime:
|
||||
case BridgeOperation::dt1GetDurationTestDelayTime:
|
||||
case BridgeOperation::dt1GetFunctionTestInterval:
|
||||
case BridgeOperation::dt1GetDurationTestInterval:
|
||||
case BridgeOperation::dt1GetTestExecutionTimeout:
|
||||
case BridgeOperation::dt1GetProlongTime:
|
||||
case BridgeOperation::dt1GetDurationTestResult:
|
||||
case BridgeOperation::dt1GetLampEmergencyTime:
|
||||
case BridgeOperation::dt1GetLampTotalOperationTime:
|
||||
case BridgeOperation::dt1GetEmergencyMinLevel:
|
||||
case BridgeOperation::dt1GetEmergencyMaxLevel:
|
||||
case BridgeOperation::dt1GetRatedDuration:
|
||||
case BridgeOperation::dt1GetExtendedVersion:
|
||||
case BridgeOperation::dt1GetEmergencyDeviceType:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
@@ -42,6 +43,12 @@ enum class GatewayCacheDaliTargetKind : uint8_t {
|
||||
kBroadcast = 2,
|
||||
};
|
||||
|
||||
enum class GatewayCacheDaliPresence : uint8_t {
|
||||
kUnknown = 0,
|
||||
kOnline = 1,
|
||||
kOffline = 2,
|
||||
};
|
||||
|
||||
struct GatewayCacheDaliTarget {
|
||||
GatewayCacheDaliTargetKind kind{GatewayCacheDaliTargetKind::kShortAddress};
|
||||
uint8_t value{0};
|
||||
@@ -138,6 +145,12 @@ class GatewayCache {
|
||||
GatewayCacheChannelFlags channelFlags(uint8_t gateway_id);
|
||||
GatewayCacheChannelFlags pendingChannelFlags(uint8_t gateway_id);
|
||||
GatewayCacheDaliAddressState daliAddressState(uint8_t gateway_id, uint8_t short_address);
|
||||
GatewayCacheDaliPresence daliAddressPresence(uint8_t gateway_id, uint8_t short_address);
|
||||
void markDaliAddressPresence(uint8_t gateway_id, uint8_t short_address,
|
||||
GatewayCacheDaliPresence presence);
|
||||
std::optional<GatewayCacheDaliTarget> decodeDaliTarget(uint8_t raw_addr);
|
||||
std::vector<uint8_t> reconciliationAddresses(
|
||||
uint8_t gateway_id, std::optional<GatewayCacheDaliTarget> target);
|
||||
GatewayCacheDaliRuntimeStatus daliGroupStatus(uint8_t gateway_id, uint8_t group_id);
|
||||
GatewayCacheDaliRuntimeStatus daliBroadcastStatus(uint8_t gateway_id);
|
||||
bool setDaliGroupMask(uint8_t gateway_id, uint8_t short_address,
|
||||
@@ -183,6 +196,8 @@ class GatewayCache {
|
||||
bool commitStorageLocked();
|
||||
bool shouldTrackUpdateFlagsLocked() const;
|
||||
uint32_t nextDaliRuntimeRevisionLocked();
|
||||
void markDaliAddressPresenceLocked(uint8_t gateway_id, uint8_t short_address,
|
||||
GatewayCacheDaliPresence presence);
|
||||
bool mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr, uint8_t command);
|
||||
void clearDaliTargetStateLocked(uint8_t gateway_id, const GatewayCacheDaliTarget& target,
|
||||
uint32_t revision);
|
||||
@@ -225,6 +240,7 @@ class GatewayCache {
|
||||
std::map<uint8_t, SceneStore> scenes_;
|
||||
std::map<uint8_t, GroupStore> groups_;
|
||||
std::map<uint8_t, std::array<GatewayCacheDaliAddressState, 64>> dali_states_;
|
||||
std::map<uint8_t, std::array<GatewayCacheDaliPresence, 64>> dali_presence_;
|
||||
std::map<uint8_t, std::array<GatewayCacheDaliRuntimeStatus, 16>> dali_group_status_;
|
||||
std::map<uint8_t, GatewayCacheDaliRuntimeStatus> dali_broadcast_status_;
|
||||
std::map<uint8_t, DtrState> dtr_states_;
|
||||
|
||||
@@ -759,6 +759,86 @@ GatewayCacheDaliAddressState GatewayCache::daliAddressState(uint8_t gateway_id,
|
||||
return ensureDaliAddressStateLocked(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;
|
||||
}
|
||||
|
||||
void GatewayCache::markDaliAddressPresence(uint8_t gateway_id, uint8_t short_address,
|
||||
GatewayCacheDaliPresence presence) {
|
||||
LockGuard guard(lock_);
|
||||
markDaliAddressPresenceLocked(gateway_id, short_address, presence);
|
||||
}
|
||||
|
||||
std::optional<GatewayCacheDaliTarget> GatewayCache::decodeDaliTarget(uint8_t raw_addr) {
|
||||
return DecodeDaliTarget(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;
|
||||
}
|
||||
|
||||
GatewayCacheDaliRuntimeStatus GatewayCache::daliGroupStatus(uint8_t gateway_id,
|
||||
uint8_t group_id) {
|
||||
LockGuard guard(lock_);
|
||||
@@ -1535,6 +1615,20 @@ bool GatewayCache::shouldTrackUpdateFlagsLocked() const {
|
||||
return config_.cache_enabled && config_.reconciliation_enabled;
|
||||
}
|
||||
|
||||
void GatewayCache::markDaliAddressPresenceLocked(uint8_t gateway_id, uint8_t short_address,
|
||||
GatewayCacheDaliPresence presence) {
|
||||
if (short_address >= 64) {
|
||||
return;
|
||||
}
|
||||
auto& states = dali_presence_[gateway_id];
|
||||
const auto previous = states[short_address];
|
||||
states[short_address] = presence;
|
||||
if (previous != presence) {
|
||||
ESP_LOGD(kTag, "presence gateway=%u short=%u state=%u", gateway_id, short_address,
|
||||
static_cast<unsigned>(presence));
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCacheDaliAddressState& GatewayCache::ensureDaliAddressStateLocked(uint8_t gateway_id,
|
||||
uint8_t short_address) {
|
||||
auto [it, inserted] = dali_states_.try_emplace(gateway_id);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
@@ -36,6 +37,8 @@ struct GatewayControllerConfig {
|
||||
bool cache_supported{true};
|
||||
uint32_t cache_refresh_interval_ms{120000};
|
||||
uint32_t cache_refresh_idle_ms{100};
|
||||
uint32_t cache_host_snooze_ms{5000};
|
||||
uint32_t cache_host_echo_ms{250};
|
||||
};
|
||||
|
||||
struct GatewayChannelSnapshot {
|
||||
@@ -101,7 +104,9 @@ class GatewayController {
|
||||
|
||||
GatewayCacheChannelFlags flags{};
|
||||
Phase phase{Phase::kReloadFlags};
|
||||
uint8_t short_address{0};
|
||||
std::optional<GatewayCacheDaliTarget> target;
|
||||
std::vector<uint8_t> addresses;
|
||||
size_t address_index{0};
|
||||
uint8_t scene_id{0};
|
||||
};
|
||||
|
||||
@@ -120,9 +125,11 @@ class GatewayController {
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void dispatchCommand(const std::vector<uint8_t>& command);
|
||||
void scheduleReconciliation(uint8_t gateway_id);
|
||||
void scheduleReconciliation(uint8_t gateway_id,
|
||||
std::optional<GatewayCacheDaliTarget> target = std::nullopt);
|
||||
bool hasPendingReconciliation() const;
|
||||
bool cacheRefreshEnabled() const;
|
||||
bool cacheMaintenanceSnoozed(uint8_t gateway_id) const;
|
||||
bool runMaintenanceStep();
|
||||
bool runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job);
|
||||
bool runCacheRefreshStep();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace gateway {
|
||||
@@ -77,6 +78,43 @@ bool AnyFlagSet(const GatewayCacheChannelFlags& flags) {
|
||||
return flags.need_update_group || flags.need_update_scene || flags.need_update_settings;
|
||||
}
|
||||
|
||||
bool SameTarget(const std::optional<GatewayCacheDaliTarget>& lhs,
|
||||
const std::optional<GatewayCacheDaliTarget>& rhs) {
|
||||
if (lhs.has_value() != rhs.has_value()) {
|
||||
return false;
|
||||
}
|
||||
if (!lhs.has_value()) {
|
||||
return true;
|
||||
}
|
||||
return lhs->kind == rhs->kind && lhs->value == rhs->value;
|
||||
}
|
||||
|
||||
bool IsDaliHostCommandOpcode(uint8_t opcode) {
|
||||
switch (opcode) {
|
||||
case 0x07:
|
||||
case 0x08:
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x12:
|
||||
case 0x13:
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
case 0x17:
|
||||
case 0x18:
|
||||
case 0x30:
|
||||
case 0x32:
|
||||
case 0x37:
|
||||
case 0x38:
|
||||
case 0xA0:
|
||||
case 0xA2:
|
||||
case kBridgeTransportRequestOpcode:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string NormalizeName(std::string_view name) {
|
||||
std::string normalized(name);
|
||||
if (normalized.size() > kMaxNameBytes) {
|
||||
@@ -375,7 +413,8 @@ void GatewayController::taskLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::scheduleReconciliation(uint8_t gateway_id) {
|
||||
void GatewayController::scheduleReconciliation(uint8_t gateway_id,
|
||||
std::optional<GatewayCacheDaliTarget> target) {
|
||||
if (!cache_.reconciliationEnabled()) {
|
||||
return;
|
||||
}
|
||||
@@ -390,7 +429,16 @@ void GatewayController::scheduleReconciliation(uint8_t gateway_id) {
|
||||
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
reconciliation_jobs_.try_emplace(gateway_id);
|
||||
auto [it, inserted] = reconciliation_jobs_.try_emplace(gateway_id);
|
||||
if (inserted) {
|
||||
it->second.target = target;
|
||||
} else if (!SameTarget(it->second.target, target)) {
|
||||
it->second.target.reset();
|
||||
it->second.phase = ReconciliationJob::Phase::kReloadFlags;
|
||||
it->second.addresses.clear();
|
||||
it->second.address_index = 0;
|
||||
it->second.scene_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (task_handle_ != nullptr) {
|
||||
@@ -408,6 +456,11 @@ bool GatewayController::cacheRefreshEnabled() const {
|
||||
config_.cache_refresh_interval_ms > 0;
|
||||
}
|
||||
|
||||
bool GatewayController::cacheMaintenanceSnoozed(uint8_t gateway_id) const {
|
||||
return config_.cache_host_snooze_ms > 0 &&
|
||||
dali_domain_.hasRecentHostActivity(gateway_id, config_.cache_host_snooze_ms);
|
||||
}
|
||||
|
||||
bool GatewayController::runMaintenanceStep() {
|
||||
if (cache_.reconciliationEnabled()) {
|
||||
bool has_job = false;
|
||||
@@ -427,6 +480,9 @@ bool GatewayController::runMaintenanceStep() {
|
||||
if (runtime_.shouldYieldMaintenance(gateway_id)) {
|
||||
return false;
|
||||
}
|
||||
if (cacheMaintenanceSnoozed(gateway_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool keep_job = runReconciliationStep(gateway_id, job);
|
||||
|
||||
@@ -459,8 +515,13 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
|
||||
return false;
|
||||
}
|
||||
|
||||
job.short_address = 0;
|
||||
job.addresses = cache_.reconciliationAddresses(gateway_id, job.target);
|
||||
job.address_index = 0;
|
||||
job.scene_id = 0;
|
||||
if (job.addresses.empty()) {
|
||||
cache_.clearChannelFlagsIfMatched(gateway_id, job.flags);
|
||||
return false;
|
||||
}
|
||||
if (job.flags.need_update_group) {
|
||||
job.phase = ReconciliationJob::Phase::kGroups;
|
||||
} else if (job.flags.need_update_scene) {
|
||||
@@ -472,9 +533,9 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
|
||||
|
||||
switch (job.phase) {
|
||||
case ReconciliationJob::Phase::kGroups:
|
||||
reconcileGroupStep(gateway_id, job.short_address++);
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
reconcileGroupStep(gateway_id, job.addresses[job.address_index++]);
|
||||
if (job.address_index >= job.addresses.size()) {
|
||||
job.address_index = 0;
|
||||
if (job.flags.need_update_scene) {
|
||||
job.phase = ReconciliationJob::Phase::kScenes;
|
||||
} else if (job.flags.need_update_settings) {
|
||||
@@ -486,15 +547,22 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case ReconciliationJob::Phase::kScenes:
|
||||
reconcileSceneStep(gateway_id, job.short_address, job.scene_id);
|
||||
++job.scene_id;
|
||||
case ReconciliationJob::Phase::kScenes: {
|
||||
const uint8_t short_address = job.addresses[job.address_index];
|
||||
if (cache_.daliAddressPresence(gateway_id, short_address) ==
|
||||
GatewayCacheDaliPresence::kOffline) {
|
||||
job.scene_id = 0;
|
||||
++job.address_index;
|
||||
} else {
|
||||
reconcileSceneStep(gateway_id, short_address, job.scene_id);
|
||||
++job.scene_id;
|
||||
}
|
||||
if (job.scene_id >= kDaliSceneCount) {
|
||||
job.scene_id = 0;
|
||||
++job.short_address;
|
||||
++job.address_index;
|
||||
}
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
if (job.address_index >= job.addresses.size()) {
|
||||
job.address_index = 0;
|
||||
if (job.flags.need_update_settings) {
|
||||
job.phase = ReconciliationJob::Phase::kSettings;
|
||||
} else if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
|
||||
@@ -504,10 +572,15 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case ReconciliationJob::Phase::kSettings:
|
||||
reconcileSettingsStep(gateway_id, job.short_address++);
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
}
|
||||
case ReconciliationJob::Phase::kSettings: {
|
||||
const uint8_t short_address = job.addresses[job.address_index++];
|
||||
if (cache_.daliAddressPresence(gateway_id, short_address) !=
|
||||
GatewayCacheDaliPresence::kOffline) {
|
||||
reconcileSettingsStep(gateway_id, short_address);
|
||||
}
|
||||
if (job.address_index >= job.addresses.size()) {
|
||||
job.address_index = 0;
|
||||
if (!cache_.clearChannelFlagsIfMatched(gateway_id, job.flags)) {
|
||||
job.phase = ReconciliationJob::Phase::kReloadFlags;
|
||||
} else {
|
||||
@@ -515,6 +588,7 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case ReconciliationJob::Phase::kReloadFlags:
|
||||
default:
|
||||
return true;
|
||||
@@ -542,22 +616,36 @@ bool GatewayController::runCacheRefreshStep() {
|
||||
}
|
||||
if (runtime_.shouldYieldMaintenance(channel.gateway_id) ||
|
||||
dali_domain_.isAllocAddr(channel.gateway_id) ||
|
||||
cacheMaintenanceSnoozed(channel.gateway_id) ||
|
||||
!dali_domain_.isBusIdle(channel.gateway_id, config_.cache_refresh_idle_ms)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto advance_job = [&]() {
|
||||
++job.short_address;
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
job.next_due_tick = xTaskGetTickCount() + interval_ticks;
|
||||
} else {
|
||||
job.next_due_tick = xTaskGetTickCount();
|
||||
}
|
||||
};
|
||||
|
||||
if (cache_.daliAddressPresence(channel.gateway_id, job.short_address) ==
|
||||
GatewayCacheDaliPresence::kOffline) {
|
||||
advance_job();
|
||||
return true;
|
||||
}
|
||||
|
||||
maintenance_activity_gateway_.store(channel.gateway_id);
|
||||
const auto actual_level = dali_domain_.queryActualLevel(channel.gateway_id, job.short_address);
|
||||
maintenance_activity_gateway_.store(-1);
|
||||
cache_.markDaliAddressPresence(channel.gateway_id, job.short_address,
|
||||
actual_level.has_value()
|
||||
? GatewayCacheDaliPresence::kOnline
|
||||
: GatewayCacheDaliPresence::kOffline);
|
||||
cache_.setDaliActualLevel(channel.gateway_id, job.short_address, actual_level);
|
||||
|
||||
++job.short_address;
|
||||
if (job.short_address >= kDaliShortAddressCount) {
|
||||
job.short_address = 0;
|
||||
job.next_due_tick = xTaskGetTickCount() + interval_ticks;
|
||||
} else {
|
||||
job.next_due_tick = xTaskGetTickCount();
|
||||
}
|
||||
advance_job();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -573,6 +661,10 @@ void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_add
|
||||
const bool applied = dali_domain_.applyGroupMask(gateway_id, short_address, state.group_mask);
|
||||
maintenance_activity_gateway_.store(-1);
|
||||
const auto verified_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
|
||||
cache_.markDaliAddressPresence(gateway_id, short_address,
|
||||
verified_mask.has_value()
|
||||
? GatewayCacheDaliPresence::kOnline
|
||||
: GatewayCacheDaliPresence::kOffline);
|
||||
cache_.setDaliGroupMask(gateway_id, short_address, verified_mask);
|
||||
if (!applied && verified_mask.has_value()) {
|
||||
ESP_LOGW(kTag, "group reconcile fallback gateway=%u short=%u", gateway_id, short_address);
|
||||
@@ -580,8 +672,11 @@ void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_add
|
||||
return;
|
||||
}
|
||||
|
||||
cache_.setDaliGroupMask(gateway_id, short_address,
|
||||
dali_domain_.queryGroupMask(gateway_id, short_address));
|
||||
const auto group_mask = dali_domain_.queryGroupMask(gateway_id, short_address);
|
||||
cache_.markDaliAddressPresence(gateway_id, short_address,
|
||||
group_mask.has_value() ? GatewayCacheDaliPresence::kOnline
|
||||
: GatewayCacheDaliPresence::kOffline);
|
||||
cache_.setDaliGroupMask(gateway_id, short_address, group_mask);
|
||||
}
|
||||
|
||||
void GatewayController::reconcileSceneStep(uint8_t gateway_id, uint8_t short_address,
|
||||
@@ -596,8 +691,11 @@ void GatewayController::reconcileSceneStep(uint8_t gateway_id, uint8_t short_add
|
||||
maintenance_activity_gateway_.store(-1);
|
||||
}
|
||||
|
||||
cache_.setDaliSceneLevel(gateway_id, short_address, scene_id,
|
||||
dali_domain_.querySceneLevel(gateway_id, short_address, scene_id));
|
||||
const auto level = dali_domain_.querySceneLevel(gateway_id, short_address, scene_id);
|
||||
cache_.markDaliAddressPresence(gateway_id, short_address,
|
||||
level.has_value() ? GatewayCacheDaliPresence::kOnline
|
||||
: GatewayCacheDaliPresence::kOffline);
|
||||
cache_.setDaliSceneLevel(gateway_id, short_address, scene_id, level);
|
||||
}
|
||||
|
||||
void GatewayController::reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address) {
|
||||
@@ -618,6 +716,9 @@ void GatewayController::reconcileSettingsStep(uint8_t gateway_id, uint8_t short_
|
||||
}
|
||||
|
||||
const auto settings = dali_domain_.queryAddressSettings(gateway_id, short_address);
|
||||
cache_.markDaliAddressPresence(gateway_id, short_address,
|
||||
settings.has_value() ? GatewayCacheDaliPresence::kOnline
|
||||
: GatewayCacheDaliPresence::kOffline);
|
||||
if (settings.has_value()) {
|
||||
cache_.setDaliSettings(gateway_id, short_address,
|
||||
GatewayCacheDaliSettingsSnapshot{settings->power_on_level,
|
||||
@@ -645,6 +746,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
ESP_LOGW(kTag, "command for unknown gateway=%u opcode=0x%02x", gateway_id, opcode);
|
||||
return;
|
||||
}
|
||||
if (IsDaliHostCommandOpcode(opcode)) {
|
||||
dali_domain_.markHostActivity(gateway_id);
|
||||
}
|
||||
|
||||
switch (opcode) {
|
||||
case 0x00:
|
||||
@@ -1021,14 +1125,18 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
}
|
||||
|
||||
const bool maintenance_activity = maintenance_activity_gateway_.load() == frame.gateway_id;
|
||||
const bool host_echo_activity =
|
||||
dali_domain_.matchesRecentHostCommandFrame(frame.gateway_id, addr, data,
|
||||
config_.cache_host_echo_ms) ||
|
||||
dali_domain_.hasRecentHostActivity(frame.gateway_id, config_.cache_host_echo_ms);
|
||||
const bool local_activity = maintenance_activity || runtime_.hasActiveCommand(frame.gateway_id) ||
|
||||
dali_domain_.isAllocAddr(frame.gateway_id);
|
||||
host_echo_activity || dali_domain_.isAllocAddr(frame.gateway_id);
|
||||
const bool flagged = cache_.observeDaliCommand(frame.gateway_id, addr, data,
|
||||
local_activity
|
||||
? GatewayCacheRawFrameOrigin::kLocalGateway
|
||||
: GatewayCacheRawFrameOrigin::kOutsideBus);
|
||||
if (flagged) {
|
||||
scheduleReconciliation(frame.gateway_id);
|
||||
scheduleReconciliation(frame.gateway_id, cache_.decodeDaliTarget(addr));
|
||||
}
|
||||
|
||||
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) || maintenance_activity ||
|
||||
@@ -1057,26 +1165,31 @@ bool GatewayController::sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr
|
||||
}
|
||||
|
||||
bool GatewayController::setBrightAndMirror(uint8_t gateway_id, int dec_address, uint8_t level) {
|
||||
const uint8_t raw_addr = rawArcAddressFromDec(dec_address);
|
||||
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, level);
|
||||
const bool sent = dali_domain_.setBright(gateway_id, dec_address, level);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, rawArcAddressFromDec(dec_address), level);
|
||||
cache_.mirrorDaliCommand(gateway_id, raw_addr, level);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool GatewayController::offAndMirror(uint8_t gateway_id, int dec_address) {
|
||||
const uint8_t raw_addr = rawCommandAddressFromDec(dec_address);
|
||||
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, kDaliCmdOff);
|
||||
const bool sent = dali_domain_.off(gateway_id, dec_address);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address), kDaliCmdOff);
|
||||
cache_.mirrorDaliCommand(gateway_id, raw_addr, kDaliCmdOff);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
bool GatewayController::onAndMirror(uint8_t gateway_id, int dec_address) {
|
||||
const uint8_t raw_addr = rawCommandAddressFromDec(dec_address);
|
||||
dali_domain_.markHostCommandFrame(gateway_id, raw_addr, kDaliCmdRecallMax);
|
||||
const bool sent = dali_domain_.on(gateway_id, dec_address);
|
||||
if (sent) {
|
||||
cache_.mirrorDaliCommand(gateway_id, rawCommandAddressFromDec(dec_address),
|
||||
kDaliCmdRecallMax);
|
||||
cache_.mirrorDaliCommand(gateway_id, raw_addr, kDaliCmdRecallMax);
|
||||
}
|
||||
return sent;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user