initial commit
This commit is contained in:
+386
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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