Implement DALI Gateway Cache with State Management and Reconciliation
- Added DaliGatewayCache class for managing DALI device states, including address states, presence, and group statuses. - Implemented methods for configuring cache, enabling/disabling features, and setting status update callbacks. - Introduced state encoding/decoding for persistence and added support for CSV parsing. - Created mutation handling for runtime status updates, group masks, scene levels, and settings. - Developed reconciliation logic to handle incoming frames and classify mutations. - Added tests to validate cache functionality, persistence, and DALI protocol handling. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#include "dali_gateway.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
int main() {
|
||||
DaliGatewayCache cache;
|
||||
std::map<std::pair<uint8_t, uint8_t>, std::string> persisted;
|
||||
int commits = 0;
|
||||
cache.setPersistenceCallbacks({
|
||||
[&](uint8_t channel, uint8_t address) -> std::optional<std::string> {
|
||||
const auto it = persisted.find({channel, address});
|
||||
return it == persisted.end() ? std::nullopt
|
||||
: std::optional<std::string>(it->second);
|
||||
},
|
||||
[&](uint8_t channel, uint8_t address, const std::optional<std::string>& payload) {
|
||||
if (payload.has_value()) {
|
||||
persisted[{channel, address}] = *payload;
|
||||
} else {
|
||||
persisted.erase({channel, address});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[&]() {
|
||||
++commits;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
int status_updates = 0;
|
||||
DaliGatewayStatusUpdate last_update;
|
||||
cache.setStatusUpdateCallback([&](const DaliGatewayStatusUpdate& update) {
|
||||
++status_updates;
|
||||
last_update = update;
|
||||
});
|
||||
|
||||
assert(cache.setGroupMask(7, 4, 0x0001));
|
||||
assert(cache.mirrorForwardFrame(7, 0x80, 42));
|
||||
assert(status_updates == 1);
|
||||
assert(last_update.channel == 7);
|
||||
assert(last_update.target.kind == DaliGatewayTargetKind::group);
|
||||
assert(last_update.target.value == 0);
|
||||
assert(last_update.status.actualLevel == 42);
|
||||
assert(last_update.affectedShortAddresses == std::vector<uint8_t>{4});
|
||||
assert(cache.groupStatus(7, 0).actualLevel == 42);
|
||||
assert(cache.addressState(7, 4).status.actualLevel == 42);
|
||||
|
||||
// The command-state cache tracks DTR-dependent settings and applies a
|
||||
// broadcast command to every state known by the adapter.
|
||||
cache.mirrorForwardFrame(7, 0xA3, 12);
|
||||
assert(cache.mirrorForwardFrame(7, 0x09, 0x2B));
|
||||
assert(cache.addressState(7, 4).settings.minLevel == 12);
|
||||
assert(cache.mirrorForwardFrame(7, 0xFF, 0x06));
|
||||
assert(cache.addressState(7, 4).status.actualLevel == 12);
|
||||
|
||||
assert(cache.observeForwardFrame(7, 0x09, 0x60, DaliGatewayFrameOrigin::outsideBus));
|
||||
assert(cache.pendingChannelFlags(7).needUpdateGroup);
|
||||
assert(cache.flush());
|
||||
assert(commits == 1);
|
||||
assert(!persisted.empty());
|
||||
|
||||
DaliGatewayCache restored;
|
||||
restored.setPersistenceCallbacks({
|
||||
[&](uint8_t channel, uint8_t address) -> std::optional<std::string> {
|
||||
const auto it = persisted.find({channel, address});
|
||||
return it == persisted.end() ? std::nullopt
|
||||
: std::optional<std::string>(it->second);
|
||||
},
|
||||
{},
|
||||
{},
|
||||
});
|
||||
assert(restored.preloadChannel(7));
|
||||
const auto restored_state = restored.addressState(7, 4);
|
||||
assert(restored_state.groupMaskKnown);
|
||||
assert(restored_state.groupMask == 0x0001);
|
||||
assert(restored_state.settings.minLevel == 12);
|
||||
assert(restored_state.status.stale);
|
||||
|
||||
DaliApplicationControllerConfig config;
|
||||
config.shortAddress = 3;
|
||||
config.randomAddress = 0x123456;
|
||||
DaliApplicationController controller(config);
|
||||
|
||||
const auto capabilities = controller.handleForwardFrame({0x07, 0xFE, 0x46});
|
||||
assert(capabilities.backwardFrame == 0x01);
|
||||
const auto status = controller.handleForwardFrame({0x07, 0xFE, 0x30});
|
||||
assert(status.backwardFrame == 0x28);
|
||||
|
||||
// Commission short address 5 by selecting the controller's random address.
|
||||
controller.handleForwardFrame({0xC1, 0x01, 0xFF}, true);
|
||||
controller.handleForwardFrame({0xC1, 0x05, 0x12});
|
||||
controller.handleForwardFrame({0xC1, 0x06, 0x34});
|
||||
controller.handleForwardFrame({0xC1, 0x07, 0x56});
|
||||
const auto compared = controller.handleForwardFrame({0xC1, 0x03, 0x00});
|
||||
assert(compared.backwardFrame == 0xFF);
|
||||
const auto programmed = controller.handleForwardFrame({0xC1, 0x08, 0x0B});
|
||||
assert(programmed.shortAddressChanged);
|
||||
assert(programmed.shortAddress == 5);
|
||||
const auto verified = controller.handleForwardFrame({0xC1, 0x09, 0x0B});
|
||||
assert(verified.backwardFrame == 0xFF);
|
||||
controller.handleForwardFrame({0xC1, 0x30, 0x5A});
|
||||
const auto dtr0 = controller.handleForwardFrame({0x0B, 0xFE, 0x36});
|
||||
assert(dtr0.backwardFrame == 0x5A);
|
||||
|
||||
const auto before_repeat = controller.handleForwardFrame({0x0B, 0xFE, 0x00});
|
||||
assert(!before_repeat.identifyRequested);
|
||||
const auto identity = controller.handleForwardFrame({0x0B, 0xFE, 0x00}, true);
|
||||
assert(identity.identifyRequested);
|
||||
|
||||
DaliApplicationControllerConfig unaddressed_config;
|
||||
unaddressed_config.shortAddress.reset();
|
||||
DaliApplicationController unaddressed(unaddressed_config);
|
||||
const auto missing_short_address = unaddressed.handleForwardFrame({0xFF, 0xFE, 0x33});
|
||||
assert(missing_short_address.backwardFrame == 0xFF);
|
||||
const auto unaddressed_status = unaddressed.handleForwardFrame({0xFF, 0xFE, 0x30});
|
||||
assert(unaddressed_status.backwardFrame == 0x2C);
|
||||
}
|
||||
Reference in New Issue
Block a user