initial commit

This commit is contained in:
Tony
2026-03-26 12:04:08 +08:00
commit 7e8ac7f566
31 changed files with 4304 additions and 0 deletions

56
include/addr.hpp Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include "base.hpp"
#include <optional>
#include <utility>
#include <vector>
struct DaliCompareAddrResult {
int retH = 0;
int retM = 0;
int retL = 0;
int nextAddr = 0;
};
class DaliAddr {
public:
explicit DaliAddr(DaliBase& base);
std::vector<int> onlineDevices;
bool isSearching = false;
int scanRangeStart = 0;
int scanRangeEnd = 63;
bool isAllocAddr() const;
void setIsAllocAddr(bool value);
int lastAllocAddr() const;
void setLastAllocAddr(int value);
void selectDevice(int address);
bool writeAddr(int addr, int newAddr);
bool removeAddr(int addr);
bool removeAllAddr();
std::vector<int> searchAddr(int addr = 63);
std::vector<int> searchAddrRange(int start = 0, int end = 63);
void stopSearch();
bool compareSingleAddress(int typ, int addr);
std::pair<int, int> precompareNew(int typ, int m = 0);
int compareAddress(int typ);
int compareAddressNew(int typ, int m = 0);
DaliCompareAddrResult compareAddr(int ad, std::optional<int> minH, std::optional<int> minM,
std::optional<int> minL);
int compareMulti(int h, int m, int l, int ad);
bool allocateAllAddr(int ads = 0);
void stopAllocAddr();
bool removeFromScene(int addr, int scene);
std::optional<int> getSceneBright(int addr, int scene);
bool resetAndAllocAddr(int n = 0, bool removeAddrFirst = false, bool closeLight = false);
private:
DaliBase& base_;
};

166
include/base.hpp Normal file
View File

@@ -0,0 +1,166 @@
#pragma once
#include "dali_comm.hpp"
#include <cmath>
#include <cstdint>
#include <chrono>
#include <map>
#include <optional>
#include <utility>
#include <vector>
struct DaliStatus {
bool controlGearPresent = false;
bool lampFailure = false;
bool lampPowerOn = false;
bool limitError = false;
bool fadingCompleted = false;
bool resetState = false;
bool missingShortAddress = false;
bool psFault = false;
static DaliStatus fromByte(uint8_t status);
};
class DaliBase {
public:
explicit DaliBase(DaliComm& comm);
// Runtime state mirrored from Dart DaliBase.
const int broadcast = 127;
bool isAllocAddr = false;
int lastAllocAddr = 0;
int selectedAddress = 127;
int64_t mcuTicks() const;
// Scene helpers
bool toScene(int a, int s);
bool reset(int a, int t = 2);
// Brightness helpers
int brightnessToLog(int brightness) const;
int logToBrightness(int logValue) const;
bool setBright(int a, int b);
bool setBrightPercentage(int a, double b);
bool stopFade(int a);
bool off(int a);
bool on(int a);
bool recallMaxLevel(int a);
bool recallMinLevel(int a);
int groupToAddr(int gp) const;
// Core sending wrappers
bool send(int a, uint8_t cmd);
bool sendCmd(uint8_t addr, uint8_t cmd);
bool sendExtCmd(int cmd, int value);
// DTR helpers
bool setDTR(int value);
bool setDTR1(int value);
bool setDTR2(int value);
std::optional<int> getDTR(int a);
std::optional<int> getDTR1(int a);
std::optional<int> getDTR2(int a);
bool copyCurrentBrightToDTR(int a);
// Colour value helpers (DT8 plumbing)
bool queryColourValue(int a);
bool storeDTRAsAddr(int a);
bool storeDTRAsSceneBright(int a, int s);
bool storeScene(int a, int s);
bool removeScene(int a, int s);
bool addToGroup(int a, int g);
bool removeFromGroup(int a, int g);
bool storeDTRAsFadeTime(int a);
bool storeDTRAsFadeRate(int a);
bool storeDTRAsPoweredBright(int a);
bool storeDTRAsSystemFailureLevel(int a);
bool storeDTRAsMinLevel(int a);
bool storeDTRAsMaxLevel(int a);
bool storeColourTempLimits(int a);
std::optional<bool> getOnlineStatus(int a);
std::optional<int> getBright(int a);
std::optional<int> getDeviceType(int a);
std::optional<int> getPhysicalMinLevel(int a);
std::optional<int> getDeviceVersion(int a);
bool dtSelect(int value);
bool activate(int a);
bool setDTRAsColourX(int a);
bool setDTRAsColourY(int a);
bool setDTRAsColourRGB(int a);
bool setDTRAsColourTemp(int a);
bool copyReportColourToTemp(int a);
bool setGradualChangeSpeed(int a, int value);
bool setGradualChangeRate(int a, int value);
std::optional<std::pair<int, int>> getGradualChange(int a);
std::optional<int> getGradualChangeRate(int a);
std::optional<int> getGradualChangeSpeed(int a);
bool setPowerOnLevel(int a, int value);
std::optional<int> getPowerOnLevel(int a);
bool setSystemFailureLevel(int a, int value);
std::optional<int> getSystemFailureLevel(int a);
bool setMinLevel(int a, int value);
std::optional<int> getMinLevel(int a);
bool setMaxLevel(int a, int value);
std::optional<int> getMaxLevel(int a);
bool setFadeTime(int a, int value);
std::optional<int> getFadeTime(int a);
bool setFadeRate(int a, int value);
std::optional<int> getFadeRate(int a);
std::optional<int> getNextDeviceType(int a);
std::optional<int> getGroupH(int a);
std::optional<int> getGroupL(int a);
std::optional<int> getGroup(int a);
bool setGroup(int a, int value);
std::optional<int> getScene(int a, int b);
bool setScene(int a, int b);
std::map<int, int> getScenes(int a);
std::optional<int> getStatus(int a);
std::optional<bool> getControlGearPresent(int a);
std::optional<bool> getLampFailureStatus(int a);
std::optional<bool> getLampPowerOnStatus(int a);
std::optional<bool> getLimitError(int a);
std::optional<bool> getResetStatus(int a);
std::optional<bool> getMissingShortAddress(int a);
bool terminate();
bool randomise();
bool initialiseAll();
bool initialise();
bool withdraw();
bool cancel();
bool physicalSelection();
bool queryAddressH(int addr);
bool queryAddressM(int addr);
bool queryAddressL(int addr);
bool programShortAddr(int a);
std::optional<int> queryShortAddr();
std::optional<bool> verifyShortAddr(int a);
std::optional<bool> compareAddress();
std::optional<bool> compare(int h, int m, int l);
std::optional<int> getRandomAddrH(int addr);
std::optional<int> getRandomAddrM(int addr);
std::optional<int> getRandomAddrL(int addr);
// Exposed query helpers mirroring Dart API.
std::optional<int> query(int a, uint8_t cmd);
std::optional<int> queryCmd(uint8_t addr, uint8_t cmd);
private:
DaliComm& comm_;
static uint8_t encodeCmdAddr(int dec_addr);
static uint8_t encodeArcAddr(int dec_addr);
};

