diff --git a/components/dali/src/include/dali.h b/components/dali/src/include/dali.h index 9b05ae7..957fced 100644 --- a/components/dali/src/include/dali.h +++ b/components/dali/src/include/dali.h @@ -22,6 +22,7 @@ Dali_msg_t dali_msg_new_generic(uint8_t bit_length, uint8_t address, uint8_t cmd // create standard DALI message: 16,24,32 bits Dali_msg_t dali_msg_new(uint8_t address, uint8_t cmd1); +Dali_msg_t dali_msg_new_1B(uint8_t data); Dali_msg_t dali_msg_new_3B(uint8_t address, uint8_t cmd1, uint8_t cmd2); Dali_msg_t dali_msg_new_4B(uint8_t address, uint8_t cmd1, uint8_t cmd2, uint8_t cmd3); @@ -86,4 +87,3 @@ void dali_change_short_address(int addr1, int addr2); #endif - diff --git a/components/dali_domain/include/dali_domain.hpp b/components/dali_domain/include/dali_domain.hpp index 99853f9..800ebad 100644 --- a/components/dali_domain/include/dali_domain.hpp +++ b/components/dali_domain/include/dali_domain.hpp @@ -140,6 +140,11 @@ class DaliDomainService { bool sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const; bool sendExtRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const; std::optional queryRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const; + bool sendControlDeviceRaw(uint8_t gateway_id, uint8_t byte0, uint8_t byte1, uint8_t byte2, + bool send_twice = false) const; + std::optional queryControlDeviceRaw(uint8_t gateway_id, uint8_t byte0, + uint8_t byte1, uint8_t byte2) const; + bool sendBackwardFrame(uint8_t gateway_id, uint8_t value) const; std::optional discoverDeviceTypes( uint8_t gateway_id, int short_address, const std::vector& fallback_types = {}, int max_next_types = 16) const; @@ -229,4 +234,4 @@ class DaliDomainService { TaskHandle_t raw_frame_task_handle_{nullptr}; }; -} // namespace gateway \ No newline at end of file +} // namespace gateway diff --git a/components/dali_domain/src/dali_domain.cpp b/components/dali_domain/src/dali_domain.cpp index 0e2fd7a..eb4e9a5 100644 --- a/components/dali_domain/src/dali_domain.cpp +++ b/components/dali_domain/src/dali_domain.cpp @@ -25,6 +25,9 @@ namespace { constexpr size_t kSerialRxPacketMaxBytes = 8; constexpr UBaseType_t kSerialRxQueueDepth = 8; constexpr uint32_t kHardwareQueryRawPostSuppressMs = 10; +constexpr uint8_t kControlDeviceSendOpcode = 0x60; +constexpr uint8_t kControlDeviceSendTwiceOpcode = 0x61; +constexpr uint8_t kControlDeviceQueryOpcode = 0x62; portMUX_TYPE s_query_raw_suppress_lock = portMUX_INITIALIZER_UNLOCKED; uint8_t s_query_raw_suppress_inflight[DALI_PHY_COUNT] = {}; @@ -146,6 +149,13 @@ std::vector LegacyQueryResponse(uint8_t status, uint8_t value = 0x00) { return {status, value}; } +std::optional ParseLegacyQueryValue(const std::vector& packet) { + if (packet.size() >= 2 && packet[0] == 0xFF) { + return packet[1]; + } + return std::nullopt; +} + void LogQueryRxPacket(const char* transport, int id, const std::vector& packet, const char* note = nullptr) { const unsigned first = packet.size() > 0 ? packet[0] : 0; @@ -156,11 +166,80 @@ void LogQueryRxPacket(const char* transport, int id, const std::vector& note == nullptr ? "" : " note=", note == nullptr ? "" : note); } -bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) { +bool SendHardwareControlDeviceFrame(uint8_t bus_id, const uint8_t* data, size_t len, + bool send_twice) { if (data == nullptr || len != 3) { return false; } + Dali_msg_t tx = dali_msg_new_3B(data[0], data[1], data[2]); + tx.id = bus_id; + ESP_LOGD(TAG, "sending hardware control-device frame for bus=%u data=%02x %02x %02x", + bus_id, data[0], data[1], data[2]); + if (send_twice) { + dali_send_double(&tx); + } else { + dali_send(&tx); + } + return tx.status == DALI_FRAME_OK; +} + +std::vector TransactHardwareControlDeviceFrame(uint8_t bus_id, const uint8_t* data, + size_t len) { + if (data == nullptr || len != 3) { + const auto packet = LegacyQueryResponse(0xFD); + LogQueryRxPacket("hardware-cd", bus_id, packet, "invalid-query-len"); + return packet; + } + + Dali_msg_t tx = dali_msg_new_3B(data[0], data[1], data[2]); + tx.id = bus_id; + Dali_msg_t rx = {}; + ESP_LOGD(TAG, "received hardware control-device query for bus=%u data=%02x %02x %02x", + bus_id, data[0], data[1], data[2]); + BeginHardwareQueryRawSuppress(bus_id); + if (dali_query(&tx, &rx) == pdTRUE) { + ClearHardwareQueryRawSuppress(bus_id); + if (rx.status != DALI_FRAME_OK || rx.length != 8) { + ESP_LOGW(TAG, "hardware control-device query response for bus=%u has invalid status or length", + bus_id); + const auto packet = LegacyQueryResponse(0xFD); + LogQueryRxPacket("hardware-cd", bus_id, packet, "invalid-status-or-length"); + return packet; + } + const std::vector packet{0xFF, rx.data[0]}; + LogQueryRxPacket("hardware-cd", bus_id, packet, "ok"); + return packet; + } + ClearHardwareQueryRawSuppress(bus_id); + const auto packet = LegacyQueryResponse(0xFE); + LogQueryRxPacket("hardware-cd", bus_id, packet, "no-response"); + return packet; +} + +bool SendHardwareBackwardFrame(uint8_t bus_id, uint8_t value) { + Dali_msg_t tx = dali_msg_new_1B(value); + tx.id = bus_id; + ESP_LOGD(TAG, "sending hardware backward frame for bus=%u data=%02x", bus_id, value); + dali_send(&tx); + return tx.status == DALI_FRAME_OK; +} + +bool SendHardwareFrame(uint8_t bus_id, const uint8_t* data, size_t len) { + if (data == nullptr) { + return false; + } + + if (len == 4 && (data[0] == kControlDeviceSendOpcode || + data[0] == kControlDeviceSendTwiceOpcode)) { + return SendHardwareControlDeviceFrame(bus_id, data + 1, 3, + data[0] == kControlDeviceSendTwiceOpcode); + } + + if (len != 3) { + return false; + } + if (data[0] == 0x00) { return true; } @@ -186,6 +265,9 @@ std::vector TransactHardwareFrame(uint8_t bus_id, const uint8_t* data, if (data == nullptr) { return {}; } + if (len == 4 && data[0] == kControlDeviceQueryOpcode) { + return TransactHardwareControlDeviceFrame(bus_id, data + 1, 3); + } if (len != 3) { if (len > 0 && data[0] == 0x12) { const auto packet = LegacyQueryResponse(0xFD); @@ -276,12 +358,14 @@ std::vector TransactSerialFrame(int uart_port, QueueHandle_t queue, LogQueryRxPacket("serial", uart_port, packet, "empty-query"); return packet; } - if (data[0] == 0x12 && len != 3) { + const bool query_frame = data[0] == 0x12 || data[0] == kControlDeviceQueryOpcode; + const size_t expected_query_len = data[0] == kControlDeviceQueryOpcode ? 4 : 3; + if (query_frame && len != expected_query_len) { const auto packet = LegacyQueryResponse(0xFD); LogQueryRxPacket("serial", uart_port, packet, "invalid-query-len"); return packet; } - if (data != nullptr && len > 0 && data[0] == 0x12) { + if (query_frame) { DrainSerialQueue(queue); } if (!WriteSerialFrame(uart_port, data, len)) { @@ -289,7 +373,7 @@ std::vector TransactSerialFrame(int uart_port, QueueHandle_t queue, LogQueryRxPacket("serial", uart_port, packet, "write-failed"); return packet; } - if (data[0] != 0x12) { + if (!query_frame) { return {0xFF}; } @@ -721,6 +805,70 @@ std::optional DaliDomainService::queryRaw(uint8_t gateway_id, uint8_t r return channel->comm->queryRawNew(raw_addr, command); } +bool DaliDomainService::sendControlDeviceRaw(uint8_t gateway_id, uint8_t byte0, uint8_t byte1, + uint8_t byte2, bool send_twice) const { + const auto* channel = findChannelByGateway(gateway_id); + if (channel == nullptr) { + return false; + } + + const uint8_t frame[3] = {byte0, byte1, byte2}; + markHostActivity(gateway_id); + markBusActivity(gateway_id); + if (channel->hardware_bus.has_value()) { + return SendHardwareControlDeviceFrame(channel->hardware_bus->bus_id, frame, sizeof(frame), + send_twice); + } + if (!channel->hooks.send) { + return false; + } + const uint8_t bridge_frame[4] = { + static_cast(send_twice ? kControlDeviceSendTwiceOpcode : kControlDeviceSendOpcode), + byte0, + byte1, + byte2, + }; + return channel->hooks.send(bridge_frame, sizeof(bridge_frame)); +} + +std::optional DaliDomainService::queryControlDeviceRaw(uint8_t gateway_id, uint8_t byte0, + uint8_t byte1, + uint8_t byte2) const { + const auto* channel = findChannelByGateway(gateway_id); + if (channel == nullptr) { + return std::nullopt; + } + + const uint8_t frame[3] = {byte0, byte1, byte2}; + markHostActivity(gateway_id); + markBusActivity(gateway_id); + if (channel->hardware_bus.has_value()) { + return ParseLegacyQueryValue( + TransactHardwareControlDeviceFrame(channel->hardware_bus->bus_id, frame, sizeof(frame))); + } + if (!channel->hooks.transact) { + return std::nullopt; + } + const uint8_t bridge_frame[4] = {kControlDeviceQueryOpcode, byte0, byte1, byte2}; + return ParseLegacyQueryValue(channel->hooks.transact(bridge_frame, sizeof(bridge_frame))); +} + +bool DaliDomainService::sendBackwardFrame(uint8_t gateway_id, uint8_t value) const { + const auto* channel = findChannelByGateway(gateway_id); + if (channel == nullptr) { + return false; + } + + markBusActivity(gateway_id); + if (channel->hardware_bus.has_value()) { + return SendHardwareBackwardFrame(channel->hardware_bus->bus_id, value); + } + if (!channel->hooks.send) { + return false; + } + return channel->hooks.send(&value, 1); +} + std::optional DaliDomainService::discoverDeviceTypes( uint8_t gateway_id, int short_address, const std::vector& fallback_types, int max_next_types) const { @@ -1682,4 +1830,4 @@ bool DaliDomainService::hasSerialPort(int uart_port) const { }); } -} // namespace gateway \ No newline at end of file +} // namespace gateway diff --git a/components/gateway_controller/include/gateway_controller.hpp b/components/gateway_controller/include/gateway_controller.hpp index 3ded687..6aaf849 100644 --- a/components/gateway_controller/include/gateway_controller.hpp +++ b/components/gateway_controller/include/gateway_controller.hpp @@ -144,6 +144,9 @@ class GatewayController { void publishPayload(uint8_t gateway_id, const std::vector& payload); void publishFrame(const std::vector& frame); void handleDaliRawFrame(const DaliRawFrame& frame); + bool handleApplicationControllerFrame(const DaliRawFrame& frame); + std::optional applicationControllerResponse(uint8_t gateway_id, uint8_t first, + uint8_t instance, uint8_t opcode) const; bool sendRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command); bool sendExtRawAndMirror(uint8_t gateway_id, uint8_t raw_addr, uint8_t command); @@ -207,6 +210,14 @@ class GatewayController { bool ble_enabled_{false}; bool wifi_enabled_{false}; bool ip_router_enabled_{true}; + bool application_controller_enabled_{true}; + bool application_controller_power_cycle_notification_{true}; + bool application_controller_power_cycle_seen_{true}; + bool application_controller_reset_state_{false}; + uint8_t application_controller_operating_mode_{0}; + uint8_t application_controller_dtr0_{0}; + uint8_t application_controller_dtr1_{0}; + uint8_t application_controller_dtr2_{0}; }; } // namespace gateway diff --git a/components/gateway_controller/src/gateway_controller.cpp b/components/gateway_controller/src/gateway_controller.cpp index a259693..2630b0e 100644 --- a/components/gateway_controller/src/gateway_controller.cpp +++ b/components/gateway_controller/src/gateway_controller.cpp @@ -28,6 +28,12 @@ constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0; constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1; constexpr uint8_t kBridgeTransportVersion = 1; constexpr size_t kBridgeTransportMaxChunkBytes = 120; +constexpr uint8_t kDali103SendOpcode = 0x60; +constexpr uint8_t kDali103SendTwiceOpcode = 0x61; +constexpr uint8_t kDali103QueryOpcode = 0x62; +constexpr uint8_t kDali103QueryResponseOpcode = 0x63; +constexpr uint8_t kDali103NoResponseOpcode = 0x64; +constexpr uint8_t kDali103RawFrameOpcode = 0x65; constexpr uint8_t kGatewayFeatureCache = 0x40; constexpr uint8_t kGatewayCacheOpcode = 0x39; constexpr uint8_t kGatewayCacheProtocolVersion = 1; @@ -106,6 +112,9 @@ bool IsDaliHostCommandOpcode(uint8_t opcode) { case 0x32: case 0x37: case 0x38: + case kDali103SendOpcode: + case kDali103SendTwiceOpcode: + case kDali103QueryOpcode: case 0xA0: case 0xA2: case kBridgeTransportRequestOpcode: @@ -933,6 +942,24 @@ void GatewayController::dispatchCommand(const std::vector& command) { } } break; + case kDali103SendOpcode: + case kDali103SendTwiceOpcode: + if (command.size() >= 8) { + dali_domain_.sendControlDeviceRaw(gateway_id, command[4], command[5], command[6], + opcode == kDali103SendTwiceOpcode); + } + break; + case kDali103QueryOpcode: + if (command.size() >= 8) { + const auto result = + dali_domain_.queryControlDeviceRaw(gateway_id, command[4], command[5], command[6]); + if (result.has_value()) { + publishPayload(gateway_id, {kDali103QueryResponseOpcode, gateway_id, result.value()}); + } else { + publishPayload(gateway_id, {kDali103NoResponseOpcode, gateway_id, 0x00}); + } + } + break; case kGatewayCacheOpcode: handleGatewayCacheCommand(gateway_id, command); break; @@ -1036,6 +1063,160 @@ void GatewayController::publishFrame(const std::vector& frame) { } } +std::optional GatewayController::applicationControllerResponse( + uint8_t gateway_id, uint8_t first, uint8_t instance, uint8_t opcode) const { + const uint8_t gateway_short = + static_cast(((gateway_id & 0x3F) << 1) | 0x01); + const bool addressed_to_gateway = first == 0xFF || first == gateway_short; + if (!addressed_to_gateway || instance != 0xFE) { + return std::nullopt; + } + + switch (opcode) { + case 0x30: { + uint8_t status = 0; + if (application_controller_enabled_) { + status |= 0x08; + } + if (application_controller_power_cycle_seen_) { + status |= 0x20; + } + if (application_controller_reset_state_) { + status |= 0x40; + } + return status; + } + case 0x31: + case 0x32: + case 0x33: + return 0x00; + case 0x34: + return 0x02; + case 0x35: + return 0x00; + case 0x36: + return application_controller_dtr0_; + case 0x37: + return application_controller_dtr1_; + case 0x38: + return application_controller_dtr2_; + case 0x39: + case 0x3A: + case 0x3B: + return 0x00; + case 0x3C: + return 0xFF; + case 0x3D: + return static_cast(application_controller_enabled_ ? 1 : 0); + case 0x3E: + return application_controller_operating_mode_; + case 0x3F: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + return 0x00; + case 0x45: + return static_cast(application_controller_power_cycle_notification_ ? 1 : 0); + case 0x46: + return 0x01; + case 0x47: + return 0x01; + case 0x48: + return static_cast(application_controller_reset_state_ ? 1 : 0); + default: + return std::nullopt; + } +} + +bool GatewayController::handleApplicationControllerFrame(const DaliRawFrame& frame) { + if (frame.data.size() != 3) { + return false; + } + + const uint8_t first = frame.data[0]; + const uint8_t instance = frame.data[1]; + const uint8_t opcode = frame.data[2]; + + if (first == 0xC1) { + switch (instance) { + case 0x30: + application_controller_dtr0_ = opcode; + break; + case 0x31: + application_controller_dtr1_ = opcode; + break; + case 0x32: + application_controller_dtr2_ = opcode; + break; + default: + break; + } + return false; + } + + if ((first & 0x01) == 0) { + return false; + } + + const uint8_t gateway_short = + static_cast(((frame.gateway_id & 0x3F) << 1) | 0x01); + if (first != 0xFF && first != gateway_short) { + return false; + } + if (instance != 0xFE) { + return false; + } + + switch (opcode) { + case 0x01: + application_controller_power_cycle_seen_ = false; + application_controller_reset_state_ = false; + break; + case 0x10: + application_controller_enabled_ = true; + application_controller_power_cycle_notification_ = true; + application_controller_reset_state_ = true; + application_controller_operating_mode_ = 0; + application_controller_dtr0_ = 0; + application_controller_dtr1_ = 0; + application_controller_dtr2_ = 0; + break; + case 0x16: + application_controller_enabled_ = true; + application_controller_reset_state_ = false; + break; + case 0x17: + application_controller_enabled_ = false; + application_controller_reset_state_ = false; + break; + case 0x18: + application_controller_operating_mode_ = application_controller_dtr0_; + application_controller_reset_state_ = false; + break; + case 0x1F: + application_controller_power_cycle_notification_ = true; + application_controller_reset_state_ = false; + break; + case 0x20: + application_controller_power_cycle_notification_ = false; + application_controller_reset_state_ = false; + break; + case 0x21: + application_controller_reset_state_ = false; + break; + default: + break; + } + + const auto response = applicationControllerResponse(frame.gateway_id, first, instance, opcode); + if (!response.has_value()) { + return false; + } + return dali_domain_.sendBackwardFrame(frame.gateway_id, response.value()); +} + void GatewayController::handleBridgeTransportCommand(uint8_t gateway_id, const std::vector& command) { const uint8_t version = command.size() > 4 ? command[4] : kBridgeTransportVersion; @@ -1119,6 +1300,20 @@ void GatewayController::handleDaliRawFrame(const DaliRawFrame& frame) { return; } + if (frame.data.size() == 3 && + (((frame.data[0] & 0x01) != 0) || frame.data[0] == 0xC1)) { + handleApplicationControllerFrame(frame); + const bool maintenance_activity = maintenance_activity_gateway_.load() == frame.gateway_id; + if (setup_mode_ || dali_domain_.isAllocAddr(frame.gateway_id) || maintenance_activity || + runtime_.hasActiveQueryCommand(frame.gateway_id)) { + return; + } + publishPayload(frame.gateway_id, + {kDali103RawFrameOpcode, frame.gateway_id, frame.data[0], frame.data[1], + frame.data[2]}); + return; + } + uint8_t addr = 0; uint8_t data = 0; if (frame.data.size() == 2) { diff --git a/components/gateway_runtime/src/gateway_runtime.cpp b/components/gateway_runtime/src/gateway_runtime.cpp index 6a2ef6c..2a27e8b 100644 --- a/components/gateway_runtime/src/gateway_runtime.cpp +++ b/components/gateway_runtime/src/gateway_runtime.cpp @@ -324,7 +324,7 @@ GatewayRuntime::CommandPriority GatewayRuntime::classifyCommandPriority( if (opcode == 0x00 || opcode == 0x01 || opcode == 0x03 || opcode == 0x04 || opcode == 0x07 || opcode == 0x08 || opcode == 0x10 || opcode == 0x11 || opcode == 0x12 || opcode == 0x13 || opcode == 0x17 || opcode == 0x18 || opcode == 0x37 || opcode == 0x38 || - (opcode == 0x30 && addr == 0)) { + opcode == 0x60 || opcode == 0x61 || opcode == 0x62 || (opcode == 0x30 && addr == 0)) { return CommandPriority::kControl; } return CommandPriority::kNormal; @@ -597,8 +597,8 @@ std::string GatewayRuntime::defaultBleGatewayName() const { } bool GatewayRuntime::isQueryCommand(const std::vector& command) const { - return command.size() >= 6 && isGatewayCommandFrame(command) && command[3] >= 0x14 && - command[3] <= 0x16; + return command.size() >= 6 && isGatewayCommandFrame(command) && + ((command[3] >= 0x14 && command[3] <= 0x16) || command[3] == 0x62); } size_t GatewayRuntime::pendingCommandCountLocked() const { @@ -639,6 +639,15 @@ std::optional GatewayRuntime::queryCommandKey( const auto gw = command[2]; const auto cmd = command[3]; + if (cmd == 0x62) { + if (command.size() < 8) { + return std::nullopt; + } + char key[40] = {0}; + std::snprintf(key, sizeof(key), "%u:%u:%u:%u:%u", gw, cmd, command[4], command[5], + command[6]); + return std::string(key); + } if (cmd == 0x16) { char key[16] = {0}; std::snprintf(key, sizeof(key), "%u:%u", gw, cmd); @@ -688,4 +697,4 @@ std::string GatewayRuntime::toHex(const std::vector& bytes) { return out; } -} // namespace gateway \ No newline at end of file +} // namespace gateway