d8d0c39630
Signed-off-by: Tony <tonylu@tony-cloud.com>
287 lines
11 KiB
C++
287 lines
11 KiB
C++
#pragma once
|
|
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
// Hardware- and platform-independent DALI gateway application primitives.
|
|
//
|
|
// A transport owns frame timing, collision handling, persistence, and the
|
|
// actual transmission of backward frames. This header only interprets DALI
|
|
// forward frames and maintains the high-level state that an IoT adapter can
|
|
// expose to its own protocol.
|
|
|
|
enum class DaliGatewayCachePriorityMode : uint8_t {
|
|
outsideBusFirst = 0,
|
|
localGatewayFirst = 1,
|
|
};
|
|
|
|
enum class DaliGatewayFrameOrigin : uint8_t {
|
|
localGateway = 0,
|
|
outsideBus = 1,
|
|
};
|
|
|
|
enum class DaliGatewayTargetKind : uint8_t {
|
|
shortAddress = 0,
|
|
group = 1,
|
|
broadcast = 2,
|
|
};
|
|
|
|
enum class DaliGatewayPresence : uint8_t {
|
|
unknown = 0,
|
|
online = 1,
|
|
offline = 2,
|
|
};
|
|
|
|
struct DaliGatewayTarget {
|
|
DaliGatewayTargetKind kind{DaliGatewayTargetKind::shortAddress};
|
|
uint8_t value{0};
|
|
};
|
|
|
|
struct DaliGatewayChannelFlags {
|
|
bool needUpdateGroup{false};
|
|
bool needUpdateScene{false};
|
|
bool needUpdateSettings{false};
|
|
|
|
bool any() const {
|
|
return needUpdateGroup || needUpdateScene || needUpdateSettings;
|
|
}
|
|
};
|
|
|
|
struct DaliGatewaySettingsSnapshot {
|
|
std::optional<uint8_t> powerOnLevel;
|
|
std::optional<uint8_t> systemFailureLevel;
|
|
std::optional<uint8_t> minLevel;
|
|
std::optional<uint8_t> maxLevel;
|
|
std::optional<uint8_t> fadeTime;
|
|
std::optional<uint8_t> fadeRate;
|
|
|
|
bool anyKnown() const {
|
|
return powerOnLevel.has_value() || systemFailureLevel.has_value() || minLevel.has_value() ||
|
|
maxLevel.has_value() || fadeTime.has_value() || fadeRate.has_value();
|
|
}
|
|
};
|
|
|
|
struct DaliGatewayRuntimeStatus {
|
|
std::optional<uint8_t> actualLevel;
|
|
std::optional<uint8_t> sceneID;
|
|
bool useMinLevel{false};
|
|
bool stale{false};
|
|
uint32_t revision{0};
|
|
|
|
bool anyKnown() const {
|
|
return actualLevel.has_value() || sceneID.has_value() || useMinLevel;
|
|
}
|
|
};
|
|
|
|
struct DaliGatewayAddressState {
|
|
bool groupMaskKnown{false};
|
|
uint16_t groupMask{0};
|
|
// Capability data is populated by discovery adapters and persisted with the
|
|
// rest of the address cache so bridge planners do not need a live bus scan
|
|
// on every boot.
|
|
std::optional<uint16_t> daliDeviceTypeMask;
|
|
std::optional<uint8_t> dt8ColorTypeFeatures;
|
|
std::array<std::optional<uint8_t>, 16> sceneLevels{};
|
|
DaliGatewaySettingsSnapshot settings;
|
|
DaliGatewayRuntimeStatus status;
|
|
};
|
|
|
|
struct DaliGatewayCacheConfig {
|
|
bool enabled{true};
|
|
bool reconciliationEnabled{true};
|
|
bool fullStateMirrorEnabled{false};
|
|
DaliGatewayCachePriorityMode priorityMode{DaliGatewayCachePriorityMode::outsideBusFirst};
|
|
};
|
|
|
|
struct DaliGatewayStatusUpdate {
|
|
uint8_t channel{0};
|
|
DaliGatewayTarget target;
|
|
DaliGatewayRuntimeStatus status;
|
|
std::vector<uint8_t> affectedShortAddresses;
|
|
};
|
|
|
|
struct DaliGatewayCachePersistenceCallbacks {
|
|
// The adapter maps a channel/address pair to its own storage. The portable
|
|
// cache owns the encoded payload and its versioning.
|
|
std::function<std::optional<std::string>(uint8_t channel, uint8_t shortAddress)> load;
|
|
std::function<bool(uint8_t channel, uint8_t shortAddress,
|
|
const std::optional<std::string>& payload)>
|
|
store;
|
|
std::function<bool()> commit;
|
|
};
|
|
|
|
class DaliGatewayCache {
|
|
public:
|
|
using StatusUpdateCallback = std::function<void(const DaliGatewayStatusUpdate&)>;
|
|
|
|
explicit DaliGatewayCache(DaliGatewayCacheConfig config = {});
|
|
|
|
void configure(DaliGatewayCacheConfig config);
|
|
bool enabled() const;
|
|
bool setEnabled(bool enabled);
|
|
bool reconciliationEnabled() const;
|
|
bool fullStateMirrorEnabled() const;
|
|
DaliGatewayCachePriorityMode priorityMode() const;
|
|
void setPriorityMode(DaliGatewayCachePriorityMode mode);
|
|
void setStatusUpdateCallback(StatusUpdateCallback callback);
|
|
// Register an additional semantic status observer without replacing the
|
|
// protocol bridge callback installed through setStatusUpdateCallback().
|
|
// Adapters registered here must outlive the cache.
|
|
void addStatusUpdateCallback(StatusUpdateCallback callback);
|
|
void setPersistenceCallbacks(DaliGatewayCachePersistenceCallbacks callbacks);
|
|
|
|
static std::optional<DaliGatewayTarget> decodeTarget(uint8_t rawAddress);
|
|
|
|
DaliGatewayAddressState addressState(uint8_t channel, uint8_t shortAddress) const;
|
|
DaliGatewayPresence addressPresence(uint8_t channel, uint8_t shortAddress) const;
|
|
DaliGatewayRuntimeStatus groupStatus(uint8_t channel, uint8_t group) const;
|
|
DaliGatewayRuntimeStatus broadcastStatus(uint8_t channel) const;
|
|
DaliGatewayChannelFlags channelFlags(uint8_t channel) const;
|
|
DaliGatewayChannelFlags pendingChannelFlags(uint8_t channel) const;
|
|
|
|
void markAddressPresence(uint8_t channel, uint8_t shortAddress, DaliGatewayPresence presence);
|
|
bool setGroupMask(uint8_t channel, uint8_t shortAddress, std::optional<uint16_t> groupMask);
|
|
bool setCapabilities(uint8_t channel, uint8_t shortAddress,
|
|
std::optional<uint16_t> daliDeviceTypeMask,
|
|
std::optional<uint8_t> dt8ColorTypeFeatures);
|
|
bool setSceneLevel(uint8_t channel, uint8_t shortAddress, uint8_t scene,
|
|
std::optional<uint8_t> level);
|
|
bool setSettings(uint8_t channel, uint8_t shortAddress,
|
|
std::optional<DaliGatewaySettingsSnapshot> settings);
|
|
bool setActualLevel(uint8_t channel, uint8_t shortAddress, std::optional<uint8_t> level);
|
|
|
|
// Call after a successful locally initiated command, or for an observed
|
|
// command that should update the state mirror. The result is true only if
|
|
// a DALI state value changed.
|
|
bool mirrorForwardFrame(uint8_t channel, uint8_t rawAddress, uint8_t command);
|
|
bool observeForwardFrame(uint8_t channel, uint8_t rawAddress, uint8_t command,
|
|
DaliGatewayFrameOrigin origin);
|
|
|
|
std::vector<uint8_t> reconciliationAddresses(
|
|
uint8_t channel, std::optional<DaliGatewayTarget> target = std::nullopt) const;
|
|
bool clearChannelFlagsIfMatched(uint8_t channel, const DaliGatewayChannelFlags& expected);
|
|
void markGroupUpdateNeeded(uint8_t channel, bool needed = true);
|
|
void markSceneUpdateNeeded(uint8_t channel, bool needed = true);
|
|
void markSettingsUpdateNeeded(uint8_t channel, bool needed = true);
|
|
|
|
// The cache owns persistence encoding and dirty tracking. The adapter only
|
|
// binds platform storage callbacks and decides when preload/flush run.
|
|
std::array<DaliGatewayAddressState, 64> addressStates(uint8_t channel) const;
|
|
void restoreAddressStates(uint8_t channel,
|
|
const std::array<DaliGatewayAddressState, 64>& states);
|
|
bool preloadChannel(uint8_t channel);
|
|
bool flush();
|
|
|
|
private:
|
|
struct DtrState {
|
|
std::optional<uint8_t> dtr0;
|
|
std::optional<uint8_t> dtr1;
|
|
std::optional<uint8_t> dtr2;
|
|
};
|
|
|
|
using AddressStates = std::array<DaliGatewayAddressState, 64>;
|
|
using PresenceStates = std::array<DaliGatewayPresence, 64>;
|
|
using GroupStatuses = std::array<DaliGatewayRuntimeStatus, 16>;
|
|
|
|
AddressStates& ensureStates(uint8_t channel);
|
|
PresenceStates& ensurePresence(uint8_t channel);
|
|
GroupStatuses& ensureGroupStatuses(uint8_t channel);
|
|
DaliGatewayRuntimeStatus& ensureBroadcastStatus(uint8_t channel);
|
|
uint32_t nextRevision();
|
|
bool mirrorForwardFrameLocked(uint8_t channel, uint8_t rawAddress, uint8_t command,
|
|
std::optional<DaliGatewayStatusUpdate>* statusUpdate);
|
|
std::optional<DaliGatewayStatusUpdate> statusUpdate(
|
|
uint8_t channel, const DaliGatewayTarget& target) const;
|
|
void markDirty(uint8_t channel);
|
|
void clearTarget(uint8_t channel, const DaliGatewayTarget& target, uint32_t revision);
|
|
void applyRuntimeStatus(uint8_t channel, const DaliGatewayTarget& target,
|
|
const DaliGatewayRuntimeStatus& status);
|
|
static void applyRuntimeStatusToAddress(DaliGatewayAddressState& address,
|
|
const DaliGatewayRuntimeStatus& status);
|
|
void applyGroupMutation(uint8_t channel, const DaliGatewayTarget& target, uint8_t group,
|
|
bool add);
|
|
void applySceneMutation(uint8_t channel, const DaliGatewayTarget& target, uint8_t scene,
|
|
std::optional<uint8_t> level);
|
|
void applySettingsMutation(uint8_t channel, const DaliGatewayTarget& target, uint8_t command,
|
|
uint8_t value);
|
|
void refreshAggregateStatus(uint8_t channel, DaliGatewayAddressState& address);
|
|
static std::optional<std::string> encodeAddressState(
|
|
const DaliGatewayAddressState& state);
|
|
static std::optional<DaliGatewayAddressState> decodeAddressState(std::string_view payload);
|
|
StatusUpdateCallback statusUpdateCallbackLocked() const;
|
|
|
|
mutable std::recursive_mutex mutex_;
|
|
DaliGatewayCacheConfig config_;
|
|
StatusUpdateCallback statusUpdateCallback_;
|
|
std::vector<StatusUpdateCallback> statusUpdateCallbacks_;
|
|
DaliGatewayCachePersistenceCallbacks persistence_;
|
|
std::map<uint8_t, AddressStates> states_;
|
|
std::map<uint8_t, PresenceStates> presence_;
|
|
std::map<uint8_t, GroupStatuses> groupStatuses_;
|
|
std::map<uint8_t, DaliGatewayRuntimeStatus> broadcastStatuses_;
|
|
std::map<uint8_t, DtrState> dtrStates_;
|
|
std::map<uint8_t, DaliGatewayChannelFlags> flags_;
|
|
std::map<uint8_t, uint64_t> dirtyGenerations_;
|
|
uint32_t revision_{0};
|
|
};
|
|
|
|
struct DaliApplicationControllerConfig {
|
|
// nullopt models an unaddressed control device. A commissioning adapter can
|
|
// persist this value and restore it at boot.
|
|
std::optional<uint8_t> shortAddress;
|
|
uint32_t randomAddress{0x00D10301U};
|
|
uint8_t versionNumber{2};
|
|
uint8_t extendedVersionNumber{1};
|
|
bool applicationControllerEnabled{true};
|
|
bool applicationControllerAlwaysActive{false};
|
|
};
|
|
|
|
struct DaliApplicationControllerResult {
|
|
std::optional<uint8_t> backwardFrame;
|
|
bool identifyRequested{false};
|
|
bool shortAddressChanged{false};
|
|
std::optional<uint8_t> shortAddress;
|
|
};
|
|
|
|
// IEC 62386-103 logical control device with application-controller capability.
|
|
// The caller supplies whether a command that is specified as "send twice" has
|
|
// been confirmed by its transport/timing layer; this keeps timing out of the
|
|
// library while preserving the required command semantics.
|
|
class DaliApplicationController {
|
|
public:
|
|
explicit DaliApplicationController(DaliApplicationControllerConfig config = {});
|
|
|
|
const DaliApplicationControllerConfig& config() const;
|
|
std::optional<uint8_t> shortAddress() const;
|
|
void setShortAddress(std::optional<uint8_t> shortAddress);
|
|
|
|
DaliApplicationControllerResult handleForwardFrame(const std::array<uint8_t, 3>& frame,
|
|
bool doubleSendConfirmed = false);
|
|
|
|
private:
|
|
bool isAddressed(const std::array<uint8_t, 3>& frame) const;
|
|
bool isSelectedForCommissioning() const;
|
|
uint32_t searchAddress() const;
|
|
std::optional<uint8_t> queryResponse(uint8_t opcode) const;
|
|
void reset();
|
|
|
|
DaliApplicationControllerConfig config_;
|
|
bool powerCycleNotificationEnabled_{true};
|
|
bool powerCycleSeen_{true};
|
|
bool resetState_{false};
|
|
bool initialising_{false};
|
|
bool withdrawn_{false};
|
|
uint8_t operatingMode_{0};
|
|
uint8_t dtr0_{0};
|
|
uint8_t dtr1_{0};
|
|
uint8_t dtr2_{0};
|
|
uint32_t searchAddress_{0};
|
|
};
|