71
include/bus_monitor.hpp Normal file
View File

@@ -0,0 +1,71 @@
#pragma once
#include "decode.hpp"
#include <cstdint>
#include <functional>
#include <vector>
enum class BusDir { front, back };
struct BusFrame {
BusDir dir = BusDir::front;
int proto = 0x10;
int b1 = 0;
int b2 = 0;
};
class BusMonitor {
public:
static BusMonitor& I() {
static BusMonitor inst;
return inst;
}
void setResponseWindowMs(int ms) { decoder.responseWindowMs = ms; }
void setRawSink(std::function<void(const BusFrame&)> sink) { rawSink_ = std::move(sink); }
void setDecodedSink(std::function<void(const DecodedRecord&)> sink) { decodedSink_ = std::move(sink); }
void emitFront(int proto, int addr, int cmd) {
BusFrame f{BusDir::front, proto, addr & 0xFF, cmd & 0xFF};
rawFrames.push_back(f);
if (rawSink_) rawSink_(f);
auto rec = decoder.decode(addr & 0xFF, cmd & 0xFF, proto);
records.push_back(rec);
if (decodedSink_) decodedSink_(rec);
}
void emitBack(int value, int prefix = 0xFF) {
BusFrame f{BusDir::back, 0xFF, prefix & 0xFF, value & 0xFF};
rawFrames.push_back(f);
if (rawSink_) rawSink_(f);
auto rec = decoder.decodeCmdResponse(value & 0xFF, prefix & 0xFF);
records.push_back(rec);
if (decodedSink_) decodedSink_(rec);
}
void clear() {
rawFrames.clear();
records.clear();
}
void dispose() {
rawFrames.clear();
records.clear();
rawSink_ = nullptr;
decodedSink_ = nullptr;
}
DaliDecode decoder;
std::vector<BusFrame> rawFrames;
std::vector<DecodedRecord> records;
private:
BusMonitor() = default;
std::function<void(const BusFrame&)> rawSink_;
std::function<void(const DecodedRecord&)> decodedSink_;
};

30
include/color.hpp Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include <array>
#include <cstdint>
#include <vector>
// Colour conversion utilities mirroring lib/dali/color.dart.
class DaliColor {
public:
static std::array<int, 4> toIntList(double a, double r, double g, double b);
static int toInt(double a, double r, double g, double b);
static double decimalRound(int num, double idp);
static std::array<double, 3> gammaCorrection(double r, double g, double b, double gamma = 2.8);
static std::array<double, 3> rgb2xyz(double r, double g, double b);
static std::array<int, 3> xyz2rgb(double x, double y, double z);
static std::array<double, 2> xyz2xy(double x, double y, double z);
static std::array<double, 3> xy2xyz(double xVal, double yVal);
static std::array<double, 2> rgb2xy(double r, double g, double b);
static std::array<int, 3> xy2rgb(double xVal, double yVal);
static std::array<double, 3> xyz2lab(double x, double y, double z);
static std::array<double, 3> rgb2lab(double r, double g, double b);
static std::array<double, 3> lab2xyz(double l, double a, double b);
static std::array<int, 3> lab2rgb(double l, double a, double b);
private:
static double srgbToLinear(double value);
static double linearToSrgb(double value);
};

3
include/comm.hpp Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
#include "dali_comm.hpp"

53
include/dali.hpp Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include "base.hpp"
#include "bus_monitor.hpp"
#include "comm.hpp"
#include "dali_comm.hpp"
#include "decode.hpp"
#include "device.hpp"
#include "dt1.hpp"
#include "dt8.hpp"
#include "addr.hpp"
#include "color.hpp"
#include "errors.hpp"
#include "log.hpp"
#include "query_scheduler.hpp"
#include "sequence.hpp"
#include "sequence_store.hpp"
#include <memory>
#include <string>
// Convenience umbrella header for the ESP-IDF DALI component.
class Dali {
public:
static Dali& instance(DaliComm& comm) {
if (!instance_) {
instance_ = std::unique_ptr<Dali>(new Dali(comm));
}
return *instance_;
}
static void resetInstance() { instance_.reset(); }
static constexpr int broadcast = 127;
std::string name = "dali1";
int gw = 0;
DaliBase base;
DaliDecode decode;
DaliDT1 dt1;
DaliDT8 dt8;
DaliAddr addr;
explicit Dali(DaliComm& comm, int g = 0, const std::string& n = "dali1")
: name(n), gw(g), base(comm), decode(), dt1(base), dt8(base), addr(base) {}
void open() {}
void close() {}
private:
inline static std::unique_ptr<Dali> instance_ = nullptr;
};

68
include/dali_comm.hpp Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <vector>
// Lightweight communicator for DALI gateway type 1 (USB UART new frame format).
// Frames:
// - Send: [0x10, addr, cmd]
// - Ext: [0x11, addr, cmd] (sent twice with a small delay)
// - Query: [0x12, addr, cmd] -> expects [0xFF, data] on success
// Callers provide UART callbacks; this class only builds frames and parses basic responses.
class DaliComm {
public:
using SendCallback = std::function<bool(const uint8_t* data, size_t len)>;
using ReadCallback = std::function<std::vector<uint8_t>(size_t len, uint32_t timeout_ms)>;
using TransactCallback = std::function<std::vector<uint8_t>(const uint8_t* data, size_t len)>;
using DelayCallback = std::function<void(uint32_t ms)>;
explicit DaliComm(SendCallback send_cb,
ReadCallback read_cb = nullptr,
TransactCallback transact_cb = nullptr,
DelayCallback delay_cb = nullptr);
void setSendCallback(SendCallback cb);
void setReadCallback(ReadCallback cb);
void setTransactCallback(TransactCallback cb);
void setDelayCallback(DelayCallback cb);
// Methods mirroring lib/dali/comm.dart naming where practical.
static std::vector<uint8_t> checksum(const std::vector<uint8_t>& data);
bool write(const std::vector<uint8_t>& data) const;
std::vector<uint8_t> read(size_t len, uint32_t timeout_ms = 100) const;
int checkGatewayType(int gateway) const;
void flush() const;
bool resetBus() const;
bool sendRaw(uint8_t addr, uint8_t cmd) const;
bool sendRawNew(uint8_t addr, uint8_t cmd, bool needVerify = false) const;
bool sendExtRaw(uint8_t addr, uint8_t cmd) const;
bool sendExtRawNew(uint8_t addr, uint8_t cmd) const;
std::optional<uint8_t> queryRaw(uint8_t addr, uint8_t cmd) const;
std::optional<uint8_t> queryRawNew(uint8_t addr, uint8_t cmd) const;
bool send(int dec_addr, uint8_t cmd) const;
std::optional<uint8_t> query(int dec_addr, uint8_t cmd) const;
bool getBusStatus() const;
// Send standard command frame (0x10).
bool sendCmd(uint8_t addr, uint8_t cmd) const;
// Send extended command frame (0x11).
bool sendExtCmd(uint8_t addr, uint8_t cmd) const;
// Send query frame (0x12) and parse single-byte response. Returns nullopt on no/invalid response.
std::optional<uint8_t> queryCmd(uint8_t addr, uint8_t cmd) const;
// Helpers to mirror Dart address conversion (DEC short address -> DALI odd/even encoded).
static uint8_t toCmdAddr(int dec_addr); // odd address for commands
static uint8_t toArcAddr(int dec_addr); // even address for direct arc
private:
SendCallback send_;
ReadCallback read_;
TransactCallback transact_;
DelayCallback delay_;
bool writeFrame(const std::vector<uint8_t>& frame) const;
void sleepMs(uint32_t ms) const;
};

