#include "dali_domain.hpp" #include "dali.h" #include "dali_hal.h" #include "dali.hpp" #include "driver/uart.h" #include "esp_log.h" #include #include namespace gateway { namespace { constexpr const char* kTag = "dali_domain"; bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) { if (data == nullptr || len != 3) { return false; } if (data[0] == 0x00) { return true; } Dali_msg_t tx = dali_msg_new(data[1], data[2]); tx.id = bus_id; switch (data[0]) { case 0x10: dali_send(&tx); return true; case 0x11: dali_send_double(&tx); return true; default: return false; } } std::vector TransactHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) { if (data == nullptr || len != 3) { return {}; } switch (data[0]) { case 0x00: return {0xFF}; case 0x01: return {1}; case 0x10: case 0x11: return SendHardwareFrame(bus_id, data, len) ? std::vector{0xFF} : std::vector{0xFD}; case 0x12: { Dali_msg_t tx = dali_msg_new(data[1], data[2]); tx.id = bus_id; Dali_msg_t rx = {}; if (dali_query(&tx, &rx) == pdTRUE) { return {0xFF, rx.data[0]}; } return {0xFE}; } default: return {}; } } bool WriteSerialFrame(int uart_port, const uint8_t* data, size_t len) { if (data == nullptr || len == 0) { return false; } return uart_write_bytes(static_cast(uart_port), data, len) == static_cast(len); } std::vector ReadSerialFrame(int uart_port, size_t len, uint32_t timeout_ms) { std::vector data(len, 0); const int read_len = uart_read_bytes(static_cast(uart_port), data.data(), len, pdMS_TO_TICKS(timeout_ms)); if (read_len <= 0) { return {}; } data.resize(static_cast(read_len)); return data; } std::vector TransactSerialFrame(int uart_port, uint32_t query_timeout_ms, const uint8_t* data, size_t len) { if (!WriteSerialFrame(uart_port, data, len)) { return {0xFD}; } if (data == nullptr || len == 0 || data[0] != 0x12) { return {0xFF}; } const auto response = ReadSerialFrame(uart_port, 2, query_timeout_ms); if (response.empty()) { return {0xFE}; } return response; } } // namespace struct DaliDomainService::DaliChannel { DaliChannelConfig config; DaliPhyKind phy_kind{DaliPhyKind::kCustom}; DaliTransportHooks hooks; std::unique_ptr comm; std::unique_ptr dali; std::optional hardware_bus; std::optional serial_bus; }; DaliDomainService::DaliDomainService() = default; DaliDomainService::~DaliDomainService() = default; bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) { if (!hooks.send) { return false; } auto channel = std::make_unique(); channel->config = config; channel->hooks = std::move(hooks); channel->comm = std::make_unique(channel->hooks.send, channel->hooks.read, channel->hooks.transact, channel->hooks.delay); channel->dali = std::make_unique(*channel->comm, config.gateway_id, config.name); auto* existing = findChannelByIndex(config.channel_index); if (existing != nullptr) { *existing = std::move(*channel); return true; } channels_.push_back(std::move(channel)); return true; } esp_err_t DaliDomainService::bindHardwareBus(const DaliHardwareBusConfig& config) { esp_err_t err = dali_hal_set_baudrate(config.baudrate); if (err != ESP_OK) { ESP_LOGE(kTag, "failed to set baudrate=%lu: %s", static_cast(config.baudrate), esp_err_to_name(err)); return err; } err = dali_hal_init(config.bus_id, config.tx_pin, config.rx_pin); if (err != ESP_OK) { ESP_LOGE(kTag, "failed to init bus=%u tx=%u rx=%u: %s", config.bus_id, config.tx_pin, config.rx_pin, esp_err_to_name(err)); return err; } DaliTransportHooks hooks; hooks.send = [bus_id = config.bus_id](const uint8_t* data, size_t len) { return SendHardwareFrame(bus_id, data, len); }; hooks.transact = [bus_id = config.bus_id](const uint8_t* data, size_t len) { return TransactHardwareFrame(bus_id, data, len); }; hooks.delay = [](uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }; const DaliChannelConfig channel_config{config.channel_index, config.gateway_id, config.name}; if (!bindTransport(channel_config, std::move(hooks))) { return ESP_ERR_INVALID_STATE; } auto* channel = findChannelByIndex(config.channel_index); if (channel != nullptr) { channel->phy_kind = DaliPhyKind::kNativeHardware; channel->hardware_bus = config; } ESP_LOGI(kTag, "bound channel=%u gateway=%u hardware bus=%u tx=%u rx=%u baudrate=%lu", config.channel_index, config.gateway_id, config.bus_id, config.tx_pin, config.rx_pin, static_cast(config.baudrate)); return ESP_OK; } esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) { if (config.uart_port < UART_NUM_0 || config.uart_port >= UART_NUM_MAX) { return ESP_ERR_INVALID_ARG; } if (hasSerialPort(config.uart_port)) { ESP_LOGE(kTag, "uart%d is already assigned to another DALI channel", config.uart_port); return ESP_ERR_INVALID_STATE; } uart_config_t uart_config = {}; uart_config.baud_rate = static_cast(config.baudrate); uart_config.data_bits = UART_DATA_8_BITS; uart_config.parity = UART_PARITY_DISABLE; uart_config.stop_bits = UART_STOP_BITS_1; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; uart_config.rx_flow_ctrl_thresh = 0; uart_config.source_clk = UART_SCLK_DEFAULT; auto uart = static_cast(config.uart_port); esp_err_t err = uart_param_config(uart, &uart_config); if (err != ESP_OK) { ESP_LOGE(kTag, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err)); return err; } err = uart_set_pin(uart, config.tx_pin, config.rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); if (err != ESP_OK) { ESP_LOGE(kTag, "failed to set uart%d pins tx=%d rx=%d: %s", config.uart_port, config.tx_pin, config.rx_pin, esp_err_to_name(err)); return err; } err = uart_driver_install(uart, config.rx_buffer_size, config.tx_buffer_size, 0, nullptr, 0); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { ESP_LOGE(kTag, "failed to install uart%d driver: %s", config.uart_port, esp_err_to_name(err)); return err; } uart_flush_input(uart); DaliTransportHooks hooks; hooks.send = [uart_port = config.uart_port](const uint8_t* data, size_t len) { return WriteSerialFrame(uart_port, data, len); }; hooks.read = [uart_port = config.uart_port](size_t len, uint32_t timeout_ms) { return ReadSerialFrame(uart_port, len, timeout_ms); }; hooks.transact = [uart_port = config.uart_port, timeout_ms = config.query_timeout_ms]( const uint8_t* data, size_t len) { return TransactSerialFrame(uart_port, timeout_ms, data, len); }; hooks.delay = [](uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); }; const DaliChannelConfig channel_config{config.channel_index, config.gateway_id, config.name}; if (!bindTransport(channel_config, std::move(hooks))) { return ESP_ERR_INVALID_STATE; } auto* channel = findChannelByIndex(config.channel_index); if (channel != nullptr) { channel->phy_kind = DaliPhyKind::kSerialUart; channel->serial_bus = config; } 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, static_cast(config.baudrate)); return ESP_OK; } bool DaliDomainService::isBound() const { return !channels_.empty(); } bool DaliDomainService::isHardwareBound(uint8_t gateway_id) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->hardware_bus.has_value(); } const char* DaliDomainService::implementationName() const { if (channels_.empty()) { return "dali_cpp"; } const bool has_hardware = std::any_of(channels_.begin(), channels_.end(), [](const auto& channel) { return channel->phy_kind == DaliPhyKind::kNativeHardware; }); const bool has_serial = std::any_of(channels_.begin(), channels_.end(), [](const auto& channel) { return channel->phy_kind == DaliPhyKind::kSerialUart; }); if (has_hardware && has_serial) { return "dali_cpp+mixed_phy"; } if (has_hardware) { return "dali_cpp+dali_hal"; } if (has_serial) { return "dali_cpp+uart"; } return "dali_cpp"; } size_t DaliDomainService::channelCount() const { return channels_.size(); } std::vector DaliDomainService::channelInfo() const { std::vector info; info.reserve(channels_.size()); for (const auto& channel : channels_) { info.push_back(DaliChannelInfo{channel->config.channel_index, channel->config.gateway_id, channel->phy_kind, channel->config.name}); } return info; } bool DaliDomainService::resetBus(uint8_t gateway_id) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->comm != nullptr && channel->comm->resetBus(); } bool DaliDomainService::sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->comm != nullptr && channel->comm->sendRawNew(raw_addr, command); } bool DaliDomainService::sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->comm != nullptr && channel->comm->sendExtRawNew(raw_addr, command); } std::optional DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const { const auto* channel = findChannelByGateway(gateway_id); if (channel == nullptr || channel->comm == nullptr) { return std::nullopt; } return channel->comm->queryRawNew(raw_addr, command); } bool DaliDomainService::setBright(uint8_t gateway_id, int short_address, int brightness) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->dali != nullptr && channel->dali->base.setBright(short_address, brightness); } bool DaliDomainService::setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->dali != nullptr && channel->dali->dt8.setColTempRaw(short_address, mirek); } bool DaliDomainService::setColTemp(uint8_t gateway_id, int short_address, int kelvin) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->dali != nullptr && channel->dali->dt8.setColorTemperature(short_address, kelvin); } bool DaliDomainService::setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->dali != nullptr && channel->dali->dt8.setColourRaw(raw_addr, x, y); } bool DaliDomainService::setColourRGB(uint8_t gateway_id, int short_address, int r, int g, int b) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->dali != nullptr && channel->dali->dt8.setColourRGB(short_address, r, g, b); } bool DaliDomainService::on(uint8_t gateway_id, int short_address) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->dali != nullptr && channel->dali->base.on(short_address); } bool DaliDomainService::off(uint8_t gateway_id, int short_address) const { const auto* channel = findChannelByGateway(gateway_id); return channel != nullptr && channel->dali != nullptr && channel->dali->base.off(short_address); } bool DaliDomainService::off(int short_address) const { if (channels_.empty()) { return false; } return off(channels_.front()->config.gateway_id, short_address); } DaliDomainService::DaliChannel* DaliDomainService::findChannelByGateway(uint8_t gateway_id) { const auto it = std::find_if(channels_.begin(), channels_.end(), [gateway_id](const auto& channel) { return channel->config.gateway_id == gateway_id; }); return it == channels_.end() ? nullptr : it->get(); } const DaliDomainService::DaliChannel* DaliDomainService::findChannelByGateway( uint8_t gateway_id) const { const auto it = std::find_if(channels_.begin(), channels_.end(), [gateway_id](const auto& channel) { return channel->config.gateway_id == gateway_id; }); return it == channels_.end() ? nullptr : it->get(); } DaliDomainService::DaliChannel* DaliDomainService::findChannelByIndex(uint8_t channel_index) { const auto it = std::find_if(channels_.begin(), channels_.end(), [channel_index](const auto& channel) { return channel->config.channel_index == channel_index; }); return it == channels_.end() ? nullptr : it->get(); } bool DaliDomainService::hasSerialPort(int uart_port) const { return std::any_of(channels_.begin(), channels_.end(), [uart_port](const auto& channel) { return channel->serial_bus.has_value() && channel->serial_bus->uart_port == uart_port; }); } } // namespace gateway