feat(gateway): implement serial communication handling in DaliDomainService and GatewayController

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Tony
2026-05-01 01:59:32 +08:00
parent ae4669e1b3
commit 2c1aa28d4f
6 changed files with 172 additions and 20 deletions
@@ -121,6 +121,9 @@ class DaliDomainService {
DaliChannel* findChannelByIndex(uint8_t channel_index); DaliChannel* findChannelByIndex(uint8_t channel_index);
const DaliChannel* findChannelByHardwareBus(uint8_t bus_id) const; const DaliChannel* findChannelByHardwareBus(uint8_t bus_id) const;
bool hasSerialPort(int uart_port) const; bool hasSerialPort(int uart_port) const;
esp_err_t startSerialRxTask(DaliChannel& channel);
static void SerialRxTaskEntry(void* arg);
void serialRxTaskLoop(DaliChannel* channel);
esp_err_t startRawFrameTask(); esp_err_t startRawFrameTask();
static void RawFrameTaskEntry(void* arg); static void RawFrameTaskEntry(void* arg);
void rawFrameTaskLoop(); void rawFrameTaskLoop();
+134 -20
View File
@@ -5,6 +5,7 @@
#include "dali.hpp" #include "dali.hpp"
#include "driver/uart.h" #include "driver/uart.h"
#include "esp_log.h" #include "esp_log.h"
#include "freertos/queue.h"
#include <algorithm> #include <algorithm>
#include <utility> #include <utility>
@@ -14,6 +15,13 @@ namespace gateway {
namespace { namespace {
constexpr const char* kTag = "dali_domain"; constexpr const char* kTag = "dali_domain";
constexpr size_t kSerialRxPacketMaxBytes = 8;
constexpr UBaseType_t kSerialRxQueueDepth = 8;
struct SerialRxPacket {
size_t len{0};
uint8_t data[kSerialRxPacketMaxBytes]{};
};
bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) { bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) {
if (data == nullptr || len != 3) { if (data == nullptr || len != 3) {
@@ -74,35 +82,66 @@ bool WriteSerialFrame(int uart_port, const uint8_t* data, size_t len) {
return uart_write_bytes(static_cast<uart_port_t>(uart_port), data, len) == static_cast<int>(len); return uart_write_bytes(static_cast<uart_port_t>(uart_port), data, len) == static_cast<int>(len);
} }
std::vector<uint8_t> ReadSerialFrame(int uart_port, size_t len, uint32_t timeout_ms) { std::vector<uint8_t> PacketToVector(const SerialRxPacket& packet, size_t len = 0) {
std::vector<uint8_t> data(len, 0); const size_t out_len = len == 0 ? packet.len : std::min(packet.len, len);
const int read_len = uart_read_bytes(static_cast<uart_port_t>(uart_port), data.data(), len, return std::vector<uint8_t>(packet.data, packet.data + out_len);
pdMS_TO_TICKS(timeout_ms));
if (read_len <= 0) {
return {};
}
data.resize(static_cast<size_t>(read_len));
return data;
} }
std::vector<uint8_t> TransactSerialFrame(int uart_port, uint32_t query_timeout_ms, void DrainSerialQueue(QueueHandle_t queue) {
const uint8_t* data, size_t len) { if (queue == nullptr) {
return;
}
SerialRxPacket packet;
while (xQueueReceive(queue, &packet, 0) == pdTRUE) {
}
}
std::vector<uint8_t> ReadSerialFrame(QueueHandle_t queue, size_t len, uint32_t timeout_ms) {
if (queue == nullptr) {
return {};
}
SerialRxPacket packet;
if (xQueueReceive(queue, &packet, pdMS_TO_TICKS(timeout_ms)) != pdTRUE) {
return {};
}
return PacketToVector(packet, len);
}
std::vector<uint8_t> TransactSerialFrame(int uart_port, QueueHandle_t queue,
uint32_t query_timeout_ms, const uint8_t* data,
size_t len) {
if (data != nullptr && len > 0 && data[0] == 0x12) {
DrainSerialQueue(queue);
}
if (!WriteSerialFrame(uart_port, data, len)) { if (!WriteSerialFrame(uart_port, data, len)) {
return {0xFD}; return {0xFD};
} }
if (data == nullptr || len == 0 || data[0] != 0x12) { if (data == nullptr || len == 0 || data[0] != 0x12) {
return {0xFF}; return {0xFF};
} }
const auto response = ReadSerialFrame(uart_port, 2, query_timeout_ms);
if (response.empty()) { const TickType_t timeout_ticks = pdMS_TO_TICKS(query_timeout_ms);
return {0xFE}; const TickType_t started = xTaskGetTickCount();
while ((xTaskGetTickCount() - started) < timeout_ticks) {
const TickType_t elapsed = xTaskGetTickCount() - started;
const TickType_t remaining = timeout_ticks > elapsed ? timeout_ticks - elapsed : 0;
SerialRxPacket packet;
if (xQueueReceive(queue, &packet, remaining) != pdTRUE) {
break;
}
auto response = PacketToVector(packet, 2);
if (!response.empty() &&
(response[0] == 0xFF || response[0] == 0xFE || response[0] == 0xFD)) {
return response;
}
} }
return response; return {0xFE};
} }
} // namespace } // namespace
struct DaliDomainService::DaliChannel { struct DaliDomainService::DaliChannel {
DaliDomainService* owner{nullptr};
DaliChannelConfig config; DaliChannelConfig config;
DaliPhyKind phy_kind{DaliPhyKind::kCustom}; DaliPhyKind phy_kind{DaliPhyKind::kCustom};
DaliTransportHooks hooks; DaliTransportHooks hooks;
@@ -110,6 +149,8 @@ struct DaliDomainService::DaliChannel {
std::unique_ptr<Dali> dali; std::unique_ptr<Dali> dali;
std::optional<DaliHardwareBusConfig> hardware_bus; std::optional<DaliHardwareBusConfig> hardware_bus;
std::optional<DaliSerialBusConfig> serial_bus; std::optional<DaliSerialBusConfig> serial_bus;
QueueHandle_t serial_rx_queue{nullptr};
TaskHandle_t serial_rx_task_handle{nullptr};
}; };
DaliDomainService::DaliDomainService() DaliDomainService::DaliDomainService()
@@ -123,6 +164,7 @@ bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTrans
} }
auto channel = std::make_unique<DaliChannel>(); auto channel = std::make_unique<DaliChannel>();
channel->owner = this;
channel->config = config; channel->config = config;
channel->hooks = std::move(hooks); channel->hooks = std::move(hooks);
channel->comm = std::make_unique<DaliComm>(channel->hooks.send, channel->hooks.read, channel->comm = std::make_unique<DaliComm>(channel->hooks.send, channel->hooks.read,
@@ -221,21 +263,28 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
} }
uart_flush_input(uart); uart_flush_input(uart);
QueueHandle_t serial_rx_queue = xQueueCreate(kSerialRxQueueDepth, sizeof(SerialRxPacket));
if (serial_rx_queue == nullptr) {
ESP_LOGE(kTag, "failed to create uart%d RX queue", config.uart_port);
return ESP_ERR_NO_MEM;
}
DaliTransportHooks hooks; DaliTransportHooks hooks;
hooks.send = [uart_port = config.uart_port](const uint8_t* data, size_t len) { hooks.send = [uart_port = config.uart_port](const uint8_t* data, size_t len) {
return WriteSerialFrame(uart_port, data, len); return WriteSerialFrame(uart_port, data, len);
}; };
hooks.read = [uart_port = config.uart_port](size_t len, uint32_t timeout_ms) { hooks.read = [serial_rx_queue](size_t len, uint32_t timeout_ms) {
return ReadSerialFrame(uart_port, len, timeout_ms); return ReadSerialFrame(serial_rx_queue, len, timeout_ms);
}; };
hooks.transact = [uart_port = config.uart_port, timeout_ms = config.query_timeout_ms]( hooks.transact = [uart_port = config.uart_port, serial_rx_queue,
const uint8_t* data, size_t len) { timeout_ms = config.query_timeout_ms](const uint8_t* data, size_t len) {
return TransactSerialFrame(uart_port, timeout_ms, data, len); return TransactSerialFrame(uart_port, serial_rx_queue, timeout_ms, data, len);
}; };
hooks.delay = [](uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }; hooks.delay = [](uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); };
const DaliChannelConfig channel_config{config.channel_index, config.gateway_id, config.name}; const DaliChannelConfig channel_config{config.channel_index, config.gateway_id, config.name};
if (!bindTransport(channel_config, std::move(hooks))) { if (!bindTransport(channel_config, std::move(hooks))) {
vQueueDelete(serial_rx_queue);
return ESP_ERR_INVALID_STATE; return ESP_ERR_INVALID_STATE;
} }
@@ -243,6 +292,13 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
if (channel != nullptr) { if (channel != nullptr) {
channel->phy_kind = DaliPhyKind::kSerialUart; channel->phy_kind = DaliPhyKind::kSerialUart;
channel->serial_bus = config; channel->serial_bus = config;
channel->serial_rx_queue = serial_rx_queue;
err = startSerialRxTask(*channel);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to start uart%d RX task: %s", config.uart_port,
esp_err_to_name(err));
return err;
}
} }
ESP_LOGI(kTag, "bound channel=%u gateway=%u serial uart%d tx=%d rx=%d baudrate=%lu", ESP_LOGI(kTag, "bound channel=%u gateway=%u serial uart%d tx=%d rx=%d baudrate=%lu",
config.channel_index, config.gateway_id, config.uart_port, config.tx_pin, config.rx_pin, config.channel_index, config.gateway_id, config.uart_port, config.tx_pin, config.rx_pin,
@@ -472,6 +528,64 @@ const DaliDomainService::DaliChannel* DaliDomainService::findChannelByHardwareBu
return it == channels_.end() ? nullptr : it->get(); return it == channels_.end() ? nullptr : it->get();
} }
esp_err_t DaliDomainService::startSerialRxTask(DaliChannel& channel) {
if (channel.serial_rx_task_handle != nullptr) {
return ESP_OK;
}
if (!channel.serial_bus.has_value() || channel.serial_rx_queue == nullptr) {
return ESP_ERR_INVALID_STATE;
}
const BaseType_t created = xTaskCreate(&DaliDomainService::SerialRxTaskEntry,
"dali_uart_rx", 4096, &channel, 4,
&channel.serial_rx_task_handle);
if (created != pdPASS) {
channel.serial_rx_task_handle = nullptr;
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
void DaliDomainService::SerialRxTaskEntry(void* arg) {
auto* channel = static_cast<DaliChannel*>(arg);
if (channel != nullptr && channel->owner != nullptr) {
channel->owner->serialRxTaskLoop(channel);
}
vTaskDelete(nullptr);
}
void DaliDomainService::serialRxTaskLoop(DaliChannel* channel) {
if (channel == nullptr || !channel->serial_bus.has_value() ||
channel->serial_rx_queue == nullptr) {
return;
}
const auto uart = static_cast<uart_port_t>(channel->serial_bus->uart_port);
while (true) {
SerialRxPacket packet;
const int read_len = uart_read_bytes(uart, packet.data, sizeof(packet.data),
pdMS_TO_TICKS(20));
if (read_len <= 0) {
continue;
}
packet.len = std::min<size_t>(static_cast<size_t>(read_len), sizeof(packet.data));
if (xQueueSend(channel->serial_rx_queue, &packet, 0) != pdTRUE) {
SerialRxPacket dropped;
xQueueReceive(channel->serial_rx_queue, &dropped, 0);
xQueueSend(channel->serial_rx_queue, &packet, 0);
}
if (packet.len != 2 && packet.len != 3) {
continue;
}
DaliRawFrame frame;
frame.channel_index = channel->config.channel_index;
frame.gateway_id = channel->config.gateway_id;
frame.phy_kind = channel->phy_kind;
frame.data.assign(packet.data, packet.data + packet.len);
notifyRawFrameSinks(frame);
}
}
esp_err_t DaliDomainService::startRawFrameTask() { esp_err_t DaliDomainService::startRawFrameTask() {
if (raw_frame_task_handle_ != nullptr) { if (raw_frame_task_handle_ != nullptr) {
return ESP_OK; return ESP_OK;
@@ -16,6 +16,7 @@
namespace gateway { namespace gateway {
class DaliDomainService; class DaliDomainService;
struct DaliRawFrame;
class GatewayRuntime; class GatewayRuntime;
struct GatewayControllerConfig { struct GatewayControllerConfig {
@@ -112,6 +113,7 @@ class GatewayController {
void refreshRuntimeGatewayNames(); void refreshRuntimeGatewayNames();
void publishPayload(uint8_t gateway_id, const std::vector<uint8_t>& payload); void publishPayload(uint8_t gateway_id, const std::vector<uint8_t>& payload);
void publishFrame(const std::vector<uint8_t>& frame); void publishFrame(const std::vector<uint8_t>& frame);
void handleDaliRawFrame(const DaliRawFrame& frame);
uint8_t resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr); uint8_t resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr);
static uint8_t normalizeGroupTargetType(uint8_t target_type); static uint8_t normalizeGroupTargetType(uint8_t target_type);
@@ -114,6 +114,7 @@ esp_err_t GatewayController::start() {
runtime_.setCommandAddressResolver([this](uint8_t gateway_id, uint8_t raw_addr) { runtime_.setCommandAddressResolver([this](uint8_t gateway_id, uint8_t raw_addr) {
return resolveInternalGroupRawAddress(gateway_id, raw_addr); return resolveInternalGroupRawAddress(gateway_id, raw_addr);
}); });
dali_domain_.addRawFrameSink([this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
for (const auto& channel : dali_domain_.channelInfo()) { for (const auto& channel : dali_domain_.channelInfo()) {
sceneStore(channel.gateway_id); sceneStore(channel.gateway_id);
@@ -508,6 +509,31 @@ void GatewayController::publishFrame(const std::vector<uint8_t>& frame) {
} }
} }
void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) {
if (frame.data.size() != 2 && frame.data.size() != 3) {
return;
}
if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) ||
runtime_.hasActiveQueryCommand(frame.gateway_id)) {
return;
}
uint8_t addr = 0;
uint8_t data = 0;
if (frame.data.size() == 2) {
addr = frame.data[0];
data = frame.data[1];
if (addr == 0xBE) {
return;
}
} else {
addr = frame.data[1];
data = frame.data[2];
}
publishPayload(frame.gateway_id, {0x01, frame.gateway_id, addr, data});
}
uint8_t GatewayController::resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr) { uint8_t GatewayController::resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr) {
if (raw_addr < 0x80 || raw_addr > 0x9f) { if (raw_addr < 0x80 || raw_addr > 0x9f) {
return raw_addr; return raw_addr;
@@ -98,6 +98,7 @@ class GatewayRuntime {
std::optional<std::vector<uint8_t>> popNextCommand(); std::optional<std::vector<uint8_t>> popNextCommand();
void completeCurrentCommand(); void completeCurrentCommand();
bool hasPendingQueryCommand(const std::vector<uint8_t>& command) const; bool hasPendingQueryCommand(const std::vector<uint8_t>& command) const;
bool hasActiveQueryCommand(uint8_t gateway_id) const;
CommandDropReason lastEnqueueDropReason() const; CommandDropReason lastEnqueueDropReason() const;
void setGatewayCount(size_t gateway_count); void setGatewayCount(size_t gateway_count);
@@ -334,6 +334,12 @@ bool GatewayRuntime::hasPendingQueryCommand(const std::vector<uint8_t>& command)
}); });
} }
bool GatewayRuntime::hasActiveQueryCommand(uint8_t gateway_id) const {
LockGuard guard(command_lock_);
return current_command_.has_value() && isQueryCommand(*current_command_) &&
current_command_->size() > 2 && (*current_command_)[2] == gateway_id;
}
GatewayRuntime::CommandDropReason GatewayRuntime::lastEnqueueDropReason() const { GatewayRuntime::CommandDropReason GatewayRuntime::lastEnqueueDropReason() const {
LockGuard guard(command_lock_); LockGuard guard(command_lock_);
return last_enqueue_drop_reason_; return last_enqueue_drop_reason_;