diff --git a/README.md b/README.md index 72e4929..879b8e1 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,26 @@ commands, address allocation, live management reads, bridge traffic, and any raw 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` advertises cache support with bit `0x40`. 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 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. + +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 +every possible gateway id one by one. For native C++ gateways, channel number is +the fixed 1-based Kconfig slot (`1` to `16`) and channel id is the persisted, +Lua-compatible gateway id used by normal `0x28 0x01 ...` command frames. + +Gateway opcode `0x0B` is the serial-scoped channel command. The serial is the +last three bytes of the ESP base MAC. Operation `0x00` reports serial plus +`(channel number, channel id)` pairs. Operations `0x01` and `0x02` get and set +the channel id for the fixed channel number in the command frame gateway byte. +Operation `0x03` wraps an existing gateway command and dispatches it by fixed +channel number after the serial matches, so BLE/Wi-Fi configuration and DALI +send/query commands can target a channel even when its variable channel id is +unknown. ## Current status @@ -113,4 +130,4 @@ BACnet/IP is owned by `gateway/components/gateway_bacnet` and is started through Provisioned BACnet models still use generic `BridgeModel` fields such as object type, object instance, property, and optional `bitIndex`. Query-style models refresh BACnet `Present_Value` from live DALI reads, and binary models with `bitIndex` expose a single packed status bit. -For discovered DALI short addresses, the gateway also mirrors the generated Modbus discrete diagnostics as BACnet binary-input objects. Object instances are allocated in a gateway-owned generated range using the channel index plus the generated Modbus discrete-input offset, so generated objects stay deterministic while avoiding the provisioned-object address space in normal deployments. \ No newline at end of file +For discovered DALI short addresses, the gateway also mirrors the generated Modbus discrete diagnostics as BACnet binary-input objects. Object instances are allocated in a gateway-owned generated range using the channel index plus the generated Modbus discrete-input offset, so generated objects stay deterministic while avoiding the provisioned-object address space in normal deployments. diff --git a/apps/gateway/main/app_main.cpp b/apps/gateway/main/app_main.cpp index ac899d3..6e96eef 100644 --- a/apps/gateway/main/app_main.cpp +++ b/apps/gateway/main/app_main.cpp @@ -893,14 +893,22 @@ bool ValidateChannelBindings() { esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain, const gateway::GatewayRuntime& runtime) { + std::array used_gateway_ids{}; for (const auto& channel : BuildChannelBindings()) { if (!channel.enabled) { continue; } + const uint8_t gateway_id = + runtime.gatewayIdForChannel(channel.channel_index, channel.gateway_id); + if (used_gateway_ids[gateway_id]) { + ESP_LOGE(kTag, "duplicate runtime gateway id configured: %u", gateway_id); + return ESP_ERR_INVALID_STATE; + } + used_gateway_ids[gateway_id] = true; if (channel.native_phy) { gateway::DaliHardwareBusConfig config{}; config.channel_index = channel.channel_index; - config.gateway_id = channel.gateway_id; + config.gateway_id = gateway_id; config.bus_id = channel.native_bus_id; config.tx_pin = static_cast(channel.native_tx_pin); config.rx_pin = static_cast(channel.native_rx_pin); @@ -914,7 +922,7 @@ esp_err_t BindConfiguredChannels(gateway::DaliDomainService& dali_domain, } else if (channel.serial_phy) { gateway::DaliSerialBusConfig config{}; config.channel_index = channel.channel_index; - config.gateway_id = channel.gateway_id; + config.gateway_id = gateway_id; config.uart_port = channel.uart_port; config.tx_pin = channel.serial_tx_pin; config.rx_pin = channel.serial_rx_pin; @@ -966,8 +974,8 @@ extern "C" void app_main(void) { }, s_dali_domain.get()); ESP_ERROR_CHECK(s_runtime->start()); - s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT); ESP_ERROR_CHECK(BindConfiguredChannels(*s_dali_domain, *s_runtime)); + s_runtime->setGatewayCount(s_dali_domain->channelCount()); gateway::GatewayCacheConfig cache_config; cache_config.cache_enabled = kCacheSupported && kCacheStartupEnabled && s_runtime->cacheEnabled(); @@ -1244,4 +1252,4 @@ extern "C" void app_main(void) { std::printf("gateway_main: runtime device type=%s serial=%s project=%s version=%s\n", device_info.type.c_str(), device_info.serial_id.c_str(), device_info.project.c_str(), device_info.version.c_str()); -} \ No newline at end of file +} diff --git a/components/dali_domain/include/dali_domain.hpp b/components/dali_domain/include/dali_domain.hpp index 800ebad..58cfc17 100644 --- a/components/dali_domain/include/dali_domain.hpp +++ b/components/dali_domain/include/dali_domain.hpp @@ -192,6 +192,8 @@ class DaliDomainService { std::optional level) const; bool applyAddressSettings(uint8_t gateway_id, int short_address, const DaliAddressSettingsSnapshot& settings) const; + std::optional gatewayIdForChannelIndex(uint8_t channel_index) const; + bool updateChannelGatewayId(uint8_t channel_index, uint8_t gateway_id); bool updateChannelName(uint8_t gateway_id, std::string_view name); bool allocateAllAddr(uint8_t gateway_id, int start_address = 0) const; void stopAllocAddr(uint8_t gateway_id) const; @@ -212,6 +214,7 @@ class DaliDomainService { DaliChannel* findChannelByGateway(uint8_t gateway_id); const DaliChannel* findChannelByGateway(uint8_t gateway_id) const; DaliChannel* findChannelByIndex(uint8_t channel_index); + const DaliChannel* findChannelByIndex(uint8_t channel_index) const; const DaliChannel* findChannelByHardwareBus(uint8_t bus_id) const; bool hasSerialPort(int uart_port) const; esp_err_t startSerialRxTask(DaliChannel& channel); diff --git a/components/dali_domain/src/dali_domain.cpp b/components/dali_domain/src/dali_domain.cpp index eb4e9a5..d345d0f 100644 --- a/components/dali_domain/src/dali_domain.cpp +++ b/components/dali_domain/src/dali_domain.cpp @@ -635,6 +635,31 @@ std::vector DaliDomainService::channelInfo() const { return info; } +std::optional DaliDomainService::gatewayIdForChannelIndex(uint8_t channel_index) const { + const auto* channel = findChannelByIndex(channel_index); + if (channel == nullptr) { + return std::nullopt; + } + return channel->config.gateway_id; +} + +bool DaliDomainService::updateChannelGatewayId(uint8_t channel_index, uint8_t gateway_id) { + auto* channel = findChannelByIndex(channel_index); + if (channel == nullptr) { + return false; + } + for (const auto& other : channels_) { + if (other.get() != channel && other->config.gateway_id == gateway_id) { + return false; + } + } + channel->config.gateway_id = gateway_id; + if (channel->dali != nullptr) { + channel->dali = std::make_unique(*channel->comm, gateway_id, channel->config.name); + } + return true; +} + void DaliDomainService::addRawFrameSink(std::function sink) { if (!sink) { return; @@ -1674,6 +1699,15 @@ DaliDomainService::DaliChannel* DaliDomainService::findChannelByIndex(uint8_t ch return it == channels_.end() ? nullptr : it->get(); } +const DaliDomainService::DaliChannel* DaliDomainService::findChannelByIndex( + uint8_t channel_index) const { + 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) { diff --git a/components/gateway_controller/include/gateway_controller.hpp b/components/gateway_controller/include/gateway_controller.hpp index 6aaf849..84967b1 100644 --- a/components/gateway_controller/include/gateway_controller.hpp +++ b/components/gateway_controller/include/gateway_controller.hpp @@ -139,6 +139,7 @@ class GatewayController { bool hasGateway(uint8_t gateway_id) const; std::vector gatewayIds() const; + std::optional gatewayIdForChannelNumber(uint8_t channel_number) const; std::string gatewayName(uint8_t gateway_id) const; void refreshRuntimeGatewayNames(); void publishPayload(uint8_t gateway_id, const std::vector& payload); @@ -181,6 +182,11 @@ class GatewayController { bool executeGroup(uint8_t gateway_id, uint8_t group_id); void handleGatewayNameCommand(uint8_t gateway_id, const std::vector& command); + void handleGatewaySerialCommand(uint8_t channel_number, const std::vector& command); + void publishGatewaySerialReport(); + void publishGatewaySerialResponse(uint8_t status, uint8_t op, + const std::vector& data); + bool gatewaySerialMatches(const std::vector& command) const; void handleGatewayIdentityCommand(uint8_t gateway_id, uint8_t op); void handleAllocationCommand(uint8_t gateway_id, const std::vector& command); void handleInternalSceneCommand(uint8_t gateway_id, const std::vector& command); diff --git a/components/gateway_controller/src/gateway_controller.cpp b/components/gateway_controller/src/gateway_controller.cpp index 2630b0e..dd4877e 100644 --- a/components/gateway_controller/src/gateway_controller.cpp +++ b/components/gateway_controller/src/gateway_controller.cpp @@ -28,13 +28,23 @@ constexpr uint8_t kBridgeTransportRequestOpcode = 0xB0; constexpr uint8_t kBridgeTransportResponseOpcode = 0xB1; constexpr uint8_t kBridgeTransportVersion = 1; constexpr size_t kBridgeTransportMaxChunkBytes = 120; +constexpr uint8_t kGatewaySerialOpcode = 0x0B; +constexpr uint8_t kGatewaySerialOpReport = 0x00; +constexpr uint8_t kGatewaySerialOpGetChannelId = 0x01; +constexpr uint8_t kGatewaySerialOpSetChannelId = 0x02; +constexpr uint8_t kGatewaySerialOpDispatchByChannelNumber = 0x03; +constexpr uint8_t kGatewaySerialStatusOk = 0x00; +constexpr uint8_t kGatewaySerialStatusInvalidArgument = 0x02; +constexpr uint8_t kGatewaySerialStatusDuplicateChannelId = 0x03; +constexpr uint8_t kGatewaySerialStatusStorageError = 0x04; 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 uint16_t kGatewayFeatureCache = 0x0040; +constexpr uint16_t kGatewayFeatureNativeCpp = 0x0100; constexpr uint8_t kGatewayCacheOpcode = 0x39; constexpr uint8_t kGatewayCacheProtocolVersion = 1; constexpr uint8_t kGatewayCacheOpSummary = 0x00; @@ -165,6 +175,13 @@ void AppendLe32(std::vector& out, uint32_t value) { out.push_back(static_cast((value >> 24) & 0xFF)); } +void AppendFeatureBits(std::vector& out, uint16_t feature) { + if (feature > 0xFF) { + out.push_back(static_cast((feature >> 8) & 0xFF)); + } + out.push_back(static_cast(feature & 0xFF)); +} + uint8_t CacheByte(std::optional value) { return value.value_or(0xFF); } @@ -759,6 +776,10 @@ void GatewayController::dispatchCommand(const std::vector& command) { publishPayload(gateway_id, payload); return; } + if (opcode == kGatewaySerialOpcode) { + handleGatewaySerialCommand(gateway_id, command); + return; + } if (!hasGateway(gateway_id)) { ESP_LOGW(kTag, "command for unknown gateway=%u opcode=0x%02x", gateway_id, opcode); return; @@ -825,7 +846,7 @@ void GatewayController::dispatchCommand(const std::vector& command) { handleGatewayNameCommand(gateway_id, command); break; case 0x06: { - uint8_t feature = 0; + uint16_t feature = kGatewayFeatureNativeCpp; if (setup_mode_ && config_.setup_supported) { feature |= 0x01; } @@ -844,7 +865,9 @@ void GatewayController::dispatchCommand(const std::vector& command) { if (config_.cache_supported) { feature |= kGatewayFeatureCache; } - publishPayload(gateway_id, {0x03, gateway_id, feature}); + std::vector payload{0x03, gateway_id}; + AppendFeatureBits(payload, feature); + publishPayload(gateway_id, payload); break; } case 0x07: @@ -995,6 +1018,23 @@ std::vector GatewayController::gatewayIds() const { return ids; } +std::optional GatewayController::gatewayIdForChannelNumber( + uint8_t channel_number) const { + if (channel_number == 0) { + return std::nullopt; + } + const uint8_t channel_index = static_cast(channel_number - 1); + const auto channels = dali_domain_.channelInfo(); + const auto it = + std::find_if(channels.begin(), channels.end(), [channel_index](const auto& channel) { + return channel.channel_index == channel_index; + }); + if (it == channels.end()) { + return std::nullopt; + } + return it->gateway_id; +} + std::string GatewayController::gatewayName(uint8_t gateway_id) const { const auto channels = dali_domain_.channelInfo(); const auto it = std::find_if(channels.begin(), channels.end(), [gateway_id](const auto& channel) { @@ -1618,6 +1658,117 @@ void GatewayController::handleGatewayNameCommand(uint8_t gateway_id, } } +void GatewayController::publishGatewaySerialResponse(uint8_t status, uint8_t op, + const std::vector& data) { + std::vector payload{kGatewaySerialOpcode, status}; + const auto serial = runtime_.serialNumberBytes(); + payload.insert(payload.end(), serial.begin(), serial.end()); + payload.push_back(op); + payload.insert(payload.end(), data.begin(), data.end()); + publishPayload(0, payload); +} + +bool GatewayController::gatewaySerialMatches(const std::vector& command) const { + if (command.size() < 9) { + return false; + } + const auto serial = runtime_.serialNumberBytes(); + return command[5] == serial[0] && command[6] == serial[1] && command[7] == serial[2]; +} + +void GatewayController::publishGatewaySerialReport() { + const auto serial = runtime_.serialNumberBytes(); + std::vector payload{kGatewaySerialOpcode, + kGatewaySerialStatusOk, + serial[0], + serial[1], + serial[2], + kGatewaySerialOpReport}; + const auto channels = dali_domain_.channelInfo(); + const auto count = std::min(channels.size(), 16); + payload.push_back(static_cast(count)); + for (size_t index = 0; index < count; ++index) { + payload.push_back(static_cast(channels[index].channel_index + 1)); + payload.push_back(channels[index].gateway_id); + } + publishPayload(0, payload); +} + +void GatewayController::handleGatewaySerialCommand(uint8_t channel_number, + const std::vector& command) { + const uint8_t op = command.size() > 4 ? command[4] : kGatewaySerialOpReport; + if (op == kGatewaySerialOpReport) { + if (command.size() >= 9 && !gatewaySerialMatches(command)) { + return; + } + publishGatewaySerialReport(); + return; + } + + if (!gatewaySerialMatches(command)) { + return; + } + + const auto gateway_id = gatewayIdForChannelNumber(channel_number); + if (!gateway_id.has_value()) { + publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op, {channel_number}); + return; + } + + switch (op) { + case kGatewaySerialOpGetChannelId: + publishGatewaySerialResponse(kGatewaySerialStatusOk, op, + {channel_number, gateway_id.value()}); + return; + case kGatewaySerialOpSetChannelId: { + if (command.size() < 10) { + publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op, {channel_number}); + return; + } + const uint8_t new_gateway_id = command[8]; + const uint8_t channel_index = static_cast(channel_number - 1); + const uint8_t old_gateway_id = gateway_id.value(); + if (!dali_domain_.updateChannelGatewayId(channel_index, new_gateway_id)) { + publishGatewaySerialResponse(kGatewaySerialStatusDuplicateChannelId, op, + {channel_number, new_gateway_id}); + return; + } + if (!runtime_.setGatewayIdForChannel(channel_index, new_gateway_id)) { + dali_domain_.updateChannelGatewayId(channel_index, old_gateway_id); + publishGatewaySerialResponse(kGatewaySerialStatusStorageError, op, + {channel_number, new_gateway_id}); + return; + } + cache_.preloadChannel(new_gateway_id); + reconciliation_jobs_.erase(old_gateway_id); + cache_refresh_jobs_.erase(old_gateway_id); + refreshRuntimeGatewayNames(); + publishGatewaySerialResponse(kGatewaySerialStatusOk, op, + {channel_number, new_gateway_id}); + return; + } + case kGatewaySerialOpDispatchByChannelNumber: { + if (command.size() < 12) { + publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op, {channel_number}); + return; + } + const uint8_t inner_opcode = command[8]; + if (inner_opcode == kGatewaySerialOpcode) { + publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op, + {channel_number, inner_opcode}); + return; + } + std::vector inner{0x28, 0x01, gateway_id.value(), inner_opcode}; + inner.insert(inner.end(), command.begin() + 9, command.end() - 1); + dispatchCommand(GatewayRuntime::checksum(std::move(inner))); + return; + } + default: + publishGatewaySerialResponse(kGatewaySerialStatusInvalidArgument, op, {channel_number}); + return; + } +} + void GatewayController::handleGatewayIdentityCommand(uint8_t gateway_id, uint8_t op) { std::string value; if (op == 0x00) { diff --git a/components/gateway_runtime/include/gateway_runtime.hpp b/components/gateway_runtime/include/gateway_runtime.hpp index d0cb382..2181dda 100644 --- a/components/gateway_runtime/include/gateway_runtime.hpp +++ b/components/gateway_runtime/include/gateway_runtime.hpp @@ -76,11 +76,14 @@ class GatewaySettingsStore { 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; + bool setChannelGatewayId(uint8_t channel_index, uint8_t gateway_id); private: std::optional readString(std::string_view key) const; bool writeString(std::string_view key, std::string_view value); std::string makeGatewayNameKey(uint8_t gateway_id) const; + std::string makeChannelGatewayIdKey(uint8_t channel_index) const; mutable nvs_handle_t handle_{0}; }; @@ -135,8 +138,11 @@ class GatewayRuntime { bool setBleEnabled(bool enabled); bool cacheEnabled() const; 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 gatewayName(uint8_t gateway_id) const; bool setGatewayName(uint8_t gateway_id, std::string_view name); + std::vector serialNumberBytes() const; std::string gatewaySerialHex(uint8_t gateway_id) const; std::string bleMacHex() const; std::string bleGatewayName(uint8_t gateway_id, std::string_view gateway_name) const; @@ -162,6 +168,7 @@ class GatewayRuntime { std::deque> normal_commands_; std::deque> maintenance_commands_; mutable std::map gateway_names_; + mutable std::map channel_gateway_ids_; size_t gateway_count_{0}; bool ble_enabled_{false}; bool cache_enabled_{true}; @@ -172,4 +179,4 @@ class GatewayRuntime { SemaphoreHandle_t command_lock_{nullptr}; }; -} // namespace gateway \ No newline at end of file +} // namespace gateway diff --git a/components/gateway_runtime/src/gateway_runtime.cpp b/components/gateway_runtime/src/gateway_runtime.cpp index 2a27e8b..956b9a4 100644 --- a/components/gateway_runtime/src/gateway_runtime.cpp +++ b/components/gateway_runtime/src/gateway_runtime.cpp @@ -57,7 +57,8 @@ esp_err_t InitializeRuntimeNvs() { std::string ReadRuntimeSerialId() { uint8_t mac[6] = {0}; - if (esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) { + if (esp_read_mac(mac, ESP_MAC_BASE) != ESP_OK && + esp_read_mac(mac, ESP_MAC_WIFI_STA) != ESP_OK) { return "DALIGW"; } @@ -188,6 +189,31 @@ bool GatewaySettingsStore::setGatewayName(uint8_t gateway_id, return writeString(makeGatewayNameKey(gateway_id), name); } +uint8_t GatewaySettingsStore::getChannelGatewayId(uint8_t channel_index, + uint8_t fallback) const { + if (handle_ == 0) { + return fallback; + } + + uint8_t gateway_id = fallback; + if (nvs_get_u8(handle_, makeChannelGatewayIdKey(channel_index).c_str(), &gateway_id) != + ESP_OK) { + return fallback; + } + return gateway_id; +} + +bool GatewaySettingsStore::setChannelGatewayId(uint8_t channel_index, + uint8_t gateway_id) { + if (handle_ == 0) { + return false; + } + + return nvs_set_u8(handle_, makeChannelGatewayIdKey(channel_index).c_str(), gateway_id) == + ESP_OK && + nvs_commit(handle_) == ESP_OK; +} + std::optional GatewaySettingsStore::readString(std::string_view key) const { if (handle_ == 0) { return std::nullopt; @@ -222,6 +248,12 @@ std::string GatewaySettingsStore::makeGatewayNameKey(uint8_t gateway_id) const { return std::string(key); } +std::string GatewaySettingsStore::makeChannelGatewayIdKey(uint8_t channel_index) const { + char key[24] = {0}; + std::snprintf(key, sizeof(key), "dali_ch_id_%u", channel_index); + return std::string(key); +} + GatewayRuntime::GatewayRuntime(BootProfile profile, GatewayRuntimeConfig config, DaliDomainService* dali_domain) : profile_(profile), @@ -323,7 +355,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 == 0x0B || opcode == 0x17 || opcode == 0x18 || opcode == 0x37 || opcode == 0x38 || opcode == 0x60 || opcode == 0x61 || opcode == 0x62 || (opcode == 0x30 && addr == 0)) { return CommandPriority::kControl; } @@ -531,6 +563,27 @@ bool GatewayRuntime::setCacheEnabled(bool enabled) { return true; } +uint8_t GatewayRuntime::gatewayIdForChannel(uint8_t channel_index, uint8_t fallback) const { + LockGuard guard(command_lock_); + const auto cached = channel_gateway_ids_.find(channel_index); + if (cached != channel_gateway_ids_.end()) { + return cached->second; + } + const uint8_t gateway_id = settings_.getChannelGatewayId(channel_index, fallback); + channel_gateway_ids_[channel_index] = gateway_id; + return gateway_id; +} + +bool GatewayRuntime::setGatewayIdForChannel(uint8_t channel_index, uint8_t gateway_id) { + if (!settings_.setChannelGatewayId(channel_index, gateway_id)) { + return false; + } + + LockGuard guard(command_lock_); + channel_gateway_ids_[channel_index] = gateway_id; + return true; +} + std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const { LockGuard guard(command_lock_); const auto cached = gateway_names_.find(gateway_id); @@ -577,6 +630,11 @@ std::string GatewayRuntime::gatewaySerialHex(uint8_t gateway_id) const { return toHex(serial); } +std::vector GatewayRuntime::serialNumberBytes() const { + const auto bytes = serialBytes(); + return {bytes[3], bytes[4], bytes[5]}; +} + std::string GatewayRuntime::bleMacHex() const { return toHex(serialBytes()); }