Enhance DALI interface: add gateway parameter to scene and color methods for improved command handling

This commit is contained in:
Tony
2026-05-02 21:32:39 +08:00
parent 9f9628db39
commit 16907857c0
6 changed files with 199 additions and 37 deletions
+3 -3
View File
@@ -131,9 +131,9 @@ class DaliBase {
std::optional<int> getGroup(int a);
bool setGroup(int a, int value);
std::optional<int> getScene(int a, int b);
bool setScene(int a, int b);
std::map<int, int> getScenes(int a);
std::optional<int> getScene(int a, int b, int gateway = -1);
bool setScene(int a, int b, int gateway = -1);
std::map<int, int> getScenes(int a, int gateway = -1);
std::optional<int> getStatus(int a);
std::optional<bool> getControlGearPresent(int a);
+4
View File
@@ -49,8 +49,12 @@ class DaliDecode {
static std::string hex(int v);
static std::string bin(int v);
static std::string yn(bool b);
static std::string ynUpper(bool b);
static std::string whoLabel(int addr, bool even);
static std::string deviceTypeName(int v);
static std::string lightSourceName(int v);
static std::string booleanQueryLabel(int c);
static std::string fadeTimeLabel(int code);
static std::string fadeRateLabel(int code);
DecodedRecord withRaw(const DecodedRecord& r) const;
};
+4 -3
View File
@@ -180,15 +180,16 @@ class DaliDT8 {
std::optional<int> getPrimaryYRaw(int a, int n);
std::optional<int> getPrimaryTy(int a, int n);
std::optional<SceneColorReport> getSceneColorReport(int a, int sense);
std::vector<double> getSceneColor(int a, int sense);
std::optional<SceneColorReport> getSceneColorReport(int a, int sense, int gateway = -1);
std::vector<double> getSceneColor(int a, int sense, int gateway = -1);
// Store / config
bool storePrimaryTy(int a, int n, int ty);
bool storePrimaryXY(int a, int n, double x, double y);
bool storeSceneSnapshot(int address, int scene, int brightness,
Dt8SceneStoreColorMode colorMode = Dt8SceneStoreColorMode::disabled,
int colorTemperature = 0, int red = 0, int green = 0, int blue = 0);
int colorTemperature = 0, int red = 0, int green = 0, int blue = 0,
int gateway = -1);
bool storePowerOnLevelSnapshot(int address, int level);
bool storeSystemFailureLevelSnapshot(int address, int level);
std::optional<Dt8LevelColorReport> getPowerOnLevelColorReport(int a);
+6 -4
View File
@@ -370,16 +370,18 @@ bool DaliBase::setGroup(int a, int value) {
return true;
}
std::optional<int> DaliBase::getScene(int a, int b) {
std::optional<int> DaliBase::getScene(int a, int b, int /*gateway*/) {
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); }
bool DaliBase::setScene(int a, int b, int /*gateway*/) {
return setDTR(b) && storeDTRAsSceneBright(a, b);
}
std::map<int, int> DaliBase::getScenes(int a) {
std::map<int, int> DaliBase::getScenes(int a, int gateway) {
std::map<int, int> ret;
for (int i = 0; i < 16; i++) {
const auto r = getScene(a, i);
const auto r = getScene(a, i, gateway);
if (r.has_value()) ret[i] = r.value();
}
return ret;
+177 -22
View File
@@ -23,7 +23,7 @@ DaliDecode::DaliDecode() {
{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_BALLAST, "QUERY_CONTROL_GEAR_PRESENT"},
{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"},
@@ -45,7 +45,7 @@ DaliDecode::DaliDecode() {
{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_GROUP_8_15, "QUERY_GROUPS_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)"},
@@ -257,44 +257,107 @@ DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/) {
}
const int c = lastQueryCmd_;
lastQueryCmd_ = 0;
lastQueryAtMs_ = 0;
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);
oss << "control gear failure = " << ynUpper((value & 0x01) != 0)
<< ", lamp failure = " << ynUpper((value & 0x02) != 0)
<< ", lamp on = " << ynUpper((value & 0x04) != 0)
<< ", limit error = " << ynUpper((value & 0x08) != 0)
<< ", fade running = " << ynUpper((value & 0x10) != 0)
<< ", reset state = " << ynUpper((value & 0x20) != 0)
<< ", missing short address = " << ynUpper((value & 0x40) != 0)
<< ", power cycle seen = " << ynUpper((value & 0x80) != 0);
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_CONTENT_DTR) {
return withRaw({"DTR0 = 0x" + hex(value) + " / " + std::to_string(value) + " / " + bin(value) + "b",
"response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_CONTENT_DTR1) {
return withRaw({"DTR1 = 0x" + hex(value) + " / " + std::to_string(value) + " / " + bin(value) + "b",
"response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_CONTENT_DTR2) {
return withRaw({"DTR2 = 0x" + hex(value) + " / " + std::to_string(value) + " / " + bin(value) + "b",
"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_VERSION_NUMBER) {
return withRaw({"version = 0x" + hex(value) + " / " + std::to_string(value), "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),
return withRaw({"fade time = " + fadeTimeLabel(ft) + ", fade rate = " + fadeRateLabel(fr),
"response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_EXTENDED_FADE_TIME) {
if (value == 0) {
return withRaw({"extended fade time = 0 ms", "response", -1, c, 0xFF});
}
return withRaw({"extended fade time index = " + std::to_string(value), "response", -1, c,
0xFF});
}
if (c == DALI_CMD_QUERY_BALLAST || c == DALI_CMD_QUERY_LAMP_FAILURE ||
c == DALI_CMD_QUERY_LAMP_POWER_ON || c == DALI_CMD_QUERY_LIMIT_ERROR ||
c == DALI_CMD_QUERY_RESET_STATE || c == DALI_CMD_QUERY_MISSING_SHORT_ADDRESS ||
c == DALI_CMD_QUERY_POWER_FAILURE || c == DALI_CMD_QUERY_MANUFACTURER_SPECIFIC_MODE ||
c == DALI_CMD_QUERY_CONTROL_GEAR_FAILURE) {
return withRaw({booleanQueryLabel(c) + " = " + ynUpper(value == 0xFF), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_DEVICE_TYPE) {
const auto name = deviceTypeName(value);
std::string text = "device type = ";
text += name.empty() ? ("0x" + hex(value) + " / " + std::to_string(value)) : name;
return withRaw({text, "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_LIGHT_SOURCE_TYPE) {
const auto name = lightSourceName(value);
std::string text = "light source = ";
text += name.empty() ? ("0x" + hex(value) + " / " + std::to_string(value)) : name;
return withRaw({text, "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_OPERATING_MODE) {
if (value == 0) {
return withRaw({"operating mode = standard", "response", -1, c, 0xFF});
}
return withRaw({"operating mode = " + std::to_string(value), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_ACTUAL_LEVEL) {
return withRaw({"actual level = " + std::to_string(value), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_PHYSICAL_MINIMUM_LEVEL) {
return withRaw({"physical minimum = " + std::to_string(value), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_MAX_LEVEL) {
return withRaw({"maximum level = " + std::to_string(value), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_MIN_LEVEL) {
return withRaw({"minimum level = " + std::to_string(value), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_POWER_ON_LEVEL) {
return withRaw({"power on level = " + std::to_string(value), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL) {
return withRaw({"system failure level = " + std::to_string(value), "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)
@@ -303,6 +366,47 @@ DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/) {
return withRaw({text, "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_GROUPS_0_7 || c == DALI_CMD_QUERY_GROUP_8_15) {
const int base = c == DALI_CMD_QUERY_GROUPS_0_7 ? 0 : 8;
std::ostringstream oss;
for (int i = 0; i < 8; i++) {
if (i > 0) oss << ", ";
oss << "group " << (base + i) << " = " << ynUpper(((value >> i) & 0x1) == 0x1);
}
return withRaw({oss.str(), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_QUERY_RANDOM_ADDRESS_H || c == DALI_CMD_QUERY_RANDOM_ADDRESS_M ||
c == DALI_CMD_QUERY_RANDOM_ADDRESS_L) {
const char which = c == DALI_CMD_QUERY_RANDOM_ADDRESS_H
? 'H'
: (c == DALI_CMD_QUERY_RANDOM_ADDRESS_M ? 'M' : 'L');
return withRaw({"random address (" + std::string(1, which) + ") = 0x" + hex(value) + " / " +
std::to_string(value) + " / " + bin(value) + "b",
"response", -1, c, 0xFF});
}
if (c == DALI_CMD_READ_MEMORY_LOCATION) {
return withRaw({"memory value = 0x" + hex(value) + " / " + std::to_string(value) + " / " +
bin(value) + "b",
"response", -1, c, 0xFF});
}
if (c == DALI_CMD_SPECIAL_COMPARE || c == DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS) {
const auto it = sCMD_.find(c);
const auto name = it == sCMD_.end() ? ("SPECIAL_CMD 0x" + hex(c)) : it->second;
return withRaw({name + " => " + ynUpper(value == 0xFF), "response", -1, c, 0xFF});
}
if (c == DALI_CMD_SPECIAL_QUERY_SHORT_ADDRESS) {
if (value == 0xFF) {
return withRaw({"QUERY_SHORT_ADDRESS => NONE", "response", -1, c, 0xFF});
}
return withRaw({"QUERY_SHORT_ADDRESS => short " + std::to_string(value / 2) + " (0x" +
hex(value) + " / " + std::to_string(value) + ")",
"response", -1, c, 0xFF});
}
if (pendingColourType_ >= 0 && (now - lastColourQueryAtMs_) <= 1000) {
const int combined = dtr1 * 256 + dtr0;
std::string pretty;
@@ -319,15 +423,18 @@ DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/) {
return withRaw({"colour value " + pretty, "response", -1, c, 0xFF});
}
return withRaw({"0x" + hex(value) + " / " + std::to_string(value) + " / " + bin(value), "response",
return withRaw({"0x" + hex(value) + " / " + std::to_string(value) + " / " + bin(value) + "b", "response",
-1, c, 0xFF});
}
DecodedRecord DaliDecode::decode(int addr, int c, int proto) {
if (proto == 0x12) {
return decodeQuery(addr, c);
}
if (addr >= DALI_CMD_SPECIAL_RANGE_MIN && addr <= DALI_CMD_SPECIAL_RANGE_MAX) {
return decodeSpCmd(addr, c);
}
if (proto == 0x12 || isQueryCmd(c) == 1) {
if (isQueryCmd(c) == 1) {
return decodeQuery(addr, c);
}
if ((addr & 1) == 0) {
@@ -369,6 +476,8 @@ std::string DaliDecode::bin(int v) {
std::string DaliDecode::yn(bool b) { return b ? "yes" : "no"; }
std::string DaliDecode::ynUpper(bool b) { return b ? "YES" : "NO"; }
std::string DaliDecode::whoLabel(int addr, bool even) {
if (even) {
if (addr == 0xFE) return "broadcast";
@@ -400,6 +509,52 @@ std::string DaliDecode::lightSourceName(int v) {
return it == map.end() ? "" : it->second;
}
std::string DaliDecode::booleanQueryLabel(int c) {
switch (c) {
case DALI_CMD_QUERY_BALLAST:
return "control gear present";
case DALI_CMD_QUERY_LAMP_FAILURE:
return "lamp failure";
case DALI_CMD_QUERY_LAMP_POWER_ON:
return "lamp power on";
case DALI_CMD_QUERY_LIMIT_ERROR:
return "limit error";
case DALI_CMD_QUERY_RESET_STATE:
return "reset state";
case DALI_CMD_QUERY_MISSING_SHORT_ADDRESS:
return "missing short address";
case DALI_CMD_QUERY_POWER_FAILURE:
return "power failure";
case DALI_CMD_QUERY_MANUFACTURER_SPECIFIC_MODE:
return "manufacturer specific mode";
case DALI_CMD_QUERY_CONTROL_GEAR_FAILURE:
return "control gear failure";
default:
return "QUERY 0x" + hex(c);
}
}
std::string DaliDecode::fadeTimeLabel(int code) {
static const std::vector<std::string> labels = {"no fade", "0.7 s", "1.0 s", "1.4 s",
"2.0 s", "2.8 s", "4.0 s", "5.7 s",
"8.0 s", "11.3 s", "16.0 s", "22.6 s",
"32.0 s", "45.3 s", "64.0 s", "90.5 s"};
const auto idx = std::clamp(code, 0, static_cast<int>(labels.size() - 1));
return labels[idx];
}
std::string DaliDecode::fadeRateLabel(int code) {
static const std::vector<double> rates = {0.0, 357.8, 253.0, 178.9, 126.5, 89.4, 63.2, 44.7,
31.6, 22.4, 15.8, 11.2, 7.9, 5.6, 4.0, 2.8};
const auto idx = std::clamp(code, 0, static_cast<int>(rates.size() - 1));
if (idx == 0) {
return "unknown";
}
std::ostringstream oss;
oss << std::fixed << std::setprecision(1) << rates[idx] << " steps/s";
return oss.str();
}
DecodedRecord DaliDecode::withRaw(const DecodedRecord& r) const {
if (!displayRaw) return r;
std::vector<std::string> parts;
+5 -5
View File
@@ -328,7 +328,7 @@ std::vector<int> DaliDT8::getColourRGB(int a) {
bool DaliDT8::storeSceneSnapshot(int address, int scene, int brightness,
Dt8SceneStoreColorMode colorMode, int colorTemperature, int red,
int green, int blue) {
int green, int blue, int /*gateway*/) {
const int sceneBrightness = std::clamp(brightness, 0, 255);
const int sceneIndex = std::clamp(scene, 0, 15);
@@ -465,8 +465,8 @@ std::optional<int> DaliDT8::getPrimaryTy(int a, int n) {
return getColourRaw(a, 66 + 3 * n);
}
std::optional<SceneColorReport> DaliDT8::getSceneColorReport(int a, int sense) {
const auto brightness = base_.getScene(a, sense);
std::optional<SceneColorReport> DaliDT8::getSceneColorReport(int a, int sense, int gateway) {
const auto brightness = base_.getScene(a, sense, gateway);
if (!brightness.has_value() || brightness.value() == 255) return std::nullopt;
const int colorTypeValue = getReportColourType(a).value_or(0);
@@ -489,8 +489,8 @@ std::optional<SceneColorReport> DaliDT8::getSceneColorReport(int a, int sense) {
return report;
}
std::vector<double> DaliDT8::getSceneColor(int a, int sense) {
const auto report = getSceneColorReport(a, sense);
std::vector<double> DaliDT8::getSceneColor(int a, int sense, int gateway) {
const auto report = getSceneColorReport(a, sense, gateway);
if (!report.has_value() || !report->colorType().xy() || !report->hasXy()) return {};
return report->xy;
}