diff --git a/include/base.hpp b/include/base.hpp index 651c1b3..239b05c 100644 --- a/include/base.hpp +++ b/include/base.hpp @@ -131,9 +131,9 @@ class DaliBase { std::optional getGroup(int a); bool setGroup(int a, int value); - std::optional getScene(int a, int b); - bool setScene(int a, int b); - std::map getScenes(int a); + std::optional getScene(int a, int b, int gateway = -1); + bool setScene(int a, int b, int gateway = -1); + std::map getScenes(int a, int gateway = -1); std::optional getStatus(int a); std::optional getControlGearPresent(int a); diff --git a/include/decode.hpp b/include/decode.hpp index 760af0b..209c3e9 100644 --- a/include/decode.hpp +++ b/include/decode.hpp @@ -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; }; diff --git a/include/dt8.hpp b/include/dt8.hpp index 5a2616d..ea2f400 100644 --- a/include/dt8.hpp +++ b/include/dt8.hpp @@ -180,15 +180,16 @@ class DaliDT8 { std::optional getPrimaryYRaw(int a, int n); std::optional getPrimaryTy(int a, int n); - std::optional getSceneColorReport(int a, int sense); - std::vector getSceneColor(int a, int sense); + std::optional getSceneColorReport(int a, int sense, int gateway = -1); + std::vector 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 getPowerOnLevelColorReport(int a); diff --git a/src/base.cpp b/src/base.cpp index 6cfcbf0..4b4363d 100644 --- a/src/base.cpp +++ b/src/base.cpp @@ -370,16 +370,18 @@ bool DaliBase::setGroup(int a, int value) { return true; } -std::optional DaliBase::getScene(int a, int b) { +std::optional DaliBase::getScene(int a, int b, int /*gateway*/) { return query(a, static_cast(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 DaliBase::getScenes(int a) { +std::map DaliBase::getScenes(int a, int gateway) { std::map 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; diff --git a/src/decode.cpp b/src/decode.cpp index 2b47831..580ff13 100644 --- a/src/decode.cpp +++ b/src/decode.cpp @@ -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(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 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(labels.size() - 1)); + return labels[idx]; +} + +std::string DaliDecode::fadeRateLabel(int code) { + static const std::vector 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(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 parts; diff --git a/src/dt8.cpp b/src/dt8.cpp index 76393e3..53e613a 100644 --- a/src/dt8.cpp +++ b/src/dt8.cpp @@ -328,7 +328,7 @@ std::vector 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 DaliDT8::getPrimaryTy(int a, int n) { return getColourRaw(a, 66 + 3 * n); } -std::optional DaliDT8::getSceneColorReport(int a, int sense) { - const auto brightness = base_.getScene(a, sense); +std::optional 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 DaliDT8::getSceneColorReport(int a, int sense) { return report; } -std::vector DaliDT8::getSceneColor(int a, int sense) { - const auto report = getSceneColorReport(a, sense); +std::vector 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; }