157
include/dali_define.hpp Normal file
View File

@@ -0,0 +1,157 @@
#pragma once
// Standard control commands (IEC 62386 command set)
#define DALI_CMD_OFF 0x00
#define DALI_CMD_RECALL_MAX 0x05
#define DALI_CMD_RECALL_MAX_LEVEL DALI_CMD_RECALL_MAX
#define DALI_CMD_RECALL_MIN 0x06
#define DALI_CMD_RECALL_MIN_LEVEL DALI_CMD_RECALL_MIN
#define DALI_CMD_RESET 0x20
#define DALI_CMD_STORE_ACTUAL_LEVEL_IN_THE_DTR 0x21
#define DALI_CMD_STORE_THE_DTR_AS_MAX_LEVEL 0x2A
#define DALI_CMD_STORE_THE_DTR_AS_MIN_LEVEL 0x2B
#define DALI_CMD_STORE_THE_DTR_AS_SYS_FAIL_LEVEL 0x2C
#define DALI_CMD_STORE_THE_DTR_AS_PWR_ON_LEVEL 0x2D
#define DALI_CMD_STORE_THE_DTR_AS_FADE_TIME 0x2E
#define DALI_CMD_STORE_THE_DTR_AS_FADE_RATE 0x2F
#define DALI_CMD_STORE_DTR_AS_SHORT_ADDRESS 0x80
#define DALI_CMD_STOP_FADE 0xFF
// Indexed command ranges
#define DALI_CMD_GO_TO_SCENE(scene) (0x10 + (scene))
#define DALI_CMD_SET_SCENE(scene) (0x40 + (scene))
#define DALI_CMD_REMOVE_SCENE(scene) (0x50 + (scene))
#define DALI_CMD_ADD_TO_GROUP(group) (0x60 + (group))
#define DALI_CMD_REMOVE_FROM_GROUP(group) (0x70 + (group))
#define DALI_CMD_QUERY_SCENE_LEVEL(scene) (0xB0 + (scene))
// Command range boundaries used by decoders
#define DALI_CMD_GO_TO_SCENE_MIN DALI_CMD_GO_TO_SCENE(0)
#define DALI_CMD_GO_TO_SCENE_MAX DALI_CMD_GO_TO_SCENE(15)
#define DALI_CMD_SET_SCENE_MIN DALI_CMD_SET_SCENE(0)
#define DALI_CMD_SET_SCENE_MAX DALI_CMD_SET_SCENE(15)
#define DALI_CMD_ADD_TO_GROUP_MIN DALI_CMD_ADD_TO_GROUP(0)
#define DALI_CMD_ADD_TO_GROUP_MAX DALI_CMD_ADD_TO_GROUP(15)
#define DALI_CMD_REMOVE_FROM_GROUP_MIN DALI_CMD_REMOVE_FROM_GROUP(0)
#define DALI_CMD_REMOVE_FROM_GROUP_MAX DALI_CMD_REMOVE_FROM_GROUP(15)
#define DALI_CMD_QUERY_SCENE_LEVEL_MIN DALI_CMD_QUERY_SCENE_LEVEL(0)
#define DALI_CMD_QUERY_SCENE_LEVEL_MAX DALI_CMD_QUERY_SCENE_LEVEL(15)
#define DALI_CMD_SPECIAL_RANGE_MIN DALI_CMD_QUERY_STATUS
#define DALI_CMD_SPECIAL_RANGE_MAX DALI_CMD_DT8_QUERY_ASSIGNED_COLOR
// Query commands
#define DALI_CMD_QUERY_STATUS 0x90
#define DALI_CMD_QUERY_BALLAST 0x91
#define DALI_CMD_QUERY_LAMP_FAILURE 0x92
#define DALI_CMD_QUERY_LAMP_POWER_ON 0x93
#define DALI_CMD_QUERY_LIMIT_ERROR 0x94
#define DALI_CMD_QUERY_RESET_STATE 0x95
#define DALI_CMD_QUERY_MISSING_SHORT_ADDRESS 0x96
#define DALI_CMD_QUERY_VERSION_NUMBER 0x97
#define DALI_CMD_QUERY_CONTENT_DTR 0x98
#define DALI_CMD_QUERY_DEVICE_TYPE 0x99
#define DALI_CMD_QUERY_PHYSICAL_MINIMUM_LEVEL 0x9A
#define DALI_CMD_QUERY_POWER_FAILURE 0x9B
#define DALI_CMD_QUERY_CONTENT_DTR1 0x9C
#define DALI_CMD_QUERY_CONTENT_DTR2 0x9D
#define DALI_CMD_QUERY_OPERATING_MODE 0x9E
#define DALI_CMD_QUERY_LIGHT_SOURCE_TYPE 0x9F
#define DALI_CMD_QUERY_ACTUAL_LEVEL 0xA0
#define DALI_CMD_QUERY_MAX_LEVEL 0xA1
#define DALI_CMD_QUERY_MIN_LEVEL 0xA2
#define DALI_CMD_QUERY_POWER_ON_LEVEL 0xA3
#define DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL 0xA4
#define DALI_CMD_QUERY_FADE_TIME_FADE_RATE 0xA5
#define DALI_CMD_QUERY_MANUFACTURER_SPECIFIC_MODE 0xA6
#define DALI_CMD_QUERY_NEXT_DEVICE_TYPE 0xA7
#define DALI_CMD_QUERY_EXTENDED_FADE_TIME 0xA8
#define DALI_CMD_QUERY_CONTROL_GEAR_FAILURE 0xAA
#define DALI_CMD_QUERY_GROUPS_0_7 0xC0
#define DALI_CMD_QUERY_GROUP_8_15 0xC1
#define DALI_CMD_QUERY_RANDOM_ADDRESS_H 0xC2
#define DALI_CMD_QUERY_RANDOM_ADDRESS_M 0xC3
#define DALI_CMD_QUERY_RANDOM_ADDRESS_L 0xC4
#define DALI_CMD_READ_MEMORY_LOCATION 0xC5
// Special/programming commands
#define DALI_CMD_SPECIAL_TERMINATE 0xA1
#define DALI_CMD_SPECIAL_SET_DTR0 0xA3
#define DALI_CMD_SPECIAL_INITIALIZE 0xA5
#define DALI_CMD_SPECIAL_RANDOMIZE 0xA7
#define DALI_CMD_SPECIAL_COMPARE 0xA9
#define DALI_CMD_SPECIAL_WITHDRAW 0xAB
#define DALI_CMD_SPECIAL_CANCEL 0xAD
#define DALI_CMD_SPECIAL_SEARCHADDRH 0xB1
#define DALI_CMD_SPECIAL_SEARCHADDRM 0xB3
#define DALI_CMD_SPECIAL_SEARCHADDRL 0xB5
#define DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS 0xB7
#define DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS 0xB9
#define DALI_CMD_SPECIAL_QUERY_SHORT_ADDRESS 0xBB
#define DALI_CMD_SPECIAL_PHYSICAL_SELECTION 0xBD
#define DALI_CMD_SPECIAL_DT_SELECT 0xC1
#define DALI_CMD_SPECIAL_SET_DTR_1 0xC3
#define DALI_CMD_SPECIAL_SET_DTR_2 0xC5
// DT8 commands and queries
#define DALI_CMD_DT8_STORE_DTR_AS_COLORX 0xE0
#define DALI_CMD_DT8_STORE_DTR_AS_COLORY 0xE1
#define DALI_CMD_DT8_ACTIVATE 0xE2
#define DALI_CMD_DT8_STEP_UP_X_COORDINATE 0xE3
#define DALI_CMD_DT8_STEP_DOWN_X_COORDINATE 0xE4
#define DALI_CMD_DT8_STEP_UP_Y_COORDINATE 0xE5
#define DALI_CMD_DT8_STEP_DOWN_Y_COORDINATE 0xE6
#define DALI_CMD_DT8_SET_COLOR_TEMPERATURE 0xE7
#define DALI_CMD_DT8_STEP_UP_COLOR_TEMPERATURE 0xE8
#define DALI_CMD_DT8_STEP_DOWN_COLOR_TEMPERATURE 0xE9
#define DALI_CMD_DT8_SET_TEMPORARY_PRIMARY_DIM_LEVEL 0xEA
#define DALI_CMD_DT8_SET_TEMPORARY_RGB_DIM_LEVELS 0xEB
#define DALI_CMD_DT8_SET_TEMPORARY_WAF_DIM_LEVELS 0xEC
#define DALI_CMD_DT8_SET_TEMPORARY_RGBWAF_CONTROL 0xED
#define DALI_CMD_DT8_COPY_REPORT_TO_TEMPORARY 0xEE
#define DALI_CMD_DT8_STORE_PRIMARY_N_TY 0xF0
#define DALI_CMD_DT8_STORE_PRIMARY_N_XY 0xF1
#define DALI_CMD_DT8_STORE_COLOR_TEMPERATURE_LIMIT 0xF2
#define DALI_CMD_DT8_SET_GEAR_FEATURES 0xF3
#define DALI_CMD_DT8_ASSIGN_COLOR_TO_LINKED_CHANNEL 0xF5
#define DALI_CMD_DT8_START_AUTO_CALIBRATION 0xF6
#define DALI_CMD_DT8_QUERY_GEAR_FEATURES_STATUS 0xF7
#define DALI_CMD_QUERY_COLOR_STATUS 0xF8
#define DALI_CMD_QUERY_COLOR_TYPE 0xF9
#define DALI_CMD_QUERY_COLOR_VALUE 0xFA
#define DALI_CMD_DT8_QUERY_RGBWAF_CONTROL 0xFB
#define DALI_CMD_DT8_QUERY_ASSIGNED_COLOR 0xFC
#define DALI_CMD_DT8_QUERY_EXTENDED_VERSION 0xFF
// DT1 commands and queries
#define DALI_CMD_DT1_REST 0xE0
#define DALI_CMD_DT1_INHIBIT 0xE1
#define DALI_CMD_DT1_RE_LIGHT_RESET_INHIBIT 0xE2
#define DALI_CMD_DT1_START_FUNCTION_TEST 0xE3
#define DALI_CMD_DT1_START_DURATION_TEST 0xE4
#define DALI_CMD_DT1_STOP_TEST 0xE5
#define DALI_CMD_DT1_RESET_FUNCTION_TEST_DONE_FLAG 0xE6
#define DALI_CMD_DT1_RESET_DURATION_TEST_DONE_FLAG 0xE7
#define DALI_CMD_DT1_RESET_LAMP_TIME 0xE8
#define DALI_CMD_DT1_STORE_DTR_AS_EMERGENCY_LEVEL 0xE9
#define DALI_CMD_DT1_STORE_DTR_AS_DELAY_TIME_HIGH 0xEA
#define DALI_CMD_DT1_STORE_DTR_AS_DELAY_TIME_LOW 0xEB
#define DALI_CMD_DT1_STORE_DTR_AS_PROLONG_TIME 0xEC
#define DALI_CMD_DT1_STORE_DTR_AS_RATED_DURATION 0xED
#define DALI_CMD_DT1_STORE_DTR_AS_EMERGENCY_MIN_LEVEL 0xEE
#define DALI_CMD_DT1_STORE_DTR_AS_EMERGENCY_MAX_LEVEL 0xEF
#define DALI_CMD_DT1_START_IDENTIFICATION 0xF0
#define DALI_CMD_DT1_QUERY_EMERGENCY_LEVEL 0xF1
#define DALI_CMD_DT1_QUERY_EMERGENCY_MIN_LEVEL 0xF2
#define DALI_CMD_DT1_QUERY_EMERGENCY_MAX_LEVEL 0xF3
#define DALI_CMD_DT1_QUERY_PROLONG_TIME 0xF4
#define DALI_CMD_DT1_QUERY_FUNCTION_TEST_INTERVAL 0xF5
#define DALI_CMD_DT1_QUERY_DURATION_TEST_INTERVAL 0xF6
#define DALI_CMD_DT1_QUERY_DURATION_TEST_RESULT 0xF7
#define DALI_CMD_DT1_QUERY_LAMP_EMERGENCY_TIME 0xF8
#define DALI_CMD_DT1_QUERY_RATED_DURATION 0xF9
#define DALI_CMD_DT1_QUERY_EMERGENCY_MODE 0xFA
#define DALI_CMD_DT1_QUERY_FEATURE 0xFB
#define DALI_CMD_DT1_QUERY_FAILURE_STATUS 0xFC
#define DALI_CMD_DT1_QUERY_STATUS 0xFD
#define DALI_CMD_DT1_PERFORM_DTR_SELECTED_FUNCTION 0xFE
#define DALI_CMD_DT1_QUERY_EXTENDED_VERSION 0xFF

