0c662ecdc3
Signed-off-by: Tony <tonylu@tony-cloud.com>
315 lines
9.4 KiB
C++
315 lines
9.4 KiB
C++
#include "dali_comm.hpp"
|
|
|
|
#include "dali_define.hpp"
|
|
#include "log.hpp"
|
|
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#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<unsigned>(value));
|
|
return buffer;
|
|
}
|
|
|
|
std::string formatBytes(const std::vector<uint8_t>& 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<uint8_t> 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<uint8_t> collected;
|
|
while (std::chrono::steady_clock::now() < deadline && collected.size() < 2) {
|
|
const auto remaining =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(deadline - std::chrono::steady_clock::now())
|
|
.count();
|
|
if (remaining <= 0) break;
|
|
|
|
const auto chunk = read_cb(2 - collected.size(), static_cast<uint32_t>(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<std::ptrdiff_t>(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<uint8_t>& resp, uint8_t addr) {
|
|
DaliQueryResult result;
|
|
result.raw = resp;
|
|
if (resp.empty()) {
|
|
result.status = DaliQueryStatus::timeout;
|
|
return result;
|
|
}
|
|
|
|
// Gateway type 1 returns: 0xFF <data> 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<uint8_t>(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<uint8_t> DaliComm::checksum(const std::vector<uint8_t>& data) {
|
|
std::vector<uint8_t> out = data;
|
|
uint32_t sum = 0;
|
|
for (const auto b : out) {
|
|
sum += b;
|
|
}
|
|
out.push_back(static_cast<uint8_t>(sum & 0xFF));
|
|
return out;
|
|
}
|
|
|
|
bool DaliComm::write(const std::vector<uint8_t>& data) const { return writeFrame(data); }
|
|
|
|
std::vector<uint8_t> 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<uint8_t> usbProbe{0x01, 0x00, 0x00};
|
|
const std::vector<uint8_t> legacyProbe{0x28, 0x01, static_cast<uint8_t>(gateway), 0x11, 0x00, 0x00,
|
|
0xFF};
|
|
const std::vector<uint8_t> newProbe{0x28, 0x01, static_cast<uint8_t>(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<uint8_t> 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<uint8_t> DaliComm::queryRaw(uint8_t addr, uint8_t cmd) const { return queryCmd(addr, cmd); }
|
|
|
|
std::optional<uint8_t> 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<uint8_t> 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<uint8_t>(dec_addr * 2 + 1); }
|
|
|
|
uint8_t DaliComm::toArcAddr(int dec_addr) { return static_cast<uint8_t>(dec_addr * 2); }
|
|
|
|
bool DaliComm::writeFrame(const std::vector<uint8_t>& 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<uint8_t> 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<uint8_t> frame{0x11, addr, cmd};
|
|
const bool ret = writeFrame(frame);
|
|
sleepMs(100);
|
|
return ret;
|
|
}
|
|
|
|
std::optional<uint8_t> 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<uint8_t> 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;
|
|
}
|