#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 #include 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 void PutOptionalInt(DaliDomainSnapshot& snapshot, const char* name, const std::optional& value) { if (value.has_value()) { snapshot.ints[name] = static_cast(value.value()); } } void PutOptionalBool(DaliDomainSnapshot& snapshot, const char* name, const std::optional& value) { if (value.has_value()) { snapshot.bools[name] = value.value(); } } void PutOptionalNumber(DaliDomainSnapshot& snapshot, const char* name, const std::optional& 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 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 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(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 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 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 comm; std::unique_ptr dali; std::optional hardware_bus; std::optional 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(); channel->owner = this; 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; } 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(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); 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(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; } void DaliDomainService::addRawFrameSink(std::function 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 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 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 DaliDomainService::discoverDeviceTypes( uint8_t gateway_id, int short_address, const std::vector& 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 fallback = fallback_types.empty() ? std::vector{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 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(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 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 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 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 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 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(std::move(snapshot)) : std::nullopt; } std::optional 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 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 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 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(*group_mask); } std::optional 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(*level); } std::optional 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(*value); } if (const auto value = channel->dali->base.getSystemFailureLevel(short_address); value.has_value()) { settings.system_failure_level = static_cast(*value); } if (const auto value = channel->dali->base.getMinLevel(short_address); value.has_value()) { settings.min_level = static_cast(*value); } if (const auto value = channel->dali->base.getMaxLevel(short_address); value.has_value()) { settings.max_level = static_cast(*value); } if (const auto value = channel->dali->base.getFadeTime(short_address); value.has_value()) { settings.fade_time = static_cast(*value); } if (const auto value = channel->dali->base.getFadeRate(short_address); value.has_value()) { settings.fade_rate = static_cast(*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 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(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(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(static_cast(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(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(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