feat(gateway): implement GatewayController and enhance GatewayRuntime
- Added GatewayController class to manage gateway operations, including command handling, scene and group management, and BLE state management. - Introduced methods for scene and group storage, including loading, saving, and deleting scenes/groups. - Enhanced GatewayRuntime with BLE management capabilities, including methods to check and set BLE state, and to generate BLE gateway names. - Implemented utility functions for string normalization and CSV parsing. - Added notification sinks for various events in the GatewayController. Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -10,8 +10,10 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
||||
- `gateway_core/`: boot profile and top-level role bootstrap.
|
||||
- `dali/`: vendored ESP-IDF DALI HAL/backend reused from LuatOS.
|
||||
- `dali_domain/`: native DALI domain facade over `dali_cpp`.
|
||||
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`.
|
||||
- `gateway_controller/`: Lua-compatible gateway command dispatcher, internal scene/group state, and notification fan-out.
|
||||
- `gateway_runtime/`: persistent runtime state, command queueing, and device info services.
|
||||
|
||||
## Current status
|
||||
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port, and an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY.
|
||||
The native rewrite now wires a shared `gateway_core` bootstrap component, a multi-channel `dali_domain` wrapper over `dali_cpp`, a local vendored `dali` hardware backend from the LuatOS ESP-IDF port, an initial `gateway_runtime` service that provides persistent settings, device info, Lua-compatible command framing helpers, and Lua-style query command deduplication, plus a `gateway_controller` service that starts the gateway command task, dispatches core Lua gateway opcodes, and owns internal scene/group state. The gateway app now also includes an initial `gateway_ble` NimBLE bridge that advertises a Lua-compatible GATT service and forwards `FFF3` framed notifications plus incoming `FFF1`/`FFF2`/`FFF3` writes into the native controller and DALI domain. The gateway app exposes per-channel PHY selection through `main/Kconfig.projbuild`; each channel can be disabled, bound to the native DALI GPIO HAL, or bound to a UART1/UART2 serial PHY.
|
||||
@@ -1,6 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS "app_main.cpp"
|
||||
REQUIRES gateway_core dali_domain gateway_runtime log
|
||||
REQUIRES gateway_core gateway_controller dali_domain gateway_runtime gateway_ble log
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -1,10 +1,13 @@
|
||||
#include "dali_domain.hpp"
|
||||
#include "gateway_ble.hpp"
|
||||
#include "gateway_controller.hpp"
|
||||
#include "gateway_core.hpp"
|
||||
#include "gateway_runtime.hpp"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
#ifndef CONFIG_GATEWAY_CHANNEL_COUNT
|
||||
#define CONFIG_GATEWAY_CHANNEL_COUNT 2
|
||||
@@ -14,6 +17,11 @@ namespace {
|
||||
constexpr const char* kProjectName = "DALI_485_Gateway";
|
||||
constexpr const char* kProjectVersion = "0.1.0";
|
||||
|
||||
std::unique_ptr<gateway::DaliDomainService> s_dali_domain;
|
||||
std::unique_ptr<gateway::GatewayRuntime> s_runtime;
|
||||
std::unique_ptr<gateway::GatewayController> s_controller;
|
||||
std::unique_ptr<gateway::GatewayBleBridge> s_ble_bridge;
|
||||
|
||||
[[maybe_unused]] void LogBindError(const char* channel_name, esp_err_t err) {
|
||||
if (err != ESP_OK) {
|
||||
std::printf("gateway_main: failed to bind %s err=%d\n", channel_name, err);
|
||||
@@ -99,24 +107,42 @@ extern "C" void app_main(void) {
|
||||
gateway::GatewayCore core(profile);
|
||||
core.start();
|
||||
|
||||
gateway::DaliDomainService dali_domain;
|
||||
gateway::GatewayRuntime runtime(
|
||||
s_dali_domain = std::make_unique<gateway::DaliDomainService>();
|
||||
s_runtime = std::make_unique<gateway::GatewayRuntime>(
|
||||
profile,
|
||||
gateway::GatewayRuntimeConfig{
|
||||
kProjectName,
|
||||
kProjectVersion,
|
||||
gateway::ReadRuntimeSerialId(),
|
||||
},
|
||||
&dali_domain);
|
||||
ESP_ERROR_CHECK(runtime.start());
|
||||
runtime.setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT);
|
||||
BindConfiguredChannels(dali_domain, runtime);
|
||||
s_dali_domain.get());
|
||||
ESP_ERROR_CHECK(s_runtime->start());
|
||||
s_runtime->setGatewayCount(CONFIG_GATEWAY_CHANNEL_COUNT);
|
||||
BindConfiguredChannels(*s_dali_domain, *s_runtime);
|
||||
|
||||
const auto device_info = runtime.deviceInfo();
|
||||
gateway::GatewayControllerConfig controller_config;
|
||||
controller_config.setup_supported = true;
|
||||
controller_config.ble_supported = profile.enable_ble;
|
||||
controller_config.wifi_supported = profile.enable_wifi;
|
||||
controller_config.ip_router_supported = profile.enable_wifi || profile.enable_eth;
|
||||
controller_config.internal_scene_supported = true;
|
||||
controller_config.internal_group_supported = true;
|
||||
|
||||
s_controller = std::make_unique<gateway::GatewayController>(*s_runtime, *s_dali_domain,
|
||||
controller_config);
|
||||
ESP_ERROR_CHECK(s_controller->start());
|
||||
|
||||
if (profile.enable_ble) {
|
||||
s_ble_bridge = std::make_unique<gateway::GatewayBleBridge>(*s_controller, *s_runtime,
|
||||
*s_dali_domain);
|
||||
ESP_ERROR_CHECK(s_ble_bridge->start());
|
||||
}
|
||||
|
||||
const auto device_info = s_runtime->deviceInfo();
|
||||
std::printf("gateway_main: dali domain implementation=%s bound=%d channels=%u\n",
|
||||
dali_domain.implementationName(), dali_domain.isBound(),
|
||||
static_cast<unsigned>(dali_domain.channelCount()));
|
||||
for (const auto& channel : dali_domain.channelInfo()) {
|
||||
s_dali_domain->implementationName(), s_dali_domain->isBound(),
|
||||
static_cast<unsigned>(s_dali_domain->channelCount()));
|
||||
for (const auto& channel : s_dali_domain->channelInfo()) {
|
||||
std::printf("gateway_main: channel=%u gateway=%u name=%s\n", channel.channel_index,
|
||||
channel.gateway_id, channel.name.c_str());
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
@@ -79,6 +80,9 @@ class DaliDomainService {
|
||||
std::vector<DaliChannelInfo> channelInfo() const;
|
||||
|
||||
bool resetBus(uint8_t gateway_id) const;
|
||||
bool writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const;
|
||||
std::vector<uint8_t> transactBridgeFrame(uint8_t gateway_id, const uint8_t* data,
|
||||
size_t len) const;
|
||||
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<uint8_t> queryRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const;
|
||||
@@ -90,6 +94,13 @@ class DaliDomainService {
|
||||
bool on(uint8_t gateway_id, int short_address) const;
|
||||
bool off(uint8_t gateway_id, int short_address) const;
|
||||
bool off(int short_address) const;
|
||||
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;
|
||||
bool resetAndAllocAddr(uint8_t gateway_id, int start_address = 0,
|
||||
bool remove_addr_first = false, bool close_light = false) const;
|
||||
bool isAllocAddr(uint8_t gateway_id) const;
|
||||
int lastAllocAddr(uint8_t gateway_id) const;
|
||||
|
||||
private:
|
||||
struct DaliChannel;
|
||||
|
||||
@@ -295,6 +295,21 @@ bool DaliDomainService::resetBus(uint8_t gateway_id) const {
|
||||
return channel != nullptr && channel->comm != nullptr && channel->comm->resetBus();
|
||||
}
|
||||
|
||||
bool DaliDomainService::writeBridgeFrame(uint8_t gateway_id, const uint8_t* data, size_t len) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->hooks.send && channel->hooks.send(data, len);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DaliDomainService::transactBridgeFrame(uint8_t gateway_id,
|
||||
const uint8_t* data,
|
||||
size_t len) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
if (channel == nullptr || !channel->hooks.transact) {
|
||||
return {};
|
||||
}
|
||||
return channel->hooks.transact(data, len);
|
||||
}
|
||||
|
||||
bool DaliDomainService::sendRaw(uint8_t gateway_id, uint8_t raw_addr, uint8_t command) const {
|
||||
const auto* channel = findChannelByGateway(gateway_id);
|
||||
return channel != nullptr && channel->comm != nullptr && channel->comm->sendRawNew(raw_addr, command);
|
||||
@@ -363,6 +378,51 @@ bool DaliDomainService::off(int short_address) const {
|
||||
return off(channels_.front()->config.gateway_id, short_address);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_ble.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES gateway_controller gateway_runtime dali_domain bt esp_timer freertos log nvs_flash
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
struct ble_gap_event;
|
||||
struct ble_gatt_access_ctxt;
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class DaliDomainService;
|
||||
class GatewayController;
|
||||
class GatewayRuntime;
|
||||
|
||||
class GatewayBleBridge {
|
||||
public:
|
||||
GatewayBleBridge(GatewayController& controller, GatewayRuntime& runtime,
|
||||
DaliDomainService& dali_domain);
|
||||
|
||||
esp_err_t start();
|
||||
void handleSync();
|
||||
int handleGapEvent(struct ble_gap_event* event);
|
||||
int handleAccess(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt* ctxt);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kInvalidConnectionHandle = 0xffff;
|
||||
|
||||
esp_err_t initNimble();
|
||||
void refreshDeviceName();
|
||||
void setEnabled(bool enabled);
|
||||
void startAdvertising();
|
||||
void stopAdvertising();
|
||||
void notifyCharacteristic(size_t index, const std::vector<uint8_t>& payload);
|
||||
void handleGatewayNotification(const std::vector<uint8_t>& frame);
|
||||
void handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload);
|
||||
void handleGatewayWrite(const std::vector<uint8_t>& payload);
|
||||
std::string resolvedDeviceName() const;
|
||||
int characteristicIndex(uint16_t attr_handle) const;
|
||||
|
||||
GatewayController& controller_;
|
||||
GatewayRuntime& runtime_;
|
||||
DaliDomainService& dali_domain_;
|
||||
bool started_{false};
|
||||
bool synced_{false};
|
||||
bool enabled_{false};
|
||||
uint8_t own_addr_type_{0};
|
||||
uint16_t conn_handle_{kInvalidConnectionHandle};
|
||||
std::string device_name_;
|
||||
std::array<bool, 3> notify_enabled_{};
|
||||
std::array<std::vector<uint8_t>, 3> characteristic_values_{};
|
||||
std::vector<uint8_t> last_notify_payload_;
|
||||
int64_t last_notify_at_us_{0};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,520 @@
|
||||
#include "gateway_ble.hpp"
|
||||
|
||||
#include "dali_domain.hpp"
|
||||
#include "gateway_controller.hpp"
|
||||
#include "gateway_runtime.hpp"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "host/ble_gap.h"
|
||||
#include "host/ble_gatt.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_store.h"
|
||||
#include "host/util/util.h"
|
||||
#include "nimble/ble.h"
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
#include "store/config/ble_store_config.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kTag = "gateway_ble";
|
||||
constexpr uint16_t kServiceUuid = 0xFFF6;
|
||||
constexpr uint16_t kChannel1Uuid = 0xFFF1;
|
||||
constexpr uint16_t kChannel2Uuid = 0xFFF2;
|
||||
constexpr uint16_t kGatewayUuid = 0xFFF3;
|
||||
constexpr int64_t kGenericDedupeWindowUs = 120000;
|
||||
constexpr size_t kGatewayCharacteristicIndex = 2;
|
||||
|
||||
gateway::GatewayBleBridge* s_active_bridge = nullptr;
|
||||
uint16_t s_value_handles[3] = {0, 0, 0};
|
||||
|
||||
int GapEvent(struct ble_gap_event* event, void* arg);
|
||||
int AccessCharacteristic(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt* ctxt, void* arg);
|
||||
|
||||
const ble_uuid16_t kServiceUuidDef = BLE_UUID16_INIT(kServiceUuid);
|
||||
const ble_uuid16_t kChannel1UuidDef = BLE_UUID16_INIT(kChannel1Uuid);
|
||||
const ble_uuid16_t kChannel2UuidDef = BLE_UUID16_INIT(kChannel2Uuid);
|
||||
const ble_uuid16_t kGatewayUuidDef = BLE_UUID16_INIT(kGatewayUuid);
|
||||
|
||||
const ble_gatt_chr_def kGattCharacteristics[] = {
|
||||
{
|
||||
.uuid = &kChannel1UuidDef.u,
|
||||
.access_cb = AccessCharacteristic,
|
||||
.arg = nullptr,
|
||||
.descriptors = nullptr,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP |
|
||||
BLE_GATT_CHR_F_NOTIFY,
|
||||
.min_key_size = 0,
|
||||
.val_handle = &s_value_handles[0],
|
||||
.cpfd = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = &kChannel2UuidDef.u,
|
||||
.access_cb = AccessCharacteristic,
|
||||
.arg = nullptr,
|
||||
.descriptors = nullptr,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP |
|
||||
BLE_GATT_CHR_F_NOTIFY,
|
||||
.min_key_size = 0,
|
||||
.val_handle = &s_value_handles[1],
|
||||
.cpfd = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = &kGatewayUuidDef.u,
|
||||
.access_cb = AccessCharacteristic,
|
||||
.arg = nullptr,
|
||||
.descriptors = nullptr,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP |
|
||||
BLE_GATT_CHR_F_NOTIFY,
|
||||
.min_key_size = 0,
|
||||
.val_handle = &s_value_handles[2],
|
||||
.cpfd = nullptr,
|
||||
},
|
||||
{
|
||||
.uuid = nullptr,
|
||||
.access_cb = nullptr,
|
||||
.arg = nullptr,
|
||||
.descriptors = nullptr,
|
||||
.flags = 0,
|
||||
.min_key_size = 0,
|
||||
.val_handle = nullptr,
|
||||
.cpfd = nullptr,
|
||||
},
|
||||
};
|
||||
|
||||
extern "C" void ble_store_config_init(void);
|
||||
|
||||
int GapEvent(struct ble_gap_event* event, void* arg) {
|
||||
auto* bridge = static_cast<gateway::GatewayBleBridge*>(arg != nullptr ? arg : s_active_bridge);
|
||||
return bridge != nullptr ? bridge->handleGapEvent(event) : 0;
|
||||
}
|
||||
|
||||
int AccessCharacteristic(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
auto* bridge = static_cast<gateway::GatewayBleBridge*>(arg != nullptr ? arg : s_active_bridge);
|
||||
return bridge != nullptr ? bridge->handleAccess(conn_handle, attr_handle, ctxt)
|
||||
: BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
void OnReset(int reason) {
|
||||
ESP_LOGW(kTag, "nimble reset reason=%d", reason);
|
||||
}
|
||||
|
||||
void OnSync(void) {
|
||||
if (s_active_bridge == nullptr) {
|
||||
return;
|
||||
}
|
||||
s_active_bridge->handleSync();
|
||||
}
|
||||
|
||||
void HostTask(void* param) {
|
||||
(void)param;
|
||||
nimble_port_run();
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
void RegisterGatt(struct ble_gatt_register_ctxt* ctxt, void* arg) {
|
||||
(void)arg;
|
||||
char buffer[BLE_UUID_STR_LEN] = {0};
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_REGISTER_OP_SVC:
|
||||
ESP_LOGD(kTag, "registered service %s handle=%u",
|
||||
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buffer), ctxt->svc.handle);
|
||||
break;
|
||||
case BLE_GATT_REGISTER_OP_CHR:
|
||||
ESP_LOGD(kTag, "registered characteristic %s def=%u val=%u",
|
||||
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buffer), ctxt->chr.def_handle,
|
||||
ctxt->chr.val_handle);
|
||||
break;
|
||||
case BLE_GATT_REGISTER_OP_DSC:
|
||||
ESP_LOGD(kTag, "registered descriptor %s handle=%u",
|
||||
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buffer), ctxt->dsc.handle);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const struct ble_gatt_svc_def kGattServices[] = {
|
||||
{
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = &kServiceUuidDef.u,
|
||||
.includes = nullptr,
|
||||
.characteristics = kGattCharacteristics,
|
||||
},
|
||||
{
|
||||
.type = 0,
|
||||
.uuid = nullptr,
|
||||
.includes = nullptr,
|
||||
.characteristics = nullptr,
|
||||
},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace gateway {
|
||||
|
||||
GatewayBleBridge::GatewayBleBridge(GatewayController& controller, GatewayRuntime& runtime,
|
||||
DaliDomainService& dali_domain)
|
||||
: controller_(controller), runtime_(runtime), dali_domain_(dali_domain) {}
|
||||
|
||||
esp_err_t GatewayBleBridge::start() {
|
||||
if (started_) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_active_bridge = this;
|
||||
enabled_ = runtime_.bleEnabled();
|
||||
refreshDeviceName();
|
||||
|
||||
controller_.addNotificationSink(
|
||||
[this](const std::vector<uint8_t>& frame) { handleGatewayNotification(frame); });
|
||||
controller_.addBleStateSink([this](bool enabled) { setEnabled(enabled); });
|
||||
controller_.addGatewayNameSink([this](uint8_t) { refreshDeviceName(); });
|
||||
|
||||
const esp_err_t err = initNimble();
|
||||
if (err != ESP_OK) {
|
||||
s_active_bridge = nullptr;
|
||||
return err;
|
||||
}
|
||||
|
||||
started_ = true;
|
||||
ESP_LOGI(kTag, "BLE bridge started enabled=%d name=%s", enabled_, device_name_.c_str());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t GatewayBleBridge::initNimble() {
|
||||
const esp_err_t err = nimble_port_init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to init NimBLE err=%s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
ble_hs_cfg.reset_cb = OnReset;
|
||||
ble_hs_cfg.sync_cb = OnSync;
|
||||
ble_hs_cfg.gatts_register_cb = RegisterGatt;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
|
||||
int rc = ble_gatts_count_cfg(kGattServices);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(kTag, "failed to count GATT services rc=%d", rc);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
rc = ble_gatts_add_svcs(kGattServices);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(kTag, "failed to add GATT services rc=%d", rc);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_NIMBLE_GAP_SERVICE
|
||||
rc = ble_svc_gap_device_name_set(device_name_.c_str());
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(kTag, "failed to set BLE device name rc=%d", rc);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
#endif
|
||||
|
||||
ble_store_config_init();
|
||||
nimble_port_freertos_init(HostTask);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void GatewayBleBridge::handleSync() {
|
||||
int rc = ble_hs_util_ensure_addr(0);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(kTag, "failed to ensure BLE address rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = ble_hs_id_infer_auto(0, &own_addr_type_);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(kTag, "failed to infer BLE address type rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
synced_ = true;
|
||||
if (enabled_) {
|
||||
startAdvertising();
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBleBridge::refreshDeviceName() {
|
||||
device_name_ = resolvedDeviceName();
|
||||
|
||||
if (!started_) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if CONFIG_BT_NIMBLE_GAP_SERVICE
|
||||
const int rc = ble_svc_gap_device_name_set(device_name_.c_str());
|
||||
if (rc != 0) {
|
||||
ESP_LOGW(kTag, "failed to refresh BLE name rc=%d", rc);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (synced_ && enabled_ && conn_handle_ == kInvalidConnectionHandle) {
|
||||
stopAdvertising();
|
||||
startAdvertising();
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBleBridge::setEnabled(bool enabled) {
|
||||
enabled_ = enabled;
|
||||
if (!synced_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabled_) {
|
||||
refreshDeviceName();
|
||||
if (conn_handle_ == kInvalidConnectionHandle) {
|
||||
startAdvertising();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
stopAdvertising();
|
||||
if (conn_handle_ != kInvalidConnectionHandle) {
|
||||
const int rc = ble_gap_terminate(conn_handle_, BLE_ERR_REM_USER_CONN_TERM);
|
||||
if (rc != 0) {
|
||||
ESP_LOGW(kTag, "failed to terminate BLE connection rc=%d", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBleBridge::startAdvertising() {
|
||||
if (!enabled_ || ble_gap_adv_active()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ble_uuid16_t advertised_service = BLE_UUID16_INIT(kServiceUuid);
|
||||
struct ble_hs_adv_fields fields = {};
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
fields.tx_pwr_lvl_is_present = 1;
|
||||
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
fields.name = reinterpret_cast<uint8_t*>(device_name_.data());
|
||||
fields.name_len = device_name_.size();
|
||||
fields.name_is_complete = 1;
|
||||
fields.uuids16 = &advertised_service;
|
||||
fields.num_uuids16 = 1;
|
||||
fields.uuids16_is_complete = 1;
|
||||
|
||||
int rc = ble_gap_adv_set_fields(&fields);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(kTag, "failed to set BLE advertising fields rc=%d", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
struct ble_gap_adv_params params = {};
|
||||
params.conn_mode = BLE_GAP_CONN_MODE_UND;
|
||||
params.disc_mode = BLE_GAP_DISC_MODE_GEN;
|
||||
rc = ble_gap_adv_start(own_addr_type_, nullptr, BLE_HS_FOREVER, ¶ms, GapEvent, this);
|
||||
if (rc != 0) {
|
||||
ESP_LOGE(kTag, "failed to start BLE advertising rc=%d", rc);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBleBridge::stopAdvertising() {
|
||||
if (!ble_gap_adv_active()) {
|
||||
return;
|
||||
}
|
||||
const int rc = ble_gap_adv_stop();
|
||||
if (rc != 0) {
|
||||
ESP_LOGW(kTag, "failed to stop BLE advertising rc=%d", rc);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBleBridge::notifyCharacteristic(size_t index, const std::vector<uint8_t>& payload) {
|
||||
if (index >= std::size(s_value_handles) || conn_handle_ == kInvalidConnectionHandle ||
|
||||
!notify_enabled_[index]) {
|
||||
return;
|
||||
}
|
||||
|
||||
characteristic_values_[index] = payload;
|
||||
struct os_mbuf* buffer = ble_hs_mbuf_from_flat(payload.data(), payload.size());
|
||||
if (buffer == nullptr) {
|
||||
ESP_LOGW(kTag, "failed to allocate notify mbuf idx=%u", static_cast<unsigned>(index));
|
||||
return;
|
||||
}
|
||||
const int rc = ble_gatts_notify_custom(conn_handle_, s_value_handles[index], buffer);
|
||||
if (rc != 0) {
|
||||
ESP_LOGW(kTag, "failed to send BLE notify idx=%u rc=%d", static_cast<unsigned>(index), rc);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBleBridge::handleGatewayNotification(const std::vector<uint8_t>& frame) {
|
||||
if (!enabled_ || conn_handle_ == kInvalidConnectionHandle || !notify_enabled_[kGatewayCharacteristicIndex] ||
|
||||
frame.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int64_t now = esp_timer_get_time();
|
||||
int64_t dedupe_window = kGenericDedupeWindowUs;
|
||||
if (frame.size() > 1 && (frame[1] == 0x03 || frame[1] == 0x04)) {
|
||||
dedupe_window = 0;
|
||||
}
|
||||
if (dedupe_window > 0 && frame == last_notify_payload_ &&
|
||||
(now - last_notify_at_us_) <= dedupe_window) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifyCharacteristic(kGatewayCharacteristicIndex, frame);
|
||||
last_notify_payload_ = frame;
|
||||
last_notify_at_us_ = now;
|
||||
}
|
||||
|
||||
void GatewayBleBridge::handleRawWrite(size_t channel_index, const std::vector<uint8_t>& payload) {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
const auto channel_it = std::find_if(channels.begin(), channels.end(),
|
||||
[channel_index](const DaliChannelInfo& channel) {
|
||||
return channel.channel_index == channel_index;
|
||||
});
|
||||
if (channel_it == channels.end()) {
|
||||
ESP_LOGW(kTag, "raw BLE write for unavailable DALI channel=%u",
|
||||
static_cast<unsigned>(channel_index));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!payload.empty() && payload[0] == 0x12) {
|
||||
const auto response =
|
||||
dali_domain_.transactBridgeFrame(channel_it->gateway_id, payload.data(), payload.size());
|
||||
if (!response.empty()) {
|
||||
notifyCharacteristic(channel_index, response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dali_domain_.writeBridgeFrame(channel_it->gateway_id, payload.data(), payload.size())) {
|
||||
ESP_LOGW(kTag, "failed to forward raw BLE payload channel=%u len=%u",
|
||||
static_cast<unsigned>(channel_index), static_cast<unsigned>(payload.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayBleBridge::handleGatewayWrite(const std::vector<uint8_t>& payload) {
|
||||
if (payload.empty()) {
|
||||
return;
|
||||
}
|
||||
if (runtime_.hasPendingQueryCommand(payload)) {
|
||||
return;
|
||||
}
|
||||
controller_.enqueueCommandFrame(payload);
|
||||
}
|
||||
|
||||
std::string GatewayBleBridge::resolvedDeviceName() const {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
if (channels.empty()) {
|
||||
return runtime_.defaultBleGatewayName();
|
||||
}
|
||||
|
||||
const auto channel_it = std::min_element(channels.begin(), channels.end(),
|
||||
[](const DaliChannelInfo& lhs,
|
||||
const DaliChannelInfo& rhs) {
|
||||
return lhs.channel_index < rhs.channel_index;
|
||||
});
|
||||
return runtime_.bleGatewayName(channel_it->gateway_id, channel_it->name);
|
||||
}
|
||||
|
||||
int GatewayBleBridge::characteristicIndex(uint16_t attr_handle) const {
|
||||
for (size_t index = 0; index < 3; ++index) {
|
||||
if (s_value_handles[index] == attr_handle) {
|
||||
return static_cast<int>(index);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int GatewayBleBridge::handleGapEvent(struct ble_gap_event* event) {
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status == 0) {
|
||||
conn_handle_ = event->connect.conn_handle;
|
||||
notify_enabled_.fill(false);
|
||||
last_notify_payload_.clear();
|
||||
last_notify_at_us_ = 0;
|
||||
ESP_LOGI(kTag, "BLE client connected handle=%u", conn_handle_);
|
||||
} else if (enabled_) {
|
||||
startAdvertising();
|
||||
}
|
||||
return 0;
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
conn_handle_ = kInvalidConnectionHandle;
|
||||
notify_enabled_.fill(false);
|
||||
last_notify_payload_.clear();
|
||||
last_notify_at_us_ = 0;
|
||||
if (enabled_) {
|
||||
startAdvertising();
|
||||
}
|
||||
ESP_LOGI(kTag, "BLE client disconnected reason=%d", event->disconnect.reason);
|
||||
return 0;
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
if (enabled_ && conn_handle_ == kInvalidConnectionHandle) {
|
||||
startAdvertising();
|
||||
}
|
||||
return 0;
|
||||
case BLE_GAP_EVENT_SUBSCRIBE: {
|
||||
const int index = characteristicIndex(event->subscribe.attr_handle);
|
||||
if (index >= 0 && event->subscribe.conn_handle == conn_handle_) {
|
||||
notify_enabled_[index] = event->subscribe.cur_notify != 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int GatewayBleBridge::handleAccess(uint16_t, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt* ctxt) {
|
||||
const int index = characteristicIndex(attr_handle);
|
||||
if (index < 0) {
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_ACCESS_OP_READ_CHR:
|
||||
if (!characteristic_values_[index].empty()) {
|
||||
const int rc = os_mbuf_append(ctxt->om, characteristic_values_[index].data(),
|
||||
characteristic_values_[index].size());
|
||||
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
|
||||
const uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
|
||||
std::vector<uint8_t> payload(len, 0);
|
||||
if (len > 0) {
|
||||
uint16_t copied = 0;
|
||||
const int rc = ble_hs_mbuf_to_flat(ctxt->om, payload.data(), len, &copied);
|
||||
if (rc != 0 || copied != len) {
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
}
|
||||
|
||||
characteristic_values_[index] = payload;
|
||||
if (!enabled_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (index == static_cast<int>(kGatewayCharacteristicIndex)) {
|
||||
handleGatewayWrite(payload);
|
||||
} else {
|
||||
handleRawWrite(static_cast<size_t>(index), payload);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
@@ -0,0 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_controller.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_domain gateway_runtime freertos log nvs_flash
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
|
||||
namespace gateway {
|
||||
|
||||
class DaliDomainService;
|
||||
class GatewayRuntime;
|
||||
|
||||
struct GatewayControllerConfig {
|
||||
uint32_t task_stack_size{6144};
|
||||
UBaseType_t task_priority{5};
|
||||
int color_temperature_min{2000};
|
||||
int color_temperature_max{6500};
|
||||
bool setup_supported{true};
|
||||
bool ble_supported{false};
|
||||
bool wifi_supported{true};
|
||||
bool ip_router_supported{true};
|
||||
bool internal_scene_supported{true};
|
||||
bool internal_group_supported{true};
|
||||
};
|
||||
|
||||
class GatewayController {
|
||||
public:
|
||||
using NotificationSink = std::function<void(const std::vector<uint8_t>& frame)>;
|
||||
using BleStateSink = std::function<void(bool enabled)>;
|
||||
using GatewayNameSink = std::function<void(uint8_t gateway_id)>;
|
||||
|
||||
GatewayController(GatewayRuntime& runtime, DaliDomainService& dali_domain,
|
||||
GatewayControllerConfig config = {});
|
||||
~GatewayController();
|
||||
|
||||
esp_err_t start();
|
||||
bool enqueueCommandFrame(const std::vector<uint8_t>& frame);
|
||||
void addNotificationSink(NotificationSink sink);
|
||||
void addBleStateSink(BleStateSink sink);
|
||||
void addGatewayNameSink(GatewayNameSink sink);
|
||||
|
||||
bool setupMode() const;
|
||||
bool bleEnabled() const;
|
||||
bool wifiEnabled() const;
|
||||
bool ipRouterEnabled() const;
|
||||
|
||||
private:
|
||||
struct InternalScene {
|
||||
bool enabled{false};
|
||||
uint8_t brightness{254};
|
||||
uint8_t color_mode{2};
|
||||
uint8_t data1{0};
|
||||
uint8_t data2{0};
|
||||
uint8_t data3{0};
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct InternalGroup {
|
||||
bool enabled{false};
|
||||
uint8_t target_type{2};
|
||||
uint8_t target_value{0};
|
||||
std::string name;
|
||||
};
|
||||
|
||||
using SceneStore = std::array<InternalScene, 16>;
|
||||
using GroupStore = std::array<InternalGroup, 16>;
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
void taskLoop();
|
||||
void dispatchCommand(const std::vector<uint8_t>& command);
|
||||
|
||||
bool hasGateway(uint8_t gateway_id) const;
|
||||
std::vector<uint8_t> gatewayIds() const;
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
void refreshRuntimeGatewayNames();
|
||||
void publishPayload(uint8_t gateway_id, const std::vector<uint8_t>& payload);
|
||||
void publishFrame(const std::vector<uint8_t>& frame);
|
||||
|
||||
uint8_t resolveInternalGroupRawAddress(uint8_t gateway_id, uint8_t raw_addr);
|
||||
static uint8_t normalizeGroupTargetType(uint8_t target_type);
|
||||
static uint8_t normalizeGroupTargetValue(uint8_t target_type, uint8_t target_value);
|
||||
static uint8_t internalGroupRawTargetAddress(uint8_t target_type, uint8_t target_value,
|
||||
uint8_t raw_addr);
|
||||
static int internalGroupDecTargetAddress(uint8_t target_type, uint8_t target_value);
|
||||
static int shortAddressFromRaw(uint8_t raw_addr);
|
||||
static int reverseInRange(int value, int min_value, int max_value);
|
||||
|
||||
SceneStore& sceneStore(uint8_t gateway_id);
|
||||
GroupStore& groupStore(uint8_t gateway_id);
|
||||
InternalScene* scene(uint8_t gateway_id, uint8_t scene_id);
|
||||
InternalGroup* group(uint8_t gateway_id, uint8_t group_id);
|
||||
|
||||
bool setSceneEnabled(uint8_t gateway_id, uint8_t scene_id, bool enabled);
|
||||
bool setSceneDetail(uint8_t gateway_id, uint8_t scene_id, uint8_t brightness,
|
||||
uint8_t color_mode, uint8_t data1, uint8_t data2, uint8_t data3);
|
||||
bool setSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name);
|
||||
bool deleteScene(uint8_t gateway_id, uint8_t scene_id);
|
||||
std::pair<uint8_t, uint8_t> sceneMask(uint8_t gateway_id);
|
||||
bool executeScene(uint8_t gateway_id, int short_address, uint8_t scene_id);
|
||||
|
||||
bool setGroupEnabled(uint8_t gateway_id, uint8_t group_id, bool enabled);
|
||||
bool setGroupDetail(uint8_t gateway_id, uint8_t group_id, uint8_t target_type,
|
||||
uint8_t target_value);
|
||||
bool setGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name);
|
||||
bool deleteGroup(uint8_t gateway_id, uint8_t group_id);
|
||||
std::pair<uint8_t, uint8_t> groupMask(uint8_t gateway_id);
|
||||
bool executeGroup(uint8_t gateway_id, uint8_t group_id);
|
||||
|
||||
void handleGatewayNameCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleGatewayIdentityCommand(uint8_t gateway_id, uint8_t op);
|
||||
void handleAllocationCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleInternalSceneCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
void handleInternalGroupCommand(uint8_t gateway_id, const std::vector<uint8_t>& command);
|
||||
|
||||
bool openStorage();
|
||||
void closeStorage();
|
||||
void loadSceneStore(uint8_t gateway_id, SceneStore& scenes);
|
||||
void loadGroupStore(uint8_t gateway_id, GroupStore& groups);
|
||||
bool saveScene(uint8_t gateway_id, uint8_t scene_id);
|
||||
bool deleteSceneStorage(uint8_t gateway_id, uint8_t scene_id);
|
||||
bool saveSceneName(uint8_t gateway_id, uint8_t scene_id, std::string_view name);
|
||||
bool deleteSceneNameStorage(uint8_t gateway_id, uint8_t scene_id);
|
||||
bool saveGroup(uint8_t gateway_id, uint8_t group_id);
|
||||
bool deleteGroupStorage(uint8_t gateway_id, uint8_t group_id);
|
||||
bool saveGroupName(uint8_t gateway_id, uint8_t group_id, std::string_view name);
|
||||
bool deleteGroupNameStorage(uint8_t gateway_id, uint8_t group_id);
|
||||
std::string readString(std::string_view key) const;
|
||||
bool writeString(std::string_view key, std::string_view value);
|
||||
bool eraseKey(std::string_view key);
|
||||
|
||||
GatewayRuntime& runtime_;
|
||||
DaliDomainService& dali_domain_;
|
||||
GatewayControllerConfig config_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
nvs_handle_t storage_{0};
|
||||
std::vector<NotificationSink> notification_sinks_;
|
||||
std::vector<BleStateSink> ble_state_sinks_;
|
||||
std::vector<GatewayNameSink> gateway_name_sinks_;
|
||||
std::map<uint8_t, SceneStore> scenes_;
|
||||
std::map<uint8_t, GroupStore> groups_;
|
||||
bool setup_mode_{false};
|
||||
bool ble_enabled_{false};
|
||||
bool wifi_enabled_{false};
|
||||
bool ip_router_enabled_{true};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
File diff suppressed because it is too large
Load Diff
@@ -99,10 +99,13 @@ class GatewayRuntime {
|
||||
void setCommandAddressResolver(std::function<uint8_t(uint8_t gw, uint8_t raw_addr)> resolver);
|
||||
|
||||
GatewayDeviceInfo deviceInfo() const;
|
||||
bool bleEnabled() const;
|
||||
bool setBleEnabled(bool enabled);
|
||||
std::string gatewayName(uint8_t gateway_id) const;
|
||||
bool setGatewayName(uint8_t gateway_id, std::string_view name);
|
||||
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;
|
||||
std::string defaultBleGatewayName() const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -312,6 +312,18 @@ GatewayDeviceInfo GatewayRuntime::deviceInfo() const {
|
||||
return info;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::bleEnabled() const {
|
||||
return ble_enabled_;
|
||||
}
|
||||
|
||||
bool GatewayRuntime::setBleEnabled(bool enabled) {
|
||||
if (!settings_.setBleEnabled(enabled)) {
|
||||
return false;
|
||||
}
|
||||
ble_enabled_ = enabled;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::gatewayName(uint8_t gateway_id) const {
|
||||
return settings_.getGatewayName(gateway_id, defaultGatewayName(gateway_id));
|
||||
}
|
||||
@@ -342,6 +354,17 @@ std::string GatewayRuntime::bleMacHex() const {
|
||||
return toHex(serialBytes());
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::bleGatewayName(uint8_t gateway_id, std::string_view gateway_name) const {
|
||||
std::string normalized(gateway_name);
|
||||
if (normalized.size() > kMaxGatewayNameBytes) {
|
||||
normalized.resize(kMaxGatewayNameBytes);
|
||||
}
|
||||
if (!normalized.empty() && normalized != defaultGatewayName(gateway_id)) {
|
||||
return normalized;
|
||||
}
|
||||
return defaultBleGatewayName();
|
||||
}
|
||||
|
||||
std::string GatewayRuntime::defaultBleGatewayName() const {
|
||||
return "DALIGW_" + bleMacHex();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user