56
include/decode.hpp Normal file
View File

@@ -0,0 +1,56 @@
#pragma once
#include <cstdint>
#include <map>
#include <string>
#include <vector>
struct DecodedRecord {
std::string text;
std::string type;
int addr = -1;
int cmd = -1;
int proto = -1;
};
class DaliDecode {
public:
bool displayRaw = true;
int responseWindowMs = 100;
int dtr0 = 0;
int dtr1 = 0;
int dtr2 = 0;
DaliDecode();
int isQueryCmd(int cmd) const;
DecodedRecord decodeBright(int addr, int level);
DecodedRecord decodeScene(int addr, int sceneCmd);
DecodedRecord decodeCmd(int addr, int c);
DecodedRecord decodeSpCmd(int addr, int c);
DecodedRecord querySpCMD(int cmdByte, int dataByte);
DecodedRecord decodeQuery(int addr, int c);
DecodedRecord decodeCmdResponse(int value, int gwPrefix = 0xFF);
DecodedRecord decode(int addr, int c, int proto = 0x10);
private:
std::map<int, std::string> cmd_;
std::map<int, std::string> sCMD_;
std::vector<int> queryCmd_;
int lastQueryCmd_ = 0;
int64_t lastQueryAtMs_ = 0;
int pendingColourType_ = -1;
int64_t lastColourQueryAtMs_ = 0;
static int64_t nowMs();
static std::string hex(int v);
static std::string bin(int v);
static std::string yn(bool b);
static std::string whoLabel(int addr, bool even);
static std::string deviceTypeName(int v);
static std::string lightSourceName(int v);
DecodedRecord withRaw(const DecodedRecord& r) const;
};

