From 40a0e8e303c2c98a7e8275efb6e95f928581a591 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 12 Jun 2026 01:26:31 +0800 Subject: [PATCH] feat: implement device name handling and update gateway name features Signed-off-by: Tony --- README.md | 12 ++-- apps/gateway/main/app_main.cpp | 9 +++ .../gateway_bacnet/include/gateway_bacnet.hpp | 3 +- .../include/gateway_bacnet_stack_port.h | 4 +- .../gateway_bacnet/src/gateway_bacnet.cpp | 30 +++++++-- .../src/gateway_bacnet_stack_port.c | 10 ++- .../gateway_bridge/include/gateway_bridge.hpp | 3 + .../gateway_bridge/src/gateway_bridge.cpp | 46 +++++++++++++- .../src/gateway_controller.cpp | 31 ++++++++-- .../include/gateway_runtime.hpp | 6 ++ .../gateway_runtime/src/gateway_runtime.cpp | 61 ++++++++++++++++++- 11 files changed, 192 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 879b8e1..740a73c 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,14 @@ DALI bus activity from another master until the bus has been idle. Group and broadcast targets are never queried for refresh. Gateway feature opcode `0x06` keeps the Lua-compatible low-byte feature bits, -advertises cache support with bit `0x40`, and advertises the native C++ gateway -type with bit `0x0100`. Gateway opcode `0x39` returns cache summary and target -snapshots so frontend clients can read cached state without issuing live DALI -queries on supported gateways. +advertises cache support with bit `0x40`, advertises gateway/channel name +support with bit `0x80`, and advertises the native C++ gateway type with bit +`0x0100`. Gateway opcode `0x05` reads and writes user-facing gateway identity: +operation `0x00` reads the channel name, `0x01` writes the channel name, +`0x02` reads the physical gateway device name, and `0x03` writes the physical +gateway device name. Empty writes reset to the default name. Gateway opcode +`0x39` returns cache summary and target snapshots so frontend clients can read +cached state without issuing live DALI queries on supported gateways. Gateway opcode `0x09` with address/data `0x00/0x00` is a chip-level channel-id report. It returns the enabled DALI channel ids so clients do not need to probe diff --git a/apps/gateway/main/app_main.cpp b/apps/gateway/main/app_main.cpp index 6e96eef..586b0e4 100644 --- a/apps/gateway/main/app_main.cpp +++ b/apps/gateway/main/app_main.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #ifndef CONFIG_GATEWAY_CHANNEL_COUNT #define CONFIG_GATEWAY_CHANNEL_COUNT 2 @@ -1144,9 +1145,17 @@ extern "C" void app_main(void) { static_cast(CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE); bridge_config.knx_task_priority = static_cast(CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY); + bridge_config.gateway_device_name_provider = []() { + return s_runtime == nullptr ? std::string() : s_runtime->deviceName(); + }; s_bridge = std::make_unique(*s_dali_domain, *s_cache, bridge_config); s_controller->setBridgeService(s_bridge.get()); + s_controller->addGatewayNameSink([](uint8_t gateway_id) { + if (s_bridge != nullptr) { + s_bridge->handleGatewayNameChanged(gateway_id); + } + }); } if (profile.enable_wifi || profile.enable_eth) { diff --git a/components/gateway_bacnet/include/gateway_bacnet.hpp b/components/gateway_bacnet/include/gateway_bacnet.hpp index d459189..a2c193b 100644 --- a/components/gateway_bacnet/include/gateway_bacnet.hpp +++ b/components/gateway_bacnet/include/gateway_bacnet.hpp @@ -18,6 +18,7 @@ namespace gateway { struct GatewayBacnetServerConfig { uint32_t device_instance{4194303}; std::string device_name{"DALI Gateway"}; + std::string channel_name; std::string local_address; uint16_t udp_port{47808}; uint32_t task_stack_size{8192}; @@ -91,4 +92,4 @@ class GatewayBacnetServer { bool started_{false}; }; -} // namespace gateway \ No newline at end of file +} // namespace gateway diff --git a/components/gateway_bacnet/include/gateway_bacnet_stack_port.h b/components/gateway_bacnet/include/gateway_bacnet_stack_port.h index 3df6b23..aac3c7c 100644 --- a/components/gateway_bacnet/include/gateway_bacnet_stack_port.h +++ b/components/gateway_bacnet/include/gateway_bacnet_stack_port.h @@ -48,6 +48,8 @@ bool gateway_bacnet_stack_start( void gateway_bacnet_stack_cleanup(void); +bool gateway_bacnet_stack_set_device_name(const char* device_name); + bool gateway_bacnet_stack_upsert_object( gateway_bacnet_object_kind_t object_kind, uint32_t object_instance, @@ -74,4 +76,4 @@ void gateway_bacnet_stack_poll(uint16_t elapsed_ms); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/components/gateway_bacnet/src/gateway_bacnet.cpp b/components/gateway_bacnet/src/gateway_bacnet.cpp index bd0e9ce..8273e19 100644 --- a/components/gateway_bacnet/src/gateway_bacnet.cpp +++ b/components/gateway_bacnet/src/gateway_bacnet.cpp @@ -251,6 +251,11 @@ esp_err_t GatewayBacnetServer::registerChannel( return ESP_ERR_INVALID_ARG; } + GatewayBacnetServerConfig effective_config = config; + if (effective_config.device_name.empty()) { + effective_config.device_name = "DALI Gateway"; + } + bindings.erase(std::remove_if(bindings.begin(), bindings.end(), [](const auto& binding) { return !IsSupportedObjectType(binding.object_type) || binding.object_instance > kMaxBacnetInstance; @@ -258,7 +263,7 @@ esp_err_t GatewayBacnetServer::registerChannel( bindings.end()); LockGuard guard(lock_); - if (started_ && !configCompatible(config)) { + if (started_ && !configCompatible(effective_config)) { return ESP_ERR_INVALID_STATE; } @@ -268,7 +273,15 @@ esp_err_t GatewayBacnetServer::registerChannel( if (bindings.empty() && !started_ && channel == channels_.end()) { return ESP_ERR_NOT_FOUND; } - ChannelRegistration registration{gateway_id, config, std::move(bindings), + if (started_ && active_config_.device_name != effective_config.device_name) { + if (!gateway_bacnet_stack_set_device_name(effective_config.device_name.c_str())) { + return ESP_FAIL; + } + active_config_.device_name = effective_config.device_name; + gateway_bacnet_stack_send_i_am(); + } + + ChannelRegistration registration{gateway_id, effective_config, std::move(bindings), std::move(write_callback), std::move(read_callback)}; if (channel == channels_.end()) { channels_.push_back(std::move(registration)); @@ -276,7 +289,7 @@ esp_err_t GatewayBacnetServer::registerChannel( *channel = std::move(registration); } - esp_err_t err = startStackLocked(config); + esp_err_t err = startStackLocked(effective_config); if (err != ESP_OK) { return err; } @@ -341,9 +354,16 @@ esp_err_t GatewayBacnetServer::rebuildObjectsLocked() { used_objects.insert(key); const std::string name = ObjectName(binding); + std::string description = channel.config.channel_name; + if (!binding.model_id.empty()) { + if (!description.empty()) { + description += " / "; + } + description += binding.model_id; + } if (!gateway_bacnet_stack_upsert_object(ToBacnetKind(binding.object_type), binding.object_instance, name.c_str(), - binding.model_id.c_str(), + description.c_str(), binding.out_of_service, binding.reliability)) { return ESP_FAIL; @@ -465,4 +485,4 @@ void GatewayBacnetServer::taskLoop() { } } -} // namespace gateway \ No newline at end of file +} // namespace gateway diff --git a/components/gateway_bacnet/src/gateway_bacnet_stack_port.c b/components/gateway_bacnet/src/gateway_bacnet_stack_port.c index adce537..858daa4 100644 --- a/components/gateway_bacnet/src/gateway_bacnet_stack_port.c +++ b/components/gateway_bacnet/src/gateway_bacnet_stack_port.c @@ -410,6 +410,14 @@ void gateway_bacnet_stack_cleanup(void) Write_Callback_Context = NULL; } +bool gateway_bacnet_stack_set_device_name(const char* device_name) +{ + if (!device_name || device_name[0] == '\0') { + device_name = "DALI Gateway"; + } + return Device_Object_Name_ANSI_Init(device_name); +} + bool gateway_bacnet_stack_upsert_object( gateway_bacnet_object_kind_t object_kind, uint32_t object_instance, @@ -636,4 +644,4 @@ void gateway_bacnet_stack_poll(uint16_t elapsed_ms) tsm_timer_milliseconds(elapsed_ms); Device_Timer(elapsed_ms); } -} \ No newline at end of file +} diff --git a/components/gateway_bridge/include/gateway_bridge.hpp b/components/gateway_bridge/include/gateway_bridge.hpp index 75c39db..0cff16d 100644 --- a/components/gateway_bridge/include/gateway_bridge.hpp +++ b/components/gateway_bridge/include/gateway_bridge.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,7 @@ struct GatewayBridgeServiceConfig { uint32_t knx_task_stack_size{12288}; UBaseType_t knx_task_priority{5}; std::optional default_knx_config; + std::function gateway_device_name_provider; }; struct GatewayBridgeHttpResponse { @@ -62,6 +64,7 @@ class GatewayBridgeService { GatewayBridgeHttpResponse handlePost(const std::string& action, int gateway_id, const std::string& body); std::string handleTransportRequest(uint8_t gateway_id, std::string_view request); + void handleGatewayNameChanged(uint8_t gateway_id); private: struct ChannelRuntime; diff --git a/components/gateway_bridge/src/gateway_bridge.cpp b/components/gateway_bridge/src/gateway_bridge.cpp index 9a7b9f7..455eaa0 100644 --- a/components/gateway_bridge/src/gateway_bridge.cpp +++ b/components/gateway_bridge/src/gateway_bridge.cpp @@ -2100,8 +2100,17 @@ struct GatewayBridgeService::ChannelRuntime { #if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED) GatewayBacnetServerConfig bacnetServerConfigLocked() const { GatewayBacnetServerConfig config; - config.device_name = channel.name.empty() ? "DALI Gateway " + std::to_string(channel.gateway_id) - : channel.name; + config.channel_name = channel.name.empty() + ? "Gateway " + std::to_string(channel.gateway_id) + : channel.name; + if (service_config.gateway_device_name_provider) { + config.device_name = service_config.gateway_device_name_provider(); + } + if (config.device_name.empty()) { + config.device_name = channel.name.empty() + ? "DALI Gateway " + std::to_string(channel.gateway_id) + : channel.name; + } config.task_stack_size = service_config.bacnet_task_stack_size; config.task_priority = service_config.bacnet_task_priority; if (bacnet_server_config.has_value()) { @@ -2320,6 +2329,21 @@ struct GatewayBridgeService::ChannelRuntime { } #endif + void updateChannelInfo(DaliChannelInfo next_channel) { + LockGuard guard(lock); + channel = std::move(next_channel); +#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED) + if (bacnet_started) { + const esp_err_t err = syncBacnetServerLocked(); + if (err != ESP_OK && err != ESP_ERR_NOT_FOUND) { + ESP_LOGW(kTag, "gateway=%u BACnet name refresh failed: %s", channel.gateway_id, + esp_err_to_name(err)); + } + bacnet_started = err == ESP_OK; + } +#endif + } + esp_err_t startKnx(std::set* used_ports = nullptr, std::set* used_uarts = nullptr) { LockGuard guard(lock); @@ -4379,6 +4403,24 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime( return nullptr; } +void GatewayBridgeService::handleGatewayNameChanged(uint8_t gateway_id) { + const auto channels = dali_domain_.channelInfo(); + for (const auto& runtime : runtimes_) { + if (gateway_id != 0xff && runtime->channel.gateway_id != gateway_id) { + continue; + } + const uint8_t runtime_gateway = runtime->channel.gateway_id; + const auto channel = + std::find_if(channels.begin(), channels.end(), [runtime_gateway]( + const DaliChannelInfo& item) { + return item.gateway_id == runtime_gateway; + }); + if (channel != channels.end()) { + runtime->updateChannelInfo(*channel); + } + } +} + GatewayBridgeService::ChannelRuntime* GatewayBridgeService::selectKnxEndpointRuntime() { auto eligible = [](ChannelRuntime* runtime) { if (runtime == nullptr) { diff --git a/components/gateway_controller/src/gateway_controller.cpp b/components/gateway_controller/src/gateway_controller.cpp index dd4877e..da94848 100644 --- a/components/gateway_controller/src/gateway_controller.cpp +++ b/components/gateway_controller/src/gateway_controller.cpp @@ -44,6 +44,7 @@ constexpr uint8_t kDali103QueryResponseOpcode = 0x63; constexpr uint8_t kDali103NoResponseOpcode = 0x64; constexpr uint8_t kDali103RawFrameOpcode = 0x65; constexpr uint16_t kGatewayFeatureCache = 0x0040; +constexpr uint16_t kGatewayFeatureNames = 0x0080; constexpr uint16_t kGatewayFeatureNativeCpp = 0x0100; constexpr uint8_t kGatewayCacheOpcode = 0x39; constexpr uint8_t kGatewayCacheProtocolVersion = 1; @@ -252,6 +253,15 @@ void AppendPaddedName(std::vector& out, std::string_view name) { } } +void AppendGatewayNameResponse(std::vector& out, std::string_view name) { + const auto normalized = NormalizeName(name); + out.push_back(static_cast(normalized.size())); + AppendStringBytes(out, normalized); + while (out.size() < 4 + kMaxNameBytes) { + out.push_back(0x00); + } +} + const char* PhyKindToString(DaliPhyKind phy_kind) { switch (phy_kind) { case DaliPhyKind::kNativeHardware: @@ -865,6 +875,7 @@ void GatewayController::dispatchCommand(const std::vector& command) { if (config_.cache_supported) { feature |= kGatewayFeatureCache; } + feature |= kGatewayFeatureNames; std::vector payload{0x03, gateway_id}; AppendFeatureBits(payload, feature); publishPayload(gateway_id, payload); @@ -1624,13 +1635,19 @@ void GatewayController::handleGatewayNameCommand(uint8_t gateway_id, const uint8_t op = command[4]; if (op == 0x00) { const auto name = gatewayName(gateway_id); - std::vector payload{0x05, gateway_id, op, - static_cast(std::min(name.size(), kMaxNameBytes))}; - AppendStringBytes(payload, NormalizeName(name)); + std::vector payload{0x05, gateway_id, op}; + AppendGatewayNameResponse(payload, name); publishPayload(gateway_id, payload); return; } - if (op != 0x01) { + if (op == 0x02) { + const auto name = runtime_.deviceName(); + std::vector payload{0x05, gateway_id, op}; + AppendGatewayNameResponse(payload, name); + publishPayload(gateway_id, payload); + return; + } + if (op != 0x01 && op != 0x03) { return; } @@ -1647,10 +1664,12 @@ void GatewayController::handleGatewayNameCommand(uint8_t gateway_id, for (size_t index = 0; index < actual_len; ++index) { name.push_back(static_cast(command[6 + index])); } - if (runtime_.setGatewayName(gateway_id, name)) { + const bool saved = op == 0x03 ? runtime_.setDeviceName(name) + : runtime_.setGatewayName(gateway_id, name); + if (saved) { refreshRuntimeGatewayNames(); for (const auto& sink : gateway_name_sinks_) { - sink(gateway_id); + sink(op == 0x03 ? 0xff : gateway_id); } publishPayload(gateway_id, {0x05, gateway_id, op, 0x01}); } else { diff --git a/components/gateway_runtime/include/gateway_runtime.hpp b/components/gateway_runtime/include/gateway_runtime.hpp index 2181dda..1b5a225 100644 --- a/components/gateway_runtime/include/gateway_runtime.hpp +++ b/components/gateway_runtime/include/gateway_runtime.hpp @@ -74,6 +74,8 @@ class GatewaySettingsStore { bool setWifiCredentials(std::string_view ssid, std::string_view password); bool clearWifiCredentials(); + std::string getDeviceName(std::string_view fallback) const; + bool setDeviceName(std::string_view name); std::string getGatewayName(uint8_t gateway_id, std::string_view fallback) const; bool setGatewayName(uint8_t gateway_id, std::string_view name); uint8_t getChannelGatewayId(uint8_t channel_index, uint8_t fallback) const; @@ -140,6 +142,8 @@ class GatewayRuntime { bool setCacheEnabled(bool enabled); uint8_t gatewayIdForChannel(uint8_t channel_index, uint8_t fallback) const; bool setGatewayIdForChannel(uint8_t channel_index, uint8_t gateway_id); + std::string deviceName() const; + bool setDeviceName(std::string_view name); std::string gatewayName(uint8_t gateway_id) const; bool setGatewayName(uint8_t gateway_id, std::string_view name); std::vector serialNumberBytes() const; @@ -154,6 +158,7 @@ class GatewayRuntime { std::deque>& queueForPriorityLocked(CommandPriority priority); const std::deque>& queueForPriorityLocked(CommandPriority priority) const; std::optional queryCommandKey(const std::vector& command) const; + std::string defaultDeviceName() const; std::string defaultGatewayName(uint8_t gateway_id) const; std::vector serialBytes() const; static std::string toHex(const std::vector& bytes); @@ -167,6 +172,7 @@ class GatewayRuntime { std::deque> control_commands_; std::deque> normal_commands_; std::deque> maintenance_commands_; + mutable std::optional device_name_; mutable std::map gateway_names_; mutable std::map channel_gateway_ids_; size_t gateway_count_{0}; diff --git a/components/gateway_runtime/src/gateway_runtime.cpp b/components/gateway_runtime/src/gateway_runtime.cpp index 956b9a4..74565d0 100644 --- a/components/gateway_runtime/src/gateway_runtime.cpp +++ b/components/gateway_runtime/src/gateway_runtime.cpp @@ -19,6 +19,7 @@ constexpr const char* kTag = "gateway_runtime"; constexpr const char* kNamespace = "gateway_rt"; constexpr const char* kBleEnabledKey = "ble_enabled"; constexpr const char* kCacheEnabledKey = "cache_enabled"; +constexpr const char* kDeviceNameKey = "device_name"; constexpr const char* kWifiSsidKey = "wifi_ssid"; constexpr const char* kWifiPasswordKey = "wifi_passwd"; constexpr size_t kMaxGatewayNameBytes = 32; @@ -174,6 +175,19 @@ bool GatewaySettingsStore::clearWifiCredentials() { return ssid_err == ESP_OK && password_err == ESP_OK && nvs_commit(handle_) == ESP_OK; } +std::string GatewaySettingsStore::getDeviceName(std::string_view fallback) const { + const auto value = readString(kDeviceNameKey); + if (!value.has_value() || value->empty()) { + return std::string(fallback); + } + + return *value; +} + +bool GatewaySettingsStore::setDeviceName(std::string_view name) { + return writeString(kDeviceNameKey, name); +} + std::string GatewaySettingsStore::getGatewayName(uint8_t gateway_id, std::string_view fallback) const { const auto value = readString(makeGatewayNameKey(gateway_id)); @@ -584,6 +598,41 @@ bool GatewayRuntime::setGatewayIdForChannel(uint8_t channel_index, uint8_t gatew return true; } +std::string GatewayRuntime::deviceName() const { + LockGuard guard(command_lock_); + if (device_name_.has_value()) { + return device_name_.value(); + } + auto name = settings_.getDeviceName(defaultDeviceName()); + if (name.size() > kMaxGatewayNameBytes) { + name.resize(kMaxGatewayNameBytes); + } + device_name_ = name; + return name; +} + +bool GatewayRuntime::setDeviceName(std::string_view name) { + std::string normalized(name); + if (normalized.size() > kMaxGatewayNameBytes) { + normalized.resize(kMaxGatewayNameBytes); + } + if (normalized.empty()) { + normalized = defaultDeviceName(); + } + + if (deviceName() == normalized) { + return true; + } + + if (!settings_.setDeviceName(normalized)) { + return false; + } + + LockGuard guard(command_lock_); + device_name_ = normalized; + return true; +} + std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const { LockGuard guard(command_lock_); const auto cached = gateway_names_.find(gateway_id); @@ -640,11 +689,13 @@ std::string GatewayRuntime::bleMacHex() const { } std::string GatewayRuntime::bleGatewayName(uint8_t gateway_id, std::string_view gateway_name) const { - std::string normalized(gateway_name); + std::string normalized(deviceName()); if (normalized.size() > kMaxGatewayNameBytes) { normalized.resize(kMaxGatewayNameBytes); } - if (!normalized.empty() && normalized != defaultGatewayName(gateway_id)) { + (void)gateway_id; + (void)gateway_name; + if (!normalized.empty() && normalized != defaultDeviceName()) { return normalized; } return defaultBleGatewayName(); @@ -718,7 +769,7 @@ std::optional GatewayRuntime::queryCommandKey( return std::string(key); } -std::string GatewayRuntime::defaultGatewayName(uint8_t) const { +std::string GatewayRuntime::defaultDeviceName() const { const std::string serial_hex = bleMacHex(); if (serial_hex.size() <= 6) { return "DALIGW_" + serial_hex; @@ -726,6 +777,10 @@ std::string GatewayRuntime::defaultGatewayName(uint8_t) const { return "DALIGW_" + serial_hex.substr(serial_hex.size() - 6); } +std::string GatewayRuntime::defaultGatewayName(uint8_t gateway_id) const { + return "Channel " + std::to_string(gateway_id); +} + std::vector GatewayRuntime::serialBytes() const { std::vector bytes; const std::string& serial = config_.serial_id;