initial commit
This commit is contained in:
16
CMakeLists.txt
Normal file
16
CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS
|
||||||
|
"src/dali_comm.cpp"
|
||||||
|
"src/base.cpp"
|
||||||
|
"src/addr.cpp"
|
||||||
|
"src/decode.cpp"
|
||||||
|
"src/device.cpp"
|
||||||
|
"src/dt1.cpp"
|
||||||
|
"src/dt8.cpp"
|
||||||
|
"src/sequence.cpp"
|
||||||
|
"src/sequence_store.cpp"
|
||||||
|
"src/color.cpp"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# DALI ESP-IDF Component
|
||||||
|
|
||||||
|
A lightweight C++ implementation of the DALI stack used in `lib/dali/*.dart` for gateway type 1 (USB UART) traffic. The component mirrors Dart method names so it can be used as an embedded replacement target.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
1. Add the component to your ESP-IDF project (e.g., via `EXTRA_COMPONENT_DIRS`).
|
||||||
|
2. Provide UART callbacks when constructing `DaliComm`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
DaliComm comm(
|
||||||
|
/* send */ [](const uint8_t* data, size_t len) {
|
||||||
|
// write bytes to the gateway UART
|
||||||
|
return my_uart_write(data, len) == ESP_OK;
|
||||||
|
},
|
||||||
|
/* read (optional) */ [](size_t len, uint32_t timeoutMs) -> std::vector<uint8_t> {
|
||||||
|
return my_uart_read(len, timeoutMs);
|
||||||
|
},
|
||||||
|
/* transact */ [](const uint8_t* data, size_t len) -> std::vector<uint8_t> {
|
||||||
|
my_uart_write(data, len);
|
||||||
|
return my_uart_read_response(); // should return the raw gateway reply
|
||||||
|
});
|
||||||
|
Dali dali(comm);
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Use the API just like the Dart version:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
dali.base.setBright(5, 200); // direct arc power control
|
||||||
|
dali.base.off(5);
|
||||||
|
dali.base.dtSelect(8);
|
||||||
|
dali.dt8.setColorTemperature(5, 4000); // Kelvin
|
||||||
|
std::vector<int> rgb = dali.dt8.getColourRGB(5);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Behaviour Parity
|
||||||
|
|
||||||
|
- Frame formats match the Dart implementation: `[0x10, addr, cmd]` (send), `[0x11, addr, cmd]` (extended), `[0x12, addr, cmd]` (query with `[0xFF, data]` response).
|
||||||
|
- Address encoding matches Dart helpers: `dec*2` for direct arc, `dec*2+1` for command/query addresses.
|
||||||
|
- Colour conversion utilities (`rgb2xy`, `xy2rgb`, XYZ/LAB helpers) are ported from `lib/dali/color.dart`.
|
||||||
|
- Public APIs from `base.dart`, `dt8.dart`, `dt1.dart`, `addr.dart`, and `decode.dart` are exposed with matching method names.
|
||||||
|
- App-side model parity modules are included for `device.dart`, `sequence.dart`, and `sequence_store.dart` via `device.hpp`, `sequence.hpp`, and `sequence_store.hpp`.
|
||||||
|
- Utility APIs from `errors.dart`, `log.dart`, `query_scheduler.dart`, and `bus_monitor.dart` are available as embedded-friendly C++ headers.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Optional query support: provide a `transact` callback that returns the gateway reply; otherwise, query methods return `std::nullopt`.
|
||||||
|
- `Dali` facade in `include/dali.hpp` mirrors `lib/dali/dali.dart` and wires `base`, `decode`, `dt1`, `dt8`, and `addr` together.
|
||||||
|
- The `t`, `d`, and `g` parameters in Dart are not required here; timing/gateway selection is driven by your callbacks.
|
||||||
9
idf_component.yml
Normal file
9
idf_component.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
dependencies:
|
||||||
|
idf: '>=5.1'
|
||||||
|
description: ESP DALI CPP library component for ESP-IDF
|
||||||
|
repository: git://github.com/tony-cloud/esp-dali-cpp.git
|
||||||
|
repository_info:
|
||||||
|
commit_sha: ee66ac9af9bb8edd19ba48fb7b8c52c49dea74d2
|
||||||
|
path: components/dali_cpp
|
||||||
|
url: https://github.com/tony-cloud/esp-dali-cpp
|
||||||
|
version: 0.0.1
|
||||||
56
include/addr.hpp
Normal file
56
include/addr.hpp
Normal 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
166
include/base.hpp
Normal 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
71
include/bus_monitor.hpp
Normal 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
30
include/color.hpp
Normal 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
3
include/comm.hpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "dali_comm.hpp"
|
||||||
53
include/dali.hpp
Normal file
53
include/dali.hpp
Normal 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
68
include/dali_comm.hpp
Normal 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
157
include/dali_define.hpp
Normal 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
56
include/decode.hpp
Normal 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
116
include/device.hpp
Normal 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
129
include/dt1.hpp
Normal 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
156
include/dt8.hpp
Normal 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
111
include/errors.hpp
Normal 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
59
include/log.hpp
Normal 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
144
include/model_value.hpp
Normal 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();
|
||||||
|
}
|
||||||
22
include/query_scheduler.hpp
Normal file
22
include/query_scheduler.hpp
Normal 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
62
include/sequence.hpp
Normal 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);
|
||||||
|
};
|
||||||
37
include/sequence_store.hpp
Normal file
37
include/sequence_store.hpp
Normal 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_;
|
||||||
|
};
|
||||||
386
src/addr.cpp
Normal file
386
src/addr.cpp
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
#include "addr.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <random>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
DaliAddr::DaliAddr(DaliBase& base) : base_(base) {}
|
||||||
|
|
||||||
|
bool DaliAddr::isAllocAddr() const { return base_.isAllocAddr; }
|
||||||
|
|
||||||
|
void DaliAddr::setIsAllocAddr(bool value) { base_.isAllocAddr = value; }
|
||||||
|
|
||||||
|
int DaliAddr::lastAllocAddr() const { return base_.lastAllocAddr; }
|
||||||
|
|
||||||
|
void DaliAddr::setLastAllocAddr(int value) { base_.lastAllocAddr = value; }
|
||||||
|
|
||||||
|
void DaliAddr::selectDevice(int address) { base_.selectedAddress = address; }
|
||||||
|
|
||||||
|
bool DaliAddr::writeAddr(int addr, int newAddr) {
|
||||||
|
const int nAddr = newAddr * 2 + 1;
|
||||||
|
return base_.setDTR(nAddr) && base_.storeDTRAsAddr(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliAddr::removeAddr(int addr) { return base_.setDTR(0xFF) && base_.storeDTRAsAddr(addr); }
|
||||||
|
|
||||||
|
bool DaliAddr::removeAllAddr() { return removeAddr(base_.broadcast); }
|
||||||
|
|
||||||
|
std::vector<int> DaliAddr::searchAddr(int addr) {
|
||||||
|
isSearching = true;
|
||||||
|
onlineDevices.clear();
|
||||||
|
|
||||||
|
const int maxAddr = std::clamp(addr, 0, 63);
|
||||||
|
for (int i = 0; i < maxAddr; i++) {
|
||||||
|
if (!isSearching) break;
|
||||||
|
const auto status = base_.getOnlineStatus(i);
|
||||||
|
if (status.has_value() && status.value()) {
|
||||||
|
onlineDevices.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearching = false;
|
||||||
|
return onlineDevices;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> DaliAddr::searchAddrRange(int start, int end) {
|
||||||
|
int s = std::clamp(start, 0, 63);
|
||||||
|
int e = std::clamp(end, 0, 63);
|
||||||
|
if (s > e) return {};
|
||||||
|
|
||||||
|
isSearching = true;
|
||||||
|
onlineDevices.clear();
|
||||||
|
|
||||||
|
for (int i = s; i <= e; i++) {
|
||||||
|
if (!isSearching) break;
|
||||||
|
const auto status = base_.getOnlineStatus(i);
|
||||||
|
if (status.has_value() && status.value()) {
|
||||||
|
onlineDevices.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isSearching = false;
|
||||||
|
return onlineDevices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaliAddr::stopSearch() { isSearching = false; }
|
||||||
|
|
||||||
|
bool DaliAddr::compareSingleAddress(int typ, int addr) {
|
||||||
|
bool ok = false;
|
||||||
|
if (typ == 1) {
|
||||||
|
ok = base_.queryAddressH(addr);
|
||||||
|
} else if (typ == 2) {
|
||||||
|
ok = base_.queryAddressM(addr);
|
||||||
|
} else if (typ == 3) {
|
||||||
|
ok = base_.queryAddressL(addr);
|
||||||
|
}
|
||||||
|
if (!ok) return false;
|
||||||
|
const auto matched = base_.compareAddress();
|
||||||
|
return matched.has_value() && matched.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> DaliAddr::precompareNew(int typ, int m) {
|
||||||
|
int min = m;
|
||||||
|
int max = 255;
|
||||||
|
while ((max - min) > 6) {
|
||||||
|
const int mid = static_cast<int>((max - min) / 2.0 + min);
|
||||||
|
const bool ok = compareSingleAddress(typ, mid);
|
||||||
|
if (ok) {
|
||||||
|
max = mid;
|
||||||
|
} else {
|
||||||
|
min = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {min, max};
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliAddr::compareAddress(int typ) {
|
||||||
|
int min = 0;
|
||||||
|
int max = 255;
|
||||||
|
int vAct = 0;
|
||||||
|
const auto mm = precompareNew(typ);
|
||||||
|
min = mm.first;
|
||||||
|
max = mm.second;
|
||||||
|
|
||||||
|
std::mt19937 rng(528643246);
|
||||||
|
|
||||||
|
for (int i = 0; i <= 100; i++) {
|
||||||
|
if (!base_.isAllocAddr) break;
|
||||||
|
if (min >= max) break;
|
||||||
|
|
||||||
|
std::uniform_int_distribution<int> dist(min, max);
|
||||||
|
const int v = dist(rng);
|
||||||
|
const bool res = compareSingleAddress(typ, v);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
if (v == 0) {
|
||||||
|
vAct = v;
|
||||||
|
compareSingleAddress(typ, v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const bool res2 = compareSingleAddress(typ, v - 1);
|
||||||
|
if (res2) {
|
||||||
|
max = v - 1;
|
||||||
|
} else {
|
||||||
|
vAct = v - 1;
|
||||||
|
compareSingleAddress(typ, v - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (v <= 254) {
|
||||||
|
const bool res3 = compareSingleAddress(typ, v + 1);
|
||||||
|
if (res3) {
|
||||||
|
vAct = v;
|
||||||
|
compareSingleAddress(typ, v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
min = v + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vAct;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliAddr::compareAddressNew(int typ, int m) {
|
||||||
|
int minVal = m;
|
||||||
|
int maxVal = 255;
|
||||||
|
int vAct = 0;
|
||||||
|
const auto mm = precompareNew(typ, minVal);
|
||||||
|
minVal = mm.first;
|
||||||
|
maxVal = mm.second;
|
||||||
|
int v = maxVal;
|
||||||
|
|
||||||
|
for (int i = 0; i <= 10; i++) {
|
||||||
|
const bool ok = compareSingleAddress(typ, v);
|
||||||
|
if (ok) {
|
||||||
|
if (v == 0) {
|
||||||
|
vAct = v;
|
||||||
|
compareSingleAddress(typ, v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const bool ok2 = compareSingleAddress(typ, v - 1);
|
||||||
|
if (ok2) {
|
||||||
|
maxVal = v - 1;
|
||||||
|
} else {
|
||||||
|
vAct = v - 1;
|
||||||
|
compareSingleAddress(typ, v - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vAct;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliCompareAddrResult DaliAddr::compareAddr(int ad, std::optional<int> /*minH*/,
|
||||||
|
std::optional<int> /*minM*/, std::optional<int> /*minL*/) {
|
||||||
|
DaliCompareAddrResult result;
|
||||||
|
result.nextAddr = ad;
|
||||||
|
|
||||||
|
if (ad > 63) {
|
||||||
|
result.nextAddr = 63;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
base_.compare(128, 0, 0);
|
||||||
|
result.retH = compareAddress(1);
|
||||||
|
result.retM = compareAddress(2);
|
||||||
|
result.retL = compareAddress(3);
|
||||||
|
|
||||||
|
if (!base_.isAllocAddr) {
|
||||||
|
result.retH = 0;
|
||||||
|
result.retM = 0;
|
||||||
|
result.retL = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.retH == 0 && result.retM == 0 && result.retL == 0) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto res = base_.compare(result.retH, result.retM, result.retL + 1);
|
||||||
|
if (!res.has_value() || !res.value()) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
result.retH = 0;
|
||||||
|
result.retM = 0;
|
||||||
|
result.retL = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const auto status = base_.getOnlineStatus(ad);
|
||||||
|
if (!status.has_value() || !status.value()) break;
|
||||||
|
ad++;
|
||||||
|
if (ad > 63) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base_.programShortAddr(ad)) return result;
|
||||||
|
const auto qsa = base_.queryShortAddr();
|
||||||
|
if (qsa.has_value() && qsa.value() == ad) {
|
||||||
|
base_.withdraw();
|
||||||
|
base_.setBright(ad, 254);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.nextAddr = ad;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliAddr::compareMulti(int h, int m, int l, int ad) {
|
||||||
|
int addr = ad + 1;
|
||||||
|
int retL = l;
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (!base_.isAllocAddr) return addr - 1;
|
||||||
|
|
||||||
|
retL++;
|
||||||
|
if (retL > 255) break;
|
||||||
|
|
||||||
|
const auto ok = base_.compare(h, m, retL);
|
||||||
|
if (!ok.has_value() || !ok.value()) {
|
||||||
|
addr--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const auto status = base_.getOnlineStatus(addr);
|
||||||
|
if (!status.has_value() || !status.value()) break;
|
||||||
|
addr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base_.programShortAddr(addr)) continue;
|
||||||
|
const auto qsa = base_.queryShortAddr();
|
||||||
|
if (qsa.has_value() && qsa.value() == addr) {
|
||||||
|
base_.withdraw();
|
||||||
|
base_.setBright(addr, 254);
|
||||||
|
addr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliAddr::allocateAllAddr(int ads) {
|
||||||
|
int ad = ads;
|
||||||
|
base_.isAllocAddr = true;
|
||||||
|
base_.lastAllocAddr = 255;
|
||||||
|
|
||||||
|
for (int i = 0; i <= 80; i++) {
|
||||||
|
if (!base_.isAllocAddr) break;
|
||||||
|
|
||||||
|
bool anyDevice = false;
|
||||||
|
const auto dev1 = base_.compare(255, 255, 255);
|
||||||
|
if (dev1.has_value() && dev1.value()) {
|
||||||
|
anyDevice = true;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
} else {
|
||||||
|
const auto dev2 = base_.compare(255, 255, 255);
|
||||||
|
if (dev2.has_value() && dev2.value()) {
|
||||||
|
anyDevice = true;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
} else {
|
||||||
|
const auto dev3 = base_.compare(255, 255, 255);
|
||||||
|
if (dev3.has_value() && dev3.value()) {
|
||||||
|
anyDevice = true;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyDevice) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto retVals = compareAddr(ad, std::nullopt, std::nullopt, std::nullopt);
|
||||||
|
ad = retVals.nextAddr;
|
||||||
|
if (!base_.isAllocAddr) break;
|
||||||
|
|
||||||
|
if (!(retVals.retH == 0 && retVals.retM == 0 && retVals.retL == 0)) {
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ad = compareMulti(retVals.retH, retVals.retM, retVals.retL + 1, ad);
|
||||||
|
base_.lastAllocAddr = ad;
|
||||||
|
ad++;
|
||||||
|
|
||||||
|
if (ad > 63) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 80) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
if (ad <= 0) {
|
||||||
|
base_.lastAllocAddr = 255;
|
||||||
|
} else {
|
||||||
|
base_.lastAllocAddr = ad - 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaliAddr::stopAllocAddr() { base_.isAllocAddr = false; }
|
||||||
|
|
||||||
|
bool DaliAddr::removeFromScene(int addr, int scene) {
|
||||||
|
const int value = scene + 80;
|
||||||
|
return base_.send(addr, static_cast<uint8_t>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliAddr::getSceneBright(int addr, int scene) {
|
||||||
|
const int value = scene + 176;
|
||||||
|
return base_.query(addr, static_cast<uint8_t>(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliAddr::resetAndAllocAddr(int n, bool removeAddrFirst, bool closeLight) {
|
||||||
|
const int startTime = static_cast<int>(base_.mcuTicks());
|
||||||
|
base_.isAllocAddr = true;
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
if (closeLight) {
|
||||||
|
base_.off(base_.broadcast);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base_.terminate()) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
|
||||||
|
if (removeAddrFirst) {
|
||||||
|
if (!base_.initialiseAll()) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
if (!removeAllAddr()) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!base_.initialise()) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||||
|
if (!base_.randomise()) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
if (!base_.randomise()) {
|
||||||
|
base_.isAllocAddr = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||||
|
|
||||||
|
const bool ok = allocateAllAddr(n);
|
||||||
|
(void)startTime;
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
396
src/base.cpp
Normal file
396
src/base.cpp
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
#include "base.hpp"
|
||||||
|
|
||||||
|
#include "dali_define.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
DaliStatus DaliStatus::fromByte(uint8_t status) {
|
||||||
|
DaliStatus s;
|
||||||
|
s.controlGearPresent = (status & 0x01) != 0;
|
||||||
|
s.lampFailure = (status & 0x02) != 0;
|
||||||
|
s.lampPowerOn = (status & 0x04) != 0;
|
||||||
|
s.limitError = (status & 0x08) != 0;
|
||||||
|
s.fadingCompleted = (status & 0x10) != 0;
|
||||||
|
s.resetState = (status & 0x20) != 0;
|
||||||
|
s.missingShortAddress = (status & 0x40) != 0;
|
||||||
|
s.psFault = (status & 0x80) != 0;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliBase::DaliBase(DaliComm& comm) : comm_(comm) {}
|
||||||
|
|
||||||
|
int64_t DaliBase::mcuTicks() const {
|
||||||
|
const auto now = std::chrono::time_point_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::system_clock::now());
|
||||||
|
return now.time_since_epoch().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t DaliBase::encodeCmdAddr(int dec_addr) { return DaliComm::toCmdAddr(dec_addr); }
|
||||||
|
uint8_t DaliBase::encodeArcAddr(int dec_addr) { return DaliComm::toArcAddr(dec_addr); }
|
||||||
|
|
||||||
|
bool DaliBase::toScene(int a, int s) {
|
||||||
|
const int scene = DALI_CMD_GO_TO_SCENE(s);
|
||||||
|
return send(a, static_cast<uint8_t>(scene));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::reset(int a, int /*t*/) { return send(a, DALI_CMD_RESET); }
|
||||||
|
|
||||||
|
int DaliBase::brightnessToLog(int brightness) const {
|
||||||
|
const double val = std::log(static_cast<double>(brightness) + 1.0) / std::log(256.0) * 255.0;
|
||||||
|
return static_cast<int>(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliBase::logToBrightness(int logValue) const {
|
||||||
|
const double val = std::pow(10.0, logValue * std::log(256.0) / std::log(10.0)) - 1.0;
|
||||||
|
return static_cast<int>(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::setBright(int a, int b) {
|
||||||
|
int bright = std::clamp(b, 0, 254);
|
||||||
|
const auto addr = encodeArcAddr(a);
|
||||||
|
return comm_.sendCmd(addr, static_cast<uint8_t>(bright));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::setBrightPercentage(int a, double b) {
|
||||||
|
int bright = static_cast<int>(b * 254.0 / 100.0);
|
||||||
|
bright = std::clamp(bright, 0, 254);
|
||||||
|
return setBright(a, bright);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::stopFade(int a) { return sendCmd(encodeCmdAddr(a), DALI_CMD_STOP_FADE); }
|
||||||
|
|
||||||
|
bool DaliBase::off(int a) { return sendCmd(encodeCmdAddr(a), DALI_CMD_OFF); }
|
||||||
|
|
||||||
|
bool DaliBase::on(int a) { return sendCmd(encodeCmdAddr(a), DALI_CMD_RECALL_MAX); }
|
||||||
|
|
||||||
|
bool DaliBase::recallMaxLevel(int a) { return sendCmd(encodeCmdAddr(a), DALI_CMD_RECALL_MAX); }
|
||||||
|
|
||||||
|
bool DaliBase::recallMinLevel(int a) { return sendCmd(encodeCmdAddr(a), DALI_CMD_RECALL_MIN); }
|
||||||
|
|
||||||
|
int DaliBase::groupToAddr(int gp) const { return 64 + gp; }
|
||||||
|
|
||||||
|
bool DaliBase::send(int a, uint8_t cmd) { return sendCmd(encodeCmdAddr(a), cmd); }
|
||||||
|
|
||||||
|
bool DaliBase::sendCmd(uint8_t addr, uint8_t cmd) { return comm_.sendCmd(addr, cmd); }
|
||||||
|
|
||||||
|
bool DaliBase::sendExtCmd(int cmd, int value) { return comm_.sendExtCmd(static_cast<uint8_t>(cmd), static_cast<uint8_t>(value)); }
|
||||||
|
|
||||||
|
bool DaliBase::setDTR(int value) { return comm_.sendCmd(DALI_CMD_SPECIAL_SET_DTR0, static_cast<uint8_t>(value)); }
|
||||||
|
|
||||||
|
bool DaliBase::setDTR1(int value) { return comm_.sendCmd(DALI_CMD_SPECIAL_SET_DTR_1, static_cast<uint8_t>(value)); }
|
||||||
|
|
||||||
|
bool DaliBase::setDTR2(int value) { return comm_.sendCmd(DALI_CMD_SPECIAL_SET_DTR_2, static_cast<uint8_t>(value)); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getDTR(int a) { return query(a, DALI_CMD_QUERY_CONTENT_DTR); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getDTR1(int a) { return query(a, DALI_CMD_QUERY_CONTENT_DTR1); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getDTR2(int a) { return query(a, DALI_CMD_QUERY_CONTENT_DTR2); }
|
||||||
|
|
||||||
|
bool DaliBase::copyCurrentBrightToDTR(int a) {
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_ACTUAL_LEVEL_IN_THE_DTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::queryColourValue(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_QUERY_COLOR_VALUE); }
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsAddr(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_DTR_AS_SHORT_ADDRESS); }
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsSceneBright(int a, int s) {
|
||||||
|
const int value = DALI_CMD_SET_SCENE(s);
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::storeScene(int a, int s) {
|
||||||
|
return copyCurrentBrightToDTR(a) && storeDTRAsSceneBright(a, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::removeScene(int a, int s) {
|
||||||
|
const int value = DALI_CMD_REMOVE_SCENE(s);
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::addToGroup(int a, int g) {
|
||||||
|
const int value = DALI_CMD_ADD_TO_GROUP(g);
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::removeFromGroup(int a, int g) {
|
||||||
|
const int value = DALI_CMD_REMOVE_FROM_GROUP(g);
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsFadeTime(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_THE_DTR_AS_FADE_TIME); }
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsFadeRate(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_THE_DTR_AS_FADE_RATE); }
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsPoweredBright(int a) {
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_THE_DTR_AS_PWR_ON_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsSystemFailureLevel(int a) {
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_THE_DTR_AS_SYS_FAIL_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsMinLevel(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_THE_DTR_AS_MIN_LEVEL); }
|
||||||
|
|
||||||
|
bool DaliBase::storeDTRAsMaxLevel(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_STORE_THE_DTR_AS_MAX_LEVEL); }
|
||||||
|
|
||||||
|
bool DaliBase::storeColourTempLimits(int a) {
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), DALI_CMD_DT8_STORE_COLOR_TEMPERATURE_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::getOnlineStatus(int a) {
|
||||||
|
const auto status = query(a, DALI_CMD_QUERY_BALLAST);
|
||||||
|
if (!status.has_value()) return std::nullopt;
|
||||||
|
return status.value() == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getBright(int a) {
|
||||||
|
const auto res = query(a, DALI_CMD_QUERY_ACTUAL_LEVEL);
|
||||||
|
if (!res.has_value()) return std::nullopt;
|
||||||
|
if (res.value() == 255) return 254;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getDeviceType(int a) { return queryCmd(encodeCmdAddr(a), DALI_CMD_QUERY_DEVICE_TYPE); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getPhysicalMinLevel(int a) {
|
||||||
|
return queryCmd(encodeCmdAddr(a), DALI_CMD_QUERY_PHYSICAL_MINIMUM_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getDeviceVersion(int a) {
|
||||||
|
return queryCmd(encodeCmdAddr(a), DALI_CMD_QUERY_VERSION_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::dtSelect(int value) { return comm_.sendCmd(DALI_CMD_SPECIAL_DT_SELECT, static_cast<uint8_t>(value)); }
|
||||||
|
|
||||||
|
bool DaliBase::activate(int a) { return comm_.sendCmd(encodeCmdAddr(a), DALI_CMD_DT8_ACTIVATE); }
|
||||||
|
|
||||||
|
bool DaliBase::setDTRAsColourX(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_DT8_STORE_DTR_AS_COLORX); }
|
||||||
|
|
||||||
|
bool DaliBase::setDTRAsColourY(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_DT8_STORE_DTR_AS_COLORY); }
|
||||||
|
|
||||||
|
bool DaliBase::setDTRAsColourRGB(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_DT8_ACTIVATE); }
|
||||||
|
|
||||||
|
bool DaliBase::setDTRAsColourTemp(int a) { return sendExtCmd(encodeCmdAddr(a), DALI_CMD_DT8_SET_COLOR_TEMPERATURE); }
|
||||||
|
|
||||||
|
bool DaliBase::copyReportColourToTemp(int a) {
|
||||||
|
if (!dtSelect(8)) return false;
|
||||||
|
return sendExtCmd(encodeCmdAddr(a), DALI_CMD_DT8_COPY_REPORT_TO_TEMPORARY);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::setGradualChangeSpeed(int a, int value) { return setDTR(value) && storeDTRAsFadeTime(a); }
|
||||||
|
|
||||||
|
bool DaliBase::setGradualChangeRate(int a, int value) { return setDTR(value) && storeDTRAsFadeRate(a); }
|
||||||
|
|
||||||
|
std::optional<std::pair<int, int>> DaliBase::getGradualChange(int a) {
|
||||||
|
const auto ret = query(a, DALI_CMD_QUERY_FADE_TIME_FADE_RATE);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
int speed = ret.value();
|
||||||
|
int rate = 0;
|
||||||
|
while (speed > 15) {
|
||||||
|
speed -= 16;
|
||||||
|
rate++;
|
||||||
|
}
|
||||||
|
return std::make_pair(rate, speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getGradualChangeRate(int a) {
|
||||||
|
const auto rs = getGradualChange(a);
|
||||||
|
if (!rs.has_value()) return std::nullopt;
|
||||||
|
return rs->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getGradualChangeSpeed(int a) {
|
||||||
|
const auto rs = getGradualChange(a);
|
||||||
|
if (!rs.has_value()) return std::nullopt;
|
||||||
|
return rs->first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::setPowerOnLevel(int a, int value) { return setDTR(value) && storeDTRAsPoweredBright(a); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getPowerOnLevel(int a) { return query(a, DALI_CMD_QUERY_MAX_LEVEL); }
|
||||||
|
|
||||||
|
bool DaliBase::setSystemFailureLevel(int a, int value) { return setDTR(value) && storeDTRAsSystemFailureLevel(a); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getSystemFailureLevel(int a) { return query(a, DALI_CMD_QUERY_MIN_LEVEL); }
|
||||||
|
|
||||||
|
bool DaliBase::setMinLevel(int a, int value) { return setDTR(value) && storeDTRAsMinLevel(a); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getMinLevel(int a) { return query(a, DALI_CMD_QUERY_POWER_ON_LEVEL); }
|
||||||
|
|
||||||
|
bool DaliBase::setMaxLevel(int a, int value) { return setDTR(value) && storeDTRAsMaxLevel(a); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getMaxLevel(int a) { return query(a, DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL); }
|
||||||
|
|
||||||
|
bool DaliBase::setFadeTime(int a, int value) {
|
||||||
|
int v = value;
|
||||||
|
if (v > 15) v = 15;
|
||||||
|
return setDTR(v) && storeDTRAsFadeTime(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getFadeTime(int a) {
|
||||||
|
const auto rs = getGradualChange(a);
|
||||||
|
if (!rs.has_value()) return std::nullopt;
|
||||||
|
return rs->first;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::setFadeRate(int a, int value) { return setDTR(value) && storeDTRAsFadeRate(a); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getFadeRate(int a) {
|
||||||
|
const auto rs = getGradualChange(a);
|
||||||
|
if (!rs.has_value()) return std::nullopt;
|
||||||
|
return rs->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getNextDeviceType(int a) { return query(a, DALI_CMD_QUERY_NEXT_DEVICE_TYPE); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getGroupH(int a) { return query(a, DALI_CMD_QUERY_GROUP_8_15); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getGroupL(int a) { return query(a, DALI_CMD_QUERY_GROUPS_0_7); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getGroup(int a) {
|
||||||
|
const auto h = getGroupH(a);
|
||||||
|
const auto l = getGroupL(a);
|
||||||
|
if (!h.has_value() || !l.has_value()) return std::nullopt;
|
||||||
|
return h.value() * 256 + l.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::setGroup(int a, int value) {
|
||||||
|
auto currentGroupOpt = getGroup(a);
|
||||||
|
int currentGroup = currentGroupOpt.value_or(-1);
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
if (currentGroup != -1 && (currentGroup & (1 << i)) == (value & (1 << i))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ((value & (1 << i)) != 0) {
|
||||||
|
if (!addToGroup(a, i)) return false;
|
||||||
|
} else {
|
||||||
|
if (!removeFromGroup(a, i)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getScene(int a, int b) {
|
||||||
|
return query(a, static_cast<uint8_t>(DALI_CMD_QUERY_SCENE_LEVEL(b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::setScene(int a, int b) { return setDTR(b) && storeDTRAsSceneBright(a, b); }
|
||||||
|
|
||||||
|
std::map<int, int> DaliBase::getScenes(int a) {
|
||||||
|
std::map<int, int> ret;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
const auto r = getScene(a, i);
|
||||||
|
if (r.has_value()) ret[i] = r.value();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getStatus(int a) { return query(a, DALI_CMD_QUERY_STATUS); }
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::getControlGearPresent(int a) {
|
||||||
|
const auto ret = query(a, DALI_CMD_QUERY_BALLAST);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
return ret.value() == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::getLampFailureStatus(int a) {
|
||||||
|
const auto ret = query(a, DALI_CMD_QUERY_LAMP_FAILURE);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
return ret.value() == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::getLampPowerOnStatus(int a) {
|
||||||
|
const auto ret = query(a, DALI_CMD_QUERY_LAMP_POWER_ON);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
return ret.value() == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::getLimitError(int a) {
|
||||||
|
const auto ret = query(a, DALI_CMD_QUERY_LIMIT_ERROR);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
return ret.value() == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::getResetStatus(int a) {
|
||||||
|
const auto ret = query(a, DALI_CMD_QUERY_RESET_STATE);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
return ret.value() == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::getMissingShortAddress(int a) {
|
||||||
|
const auto ret = query(a, DALI_CMD_QUERY_MISSING_SHORT_ADDRESS);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
return ret.value() == 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::terminate() { return comm_.sendCmd(DALI_CMD_SPECIAL_TERMINATE, DALI_CMD_OFF); }
|
||||||
|
|
||||||
|
bool DaliBase::randomise() { return comm_.sendExtCmd(DALI_CMD_SPECIAL_RANDOMIZE, DALI_CMD_OFF); }
|
||||||
|
|
||||||
|
bool DaliBase::initialiseAll() { return comm_.sendExtCmd(DALI_CMD_SPECIAL_INITIALIZE, DALI_CMD_OFF); }
|
||||||
|
|
||||||
|
bool DaliBase::initialise() { return comm_.sendExtCmd(DALI_CMD_SPECIAL_INITIALIZE, DALI_CMD_STOP_FADE); }
|
||||||
|
|
||||||
|
bool DaliBase::withdraw() { return comm_.sendCmd(DALI_CMD_SPECIAL_WITHDRAW, DALI_CMD_OFF); }
|
||||||
|
|
||||||
|
bool DaliBase::cancel() { return comm_.sendCmd(DALI_CMD_SPECIAL_CANCEL, DALI_CMD_OFF); }
|
||||||
|
|
||||||
|
bool DaliBase::physicalSelection() {
|
||||||
|
return comm_.sendCmd(DALI_CMD_SPECIAL_PHYSICAL_SELECTION, DALI_CMD_OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::queryAddressH(int addr) {
|
||||||
|
return comm_.sendCmd(DALI_CMD_SPECIAL_SEARCHADDRH, static_cast<uint8_t>(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::queryAddressM(int addr) {
|
||||||
|
return comm_.sendCmd(DALI_CMD_SPECIAL_SEARCHADDRM, static_cast<uint8_t>(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::queryAddressL(int addr) {
|
||||||
|
return comm_.sendCmd(DALI_CMD_SPECIAL_SEARCHADDRL, static_cast<uint8_t>(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliBase::programShortAddr(int a) {
|
||||||
|
return comm_.sendCmd(DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS, encodeCmdAddr(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::queryShortAddr() {
|
||||||
|
const auto ret1 = queryCmd(DALI_CMD_SPECIAL_QUERY_SHORT_ADDRESS, DALI_CMD_OFF);
|
||||||
|
if (!ret1.has_value()) return std::nullopt;
|
||||||
|
int ret = ret1.value() - 1;
|
||||||
|
return ret / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::verifyShortAddr(int a) {
|
||||||
|
const auto res = queryCmd(DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS, encodeCmdAddr(a));
|
||||||
|
if (!res.has_value()) return std::nullopt;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::compareAddress() {
|
||||||
|
const auto ret = queryCmd(DALI_CMD_SPECIAL_COMPARE, DALI_CMD_OFF);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
return ret.value() >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<bool> DaliBase::compare(int h, int m, int l) {
|
||||||
|
if (!queryAddressL(l)) return std::nullopt;
|
||||||
|
if (!queryAddressM(m)) return std::nullopt;
|
||||||
|
if (!queryAddressH(h)) return std::nullopt;
|
||||||
|
const auto matched = compareAddress();
|
||||||
|
if (!matched.has_value()) return std::nullopt;
|
||||||
|
return matched.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getRandomAddrH(int addr) { return query(addr, DALI_CMD_QUERY_RANDOM_ADDRESS_H); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getRandomAddrM(int addr) { return query(addr, DALI_CMD_QUERY_RANDOM_ADDRESS_M); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::getRandomAddrL(int addr) { return query(addr, DALI_CMD_QUERY_RANDOM_ADDRESS_L); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::query(int a, uint8_t cmd) { return comm_.queryCmd(encodeCmdAddr(a), cmd); }
|
||||||
|
|
||||||
|
std::optional<int> DaliBase::queryCmd(uint8_t addr, uint8_t cmd) { return comm_.queryCmd(addr, cmd); }
|
||||||
145
src/color.cpp
Normal file
145
src/color.cpp
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#include "color.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
std::array<int, 4> DaliColor::toIntList(double a, double r, double g, double b) {
|
||||||
|
const int ai = static_cast<int>(a * 255.0);
|
||||||
|
const int ri = static_cast<int>(r * 255.0);
|
||||||
|
const int gi = static_cast<int>(g * 255.0);
|
||||||
|
const int bi = static_cast<int>(b * 255.0);
|
||||||
|
return {ai, ri, gi, bi};
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliColor::toInt(double a, double r, double g, double b) {
|
||||||
|
const auto c = toIntList(a, r, g, b);
|
||||||
|
return (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
double DaliColor::decimalRound(int num, double idp) {
|
||||||
|
const double mult = std::pow(10.0, idp);
|
||||||
|
return (num * mult + 0.5) / mult;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 3> DaliColor::gammaCorrection(double r, double g, double b, double gamma) {
|
||||||
|
return {std::pow(r, gamma), std::pow(g, gamma), std::pow(b, gamma)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 3> DaliColor::rgb2xyz(double r, double g, double b) {
|
||||||
|
const double lr = srgbToLinear(r);
|
||||||
|
const double lg = srgbToLinear(g);
|
||||||
|
const double lb = srgbToLinear(b);
|
||||||
|
|
||||||
|
const double x = 0.412453 * lr + 0.357580 * lg + 0.180423 * lb;
|
||||||
|
const double y = 0.212671 * lr + 0.715160 * lg + 0.072169 * lb;
|
||||||
|
const double z = 0.019334 * lr + 0.119193 * lg + 0.950227 * lb;
|
||||||
|
return {x, y, z};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<int, 3> DaliColor::xyz2rgb(double x, double y, double z) {
|
||||||
|
const double lr = 3.240479 * x - 1.537150 * y - 0.498535 * z;
|
||||||
|
const double lg = -0.969256 * x + 1.875992 * y + 0.041556 * z;
|
||||||
|
const double lb = 0.055648 * x - 0.204043 * y + 1.057311 * z;
|
||||||
|
|
||||||
|
const double sr = linearToSrgb(lr);
|
||||||
|
const double sg = linearToSrgb(lg);
|
||||||
|
const double sb = linearToSrgb(lb);
|
||||||
|
|
||||||
|
auto toChannel = [](double v) {
|
||||||
|
return static_cast<int>(std::clamp(std::round(v * 255.0), 0.0, 255.0));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {toChannel(sr), toChannel(sg), toChannel(sb)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 2> DaliColor::xyz2xy(double x, double y, double z) {
|
||||||
|
const double sum = x + y + z;
|
||||||
|
if (sum == 0.0) return {0.0, 0.0};
|
||||||
|
return {x / sum, y / sum};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 3> DaliColor::xy2xyz(double xVal, double yVal) {
|
||||||
|
if (yVal == 0.0) return {0.0, 0.0, 0.0};
|
||||||
|
const double x = xVal / yVal;
|
||||||
|
const double y = 1.0;
|
||||||
|
const double z = (1.0 - xVal - yVal) / yVal;
|
||||||
|
return {x, y, z};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 2> DaliColor::rgb2xy(double r, double g, double b) {
|
||||||
|
const auto xyz = rgb2xyz(r, g, b);
|
||||||
|
const auto xy = xyz2xy(xyz[0], xyz[1], xyz[2]);
|
||||||
|
return {xy[0], xy[1]};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<int, 3> DaliColor::xy2rgb(double xVal, double yVal) {
|
||||||
|
const auto xyz = xy2xyz(xVal, yVal);
|
||||||
|
return xyz2rgb(xyz[0], xyz[1], xyz[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 3> DaliColor::xyz2lab(double x, double y, double z) {
|
||||||
|
constexpr double xn = 0.950456;
|
||||||
|
constexpr double yn = 1.000000;
|
||||||
|
constexpr double zn = 1.088754;
|
||||||
|
double fx = x / xn;
|
||||||
|
double fy = y / yn;
|
||||||
|
double fz = z / zn;
|
||||||
|
|
||||||
|
auto f = [](double v) {
|
||||||
|
return (v > 0.008856) ? std::pow(v, 1.0 / 3.0) : (7.787 * v) + (16.0 / 116.0);
|
||||||
|
};
|
||||||
|
|
||||||
|
fx = f(fx);
|
||||||
|
fy = f(fy);
|
||||||
|
fz = f(fz);
|
||||||
|
|
||||||
|
const double l = (116.0 * fy) - 16.0;
|
||||||
|
const double a = 500.0 * (fx - fy);
|
||||||
|
const double b = 200.0 * (fy - fz);
|
||||||
|
return {l, a, b};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 3> DaliColor::rgb2lab(double r, double g, double b) {
|
||||||
|
const auto xyz = rgb2xyz(r, g, b);
|
||||||
|
const auto lab = xyz2lab(xyz[0], xyz[1], xyz[2]);
|
||||||
|
return {lab[0], lab[1], lab[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<double, 3> DaliColor::lab2xyz(double l, double a, double b) {
|
||||||
|
const double fy = (l + 16.0) / 116.0;
|
||||||
|
const double fx = fy + (a / 500.0);
|
||||||
|
const double fz = fy - (b / 200.0);
|
||||||
|
|
||||||
|
const double fx3 = fx * fx * fx;
|
||||||
|
const double fy3 = fy * fy * fy;
|
||||||
|
const double fz3 = fz * fz * fz;
|
||||||
|
|
||||||
|
const double xr = (fx3 > 0.008856) ? fx3 : ((fx - (16.0 / 116.0)) / 7.787);
|
||||||
|
const double yr = (fy3 > 0.008856) ? fy3 : ((fy - (16.0 / 116.0)) / 7.787);
|
||||||
|
const double zr = (fz3 > 0.008856) ? fz3 : ((fz - (16.0 / 116.0)) / 7.787);
|
||||||
|
|
||||||
|
constexpr double xn = 0.950456;
|
||||||
|
constexpr double yn = 1.000000;
|
||||||
|
constexpr double zn = 1.088754;
|
||||||
|
|
||||||
|
return {xr * xn, yr * yn, zr * zn};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<int, 3> DaliColor::lab2rgb(double l, double a, double b) {
|
||||||
|
const auto xyz = lab2xyz(l, a, b);
|
||||||
|
return xyz2rgb(xyz[0], xyz[1], xyz[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
double DaliColor::srgbToLinear(double value) {
|
||||||
|
const double v = std::clamp(value, 0.0, 1.0);
|
||||||
|
if (v <= 0.04045) {
|
||||||
|
return v / 12.92;
|
||||||
|
}
|
||||||
|
return std::pow((v + 0.055) / 1.055, 2.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
double DaliColor::linearToSrgb(double value) {
|
||||||
|
if (value <= 0.0) return 0.0;
|
||||||
|
const double srgb = (value <= 0.0031308) ? (value * 12.92) : (1.055 * std::pow(value, 1.0 / 2.4) - 0.055);
|
||||||
|
return std::clamp(srgb, 0.0, 1.0);
|
||||||
|
}
|
||||||
156
src/dali_comm.cpp
Normal file
156
src/dali_comm.cpp
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#include "dali_comm.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DaliComm::DaliComm(SendCallback send_cb,
|
||||||
|
ReadCallback read_cb,
|
||||||
|
TransactCallback transact_cb,
|
||||||
|
DelayCallback delay_cb)
|
||||||
|
: send_(std::move(send_cb)),
|
||||||
|
read_(std::move(read_cb)),
|
||||||
|
transact_(std::move(transact_cb)),
|
||||||
|
delay_(std::move(delay_cb)) {}
|
||||||
|
|
||||||
|
void DaliComm::setSendCallback(SendCallback cb) { send_ = std::move(cb); }
|
||||||
|
|
||||||
|
void DaliComm::setReadCallback(ReadCallback cb) { read_ = std::move(cb); }
|
||||||
|
|
||||||
|
void DaliComm::setTransactCallback(TransactCallback cb) { transact_ = std::move(cb); }
|
||||||
|
|
||||||
|
void DaliComm::setDelayCallback(DelayCallback cb) { delay_ = std::move(cb); }
|
||||||
|
|
||||||
|
std::vector<uint8_t> DaliComm::checksum(const std::vector<uint8_t>& data) {
|
||||||
|
std::vector<uint8_t> out = data;
|
||||||
|
uint32_t sum = 0;
|
||||||
|
for (const auto b : out) {
|
||||||
|
sum += b;
|
||||||
|
}
|
||||||
|
out.push_back(static_cast<uint8_t>(sum & 0xFF));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::write(const std::vector<uint8_t>& data) const { return writeFrame(data); }
|
||||||
|
|
||||||
|
std::vector<uint8_t> DaliComm::read(size_t len, uint32_t timeout_ms) const {
|
||||||
|
if (!read_) return {};
|
||||||
|
return read_(len, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliComm::checkGatewayType(int gateway) const {
|
||||||
|
if (!transact_) return 0;
|
||||||
|
|
||||||
|
const std::vector<uint8_t> usbProbe{0x01, 0x00, 0x00};
|
||||||
|
const std::vector<uint8_t> legacyProbe{0x28, 0x01, static_cast<uint8_t>(gateway), 0x11, 0x00, 0x00,
|
||||||
|
0xFF};
|
||||||
|
const std::vector<uint8_t> newProbe{0x28, 0x01, static_cast<uint8_t>(gateway), 0x11, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xFF};
|
||||||
|
|
||||||
|
const auto usbResp = transact_(usbProbe.data(), usbProbe.size());
|
||||||
|
if (!usbResp.empty() && usbResp[0] > 0) return 1;
|
||||||
|
|
||||||
|
const auto legacyResp = transact_(legacyProbe.data(), legacyProbe.size());
|
||||||
|
if (legacyResp.size() >= 2 && legacyResp[0] == gateway) return 2;
|
||||||
|
|
||||||
|
const auto newResp = transact_(newProbe.data(), newProbe.size());
|
||||||
|
if (newResp.size() >= 2 && newResp[0] == gateway) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaliComm::flush() const {
|
||||||
|
if (!read_) return;
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
const auto data = read_(2, 100);
|
||||||
|
if (data.empty()) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::resetBus() const {
|
||||||
|
const std::vector<uint8_t> frame{0x00, 0x00, 0x00};
|
||||||
|
if (!write(frame)) return false;
|
||||||
|
sleepMs(100);
|
||||||
|
if (!write(frame)) return false;
|
||||||
|
sleepMs(100);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::sendRaw(uint8_t addr, uint8_t cmd) const { return sendCmd(addr, cmd); }
|
||||||
|
|
||||||
|
bool DaliComm::sendRawNew(uint8_t addr, uint8_t cmd, bool needVerify) const {
|
||||||
|
if (!needVerify) return sendCmd(addr, cmd);
|
||||||
|
return queryCmd(addr, cmd).has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::sendExtRaw(uint8_t addr, uint8_t cmd) const { return sendExtCmd(addr, cmd); }
|
||||||
|
|
||||||
|
bool DaliComm::sendExtRawNew(uint8_t addr, uint8_t cmd) const { return sendExtCmd(addr, cmd); }
|
||||||
|
|
||||||
|
std::optional<uint8_t> DaliComm::queryRaw(uint8_t addr, uint8_t cmd) const { return queryCmd(addr, cmd); }
|
||||||
|
|
||||||
|
std::optional<uint8_t> DaliComm::queryRawNew(uint8_t addr, uint8_t cmd) const {
|
||||||
|
return queryCmd(addr, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::send(int dec_addr, uint8_t cmd) const { return sendCmd(toCmdAddr(dec_addr), cmd); }
|
||||||
|
|
||||||
|
std::optional<uint8_t> DaliComm::query(int dec_addr, uint8_t cmd) const {
|
||||||
|
return queryCmd(toCmdAddr(dec_addr), cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::getBusStatus() const { return checkGatewayType(0) > 0; }
|
||||||
|
|
||||||
|
uint8_t DaliComm::toCmdAddr(int dec_addr) { return static_cast<uint8_t>(dec_addr * 2 + 1); }
|
||||||
|
|
||||||
|
uint8_t DaliComm::toArcAddr(int dec_addr) { return static_cast<uint8_t>(dec_addr * 2); }
|
||||||
|
|
||||||
|
bool DaliComm::writeFrame(const std::vector<uint8_t>& frame) const {
|
||||||
|
if (!send_) return false;
|
||||||
|
return send_(frame.data(), frame.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaliComm::sleepMs(uint32_t ms) const {
|
||||||
|
if (delay_) {
|
||||||
|
delay_(ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(ms));
|
||||||
|
#else
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::sendCmd(uint8_t addr, uint8_t cmd) const {
|
||||||
|
const std::vector<uint8_t> frame{0x10, addr, cmd};
|
||||||
|
return writeFrame(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliComm::sendExtCmd(uint8_t addr, uint8_t cmd) const {
|
||||||
|
const std::vector<uint8_t> frame{0x11, addr, cmd};
|
||||||
|
const bool ret = writeFrame(frame);
|
||||||
|
sleepMs(100);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uint8_t> DaliComm::queryCmd(uint8_t addr, uint8_t cmd) const {
|
||||||
|
if (!transact_) return std::nullopt;
|
||||||
|
const std::vector<uint8_t> frame{0x12, addr, cmd};
|
||||||
|
const auto resp = transact_(frame.data(), frame.size());
|
||||||
|
if (resp.empty()) return std::nullopt;
|
||||||
|
|
||||||
|
// Gateway type 1 returns: 0xFF <data> on success; 0xFE no response; 0xFD invalid frame.
|
||||||
|
if (resp.size() == 1) {
|
||||||
|
if (resp[0] == 0xFE || resp[0] == 0xFD) return std::nullopt;
|
||||||
|
return resp[0];
|
||||||
|
}
|
||||||
|
if (resp[0] == 0xFF && resp.size() >= 2) {
|
||||||
|
return resp[1];
|
||||||
|
}
|
||||||
|
return resp.back();
|
||||||
|
}
|
||||||
418
src/decode.cpp
Normal file
418
src/decode.cpp
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
#include "decode.hpp"
|
||||||
|
|
||||||
|
#include "base.hpp"
|
||||||
|
#include "dali_define.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
DaliDecode::DaliDecode() {
|
||||||
|
cmd_ = {
|
||||||
|
{DALI_CMD_OFF, "OFF"},
|
||||||
|
{DALI_CMD_RECALL_MAX_LEVEL, "RECALL_MAX_LEVEL"},
|
||||||
|
{DALI_CMD_RECALL_MIN_LEVEL, "RECALL_MIN_LEVEL"},
|
||||||
|
{DALI_CMD_RESET, "RESET"},
|
||||||
|
{DALI_CMD_STORE_ACTUAL_LEVEL_IN_THE_DTR, "STORE_ACTUAL_LEVEL_IN_THE_DTR"},
|
||||||
|
{DALI_CMD_STORE_THE_DTR_AS_MAX_LEVEL, "STORE_THE_DTR_AS_MAX_LEVEL"},
|
||||||
|
{DALI_CMD_STORE_THE_DTR_AS_MIN_LEVEL, "STORE_THE_DTR_AS_MIN_LEVEL"},
|
||||||
|
{DALI_CMD_STORE_THE_DTR_AS_SYS_FAIL_LEVEL, "STORE_THE_DTR_AS_SYS_FAIL_LEVEL"},
|
||||||
|
{DALI_CMD_STORE_THE_DTR_AS_PWR_ON_LEVEL, "STORE_THE_DTR_AS_PWR_ON_LEVEL"},
|
||||||
|
{DALI_CMD_STORE_THE_DTR_AS_FADE_TIME, "STORE_THE_DTR_AS_FADE_TIME"},
|
||||||
|
{DALI_CMD_STORE_THE_DTR_AS_FADE_RATE, "STORE_THE_DTR_AS_FADE_RATE"},
|
||||||
|
{DALI_CMD_STORE_DTR_AS_SHORT_ADDRESS, "STORE_DTR_AS_SHORT_ADDRESS"},
|
||||||
|
{DALI_CMD_QUERY_STATUS, "QUERY_STATUS"},
|
||||||
|
{DALI_CMD_QUERY_BALLAST, "QUERY_BALLAST"},
|
||||||
|
{DALI_CMD_QUERY_LAMP_FAILURE, "QUERY_LAMP_FAILURE"},
|
||||||
|
{DALI_CMD_QUERY_LAMP_POWER_ON, "QUERY_LAMP_POWER_ON"},
|
||||||
|
{DALI_CMD_QUERY_LIMIT_ERROR, "QUERY_LIMIT_ERROR"},
|
||||||
|
{DALI_CMD_QUERY_RESET_STATE, "QUERY_RESET_STATE"},
|
||||||
|
{DALI_CMD_QUERY_MISSING_SHORT_ADDRESS, "QUERY_MISSING_SHORT_ADDRESS"},
|
||||||
|
{DALI_CMD_QUERY_VERSION_NUMBER, "QUERY_VERSION_NUMBER"},
|
||||||
|
{DALI_CMD_QUERY_CONTENT_DTR, "QUERY_CONTENT_DTR"},
|
||||||
|
{DALI_CMD_QUERY_DEVICE_TYPE, "QUERY_DEVICE_TYPE"},
|
||||||
|
{DALI_CMD_QUERY_PHYSICAL_MINIMUM_LEVEL, "QUERY_PHYSICAL_MINIMUM_LEVEL"},
|
||||||
|
{DALI_CMD_QUERY_CONTENT_DTR1, "QUERY_CONTENT_DTR1"},
|
||||||
|
{DALI_CMD_QUERY_CONTENT_DTR2, "QUERY_CONTENT_DTR2"},
|
||||||
|
{DALI_CMD_QUERY_OPERATING_MODE, "QUERY_OPERATING_MODE"},
|
||||||
|
{DALI_CMD_QUERY_LIGHT_SOURCE_TYPE, "QUERY_LIGHT_SOURCE_TYPE"},
|
||||||
|
{DALI_CMD_QUERY_ACTUAL_LEVEL, "QUERY_ACTUAL_LEVEL"},
|
||||||
|
{DALI_CMD_QUERY_MAX_LEVEL, "QUERY_MAX_LEVEL"},
|
||||||
|
{DALI_CMD_QUERY_MIN_LEVEL, "QUERY_MIN_LEVEL"},
|
||||||
|
{DALI_CMD_QUERY_POWER_ON_LEVEL, "QUERY_POWER_ON_LEVEL"},
|
||||||
|
{DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL, "QUERY_SYSTEM_FAILURE_LEVEL"},
|
||||||
|
{DALI_CMD_QUERY_FADE_TIME_FADE_RATE, "QUERY_FADE_TIME/FADE_RATE"},
|
||||||
|
{DALI_CMD_QUERY_NEXT_DEVICE_TYPE, "QUERY_NEXT_DEVICE_TYPE"},
|
||||||
|
{DALI_CMD_QUERY_GROUPS_0_7, "QUERY_GROUPS_0-7"},
|
||||||
|
{DALI_CMD_QUERY_GROUP_8_15, "QUERY_GROUP_8-15"},
|
||||||
|
{DALI_CMD_QUERY_RANDOM_ADDRESS_H, "QUERY_RANDOM_ADDRESS_(H)"},
|
||||||
|
{DALI_CMD_QUERY_RANDOM_ADDRESS_M, "QUERY_RANDOM_ADDRESS_(M)"},
|
||||||
|
{DALI_CMD_QUERY_RANDOM_ADDRESS_L, "QUERY_RANDOM_ADDRESS_(L)"},
|
||||||
|
{DALI_CMD_READ_MEMORY_LOCATION, "READ_MEMORY_LOCATION"},
|
||||||
|
{DALI_CMD_DT8_STORE_DTR_AS_COLORX, "STORE_DTR_AS_COLORX"},
|
||||||
|
{DALI_CMD_DT8_STORE_DTR_AS_COLORY, "STORE_DTR_AS_COLORY"},
|
||||||
|
{DALI_CMD_DT8_ACTIVATE, "ACTIVATE"},
|
||||||
|
{DALI_CMD_DT8_SET_COLOR_TEMPERATURE, "SET_COLOR_TEMPERATURE"},
|
||||||
|
{DALI_CMD_DT8_STEP_UP_COLOR_TEMPERATURE, "STEP_UP_COLOR_TEMPERATURE"},
|
||||||
|
{DALI_CMD_DT8_STEP_DOWN_COLOR_TEMPERATURE, "STEP_DOWN_COLOR_TEMPERATURE"},
|
||||||
|
{DALI_CMD_QUERY_COLOR_STATUS, "QUERY_COLOR_STATUS"},
|
||||||
|
{DALI_CMD_QUERY_COLOR_TYPE, "QUERY_COLOR_TYPE"},
|
||||||
|
{DALI_CMD_QUERY_COLOR_VALUE, "QUERY_COLOR_VALUE"},
|
||||||
|
};
|
||||||
|
|
||||||
|
sCMD_ = {
|
||||||
|
{DALI_CMD_SPECIAL_TERMINATE, "TERMINATE"},
|
||||||
|
{DALI_CMD_SPECIAL_SET_DTR0, "SET_DTR0"},
|
||||||
|
{DALI_CMD_SPECIAL_INITIALIZE, "INITIALIZE"},
|
||||||
|
{DALI_CMD_SPECIAL_RANDOMIZE, "RANDOMIZE"},
|
||||||
|
{DALI_CMD_SPECIAL_COMPARE, "COMPARE"},
|
||||||
|
{DALI_CMD_SPECIAL_WITHDRAW, "WITHDRAW"},
|
||||||
|
{DALI_CMD_SPECIAL_SEARCHADDRH, "SEARCHADDRH"},
|
||||||
|
{DALI_CMD_SPECIAL_SEARCHADDRM, "SEARCHADDRM"},
|
||||||
|
{DALI_CMD_SPECIAL_SEARCHADDRL, "SEARCHADDRL"},
|
||||||
|
{DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS, "PROGRAM_SHORT_ADDRESS"},
|
||||||
|
{DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS, "VERIFY_SHORT_ADDRESS"},
|
||||||
|
{DALI_CMD_SPECIAL_QUERY_SHORT_ADDRESS, "QUERY_SHORT_ADDRESS"},
|
||||||
|
{DALI_CMD_SPECIAL_PHYSICAL_SELECTION, "PHYSICAL_SELECTION"},
|
||||||
|
{DALI_CMD_SPECIAL_DT_SELECT, "DT_SELECT"},
|
||||||
|
{DALI_CMD_SPECIAL_SET_DTR_1, "SET_DTR_1"},
|
||||||
|
{DALI_CMD_SPECIAL_SET_DTR_2, "SET_DTR_2"},
|
||||||
|
};
|
||||||
|
|
||||||
|
queryCmd_ = {DALI_CMD_QUERY_STATUS,
|
||||||
|
DALI_CMD_QUERY_BALLAST,
|
||||||
|
DALI_CMD_QUERY_LAMP_FAILURE,
|
||||||
|
DALI_CMD_QUERY_LAMP_POWER_ON,
|
||||||
|
DALI_CMD_QUERY_LIMIT_ERROR,
|
||||||
|
DALI_CMD_QUERY_RESET_STATE,
|
||||||
|
DALI_CMD_QUERY_MISSING_SHORT_ADDRESS,
|
||||||
|
DALI_CMD_QUERY_VERSION_NUMBER,
|
||||||
|
DALI_CMD_QUERY_CONTENT_DTR,
|
||||||
|
DALI_CMD_QUERY_DEVICE_TYPE,
|
||||||
|
DALI_CMD_QUERY_PHYSICAL_MINIMUM_LEVEL,
|
||||||
|
DALI_CMD_QUERY_POWER_FAILURE,
|
||||||
|
DALI_CMD_QUERY_CONTENT_DTR1,
|
||||||
|
DALI_CMD_QUERY_CONTENT_DTR2,
|
||||||
|
DALI_CMD_QUERY_OPERATING_MODE,
|
||||||
|
DALI_CMD_QUERY_LIGHT_SOURCE_TYPE,
|
||||||
|
DALI_CMD_QUERY_ACTUAL_LEVEL,
|
||||||
|
DALI_CMD_QUERY_MAX_LEVEL,
|
||||||
|
DALI_CMD_QUERY_MIN_LEVEL,
|
||||||
|
DALI_CMD_QUERY_POWER_ON_LEVEL,
|
||||||
|
DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL,
|
||||||
|
DALI_CMD_QUERY_FADE_TIME_FADE_RATE,
|
||||||
|
DALI_CMD_QUERY_MANUFACTURER_SPECIFIC_MODE,
|
||||||
|
DALI_CMD_QUERY_NEXT_DEVICE_TYPE,
|
||||||
|
DALI_CMD_QUERY_EXTENDED_FADE_TIME,
|
||||||
|
DALI_CMD_QUERY_CONTROL_GEAR_FAILURE,
|
||||||
|
DALI_CMD_QUERY_GROUPS_0_7,
|
||||||
|
DALI_CMD_QUERY_GROUP_8_15,
|
||||||
|
DALI_CMD_QUERY_RANDOM_ADDRESS_H,
|
||||||
|
DALI_CMD_QUERY_RANDOM_ADDRESS_M,
|
||||||
|
DALI_CMD_QUERY_RANDOM_ADDRESS_L,
|
||||||
|
DALI_CMD_READ_MEMORY_LOCATION,
|
||||||
|
DALI_CMD_DT8_ACTIVATE,
|
||||||
|
DALI_CMD_DT8_SET_COLOR_TEMPERATURE,
|
||||||
|
DALI_CMD_DT8_STEP_UP_COLOR_TEMPERATURE,
|
||||||
|
DALI_CMD_DT8_STEP_DOWN_COLOR_TEMPERATURE,
|
||||||
|
DALI_CMD_QUERY_COLOR_VALUE};
|
||||||
|
for (int c = DALI_CMD_QUERY_SCENE_LEVEL_MIN; c <= DALI_CMD_QUERY_SCENE_LEVEL_MAX; c++) {
|
||||||
|
queryCmd_.push_back(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliDecode::isQueryCmd(int cmd) const {
|
||||||
|
return std::find(queryCmd_.begin(), queryCmd_.end(), cmd) != queryCmd_.end() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::decodeBright(int addr, int level) {
|
||||||
|
DecodedRecord r;
|
||||||
|
r.text = whoLabel(addr, true) + " DAPC level=" + std::to_string(level);
|
||||||
|
r.type = "brightness";
|
||||||
|
r.addr = addr;
|
||||||
|
r.cmd = level;
|
||||||
|
r.proto = 0x10;
|
||||||
|
return withRaw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::decodeScene(int addr, int sceneCmd) {
|
||||||
|
const int sc = sceneCmd - DALI_CMD_GO_TO_SCENE_MIN;
|
||||||
|
DecodedRecord r;
|
||||||
|
r.text = whoLabel(addr, false) + " GO_TO_SCENE " + std::to_string(sc);
|
||||||
|
r.type = "cmd";
|
||||||
|
r.addr = addr;
|
||||||
|
r.cmd = sceneCmd;
|
||||||
|
r.proto = 0x10;
|
||||||
|
return withRaw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::decodeCmd(int addr, int c) {
|
||||||
|
std::string name = "CMD 0x" + hex(c);
|
||||||
|
const auto it = cmd_.find(c);
|
||||||
|
if (it != cmd_.end()) name = it->second;
|
||||||
|
|
||||||
|
if (c >= DALI_CMD_SET_SCENE_MIN && c <= DALI_CMD_SET_SCENE_MAX) {
|
||||||
|
name = "SET_SCENE " + std::to_string(c - DALI_CMD_SET_SCENE_MIN);
|
||||||
|
} else if (c >= DALI_CMD_ADD_TO_GROUP_MIN && c <= DALI_CMD_ADD_TO_GROUP_MAX) {
|
||||||
|
name = "ADD_TO_GROUP " + std::to_string(c - DALI_CMD_ADD_TO_GROUP_MIN);
|
||||||
|
} else if (c >= DALI_CMD_REMOVE_FROM_GROUP_MIN && c <= DALI_CMD_REMOVE_FROM_GROUP_MAX) {
|
||||||
|
name = "REMOVE_FROM_GROUP " + std::to_string(c - DALI_CMD_REMOVE_FROM_GROUP_MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord r;
|
||||||
|
r.text = whoLabel(addr, false) + " " + name;
|
||||||
|
r.type = "cmd";
|
||||||
|
r.addr = addr;
|
||||||
|
r.cmd = c;
|
||||||
|
r.proto = 0x10;
|
||||||
|
return withRaw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::decodeSpCmd(int addr, int c) {
|
||||||
|
const int cmdByte = addr & 0xFF;
|
||||||
|
const int dataByte = c & 0xFF;
|
||||||
|
|
||||||
|
if (cmdByte == DALI_CMD_SPECIAL_COMPARE || cmdByte == DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS ||
|
||||||
|
cmdByte == DALI_CMD_SPECIAL_QUERY_SHORT_ADDRESS) {
|
||||||
|
return querySpCMD(cmdByte, dataByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name = "SPECIAL_CMD 0x" + hex(cmdByte);
|
||||||
|
const auto it = sCMD_.find(cmdByte);
|
||||||
|
if (it != sCMD_.end()) name = it->second;
|
||||||
|
|
||||||
|
if (cmdByte == DALI_CMD_SPECIAL_SET_DTR0) {
|
||||||
|
dtr0 = dataByte;
|
||||||
|
} else if (cmdByte == DALI_CMD_SPECIAL_SET_DTR_1) {
|
||||||
|
dtr1 = dataByte;
|
||||||
|
} else if (cmdByte == DALI_CMD_SPECIAL_SET_DTR_2) {
|
||||||
|
dtr2 = dataByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord r;
|
||||||
|
r.text = name + " data=0x" + hex(dataByte) + " (" + std::to_string(dataByte) + ")";
|
||||||
|
r.type = "special";
|
||||||
|
r.addr = addr;
|
||||||
|
r.cmd = c;
|
||||||
|
r.proto = 0x10;
|
||||||
|
return withRaw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::querySpCMD(int cmdByte, int dataByte) {
|
||||||
|
lastQueryCmd_ = cmdByte;
|
||||||
|
lastQueryAtMs_ = nowMs();
|
||||||
|
|
||||||
|
std::string name = "SPECIAL_CMD 0x" + hex(cmdByte);
|
||||||
|
const auto it = sCMD_.find(cmdByte);
|
||||||
|
if (it != sCMD_.end()) name = it->second;
|
||||||
|
|
||||||
|
DecodedRecord r;
|
||||||
|
r.text = name + " query data=0x" + hex(dataByte) + " (" + std::to_string(dataByte) + ")";
|
||||||
|
r.type = "query";
|
||||||
|
r.addr = cmdByte;
|
||||||
|
r.cmd = dataByte;
|
||||||
|
r.proto = 0x10;
|
||||||
|
return withRaw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::decodeQuery(int addr, int c) {
|
||||||
|
lastQueryCmd_ = c;
|
||||||
|
lastQueryAtMs_ = nowMs();
|
||||||
|
|
||||||
|
std::string name = "QUERY 0x" + hex(c);
|
||||||
|
const auto it = cmd_.find(c);
|
||||||
|
if (it != cmd_.end()) name = it->second;
|
||||||
|
|
||||||
|
if (c >= DALI_CMD_QUERY_SCENE_LEVEL_MIN && c <= DALI_CMD_QUERY_SCENE_LEVEL_MAX) {
|
||||||
|
name = "QUERY_SCENE_LEVEL " + std::to_string(c - DALI_CMD_QUERY_SCENE_LEVEL_MIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == DALI_CMD_QUERY_COLOR_VALUE) {
|
||||||
|
lastColourQueryAtMs_ = nowMs();
|
||||||
|
int t = dtr0;
|
||||||
|
if (t == 128) t = 0;
|
||||||
|
if (t == 130) t = 1;
|
||||||
|
pendingColourType_ = (t == 0 || t == 1 || t == 2) ? t : -1;
|
||||||
|
if (pendingColourType_ == 0) name = "QUERY_COLOUR_VALUE(x)";
|
||||||
|
if (pendingColourType_ == 1) name = "QUERY_COLOUR_VALUE(y)";
|
||||||
|
if (pendingColourType_ == 2) name = "QUERY_COLOUR_VALUE(ct)";
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord r;
|
||||||
|
r.text = whoLabel(addr, false) + " " + name;
|
||||||
|
r.type = "query";
|
||||||
|
r.addr = addr;
|
||||||
|
r.cmd = c;
|
||||||
|
r.proto = 0x12;
|
||||||
|
return withRaw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/) {
|
||||||
|
const int64_t now = nowMs();
|
||||||
|
if ((now - lastQueryAtMs_) > responseWindowMs || lastQueryCmd_ == 0) {
|
||||||
|
DecodedRecord r{"unknown back 0x" + hex(value) + " / " + std::to_string(value), "unknown", -1,
|
||||||
|
-1, 0xFF};
|
||||||
|
return withRaw(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int c = lastQueryCmd_;
|
||||||
|
|
||||||
|
if (c == DALI_CMD_QUERY_CONTENT_DTR) dtr0 = value & 0xFF;
|
||||||
|
if (c == DALI_CMD_QUERY_CONTENT_DTR1) dtr1 = value & 0xFF;
|
||||||
|
if (c == DALI_CMD_QUERY_CONTENT_DTR2) dtr2 = value & 0xFF;
|
||||||
|
|
||||||
|
if (c == DALI_CMD_QUERY_STATUS) {
|
||||||
|
const auto ds = DaliStatus::fromByte(static_cast<uint8_t>(value));
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "status gearPresent=" << yn(ds.controlGearPresent) << " lampFailure=" << yn(ds.lampFailure)
|
||||||
|
<< " lampOn=" << yn(ds.lampPowerOn) << " limitError=" << yn(ds.limitError)
|
||||||
|
<< " fadingDone=" << yn(ds.fadingCompleted) << " reset=" << yn(ds.resetState)
|
||||||
|
<< " missingAddr=" << yn(ds.missingShortAddress) << " psFault=" << yn(ds.psFault);
|
||||||
|
return withRaw({oss.str(), "response", -1, c, 0xFF});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == DALI_CMD_QUERY_DEVICE_TYPE) {
|
||||||
|
const auto name = deviceTypeName(value);
|
||||||
|
std::string text = "QUERY_DEVICE_TYPE => ";
|
||||||
|
text += name.empty() ? ("0x" + hex(value) + " / " + std::to_string(value))
|
||||||
|
: (name + " (0x" + hex(value) + " / " + std::to_string(value) + ")");
|
||||||
|
return withRaw({text, "response", -1, c, 0xFF});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == DALI_CMD_QUERY_LIGHT_SOURCE_TYPE) {
|
||||||
|
const auto name = lightSourceName(value);
|
||||||
|
std::string text = "QUERY_LIGHT_SOURCE_TYPE => ";
|
||||||
|
text += name.empty() ? ("0x" + hex(value) + " / " + std::to_string(value))
|
||||||
|
: (name + " (0x" + hex(value) + " / " + std::to_string(value) + ")");
|
||||||
|
return withRaw({text, "response", -1, c, 0xFF});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == DALI_CMD_QUERY_FADE_TIME_FADE_RATE) {
|
||||||
|
const int ft = (value >> 4) & 0xF;
|
||||||
|
const int fr = value & 0xF;
|
||||||
|
return withRaw({"fade time index=" + std::to_string(ft) + ", fade rate code=" + std::to_string(fr),
|
||||||
|
"response", -1, c, 0xFF});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c >= DALI_CMD_QUERY_SCENE_LEVEL_MIN && c <= DALI_CMD_QUERY_SCENE_LEVEL_MAX) {
|
||||||
|
const int idx = c - DALI_CMD_QUERY_SCENE_LEVEL_MIN;
|
||||||
|
const std::string text = (value == 0xFF)
|
||||||
|
? ("scene level " + std::to_string(idx) + " = MASK")
|
||||||
|
: ("scene level " + std::to_string(idx) + " = " + std::to_string(value));
|
||||||
|
return withRaw({text, "response", -1, c, 0xFF});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingColourType_ >= 0 && (now - lastColourQueryAtMs_) <= 1000) {
|
||||||
|
const int combined = dtr1 * 256 + dtr0;
|
||||||
|
std::string pretty;
|
||||||
|
if (pendingColourType_ == 2) {
|
||||||
|
const int kelvin = combined == 0 ? 0 : (1000000 / combined);
|
||||||
|
pretty = "ct mirek=" + std::to_string(combined) + " kelvin=" + std::to_string(kelvin);
|
||||||
|
} else {
|
||||||
|
const char key = pendingColourType_ == 0 ? 'x' : 'y';
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << key << "=" << std::fixed << std::setprecision(4)
|
||||||
|
<< (static_cast<double>(combined) / 65535.0) << " (" << combined << ")";
|
||||||
|
pretty = oss.str();
|
||||||
|
}
|
||||||
|
return withRaw({"colour value " + pretty, "response", -1, c, 0xFF});
|
||||||
|
}
|
||||||
|
|
||||||
|
return withRaw({"0x" + hex(value) + " / " + std::to_string(value) + " / " + bin(value), "response",
|
||||||
|
-1, c, 0xFF});
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::decode(int addr, int c, int proto) {
|
||||||
|
if (addr >= DALI_CMD_SPECIAL_RANGE_MIN && addr <= DALI_CMD_SPECIAL_RANGE_MAX) {
|
||||||
|
return decodeSpCmd(addr, c);
|
||||||
|
}
|
||||||
|
if (proto == 0x12 || isQueryCmd(c) == 1) {
|
||||||
|
return decodeQuery(addr, c);
|
||||||
|
}
|
||||||
|
if ((addr & 1) == 0) {
|
||||||
|
return decodeBright(addr, c);
|
||||||
|
}
|
||||||
|
if (c >= DALI_CMD_GO_TO_SCENE_MIN && c <= DALI_CMD_GO_TO_SCENE_MAX) {
|
||||||
|
return decodeScene(addr, c);
|
||||||
|
}
|
||||||
|
if (proto == 0x11) {
|
||||||
|
if (c == DALI_CMD_QUERY_COLOR_VALUE) return decodeQuery(addr, c);
|
||||||
|
std::string name = "EXT 0x" + hex(c);
|
||||||
|
const auto it = cmd_.find(c);
|
||||||
|
if (it != cmd_.end()) name = it->second;
|
||||||
|
return withRaw({whoLabel(addr, false) + " " + name, "ext", addr, c, proto});
|
||||||
|
}
|
||||||
|
return decodeCmd(addr, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t DaliDecode::nowMs() {
|
||||||
|
const auto now = std::chrono::time_point_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::system_clock::now());
|
||||||
|
return now.time_since_epoch().count();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DaliDecode::hex(int v) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << (v & 0xFF);
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DaliDecode::bin(int v) {
|
||||||
|
std::string out;
|
||||||
|
out.reserve(8);
|
||||||
|
for (int i = 7; i >= 0; i--) {
|
||||||
|
out.push_back(((v >> i) & 0x1) ? '1' : '0');
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DaliDecode::yn(bool b) { return b ? "yes" : "no"; }
|
||||||
|
|
||||||
|
std::string DaliDecode::whoLabel(int addr, bool even) {
|
||||||
|
if (even) {
|
||||||
|
if (addr == 0xFE) return "broadcast";
|
||||||
|
if (addr >= 0x80 && addr <= 0x8F) return "group " + std::to_string(addr - 0x80);
|
||||||
|
return "short " + std::to_string(addr / 2);
|
||||||
|
}
|
||||||
|
if (addr == 0xFF) return "broadcast";
|
||||||
|
if (addr >= 0x80 && addr <= 0x8F) return "group " + std::to_string(addr - 0x80);
|
||||||
|
return "short " + std::to_string((addr - 1) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DaliDecode::deviceTypeName(int v) {
|
||||||
|
static const std::map<int, std::string> map = {{0x00, "general control gear"},
|
||||||
|
{0x01, "self-contained emergency"},
|
||||||
|
{0x06, "LED control gear"},
|
||||||
|
{0x08, "colour control (DT8)"}};
|
||||||
|
const auto it = map.find(v);
|
||||||
|
return it == map.end() ? "" : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DaliDecode::lightSourceName(int v) {
|
||||||
|
static const std::map<int, std::string> map = {
|
||||||
|
{0x00, "fluorescent"}, {0x01, "compact fluorescent"}, {0x02, "high intensity discharge"},
|
||||||
|
{0x04, "incandescent/halogen"}, {0x06, "LED"}, {0x07, "other"}};
|
||||||
|
const auto it = map.find(v);
|
||||||
|
return it == map.end() ? "" : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
DecodedRecord DaliDecode::withRaw(const DecodedRecord& r) const {
|
||||||
|
if (!displayRaw) return r;
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
if (r.addr >= 0) parts.push_back("0x" + hex(r.addr));
|
||||||
|
if (r.cmd >= 0) parts.push_back("0x" + hex(r.cmd));
|
||||||
|
|
||||||
|
if (parts.empty()) return r;
|
||||||
|
|
||||||
|
DecodedRecord out = r;
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << r.text << " [";
|
||||||
|
for (size_t i = 0; i < parts.size(); i++) {
|
||||||
|
if (i > 0) oss << ", ";
|
||||||
|
oss << parts[i];
|
||||||
|
}
|
||||||
|
oss << "]";
|
||||||
|
out.text = oss.str();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
352
src/device.cpp
Normal file
352
src/device.cpp
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::optional<std::vector<int>> asIntList(const DaliValue* value) {
|
||||||
|
if (!value) return std::nullopt;
|
||||||
|
const auto* arr = value->asArray();
|
||||||
|
if (!arr) return std::nullopt;
|
||||||
|
std::vector<int> out;
|
||||||
|
out.reserve(arr->size());
|
||||||
|
for (const auto& v : *arr) {
|
||||||
|
out.push_back(v.asInt().value_or(0));
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue toIntArray(const std::optional<std::vector<int>>& values) {
|
||||||
|
if (!values.has_value()) return DaliValue();
|
||||||
|
DaliValue::Array out;
|
||||||
|
out.reserve(values->size());
|
||||||
|
for (const int v : *values) {
|
||||||
|
out.emplace_back(v);
|
||||||
|
}
|
||||||
|
return DaliValue(std::move(out));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
DaliLongAddress DaliLongAddress::fromJson(const DaliValue::Object* json) {
|
||||||
|
if (!json) return DaliLongAddress{};
|
||||||
|
DaliLongAddress addr;
|
||||||
|
addr.h = getObjectInt(*json, "H").value_or(0);
|
||||||
|
addr.m = getObjectInt(*json, "M").value_or(0);
|
||||||
|
addr.l = getObjectInt(*json, "L").value_or(0);
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object DaliLongAddress::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
out["H"] = h;
|
||||||
|
out["M"] = m;
|
||||||
|
out["L"] = l;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliDeviceCapabilities DaliDeviceCapabilities::fromJson(const DaliValue::Object* json) {
|
||||||
|
DaliDeviceCapabilities c;
|
||||||
|
if (!json) return c;
|
||||||
|
c.supportsDt1 = getObjectBool(*json, "dt1");
|
||||||
|
c.supportsDt8 = getObjectBool(*json, "dt8");
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object DaliDeviceCapabilities::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
if (supportsDt1.has_value()) out["dt1"] = supportsDt1.value();
|
||||||
|
if (supportsDt8.has_value()) out["dt8"] = supportsDt8.value();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaliDeviceCapabilities::merge(const DaliDeviceCapabilities& other) {
|
||||||
|
if (!supportsDt1.has_value()) supportsDt1 = other.supportsDt1;
|
||||||
|
if (!supportsDt8.has_value()) supportsDt8 = other.supportsDt8;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliStatusFlags DaliStatusFlags::fromJson(const DaliValue::Object* json) {
|
||||||
|
DaliStatusFlags s;
|
||||||
|
if (!json) return s;
|
||||||
|
s.controlGearPresent = getObjectBool(*json, "controlGearPresent");
|
||||||
|
s.lampFailure = getObjectBool(*json, "lampFailure");
|
||||||
|
s.lampPowerOn = getObjectBool(*json, "lampPowerOn");
|
||||||
|
s.limitError = getObjectBool(*json, "limitError");
|
||||||
|
s.fadingCompleted = getObjectBool(*json, "fadingCompleted");
|
||||||
|
s.resetState = getObjectBool(*json, "resetState");
|
||||||
|
s.missingShortAddress = getObjectBool(*json, "missingShortAddress");
|
||||||
|
s.psFault = getObjectBool(*json, "psFault");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object DaliStatusFlags::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
if (controlGearPresent.has_value()) out["controlGearPresent"] = controlGearPresent.value();
|
||||||
|
if (lampFailure.has_value()) out["lampFailure"] = lampFailure.value();
|
||||||
|
if (lampPowerOn.has_value()) out["lampPowerOn"] = lampPowerOn.value();
|
||||||
|
if (limitError.has_value()) out["limitError"] = limitError.value();
|
||||||
|
if (fadingCompleted.has_value()) out["fadingCompleted"] = fadingCompleted.value();
|
||||||
|
if (resetState.has_value()) out["resetState"] = resetState.value();
|
||||||
|
if (missingShortAddress.has_value()) out["missingShortAddress"] = missingShortAddress.value();
|
||||||
|
if (psFault.has_value()) out["psFault"] = psFault.value();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliStatusFlags::hasData() const {
|
||||||
|
return controlGearPresent.has_value() || lampFailure.has_value() || lampPowerOn.has_value() ||
|
||||||
|
limitError.has_value() || fadingCompleted.has_value() || resetState.has_value() ||
|
||||||
|
missingShortAddress.has_value() || psFault.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaliStatusFlags::merge(const DaliStatusFlags& other) {
|
||||||
|
if (!controlGearPresent.has_value()) controlGearPresent = other.controlGearPresent;
|
||||||
|
if (!lampFailure.has_value()) lampFailure = other.lampFailure;
|
||||||
|
if (!lampPowerOn.has_value()) lampPowerOn = other.lampPowerOn;
|
||||||
|
if (!limitError.has_value()) limitError = other.limitError;
|
||||||
|
if (!fadingCompleted.has_value()) fadingCompleted = other.fadingCompleted;
|
||||||
|
if (!resetState.has_value()) resetState = other.resetState;
|
||||||
|
if (!missingShortAddress.has_value()) missingShortAddress = other.missingShortAddress;
|
||||||
|
if (!psFault.has_value()) psFault = other.psFault;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliDt8State DaliDt8State::fromJson(const DaliValue::Object* json) {
|
||||||
|
DaliDt8State s;
|
||||||
|
if (!json) return s;
|
||||||
|
|
||||||
|
s.colorType = getObjectInt(*json, "colorType");
|
||||||
|
s.activeMode = getObjectString(*json, "activeMode");
|
||||||
|
|
||||||
|
if (const auto* xyVal = getObjectValue(*json, "xy")) {
|
||||||
|
if (const auto* xy = xyVal->asObject()) {
|
||||||
|
s.xyX = getObjectInt(*xy, "x");
|
||||||
|
s.xyY = getObjectInt(*xy, "y");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* gamutVal = getObjectValue(*json, "gamut")) {
|
||||||
|
if (const auto* gamut = gamutVal->asObject()) {
|
||||||
|
s.xyMinX = getObjectInt(*gamut, "xMin");
|
||||||
|
s.xyMaxX = getObjectInt(*gamut, "xMax");
|
||||||
|
s.xyMinY = getObjectInt(*gamut, "yMin");
|
||||||
|
s.xyMaxY = getObjectInt(*gamut, "yMax");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mirek = getObjectInt(*json, "mirek");
|
||||||
|
s.mirekMin = getObjectInt(*json, "mirekMin");
|
||||||
|
s.mirekMax = getObjectInt(*json, "mirekMax");
|
||||||
|
s.rgbwaf = asIntList(getObjectValue(*json, "rgbwaf"));
|
||||||
|
s.primaryN = asIntList(getObjectValue(*json, "primaryN"));
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object DaliDt8State::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
if (colorType.has_value()) out["colorType"] = colorType.value();
|
||||||
|
if (activeMode.has_value()) out["activeMode"] = activeMode.value();
|
||||||
|
|
||||||
|
if (xyX.has_value() || xyY.has_value()) {
|
||||||
|
DaliValue::Object xy;
|
||||||
|
if (xyX.has_value()) xy["x"] = xyX.value();
|
||||||
|
if (xyY.has_value()) xy["y"] = xyY.value();
|
||||||
|
out["xy"] = std::move(xy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xyMinX.has_value() || xyMaxX.has_value() || xyMinY.has_value() || xyMaxY.has_value()) {
|
||||||
|
DaliValue::Object gamut;
|
||||||
|
if (xyMinX.has_value()) gamut["xMin"] = xyMinX.value();
|
||||||
|
if (xyMaxX.has_value()) gamut["xMax"] = xyMaxX.value();
|
||||||
|
if (xyMinY.has_value()) gamut["yMin"] = xyMinY.value();
|
||||||
|
if (xyMaxY.has_value()) gamut["yMax"] = xyMaxY.value();
|
||||||
|
out["gamut"] = std::move(gamut);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mirek.has_value()) out["mirek"] = mirek.value();
|
||||||
|
if (mirekMin.has_value()) out["mirekMin"] = mirekMin.value();
|
||||||
|
if (mirekMax.has_value()) out["mirekMax"] = mirekMax.value();
|
||||||
|
if (rgbwaf.has_value()) out["rgbwaf"] = toIntArray(rgbwaf);
|
||||||
|
if (primaryN.has_value()) out["primaryN"] = toIntArray(primaryN);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliDt1State DaliDt1State::fromJson(const DaliValue::Object* json) {
|
||||||
|
DaliDt1State s;
|
||||||
|
if (!json) return s;
|
||||||
|
|
||||||
|
s.emergencyLevel = getObjectInt(*json, "emergencyLevel");
|
||||||
|
s.emergencyMinLevel = getObjectInt(*json, "emergencyMinLevel");
|
||||||
|
s.emergencyMaxLevel = getObjectInt(*json, "emergencyMaxLevel");
|
||||||
|
s.prolongTimeMinutes = getObjectInt(*json, "prolongTimeMinutes");
|
||||||
|
s.ratedDurationMinutes = getObjectInt(*json, "ratedDurationMinutes");
|
||||||
|
s.testDelayTime = getObjectInt(*json, "testDelayTime");
|
||||||
|
s.failureStatus = getObjectInt(*json, "failureStatus");
|
||||||
|
s.emergencyStatus = getObjectInt(*json, "emergencyStatus");
|
||||||
|
s.emergencyMode = getObjectInt(*json, "emergencyMode");
|
||||||
|
s.feature = getObjectInt(*json, "feature");
|
||||||
|
s.version = getObjectInt(*json, "version");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object DaliDt1State::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
if (emergencyLevel.has_value()) out["emergencyLevel"] = emergencyLevel.value();
|
||||||
|
if (emergencyMinLevel.has_value()) out["emergencyMinLevel"] = emergencyMinLevel.value();
|
||||||
|
if (emergencyMaxLevel.has_value()) out["emergencyMaxLevel"] = emergencyMaxLevel.value();
|
||||||
|
if (prolongTimeMinutes.has_value()) out["prolongTimeMinutes"] = prolongTimeMinutes.value();
|
||||||
|
if (ratedDurationMinutes.has_value()) out["ratedDurationMinutes"] = ratedDurationMinutes.value();
|
||||||
|
if (testDelayTime.has_value()) out["testDelayTime"] = testDelayTime.value();
|
||||||
|
if (failureStatus.has_value()) out["failureStatus"] = failureStatus.value();
|
||||||
|
if (emergencyStatus.has_value()) out["emergencyStatus"] = emergencyStatus.value();
|
||||||
|
if (emergencyMode.has_value()) out["emergencyMode"] = emergencyMode.value();
|
||||||
|
if (feature.has_value()) out["feature"] = feature.value();
|
||||||
|
if (version.has_value()) out["version"] = version.value();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliDevice DaliDevice::fromJson(const DaliValue::Object& json) {
|
||||||
|
DaliDevice d;
|
||||||
|
d.name = getObjectString(json, "name").value_or("");
|
||||||
|
d.id = getObjectString(json, "id").value_or("");
|
||||||
|
|
||||||
|
if (d.id.empty()) {
|
||||||
|
d.id = d.name.empty() ? "device-unknown" : d.name;
|
||||||
|
}
|
||||||
|
if (d.name.empty()) {
|
||||||
|
d.name = d.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
d.shortAddress = getObjectInt(json, "shortAddress");
|
||||||
|
|
||||||
|
if (const auto* lv = getObjectValue(json, "longAddress")) {
|
||||||
|
if (const auto* lo = lv->asObject()) {
|
||||||
|
d.longAddress = DaliLongAddress::fromJson(lo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.isolated = getObjectBool(json, "isolated").value_or(false);
|
||||||
|
d.brightness = getObjectInt(json, "brightness");
|
||||||
|
d.groupBits = getObjectInt(json, "groupBits");
|
||||||
|
d.scenes = asIntList(getObjectValue(json, "scenes"));
|
||||||
|
|
||||||
|
d.fadeTime = getObjectInt(json, "fadeTime");
|
||||||
|
d.fadeRate = getObjectInt(json, "fadeRate");
|
||||||
|
d.powerOnLevel = getObjectInt(json, "powerOnLevel");
|
||||||
|
d.systemFailureLevel = getObjectInt(json, "systemFailureLevel");
|
||||||
|
d.minLevel = getObjectInt(json, "minLevel");
|
||||||
|
d.maxLevel = getObjectInt(json, "maxLevel");
|
||||||
|
d.operatingMode = getObjectInt(json, "operatingMode");
|
||||||
|
d.physicalMinLevel = getObjectInt(json, "physicalMinLevel");
|
||||||
|
|
||||||
|
d.deviceType = getObjectInt(json, "deviceType");
|
||||||
|
d.extType = asIntList(getObjectValue(json, "extType")).value_or(std::vector<int>{});
|
||||||
|
d.version = getObjectInt(json, "version");
|
||||||
|
|
||||||
|
if (const auto* cv = getObjectValue(json, "capabilities")) {
|
||||||
|
d.capabilities = DaliDeviceCapabilities::fromJson(cv->asObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* dt8v = getObjectValue(json, "dt8")) {
|
||||||
|
if (const auto* obj = dt8v->asObject()) {
|
||||||
|
d.dt8 = DaliDt8State::fromJson(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* dt1v = getObjectValue(json, "dt1")) {
|
||||||
|
if (const auto* obj = dt1v->asObject()) {
|
||||||
|
d.dt1 = DaliDt1State::fromJson(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto* sfv = getObjectValue(json, "statusFlags")) {
|
||||||
|
d.statusFlags = DaliStatusFlags::fromJson(sfv->asObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
d.lastSyncedUtc = getObjectString(json, "lastSyncedUtc");
|
||||||
|
|
||||||
|
if (const auto* mv = getObjectValue(json, "meta")) {
|
||||||
|
if (const auto* obj = mv->asObject()) {
|
||||||
|
d.metadata = *obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object DaliDevice::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
out["id"] = id;
|
||||||
|
out["name"] = name;
|
||||||
|
if (shortAddress.has_value()) out["shortAddress"] = shortAddress.value();
|
||||||
|
if (longAddress.has_value()) out["longAddress"] = longAddress->toJson();
|
||||||
|
out["isolated"] = isolated;
|
||||||
|
|
||||||
|
if (brightness.has_value()) out["brightness"] = brightness.value();
|
||||||
|
if (groupBits.has_value()) out["groupBits"] = groupBits.value();
|
||||||
|
if (scenes.has_value()) out["scenes"] = toIntArray(scenes);
|
||||||
|
|
||||||
|
if (fadeTime.has_value()) out["fadeTime"] = fadeTime.value();
|
||||||
|
if (fadeRate.has_value()) out["fadeRate"] = fadeRate.value();
|
||||||
|
if (powerOnLevel.has_value()) out["powerOnLevel"] = powerOnLevel.value();
|
||||||
|
if (systemFailureLevel.has_value()) out["systemFailureLevel"] = systemFailureLevel.value();
|
||||||
|
if (minLevel.has_value()) out["minLevel"] = minLevel.value();
|
||||||
|
if (maxLevel.has_value()) out["maxLevel"] = maxLevel.value();
|
||||||
|
if (operatingMode.has_value()) out["operatingMode"] = operatingMode.value();
|
||||||
|
if (physicalMinLevel.has_value()) out["physicalMinLevel"] = physicalMinLevel.value();
|
||||||
|
|
||||||
|
if (deviceType.has_value()) out["deviceType"] = deviceType.value();
|
||||||
|
if (!extType.empty()) {
|
||||||
|
DaliValue::Array arr;
|
||||||
|
arr.reserve(extType.size());
|
||||||
|
for (const int t : extType) arr.emplace_back(t);
|
||||||
|
out["extType"] = std::move(arr);
|
||||||
|
}
|
||||||
|
if (version.has_value()) out["version"] = version.value();
|
||||||
|
|
||||||
|
const auto caps = capabilities.toJson();
|
||||||
|
if (!caps.empty()) out["capabilities"] = caps;
|
||||||
|
|
||||||
|
if (dt8.has_value()) out["dt8"] = dt8->toJson();
|
||||||
|
if (dt1.has_value()) out["dt1"] = dt1->toJson();
|
||||||
|
|
||||||
|
if (statusFlags.hasData()) out["statusFlags"] = statusFlags.toJson();
|
||||||
|
if (lastSyncedUtc.has_value()) out["lastSyncedUtc"] = lastSyncedUtc.value();
|
||||||
|
if (!metadata.empty()) out["meta"] = metadata;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DaliDevice::displayName() const { return name.empty() ? id : name; }
|
||||||
|
|
||||||
|
void DaliDevice::merge(const DaliDevice& other) {
|
||||||
|
if (other.shortAddress.has_value()) shortAddress = other.shortAddress;
|
||||||
|
if (!longAddress.has_value()) longAddress = other.longAddress;
|
||||||
|
isolated = other.isolated;
|
||||||
|
|
||||||
|
if (!brightness.has_value()) brightness = other.brightness;
|
||||||
|
if (!groupBits.has_value()) groupBits = other.groupBits;
|
||||||
|
if (!scenes.has_value() && other.scenes.has_value()) scenes = other.scenes;
|
||||||
|
|
||||||
|
if (!fadeTime.has_value()) fadeTime = other.fadeTime;
|
||||||
|
if (!fadeRate.has_value()) fadeRate = other.fadeRate;
|
||||||
|
if (!powerOnLevel.has_value()) powerOnLevel = other.powerOnLevel;
|
||||||
|
if (!systemFailureLevel.has_value()) systemFailureLevel = other.systemFailureLevel;
|
||||||
|
if (!minLevel.has_value()) minLevel = other.minLevel;
|
||||||
|
if (!maxLevel.has_value()) maxLevel = other.maxLevel;
|
||||||
|
if (!operatingMode.has_value()) operatingMode = other.operatingMode;
|
||||||
|
if (!physicalMinLevel.has_value()) physicalMinLevel = other.physicalMinLevel;
|
||||||
|
|
||||||
|
if (!deviceType.has_value()) deviceType = other.deviceType;
|
||||||
|
if (extType.empty() && !other.extType.empty()) extType = other.extType;
|
||||||
|
if (!version.has_value()) version = other.version;
|
||||||
|
|
||||||
|
capabilities.merge(other.capabilities);
|
||||||
|
if (!dt8.has_value() && other.dt8.has_value()) dt8 = other.dt8;
|
||||||
|
if (!dt1.has_value() && other.dt1.has_value()) dt1 = other.dt1;
|
||||||
|
|
||||||
|
statusFlags.merge(other.statusFlags);
|
||||||
|
if (other.lastSyncedUtc.has_value()) lastSyncedUtc = other.lastSyncedUtc;
|
||||||
|
|
||||||
|
for (const auto& kv : other.metadata) {
|
||||||
|
metadata[kv.first] = kv.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
226
src/dt1.cpp
Normal file
226
src/dt1.cpp
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
#include "dt1.hpp"
|
||||||
|
|
||||||
|
#include "dali_define.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
DaliDT1::DaliDT1(DaliBase& base) : base_(base) {}
|
||||||
|
|
||||||
|
bool DaliDT1::enable() { return base_.dtSelect(1); }
|
||||||
|
|
||||||
|
int DaliDT1::addrOf(int a) { return a * 2 + 1; }
|
||||||
|
|
||||||
|
bool DaliDT1::send(int a, int code) { return enable() && base_.sendExtCmd(addrOf(a), code); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::query(int a, int code) {
|
||||||
|
if (!enable()) return std::nullopt;
|
||||||
|
const auto v = base_.queryCmd(static_cast<uint8_t>(addrOf(a)), static_cast<uint8_t>(code));
|
||||||
|
if (!v.has_value() || v.value() == 0xFF) return std::nullopt;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::enableDT1() { return enable(); }
|
||||||
|
|
||||||
|
bool DaliDT1::startDT1Test(int a, int t) {
|
||||||
|
if (t != 1) return false;
|
||||||
|
return send(a, DALI_CMD_DT1_START_FUNCTION_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getDT1EmergencyMode(int a) { return query(a, DALI_CMD_DT1_QUERY_EMERGENCY_MODE); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getDT1Feature(int a) { return query(a, DALI_CMD_DT1_QUERY_FEATURE); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getDT1FailureStatus(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_FAILURE_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getDT1Status(int a) { return query(a, DALI_CMD_DT1_QUERY_STATUS); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getDT1SelfTestStatus(int a) {
|
||||||
|
const auto ret = getDT1FailureStatus(a);
|
||||||
|
if (!ret.has_value()) return std::nullopt;
|
||||||
|
const bool inProgress = (ret.value() & 0x01) != 0;
|
||||||
|
return inProgress ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DT1TestStatusDetailed> DaliDT1::getDT1TestStatusDetailed(int a) {
|
||||||
|
DT1TestStatusDetailed result;
|
||||||
|
result.failureStatus = getDT1FailureStatus(a);
|
||||||
|
result.emergencyStatus = getDT1Status(a);
|
||||||
|
result.emergencyMode = getDT1EmergencyMode(a);
|
||||||
|
result.feature = getDT1Feature(a);
|
||||||
|
|
||||||
|
if (!result.failureStatus.has_value()) return std::nullopt;
|
||||||
|
|
||||||
|
const int failure = result.failureStatus.value();
|
||||||
|
result.testInProgress = (failure & 0x01) != 0;
|
||||||
|
result.lampFailure = (failure & 0x02) != 0;
|
||||||
|
result.batteryFailure = (failure & 0x04) != 0;
|
||||||
|
result.functionTestActive = (failure & 0x08) != 0;
|
||||||
|
result.durationTestActive = (failure & 0x10) != 0;
|
||||||
|
result.testDone = (failure & 0x20) != 0;
|
||||||
|
result.identifyActive = (failure & 0x40) != 0;
|
||||||
|
result.physicalSelectionActive = (failure & 0x80) != 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::performDT1Test(int a, int timeout) {
|
||||||
|
if (!startDT1Test(a)) return false;
|
||||||
|
for (int i = 0; i < timeout; i++) {
|
||||||
|
const auto ret = getDT1SelfTestStatus(a);
|
||||||
|
if (!ret.has_value()) return false;
|
||||||
|
if (ret.value() == 0) return true;
|
||||||
|
if (ret.value() != 1) return false;
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::rest(int a) { return send(a, DALI_CMD_DT1_REST); }
|
||||||
|
|
||||||
|
bool DaliDT1::inhibit(int a) { return send(a, DALI_CMD_DT1_INHIBIT); }
|
||||||
|
|
||||||
|
bool DaliDT1::reLightOrResetInhibit(int a) { return send(a, DALI_CMD_DT1_RE_LIGHT_RESET_INHIBIT); }
|
||||||
|
|
||||||
|
bool DaliDT1::startFunctionTestCmd(int a) { return send(a, DALI_CMD_DT1_START_FUNCTION_TEST); }
|
||||||
|
|
||||||
|
bool DaliDT1::startDurationTestCmd(int a) { return send(a, DALI_CMD_DT1_START_DURATION_TEST); }
|
||||||
|
|
||||||
|
bool DaliDT1::stopTest(int a) { return send(a, DALI_CMD_DT1_STOP_TEST); }
|
||||||
|
|
||||||
|
bool DaliDT1::resetFunctionTestDoneFlag(int a) {
|
||||||
|
return send(a, DALI_CMD_DT1_RESET_FUNCTION_TEST_DONE_FLAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::resetDurationTestDoneFlag(int a) {
|
||||||
|
return send(a, DALI_CMD_DT1_RESET_DURATION_TEST_DONE_FLAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::resetLampTime(int a) { return send(a, DALI_CMD_DT1_RESET_LAMP_TIME); }
|
||||||
|
|
||||||
|
bool DaliDT1::resetStatusFlags(int a) { return resetFunctionTestDoneFlag(a); }
|
||||||
|
|
||||||
|
bool DaliDT1::resetLampOperationTime(int a) { return resetDurationTestDoneFlag(a); }
|
||||||
|
|
||||||
|
bool DaliDT1::resetTestResults(int a) { return resetLampTime(a); }
|
||||||
|
|
||||||
|
bool DaliDT1::storeEmergencyLevel(int a, int level) {
|
||||||
|
const int v = std::clamp(level, 0, 254);
|
||||||
|
return enable() && base_.setDTR(v) &&
|
||||||
|
base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_STORE_DTR_AS_EMERGENCY_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeTestDelayTimeHighByte(int a, int highByte) {
|
||||||
|
const int v = std::clamp(highByte, 0, 255);
|
||||||
|
return enable() && base_.setDTR(v) &&
|
||||||
|
base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_STORE_DTR_AS_DELAY_TIME_HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeTestDelayTimeLowByte(int a, int lowByte) {
|
||||||
|
const int v = std::clamp(lowByte, 0, 255);
|
||||||
|
return enable() && base_.setDTR(v) &&
|
||||||
|
base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_STORE_DTR_AS_DELAY_TIME_LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeFunctionTestIntervalDays(int a, int days) {
|
||||||
|
return storeTestDelayTimeHighByte(a, days);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeDurationTestIntervalWeeks(int a, int weeks) {
|
||||||
|
return storeTestDelayTimeLowByte(a, weeks);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeTestDelayTime16(int a, int quartersOfHour) {
|
||||||
|
const int v = std::clamp(quartersOfHour, 0, 0xFFFF);
|
||||||
|
const int hi = (v >> 8) & 0xFF;
|
||||||
|
const int lo = v & 0xFF;
|
||||||
|
return storeTestDelayTimeHighByte(a, hi) && storeTestDelayTimeLowByte(a, lo);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeProlongTimeMinutes(int a, int minutes) {
|
||||||
|
const int v = std::clamp(minutes, 0, 255);
|
||||||
|
return enable() && base_.setDTR(v) &&
|
||||||
|
base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_STORE_DTR_AS_PROLONG_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeRatedDurationMinutes(int a, int minutes) {
|
||||||
|
const int v = std::clamp(minutes, 0, 255);
|
||||||
|
return enable() && base_.setDTR(v) &&
|
||||||
|
base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_STORE_DTR_AS_RATED_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeEmergencyMinLevel(int a, int level) {
|
||||||
|
const int v = std::clamp(level, 0, 254);
|
||||||
|
return enable() && base_.setDTR(v) &&
|
||||||
|
base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_STORE_DTR_AS_EMERGENCY_MIN_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::storeEmergencyMaxLevel(int a, int level) {
|
||||||
|
const int v = std::clamp(level, 0, 254);
|
||||||
|
return enable() && base_.setDTR(v) &&
|
||||||
|
base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_STORE_DTR_AS_EMERGENCY_MAX_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT1::startIdentification(int a) { return send(a, DALI_CMD_DT1_START_IDENTIFICATION); }
|
||||||
|
|
||||||
|
bool DaliDT1::performDTRSelectedFunction(int a,
|
||||||
|
const std::optional<int>& dtr0,
|
||||||
|
const std::optional<int>& dtr1) {
|
||||||
|
if (!enable()) return false;
|
||||||
|
if (dtr0.has_value() && !base_.setDTR(dtr0.value() & 0xFF)) return false;
|
||||||
|
if (dtr1.has_value() && !base_.setDTR1(dtr1.value() & 0xFF)) return false;
|
||||||
|
return base_.sendExtCmd(addrOf(a), DALI_CMD_DT1_PERFORM_DTR_SELECTED_FUNCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getExtendedVersionDT1(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_EXTENDED_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getEmergencyLevel(int a) { return query(a, DALI_CMD_DT1_QUERY_EMERGENCY_LEVEL); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getEmergencyMinLevel(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_EMERGENCY_MIN_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getEmergencyMaxLevel(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_EMERGENCY_MAX_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getProlongTimeMinutes(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_PROLONG_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getFunctionTestIntervalDays(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_FUNCTION_TEST_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getDurationTestIntervalWeeks(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_DURATION_TEST_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getDurationTestResult(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_DURATION_TEST_RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getLampEmergencyTimeMinutes(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_LAMP_EMERGENCY_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT1::getRatedDurationMinutes(int a) {
|
||||||
|
return query(a, DALI_CMD_DT1_QUERY_RATED_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DaliDT1DeviceStatus> DaliDT1::getDeviceStatus(int a) {
|
||||||
|
if (!enable()) return std::nullopt;
|
||||||
|
const auto raw = base_.queryCmd(static_cast<uint8_t>(addrOf(a)), DALI_CMD_QUERY_STATUS);
|
||||||
|
if (!raw.has_value()) return std::nullopt;
|
||||||
|
return DaliDT1DeviceStatus(raw.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DaliDT1EmergencyStatus> DaliDT1::getEmergencyStatusDecoded(int a) {
|
||||||
|
const auto v = getDT1Status(a);
|
||||||
|
if (!v.has_value()) return std::nullopt;
|
||||||
|
return DaliDT1EmergencyStatus(v.value());
|
||||||
|
}
|
||||||
468
src/dt8.cpp
Normal file
468
src/dt8.cpp
Normal file
@@ -0,0 +1,468 @@
|
|||||||
|
#include "dt8.hpp"
|
||||||
|
|
||||||
|
#include "dali_define.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
DaliDT8::DaliDT8(DaliBase& base) : base_(base) {}
|
||||||
|
|
||||||
|
bool DaliDT8::enableDT8() { return base_.dtSelect(8); }
|
||||||
|
|
||||||
|
std::optional<ColorTypeFeature> DaliDT8::getColorTypeFeature(int a) {
|
||||||
|
const auto addr = DaliComm::toCmdAddr(a);
|
||||||
|
base_.dtSelect(8);
|
||||||
|
const auto result = base_.queryCmd(addr, DALI_CMD_QUERY_COLOR_TYPE);
|
||||||
|
if (!result.has_value()) return std::nullopt;
|
||||||
|
return ColorTypeFeature(result.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ColorStatus> DaliDT8::getColorStatus(int a) {
|
||||||
|
const auto addr = DaliComm::toCmdAddr(a);
|
||||||
|
base_.dtSelect(8);
|
||||||
|
const auto result = base_.queryCmd(addr, DALI_CMD_QUERY_COLOR_STATUS);
|
||||||
|
if (!result.has_value()) return std::nullopt;
|
||||||
|
return ColorStatus(result.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getColTempRaw(int a, int type) {
|
||||||
|
int selector;
|
||||||
|
switch (type) {
|
||||||
|
case 0:
|
||||||
|
selector = 128;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
selector = 130;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
selector = 129;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
selector = 131;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
default:
|
||||||
|
selector = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto features = getColorTypeFeature(a);
|
||||||
|
if (!features.has_value() || !features->ctCapable()) return 0;
|
||||||
|
const auto v = getColourRaw(a, selector);
|
||||||
|
if (!v.has_value()) return 0;
|
||||||
|
return v.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setColTempRaw(int a, int value) {
|
||||||
|
int v = value;
|
||||||
|
if (v < 0) v = 0;
|
||||||
|
if (v > 65535) v = 65535;
|
||||||
|
const int dtr = v & 0xFF;
|
||||||
|
const int dtr1 = (v >> 8) & 0xFF;
|
||||||
|
|
||||||
|
if (!base_.setDTR(dtr)) return false;
|
||||||
|
if (!base_.setDTR1(dtr1)) return false;
|
||||||
|
if (!base_.dtSelect(8)) return false;
|
||||||
|
if (!base_.setDTRAsColourTemp(a)) return false;
|
||||||
|
if (!base_.dtSelect(8)) return false;
|
||||||
|
return base_.activate(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setColorTemperature(int addr, int kelvin) {
|
||||||
|
int v = kelvin == 0 ? 1 : kelvin;
|
||||||
|
const int mirek = static_cast<int>(std::floor(1000000.0 / static_cast<double>(v)));
|
||||||
|
return setColTempRaw(addr, mirek);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getColorTemperature(int a) {
|
||||||
|
const auto mirek = getColourRaw(a, 2);
|
||||||
|
if (!mirek.has_value() || mirek.value() == 0) return std::nullopt;
|
||||||
|
const int kelvin = static_cast<int>(std::floor(1000000.0 / static_cast<double>(mirek.value())));
|
||||||
|
return kelvin;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getMinColorTemperature(int a) {
|
||||||
|
const auto mirek = getColTempRaw(a, 0);
|
||||||
|
if (!mirek.has_value() || mirek.value() == 0) return std::nullopt;
|
||||||
|
return static_cast<int>(std::floor(1000000.0 / static_cast<double>(mirek.value())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getMaxColorTemperature(int a) {
|
||||||
|
const auto mirek = getColTempRaw(a, 1);
|
||||||
|
if (!mirek.has_value() || mirek.value() == 0) return std::nullopt;
|
||||||
|
return static_cast<int>(std::floor(1000000.0 / static_cast<double>(mirek.value())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getPhysicalMinColorTemperature(int a) {
|
||||||
|
const auto mirek = getColTempRaw(a, 3);
|
||||||
|
if (!mirek.has_value() || mirek.value() == 0) return std::nullopt;
|
||||||
|
return static_cast<int>(std::floor(1000000.0 / static_cast<double>(mirek.value())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getPhysicalMaxColorTemperature(int a) {
|
||||||
|
const auto mirek = getColTempRaw(a, 4);
|
||||||
|
if (!mirek.has_value() || mirek.value() == 0) return std::nullopt;
|
||||||
|
return static_cast<int>(std::floor(1000000.0 / static_cast<double>(mirek.value())));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setColourRaw(int addr, int x1, int y1) {
|
||||||
|
const int x1L = x1 & 0xFF;
|
||||||
|
const int y1L = y1 & 0xFF;
|
||||||
|
const int x1H = (x1 >> 8) & 0xFF;
|
||||||
|
const int y1H = (y1 >> 8) & 0xFF;
|
||||||
|
|
||||||
|
const int a = addr / 2;
|
||||||
|
if (!base_.setDTR(x1L)) return false;
|
||||||
|
if (!base_.setDTR1(x1H)) return false;
|
||||||
|
if (!base_.dtSelect(8)) return false;
|
||||||
|
if (!base_.setDTRAsColourX(a)) return false;
|
||||||
|
if (!base_.setDTR(y1L)) return false;
|
||||||
|
if (!base_.setDTR1(y1H)) return false;
|
||||||
|
if (!base_.dtSelect(8)) return false;
|
||||||
|
if (!base_.setDTRAsColourY(a)) return false;
|
||||||
|
if (!base_.dtSelect(8)) return false;
|
||||||
|
return base_.activate(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryColourXRaw(int addr, int x1) {
|
||||||
|
const int x1L = x1 & 0xFF;
|
||||||
|
const int x1H = (x1 >> 8) & 0xFF;
|
||||||
|
const int a = addr / 2;
|
||||||
|
return base_.setDTR(x1L) && base_.setDTR1(x1H) && base_.dtSelect(8) && base_.setDTRAsColourX(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryColourYRaw(int addr, int y1) {
|
||||||
|
const int y1L = y1 & 0xFF;
|
||||||
|
const int y1H = (y1 >> 8) & 0xFF;
|
||||||
|
const int a = addr / 2;
|
||||||
|
return base_.setDTR(y1L) && base_.setDTR1(y1H) && base_.dtSelect(8) && base_.setDTRAsColourY(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryColourXY(int a, double x, double y) {
|
||||||
|
double xClamped = std::clamp(x, 0.0, 1.0);
|
||||||
|
double yClamped = std::clamp(y, 0.0, 1.0);
|
||||||
|
const int x1 = static_cast<int>(std::round(xClamped * 65535.0));
|
||||||
|
const int y1 = static_cast<int>(std::round(yClamped * 65535.0));
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return setTemporaryColourXRaw(addr, x1) && setTemporaryColourYRaw(addr, y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryColourTemperature(int a, int kelvin) {
|
||||||
|
int k = kelvin <= 0 ? 1 : kelvin;
|
||||||
|
const int mirek = static_cast<int>(std::floor(1000000.0 / static_cast<double>(k)));
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
const int dtr = mirek & 0xFF;
|
||||||
|
const int dtr1 = (mirek >> 8) & 0xFF;
|
||||||
|
const int dec = addr / 2;
|
||||||
|
return base_.setDTR(dtr) && base_.setDTR1(dtr1) && base_.dtSelect(8) && base_.setDTRAsColourTemp(dec);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryPrimaryDimLevel(int a, int n, double level) {
|
||||||
|
int idx = std::clamp(n, 0, 5);
|
||||||
|
double v = std::clamp(level, 0.0, 1.0);
|
||||||
|
int raw = static_cast<int>(std::round(v * 65535.0));
|
||||||
|
if (raw > 65534) raw = 65534;
|
||||||
|
const int lsb = raw & 0xFF;
|
||||||
|
const int msb = (raw >> 8) & 0xFF;
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.setDTR(lsb) && base_.setDTR1(msb) && base_.setDTR2(idx) &&
|
||||||
|
base_.dtSelect(8) && base_.sendExtCmd(addr, DALI_CMD_DT8_SET_TEMPORARY_PRIMARY_DIM_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryRGBDimLevels(int a, int r, int g, int b) {
|
||||||
|
const int R = std::clamp(r, 0, 255);
|
||||||
|
const int G = std::clamp(g, 0, 255);
|
||||||
|
const int B = std::clamp(b, 0, 255);
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.setDTR(R) && base_.setDTR1(G) && base_.setDTR2(B) &&
|
||||||
|
base_.dtSelect(8) && base_.sendExtCmd(addr, DALI_CMD_DT8_SET_TEMPORARY_RGB_DIM_LEVELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryWAFDimLevels(int a, int w, int amber, int freecolour) {
|
||||||
|
const int W = std::clamp(w, 0, 255);
|
||||||
|
const int A = std::clamp(amber, 0, 255);
|
||||||
|
const int F = std::clamp(freecolour, 0, 255);
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.setDTR(W) && base_.setDTR1(A) && base_.setDTR2(F) &&
|
||||||
|
base_.dtSelect(8) && base_.sendExtCmd(addr, DALI_CMD_DT8_SET_TEMPORARY_WAF_DIM_LEVELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setTemporaryRGBWAFControl(int a, int control) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.setDTR(control & 0xFF) && base_.dtSelect(8) &&
|
||||||
|
base_.sendExtCmd(addr, DALI_CMD_DT8_SET_TEMPORARY_RGBWAF_CONTROL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::copyReportToTemporary(int a) { return base_.dtSelect(8) && base_.copyReportColourToTemp(a); }
|
||||||
|
|
||||||
|
bool DaliDT8::stepXUp(int a) {
|
||||||
|
return base_.dtSelect(8) && base_.sendExtCmd(a * 2 + 1, DALI_CMD_DT8_STEP_UP_X_COORDINATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::stepXDown(int a) {
|
||||||
|
return base_.dtSelect(8) && base_.sendExtCmd(a * 2 + 1, DALI_CMD_DT8_STEP_DOWN_X_COORDINATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::stepYUp(int a) {
|
||||||
|
return base_.dtSelect(8) && base_.sendExtCmd(a * 2 + 1, DALI_CMD_DT8_STEP_UP_Y_COORDINATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::stepYDown(int a) {
|
||||||
|
return base_.dtSelect(8) && base_.sendExtCmd(a * 2 + 1, DALI_CMD_DT8_STEP_DOWN_Y_COORDINATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::stepTcCooler(int a) {
|
||||||
|
return base_.dtSelect(8) &&
|
||||||
|
base_.sendExtCmd(a * 2 + 1, DALI_CMD_DT8_STEP_UP_COLOR_TEMPERATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::stepTcWarmer(int a) {
|
||||||
|
return base_.dtSelect(8) &&
|
||||||
|
base_.sendExtCmd(a * 2 + 1, DALI_CMD_DT8_STEP_DOWN_COLOR_TEMPERATURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setColourRGBRaw(int addr, int r, int g, int b) {
|
||||||
|
const int a = addr / 2;
|
||||||
|
return base_.setDTR(r) && base_.setDTR1(g) && base_.setDTR2(b) && base_.dtSelect(8) && base_.setDTRAsColourRGB(a) && base_.dtSelect(8) && base_.activate(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setColour(int a, double x, double y) {
|
||||||
|
double xClamped = std::clamp(x, 0.0, 1.0);
|
||||||
|
double yClamped = std::clamp(y, 0.0, 1.0);
|
||||||
|
const int x1 = static_cast<int>(std::round(xClamped * 65535.0));
|
||||||
|
const int y1 = static_cast<int>(std::round(yClamped * 65535.0));
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return setColourRaw(addr, x1, y1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getColourRaw(int a, int type) {
|
||||||
|
const uint8_t code = static_cast<uint8_t>(type & 0xFF);
|
||||||
|
const auto features = getColorTypeFeature(a);
|
||||||
|
if (!features.has_value()) return std::nullopt;
|
||||||
|
if ((code == 2 || code == 128 || code == 129 || code == 130 || code == 131) && !features->ctCapable()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is8bit = false;
|
||||||
|
if (code == 82 || (code >= 9 && code <= 15) || (code >= 201 && code <= 207) || code == 208 ||
|
||||||
|
(code >= 233 && code <= 239) || code == 240) {
|
||||||
|
is8bit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base_.setDTR(code)) return std::nullopt;
|
||||||
|
if (!base_.dtSelect(8)) return std::nullopt;
|
||||||
|
if (!base_.queryColourValue(a)) return std::nullopt;
|
||||||
|
const auto dtr = base_.getDTR(a);
|
||||||
|
const auto dtr1 = base_.getDTR1(a);
|
||||||
|
if (!dtr.has_value() || !dtr1.has_value()) return std::nullopt;
|
||||||
|
|
||||||
|
if (is8bit) {
|
||||||
|
if (dtr.value() == 0xFF) return std::nullopt;
|
||||||
|
return dtr.value() & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dtr.value() == 0xFF && dtr1.value() == 0xFF) return std::nullopt;
|
||||||
|
return ((dtr1.value() & 0xFF) << 8) | (dtr.value() & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> DaliDT8::getColour(int a) {
|
||||||
|
const auto x = getColourRaw(a, 0);
|
||||||
|
const auto y = getColourRaw(a, 1);
|
||||||
|
if (!x.has_value() || !y.has_value()) return {};
|
||||||
|
return {x.value() / 65535.0, y.value() / 65535.0};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setColourRGB(int addr, int r, int g, int b) {
|
||||||
|
int R = std::clamp(r, 0, 255);
|
||||||
|
int G = std::clamp(g, 0, 255);
|
||||||
|
int B = std::clamp(b, 0, 255);
|
||||||
|
const auto xy = DaliColor::rgb2xy(static_cast<double>(R) / 255.0, static_cast<double>(G) / 255.0,
|
||||||
|
static_cast<double>(B) / 255.0);
|
||||||
|
return setColour(addr, xy[0], xy[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> DaliDT8::getColourRGB(int a) {
|
||||||
|
const auto xy = getColour(a);
|
||||||
|
if (xy.empty()) return {};
|
||||||
|
const auto rgb = DaliColor::xy2rgb(xy[0], xy[1]);
|
||||||
|
return {rgb[0], rgb[1], rgb[2]};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::activateTemporaryColour(int a) { return base_.dtSelect(8) && base_.activate(a); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getPrimaryDimLevel(int a, int n) {
|
||||||
|
if (n < 0 || n > 5) return std::nullopt;
|
||||||
|
return getColourRaw(a, 3 + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getRedDimLevel(int a) { return getColourRaw(a, 9); }
|
||||||
|
std::optional<int> DaliDT8::getGreenDimLevel(int a) { return getColourRaw(a, 10); }
|
||||||
|
std::optional<int> DaliDT8::getBlueDimLevel(int a) { return getColourRaw(a, 11); }
|
||||||
|
std::optional<int> DaliDT8::getWhiteDimLevel(int a) { return getColourRaw(a, 12); }
|
||||||
|
std::optional<int> DaliDT8::getAmberDimLevel(int a) { return getColourRaw(a, 13); }
|
||||||
|
std::optional<int> DaliDT8::getFreecolourDimLevel(int a) { return getColourRaw(a, 14); }
|
||||||
|
std::optional<int> DaliDT8::getRGBWAFControl(int a) { return getColourRaw(a, 15); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getTemporaryXRaw(int a) { return getColourRaw(a, 192); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryYRaw(int a) { return getColourRaw(a, 193); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryColourTemperatureRaw(int a) { return getColourRaw(a, 194); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getTemporaryPrimaryDimLevel(int a, int n) {
|
||||||
|
if (n < 0 || n > 5) return std::nullopt;
|
||||||
|
return getColourRaw(a, 195 + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getTemporaryRedDimLevel(int a) { return getColourRaw(a, 201); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryGreenDimLevel(int a) { return getColourRaw(a, 202); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryBlueDimLevel(int a) { return getColourRaw(a, 203); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryWhiteDimLevel(int a) { return getColourRaw(a, 204); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryAmberDimLevel(int a) { return getColourRaw(a, 205); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryFreecolourDimLevel(int a) { return getColourRaw(a, 206); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryRGBWAFControl(int a) { return getColourRaw(a, 207); }
|
||||||
|
std::optional<int> DaliDT8::getTemporaryColourType(int a) { return getColourRaw(a, 208); }
|
||||||
|
|
||||||
|
std::vector<double> DaliDT8::getTemporaryColour(int a) {
|
||||||
|
const auto x = getTemporaryXRaw(a);
|
||||||
|
const auto y = getTemporaryYRaw(a);
|
||||||
|
if (!x.has_value() || !y.has_value()) return {};
|
||||||
|
return {x.value() / 65535.0, y.value() / 65535.0};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getTemporaryColorTemperature(int a) {
|
||||||
|
const auto mirek = getTemporaryColourTemperatureRaw(a);
|
||||||
|
if (!mirek.has_value() || mirek.value() == 0) return std::nullopt;
|
||||||
|
return static_cast<int>(std::floor(1000000.0 / static_cast<double>(mirek.value())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getReportXRaw(int a) { return getColourRaw(a, 224); }
|
||||||
|
std::optional<int> DaliDT8::getReportYRaw(int a) { return getColourRaw(a, 225); }
|
||||||
|
std::optional<int> DaliDT8::getReportColourTemperatureRaw(int a) { return getColourRaw(a, 226); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getReportPrimaryDimLevel(int a, int n) {
|
||||||
|
if (n < 0 || n > 5) return std::nullopt;
|
||||||
|
return getColourRaw(a, 227 + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getReportRedDimLevel(int a) { return getColourRaw(a, 233); }
|
||||||
|
std::optional<int> DaliDT8::getReportGreenDimLevel(int a) { return getColourRaw(a, 234); }
|
||||||
|
std::optional<int> DaliDT8::getReportBlueDimLevel(int a) { return getColourRaw(a, 235); }
|
||||||
|
std::optional<int> DaliDT8::getReportWhiteDimLevel(int a) { return getColourRaw(a, 236); }
|
||||||
|
std::optional<int> DaliDT8::getReportAmberDimLevel(int a) { return getColourRaw(a, 237); }
|
||||||
|
std::optional<int> DaliDT8::getReportFreecolourDimLevel(int a) { return getColourRaw(a, 238); }
|
||||||
|
std::optional<int> DaliDT8::getReportRGBWAFControl(int a) { return getColourRaw(a, 239); }
|
||||||
|
std::optional<int> DaliDT8::getReportColourType(int a) { return getColourRaw(a, 240); }
|
||||||
|
|
||||||
|
std::vector<double> DaliDT8::getReportColour(int a) {
|
||||||
|
const auto x = getReportXRaw(a);
|
||||||
|
const auto y = getReportYRaw(a);
|
||||||
|
if (!x.has_value() || !y.has_value()) return {};
|
||||||
|
return {x.value() / 65535.0, y.value() / 65535.0};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getReportColorTemperature(int a) {
|
||||||
|
const auto mirek = getReportColourTemperatureRaw(a);
|
||||||
|
if (!mirek.has_value() || mirek.value() == 0) return std::nullopt;
|
||||||
|
return static_cast<int>(std::floor(1000000.0 / static_cast<double>(mirek.value())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getNumberOfPrimaries(int a) { return getColourRaw(a, 82); }
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getPrimaryXRaw(int a, int n) {
|
||||||
|
if (n < 0 || n > 5) return std::nullopt;
|
||||||
|
return getColourRaw(a, 64 + 3 * n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getPrimaryYRaw(int a, int n) {
|
||||||
|
if (n < 0 || n > 5) return std::nullopt;
|
||||||
|
return getColourRaw(a, 65 + 3 * n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getPrimaryTy(int a, int n) {
|
||||||
|
if (n < 0 || n > 5) return std::nullopt;
|
||||||
|
return getColourRaw(a, 66 + 3 * n);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<double> DaliDT8::getSceneColor(int a, int sense) {
|
||||||
|
const auto bright = base_.getScene(a, sense);
|
||||||
|
if (!bright.has_value() || bright.value() == 255) return {};
|
||||||
|
base_.copyReportColourToTemp(a);
|
||||||
|
return getColour(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::storePrimaryTy(int a, int n, int ty) {
|
||||||
|
int idx = std::clamp(n, 0, 5);
|
||||||
|
int t = std::clamp(ty, 0, 65535);
|
||||||
|
const int lsb = t & 0xFF;
|
||||||
|
const int msb = (t >> 8) & 0xFF;
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.setDTR(lsb) && base_.setDTR1(msb) && base_.setDTR2(idx) &&
|
||||||
|
base_.dtSelect(8) && base_.sendExtCmd(addr, DALI_CMD_DT8_STORE_PRIMARY_N_TY);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::storePrimaryXY(int a, int n, double x, double y) {
|
||||||
|
int idx = std::clamp(n, 0, 5);
|
||||||
|
if (!setTemporaryColourXY(a, x, y)) return false;
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.setDTR2(idx) && base_.dtSelect(8) &&
|
||||||
|
base_.sendExtCmd(addr, DALI_CMD_DT8_STORE_PRIMARY_N_XY);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::storeColourTempLimitRaw(int a, int limitType, int mirek) {
|
||||||
|
int m = mirek;
|
||||||
|
if (m < 1) m = 1;
|
||||||
|
if (m > 65534) m = 65534;
|
||||||
|
const int lsb = m & 0xFF;
|
||||||
|
const int msb = (m >> 8) & 0xFF;
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.setDTR(lsb) && base_.setDTR1(msb) && base_.setDTR2(limitType & 0xFF) &&
|
||||||
|
base_.dtSelect(8) && base_.sendExtCmd(addr, DALI_CMD_DT8_STORE_COLOR_TEMPERATURE_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::storeColourTempLimit(int a, int limitType, int kelvin) {
|
||||||
|
const int mirek = static_cast<int>(std::floor(1000000.0 / static_cast<double>(kelvin <= 0 ? 1 : kelvin)));
|
||||||
|
return storeColourTempLimitRaw(a, limitType, mirek);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::setGearAutoActivate(int a, bool enable) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
const int opts = enable ? 0x01 : 0x00;
|
||||||
|
return base_.setDTR(opts) && base_.dtSelect(8) &&
|
||||||
|
base_.sendExtCmd(addr, DALI_CMD_DT8_SET_GEAR_FEATURES);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::assignColourToLinkedChannels(int a, int colourId) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
const int id = std::clamp(colourId, 0, 6);
|
||||||
|
return base_.setDTR(id) && base_.dtSelect(8) &&
|
||||||
|
base_.sendExtCmd(addr, DALI_CMD_DT8_ASSIGN_COLOR_TO_LINKED_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaliDT8::startAutoCalibration(int a) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
return base_.dtSelect(8) && base_.sendExtCmd(addr, DALI_CMD_DT8_START_AUTO_CALIBRATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getGearFeaturesStatus(int a) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
base_.dtSelect(8);
|
||||||
|
return base_.queryCmd(addr, DALI_CMD_DT8_QUERY_GEAR_FEATURES_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getRGBWAFControlDirect(int a) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
base_.dtSelect(8);
|
||||||
|
return base_.queryCmd(addr, DALI_CMD_DT8_QUERY_RGBWAF_CONTROL);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getAssignedColourForChannel(int a, int channelId) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
const int ch = channelId & 0xFF;
|
||||||
|
if (!base_.setDTR(ch)) return std::nullopt;
|
||||||
|
if (!base_.dtSelect(8)) return std::nullopt;
|
||||||
|
return base_.queryCmd(addr, DALI_CMD_DT8_QUERY_ASSIGNED_COLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<int> DaliDT8::getExtendedVersion(int a) {
|
||||||
|
const int addr = a * 2 + 1;
|
||||||
|
base_.dtSelect(8);
|
||||||
|
return base_.queryCmd(addr, DALI_CMD_DT8_QUERY_EXTENDED_VERSION);
|
||||||
|
}
|
||||||
125
src/sequence.cpp
Normal file
125
src/sequence.cpp
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#include "sequence.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
std::string toString(DaliCommandType type) {
|
||||||
|
static const std::map<DaliCommandType, std::string> map = {
|
||||||
|
{DaliCommandType::setBright, "setBright"},
|
||||||
|
{DaliCommandType::on, "on"},
|
||||||
|
{DaliCommandType::off, "off"},
|
||||||
|
{DaliCommandType::toScene, "toScene"},
|
||||||
|
{DaliCommandType::setScene, "setScene"},
|
||||||
|
{DaliCommandType::removeScene, "removeScene"},
|
||||||
|
{DaliCommandType::addToGroup, "addToGroup"},
|
||||||
|
{DaliCommandType::removeFromGroup, "removeFromGroup"},
|
||||||
|
{DaliCommandType::setFadeTime, "setFadeTime"},
|
||||||
|
{DaliCommandType::setFadeRate, "setFadeRate"},
|
||||||
|
{DaliCommandType::wait, "wait"},
|
||||||
|
{DaliCommandType::modifyShortAddress, "modifyShortAddress"},
|
||||||
|
{DaliCommandType::deleteShortAddress, "deleteShortAddress"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto it = map.find(type);
|
||||||
|
if (it == map.end()) return "setBright";
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliCommandType commandTypeFromString(const std::string& name, DaliCommandType fallback) {
|
||||||
|
static const std::map<std::string, DaliCommandType> map = {
|
||||||
|
{"setBright", DaliCommandType::setBright},
|
||||||
|
{"on", DaliCommandType::on},
|
||||||
|
{"off", DaliCommandType::off},
|
||||||
|
{"toScene", DaliCommandType::toScene},
|
||||||
|
{"setScene", DaliCommandType::setScene},
|
||||||
|
{"removeScene", DaliCommandType::removeScene},
|
||||||
|
{"addToGroup", DaliCommandType::addToGroup},
|
||||||
|
{"removeFromGroup", DaliCommandType::removeFromGroup},
|
||||||
|
{"setFadeTime", DaliCommandType::setFadeTime},
|
||||||
|
{"setFadeRate", DaliCommandType::setFadeRate},
|
||||||
|
{"wait", DaliCommandType::wait},
|
||||||
|
{"modifyShortAddress", DaliCommandType::modifyShortAddress},
|
||||||
|
{"deleteShortAddress", DaliCommandType::deleteShortAddress},
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto it = map.find(name);
|
||||||
|
if (it == map.end()) return fallback;
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaliCommandParams::getInt(const std::string& key, int def) const {
|
||||||
|
const auto* value = getObjectValue(data, key);
|
||||||
|
if (!value) return def;
|
||||||
|
return value->asInt().value_or(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliCommandParams DaliCommandParams::copy() const { return DaliCommandParams(data); }
|
||||||
|
|
||||||
|
DaliValue::Object DaliCommandParams::toJson() const { return data; }
|
||||||
|
|
||||||
|
DaliCommandParams DaliCommandParams::fromJson(const DaliValue::Object& json) {
|
||||||
|
return DaliCommandParams(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
SequenceStep SequenceStep::copy() const {
|
||||||
|
SequenceStep s;
|
||||||
|
s.id = id;
|
||||||
|
s.remark = remark;
|
||||||
|
s.type = type;
|
||||||
|
s.params = params.copy();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object SequenceStep::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
out["id"] = id;
|
||||||
|
if (remark.has_value()) out["remark"] = remark.value();
|
||||||
|
out["type"] = toString(type);
|
||||||
|
out["params"] = params.toJson();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
SequenceStep SequenceStep::fromJson(const DaliValue::Object& json) {
|
||||||
|
SequenceStep s;
|
||||||
|
s.id = getObjectString(json, "id").value_or("");
|
||||||
|
s.remark = getObjectString(json, "remark");
|
||||||
|
s.type = commandTypeFromString(getObjectString(json, "type").value_or("setBright"));
|
||||||
|
if (const auto* paramsVal = getObjectValue(json, "params")) {
|
||||||
|
if (const auto* obj = paramsVal->asObject()) {
|
||||||
|
s.params = DaliCommandParams::fromJson(*obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue::Object CommandSequence::toJson() const {
|
||||||
|
DaliValue::Object out;
|
||||||
|
out["id"] = id;
|
||||||
|
out["name"] = name;
|
||||||
|
|
||||||
|
DaliValue::Array arr;
|
||||||
|
arr.reserve(steps.size());
|
||||||
|
for (const auto& s : steps) {
|
||||||
|
arr.emplace_back(s.toJson());
|
||||||
|
}
|
||||||
|
out["steps"] = std::move(arr);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandSequence CommandSequence::fromJson(const DaliValue::Object& json) {
|
||||||
|
CommandSequence seq;
|
||||||
|
seq.id = getObjectString(json, "id").value_or("");
|
||||||
|
seq.name = getObjectString(json, "name").value_or("");
|
||||||
|
|
||||||
|
if (const auto* stepsVal = getObjectValue(json, "steps")) {
|
||||||
|
if (const auto* arr = stepsVal->asArray()) {
|
||||||
|
seq.steps.reserve(arr->size());
|
||||||
|
for (const auto& v : *arr) {
|
||||||
|
if (const auto* stepObj = v.asObject()) {
|
||||||
|
seq.steps.push_back(SequenceStep::fromJson(*stepObj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return seq;
|
||||||
|
}
|
||||||
62
src/sequence_store.cpp
Normal file
62
src/sequence_store.cpp
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#include "sequence_store.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
bool SequenceRepository::load() {
|
||||||
|
if (!loadCallback_) {
|
||||||
|
sequences_.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DaliValue stored;
|
||||||
|
if (!loadCallback_(kSequencesKey, &stored)) {
|
||||||
|
sequences_.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* arr = stored.asArray();
|
||||||
|
if (!arr) {
|
||||||
|
sequences_.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sequences_.clear();
|
||||||
|
sequences_.reserve(arr->size());
|
||||||
|
for (const auto& v : *arr) {
|
||||||
|
const auto* obj = v.asObject();
|
||||||
|
if (!obj) continue;
|
||||||
|
sequences_.push_back(CommandSequence::fromJson(*obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SequenceRepository::save() const {
|
||||||
|
if (!saveCallback_) return false;
|
||||||
|
|
||||||
|
DaliValue::Array arr;
|
||||||
|
arr.reserve(sequences_.size());
|
||||||
|
for (const auto& s : sequences_) {
|
||||||
|
arr.emplace_back(s.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveCallback_(kSequencesKey, DaliValue(std::move(arr)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SequenceRepository::add(const CommandSequence& s) { sequences_.push_back(s); }
|
||||||
|
|
||||||
|
void SequenceRepository::remove(const std::string& id) {
|
||||||
|
sequences_.erase(
|
||||||
|
std::remove_if(sequences_.begin(), sequences_.end(),
|
||||||
|
[&](const CommandSequence& seq) { return seq.id == id; }),
|
||||||
|
sequences_.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SequenceRepository::replace(const CommandSequence& s) {
|
||||||
|
for (auto& seq : sequences_) {
|
||||||
|
if (seq.id == s.id) {
|
||||||
|
seq = s;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user