feat(gateway_cache): enhance DALI state management and caching
- Increased flush interval to 10 seconds and added a refresh interval of 120 seconds in GatewayCacheConfig. - Introduced a new boolean `stale` in GatewayCacheDaliRuntimeStatus to track stale states. - Added methods for setting actual DALI levels and persisting DALI address states. - Implemented functions to build and apply DALI state payloads, including handling scene levels. - Enhanced the GatewayCache class to manage DALI states more effectively, including loading and persisting states. - Updated GatewayController to support cache refresh operations, including handling cache commands and reporting cache status. - Added mechanisms for periodic cache refresh based on idle time and configured intervals. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -126,6 +126,7 @@ class DaliDomainService {
|
||||
void addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink);
|
||||
|
||||
bool resetBus(uint8_t gateway_id) const;
|
||||
bool isBusIdle(uint8_t gateway_id, uint32_t quiet_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;
|
||||
@@ -163,6 +164,7 @@ class DaliDomainService {
|
||||
bool on(uint8_t gateway_id, int short_address) const;
|
||||
bool off(uint8_t gateway_id, int short_address) const;
|
||||
bool off(int short_address) const;
|
||||
std::optional<uint8_t> queryActualLevel(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<uint16_t> queryGroupMask(uint8_t gateway_id, int short_address) const;
|
||||
std::optional<uint8_t> querySceneLevel(uint8_t gateway_id, int short_address, int scene) const;
|
||||
std::optional<DaliAddressSettingsSnapshot> queryAddressSettings(uint8_t gateway_id,
|
||||
@@ -195,10 +197,13 @@ class DaliDomainService {
|
||||
static void RawFrameTaskEntry(void* arg);
|
||||
void rawFrameTaskLoop();
|
||||
void notifyRawFrameSinks(const DaliRawFrame& frame);
|
||||
void markBusActivity(uint8_t gateway_id) const;
|
||||
|
||||
std::vector<std::unique_ptr<DaliChannel>> channels_;
|
||||
std::vector<std::function<void(const DaliRawFrame& frame)>> raw_frame_sinks_;
|
||||
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_;
|
||||
TaskHandle_t raw_frame_task_handle_{nullptr};
|
||||
};
|
||||
|
||||
|
||||
@@ -314,11 +314,21 @@ struct DaliDomainService::DaliChannel {
|
||||
};
|
||||
|
||||
DaliDomainService::DaliDomainService()
|
||||
: raw_frame_sink_lock_(xSemaphoreCreateMutex()) {
|
||||
: raw_frame_sink_lock_(xSemaphoreCreateMutex()),
|
||||
bus_activity_lock_(xSemaphoreCreateMutex()) {
|
||||
esp_log_level_set(TAG, (esp_log_level_t)CONFIG_DALI_LOG_LEVEL);
|
||||
}
|
||||
|
||||
DaliDomainService::~DaliDomainService() = default;
|
||||
DaliDomainService::~DaliDomainService() {
|
||||
if (raw_frame_sink_lock_ != nullptr) {
|
||||
vSemaphoreDelete(raw_frame_sink_lock_);
|
||||
raw_frame_sink_lock_ = nullptr;
|
||||
}
|
||||
if (bus_activity_lock_ != nullptr) {
|
||||
vSemaphoreDelete(bus_activity_lock_);
|
||||
bus_activity_lock_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) {
|
||||
if (!hooks.send) {
|
||||
@@ -533,12 +543,38 @@ void DaliDomainService::addRawFrameSink(std::function<void(const DaliRawFrame& f
|
||||
|
||||
bool DaliDomainService::resetBus(uint8_t gateway_id) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->comm != nullptr && channel->comm->resetBus();
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->comm->resetBus();
|
||||
}
|
||||
|
||||
bool DaliDomainService::isBusIdle(uint8_t gateway_id, uint32_t quiet_ms) const {
|
||||
TickType_t last_activity = 0;
|
||||
if (bus_activity_lock_ != nullptr) {
|
||||
xSemaphoreTake(bus_activity_lock_, portMAX_DELAY);
|
||||
}
|
||||
if (const auto it = last_bus_activity_ticks_.find(gateway_id);
|
||||
it != last_bus_activity_ticks_.end()) {
|
||||
last_activity = it->second;
|
||||
}
|
||||
if (bus_activity_lock_ != nullptr) {
|
||||
xSemaphoreGive(bus_activity_lock_);
|
||||
}
|
||||
if (last_activity == 0) {
|
||||
return true;
|
||||
}
|
||||
return (xTaskGetTickCount() - last_activity) >= pdMS_TO_TICKS(quiet_ms);
|
||||
}
|
||||
|
||||
bool DaliDomainService::writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->hooks.send && channel->hooks.send(data, len);
|
||||
if (channel == nullptr || !channel->hooks.send) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->hooks.send(data, len);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DaliDomainService::transactBridgeFrame(uint8_t gateway_id,
|
||||
@@ -548,17 +584,26 @@ std::vector<uint8_t> DaliDomainService::transactBridgeFrame(uint8_t gateway_id,
|
||||
if (channel == nullptr || !channel->hooks.transact) {
|
||||
return {};
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->hooks.transact(data, len);
|
||||
}
|
||||
|
||||
bool DaliDomainService::sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->comm != nullptr && channel->comm->sendRawNew(raw_addr, command);
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->comm->sendRawNew(raw_addr, command);
|
||||
}
|
||||
|
||||
bool DaliDomainService::sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->comm != nullptr && channel->comm->sendExtRawNew(raw_addr, command);
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->comm->sendExtRawNew(raw_addr, command);
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t raw_addr,
|
||||
@@ -567,6 +612,7 @@ std::optional<uint8_t> DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t r
|
||||
if (channel == nullptr || channel->comm == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->comm->queryRawNew(raw_addr, command);
|
||||
}
|
||||
|
||||
@@ -577,6 +623,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const std::vector<int> fallback = fallback_types.empty() ? std::vector<int>{1, 4, 5, 6, 8}
|
||||
: fallback_types;
|
||||
auto discovery = channel->dali->base.discoverDeviceTypes(short_address, fallback,
|
||||
@@ -598,6 +645,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::baseStatusSnapshot(
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const auto raw_status = channel->dali->base.getStatus(short_address);
|
||||
if (!raw_status.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -623,6 +671,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt1Snapshot(uint8_t gateway
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const auto detailed = channel->dali->dt1.getDT1TestStatusDetailed(short_address);
|
||||
if (!detailed.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -705,6 +754,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt4");
|
||||
auto& dt4 = channel->dali->dt4;
|
||||
PutOptionalInt(snapshot, "extendedVersion", dt4.getExtendedVersion(short_address));
|
||||
@@ -787,6 +837,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt5Snapshot(uint8_t gateway
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt5");
|
||||
auto& dt5 = channel->dali->dt5;
|
||||
PutOptionalInt(snapshot, "extendedVersion", dt5.getExtendedVersion(short_address));
|
||||
@@ -828,6 +879,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt6Snapshot(uint8_t gateway
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt6");
|
||||
auto& dt6 = channel->dali->dt6;
|
||||
PutOptionalInt(snapshot, "extendedVersion", dt6.getExtendedVersion(short_address));
|
||||
@@ -891,6 +943,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8StatusSnapshot(
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
|
||||
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_status");
|
||||
bool has_data = false;
|
||||
@@ -925,6 +978,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const auto report = channel->dali->dt8.getSceneColorReport(short_address, scene);
|
||||
if (!report.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -948,6 +1002,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8PowerOnLevelColorReport(
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const auto report = channel->dali->dt8.getPowerOnLevelColorReport(short_address);
|
||||
if (!report.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -970,6 +1025,7 @@ std::optional<DaliDomainSnapshot> DaliDomainService::dt8SystemFailureLevelColorR
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const auto report = channel->dali->dt8.getSystemFailureLevelColorReport(short_address);
|
||||
if (!report.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -992,8 +1048,11 @@ bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_addr
|
||||
int color_temperature, int red, int green,
|
||||
int blue) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.storeSceneSnapshot(short_address, scene, brightness,
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.storeSceneSnapshot(short_address, scene, brightness,
|
||||
ToDaliCppColorMode(color_mode), color_temperature,
|
||||
red, green, blue);
|
||||
}
|
||||
@@ -1001,56 +1060,85 @@ bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_addr
|
||||
bool DaliDomainService::storeDt8PowerOnLevelSnapshot(uint8_t gateway_id, int short_address,
|
||||
int level) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.storePowerOnLevelSnapshot(short_address, level);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.storePowerOnLevelSnapshot(short_address, level);
|
||||
}
|
||||
|
||||
bool DaliDomainService::storeDt8SystemFailureLevelSnapshot(uint8_t gateway_id,
|
||||
int short_address, int level) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.storeSystemFailureLevelSnapshot(short_address, level);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.storeSystemFailureLevelSnapshot(short_address, level);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setBright(uint8_t gateway_id, int short_address, int brightness) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->base.setBright(short_address, brightness);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->base.setBright(short_address, brightness);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.setColTempRaw(short_address, mirek);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.setColTempRaw(short_address, mirek);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColTemp(uint8_t gateway_id, int short_address, int kelvin) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.setColorTemperature(short_address, kelvin);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.setColorTemperature(short_address, kelvin);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.setColourRaw(raw_addr, x, y);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.setColourRaw(raw_addr, x, y);
|
||||
}
|
||||
|
||||
bool DaliDomainService::setColourRGB(uint8_t gateway_id, int short_address, int r, int g,
|
||||
int b) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->dt8.setColourRGB(short_address, r, g, b);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->dt8.setColourRGB(short_address, r, g, b);
|
||||
}
|
||||
|
||||
bool DaliDomainService::on(uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr && channel->dali->base.on(short_address);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->base.on(short_address);
|
||||
}
|
||||
|
||||
bool DaliDomainService::off(uint8_t gateway_id, int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr && channel->dali->base.off(short_address);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->base.off(short_address);
|
||||
}
|
||||
|
||||
bool DaliDomainService::off(int short_address) const {
|
||||
@@ -1061,6 +1149,20 @@ bool DaliDomainService::off(int short_address) const {
|
||||
return off(channels_.front()->config.gateway_id, short_address);
|
||||
}
|
||||
|
||||
std::optional<uint8_t> DaliDomainService::queryActualLevel(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
const auto level = channel->dali->base.getBright(short_address);
|
||||
if (!level.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return static_cast<uint8_t>(std::clamp(*level, 0, 254));
|
||||
}
|
||||
|
||||
std::optional<uint16_t> DaliDomainService::queryGroupMask(uint8_t gateway_id,
|
||||
int short_address) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
@@ -1068,6 +1170,8 @@ std::optional<uint16_t> DaliDomainService::queryGroupMask(uint8_t gateway_id,
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
markBusActivity(gateway_id);
|
||||
|
||||
const auto group_mask = channel->dali->base.getGroup(short_address);
|
||||
if (!group_mask.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -1083,6 +1187,8 @@ std::optional<uint8_t> DaliDomainService::querySceneLevel(uint8_t gateway_id, in
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
markBusActivity(gateway_id);
|
||||
|
||||
const auto level = channel->dali->base.getScene(short_address, scene);
|
||||
if (!level.has_value()) {
|
||||
return std::nullopt;
|
||||
@@ -1098,6 +1204,8 @@ std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettin
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
markBusActivity(gateway_id);
|
||||
|
||||
DaliAddressSettingsSnapshot settings{};
|
||||
|
||||
if (const auto value = channel->dali->base.getPowerOnLevel(short_address); value.has_value()) {
|
||||
@@ -1130,8 +1238,11 @@ std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettin
|
||||
bool DaliDomainService::applyGroupMask(uint8_t gateway_id, int short_address,
|
||||
uint16_t group_mask) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->dali != nullptr &&
|
||||
channel->dali->base.setGroup(short_address, group_mask);
|
||||
if (channel == nullptr || channel->dali == nullptr) {
|
||||
return false;
|
||||
}
|
||||
markBusActivity(gateway_id);
|
||||
return channel->dali->base.setGroup(short_address, group_mask);
|
||||
}
|
||||
|
||||
bool DaliDomainService::applySceneLevel(uint8_t gateway_id, int short_address, int scene,
|
||||
@@ -1141,6 +1252,8 @@ bool DaliDomainService::applySceneLevel(uint8_t gateway_id, int short_address, i
|
||||
return false;
|
||||
}
|
||||
|
||||
markBusActivity(gateway_id);
|
||||
|
||||
if (*level == 255U) {
|
||||
return channel->dali->base.removeScene(short_address, scene);
|
||||
}
|
||||
@@ -1156,6 +1269,8 @@ bool DaliDomainService::applyAddressSettings(uint8_t gateway_id, int short_addre
|
||||
return false;
|
||||
}
|
||||
|
||||
markBusActivity(gateway_id);
|
||||
|
||||
bool ok = true;
|
||||
if (settings.power_on_level.has_value()) {
|
||||
ok = ok && channel->dali->base.setPowerOnLevel(short_address, *settings.power_on_level);
|
||||
@@ -1375,6 +1490,7 @@ void DaliDomainService::rawFrameTaskLoop() {
|
||||
}
|
||||
|
||||
void DaliDomainService::notifyRawFrameSinks(const DaliRawFrame& frame) {
|
||||
markBusActivity(frame.gateway_id);
|
||||
if (raw_frame_sink_lock_ != nullptr) {
|
||||
xSemaphoreTake(raw_frame_sink_lock_, portMAX_DELAY);
|
||||
}
|
||||
@@ -1387,6 +1503,16 @@ void DaliDomainService::notifyRawFrameSinks(const DaliRawFrame& frame) {
|
||||
}
|
||||
}
|
||||
|
||||
void DaliDomainService::markBusActivity(uint8_t gateway_id) const {
|
||||
if (bus_activity_lock_ != nullptr) {
|
||||
xSemaphoreTake(bus_activity_lock_, portMAX_DELAY);
|
||||
}
|
||||
last_bus_activity_ticks_[gateway_id] = xTaskGetTickCount();
|
||||
if (bus_activity_lock_ != nullptr) {
|
||||
xSemaphoreGive(bus_activity_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
bool DaliDomainService::hasSerialPort(int uart_port) const {
|
||||
return std::any_of(channels_.begin(), channels_.end(), [uart_port](const auto& channel) {
|
||||
return channel->serial_bus.has_value() && channel->serial_bus->uart_port == uart_port;
|
||||
|
||||
@@ -24,7 +24,8 @@ struct GatewayCacheConfig {
|
||||
bool cache_enabled{true};
|
||||
bool reconciliation_enabled{true};
|
||||
bool full_state_mirror_enabled{false};
|
||||
uint32_t flush_interval_ms{5000};
|
||||
uint32_t flush_interval_ms{10000};
|
||||
uint32_t refresh_interval_ms{120000};
|
||||
uint32_t task_stack_size{4096};
|
||||
UBaseType_t task_priority{3};
|
||||
GatewayCachePriorityMode default_priority_mode{GatewayCachePriorityMode::kOutsideBusFirst};
|
||||
@@ -71,6 +72,7 @@ struct GatewayCacheDaliRuntimeStatus {
|
||||
std::optional<uint8_t> actual_level;
|
||||
std::optional<uint8_t> scene_id;
|
||||
bool use_min_level{false};
|
||||
bool stale{false};
|
||||
uint32_t revision{0};
|
||||
|
||||
bool anyKnown() const {
|
||||
@@ -144,6 +146,8 @@ class GatewayCache {
|
||||
std::optional<uint8_t> level);
|
||||
bool setDaliSettings(uint8_t gateway_id, uint8_t short_address,
|
||||
std::optional<GatewayCacheDaliSettingsSnapshot> settings);
|
||||
bool setDaliActualLevel(uint8_t gateway_id, uint8_t short_address,
|
||||
std::optional<uint8_t> level);
|
||||
bool clearChannelFlagsIfMatched(uint8_t gateway_id, const GatewayCacheChannelFlags& flags);
|
||||
void markGroupUpdateNeeded(uint8_t gateway_id, bool needed = true);
|
||||
void markSceneUpdateNeeded(uint8_t gateway_id, bool needed = true);
|
||||
@@ -174,6 +178,8 @@ class GatewayCache {
|
||||
void closeStorageLocked();
|
||||
bool persistSceneLocked(uint8_t gateway_id, uint8_t scene_id, const SceneEntry& scene);
|
||||
bool persistGroupLocked(uint8_t gateway_id, uint8_t group_id, const GroupEntry& group);
|
||||
bool persistDaliAddressStateLocked(uint8_t gateway_id, uint8_t short_address,
|
||||
const GatewayCacheDaliAddressState& state);
|
||||
bool commitStorageLocked();
|
||||
bool shouldTrackUpdateFlagsLocked() const;
|
||||
uint32_t nextDaliRuntimeRevisionLocked();
|
||||
@@ -205,6 +211,8 @@ class GatewayCache {
|
||||
GroupStore& ensureGroupStoreLocked(uint8_t gateway_id);
|
||||
void loadSceneStoreLocked(uint8_t gateway_id, SceneStore& scenes);
|
||||
void loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups);
|
||||
void loadDaliStateStoreLocked(uint8_t gateway_id,
|
||||
std::array<GatewayCacheDaliAddressState, 64>& states);
|
||||
std::string readStringLocked(std::string_view key);
|
||||
bool writeStringLocked(std::string_view key, std::string_view value);
|
||||
bool eraseKeyLocked(std::string_view key);
|
||||
|
||||
@@ -43,6 +43,18 @@ constexpr uint8_t kDaliCmdDt8StoreDtrAsColorX = 0xE0;
|
||||
constexpr uint8_t kDaliCmdDt8StoreDtrAsColorY = 0xE1;
|
||||
constexpr uint8_t kDaliCmdDt8StorePrimaryMin = 0xF0;
|
||||
constexpr uint8_t kDaliCmdDt8StartAutoCalibration = 0xF6;
|
||||
constexpr int kDaliStatePayloadVersion = 1;
|
||||
constexpr uint32_t kDaliStateGroupMaskKnown = 1U << 0;
|
||||
constexpr uint32_t kDaliStateActualKnown = 1U << 1;
|
||||
constexpr uint32_t kDaliStateSceneKnown = 1U << 2;
|
||||
constexpr uint32_t kDaliStateUseMinLevel = 1U << 3;
|
||||
constexpr uint32_t kDaliStateStatusStale = 1U << 4;
|
||||
constexpr uint32_t kDaliStatePowerOnKnown = 1U << 5;
|
||||
constexpr uint32_t kDaliStateSystemFailureKnown = 1U << 6;
|
||||
constexpr uint32_t kDaliStateMinKnown = 1U << 7;
|
||||
constexpr uint32_t kDaliStateMaxKnown = 1U << 8;
|
||||
constexpr uint32_t kDaliStateFadeTimeKnown = 1U << 9;
|
||||
constexpr uint32_t kDaliStateFadeRateKnown = 1U << 10;
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
@@ -134,6 +146,18 @@ bool ShouldMirrorObservedMutation(GatewayCacheRawFrameOrigin origin,
|
||||
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;
|
||||
@@ -215,6 +239,139 @@ std::string BuildGroupPayload(const GatewayCache::GroupEntry& group) {
|
||||
return std::string(payload);
|
||||
}
|
||||
|
||||
uint8_t ByteValue(int value) {
|
||||
return static_cast<uint8_t>(std::clamp(value, 0, 255));
|
||||
}
|
||||
|
||||
uint16_t WordValue(int value) {
|
||||
return static_cast<uint16_t>(std::clamp(value, 0, 0xffff));
|
||||
}
|
||||
|
||||
uint16_t SceneKnownMask(const GatewayCacheDaliAddressState& state) {
|
||||
uint16_t mask = 0;
|
||||
for (size_t index = 0; index < state.scene_levels.size(); ++index) {
|
||||
if (state.scene_levels[index].has_value()) {
|
||||
mask |= static_cast<uint16_t>(1U << index);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
bool IsDefaultDaliAddressState(const GatewayCacheDaliAddressState& state) {
|
||||
return !state.group_mask_known && state.group_mask == 0 && SceneKnownMask(state) == 0 &&
|
||||
!state.settings.anyKnown() && !state.status.anyKnown();
|
||||
}
|
||||
|
||||
uint32_t DaliStateFlags(const GatewayCacheDaliAddressState& state) {
|
||||
uint32_t flags = 0;
|
||||
if (state.group_mask_known) {
|
||||
flags |= kDaliStateGroupMaskKnown;
|
||||
}
|
||||
if (state.status.actual_level.has_value()) {
|
||||
flags |= kDaliStateActualKnown;
|
||||
}
|
||||
if (state.status.scene_id.has_value()) {
|
||||
flags |= kDaliStateSceneKnown;
|
||||
}
|
||||
if (state.status.use_min_level) {
|
||||
flags |= kDaliStateUseMinLevel;
|
||||
}
|
||||
if (state.status.stale) {
|
||||
flags |= kDaliStateStatusStale;
|
||||
}
|
||||
if (state.settings.power_on_level.has_value()) {
|
||||
flags |= kDaliStatePowerOnKnown;
|
||||
}
|
||||
if (state.settings.system_failure_level.has_value()) {
|
||||
flags |= kDaliStateSystemFailureKnown;
|
||||
}
|
||||
if (state.settings.min_level.has_value()) {
|
||||
flags |= kDaliStateMinKnown;
|
||||
}
|
||||
if (state.settings.max_level.has_value()) {
|
||||
flags |= kDaliStateMaxKnown;
|
||||
}
|
||||
if (state.settings.fade_time.has_value()) {
|
||||
flags |= kDaliStateFadeTimeKnown;
|
||||
}
|
||||
if (state.settings.fade_rate.has_value()) {
|
||||
flags |= kDaliStateFadeRateKnown;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
std::string BuildDaliStatePayload(const GatewayCacheDaliAddressState& state) {
|
||||
const uint16_t scene_known_mask = SceneKnownMask(state);
|
||||
std::string payload = std::to_string(kDaliStatePayloadVersion);
|
||||
payload += "," + std::to_string(DaliStateFlags(state));
|
||||
payload += "," + std::to_string(state.status.revision);
|
||||
payload += "," + std::to_string(state.group_mask);
|
||||
payload += "," + std::to_string(state.status.actual_level.value_or(0));
|
||||
payload += "," + std::to_string(state.status.scene_id.value_or(0));
|
||||
payload += "," + std::to_string(state.settings.power_on_level.value_or(0));
|
||||
payload += "," + std::to_string(state.settings.system_failure_level.value_or(0));
|
||||
payload += "," + std::to_string(state.settings.min_level.value_or(0));
|
||||
payload += "," + std::to_string(state.settings.max_level.value_or(0));
|
||||
payload += "," + std::to_string(state.settings.fade_time.value_or(0));
|
||||
payload += "," + std::to_string(state.settings.fade_rate.value_or(0));
|
||||
payload += "," + std::to_string(scene_known_mask);
|
||||
for (const auto& scene_level : state.scene_levels) {
|
||||
payload += "," + std::to_string(scene_level.value_or(255));
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
void ApplyDaliStatePayload(std::string_view raw, GatewayCacheDaliAddressState& state) {
|
||||
const auto values = ParseCsv(raw);
|
||||
if (values.size() < 13 || values[0] != kDaliStatePayloadVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t flags = static_cast<uint32_t>(std::max(values[1], 0));
|
||||
state.group_mask_known = (flags & kDaliStateGroupMaskKnown) != 0;
|
||||
state.group_mask = state.group_mask_known ? WordValue(values[3]) : 0;
|
||||
|
||||
state.status = {};
|
||||
state.status.revision = static_cast<uint32_t>(std::max(values[2], 0));
|
||||
state.status.stale = (flags & kDaliStateStatusStale) != 0;
|
||||
state.status.use_min_level = (flags & kDaliStateUseMinLevel) != 0;
|
||||
if ((flags & kDaliStateActualKnown) != 0) {
|
||||
state.status.actual_level = ByteValue(values[4]);
|
||||
}
|
||||
if ((flags & kDaliStateSceneKnown) != 0) {
|
||||
state.status.scene_id = static_cast<uint8_t>(std::min<int>(ByteValue(values[5]), 15));
|
||||
}
|
||||
|
||||
state.settings = {};
|
||||
if ((flags & kDaliStatePowerOnKnown) != 0) {
|
||||
state.settings.power_on_level = ByteValue(values[6]);
|
||||
}
|
||||
if ((flags & kDaliStateSystemFailureKnown) != 0) {
|
||||
state.settings.system_failure_level = ByteValue(values[7]);
|
||||
}
|
||||
if ((flags & kDaliStateMinKnown) != 0) {
|
||||
state.settings.min_level = ByteValue(values[8]);
|
||||
}
|
||||
if ((flags & kDaliStateMaxKnown) != 0) {
|
||||
state.settings.max_level = ByteValue(values[9]);
|
||||
}
|
||||
if ((flags & kDaliStateFadeTimeKnown) != 0) {
|
||||
state.settings.fade_time = ByteValue(values[10]);
|
||||
}
|
||||
if ((flags & kDaliStateFadeRateKnown) != 0) {
|
||||
state.settings.fade_rate = ByteValue(values[11]);
|
||||
}
|
||||
|
||||
state.scene_levels.fill(std::nullopt);
|
||||
const uint16_t scene_known_mask = WordValue(values[12]);
|
||||
for (uint8_t scene_id = 0; scene_id < state.scene_levels.size(); ++scene_id) {
|
||||
const size_t value_index = 13 + scene_id;
|
||||
if ((scene_known_mask & (1U << scene_id)) != 0 && value_index < values.size()) {
|
||||
state.scene_levels[scene_id] = ByteValue(values[value_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GatewayCache::GatewayCache(GatewayCacheConfig config)
|
||||
@@ -269,8 +426,11 @@ esp_err_t GatewayCache::start() {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag, "cache started namespace=%s flush_interval_ms=%u reconciliation=%d full_mirror=%d",
|
||||
ESP_LOGI(kTag,
|
||||
"cache started namespace=%s flush_interval_ms=%u refresh_interval_ms=%u "
|
||||
"reconciliation=%d full_mirror=%d",
|
||||
config_.storage_namespace.c_str(), static_cast<unsigned>(config_.flush_interval_ms),
|
||||
static_cast<unsigned>(config_.refresh_interval_ms),
|
||||
config_.reconciliation_enabled, config_.full_state_mirror_enabled);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -282,6 +442,10 @@ void GatewayCache::preloadChannel(uint8_t gateway_id) {
|
||||
}
|
||||
ensureSceneStoreLocked(gateway_id);
|
||||
ensureGroupStoreLocked(gateway_id);
|
||||
auto [it, inserted] = dali_states_.try_emplace(gateway_id);
|
||||
if (inserted) {
|
||||
loadDaliStateStoreLocked(gateway_id, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
GatewayCache::SceneStore GatewayCache::scenes(uint8_t gateway_id) {
|
||||
@@ -620,6 +784,7 @@ bool GatewayCache::setDaliGroupMask(uint8_t gateway_id, uint8_t 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;
|
||||
}
|
||||
|
||||
@@ -632,6 +797,7 @@ bool GatewayCache::setDaliSceneLevel(uint8_t gateway_id, uint8_t short_address,
|
||||
|
||||
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
|
||||
state.scene_levels[scene_id] = level;
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -644,6 +810,31 @@ bool GatewayCache::setDaliSettings(uint8_t gateway_id, uint8_t short_address,
|
||||
|
||||
auto& state = ensureDaliAddressStateLocked(gateway_id, short_address);
|
||||
state.settings = settings.value_or(GatewayCacheDaliSettingsSnapshot{});
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -712,7 +903,8 @@ bool GatewayCache::observeDaliCommand(uint8_t gateway_id, uint8_t raw_addr, uint
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ShouldMirrorObservedMutation(origin, priority_mode_)) {
|
||||
if (ShouldAlwaysMirrorObservedStatus(raw_addr, command) ||
|
||||
ShouldMirrorObservedMutation(origin, priority_mode_)) {
|
||||
mirrorDaliCommandLocked(gateway_id, raw_addr, command);
|
||||
}
|
||||
|
||||
@@ -791,12 +983,15 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
|
||||
GatewayCacheDaliRuntimeStatus status;
|
||||
status.actual_level = command;
|
||||
status.revision = nextDaliRuntimeRevisionLocked();
|
||||
status.stale = false;
|
||||
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (command == kDaliCmdReset) {
|
||||
clearDaliTargetStateLocked(gateway_id, *target, nextDaliRuntimeRevisionLocked());
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -804,7 +999,9 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
|
||||
GatewayCacheDaliRuntimeStatus status;
|
||||
status.actual_level = command == kDaliCmdOff ? 0 : 254;
|
||||
status.revision = nextDaliRuntimeRevisionLocked();
|
||||
status.stale = false;
|
||||
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -812,7 +1009,9 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
|
||||
GatewayCacheDaliRuntimeStatus status;
|
||||
status.use_min_level = true;
|
||||
status.revision = nextDaliRuntimeRevisionLocked();
|
||||
status.stale = false;
|
||||
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -820,7 +1019,9 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
|
||||
GatewayCacheDaliRuntimeStatus status;
|
||||
status.scene_id = static_cast<uint8_t>(command - kDaliCmdGoToSceneMin);
|
||||
status.revision = nextDaliRuntimeRevisionLocked();
|
||||
status.stale = false;
|
||||
applyDaliTargetRuntimeStatusLocked(gateway_id, *target, status);
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -828,6 +1029,7 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
|
||||
applyDaliTargetGroupMutationLocked(gateway_id, *target,
|
||||
static_cast<uint8_t>(command & 0x0F),
|
||||
command < (kDaliCmdAddToGroupMin + 16));
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -836,6 +1038,7 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
|
||||
applyDaliTargetSceneLevelLocked(gateway_id, *target,
|
||||
static_cast<uint8_t>(command - kDaliCmdSetSceneMin),
|
||||
*dtr_state.dtr0);
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -843,12 +1046,14 @@ bool GatewayCache::mirrorDaliCommandLocked(uint8_t gateway_id, uint8_t raw_addr,
|
||||
applyDaliTargetSceneLevelLocked(
|
||||
gateway_id, *target, static_cast<uint8_t>(command - (kDaliCmdSetSceneMin + 16)),
|
||||
static_cast<uint8_t>(255U));
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (command >= kDaliCmdStoreDtrAsMaxLevel && command <= kDaliCmdStoreDtrAsFadeRate &&
|
||||
dtr_state.dtr0.has_value()) {
|
||||
applyDaliTargetSettingsLocked(gateway_id, *target, command, *dtr_state.dtr0);
|
||||
dirty_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -977,6 +1182,7 @@ void GatewayCache::applyDaliRuntimeStatusToAddressLocked(
|
||||
}
|
||||
}
|
||||
state.status.revision = status.revision;
|
||||
state.status.stale = status.stale;
|
||||
}
|
||||
|
||||
void GatewayCache::applyDaliTargetGroupMutationLocked(uint8_t gateway_id,
|
||||
@@ -1127,7 +1333,9 @@ void GatewayCache::refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id,
|
||||
|
||||
if (const auto broadcast = dali_broadcast_status_.find(gateway_id);
|
||||
broadcast != dali_broadcast_status_.end()) {
|
||||
applyDaliRuntimeStatusToAddressLocked(state, broadcast->second);
|
||||
if (!broadcast->second.stale) {
|
||||
applyDaliRuntimeStatusToAddressLocked(state, broadcast->second);
|
||||
}
|
||||
}
|
||||
|
||||
const auto groups = dali_group_status_.find(gateway_id);
|
||||
@@ -1137,6 +1345,9 @@ void GatewayCache::refreshDaliAddressAggregateStatusLocked(uint8_t gateway_id,
|
||||
for (uint8_t group_id = 0; group_id < groups->second.size(); ++group_id) {
|
||||
const uint16_t bit = static_cast<uint16_t>(1U << group_id);
|
||||
if ((state.group_mask & bit) != 0) {
|
||||
if (groups->second[group_id].stale) {
|
||||
continue;
|
||||
}
|
||||
applyDaliRuntimeStatusToAddressLocked(state, groups->second[group_id]);
|
||||
}
|
||||
}
|
||||
@@ -1209,6 +1420,14 @@ bool GatewayCache::flushDirty() {
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [gateway_id, states] : dali_states_) {
|
||||
for (uint8_t short_address = 0; short_address < states.size(); ++short_address) {
|
||||
if (!persistDaliAddressStateLocked(gateway_id, short_address, states[short_address])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const esp_err_t commit_err = nvs_commit(storage_);
|
||||
if (commit_err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "cache commit failed: %s", esp_err_to_name(commit_err));
|
||||
@@ -1288,6 +1507,18 @@ bool GatewayCache::persistGroupLocked(uint8_t gateway_id, uint8_t group_id,
|
||||
return commitStorageLocked();
|
||||
}
|
||||
|
||||
bool GatewayCache::persistDaliAddressStateLocked(
|
||||
uint8_t gateway_id, uint8_t short_address, const GatewayCacheDaliAddressState& state) {
|
||||
if (short_address >= 64) {
|
||||
return false;
|
||||
}
|
||||
if (!IsDefaultDaliAddressState(state)) {
|
||||
return writeStringLocked(ShortKey("ds", gateway_id, short_address),
|
||||
BuildDaliStatePayload(state));
|
||||
}
|
||||
return eraseKeyLocked(ShortKey("ds", gateway_id, short_address));
|
||||
}
|
||||
|
||||
bool GatewayCache::commitStorageLocked() {
|
||||
if (storage_ == 0) {
|
||||
return false;
|
||||
@@ -1307,7 +1538,9 @@ bool GatewayCache::shouldTrackUpdateFlagsLocked() const {
|
||||
GatewayCacheDaliAddressState& GatewayCache::ensureDaliAddressStateLocked(uint8_t gateway_id,
|
||||
uint8_t short_address) {
|
||||
auto [it, inserted] = dali_states_.try_emplace(gateway_id);
|
||||
(void)inserted;
|
||||
if (inserted) {
|
||||
loadDaliStateStoreLocked(gateway_id, it->second);
|
||||
}
|
||||
return it->second[short_address];
|
||||
}
|
||||
|
||||
@@ -1377,6 +1610,23 @@ void GatewayCache::loadGroupStoreLocked(uint8_t gateway_id, GroupStore& groups)
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayCache::loadDaliStateStoreLocked(
|
||||
uint8_t gateway_id, std::array<GatewayCacheDaliAddressState, 64>& states) {
|
||||
for (uint8_t short_address = 0; short_address < states.size(); ++short_address) {
|
||||
ClearDaliState(states[short_address]);
|
||||
const auto raw = readStringLocked(ShortKey("ds", gateway_id, short_address));
|
||||
if (!raw.empty()) {
|
||||
ApplyDaliStatePayload(raw, states[short_address]);
|
||||
if (states[short_address].status.anyKnown()) {
|
||||
states[short_address].status.stale = true;
|
||||
}
|
||||
if (states[short_address].status.revision > dali_runtime_revision_) {
|
||||
dali_runtime_revision_ = states[short_address].status.revision;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string GatewayCache::readStringLocked(std::string_view key) {
|
||||
if (!openStorageLocked()) {
|
||||
return {};
|
||||
|
||||
@@ -33,6 +33,9 @@ struct GatewayControllerConfig {
|
||||
bool ip_router_supported{true};
|
||||
bool internal_scene_supported{true};
|
||||
bool internal_group_supported{true};
|
||||
bool cache_supported{true};
|
||||
uint32_t cache_refresh_interval_ms{120000};
|
||||
uint32_t cache_refresh_idle_ms{100};
|
||||
};
|
||||
|
||||
struct GatewayChannelSnapshot {
|
||||
@@ -109,13 +112,20 @@ class GatewayController {
|
||||
std::map<uint8_t, std::vector<uint8_t>> chunks;
|
||||
};
|
||||
|
||||
struct CacheRefreshJob {
|
||||
TickType_t next_due_tick{0};
|
||||
uint8_t short_address{0};
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void dispatchCommand(const std::vector<uint8_t>& command);
|
||||
void scheduleReconciliation(uint8_t gateway_id);
|
||||
bool hasPendingReconciliation() const;
|
||||
bool cacheRefreshEnabled() const;
|
||||
bool runMaintenanceStep();
|
||||
bool runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job);
|
||||
bool runCacheRefreshStep();
|
||||
void reconcileGroupStep(uint8_t gateway_id, uint8_t short_address);
|
||||
void reconcileSceneStep(uint8_t gateway_id, uint8_t short_address, uint8_t scene_id);
|
||||
void reconcileSettingsStep(uint8_t gateway_id, uint8_t short_address);
|
||||
@@ -165,6 +175,7 @@ class GatewayController {
|
||||
void handleAllocationCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleInternalSceneCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleInternalGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleGatewayCacheCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleBridgeTransportCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void publishBridgeTransportResponse(uint8_t gateway_id, uint8_t version, uint8_t sequence,
|
||||
std::string_view response);
|
||||
@@ -182,6 +193,7 @@ class GatewayController {
|
||||
std::vector<GatewayNameSink> gateway_name_sinks_;
|
||||
std::map<uint16_t, BridgeTransportRequestState> bridge_transport_requests_;
|
||||
std::map<uint8_t, ReconciliationJob> reconciliation_jobs_;
|
||||
std::map<uint8_t, CacheRefreshJob> cache_refresh_jobs_;
|
||||
std::atomic<int> maintenance_activity_gateway_{-1};
|
||||
bool setup_mode_{false};
|
||||
bool wireless_setup_mode_{false};
|
||||
|
||||
@@ -22,10 +22,32 @@ constexpr uint8_t kDaliSceneCount = 16;
|
||||
constexpr uint8_t kDaliCmdOff = 0x00;
|
||||
constexpr uint8_t kDaliCmdRecallMax = 0x05;
|
||||
constexpr TickType_t kMaintenancePollTicks = pdMS_TO_TICKS(20);
|
||||
constexpr TickType_t kIdleMaintenancePollTicks = pdMS_TO_TICKS(1000);
|
||||
constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0;
|
||||
constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1;
|
||||
constexpr uint8_t kBridgeTransportVersion = 1;
|
||||
constexpr size_t kBridgeTransportMaxChunkBytes = 120;
|
||||
constexpr uint8_t kGatewayFeatureCache = 0x40;
|
||||
constexpr uint8_t kGatewayCacheOpcode = 0x39;
|
||||
constexpr uint8_t kGatewayCacheProtocolVersion = 1;
|
||||
constexpr uint8_t kGatewayCacheOpSummary = 0x00;
|
||||
constexpr uint8_t kGatewayCacheOpShortAddress = 0x01;
|
||||
constexpr uint8_t kGatewayCacheOpGroup = 0x02;
|
||||
constexpr uint8_t kGatewayCacheOpBroadcast = 0x03;
|
||||
constexpr uint8_t kGatewayCacheStatusOk = 0x00;
|
||||
constexpr uint8_t kGatewayCacheStatusDisabled = 0x01;
|
||||
constexpr uint8_t kGatewayCacheStatusInvalidArgument = 0x02;
|
||||
constexpr uint16_t kCacheFlagActualKnown = 1U << 0;
|
||||
constexpr uint16_t kCacheFlagSceneKnown = 1U << 1;
|
||||
constexpr uint16_t kCacheFlagUseMinLevel = 1U << 2;
|
||||
constexpr uint16_t kCacheFlagStatusStale = 1U << 3;
|
||||
constexpr uint16_t kCacheFlagGroupMaskKnown = 1U << 4;
|
||||
constexpr uint16_t kCacheFlagPowerOnKnown = 1U << 5;
|
||||
constexpr uint16_t kCacheFlagSystemFailureKnown = 1U << 6;
|
||||
constexpr uint16_t kCacheFlagMinKnown = 1U << 7;
|
||||
constexpr uint16_t kCacheFlagMaxKnown = 1U << 8;
|
||||
constexpr uint16_t kCacheFlagFadeTimeKnown = 1U << 9;
|
||||
constexpr uint16_t kCacheFlagFadeRateKnown = 1U << 10;
|
||||
constexpr const char* kBridgeTransportInvalidFrameResponse =
|
||||
"{\"statusCode\":400,\"error\":\"invalid bridge transport frame\","
|
||||
"\"message\":\"invalid bridge transport frame\"}";
|
||||
@@ -84,6 +106,75 @@ void AppendStringBytes(std::vector<uint8_t>& out, std::string_view value) {
|
||||
}
|
||||
}
|
||||
|
||||
void AppendLe16(std::vector<uint8_t>& out, uint16_t value) {
|
||||
out.push_back(static_cast<uint8_t>(value & 0xFF));
|
||||
out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
|
||||
}
|
||||
|
||||
void AppendLe32(std::vector<uint8_t>& out, uint32_t value) {
|
||||
out.push_back(static_cast<uint8_t>(value & 0xFF));
|
||||
out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
|
||||
out.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
|
||||
out.push_back(static_cast<uint8_t>((value >> 24) & 0xFF));
|
||||
}
|
||||
|
||||
uint8_t CacheByte(std::optional<uint8_t> value) {
|
||||
return value.value_or(0xFF);
|
||||
}
|
||||
|
||||
uint16_t CacheRuntimeFlags(const GatewayCacheDaliRuntimeStatus& status) {
|
||||
uint16_t flags = 0;
|
||||
if (status.actual_level.has_value()) {
|
||||
flags |= kCacheFlagActualKnown;
|
||||
}
|
||||
if (status.scene_id.has_value()) {
|
||||
flags |= kCacheFlagSceneKnown;
|
||||
}
|
||||
if (status.use_min_level) {
|
||||
flags |= kCacheFlagUseMinLevel;
|
||||
}
|
||||
if (status.stale) {
|
||||
flags |= kCacheFlagStatusStale;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
uint16_t CacheAddressFlags(const GatewayCacheDaliAddressState& state) {
|
||||
uint16_t flags = CacheRuntimeFlags(state.status);
|
||||
if (state.group_mask_known) {
|
||||
flags |= kCacheFlagGroupMaskKnown;
|
||||
}
|
||||
if (state.settings.power_on_level.has_value()) {
|
||||
flags |= kCacheFlagPowerOnKnown;
|
||||
}
|
||||
if (state.settings.system_failure_level.has_value()) {
|
||||
flags |= kCacheFlagSystemFailureKnown;
|
||||
}
|
||||
if (state.settings.min_level.has_value()) {
|
||||
flags |= kCacheFlagMinKnown;
|
||||
}
|
||||
if (state.settings.max_level.has_value()) {
|
||||
flags |= kCacheFlagMaxKnown;
|
||||
}
|
||||
if (state.settings.fade_time.has_value()) {
|
||||
flags |= kCacheFlagFadeTimeKnown;
|
||||
}
|
||||
if (state.settings.fade_rate.has_value()) {
|
||||
flags |= kCacheFlagFadeRateKnown;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
uint16_t CacheSceneKnownMask(const GatewayCacheDaliAddressState& state) {
|
||||
uint16_t mask = 0;
|
||||
for (size_t index = 0; index < state.scene_levels.size(); ++index) {
|
||||
if (state.scene_levels[index].has_value()) {
|
||||
mask |= static_cast<uint16_t>(1U << index);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
uint16_t BridgeTransportRequestKey(uint8_t gateway_id, uint8_t sequence) {
|
||||
return static_cast<uint16_t>((static_cast<uint16_t>(gateway_id) << 8) | sequence);
|
||||
}
|
||||
@@ -275,7 +366,11 @@ void GatewayController::taskLoop() {
|
||||
worked = runMaintenanceStep();
|
||||
}
|
||||
if (!worked) {
|
||||
ulTaskNotifyTake(pdTRUE, hasPendingReconciliation() ? kMaintenancePollTicks : portMAX_DELAY);
|
||||
const TickType_t wait_ticks = hasPendingReconciliation()
|
||||
? kMaintenancePollTicks
|
||||
: cacheRefreshEnabled() ? kIdleMaintenancePollTicks
|
||||
: portMAX_DELAY;
|
||||
ulTaskNotifyTake(pdTRUE, wait_ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,42 +403,50 @@ bool GatewayController::hasPendingReconciliation() const {
|
||||
return !reconciliation_jobs_.empty();
|
||||
}
|
||||
|
||||
bool GatewayController::cacheRefreshEnabled() const {
|
||||
return config_.cache_supported && cache_.cacheEnabled() &&
|
||||
config_.cache_refresh_interval_ms > 0;
|
||||
}
|
||||
|
||||
bool GatewayController::runMaintenanceStep() {
|
||||
if (!cache_.reconciliationEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t gateway_id = 0;
|
||||
ReconciliationJob job;
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
if (reconciliation_jobs_.empty()) {
|
||||
return false;
|
||||
if (cache_.reconciliationEnabled()) {
|
||||
bool has_job = false;
|
||||
uint8_t gateway_id = 0;
|
||||
ReconciliationJob job;
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
if (!reconciliation_jobs_.empty()) {
|
||||
const auto it = reconciliation_jobs_.begin();
|
||||
has_job = true;
|
||||
gateway_id = it->first;
|
||||
job = it->second;
|
||||
}
|
||||
}
|
||||
const auto it = reconciliation_jobs_.begin();
|
||||
gateway_id = it->first;
|
||||
job = it->second;
|
||||
}
|
||||
|
||||
if (runtime_.shouldYieldMaintenance(gateway_id)) {
|
||||
return false;
|
||||
}
|
||||
if (has_job) {
|
||||
if (runtime_.shouldYieldMaintenance(gateway_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool keep_job = runReconciliationStep(gateway_id, job);
|
||||
const bool keep_job = runReconciliationStep(gateway_id, job);
|
||||
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
auto it = reconciliation_jobs_.find(gateway_id);
|
||||
if (it == reconciliation_jobs_.end()) {
|
||||
{
|
||||
LockGuard guard(maintenance_lock_);
|
||||
auto it = reconciliation_jobs_.find(gateway_id);
|
||||
if (it == reconciliation_jobs_.end()) {
|
||||
return true;
|
||||
}
|
||||
if (keep_job) {
|
||||
it->second = job;
|
||||
} else {
|
||||
reconciliation_jobs_.erase(it);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (keep_job) {
|
||||
it->second = job;
|
||||
} else {
|
||||
reconciliation_jobs_.erase(it);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return runCacheRefreshStep();
|
||||
}
|
||||
|
||||
bool GatewayController::runReconciliationStep(uint8_t gateway_id, ReconciliationJob& job) {
|
||||
@@ -418,6 +521,49 @@ bool GatewayController::runReconciliationStep(uint8_t gateway_id, Reconciliation
|
||||
}
|
||||
}
|
||||
|
||||
bool GatewayController::runCacheRefreshStep() {
|
||||
if (!cacheRefreshEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const TickType_t interval_ticks =
|
||||
std::max<TickType_t>(1, pdMS_TO_TICKS(config_.cache_refresh_interval_ms));
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
|
||||
for (const auto& channel : channels) {
|
||||
auto& job = cache_refresh_jobs_[channel.gateway_id];
|
||||
if (job.next_due_tick == 0) {
|
||||
job.next_due_tick = now + interval_ticks;
|
||||
continue;
|
||||
}
|
||||
if (now < job.next_due_tick) {
|
||||
continue;
|
||||
}
|
||||
if (runtime_.shouldYieldMaintenance(channel.gateway_id) ||
|
||||
dali_domain_.isAllocAddr(channel.gateway_id) ||
|
||||
!dali_domain_.isBusIdle(channel.gateway_id, config_.cache_refresh_idle_ms)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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_.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();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GatewayController::reconcileGroupStep(uint8_t gateway_id, uint8_t short_address) {
|
||||
const auto policy = cache_.priorityMode();
|
||||
const auto state = cache_.daliAddressState(gateway_id, short_address);
|
||||
@@ -574,6 +720,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
if (config_.internal_group_supported) {
|
||||
feature |= 0x20;
|
||||
}
|
||||
if (config_.cache_supported) {
|
||||
feature |= kGatewayFeatureCache;
|
||||
}
|
||||
publishPayload(gateway_id, {0x03, gateway_id, feature});
|
||||
break;
|
||||
}
|
||||
@@ -672,6 +821,9 @@ void GatewayController::dispatchCommand(const std::vector<uint8_t>& command) {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kGatewayCacheOpcode:
|
||||
handleGatewayCacheCommand(gateway_id, command);
|
||||
break;
|
||||
case 0xA0:
|
||||
handleInternalSceneCommand(gateway_id, command);
|
||||
break;
|
||||
@@ -1360,4 +1512,90 @@ void GatewayController::handleInternalGroupCommand(uint8_t gateway_id,
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::handleGatewayCacheCommand(uint8_t gateway_id,
|
||||
const std::vector<uint8_t>& command) {
|
||||
const uint8_t op = command.size() > 4 ? command[4] : kGatewayCacheOpSummary;
|
||||
const uint8_t arg = command.size() > 5 ? command[5] : 0;
|
||||
const bool enabled = config_.cache_supported && cache_.cacheEnabled();
|
||||
|
||||
if (op == kGatewayCacheOpSummary) {
|
||||
const uint8_t flags = static_cast<uint8_t>((config_.cache_supported ? 0x01 : 0x00) |
|
||||
(cache_.cacheEnabled() ? 0x02 : 0x00) |
|
||||
(cacheRefreshEnabled() ? 0x04 : 0x00));
|
||||
std::vector<uint8_t> payload{kGatewayCacheOpcode,
|
||||
gateway_id,
|
||||
op,
|
||||
kGatewayCacheStatusOk,
|
||||
kGatewayCacheProtocolVersion,
|
||||
flags};
|
||||
AppendLe16(payload, static_cast<uint16_t>(std::min<uint32_t>(
|
||||
config_.cache_refresh_interval_ms / 1000U, 0xffffU)));
|
||||
publishPayload(gateway_id, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
|
||||
kGatewayCacheStatusDisabled, arg});
|
||||
return;
|
||||
}
|
||||
|
||||
if (op == kGatewayCacheOpShortAddress) {
|
||||
if (arg >= kDaliShortAddressCount) {
|
||||
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
|
||||
kGatewayCacheStatusInvalidArgument, arg});
|
||||
return;
|
||||
}
|
||||
const auto state = cache_.daliAddressState(gateway_id, arg);
|
||||
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
|
||||
AppendLe16(payload, CacheAddressFlags(state));
|
||||
payload.push_back(CacheByte(state.status.actual_level));
|
||||
payload.push_back(CacheByte(state.status.scene_id));
|
||||
AppendLe16(payload, state.group_mask_known ? state.group_mask : 0);
|
||||
payload.push_back(CacheByte(state.settings.power_on_level));
|
||||
payload.push_back(CacheByte(state.settings.system_failure_level));
|
||||
payload.push_back(CacheByte(state.settings.min_level));
|
||||
payload.push_back(CacheByte(state.settings.max_level));
|
||||
payload.push_back(CacheByte(state.settings.fade_time));
|
||||
payload.push_back(CacheByte(state.settings.fade_rate));
|
||||
AppendLe32(payload, state.status.revision);
|
||||
AppendLe16(payload, CacheSceneKnownMask(state));
|
||||
for (const auto& level : state.scene_levels) {
|
||||
payload.push_back(CacheByte(level));
|
||||
}
|
||||
publishPayload(gateway_id, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (op == kGatewayCacheOpGroup) {
|
||||
if (arg >= kDaliSceneCount) {
|
||||
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
|
||||
kGatewayCacheStatusInvalidArgument, arg});
|
||||
return;
|
||||
}
|
||||
const auto status = cache_.daliGroupStatus(gateway_id, arg);
|
||||
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, arg};
|
||||
AppendLe16(payload, CacheRuntimeFlags(status));
|
||||
payload.push_back(CacheByte(status.actual_level));
|
||||
payload.push_back(CacheByte(status.scene_id));
|
||||
AppendLe32(payload, status.revision);
|
||||
publishPayload(gateway_id, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
if (op == kGatewayCacheOpBroadcast) {
|
||||
const auto status = cache_.daliBroadcastStatus(gateway_id);
|
||||
std::vector<uint8_t> payload{kGatewayCacheOpcode, gateway_id, op, kGatewayCacheStatusOk, 0};
|
||||
AppendLe16(payload, CacheRuntimeFlags(status));
|
||||
payload.push_back(CacheByte(status.actual_level));
|
||||
payload.push_back(CacheByte(status.scene_id));
|
||||
AppendLe32(payload, status.revision);
|
||||
publishPayload(gateway_id, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
publishPayload(gateway_id, {kGatewayCacheOpcode, gateway_id, op,
|
||||
kGatewayCacheStatusInvalidArgument, arg});
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
|
||||
Reference in New Issue
Block a user