7424b43bdd
- Introduced new enum value `kShortDiagnosticBit` to `GatewayModbusGeneratedKind`. - Enhanced `GatewayModbusPoint` and `GatewayModbusPointBinding` structures to include diagnostic snapshot, boolean key, and device type. - Added new diagnostic bit specifications and updated the corresponding arrays for generated discrete inputs and holding registers. - Implemented `addGeneratedDiagnosticPoint` function to handle the creation of diagnostic points. - Updated `rebuildMap` method to include generated diagnostic points during the map rebuilding process. Co-authored-by: Copilot <copilot@github.com>
1264 lines
52 KiB
C++
1264 lines
52 KiB
C++
#include "dali_domain.hpp"
|
|
|
|
#include "dali.h"
|
|
#include "dali_hal.h"
|
|
#include "dali.hpp"
|
|
#include "driver/uart.h"
|
|
#include "esp_log.h"
|
|
#include "freertos/queue.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
namespace gateway {
|
|
|
|
namespace {
|
|
|
|
constexpr const char* kTag = "dali_domain";
|
|
constexpr size_t kSerialRxPacketMaxBytes = 8;
|
|
constexpr UBaseType_t kSerialRxQueueDepth = 8;
|
|
|
|
DaliDomainSnapshot MakeSnapshot(uint8_t gateway_id, int address, const char* kind) {
|
|
DaliDomainSnapshot snapshot;
|
|
snapshot.gateway_id = gateway_id;
|
|
snapshot.address = address;
|
|
snapshot.kind = kind == nullptr ? "" : kind;
|
|
return snapshot;
|
|
}
|
|
|
|
template <typename T>
|
|
void PutOptionalInt(DaliDomainSnapshot& snapshot, const char* name, const std::optional<T>& value) {
|
|
if (value.has_value()) {
|
|
snapshot.ints[name] = static_cast<int>(value.value());
|
|
}
|
|
}
|
|
|
|
void PutOptionalBool(DaliDomainSnapshot& snapshot, const char* name,
|
|
const std::optional<bool>& value) {
|
|
if (value.has_value()) {
|
|
snapshot.bools[name] = value.value();
|
|
}
|
|
}
|
|
|
|
void PutOptionalNumber(DaliDomainSnapshot& snapshot, const char* name,
|
|
const std::optional<double>& value) {
|
|
if (value.has_value()) {
|
|
snapshot.numbers[name] = value.value();
|
|
}
|
|
}
|
|
|
|
Dt8SceneStoreColorMode ToDaliCppColorMode(DaliDt8SceneColorMode color_mode) {
|
|
switch (color_mode) {
|
|
case DaliDt8SceneColorMode::kColorTemperature:
|
|
return Dt8SceneStoreColorMode::colorTemperature;
|
|
case DaliDt8SceneColorMode::kRgb:
|
|
return Dt8SceneStoreColorMode::rgb;
|
|
case DaliDt8SceneColorMode::kDisabled:
|
|
default:
|
|
return Dt8SceneStoreColorMode::disabled;
|
|
}
|
|
}
|
|
|
|
struct SerialRxPacket {
|
|
size_t len{0};
|
|
uint8_t data[kSerialRxPacketMaxBytes]{};
|
|
};
|
|
|
|
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> PacketToVector(const SerialRxPacket& packet, size_t len = 0) {
|
|
const size_t out_len = len == 0 ? packet.len : std::min(packet.len, len);
|
|
return std::vector<uint8_t>(packet.data, packet.data + out_len);
|
|
}
|
|
|
|
void DrainSerialQueue(QueueHandle_t queue) {
|
|
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)) {
|
|
return {0xFD};
|
|
}
|
|
if (data == nullptr || len == 0 || data[0] != 0x12) {
|
|
return {0xFF};
|
|
}
|
|
|
|
const TickType_t timeout_ticks = pdMS_TO_TICKS(query_timeout_ms);
|
|
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 {0xFE};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
struct DaliDomainService::DaliChannel {
|
|
DaliDomainService* owner{nullptr};
|
|
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;
|
|
QueueHandle_t serial_rx_queue{nullptr};
|
|
TaskHandle_t serial_rx_task_handle{nullptr};
|
|
};
|
|
|
|
DaliDomainService::DaliDomainService()
|
|
: raw_frame_sink_lock_(xSemaphoreCreateMutex()) {}
|
|
|
|
DaliDomainService::~DaliDomainService() = default;
|
|
|
|
bool DaliDomainService::bindTransport(const DaliChannelConfig& config, DaliTransportHooks hooks) {
|
|
if (!hooks.send) {
|
|
return false;
|
|
}
|
|
|
|
auto channel = std::make_unique<DaliChannel>();
|
|
channel->owner = this;
|
|
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;
|
|
}
|
|
err = startRawFrameTask();
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(kTag, "failed to start raw frame task: %s", esp_err_to_name(err));
|
|
}
|
|
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);
|
|
|
|
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;
|
|
hooks.send = [uart_port = config.uart_port](const uint8_t* data, size_t len) {
|
|
return WriteSerialFrame(uart_port, data, len);
|
|
};
|
|
hooks.read = [serial_rx_queue](size_t len, uint32_t timeout_ms) {
|
|
return ReadSerialFrame(serial_rx_queue, len, timeout_ms);
|
|
};
|
|
hooks.transact = [uart_port = config.uart_port, serial_rx_queue,
|
|
timeout_ms = config.query_timeout_ms](const uint8_t* data, size_t len) {
|
|
return TransactSerialFrame(uart_port, serial_rx_queue, 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))) {
|
|
vQueueDelete(serial_rx_queue);
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
auto* channel = findChannelByIndex(config.channel_index);
|
|
if (channel != nullptr) {
|
|
channel->phy_kind = DaliPhyKind::kSerialUart;
|
|
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",
|
|
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;
|
|
}
|
|
|
|
void DaliDomainService::addRawFrameSink(std::function<void(const DaliRawFrame& frame)> sink) {
|
|
if (!sink) {
|
|
return;
|
|
}
|
|
if (raw_frame_sink_lock_ != nullptr) {
|
|
xSemaphoreTake(raw_frame_sink_lock_, portMAX_DELAY);
|
|
}
|
|
raw_frame_sinks_.push_back(std::move(sink));
|
|
if (raw_frame_sink_lock_ != nullptr) {
|
|
xSemaphoreGive(raw_frame_sink_lock_);
|
|
}
|
|
}
|
|
|
|
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::writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->hooks.send && channel->hooks.send(data, len);
|
|
}
|
|
|
|
std::vector<uint8_t> DaliDomainService::transactBridgeFrame(uint8_t gateway_id,
|
|
const uint8_t* data,
|
|
size_t len) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || !channel->hooks.transact) {
|
|
return {};
|
|
}
|
|
return channel->hooks.transact(data, len);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::discoverDeviceTypes(
|
|
uint8_t gateway_id, int short_address, const std::vector<int>& fallback_types,
|
|
int max_next_types) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
const std::vector<int> fallback = fallback_types.empty() ? std::vector<int>{1, 4, 5, 6, 8}
|
|
: fallback_types;
|
|
auto discovery = channel->dali->base.discoverDeviceTypes(short_address, fallback,
|
|
max_next_types);
|
|
if (!discovery.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "device");
|
|
PutOptionalInt(snapshot, "rawQueryType", discovery->rawQueryType);
|
|
PutOptionalInt(snapshot, "primaryType", discovery->primaryType());
|
|
snapshot.int_arrays["types"] = discovery->types;
|
|
snapshot.int_arrays["extraTypes"] = discovery->extraTypes();
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::baseStatusSnapshot(
|
|
uint8_t gateway_id, int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
const auto raw_status = channel->dali->base.getStatus(short_address);
|
|
if (!raw_status.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
const DaliStatus status = DaliStatus::fromByte(static_cast<uint8_t>(raw_status.value()));
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "base_status");
|
|
snapshot.ints["rawStatus"] = raw_status.value() & 0xFF;
|
|
snapshot.bools["controlGearPresent"] = status.controlGearPresent;
|
|
snapshot.bools["lampFailure"] = status.lampFailure;
|
|
snapshot.bools["lampPowerOn"] = status.lampPowerOn;
|
|
snapshot.bools["limitError"] = status.limitError;
|
|
snapshot.bools["fadingCompleted"] = status.fadingCompleted;
|
|
snapshot.bools["resetState"] = status.resetState;
|
|
snapshot.bools["missingShortAddress"] = status.missingShortAddress;
|
|
snapshot.bools["powerSupplyFault"] = status.psFault;
|
|
snapshot.bools["psFault"] = status.psFault;
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt1Snapshot(uint8_t gateway_id,
|
|
int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
const auto detailed = channel->dali->dt1.getDT1TestStatusDetailed(short_address);
|
|
if (!detailed.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt1");
|
|
PutOptionalInt(snapshot, "failureStatusRaw", detailed->failureStatus);
|
|
PutOptionalInt(snapshot, "emergencyStatusRaw", detailed->emergencyStatus);
|
|
PutOptionalInt(snapshot, "emergencyModeRaw", detailed->emergencyMode);
|
|
PutOptionalInt(snapshot, "featuresRaw", detailed->feature);
|
|
PutOptionalInt(snapshot, "deviceStatusRaw", detailed->deviceStatus);
|
|
|
|
snapshot.bools["testInProgress"] = detailed->testInProgress;
|
|
snapshot.bools["lampFailure"] = detailed->lampFailure;
|
|
snapshot.bools["batteryFailure"] = detailed->batteryFailure;
|
|
snapshot.bools["functionTestActive"] = detailed->functionTestActive;
|
|
snapshot.bools["durationTestActive"] = detailed->durationTestActive;
|
|
snapshot.bools["testDone"] = detailed->testDone;
|
|
snapshot.bools["identifyActive"] = detailed->identifyActive;
|
|
snapshot.bools["physicalSelectionActive"] = detailed->physicalSelectionActive;
|
|
snapshot.bools["circuitFailure"] = detailed->circuitFailure;
|
|
snapshot.bools["batteryDurationFailure"] = detailed->batteryDurationFailure;
|
|
snapshot.bools["emergencyLampFailure"] = detailed->emergencyLampFailure;
|
|
snapshot.bools["functionTestMaxDelayExceeded"] = detailed->functionTestMaxDelayExceeded;
|
|
snapshot.bools["durationTestMaxDelayExceeded"] = detailed->durationTestMaxDelayExceeded;
|
|
snapshot.bools["functionTestFailed"] = detailed->functionTestFailed;
|
|
snapshot.bools["durationTestFailed"] = detailed->durationTestFailed;
|
|
snapshot.bools["functionTestResultValid"] = detailed->functionTestResultValid;
|
|
snapshot.bools["durationTestResultValid"] = detailed->durationTestResultValid;
|
|
snapshot.bools["batteryFullyCharged"] = detailed->batteryFullyCharged;
|
|
snapshot.bools["functionTestPending"] = detailed->functionTestPending;
|
|
snapshot.bools["durationTestPending"] = detailed->durationTestPending;
|
|
snapshot.bools["restModeActive"] = detailed->restModeActive;
|
|
snapshot.bools["normalModeActive"] = detailed->normalModeActive;
|
|
snapshot.bools["emergencyModeActive"] = detailed->emergencyModeActive;
|
|
snapshot.bools["extendedEmergencyModeActive"] = detailed->extendedEmergencyModeActive;
|
|
snapshot.bools["hardwiredInhibitActive"] = detailed->hardwiredInhibitActive;
|
|
snapshot.bools["hardwiredSwitchOn"] = detailed->hardwiredSwitchOn;
|
|
snapshot.bools["supportsAutoTest"] = detailed->supportsAutoTest;
|
|
snapshot.bools["supportsAdjustableEmergencyLevel"] = detailed->supportsAdjustableEmergencyLevel;
|
|
|
|
if (detailed->emergencyStatus.has_value()) {
|
|
const DaliDT1EmergencyStatus status(detailed->emergencyStatus.value());
|
|
snapshot.bools["inhibitMode"] = status.inhibitMode();
|
|
snapshot.bools["functionTestRequestPending"] = status.functionTestRequestPending();
|
|
snapshot.bools["durationTestRequestPending"] = status.durationTestRequestPending();
|
|
snapshot.bools["identificationActive"] = status.identificationActive();
|
|
snapshot.bools["physicallySelected"] = status.physicallySelected();
|
|
}
|
|
if (detailed->emergencyMode.has_value()) {
|
|
const DaliDT1EmergencyMode mode(detailed->emergencyMode.value());
|
|
snapshot.bools["functionTestInProgress"] = mode.functionTestInProgress();
|
|
snapshot.bools["durationTestInProgress"] = mode.durationTestInProgress();
|
|
}
|
|
if (detailed->feature.has_value()) {
|
|
const DaliDT1Features features(detailed->feature.value());
|
|
snapshot.bools["integralEmergencyControlGear"] = features.integralEmergencyControlGear();
|
|
snapshot.bools["maintainedControlGear"] = features.maintainedControlGear();
|
|
snapshot.bools["switchedMaintainedControlGear"] = features.switchedMaintainedControlGear();
|
|
snapshot.bools["autoTestCapability"] = features.autoTestCapability();
|
|
snapshot.bools["adjustableEmergencyLevel"] = features.adjustableEmergencyLevel();
|
|
snapshot.bools["hardwiredInhibitSupported"] = features.hardwiredInhibitSupported();
|
|
snapshot.bools["physicalSelectionSupported"] = features.physicalSelectionSupported();
|
|
snapshot.bools["relightInRestModeSupported"] = features.relightInRestModeSupported();
|
|
}
|
|
if (detailed->deviceStatus.has_value()) {
|
|
const DaliDT1DeviceStatus status(detailed->deviceStatus.value());
|
|
snapshot.bools["controlGearFailure"] = status.controlGearFailure();
|
|
snapshot.bools["controlGearOk"] = status.controlGearOk();
|
|
snapshot.bools["lampPoweredByEmergencyGear"] = status.lampPoweredByEmergencyGear();
|
|
snapshot.bools["deviceResetState"] = status.resetState();
|
|
snapshot.bools["deviceMissingShortAddress"] = status.missingShortAddress();
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt4Snapshot(uint8_t gateway_id,
|
|
int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt4");
|
|
auto& dt4 = channel->dali->dt4;
|
|
PutOptionalInt(snapshot, "extendedVersion", dt4.getExtendedVersion(short_address));
|
|
PutOptionalInt(snapshot, "dimmingCurve", dt4.getDimmingCurve(short_address));
|
|
PutOptionalInt(snapshot, "dimmerTemperatureRaw", dt4.getDimmerTemperatureRaw(short_address));
|
|
PutOptionalInt(snapshot, "rmsSupplyVoltageRaw", dt4.getRmsSupplyVoltageRaw(short_address));
|
|
PutOptionalInt(snapshot, "supplyFrequencyRaw", dt4.getSupplyFrequencyRaw(short_address));
|
|
PutOptionalInt(snapshot, "rmsLoadVoltageRaw", dt4.getRmsLoadVoltageRaw(short_address));
|
|
PutOptionalInt(snapshot, "rmsLoadCurrentRaw", dt4.getRmsLoadCurrentRaw(short_address));
|
|
PutOptionalInt(snapshot, "realLoadPowerRaw", dt4.getRealLoadPowerRaw(short_address));
|
|
PutOptionalInt(snapshot, "loadRatingRaw", dt4.getLoadRatingRaw(short_address));
|
|
PutOptionalNumber(snapshot, "rmsSupplyVoltageVolts", dt4.getRmsSupplyVoltageVolts(short_address));
|
|
PutOptionalNumber(snapshot, "supplyFrequencyHertz", dt4.getSupplyFrequencyHertz(short_address));
|
|
PutOptionalNumber(snapshot, "rmsLoadVoltageVolts", dt4.getRmsLoadVoltageVolts(short_address));
|
|
PutOptionalNumber(snapshot, "rmsLoadCurrentPercent", dt4.getRmsLoadCurrentPercent(short_address));
|
|
PutOptionalNumber(snapshot, "realLoadPowerWatts", dt4.getRealLoadPowerWatts(short_address));
|
|
PutOptionalNumber(snapshot, "loadRatingAmps", dt4.getLoadRatingAmps(short_address));
|
|
PutOptionalBool(snapshot, "referenceRunning", dt4.isReferenceRunning(short_address));
|
|
PutOptionalBool(snapshot, "referenceMeasurementFailed",
|
|
dt4.isReferenceMeasurementFailed(short_address));
|
|
|
|
if (const auto status = dt4.getDimmerStatus(short_address)) {
|
|
snapshot.ints["dimmerStatusRaw"] = status->raw();
|
|
snapshot.bools["leadingEdgeModeRunning"] = status->leadingEdgeModeRunning();
|
|
snapshot.bools["trailingEdgeModeRunning"] = status->trailingEdgeModeRunning();
|
|
snapshot.bools["referenceMeasurementRunning"] = status->referenceMeasurementRunning();
|
|
snapshot.bools["nonLogarithmicDimmingCurveActive"] =
|
|
status->nonLogarithmicDimmingCurveActive();
|
|
}
|
|
if (const auto features = dt4.getFeatures(short_address)) {
|
|
snapshot.ints["featuresRaw1"] = features->raw1();
|
|
snapshot.ints["featuresRaw2"] = features->raw2();
|
|
snapshot.ints["featuresRaw3"] = features->raw3();
|
|
snapshot.ints["dimmingMethodCode"] = features->dimmingMethodCode();
|
|
snapshot.bools["canQueryLoadOverCurrentShutdown"] =
|
|
features->canQueryLoadOverCurrentShutdown();
|
|
snapshot.bools["canQueryOpenCircuitDetection"] = features->canQueryOpenCircuitDetection();
|
|
snapshot.bools["canQueryLoadDecrease"] = features->canQueryLoadDecrease();
|
|
snapshot.bools["canQueryLoadIncrease"] = features->canQueryLoadIncrease();
|
|
snapshot.bools["canQueryThermalShutdown"] = features->canQueryThermalShutdown();
|
|
snapshot.bools["canQueryThermalOverloadReduction"] =
|
|
features->canQueryThermalOverloadReduction();
|
|
snapshot.bools["canQueryTemperature"] = features->canQueryTemperature();
|
|
snapshot.bools["canQuerySupplyVoltage"] = features->canQuerySupplyVoltage();
|
|
snapshot.bools["canQuerySupplyFrequency"] = features->canQuerySupplyFrequency();
|
|
snapshot.bools["canQueryLoadVoltage"] = features->canQueryLoadVoltage();
|
|
snapshot.bools["canQueryLoadCurrent"] = features->canQueryLoadCurrent();
|
|
snapshot.bools["canQueryRealLoadPower"] = features->canQueryRealLoadPower();
|
|
snapshot.bools["canQueryLoadRating"] = features->canQueryLoadRating();
|
|
snapshot.bools["canQueryCurrentOverloadReduction"] =
|
|
features->canQueryCurrentOverloadReduction();
|
|
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
|
|
snapshot.bools["canSelectNonLogarithmicDimmingCurve"] =
|
|
features->canSelectNonLogarithmicDimmingCurve();
|
|
snapshot.bools["canQueryUnsuitableLoad"] = features->canQueryUnsuitableLoad();
|
|
}
|
|
if (const auto failure = dt4.getFailureStatus(short_address)) {
|
|
snapshot.ints["failureRaw1"] = failure->raw1();
|
|
snapshot.ints["failureRaw2"] = failure->raw2();
|
|
snapshot.bools["loadOverCurrentShutdown"] = failure->loadOverCurrentShutdown();
|
|
snapshot.bools["openCircuitDetected"] = failure->openCircuitDetected();
|
|
snapshot.bools["loadDecreaseDetected"] = failure->loadDecreaseDetected();
|
|
snapshot.bools["loadIncreaseDetected"] = failure->loadIncreaseDetected();
|
|
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
|
|
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
|
|
snapshot.bools["referenceMeasurementFailedStatus"] = failure->referenceMeasurementFailed();
|
|
snapshot.bools["loadUnsuitableForSelectedMethod"] =
|
|
failure->loadUnsuitableForSelectedMethod();
|
|
snapshot.bools["supplyVoltageOutOfLimits"] = failure->supplyVoltageOutOfLimits();
|
|
snapshot.bools["supplyFrequencyOutOfLimits"] = failure->supplyFrequencyOutOfLimits();
|
|
snapshot.bools["loadVoltageOutOfLimits"] = failure->loadVoltageOutOfLimits();
|
|
snapshot.bools["loadCurrentOverloadReduction"] = failure->loadCurrentOverloadReduction();
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt5Snapshot(uint8_t gateway_id,
|
|
int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt5");
|
|
auto& dt5 = channel->dali->dt5;
|
|
PutOptionalInt(snapshot, "extendedVersion", dt5.getExtendedVersion(short_address));
|
|
PutOptionalInt(snapshot, "dimmingCurve", dt5.getDimmingCurve(short_address));
|
|
PutOptionalInt(snapshot, "outputLevelRaw", dt5.getOutputLevelRaw(short_address));
|
|
PutOptionalNumber(snapshot, "outputLevelVolts", dt5.getOutputLevelVolts(short_address));
|
|
if (const auto features = dt5.getConverterFeatures(short_address)) {
|
|
snapshot.ints["featuresRaw"] = features->raw();
|
|
snapshot.bools["outputRange0To10VSelectable"] = features->outputRange0To10VSelectable();
|
|
snapshot.bools["internalPullUpSelectable"] = features->internalPullUpSelectable();
|
|
snapshot.bools["outputFaultDetectionSelectable"] =
|
|
features->outputFaultDetectionSelectable();
|
|
snapshot.bools["mainsRelay"] = features->mainsRelay();
|
|
snapshot.bools["outputLevelQueryable"] = features->outputLevelQueryable();
|
|
snapshot.bools["nonLogarithmicDimmingCurveSupported"] =
|
|
features->nonLogarithmicDimmingCurveSupported();
|
|
snapshot.bools["physicalSelectionByOutputLossSupported"] =
|
|
features->physicalSelectionByOutputLossSupported();
|
|
snapshot.bools["physicalSelectionSwitchSupported"] =
|
|
features->physicalSelectionSwitchSupported();
|
|
}
|
|
if (const auto failure = dt5.getFailureStatus(short_address)) {
|
|
snapshot.ints["failureRaw"] = failure->raw();
|
|
snapshot.bools["outputFaultDetected"] = failure->outputFaultDetected();
|
|
}
|
|
if (const auto status = dt5.getConverterStatus(short_address)) {
|
|
snapshot.ints["converterStatusRaw"] = status->raw();
|
|
snapshot.bools["zeroToTenVoltOperation"] = status->zeroToTenVoltOperation();
|
|
snapshot.bools["internalPullUpOn"] = status->internalPullUpOn();
|
|
snapshot.bools["nonLogarithmicDimmingCurveActive"] =
|
|
status->nonLogarithmicDimmingCurveActive();
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt6Snapshot(uint8_t gateway_id,
|
|
int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt6");
|
|
auto& dt6 = channel->dali->dt6;
|
|
PutOptionalInt(snapshot, "extendedVersion", dt6.getExtendedVersion(short_address));
|
|
PutOptionalInt(snapshot, "dimmingCurve", dt6.getDimmingCurve(short_address));
|
|
PutOptionalInt(snapshot, "fastFadeTime", dt6.getFastFadeTime(short_address));
|
|
PutOptionalInt(snapshot, "minFastFadeTime", dt6.getMinFastFadeTime(short_address));
|
|
PutOptionalBool(snapshot, "currentProtectorEnabled",
|
|
dt6.isCurrentProtectorEnabled(short_address));
|
|
if (const auto gear = dt6.getGearType(short_address)) {
|
|
snapshot.ints["gearTypeRaw"] = gear->raw();
|
|
snapshot.bools["ledPowerSupplyIntegrated"] = gear->ledPowerSupplyIntegrated();
|
|
snapshot.bools["ledModuleIntegrated"] = gear->ledModuleIntegrated();
|
|
snapshot.bools["acSupplyPossible"] = gear->acSupplyPossible();
|
|
snapshot.bools["dcSupplyPossible"] = gear->dcSupplyPossible();
|
|
}
|
|
if (const auto modes = dt6.getPossibleOperatingModes(short_address)) {
|
|
snapshot.ints["possibleOperatingModesRaw"] = modes->raw();
|
|
snapshot.bools["pwmModePossible"] = modes->pwmModePossible();
|
|
snapshot.bools["amModePossible"] = modes->amModePossible();
|
|
snapshot.bools["currentControlledOutputPossible"] = modes->currentControlledOutput();
|
|
snapshot.bools["highCurrentPulseModePossible"] = modes->highCurrentPulseMode();
|
|
}
|
|
if (const auto features = dt6.getFeatures(short_address)) {
|
|
snapshot.ints["featuresRaw"] = features->raw();
|
|
snapshot.bools["canQueryShortCircuit"] = features->canQueryShortCircuit();
|
|
snapshot.bools["canQueryOpenCircuit"] = features->canQueryOpenCircuit();
|
|
snapshot.bools["canQueryLoadDecrease"] = features->canQueryLoadDecrease();
|
|
snapshot.bools["canQueryLoadIncrease"] = features->canQueryLoadIncrease();
|
|
snapshot.bools["canQueryCurrentProtector"] = features->canQueryCurrentProtector();
|
|
snapshot.bools["canQueryThermalShutdown"] = features->canQueryThermalShutdown();
|
|
snapshot.bools["canQueryThermalOverloadReduction"] =
|
|
features->canQueryThermalOverloadReduction();
|
|
snapshot.bools["physicalSelectionSupported"] = features->physicalSelectionSupported();
|
|
}
|
|
if (const auto failure = dt6.getFailureStatus(short_address)) {
|
|
snapshot.ints["failureRaw"] = failure->raw();
|
|
snapshot.bools["shortCircuit"] = failure->shortCircuit();
|
|
snapshot.bools["openCircuit"] = failure->openCircuit();
|
|
snapshot.bools["loadDecrease"] = failure->loadDecrease();
|
|
snapshot.bools["loadIncrease"] = failure->loadIncrease();
|
|
snapshot.bools["currentProtectorActive"] = failure->currentProtectorActive();
|
|
snapshot.bools["thermalShutdown"] = failure->thermalShutdown();
|
|
snapshot.bools["thermalOverloadReduction"] = failure->thermalOverloadReduction();
|
|
snapshot.bools["referenceMeasurementFailed"] = failure->referenceMeasurementFailed();
|
|
}
|
|
if (const auto mode = dt6.getOperatingMode(short_address)) {
|
|
snapshot.ints["operatingModeRaw"] = mode->raw();
|
|
snapshot.bools["pwmModeActive"] = mode->pwmModeActive();
|
|
snapshot.bools["amModeActive"] = mode->amModeActive();
|
|
snapshot.bools["currentControlledOutput"] = mode->currentControlledOutput();
|
|
snapshot.bools["highCurrentPulseModeActive"] = mode->highCurrentPulseModeActive();
|
|
snapshot.bools["nonLogarithmicDimmingCurveActive"] =
|
|
mode->nonLogarithmicDimmingCurveActive();
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt8StatusSnapshot(
|
|
uint8_t gateway_id, int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_status");
|
|
bool has_data = false;
|
|
if (const auto status = channel->dali->dt8.getColorStatus(short_address)) {
|
|
has_data = true;
|
|
snapshot.ints["colorStatusRaw"] = status->raw();
|
|
snapshot.bools["xyOutOfRange"] = status->xyOutOfRange();
|
|
snapshot.bools["ctOutOfRange"] = status->ctOutOfRange();
|
|
snapshot.bools["autoCalibrationActive"] = status->autoCalibrationActive();
|
|
snapshot.bools["autoCalibrationSuccess"] = status->autoCalibrationSuccess();
|
|
snapshot.bools["xyActive"] = status->xyActive();
|
|
snapshot.bools["ctActive"] = status->ctActive();
|
|
snapshot.bools["primaryNActive"] = status->primaryNActive();
|
|
snapshot.bools["rgbwafActive"] = status->rgbwafActive();
|
|
}
|
|
if (const auto features = channel->dali->dt8.getColorTypeFeature(short_address)) {
|
|
has_data = true;
|
|
snapshot.ints["colorTypeFeaturesRaw"] = features->features();
|
|
snapshot.ints["primaryCount"] = features->primaryCount();
|
|
snapshot.ints["rgbwafChannels"] = features->rgbwafChannels();
|
|
snapshot.bools["xyCapable"] = features->xyCapable();
|
|
snapshot.bools["ctCapable"] = features->ctCapable();
|
|
snapshot.bools["primaryNCapable"] = features->primaryNCapable();
|
|
snapshot.bools["rgbwafCapable"] = features->rgbwafCapable();
|
|
}
|
|
return has_data ? std::optional<DaliDomainSnapshot>(std::move(snapshot)) : std::nullopt;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SceneColorReport(
|
|
uint8_t gateway_id, int short_address, int scene) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
const auto report = channel->dali->dt8.getSceneColorReport(short_address, scene);
|
|
if (!report.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_scene");
|
|
snapshot.ints["scene"] = scene;
|
|
snapshot.ints["brightness"] = report->brightness;
|
|
snapshot.ints["colorType"] = report->colorTypeValue;
|
|
if (report->hasColorTemperature()) {
|
|
snapshot.ints["colorTemperature"] = report->colorTemperature.value();
|
|
}
|
|
if (report->hasXy()) {
|
|
snapshot.number_arrays["xy"] = report->xy;
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt8PowerOnLevelColorReport(
|
|
uint8_t gateway_id, int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
const auto report = channel->dali->dt8.getPowerOnLevelColorReport(short_address);
|
|
if (!report.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_power_on");
|
|
snapshot.ints["level"] = report->level;
|
|
snapshot.ints["colorType"] = report->colorTypeValue;
|
|
if (report->hasColorTemperature()) {
|
|
snapshot.ints["colorTemperature"] = report->colorTemperature.value();
|
|
}
|
|
if (report->hasXy()) {
|
|
snapshot.number_arrays["xy"] = report->xy;
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<DaliDomainSnapshot> DaliDomainService::dt8SystemFailureLevelColorReport(
|
|
uint8_t gateway_id, int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
const auto report = channel->dali->dt8.getSystemFailureLevelColorReport(short_address);
|
|
if (!report.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
auto snapshot = MakeSnapshot(gateway_id, short_address, "dt8_system_failure");
|
|
snapshot.ints["level"] = report->level;
|
|
snapshot.ints["colorType"] = report->colorTypeValue;
|
|
if (report->hasColorTemperature()) {
|
|
snapshot.ints["colorTemperature"] = report->colorTemperature.value();
|
|
}
|
|
if (report->hasXy()) {
|
|
snapshot.number_arrays["xy"] = report->xy;
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
bool DaliDomainService::storeDt8SceneSnapshot(uint8_t gateway_id, int short_address, int scene,
|
|
int brightness,
|
|
DaliDt8SceneColorMode color_mode,
|
|
int color_temperature, int red, int green,
|
|
int blue) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->dali != nullptr &&
|
|
channel->dali->dt8.storeSceneSnapshot(short_address, scene, brightness,
|
|
ToDaliCppColorMode(color_mode), color_temperature,
|
|
red, green, blue);
|
|
}
|
|
|
|
bool DaliDomainService::storeDt8PowerOnLevelSnapshot(uint8_t gateway_id, int short_address,
|
|
int level) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->dali != nullptr &&
|
|
channel->dali->dt8.storePowerOnLevelSnapshot(short_address, level);
|
|
}
|
|
|
|
bool DaliDomainService::storeDt8SystemFailureLevelSnapshot(uint8_t gateway_id,
|
|
int short_address, int level) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->dali != nullptr &&
|
|
channel->dali->dt8.storeSystemFailureLevelSnapshot(short_address, level);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
std::optional<uint16_t> DaliDomainService::queryGroupMask(uint8_t gateway_id,
|
|
int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto group_mask = channel->dali->base.getGroup(short_address);
|
|
if (!group_mask.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return static_cast<uint16_t>(*group_mask);
|
|
}
|
|
|
|
std::optional<uint8_t> DaliDomainService::querySceneLevel(uint8_t gateway_id, int short_address,
|
|
int scene) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto level = channel->dali->base.getScene(short_address, scene);
|
|
if (!level.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return static_cast<uint8_t>(*level);
|
|
}
|
|
|
|
std::optional<DaliAddressSettingsSnapshot> DaliDomainService::queryAddressSettings(
|
|
uint8_t gateway_id, int short_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
DaliAddressSettingsSnapshot settings{};
|
|
|
|
if (const auto value = channel->dali->base.getPowerOnLevel(short_address); value.has_value()) {
|
|
settings.power_on_level = static_cast<uint8_t>(*value);
|
|
}
|
|
if (const auto value = channel->dali->base.getSystemFailureLevel(short_address);
|
|
value.has_value()) {
|
|
settings.system_failure_level = static_cast<uint8_t>(*value);
|
|
}
|
|
if (const auto value = channel->dali->base.getMinLevel(short_address); value.has_value()) {
|
|
settings.min_level = static_cast<uint8_t>(*value);
|
|
}
|
|
if (const auto value = channel->dali->base.getMaxLevel(short_address); value.has_value()) {
|
|
settings.max_level = static_cast<uint8_t>(*value);
|
|
}
|
|
if (const auto value = channel->dali->base.getFadeTime(short_address); value.has_value()) {
|
|
settings.fade_time = static_cast<uint8_t>(*value);
|
|
}
|
|
if (const auto value = channel->dali->base.getFadeRate(short_address); value.has_value()) {
|
|
settings.fade_rate = static_cast<uint8_t>(*value);
|
|
}
|
|
|
|
if (!settings.anyKnown()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
bool DaliDomainService::applyGroupMask(uint8_t gateway_id, int short_address,
|
|
uint16_t group_mask) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->dali != nullptr &&
|
|
channel->dali->base.setGroup(short_address, group_mask);
|
|
}
|
|
|
|
bool DaliDomainService::applySceneLevel(uint8_t gateway_id, int short_address, int scene,
|
|
std::optional<uint8_t> level) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr || !level.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
if (*level == 255U) {
|
|
return channel->dali->base.removeScene(short_address, scene);
|
|
}
|
|
|
|
return channel->dali->base.setDTR(*level) &&
|
|
channel->dali->base.storeDTRAsSceneBright(short_address, scene);
|
|
}
|
|
|
|
bool DaliDomainService::applyAddressSettings(uint8_t gateway_id, int short_address,
|
|
const DaliAddressSettingsSnapshot& settings) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
bool ok = true;
|
|
if (settings.power_on_level.has_value()) {
|
|
ok = ok && channel->dali->base.setPowerOnLevel(short_address, *settings.power_on_level);
|
|
}
|
|
if (settings.system_failure_level.has_value()) {
|
|
ok = ok &&
|
|
channel->dali->base.setSystemFailureLevel(short_address, *settings.system_failure_level);
|
|
}
|
|
if (settings.min_level.has_value()) {
|
|
ok = ok && channel->dali->base.setMinLevel(short_address, *settings.min_level);
|
|
}
|
|
if (settings.max_level.has_value()) {
|
|
ok = ok && channel->dali->base.setMaxLevel(short_address, *settings.max_level);
|
|
}
|
|
if (settings.fade_time.has_value()) {
|
|
ok = ok && channel->dali->base.setFadeTime(short_address, *settings.fade_time);
|
|
}
|
|
if (settings.fade_rate.has_value()) {
|
|
ok = ok && channel->dali->base.setFadeRate(short_address, *settings.fade_rate);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool DaliDomainService::updateChannelName(uint8_t gateway_id, std::string_view name) {
|
|
auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr) {
|
|
return false;
|
|
}
|
|
channel->config.name = std::string(name);
|
|
if (channel->dali != nullptr) {
|
|
channel->dali->name = channel->config.name;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DaliDomainService::allocateAllAddr(uint8_t gateway_id, int start_address) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->dali != nullptr &&
|
|
channel->dali->addr.allocateAllAddr(start_address);
|
|
}
|
|
|
|
void DaliDomainService::stopAllocAddr(uint8_t gateway_id) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel != nullptr && channel->dali != nullptr) {
|
|
channel->dali->addr.stopAllocAddr();
|
|
}
|
|
}
|
|
|
|
bool DaliDomainService::resetAndAllocAddr(uint8_t gateway_id, int start_address,
|
|
bool remove_addr_first, bool close_light) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->dali != nullptr &&
|
|
channel->dali->addr.resetAndAllocAddr(start_address, remove_addr_first, close_light);
|
|
}
|
|
|
|
bool DaliDomainService::isAllocAddr(uint8_t gateway_id) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
return channel != nullptr && channel->dali != nullptr && channel->dali->addr.isAllocAddr();
|
|
}
|
|
|
|
int DaliDomainService::lastAllocAddr(uint8_t gateway_id) const {
|
|
const auto* channel = findChannelByGateway(gateway_id);
|
|
if (channel == nullptr || channel->dali == nullptr) {
|
|
return 0;
|
|
}
|
|
return channel->dali->addr.lastAllocAddr();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
const DaliDomainService::DaliChannel* DaliDomainService::findChannelByHardwareBus(
|
|
uint8_t bus_id) const {
|
|
const auto it = std::find_if(channels_.begin(), channels_.end(), [bus_id](const auto& channel) {
|
|
return channel->hardware_bus.has_value() && channel->hardware_bus->bus_id == bus_id;
|
|
});
|
|
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() {
|
|
if (raw_frame_task_handle_ != nullptr) {
|
|
return ESP_OK;
|
|
}
|
|
QueueHandle_t queue = dali_hal_raw_receive_queue();
|
|
if (queue == nullptr) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
const BaseType_t created = xTaskCreate(&DaliDomainService::RawFrameTaskEntry,
|
|
"dali_raw_rx", 4096, this, 4,
|
|
&raw_frame_task_handle_);
|
|
if (created != pdPASS) {
|
|
raw_frame_task_handle_ = nullptr;
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
void DaliDomainService::RawFrameTaskEntry(void* arg) {
|
|
static_cast<DaliDomainService*>(arg)->rawFrameTaskLoop();
|
|
}
|
|
|
|
void DaliDomainService::rawFrameTaskLoop() {
|
|
QueueHandle_t queue = dali_hal_raw_receive_queue();
|
|
Dali_msg_t message = {};
|
|
while (true) {
|
|
if (queue == nullptr) {
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
queue = dali_hal_raw_receive_queue();
|
|
continue;
|
|
}
|
|
if (xQueueReceive(queue, &message, portMAX_DELAY) != pdTRUE) {
|
|
continue;
|
|
}
|
|
if (message.status != DALI_FRAME_OK) {
|
|
continue;
|
|
}
|
|
const auto* channel = findChannelByHardwareBus(message.id);
|
|
if (channel == nullptr) {
|
|
continue;
|
|
}
|
|
size_t byte_count = (static_cast<size_t>(message.length) + 7U) / 8U;
|
|
if (byte_count > DALI_MAX_BYTES) {
|
|
byte_count = DALI_MAX_BYTES;
|
|
}
|
|
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(message.data, message.data + byte_count);
|
|
notifyRawFrameSinks(frame);
|
|
}
|
|
}
|
|
|
|
void DaliDomainService::notifyRawFrameSinks(const DaliRawFrame& frame) {
|
|
if (raw_frame_sink_lock_ != nullptr) {
|
|
xSemaphoreTake(raw_frame_sink_lock_, portMAX_DELAY);
|
|
}
|
|
auto sinks = raw_frame_sinks_;
|
|
if (raw_frame_sink_lock_ != nullptr) {
|
|
xSemaphoreGive(raw_frame_sink_lock_);
|
|
}
|
|
for (const auto& sink : sinks) {
|
|
sink(frame);
|
|
}
|
|
}
|
|
|
|
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
|