116
include/device.hpp Normal file
View File

@@ -0,0 +1,116 @@
#pragma once
#include "model_value.hpp"
#include <optional>
#include <string>
#include <vector>
struct DaliLongAddress {
int h = 0;
int m = 0;
int l = 0;
static DaliLongAddress fromJson(const DaliValue::Object* json);
DaliValue::Object toJson() const;
};
struct DaliDeviceCapabilities {
std::optional<bool> supportsDt1;
std::optional<bool> supportsDt8;
static DaliDeviceCapabilities fromJson(const DaliValue::Object* json);
DaliValue::Object toJson() const;
void merge(const DaliDeviceCapabilities& other);
};
struct DaliStatusFlags {
std::optional<bool> controlGearPresent;
std::optional<bool> lampFailure;
std::optional<bool> lampPowerOn;
std::optional<bool> limitError;
std::optional<bool> fadingCompleted;
std::optional<bool> resetState;
std::optional<bool> missingShortAddress;
std::optional<bool> psFault;
static DaliStatusFlags fromJson(const DaliValue::Object* json);
DaliValue::Object toJson() const;
bool hasData() const;
void merge(const DaliStatusFlags& other);
};
struct DaliDt8State {
std::optional<int> colorType;
std::optional<std::string> activeMode;
std::optional<int> xyX;
std::optional<int> xyY;
std::optional<int> xyMinX;
std::optional<int> xyMaxX;
std::optional<int> xyMinY;
std::optional<int> xyMaxY;
std::optional<int> mirek;
std::optional<int> mirekMin;
std::optional<int> mirekMax;
std::optional<std::vector<int>> rgbwaf;
std::optional<std::vector<int>> primaryN;
static DaliDt8State fromJson(const DaliValue::Object* json);
DaliValue::Object toJson() const;
};
struct DaliDt1State {
std::optional<int> emergencyLevel;
std::optional<int> emergencyMinLevel;
std::optional<int> emergencyMaxLevel;
std::optional<int> prolongTimeMinutes;
std::optional<int> ratedDurationMinutes;
std::optional<int> testDelayTime;
std::optional<int> failureStatus;
std::optional<int> emergencyStatus;
std::optional<int> emergencyMode;
std::optional<int> feature;
std::optional<int> version;
static DaliDt1State fromJson(const DaliValue::Object* json);
DaliValue::Object toJson() const;
};
struct DaliDevice {
std::string id;
std::string name;
std::optional<int> shortAddress;
std::optional<DaliLongAddress> longAddress;
bool isolated = false;
std::optional<int> brightness;
std::optional<int> groupBits;
std::optional<std::vector<int>> scenes;
std::optional<int> fadeTime;
std::optional<int> fadeRate;
std::optional<int> powerOnLevel;
std::optional<int> systemFailureLevel;
std::optional<int> minLevel;
std::optional<int> maxLevel;
std::optional<int> operatingMode;
std::optional<int> physicalMinLevel;
std::optional<int> deviceType;
std::vector<int> extType;
std::optional<int> version;
DaliDeviceCapabilities capabilities;
std::optional<DaliDt8State> dt8;
std::optional<DaliDt1State> dt1;
DaliStatusFlags statusFlags;
std::optional<std::string> lastSyncedUtc;
DaliValue::Object metadata;
static DaliDevice fromJson(const DaliValue::Object& json);
DaliValue::Object toJson() const;
std::string displayName() const;
void merge(const DaliDevice& other);
};

129
include/dt1.hpp Normal file
View File

@@ -0,0 +1,129 @@
#pragma once
#include "base.hpp"
#include <cstdint>
#include <optional>
struct DT1TestStatusDetailed {
std::optional<int> failureStatus;
std::optional<int> emergencyStatus;
std::optional<int> emergencyMode;
std::optional<int> feature;
bool testInProgress = false;
bool lampFailure = false;
bool batteryFailure = false;
bool functionTestActive = false;
bool durationTestActive = false;
bool testDone = false;
bool identifyActive = false;
bool physicalSelectionActive = false;
};
class DaliDT1DeviceStatus {
public:
explicit DaliDT1DeviceStatus(int raw) : raw_(raw & 0xFF) {}
int raw() const { return raw_; }
bool controlGearFailure() const { return bit(0x01); }
bool controlGearOk() const { return !controlGearFailure(); }
bool lampFailure() const { return bit(0x02); }
bool lampPoweredByEmergencyGear() const { return bit(0x04); }
bool arcPowerBit3() const { return bit(0x08); }
bool arcPowerBit4() const { return bit(0x10); }
bool resetState() const { return bit(0x20); }
bool missingShortAddress() const { return bit(0x40); }
bool arcPowerBit7() const { return bit(0x80); }
private:
int raw_ = 0;
bool bit(int mask) const { return (raw_ & mask) != 0; }
};
class DaliDT1EmergencyStatus {
public:
explicit DaliDT1EmergencyStatus(int raw) : raw_(raw & 0xFF) {}
int raw() const { return raw_; }
bool inhibitMode() const { return bit(0x01); }
bool functionTestResultValid() const { return bit(0x02); }
bool durationTestResultValid() const { return bit(0x04); }
bool batteryFullyCharged() const { return bit(0x08); }
bool functionTestRequestPending() const { return bit(0x10); }
bool durationTestRequestPending() const { return bit(0x20); }
bool identificationActive() const { return bit(0x40); }
bool physicallySelected() const { return bit(0x80); }
bool batteryChargingInProgress() const { return !batteryFullyCharged(); }
private:
int raw_ = 0;
bool bit(int mask) const { return (raw_ & mask) != 0; }
};
class DaliDT1 {
public:
explicit DaliDT1(DaliBase& base);
bool enableDT1();
bool startDT1Test(int a, int t = 1);
std::optional<int> getDT1EmergencyMode(int a);
std::optional<int> getDT1Feature(int a);
std::optional<int> getDT1FailureStatus(int a);
std::optional<int> getDT1Status(int a);
std::optional<int> getDT1SelfTestStatus(int a);
std::optional<DT1TestStatusDetailed> getDT1TestStatusDetailed(int a);
bool performDT1Test(int a, int timeout = 5);
bool rest(int a);
bool inhibit(int a);
bool reLightOrResetInhibit(int a);
bool startFunctionTestCmd(int a);
bool startDurationTestCmd(int a);
bool stopTest(int a);
bool resetFunctionTestDoneFlag(int a);
bool resetDurationTestDoneFlag(int a);
bool resetLampTime(int a);
bool resetStatusFlags(int a);
bool resetLampOperationTime(int a);
bool resetTestResults(int a);
bool storeEmergencyLevel(int a, int level);
bool storeTestDelayTimeHighByte(int a, int highByte);
bool storeTestDelayTimeLowByte(int a, int lowByte);
bool storeFunctionTestIntervalDays(int a, int days);
bool storeDurationTestIntervalWeeks(int a, int weeks);
bool storeTestDelayTime16(int a, int quartersOfHour);
bool storeProlongTimeMinutes(int a, int minutes);
bool storeRatedDurationMinutes(int a, int minutes);
bool storeEmergencyMinLevel(int a, int level);
bool storeEmergencyMaxLevel(int a, int level);
bool startIdentification(int a);
bool performDTRSelectedFunction(int a,
const std::optional<int>& dtr0 = std::nullopt,
const std::optional<int>& dtr1 = std::nullopt);
std::optional<int> getExtendedVersionDT1(int a);
std::optional<int> getEmergencyLevel(int a);
std::optional<int> getEmergencyMinLevel(int a);
std::optional<int> getEmergencyMaxLevel(int a);
std::optional<int> getProlongTimeMinutes(int a);
std::optional<int> getFunctionTestIntervalDays(int a);
std::optional<int> getDurationTestIntervalWeeks(int a);
std::optional<int> getDurationTestResult(int a);
std::optional<int> getLampEmergencyTimeMinutes(int a);
std::optional<int> getRatedDurationMinutes(int a);
std::optional<DaliDT1DeviceStatus> getDeviceStatus(int a);
std::optional<DaliDT1EmergencyStatus> getEmergencyStatusDecoded(int a);
private:
DaliBase& base_;
bool enable();
static int addrOf(int a);
bool send(int a, int code);
std::optional<int> query(int a, int code);
};

