From 211a9a9c416ae43365a81012daf5f752c9a3c2f9 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 13 Jun 2026 14:25:27 +0800 Subject: [PATCH] Enhance DaliDecode: add support for device type decoding and context management - Introduced new structures and methods for handling device type decoding, including DaliDecodeContext and DeviceTypeDecodeTable. - Implemented decoding functions for device types DT1, DT4, DT5, DT6, and DT8, with specific command and query handling. - Updated existing decode methods to incorporate gateway and source parameters for better context tracking. - Added detailed formatting for device type commands and queries, enhancing the decoding process for various device types. - Refactored the decode logic to utilize the new context management for maintaining state across different device types. Signed-off-by: Tony --- CMakeLists.txt | 6 ++ include/bus_monitor.hpp | 23 ++-- include/decode.hpp | 35 +++--- include/decode_device_type.hpp | 55 ++++++++++ src/decode.cpp | 191 +++++++++++++++++++++++---------- src/decode_device_type.cpp | 107 ++++++++++++++++++ src/decode_dt1.cpp | 43 ++++++++ src/decode_dt4.cpp | 27 +++++ src/decode_dt5.cpp | 24 +++++ src/decode_dt6.cpp | 35 ++++++ src/decode_dt8.cpp | 55 ++++++++++ 11 files changed, 527 insertions(+), 74 deletions(-) create mode 100644 include/decode_device_type.hpp create mode 100644 src/decode_device_type.cpp create mode 100644 src/decode_dt1.cpp create mode 100644 src/decode_dt4.cpp create mode 100644 src/decode_dt5.cpp create mode 100644 src/decode_dt6.cpp create mode 100644 src/decode_dt8.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f028864..2a11449 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,12 @@ idf_component_register( "src/bridge_model.cpp" "src/bridge_provisioning.cpp" "src/decode.cpp" + "src/decode_device_type.cpp" + "src/decode_dt1.cpp" + "src/decode_dt4.cpp" + "src/decode_dt5.cpp" + "src/decode_dt6.cpp" + "src/decode_dt8.cpp" "src/device.cpp" "src/dt1.cpp" "src/dt4.cpp" diff --git a/include/bus_monitor.hpp b/include/bus_monitor.hpp index 8641aec..8debe44 100644 --- a/include/bus_monitor.hpp +++ b/include/bus_monitor.hpp @@ -4,15 +4,20 @@ #include #include +#include #include enum class BusDir { front, back }; +enum class BusFrameSource { local, external }; struct BusFrame { BusDir dir = BusDir::front; int proto = 0x10; int b1 = 0; int b2 = 0; + int b3 = -1; + int gateway = -1; + BusFrameSource source = BusFrameSource::local; }; class BusMonitor { @@ -27,22 +32,24 @@ class BusMonitor { void setRawSink(std::function sink) { rawSink_ = std::move(sink); } void setDecodedSink(std::function sink) { decodedSink_ = std::move(sink); } - void emitFront(int proto, int addr, int cmd) { - BusFrame f{BusDir::front, proto, addr & 0xFF, cmd & 0xFF}; + void emitFront(int proto, int addr, int cmd, int gateway = -1, + BusFrameSource source = BusFrameSource::local) { + BusFrame f{BusDir::front, proto, addr & 0xFF, cmd & 0xFF, -1, gateway, source}; rawFrames.push_back(f); if (rawSink_) rawSink_(f); - auto rec = decoder.decode(addr & 0xFF, cmd & 0xFF, proto); + auto rec = decoder.decode(addr & 0xFF, cmd & 0xFF, proto, gateway, sourceName(source)); records.push_back(rec); if (decodedSink_) decodedSink_(rec); } - void emitBack(int value, int prefix = 0xFF) { - BusFrame f{BusDir::back, 0xFF, prefix & 0xFF, value & 0xFF}; + void emitBack(int value, int prefix = 0xFF, int gateway = -1, + BusFrameSource source = BusFrameSource::local) { + BusFrame f{BusDir::back, 0xFF, prefix & 0xFF, value & 0xFF, -1, gateway, source}; rawFrames.push_back(f); if (rawSink_) rawSink_(f); - auto rec = decoder.decodeCmdResponse(value & 0xFF, prefix & 0xFF); + auto rec = decoder.decodeCmdResponse(value & 0xFF, prefix & 0xFF, gateway, sourceName(source)); records.push_back(rec); if (decodedSink_) decodedSink_(rec); } @@ -66,6 +73,10 @@ class BusMonitor { private: BusMonitor() = default; + static std::string sourceName(BusFrameSource source) { + return source == BusFrameSource::external ? "external" : "local"; + } + std::function rawSink_; std::function decodedSink_; }; diff --git a/include/decode.hpp b/include/decode.hpp index 209c3e9..57f65e3 100644 --- a/include/decode.hpp +++ b/include/decode.hpp @@ -5,12 +5,16 @@ #include #include +#include "decode_device_type.hpp" + struct DecodedRecord { std::string text; std::string type; int addr = -1; int cmd = -1; int proto = -1; + int gateway = -1; + std::string source; }; class DaliDecode { @@ -26,24 +30,26 @@ class DaliDecode { int isQueryCmd(int cmd) const; - DecodedRecord decodeBright(int addr, int level); - DecodedRecord decodeScene(int addr, int sceneCmd); - DecodedRecord decodeCmd(int addr, int c); - DecodedRecord decodeSpCmd(int addr, int c); - DecodedRecord querySpCMD(int cmdByte, int dataByte); - DecodedRecord decodeQuery(int addr, int c); - DecodedRecord decodeCmdResponse(int value, int gwPrefix = 0xFF); - DecodedRecord decode(int addr, int c, int proto = 0x10); + DecodedRecord decodeBright(int addr, int level, int gateway = -1, const std::string& source = ""); + DecodedRecord decodeScene(int addr, int sceneCmd, int gateway = -1, + const std::string& source = ""); + DecodedRecord decodeCmd(int addr, int c, int gateway = -1, const std::string& source = ""); + DecodedRecord decodeSpCmd(int addr, int c, int gateway = -1, const std::string& source = ""); + DecodedRecord querySpCMD(int cmdByte, int dataByte, int gateway = -1, + const std::string& source = ""); + DecodedRecord decodeQuery(int addr, int c, int gateway = -1, const std::string& source = "", + int deviceType = -1); + DecodedRecord decodeCmdResponse(int value, int gwPrefix = 0xFF, int gateway = -1, + const std::string& source = ""); + DecodedRecord decode(int addr, int c, int proto = 0x10, int gateway = -1, + const std::string& source = ""); private: std::map cmd_; std::map sCMD_; std::vector queryCmd_; - int lastQueryCmd_ = 0; - int64_t lastQueryAtMs_ = 0; - int pendingColourType_ = -1; - int64_t lastColourQueryAtMs_ = 0; + std::map contexts_; static int64_t nowMs(); static std::string hex(int v); @@ -56,5 +62,10 @@ class DaliDecode { static std::string booleanQueryLabel(int c); static std::string fadeTimeLabel(int code); static std::string fadeRateLabel(int code); + DaliDecodeContext& contextFor(int gateway); + void syncDefaultDtrFromContext(); + void syncDefaultDtrToContext(); + DecodedRecord decodeDeviceTypeCommand(int addr, int c, const DtCommandSpec& spec, int proto, + int gateway, const std::string& source); DecodedRecord withRaw(const DecodedRecord& r) const; }; diff --git a/include/decode_device_type.hpp b/include/decode_device_type.hpp new file mode 100644 index 0000000..4deee5c --- /dev/null +++ b/include/decode_device_type.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +struct DaliDecodeContext { + int lastQueryCmd = 0; + int lastQueryDeviceType = -1; + int64_t lastQueryAtMs = 0; + int dtr0 = 0; + int dtr1 = 0; + int dtr2 = 0; + int pendingColourType = -1; + int64_t lastColourQueryAtMs = 0; + int pendingDeviceType = -1; +}; + +struct DtCommandSpec { + std::string name; + std::string type = "cmd"; + bool isQuery = false; + std::function detail; + + std::string format(const DaliDecodeContext& context) const; +}; + +struct DeviceTypeDecodeTable { + int deviceType = -1; + std::map commands; + std::map queries; + + const DtCommandSpec* command(int code) const; + const DtCommandSpec* query(int code) const; +}; + +const DeviceTypeDecodeTable* deviceTypeDecodeTable(int deviceType); + +std::string dtr0Detail(const DaliDecodeContext& context); +std::string dtr1Detail(const DaliDecodeContext& context); +std::string dtr2Detail(const DaliDecodeContext& context); +std::string dtr10WordDetail(const DaliDecodeContext& context, const std::string& label); +std::string dt8CoordinateDetail(const DaliDecodeContext& context, const std::string& label); +std::string dt8MirekDetail(const DaliDecodeContext& context); +std::string dt8RgbDetail(const DaliDecodeContext& context); +std::string dt8WafDetail(const DaliDecodeContext& context); +std::string dt8PrimaryDetail(const DaliDecodeContext& context); +std::string dt8ControlDetail(const DaliDecodeContext& context); + +const DeviceTypeDecodeTable& dt1DecodeTable(); +const DeviceTypeDecodeTable& dt4DecodeTable(); +const DeviceTypeDecodeTable& dt5DecodeTable(); +const DeviceTypeDecodeTable& dt6DecodeTable(); +const DeviceTypeDecodeTable& dt8DecodeTable(); diff --git a/src/decode.cpp b/src/decode.cpp index 580ff13..8c34bc1 100644 --- a/src/decode.cpp +++ b/src/decode.cpp @@ -50,15 +50,6 @@ DaliDecode::DaliDecode() { {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_ = { @@ -111,12 +102,7 @@ DaliDecode::DaliDecode() { 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}; + DALI_CMD_READ_MEMORY_LOCATION}; for (int c = DALI_CMD_QUERY_SCENE_LEVEL_MIN; c <= DALI_CMD_QUERY_SCENE_LEVEL_MAX; c++) { queryCmd_.push_back(c); } @@ -126,17 +112,39 @@ 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) { +DaliDecodeContext& DaliDecode::contextFor(int gateway) { + return contexts_[gateway]; +} + +void DaliDecode::syncDefaultDtrFromContext() { + const auto it = contexts_.find(-1); + if (it == contexts_.end()) return; + dtr0 = it->second.dtr0; + dtr1 = it->second.dtr1; + dtr2 = it->second.dtr2; +} + +void DaliDecode::syncDefaultDtrToContext() { + auto& context = contexts_[-1]; + context.dtr0 = dtr0 & 0xFF; + context.dtr1 = dtr1 & 0xFF; + context.dtr2 = dtr2 & 0xFF; +} + +DecodedRecord DaliDecode::decodeBright(int addr, int level, int gateway, const std::string& source) { 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; + r.gateway = gateway; + r.source = source; return withRaw(r); } -DecodedRecord DaliDecode::decodeScene(int addr, int sceneCmd) { +DecodedRecord DaliDecode::decodeScene(int addr, int sceneCmd, int gateway, + const std::string& source) { const int sc = sceneCmd - DALI_CMD_GO_TO_SCENE_MIN; DecodedRecord r; r.text = whoLabel(addr, false) + " GO_TO_SCENE " + std::to_string(sc); @@ -144,10 +152,12 @@ DecodedRecord DaliDecode::decodeScene(int addr, int sceneCmd) { r.addr = addr; r.cmd = sceneCmd; r.proto = 0x10; + r.gateway = gateway; + r.source = source; return withRaw(r); } -DecodedRecord DaliDecode::decodeCmd(int addr, int c) { +DecodedRecord DaliDecode::decodeCmd(int addr, int c, int gateway, const std::string& source) { std::string name = "CMD 0x" + hex(c); const auto it = cmd_.find(c); if (it != cmd_.end()) name = it->second; @@ -166,16 +176,21 @@ DecodedRecord DaliDecode::decodeCmd(int addr, int c) { r.addr = addr; r.cmd = c; r.proto = 0x10; + r.gateway = gateway; + r.source = source; return withRaw(r); } -DecodedRecord DaliDecode::decodeSpCmd(int addr, int c) { +DecodedRecord DaliDecode::decodeSpCmd(int addr, int c, int gateway, const std::string& source) { + auto& context = contextFor(gateway); + if (gateway == -1) syncDefaultDtrToContext(); 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); + context.pendingDeviceType = -1; + return querySpCMD(cmdByte, dataByte, gateway, source); } std::string name = "SPECIAL_CMD 0x" + hex(cmdByte); @@ -183,12 +198,20 @@ DecodedRecord DaliDecode::decodeSpCmd(int addr, int c) { if (it != sCMD_.end()) name = it->second; if (cmdByte == DALI_CMD_SPECIAL_SET_DTR0) { - dtr0 = dataByte; + context.pendingDeviceType = -1; + context.dtr0 = dataByte; } else if (cmdByte == DALI_CMD_SPECIAL_SET_DTR_1) { - dtr1 = dataByte; + context.pendingDeviceType = -1; + context.dtr1 = dataByte; } else if (cmdByte == DALI_CMD_SPECIAL_SET_DTR_2) { - dtr2 = dataByte; + context.pendingDeviceType = -1; + context.dtr2 = dataByte; + } else if (cmdByte == DALI_CMD_SPECIAL_DT_SELECT) { + context.pendingDeviceType = dataByte; + } else { + context.pendingDeviceType = -1; } + if (gateway == -1) syncDefaultDtrFromContext(); DecodedRecord r; r.text = name + " data=0x" + hex(dataByte) + " (" + std::to_string(dataByte) + ")"; @@ -196,12 +219,17 @@ DecodedRecord DaliDecode::decodeSpCmd(int addr, int c) { r.addr = addr; r.cmd = c; r.proto = 0x10; + r.gateway = gateway; + r.source = source; return withRaw(r); } -DecodedRecord DaliDecode::querySpCMD(int cmdByte, int dataByte) { - lastQueryCmd_ = cmdByte; - lastQueryAtMs_ = nowMs(); +DecodedRecord DaliDecode::querySpCMD(int cmdByte, int dataByte, int gateway, + const std::string& source) { + auto& context = contextFor(gateway); + context.lastQueryCmd = cmdByte; + context.lastQueryDeviceType = -1; + context.lastQueryAtMs = nowMs(); std::string name = "SPECIAL_CMD 0x" + hex(cmdByte); const auto it = sCMD_.find(cmdByte); @@ -213,30 +241,41 @@ DecodedRecord DaliDecode::querySpCMD(int cmdByte, int dataByte) { r.addr = cmdByte; r.cmd = dataByte; r.proto = 0x10; + r.gateway = gateway; + r.source = source; return withRaw(r); } -DecodedRecord DaliDecode::decodeQuery(int addr, int c) { - lastQueryCmd_ = c; - lastQueryAtMs_ = nowMs(); +DecodedRecord DaliDecode::decodeQuery(int addr, int c, int gateway, const std::string& source, + int deviceType) { + auto& context = contextFor(gateway); + if (gateway == -1) syncDefaultDtrToContext(); + context.lastQueryCmd = c; + context.lastQueryDeviceType = deviceType; + context.lastQueryAtMs = nowMs(); std::string name = "QUERY 0x" + hex(c); const auto it = cmd_.find(c); if (it != cmd_.end()) name = it->second; + const auto* table = deviceTypeDecodeTable(deviceType); + if (table != nullptr) { + const auto* spec = table->query(c); + if (spec != nullptr) name = spec->format(context); + } 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; + context.lastColourQueryAtMs = nowMs(); + int t = context.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)"; + context.pendingColourType = (t == 0 || t == 1 || t == 2) ? t : -1; + if (context.pendingColourType == 0) name = "QUERY_COLOUR_VALUE(x)"; + if (context.pendingColourType == 1) name = "QUERY_COLOUR_VALUE(y)"; + if (context.pendingColourType == 2) name = "QUERY_COLOUR_VALUE(ct)"; } DecodedRecord r; @@ -245,24 +284,33 @@ DecodedRecord DaliDecode::decodeQuery(int addr, int c) { r.addr = addr; r.cmd = c; r.proto = 0x12; + r.gateway = gateway; + r.source = source; return withRaw(r); } -DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/) { +DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/, int gateway, + const std::string& source) { + auto& context = contextFor(gateway); + if (gateway == -1) syncDefaultDtrToContext(); const int64_t now = nowMs(); - if ((now - lastQueryAtMs_) > responseWindowMs || lastQueryCmd_ == 0) { + if ((now - context.lastQueryAtMs) > responseWindowMs || context.lastQueryCmd == 0) { DecodedRecord r{"unknown back 0x" + hex(value) + " / " + std::to_string(value), "unknown", -1, -1, 0xFF}; + r.gateway = gateway; + r.source = source; return withRaw(r); } - const int c = lastQueryCmd_; - lastQueryCmd_ = 0; - lastQueryAtMs_ = 0; + const int c = context.lastQueryCmd; + context.lastQueryCmd = 0; + context.lastQueryDeviceType = -1; + context.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_CONTENT_DTR) context.dtr0 = value & 0xFF; + if (c == DALI_CMD_QUERY_CONTENT_DTR1) context.dtr1 = value & 0xFF; + if (c == DALI_CMD_QUERY_CONTENT_DTR2) context.dtr2 = value & 0xFF; + if (gateway == -1) syncDefaultDtrFromContext(); if (c == DALI_CMD_QUERY_STATUS) { std::ostringstream oss; @@ -407,14 +455,14 @@ DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/) { "response", -1, c, 0xFF}); } - if (pendingColourType_ >= 0 && (now - lastColourQueryAtMs_) <= 1000) { - const int combined = dtr1 * 256 + dtr0; + if (context.pendingColourType >= 0 && (now - context.lastColourQueryAtMs) <= 1000) { + const int combined = context.dtr1 * 256 + context.dtr0; std::string pretty; - if (pendingColourType_ == 2) { + if (context.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'; + const char key = context.pendingColourType == 0 ? 'x' : 'y'; std::ostringstream oss; oss << key << "=" << std::fixed << std::setprecision(4) << (static_cast(combined) / 65535.0) << " (" << combined << ")"; @@ -427,30 +475,61 @@ DecodedRecord DaliDecode::decodeCmdResponse(int value, int /*gwPrefix*/) { -1, c, 0xFF}); } -DecodedRecord DaliDecode::decode(int addr, int c, int proto) { +DecodedRecord DaliDecode::decode(int addr, int c, int proto, int gateway, const std::string& source) { + auto& context = contextFor(gateway); + if (gateway == -1) syncDefaultDtrToContext(); if (proto == 0x12) { - return decodeQuery(addr, c); + const int selectedDeviceType = context.pendingDeviceType; + context.pendingDeviceType = -1; + return decodeQuery(addr, c, gateway, source, selectedDeviceType); } if (addr >= DALI_CMD_SPECIAL_RANGE_MIN && addr <= DALI_CMD_SPECIAL_RANGE_MAX) { - return decodeSpCmd(addr, c); + return decodeSpCmd(addr, c, gateway, source); + } + const int selectedDeviceType = context.pendingDeviceType; + context.pendingDeviceType = -1; + const auto* table = deviceTypeDecodeTable(selectedDeviceType); + if (table != nullptr) { + const auto* querySpec = table->query(c); + if (querySpec != nullptr) { + return decodeQuery(addr, c, gateway, source, selectedDeviceType); + } + const auto* commandSpec = table->command(c); + if (commandSpec != nullptr) { + return decodeDeviceTypeCommand(addr, c, *commandSpec, proto, gateway, source); + } } if (isQueryCmd(c) == 1) { - return decodeQuery(addr, c); + return decodeQuery(addr, c, gateway, source); } if ((addr & 1) == 0) { - return decodeBright(addr, c); + return decodeBright(addr, c, gateway, source); } if (c >= DALI_CMD_GO_TO_SCENE_MIN && c <= DALI_CMD_GO_TO_SCENE_MAX) { - return decodeScene(addr, c); + return decodeScene(addr, c, gateway, source); } 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 withRaw({whoLabel(addr, false) + " " + name, "ext", addr, c, proto, gateway, source}); } - return decodeCmd(addr, c); + return decodeCmd(addr, c, gateway, source); +} + +DecodedRecord DaliDecode::decodeDeviceTypeCommand(int addr, int c, const DtCommandSpec& spec, + int proto, int gateway, + const std::string& source) { + auto& context = contextFor(gateway); + DecodedRecord r; + r.text = whoLabel(addr, false) + " " + spec.format(context); + r.type = proto == 0x11 ? "ext" : spec.type; + r.addr = addr; + r.cmd = c; + r.proto = proto; + r.gateway = gateway; + r.source = source; + return withRaw(r); } int64_t DaliDecode::nowMs() { diff --git a/src/decode_device_type.cpp b/src/decode_device_type.cpp new file mode 100644 index 0000000..9167f43 --- /dev/null +++ b/src/decode_device_type.cpp @@ -0,0 +1,107 @@ +#include "decode_device_type.hpp" + +#include +#include + +namespace { + +std::string byteHex(int value) { + std::ostringstream oss; + oss << std::hex << std::uppercase << std::setfill('0') << std::setw(2) << (value & 0xFF); + return oss.str(); +} + +std::string wordHex(int value) { + std::ostringstream oss; + oss << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << (value & 0xFFFF); + return oss.str(); +} + +} // namespace + +std::string DtCommandSpec::format(const DaliDecodeContext& context) const { + if (!detail) return name; + const auto extra = detail(context); + if (extra.empty()) return name; + return name + " (" + extra + ")"; +} + +const DtCommandSpec* DeviceTypeDecodeTable::command(int code) const { + const auto it = commands.find(code & 0xFF); + return it == commands.end() ? nullptr : &it->second; +} + +const DtCommandSpec* DeviceTypeDecodeTable::query(int code) const { + const auto it = queries.find(code & 0xFF); + return it == queries.end() ? nullptr : &it->second; +} + +const DeviceTypeDecodeTable* deviceTypeDecodeTable(int deviceType) { + switch (deviceType) { + case 1: + return &dt1DecodeTable(); + case 4: + return &dt4DecodeTable(); + case 5: + return &dt5DecodeTable(); + case 6: + return &dt6DecodeTable(); + case 8: + return &dt8DecodeTable(); + default: + return nullptr; + } +} + +std::string dtr0Detail(const DaliDecodeContext& context) { + return "DTR0=0x" + byteHex(context.dtr0) + " / " + std::to_string(context.dtr0); +} + +std::string dtr1Detail(const DaliDecodeContext& context) { + return "DTR1=0x" + byteHex(context.dtr1) + " / " + std::to_string(context.dtr1); +} + +std::string dtr2Detail(const DaliDecodeContext& context) { + return "DTR2=0x" + byteHex(context.dtr2) + " / " + std::to_string(context.dtr2); +} + +std::string dtr10WordDetail(const DaliDecodeContext& context, const std::string& label) { + const int value = ((context.dtr1 & 0xFF) << 8) | (context.dtr0 & 0xFF); + return label + "=0x" + wordHex(value) + " / " + std::to_string(value) + ", DTR1=0x" + + byteHex(context.dtr1) + ", DTR0=0x" + byteHex(context.dtr0); +} + +std::string dt8CoordinateDetail(const DaliDecodeContext& context, const std::string& label) { + const int value = ((context.dtr1 & 0xFF) << 8) | (context.dtr0 & 0xFF); + std::ostringstream oss; + oss << label << "=0x" << wordHex(value) << " / " << value << ", normalized=" << std::fixed + << std::setprecision(4) << (static_cast(value) / 65535.0) << ", DTR1=0x" + << byteHex(context.dtr1) << ", DTR0=0x" << byteHex(context.dtr0); + return oss.str(); +} + +std::string dt8MirekDetail(const DaliDecodeContext& context) { + const int mirek = ((context.dtr1 & 0xFF) << 8) | (context.dtr0 & 0xFF); + const int kelvin = mirek > 0 ? 1000000 / mirek : 0; + return "mirek=0x" + wordHex(mirek) + " / " + std::to_string(mirek) + + ", kelvin=" + std::to_string(kelvin) + ", DTR1=0x" + byteHex(context.dtr1) + + ", DTR0=0x" + byteHex(context.dtr0); +} + +std::string dt8RgbDetail(const DaliDecodeContext& context) { + return "R=" + std::to_string(context.dtr0) + ", G=" + std::to_string(context.dtr1) + + ", B=" + std::to_string(context.dtr2); +} + +std::string dt8WafDetail(const DaliDecodeContext& context) { + return "W=" + std::to_string(context.dtr0) + ", A=" + std::to_string(context.dtr1) + + ", F=" + std::to_string(context.dtr2); +} + +std::string dt8PrimaryDetail(const DaliDecodeContext& context) { + return dtr10WordDetail(context, "value") + ", primary=" + std::to_string(context.dtr2 & 0xFF); +} + +std::string dt8ControlDetail(const DaliDecodeContext& context) { + return "control=0x" + byteHex(context.dtr0) + " / " + std::to_string(context.dtr0); +} diff --git a/src/decode_dt1.cpp b/src/decode_dt1.cpp new file mode 100644 index 0000000..2428456 --- /dev/null +++ b/src/decode_dt1.cpp @@ -0,0 +1,43 @@ +#include "decode_device_type.hpp" + +const DeviceTypeDecodeTable& dt1DecodeTable() { + static const DeviceTypeDecodeTable table = { + 1, + { + {0xE0, {"REST"}}, + {0xE1, {"INHIBIT"}}, + {0xE2, {"RE_LIGHT_OR_RESET_INHIBIT"}}, + {0xE3, {"START_FUNCTION_TEST"}}, + {0xE4, {"START_DURATION_TEST"}}, + {0xE5, {"STOP_TEST"}}, + {0xE6, {"RESET_FUNCTION_TEST_DONE_FLAG"}}, + {0xE7, {"RESET_DURATION_TEST_DONE_FLAG"}}, + {0xE8, {"RESET_LAMP_TIME"}}, + {0xE9, {"STORE_EMERGENCY_LEVEL", "cmd", false, dtr0Detail}}, + {0xEA, {"STORE_TEST_DELAY_TIME_HIGH_BYTE", "cmd", false, dtr0Detail}}, + {0xEB, {"STORE_TEST_DELAY_TIME_LOW_BYTE", "cmd", false, dtr0Detail}}, + {0xEC, {"STORE_FUNCTION_TEST_INTERVAL", "cmd", false, dtr0Detail}}, + {0xED, {"STORE_DURATION_TEST_INTERVAL", "cmd", false, dtr0Detail}}, + {0xEE, {"STORE_TEST_EXECUTION_TIMEOUT", "cmd", false, dtr0Detail}}, + {0xEF, {"STORE_PROLONG_TIME", "cmd", false, dtr0Detail}}, + {0xF0, {"START_IDENTIFICATION"}}, + {0xFE, {"PERFORM_DTR_SELECTED_FUNCTION", "cmd", false, dtr0Detail}}, + }, + { + {0xF1, {"QUERY_BATTERY_CHARGE_LEVEL", "query", true}}, + {0xF2, {"QUERY_TEST_TIMING", "query", true, dtr0Detail}}, + {0xF3, {"QUERY_DURATION_TEST_RESULT", "query", true}}, + {0xF4, {"QUERY_LAMP_EMERGENCY_TIME", "query", true}}, + {0xF5, {"QUERY_LAMP_TOTAL_OPERATION_TIME", "query", true}}, + {0xF6, {"QUERY_EMERGENCY_LEVEL", "query", true}}, + {0xF7, {"QUERY_EMERGENCY_MIN_LEVEL", "query", true}}, + {0xF8, {"QUERY_EMERGENCY_MAX_LEVEL", "query", true}}, + {0xF9, {"QUERY_RATED_DURATION", "query", true}}, + {0xFA, {"QUERY_EMERGENCY_MODE", "query", true}}, + {0xFB, {"QUERY_FEATURES", "query", true}}, + {0xFC, {"QUERY_FAILURE_STATUS", "query", true}}, + {0xFD, {"QUERY_EMERGENCY_STATUS", "query", true}}, + {0xFF, {"QUERY_EXTENDED_VERSION_NUMBER", "query", true}}, + }}; + return table; +} diff --git a/src/decode_dt4.cpp b/src/decode_dt4.cpp new file mode 100644 index 0000000..716b2ea --- /dev/null +++ b/src/decode_dt4.cpp @@ -0,0 +1,27 @@ +#include "decode_device_type.hpp" + +const DeviceTypeDecodeTable& dt4DecodeTable() { + static const DeviceTypeDecodeTable table = { + 4, + { + {0xE0, {"REFERENCE_SYSTEM_POWER"}}, + {0xE1, {"SELECT_DIMMING_CURVE", "cmd", false, dtr0Detail}}, + }, + { + {0xEE, {"QUERY_DIMMING_CURVE", "query", true}}, + {0xEF, {"QUERY_RATED_MINIMUM_LEVEL", "query", true}}, + {0xF0, {"QUERY_RATED_MAXIMUM_LEVEL", "query", true}}, + {0xF1, {"QUERY_SYSTEM_POWER", "query", true}}, + {0xF2, {"QUERY_DIMMER_TEMPERATURE", "query", true}}, + {0xF3, {"QUERY_RMS_SUPPLY_VOLTAGE", "query", true}}, + {0xF4, {"QUERY_SUPPLY_FREQUENCY", "query", true}}, + {0xF5, {"QUERY_RMS_LOAD_VOLTAGE", "query", true}}, + {0xF6, {"QUERY_RMS_LOAD_CURRENT", "query", true}}, + {0xF7, {"QUERY_REAL_LOAD_POWER", "query", true}}, + {0xF8, {"QUERY_LOAD_RATING", "query", true}}, + {0xF9, {"QUERY_REFERENCE_RUNNING", "query", true}}, + {0xFA, {"QUERY_REFERENCE_MEASUREMENT_FAILED", "query", true}}, + {0xFF, {"QUERY_EXTENDED_VERSION_NUMBER", "query", true}}, + }}; + return table; +} diff --git a/src/decode_dt5.cpp b/src/decode_dt5.cpp new file mode 100644 index 0000000..e2941ee --- /dev/null +++ b/src/decode_dt5.cpp @@ -0,0 +1,24 @@ +#include "decode_device_type.hpp" + +const DeviceTypeDecodeTable& dt5DecodeTable() { + static const DeviceTypeDecodeTable table = { + 5, + { + {0xE0, {"SET_OUTPUT_RANGE_1_TO_10V"}}, + {0xE1, {"SET_OUTPUT_RANGE_0_TO_10V"}}, + {0xE2, {"SWITCH_ON_INTERNAL_PULL_UP"}}, + {0xE3, {"SWITCH_OFF_INTERNAL_PULL_UP"}}, + {0xE4, {"STORE_DTR_AS_PHYSICAL_MINIMUM_LEVEL", "cmd", false, dtr0Detail}}, + {0xE5, {"SELECT_DIMMING_CURVE", "cmd", false, dtr0Detail}}, + {0xE6, {"RESET_CONVERTER_SETTINGS"}}, + }, + { + {0xEE, {"QUERY_DIMMING_CURVE", "query", true}}, + {0xEF, {"QUERY_OUTPUT_LEVEL", "query", true}}, + {0xF0, {"QUERY_CONVERTER_STATUS", "query", true}}, + {0xF1, {"QUERY_OUTPUT_RANGE", "query", true}}, + {0xF2, {"QUERY_FEATURES", "query", true}}, + {0xFF, {"QUERY_EXTENDED_VERSION_NUMBER", "query", true}}, + }}; + return table; +} diff --git a/src/decode_dt6.cpp b/src/decode_dt6.cpp new file mode 100644 index 0000000..6de9976 --- /dev/null +++ b/src/decode_dt6.cpp @@ -0,0 +1,35 @@ +#include "decode_device_type.hpp" + +const DeviceTypeDecodeTable& dt6DecodeTable() { + static const DeviceTypeDecodeTable table = { + 6, + { + {0xE0, {"REFERENCE_SYSTEM_POWER"}}, + {0xE1, {"ENABLE_CURRENT_PROTECTOR"}}, + {0xE2, {"DISABLE_CURRENT_PROTECTOR"}}, + {0xE3, {"SELECT_DIMMING_CURVE", "cmd", false, dtr0Detail}}, + {0xE4, {"STORE_DTR_AS_FAST_FADE_TIME", "cmd", false, dtr0Detail}}, + }, + { + {0xED, {"QUERY_GEAR_TYPE", "query", true}}, + {0xEE, {"QUERY_DIMMING_CURVE", "query", true}}, + {0xEF, {"QUERY_POSSIBLE_OPERATING_MODES", "query", true}}, + {0xF0, {"QUERY_FEATURES", "query", true}}, + {0xF1, {"QUERY_FAILURE_STATUS", "query", true}}, + {0xF2, {"QUERY_SHORT_CIRCUIT", "query", true}}, + {0xF3, {"QUERY_OPEN_CIRCUIT", "query", true}}, + {0xF4, {"QUERY_LOAD_DECREASE", "query", true}}, + {0xF5, {"QUERY_LOAD_INCREASE", "query", true}}, + {0xF6, {"QUERY_CURRENT_PROTECTOR_ACTIVE", "query", true}}, + {0xF7, {"QUERY_THERMAL_SHUTDOWN", "query", true}}, + {0xF8, {"QUERY_THERMAL_OVERLOAD", "query", true}}, + {0xF9, {"QUERY_REFERENCE_RUNNING", "query", true}}, + {0xFA, {"QUERY_REFERENCE_MEASUREMENT_FAILED", "query", true}}, + {0xFB, {"QUERY_CURRENT_PROTECTOR_ENABLED", "query", true}}, + {0xFC, {"QUERY_OPERATING_MODE", "query", true}}, + {0xFD, {"QUERY_FAST_FADE_TIME", "query", true}}, + {0xFE, {"QUERY_MIN_FAST_FADE_TIME", "query", true}}, + {0xFF, {"QUERY_EXTENDED_VERSION_NUMBER", "query", true}}, + }}; + return table; +} diff --git a/src/decode_dt8.cpp b/src/decode_dt8.cpp new file mode 100644 index 0000000..5c990ec --- /dev/null +++ b/src/decode_dt8.cpp @@ -0,0 +1,55 @@ +#include "decode_device_type.hpp" + +namespace { + +std::string colorXDetail(const DaliDecodeContext& context) { + return dt8CoordinateDetail(context, "x"); +} + +std::string colorYDetail(const DaliDecodeContext& context) { + return dt8CoordinateDetail(context, "y"); +} + +std::string mirekLimitDetail(const DaliDecodeContext& context) { + return dt8MirekDetail(context) + ", limit=" + std::to_string(context.dtr2 & 0xFF); +} + +} // namespace + +const DeviceTypeDecodeTable& dt8DecodeTable() { + static const DeviceTypeDecodeTable table = { + 8, + { + {0xE0, {"STORE_DTR_AS_COLORX", "cmd", false, colorXDetail}}, + {0xE1, {"STORE_DTR_AS_COLORY", "cmd", false, colorYDetail}}, + {0xE2, {"ACTIVATE"}}, + {0xE3, {"STEP_UP_COLORX"}}, + {0xE4, {"STEP_DOWN_COLORX"}}, + {0xE5, {"STEP_UP_COLORY"}}, + {0xE6, {"STEP_DOWN_COLORY"}}, + {0xE7, {"SET_COLOR_TEMPERATURE", "cmd", false, dt8MirekDetail}}, + {0xE8, {"STEP_UP_COLOR_TEMPERATURE"}}, + {0xE9, {"STEP_DOWN_COLOR_TEMPERATURE"}}, + {0xEA, {"SET_TEMPORARY_PRIMARY_N_DIM_LEVEL", "cmd", false, dt8PrimaryDetail}}, + {0xEB, {"SET_TEMPORARY_RGB_DIM_LEVELS", "cmd", false, dt8RgbDetail}}, + {0xEC, {"SET_TEMPORARY_WAF_DIM_LEVELS", "cmd", false, dt8WafDetail}}, + {0xED, {"SET_TEMPORARY_RGBWAF_CONTROL", "cmd", false, dt8ControlDetail}}, + {0xEE, {"COPY_REPORT_TO_TEMPORARY"}}, + {0xF0, {"STORE_XY_PRIMARY_N", "cmd", false, dt8PrimaryDetail}}, + {0xF1, {"STORE_TEMPORARY_XY_AS_PRIMARY_N", "cmd", false, dtr2Detail}}, + {0xF2, {"STORE_COLOR_TEMPERATURE_LIMIT", "cmd", false, mirekLimitDetail}}, + {0xF3, {"STORE_GEAR_FEATURES_STATUS", "cmd", false, dtr0Detail}}, + {0xF5, {"ASSIGN_COLOR_TO_LINKED_CHANNELS", "cmd", false, dtr0Detail}}, + {0xF6, {"START_AUTO_CALIBRATION"}}, + }, + { + {0xF7, {"QUERY_GEAR_FEATURES_STATUS", "query", true}}, + {0xF8, {"QUERY_COLOR_STATUS", "query", true}}, + {0xF9, {"QUERY_COLOR_TYPE_FEATURES", "query", true}}, + {0xFA, {"QUERY_COLOUR_VALUE", "query", true, dtr0Detail}}, + {0xFB, {"QUERY_RGBWAF_CONTROL", "query", true}}, + {0xFC, {"QUERY_ASSIGNED_COLOR_FOR_CHANNEL", "query", true, dtr0Detail}}, + {0xFF, {"QUERY_EXTENDED_VERSION_NUMBER", "query", true}}, + }}; + return table; +}