#include "decode.hpp" #include "base.hpp" #include "dali_define.hpp" #include #include #include #include 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(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(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::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 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 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 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; }