156
include/dt8.hpp Normal file
View File

@@ -0,0 +1,156 @@
#pragma once
#include "base.hpp"
#include "color.hpp"
#include <optional>
#include <utility>
#include <vector>
class ColorStatus {
public:
explicit ColorStatus(int status) : status_(status) {}
bool xyOutOfRange() const { return (status_ & 0x01) != 0; }
bool ctOutOfRange() const { return (status_ & 0x02) != 0; }
bool autoCalibrationActive() const { return (status_ & 0x04) != 0; }
bool autoCalibrationSuccess() const { return (status_ & 0x08) != 0; }
bool xyActive() const { return (status_ & 0x10) != 0; }
bool ctActive() const { return (status_ & 0x20) != 0; }
bool primaryNActive() const { return (status_ & 0x40) != 0; }
bool rgbwafActive() const { return (status_ & 0x80) != 0; }
private:
int status_ = 0;
};
class ColorTypeFeature {
public:
explicit ColorTypeFeature(int features) : features_(features) {}
int features() const { return features_; }
bool xyCapable() const { return (features_ & 0x01) != 0; }
bool ctCapable() const { return (features_ & 0x02) != 0; }
int primaryCount() const { return (features_ >> 2) & 0x07; }
int rgbwafChannels() const { return (features_ >> 5) & 0x07; }
bool primaryNCapable() const { return primaryCount() > 0; }
bool rgbwafCapable() const { return rgbwafChannels() > 0; }
private:
int features_ = 0;
};
class DaliDT8 {
public:
explicit DaliDT8(DaliBase& base);
bool enableDT8();
std::optional<ColorTypeFeature> getColorTypeFeature(int a);
std::optional<ColorStatus> getColorStatus(int a);
std::optional<int> getColTempRaw(int a, int type = 2);
bool setColTempRaw(int a, int value);
bool setColorTemperature(int addr, int kelvin);
std::optional<int> getColorTemperature(int a);
std::optional<int> getMinColorTemperature(int a);
std::optional<int> getMaxColorTemperature(int a);
std::optional<int> getPhysicalMinColorTemperature(int a);
std::optional<int> getPhysicalMaxColorTemperature(int a);
bool setColourRaw(int addr, int x1, int y1);
// Temporary setters
bool setTemporaryColourXRaw(int addr, int x1);
bool setTemporaryColourYRaw(int addr, int y1);
bool setTemporaryColourXY(int a, double x, double y);
bool setTemporaryColourTemperature(int a, int kelvin);
bool setTemporaryPrimaryDimLevel(int a, int n, double level);
bool setTemporaryRGBDimLevels(int a, int r, int g, int b);
bool setTemporaryWAFDimLevels(int a, int w, int amber, int freecolour);
bool setTemporaryRGBWAFControl(int a, int control);
bool copyReportToTemporary(int a);
// Step commands
bool stepXUp(int a);
bool stepXDown(int a);
bool stepYUp(int a);
bool stepYDown(int a);
bool stepTcCooler(int a);
bool stepTcWarmer(int a);
bool setColourRGBRaw(int addr, int r, int g, int b);
bool setColour(int a, double x, double y);
std::optional<int> getColourRaw(int a, int type);
std::vector<double> getColour(int a);
bool setColourRGB(int addr, int r, int g, int b);
std::vector<int> getColourRGB(int a);
bool activateTemporaryColour(int a);
// Active-type queries
std::optional<int> getPrimaryDimLevel(int a, int n);
std::optional<int> getRedDimLevel(int a);
std::optional<int> getGreenDimLevel(int a);
std::optional<int> getBlueDimLevel(int a);
std::optional<int> getWhiteDimLevel(int a);
std::optional<int> getAmberDimLevel(int a);
std::optional<int> getFreecolourDimLevel(int a);
std::optional<int> getRGBWAFControl(int a);
// Temporary colour queries
std::optional<int> getTemporaryXRaw(int a);
std::optional<int> getTemporaryYRaw(int a);
std::optional<int> getTemporaryColourTemperatureRaw(int a);
std::optional<int> getTemporaryPrimaryDimLevel(int a, int n);
std::optional<int> getTemporaryRedDimLevel(int a);
std::optional<int> getTemporaryGreenDimLevel(int a);
std::optional<int> getTemporaryBlueDimLevel(int a);
std::optional<int> getTemporaryWhiteDimLevel(int a);
std::optional<int> getTemporaryAmberDimLevel(int a);
std::optional<int> getTemporaryFreecolourDimLevel(int a);
std::optional<int> getTemporaryRGBWAFControl(int a);
std::optional<int> getTemporaryColourType(int a);
std::vector<double> getTemporaryColour(int a);
std::optional<int> getTemporaryColorTemperature(int a);
// Report colour queries
std::optional<int> getReportXRaw(int a);
std::optional<int> getReportYRaw(int a);
std::optional<int> getReportColourTemperatureRaw(int a);
std::optional<int> getReportPrimaryDimLevel(int a, int n);
std::optional<int> getReportRedDimLevel(int a);
std::optional<int> getReportGreenDimLevel(int a);
std::optional<int> getReportBlueDimLevel(int a);
std::optional<int> getReportWhiteDimLevel(int a);
std::optional<int> getReportAmberDimLevel(int a);
std::optional<int> getReportFreecolourDimLevel(int a);
std::optional<int> getReportRGBWAFControl(int a);
std::optional<int> getReportColourType(int a);
std::vector<double> getReportColour(int a);
std::optional<int> getReportColorTemperature(int a);
std::optional<int> getNumberOfPrimaries(int a);
std::optional<int> getPrimaryXRaw(int a, int n);
std::optional<int> getPrimaryYRaw(int a, int n);
std::optional<int> getPrimaryTy(int a, int n);
std::vector<double> getSceneColor(int a, int sense);
// Store / config
bool storePrimaryTy(int a, int n, int ty);
bool storePrimaryXY(int a, int n, double x, double y);
bool storeColourTempLimitRaw(int a, int limitType, int mirek);
bool storeColourTempLimit(int a, int limitType, int kelvin);
bool setGearAutoActivate(int a, bool enable);
bool assignColourToLinkedChannels(int a, int colourId);
bool startAutoCalibration(int a);
// Direct queries
std::optional<int> getGearFeaturesStatus(int a);
std::optional<int> getRGBWAFControlDirect(int a);
std::optional<int> getAssignedColourForChannel(int a, int channelId);
std::optional<int> getExtendedVersion(int a);
private:
DaliBase& base_;
};

