initial commit

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