#include "dali_comm.hpp" #include "dali_define.hpp" #include "log.hpp" #include #include #include #include #ifdef ESP_PLATFORM #include "freertos/FreeRTOS.h" #include "freertos/task.h" #endif namespace { constexpr uint32_t kQueryTimeoutMs = 100; std::string hexByte(uint8_t value) { char buffer[5]{}; std::snprintf(buffer, sizeof(buffer), "0x%02X", static_cast(value)); return buffer; } std::string formatBytes(const std::vector& bytes) { if (bytes.empty()) return "[]"; std::string out = "["; for (size_t i = 0; i < bytes.size(); ++i) { if (i > 0) out += ' '; out += hexByte(bytes[i]); } out += ']'; return out; } std::vector readQueryResponse(const DaliComm::ReadCallback& read_cb, uint32_t timeout_ms) { if (!read_cb) return {}; const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeout_ms); std::vector collected; while (std::chrono::steady_clock::now() < deadline && collected.size() < 2) { const auto remaining = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()) .count(); if (remaining <= 0) break; const auto chunk = read_cb(2 - collected.size(), static_cast(remaining)); if (chunk.empty()) break; const size_t needed = 2 - collected.size(); const size_t take = std::min(needed, chunk.size()); collected.insert(collected.end(), chunk.begin(), chunk.begin() + static_cast(take)); // Non-prefixed one-byte replies are complete as soon as they arrive. if (collected.size() == 1 && collected[0] != 0xFF && collected[0] != 0xFE && collected[0] != 0xFD) { break; } } return collected; } DaliQueryResult parseQueryResponse(const std::vector& resp, uint8_t addr) { DaliQueryResult result; result.raw = resp; if (resp.empty()) { result.status = DaliQueryStatus::timeout; return result; } // Gateway type 1 returns: 0xFF on success; 0xFE 0x00 on no response; // 0xFD xx on invalid frame. COMPARE is special: collisions still mean a match. if (resp[0] == 0xFE) { result.status = DaliQueryStatus::noResponse; return result; } if (resp[0] == 0xFD) { if (addr == DALI_CMD_SPECIAL_COMPARE) { result.status = DaliQueryStatus::success; result.value = static_cast(resp.size() >= 2 ? resp[1] : 0xFF); return result; } result.status = DaliQueryStatus::invalidFrame; return result; } if (resp.size() == 1) { result.status = DaliQueryStatus::success; result.value = resp[0]; return result; } if (resp[0] == 0xFF && resp.size() >= 2) { result.status = DaliQueryStatus::success; result.value = resp[1]; return result; } result.status = DaliQueryStatus::unknownFrame; return result; } void logQueryResult(uint8_t addr, uint8_t cmd, const DaliQueryResult& result) { std::string message = "dali:query addr=" + hexByte(addr) + " cmd=" + hexByte(cmd) + " status=" + daliQueryStatusName(result.status); if (result.value.has_value()) { message += " value=" + hexByte(result.value.value()); } message += " raw=" + formatBytes(result.raw); DaliLog::instance().debugLog(message); } } // namespace const char* daliQueryStatusName(DaliQueryStatus status) { switch (status) { case DaliQueryStatus::success: return "success"; case DaliQueryStatus::noResponse: return "noResponse"; case DaliQueryStatus::invalidFrame: return "invalidFrame"; case DaliQueryStatus::unknownFrame: return "unknownFrame"; case DaliQueryStatus::sendFailed: return "sendFailed"; case DaliQueryStatus::transportUnavailable: return "transportUnavailable"; case DaliQueryStatus::timeout: return "timeout"; } return "unknown"; } DaliComm::DaliComm(SendCallback send_cb, ReadCallback read_cb, TransactCallback transact_cb, DelayCallback delay_cb) : send_(std::move(send_cb)), read_(std::move(read_cb)), transact_(std::move(transact_cb)), delay_(std::move(delay_cb)) {} void DaliComm::setSendCallback(SendCallback cb) { send_ = std::move(cb); } void DaliComm::setReadCallback(ReadCallback cb) { read_ = std::move(cb); } void DaliComm::setTransactCallback(TransactCallback cb) { transact_ = std::move(cb); } void DaliComm::setDelayCallback(DelayCallback cb) { delay_ = std::move(cb); } std::vector DaliComm::checksum(const std::vector& data) { std::vector out = data; uint32_t sum = 0; for (const auto b : out) { sum += b; } out.push_back(static_cast(sum & 0xFF)); return out; } bool DaliComm::write(const std::vector& data) const { return writeFrame(data); } std::vector DaliComm::read(size_t len, uint32_t timeout_ms) const { if (!read_) return {}; return read_(len, timeout_ms); } int DaliComm::checkGatewayType(int gateway) const { if (!transact_) return 0; const std::vector usbProbe{0x01, 0x00, 0x00}; const std::vector legacyProbe{0x28, 0x01, static_cast(gateway), 0x11, 0x00, 0x00, 0xFF}; const std::vector newProbe{0x28, 0x01, static_cast(gateway), 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}; prepareForQuery(); const auto usbResp = transact_(usbProbe.data(), usbProbe.size()); if (!usbResp.empty() && usbResp[0] > 0) return 1; prepareForQuery(); const auto legacyResp = transact_(legacyProbe.data(), legacyProbe.size()); if (legacyResp.size() >= 2 && legacyResp[0] == gateway) return 2; prepareForQuery(); const auto newResp = transact_(newProbe.data(), newProbe.size()); if (newResp.size() >= 2 && newResp[0] == gateway) return 3; return 0; } void DaliComm::flush() const { if (!read_) return; for (int i = 0; i < 10; i++) { const auto data = read_(2, 100); if (data.empty()) break; } } bool DaliComm::resetBus() const { const std::vector frame{0x00, 0x00, 0x00}; if (!write(frame)) return false; sleepMs(100); if (!write(frame)) return false; sleepMs(100); return true; } bool DaliComm::sendRaw(uint8_t addr, uint8_t cmd) const { return sendCmd(addr, cmd); } bool DaliComm::sendRawNew(uint8_t addr, uint8_t cmd, bool needVerify) const { if (!needVerify) return sendCmd(addr, cmd); return queryCmd(addr, cmd).has_value(); } bool DaliComm::sendExtRaw(uint8_t addr, uint8_t cmd) const { return sendExtCmd(addr, cmd); } bool DaliComm::sendExtRawNew(uint8_t addr, uint8_t cmd) const { return sendExtCmd(addr, cmd); } std::optional DaliComm::queryRaw(uint8_t addr, uint8_t cmd) const { return queryCmd(addr, cmd); } std::optional DaliComm::queryRawNew(uint8_t addr, uint8_t cmd) const { return queryCmd(addr, cmd); } DaliQueryResult DaliComm::queryRawResult(uint8_t addr, uint8_t cmd) const { return queryCmdResult(addr, cmd); } DaliQueryResult DaliComm::queryRawNewResult(uint8_t addr, uint8_t cmd) const { return queryCmdResult(addr, cmd); } bool DaliComm::send(int dec_addr, uint8_t cmd) const { return sendCmd(toCmdAddr(dec_addr), cmd); } std::optional DaliComm::query(int dec_addr, uint8_t cmd) const { return queryCmd(toCmdAddr(dec_addr), cmd); } bool DaliComm::getBusStatus() const { return checkGatewayType(0) > 0; } uint8_t DaliComm::toCmdAddr(int dec_addr) { return static_cast(dec_addr * 2 + 1); } uint8_t DaliComm::toArcAddr(int dec_addr) { return static_cast(dec_addr * 2); } bool DaliComm::writeFrame(const std::vector& frame) const { if (!send_) return false; return send_(frame.data(), frame.size()); } void DaliComm::prepareForQuery() const { if (!read_) return; for (int i = 0; i < 8; ++i) { const auto data = read_(64, 1); if (data.empty()) { break; } } } void DaliComm::sleepMs(uint32_t ms) const { if (delay_) { delay_(ms); return; } #ifdef ESP_PLATFORM vTaskDelay(pdMS_TO_TICKS(ms)); #else std::this_thread::sleep_for(std::chrono::milliseconds(ms)); #endif } bool DaliComm::sendCmd(uint8_t addr, uint8_t cmd) const { const std::vector frame{0x10, addr, cmd}; return writeFrame(frame); } // Send extended command frame (0x11). Gateway will send twice for 0x11, but we only need to send once. bool DaliComm::sendExtCmd(uint8_t addr, uint8_t cmd) const { const std::vector frame{0x11, addr, cmd}; const bool ret = writeFrame(frame); sleepMs(100); return ret; } std::optional DaliComm::queryCmd(uint8_t addr, uint8_t cmd) const { const auto result = queryCmdResult(addr, cmd); if (!result.hasValue()) return std::nullopt; return result.value; } DaliQueryResult DaliComm::queryCmdResult(uint8_t addr, uint8_t cmd) const { const std::vector frame{0x12, addr, cmd}; DaliQueryResult result; if (send_ && read_) { prepareForQuery(); if (!writeFrame(frame)) { result.status = DaliQueryStatus::sendFailed; logQueryResult(addr, cmd, result); return result; } result = parseQueryResponse(readQueryResponse(read_, kQueryTimeoutMs), addr); logQueryResult(addr, cmd, result); return result; } prepareForQuery(); if (!transact_) { result.status = DaliQueryStatus::transportUnavailable; logQueryResult(addr, cmd, result); return result; } result = parseQueryResponse(transact_(frame.data(), frame.size()), addr); logQueryResult(addr, cmd, result); return result; }