111
include/errors.hpp Normal file
View File

@@ -0,0 +1,111 @@
#pragma once
#include <exception>
#include <functional>
#include <optional>
#include <string>
class DaliQueryException : public std::exception {
public:
enum class Code {
busUnavailable,
gatewayTimeout,
deviceNoResponse,
invalidFrame,
invalidGatewayFrame,
unknown,
};
DaliQueryException(Code code,
std::string message,
std::optional<int> addr = std::nullopt,
std::optional<int> cmd = std::nullopt)
: code_(code), message_(std::move(message)), addr_(addr), cmd_(cmd) {}
const char* what() const noexcept override { return message_.c_str(); }
Code code() const { return code_; }
const std::optional<int>& addr() const { return addr_; }
const std::optional<int>& cmd() const { return cmd_; }
private:
Code code_ = Code::unknown;
std::string message_;
std::optional<int> addr_;
std::optional<int> cmd_;
};
class DaliBusUnavailableException : public DaliQueryException {
public:
explicit DaliBusUnavailableException(std::optional<int> addr = std::nullopt,
std::optional<int> cmd = std::nullopt)
: DaliQueryException(Code::busUnavailable, "Bus unavailable", addr, cmd) {}
};
class DaliGatewayTimeoutException : public DaliQueryException {
public:
explicit DaliGatewayTimeoutException(std::optional<int> addr = std::nullopt,
std::optional<int> cmd = std::nullopt)
: DaliQueryException(Code::gatewayTimeout, "Gateway no response", addr, cmd) {}
};
class DaliDeviceNoResponseException : public DaliQueryException {
public:
explicit DaliDeviceNoResponseException(std::optional<int> addr = std::nullopt,
std::optional<int> cmd = std::nullopt)
: DaliQueryException(Code::deviceNoResponse, "Device no response", addr, cmd) {}
};
class DaliInvalidFrameException : public DaliQueryException {
public:
explicit DaliInvalidFrameException(std::optional<int> addr = std::nullopt,
std::optional<int> cmd = std::nullopt)
: DaliQueryException(Code::invalidFrame, "Invalid frame", addr, cmd) {}
};
class DaliInvalidGatewayFrameException : public DaliQueryException {
public:
explicit DaliInvalidGatewayFrameException(std::optional<int> addr = std::nullopt,
std::optional<int> cmd = std::nullopt)
: DaliQueryException(Code::invalidGatewayFrame, "Invalid gateway frame", addr, cmd) {}
};
inline std::string mapDaliErrorToMessage(const DaliQueryException& e) {
switch (e.code()) {
case DaliQueryException::Code::busUnavailable:
return "dali.error.bus_unavailable";
case DaliQueryException::Code::gatewayTimeout:
return "dali.error.gateway_timeout";
case DaliQueryException::Code::deviceNoResponse:
return "dali.error.device_no_response";
case DaliQueryException::Code::invalidFrame:
return "dali.error.invalid_frame";
case DaliQueryException::Code::invalidGatewayFrame:
return "dali.error.invalid_gateway_frame";
case DaliQueryException::Code::unknown:
default:
break;
}
return "dali.error.unknown";
}
template <typename T>
std::optional<T> daliSafe(const std::function<T()>& action,
const std::function<void(const std::string&)>& onError = nullptr,
bool rethrowOthers = false) {
#if defined(__cpp_exceptions)
try {
return action();
} catch (const DaliQueryException& e) {
if (onError) onError(mapDaliErrorToMessage(e));
return std::nullopt;
} catch (...) {
if (rethrowOthers) throw;
if (onError) onError("Unexpected error");
return std::nullopt;
}
#else
(void)onError;
(void)rethrowOthers;
return action();
#endif
}

59
include/log.hpp Normal file
View File

@@ -0,0 +1,59 @@
#pragma once
#include <functional>
#include <mutex>
#include <string>
#include <vector>
enum class LogLevel { debug = 0, info = 1, warning = 2, error = 3 };
class DaliLog {
public:
static DaliLog& instance() {
static DaliLog inst;
return inst;
}
void setLevel(LogLevel level) {
std::lock_guard<std::mutex> lock(mu_);
level_ = level;
}
LogLevel currentLevel() const { return level_; }
void setSink(std::function<void(const std::string&)> sink) {
std::lock_guard<std::mutex> lock(mu_);
sink_ = std::move(sink);
}
void debugLog(const std::string& message) { log(LogLevel::debug, "DEBUG", message); }
void infoLog(const std::string& message) { log(LogLevel::info, "INFO", message); }
void warningLog(const std::string& message) { log(LogLevel::warning, "WARNING", message); }
void errorLog(const std::string& message) { log(LogLevel::error, "ERROR", message); }
std::vector<std::string> logMessages() const {
std::lock_guard<std::mutex> lock(mu_);
return logs_;
}
void clearLogs() {
std::lock_guard<std::mutex> lock(mu_);
logs_.clear();
}
private:
DaliLog() = default;
void log(LogLevel level, const char* tag, const std::string& message) {
std::lock_guard<std::mutex> lock(mu_);
if (static_cast<int>(level) < static_cast<int>(level_)) return;
const std::string line = std::string("[") + tag + "] " + message;
logs_.push_back(line);
if (sink_) sink_(line);
}
mutable std::mutex mu_;
LogLevel level_ = LogLevel::info;
std::vector<std::string> logs_;
std::function<void(const std::string&)> sink_;
};

144
include/model_value.hpp Normal file
View File

