Initial commit
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/dali_domain.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_cpp dali esp_driver_uart freertos log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
class Dali;
|
||||
class DaliComm;
|
||||
|
||||
namespace gateway {
|
||||
|
||||
struct DaliTransportHooks {
|
||||
std::function<bool(const uint8_t* data, size_t len)> send;
|
||||
std::function<std::vector<uint8_t>(size_t len, uint32_t timeout_ms)> read;
|
||||
std::function<std::vector<uint8_t>(const uint8_t* data, size_t len)> transact;
|
||||
std::function<void(uint32_t ms)> delay;
|
||||
};
|
||||
|
||||
struct DaliHardwareBusConfig {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
uint8_t bus_id{0};
|
||||
uint8_t tx_pin{0};
|
||||
uint8_t rx_pin{0};
|
||||
uint32_t baudrate{1200};
|
||||
std::string name{"gateway"};
|
||||
};
|
||||
|
||||
struct DaliSerialBusConfig {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
int uart_port{1};
|
||||
int tx_pin{0};
|
||||
int rx_pin{1};
|
||||
uint32_t baudrate{9600};
|
||||
size_t rx_buffer_size{256};
|
||||
size_t tx_buffer_size{256};
|
||||
uint32_t query_timeout_ms{500};
|
||||
std::string name{"gateway"};
|
||||
};
|
||||
|
||||
struct DaliChannelConfig {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
std::string name{"gateway"};
|
||||
};
|
||||
|
||||
enum class DaliPhyKind {
|
||||
kCustom,
|
||||
kNativeHardware,
|
||||
kSerialUart,
|
||||
};
|
||||
|
||||
struct DaliChannelInfo {
|
||||
uint8_t channel_index{0};
|
||||
uint8_t gateway_id{0};
|
||||
DaliPhyKind phy_kind{DaliPhyKind::kCustom};
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class DaliDomainService {
|
||||
public:
|
||||
DaliDomainService();
|
||||
~DaliDomainService();
|
||||
|
||||
bool bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks);
|
||||
esp_err_t bindHardwareBus(const DaliHardwareBusConfig& config);
|
||||
esp_err_t bindSerialBus(const DaliSerialBusConfig& config);
|
||||
bool isBound() const;
|
||||
bool isHardwareBound(uint8_t gateway_id) const;
|
||||
const char* implementationName() const;
|
||||
size_t channelCount() const;
|
||||
std::vector<DaliChannelInfo> channelInfo() const;
|
||||
|
||||
bool resetBus(uint8_t gateway_id) const;
|
||||
bool sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
bool sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
std::optional<uint8_t> queryRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
bool setBright(uint8_t gateway_id, int short_address, int brightness) const;
|
||||
bool setColTempRaw(uint8_t gateway_id, int short_address, int mirek) const;
|
||||
bool setColTemp(uint8_t gateway_id, int short_address, int kelvin) const;
|
||||
bool setColourRaw(uint8_t gateway_id, int raw_addr, int x, int y) const;
|
||||
bool setColourRGB(uint8_t gateway_id, int short_address, int r, int g, int b) const;
|
||||
bool on(uint8_t gateway_id, int short_address) const;
|
||||
bool off(uint8_t gateway_id, int short_address) const;
|
||||
bool off(int short_address) const;
|
||||
|
||||
private:
|
||||
struct DaliChannel;
|
||||
|
||||
DaliChannel* findChannelByGateway(uint8_t gateway_id);
|
||||
const DaliChannel* findChannelByGateway(uint8_t gateway_id) const;
|
||||
DaliChannel* findChannelByIndex(uint8_t channel_index);
|
||||
bool hasSerialPort(int uart_port) const;
|
||||
|
||||
std::vector<std::unique_ptr<DaliChannel>> channels_;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,395 @@
|
||||
#include "dali_domain.hpp"
|
||||
|
||||
#include "dali.h"
|
||||
#include "dali_hal.h"
|
||||
#include "dali.hpp"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
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<uint8_t> 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<uint8_t>{0xFF}
|
||||
: std::vector<uint8_t>{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_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> data(len, 0);
|
||||
const int read_len = uart_read_bytes(static_cast<uart_port_t>(uart_port), data.data(), 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,
|
||||
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<DaliComm> comm;
|
||||
std::unique_ptr<Dali> dali;
|
||||
std::optional<DaliHardwareBusConfig> hardware_bus;
|
||||
std::optional<DaliSerialBusConfig> 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<DaliChannel>();
|
||||
channel->config = config;
|
||||
channel->hooks = std::move(hooks);
|
||||
channel->comm = std::make_unique<DaliComm>(channel->hooks.send, channel->hooks.read,
|
||||
channel->hooks.transact, channel->hooks.delay);
|
||||
channel->dali = std::make_unique<Dali>(*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<unsigned long>(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<unsigned long>(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<int>(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<uart_port_t>(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<unsigned long>(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<DaliChannelInfo> DaliDomainService::channelInfo() const {
|
||||
std::vector<DaliChannelInfo> 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<uint8_t> 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
|
||||
Reference in New Issue
Block a user