Files
dali_cpp/src/decode.cpp
T
2026-03-26 12:04:08 +08:00

419 lines
16 KiB
C++

#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;
}