@@ -0,0 +1,144 @@
#pragma once
#include <cerrno>
#include <cstdlib>
#include <map>
#include <optional>
#include <string>
#include <variant>
#include <vector>
class DaliValue {
public:
using Array = std::vector<DaliValue>;
using Object = std::map<std::string, DaliValue>;
using Variant = std::variant<std::monostate, bool, int64_t, double, std::string, Array, Object>;
DaliValue() = default;
DaliValue(std::nullptr_t) : value_(std::monostate{}) {}
DaliValue(bool v) : value_(v) {}
DaliValue(int v) : value_(static_cast<int64_t>(v)) {}
DaliValue(int64_t v) : value_(v) {}
DaliValue(double v) : value_(v) {}
DaliValue(const char* v) : value_(std::string(v)) {}
DaliValue(std::string v) : value_(std::move(v)) {}
DaliValue(Array v) : value_(std::move(v)) {}
DaliValue(Object v) : value_(std::move(v)) {}
bool isNull() const { return std::holds_alternative<std::monostate>(value_); }
bool isBool() const { return std::holds_alternative<bool>(value_); }
bool isInt() const { return std::holds_alternative<int64_t>(value_); }
bool isDouble() const { return std::holds_alternative<double>(value_); }
bool isString() const { return std::holds_alternative<std::string>(value_); }
bool isArray() const { return std::holds_alternative<Array>(value_); }
bool isObject() const { return std::holds_alternative<Object>(value_); }
std::optional<bool> asBool() const {
if (isBool()) return std::get<bool>(value_);
if (isInt()) return std::get<int64_t>(value_) != 0;
if (isString()) {
const auto& s = std::get<std::string>(value_);
if (s == "true" || s == "TRUE" || s == "1") return true;
if (s == "false" || s == "FALSE" || s == "0") return false;
}
return std::nullopt;
}
std::optional<int> asInt() const {
if (isInt()) return static_cast<int>(std::get<int64_t>(value_));
if (isDouble()) return static_cast<int>(std::get<double>(value_));
if (isString()) {
const auto& s = std::get<std::string>(value_);
if (s.empty()) {
return std::nullopt;
}
char* end = nullptr;
errno = 0;
const long parsed = std::strtol(s.c_str(), &end, 10);
if (errno != 0 || end == s.c_str() || *end != '\0') {
return std::nullopt;
}
return static_cast<int>(parsed);
}
return std::nullopt;
}
std::optional<double> asDouble() const {
if (isDouble()) return std::get<double>(value_);
if (isInt()) return static_cast<double>(std::get<int64_t>(value_));
if (isString()) {
const auto& s = std::get<std::string>(value_);
if (s.empty()) {
return std::nullopt;
}
char* end = nullptr;
errno = 0;
const double parsed = std::strtod(s.c_str(), &end);
if (errno != 0 || end == s.c_str() || *end != '\0') {
return std::nullopt;
}
return parsed;
}
return std::nullopt;
}
std::optional<std::string> asString() const {
if (isString()) return std::get<std::string>(value_);
if (isBool()) return std::get<bool>(value_) ? "true" : "false";
if (isInt()) return std::to_string(std::get<int64_t>(value_));
if (isDouble()) return std::to_string(std::get<double>(value_));
return std::nullopt;
}
const Array* asArray() const {
if (!isArray()) return nullptr;
return &std::get<Array>(value_);
}
const Object* asObject() const {
if (!isObject()) return nullptr;
return &std::get<Object>(value_);
}
Array* asArray() {
if (!isArray()) return nullptr;
return &std::get<Array>(value_);
}
Object* asObject() {
if (!isObject()) return nullptr;
return &std::get<Object>(value_);
}
const Variant& variant() const { return value_; }
Variant& variant() { return value_; }
private:
Variant value_;
};
inline const DaliValue* getObjectValue(const DaliValue::Object& obj, const std::string& key) {
const auto it = obj.find(key);
if (it == obj.end()) return nullptr;
return &it->second;
}
inline std::optional<int> getObjectInt(const DaliValue::Object& obj, const std::string& key) {
const auto* v = getObjectValue(obj, key);
if (!v) return std::nullopt;
return v->asInt();
}
inline std::optional<bool> getObjectBool(const DaliValue::Object& obj, const std::string& key) {
const auto* v = getObjectValue(obj, key);
if (!v) return std::nullopt;
return v->asBool();
}
inline std::optional<std::string> getObjectString(const DaliValue::Object& obj,
const std::string& key) {
const auto* v = getObjectValue(obj, key);
if (!v) return std::nullopt;
return v->asString();
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <functional>
#include <mutex>
class DaliQueryScheduler {
public:
static DaliQueryScheduler& instance() {
static DaliQueryScheduler inst;
return inst;
}
template <typename Fn>
auto run(Fn&& action) -> decltype(action()) {
std::lock_guard<std::mutex> lock(mu_);
return action();
}
private:
DaliQueryScheduler() = default;
std::mutex mu_;
};

62
include/sequence.hpp Normal file
View File

@@ -0,0 +1,62 @@
#pragma once
#include "model_value.hpp"
#include <optional>
#include <string>
#include <vector>
// Mirrors lib/dali/sequence.dart command type names.
enum class DaliCommandType {
setBright,
on,
off,
toScene,
setScene,
removeScene,
addToGroup,
removeFromGroup,
setFadeTime,
setFadeRate,
wait,
modifyShortAddress,
deleteShortAddress,
};
std::string toString(DaliCommandType type);
DaliCommandType commandTypeFromString(const std::string& name,
DaliCommandType fallback = DaliCommandType::setBright);
struct DaliCommandParams {
DaliValue::Object data;
DaliCommandParams() = default;
explicit DaliCommandParams(DaliValue::Object d) : data(std::move(d)) {}
int getInt(const std::string& key, int def = 0) const;
DaliCommandParams copy() const;
DaliValue::Object toJson() const;
static DaliCommandParams fromJson(const DaliValue::Object& json);
};
struct SequenceStep {
std::string id;
std::optional<std::string> remark;
DaliCommandType type = DaliCommandType::setBright;
DaliCommandParams params;
SequenceStep copy() const;
DaliValue::Object toJson() const;
static SequenceStep fromJson(const DaliValue::Object& json);
};
struct CommandSequence {
std::string id;
std::string name;
std::vector<SequenceStep> steps;
DaliValue::Object toJson() const;
static CommandSequence fromJson(const DaliValue::Object& json);
};

View File

@@ -0,0 +1,37 @@
#pragma once
#include "model_value.hpp"
#include "sequence.hpp"
#include <functional>
#include <string>
#include <vector>
constexpr const char* kSequencesKey = "command_sequences_v1";
class SequenceRepository {
public:
using LoadCallback = std::function<bool(const std::string& key, DaliValue* out)>;
using SaveCallback = std::function<bool(const std::string& key, const DaliValue& value)>;
SequenceRepository() = default;
SequenceRepository(LoadCallback loadCb, SaveCallback saveCb)
: loadCallback_(std::move(loadCb)), saveCallback_(std::move(saveCb)) {}
void setLoadCallback(LoadCallback cb) { loadCallback_ = std::move(cb); }
void setSaveCallback(SaveCallback cb) { saveCallback_ = std::move(cb); }
bool load();
bool save() const;
const std::vector<CommandSequence>& sequences() const { return sequences_; }
void add(const CommandSequence& s);
void remove(const std::string& id);
void replace(const CommandSequence& s);
private:
std::vector<CommandSequence> sequences_;
LoadCallback loadCallback_;
SaveCallback saveCallback_;
};