Refactor GatewayModbus and GatewayNetwork components
- Updated GatewayModbusConfig to allow uart_port and pin values to be -1, indicating an unconfigured state. - Enhanced GatewayNetworkService to support an additional setup AP button with configurable GPIO and active low settings. - Refactored boot button configuration logic to reduce redundancy and improve clarity. - Introduced a new method for handling GPIO input configuration. - Improved boot button task loop to handle both boot and setup AP buttons more effectively. - Added programming mode functionality to EtsDeviceRuntime, allowing toggling and querying of the programming state. - Implemented memory checks to avoid unnecessary reads in EtsDeviceRuntime. - Enhanced security storage to derive factory FDSK from the device's serial number and store it in NVS. - Updated factory FDSK loading logic to ensure proper key generation and storage. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -290,8 +290,9 @@ esp_err_t DaliDomainService::bindSerialBus(const DaliSerialBusConfig& config) {
|
||||
ESP_LOGE(kTag, "failed to configure uart%d: %s", config.uart_port, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = uart_set_pin(uart, config.tx_pin, config.rx_pin, UART_PIN_NO_CHANGE,
|
||||
UART_PIN_NO_CHANGE);
|
||||
err = uart_set_pin(uart, config.tx_pin < 0 ? UART_PIN_NO_CHANGE : config.tx_pin,
|
||||
config.rx_pin < 0 ? UART_PIN_NO_CHANGE : config.rx_pin,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to set uart%d pins tx=%d rx=%d: %s", config.uart_port,
|
||||
config.tx_pin, config.rx_pin, esp_err_to_name(err));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
@@ -37,7 +38,7 @@ struct GatewayBridgeServiceConfig {
|
||||
std::vector<int> reserved_uart_ports;
|
||||
uint32_t bacnet_task_stack_size{8192};
|
||||
UBaseType_t bacnet_task_priority{5};
|
||||
uint32_t knx_task_stack_size{8192};
|
||||
uint32_t knx_task_stack_size{12288};
|
||||
UBaseType_t knx_task_priority{5};
|
||||
std::optional<GatewayKnxConfig> default_knx_config;
|
||||
};
|
||||
@@ -66,6 +67,14 @@ class GatewayBridgeService {
|
||||
|
||||
ChannelRuntime* findRuntime(uint8_t gateway_id);
|
||||
const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
|
||||
ChannelRuntime* selectKnxEndpointRuntime();
|
||||
bool isKnxEndpointRuntime(const ChannelRuntime* runtime) const;
|
||||
esp_err_t startKnxEndpoint(ChannelRuntime* requested_runtime,
|
||||
std::set<int>* used_uarts = nullptr);
|
||||
esp_err_t stopKnxEndpoint(ChannelRuntime* requested_runtime);
|
||||
DaliBridgeResult routeKnxCemiFrame(const uint8_t* data, size_t len);
|
||||
DaliBridgeResult routeKnxGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
size_t len);
|
||||
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||
void collectUsedRuntimeResources(uint8_t except_gateway_id,
|
||||
std::set<uint16_t>* modbus_tcp_ports,
|
||||
@@ -76,6 +85,7 @@ class GatewayBridgeService {
|
||||
GatewayCache& cache_;
|
||||
GatewayBridgeServiceConfig config_;
|
||||
std::vector<std::unique_ptr<ChannelRuntime>> runtimes_;
|
||||
ChannelRuntime* knx_endpoint_runtime_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "gateway_knx.hpp"
|
||||
#include "gateway_modbus.hpp"
|
||||
#include "gateway_provisioning.hpp"
|
||||
#include "openknx_idf/ets_memory_loader.h"
|
||||
#include "openknx_idf/security_storage.h"
|
||||
|
||||
#include "cJSON.h"
|
||||
@@ -1296,12 +1295,20 @@ uart_stop_bits_t UartStopBits(int bits) {
|
||||
} // namespace
|
||||
|
||||
struct GatewayBridgeService::ChannelRuntime {
|
||||
explicit ChannelRuntime(DaliDomainService& domain, GatewayCache& cache, DaliChannelInfo channel,
|
||||
explicit ChannelRuntime(GatewayBridgeService& service, DaliDomainService& domain,
|
||||
GatewayCache& cache, DaliChannelInfo channel,
|
||||
GatewayBridgeServiceConfig service_config)
|
||||
: domain(domain), cache(cache), channel(std::move(channel)), service_config(service_config),
|
||||
: service(service),
|
||||
domain(domain),
|
||||
cache(cache),
|
||||
channel(std::move(channel)),
|
||||
service_config(service_config),
|
||||
lock(xSemaphoreCreateRecursiveMutex()) {}
|
||||
|
||||
~ChannelRuntime() {
|
||||
if (knx_router != nullptr) {
|
||||
knx_router->stop();
|
||||
}
|
||||
if (cloud != nullptr) {
|
||||
cloud->stop();
|
||||
}
|
||||
@@ -1311,6 +1318,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
GatewayBridgeService& service;
|
||||
DaliDomainService& domain;
|
||||
GatewayCache& cache;
|
||||
DaliChannelInfo channel;
|
||||
@@ -1427,18 +1435,17 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
knx = std::make_unique<GatewayKnxBridge>(*engine);
|
||||
knx_router = std::make_unique<GatewayKnxTpIpRouter>(
|
||||
*knx, [this](const uint8_t* data, size_t len) {
|
||||
LockGuard guard(lock);
|
||||
if (knx == nullptr) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "KNX bridge is not ready";
|
||||
return result;
|
||||
}
|
||||
return knx->handleCemiFrame(data, len);
|
||||
return service.routeKnxCemiFrame(data, len);
|
||||
},
|
||||
openKnxNamespace());
|
||||
if (knx_config.has_value()) {
|
||||
knx->setConfig(knx_config.value());
|
||||
knx_router->setConfig(knx_config.value());
|
||||
knx_router->setGroupWriteHandler(
|
||||
[this](uint16_t group_address, const uint8_t* data, size_t len) {
|
||||
return service.routeKnxGroupWrite(group_address, data, len);
|
||||
});
|
||||
if (const auto active_knx = activeKnxConfigLocked(); active_knx.has_value()) {
|
||||
knx->setConfig(active_knx.value());
|
||||
knx_router->setConfig(active_knx.value());
|
||||
knx_router->setCommissioningOnly(!knx_config.has_value());
|
||||
}
|
||||
|
||||
#if defined(CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED)
|
||||
@@ -1459,33 +1466,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
|
||||
void refreshOpenKnxEtsAssociationsLocked() {
|
||||
if (!service_config.knx_enabled) {
|
||||
return;
|
||||
}
|
||||
const auto active_config = activeKnxConfigLocked();
|
||||
if (!active_config.has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto snapshot = openknx::LoadEtsMemorySnapshot(openKnxNamespace());
|
||||
const bool has_downloaded_address = snapshot.individual_address != 0 &&
|
||||
snapshot.individual_address != 0xffff;
|
||||
if (!snapshot.configured && !has_downloaded_address && snapshot.associations.empty()) {
|
||||
return;
|
||||
}
|
||||
GatewayKnxConfig updated = active_config.value();
|
||||
if (has_downloaded_address) {
|
||||
updated.individual_address = snapshot.individual_address;
|
||||
}
|
||||
updated.ets_associations.clear();
|
||||
updated.ets_associations.reserve(snapshot.associations.size());
|
||||
for (const auto& association : snapshot.associations) {
|
||||
updated.ets_associations.push_back(GatewayKnxEtsAssociation{
|
||||
association.group_address, association.group_object_number});
|
||||
}
|
||||
knx_config = std::move(updated);
|
||||
ESP_LOGI(kTag, "gateway=%u loaded OpenKNX ETS address=0x%04x associations=%u from NVS namespace %s",
|
||||
channel.gateway_id, snapshot.individual_address,
|
||||
static_cast<unsigned>(snapshot.associations.size()), openKnxNamespace().c_str());
|
||||
// Bau07B0/OpenKNX memory restore is stack-heavy and owns TP-UART internals;
|
||||
// the live KNX task restores and syncs ETS associations after startup.
|
||||
}
|
||||
|
||||
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
|
||||
@@ -2056,25 +2038,41 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
knx_last_error = validation_error;
|
||||
return validation_err;
|
||||
}
|
||||
if (config->ip_router_enabled && used_ports != nullptr) {
|
||||
if (used_ports->find(config->udp_port) != used_ports->end()) {
|
||||
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(config->udp_port);
|
||||
GatewayKnxConfig runtime_config = config.value();
|
||||
if (runtime_config.ip_router_enabled && used_ports != nullptr) {
|
||||
if (used_ports->find(runtime_config.udp_port) != used_ports->end()) {
|
||||
knx_last_error = "KNXnet/IP UDP port " + std::to_string(runtime_config.udp_port) +
|
||||
" is already owned by another runtime";
|
||||
ESP_LOGW(kTag, "gateway=%u skips duplicate KNXnet/IP UDP port %u",
|
||||
channel.gateway_id, static_cast<unsigned>(runtime_config.udp_port));
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
used_ports->insert(config->udp_port);
|
||||
used_ports->insert(runtime_config.udp_port);
|
||||
}
|
||||
if (config->ip_router_enabled && used_uarts != nullptr) {
|
||||
const int uart_port = config->tp_uart.uart_port;
|
||||
if (GatewayKnxConfigUsesTpUart(runtime_config) && used_uarts != nullptr) {
|
||||
const int uart_port = runtime_config.tp_uart.uart_port;
|
||||
if (used_uarts->find(uart_port) != used_uarts->end()) {
|
||||
knx_last_error = "KNX TP-UART UART" + std::to_string(uart_port) +
|
||||
" is already used by another runtime";
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
ESP_LOGW(kTag,
|
||||
"gateway=%u KNX TP-UART UART%d is already owned by another runtime; "
|
||||
"starting this KNX/IP runtime without opening a second UART driver",
|
||||
channel.gateway_id, uart_port);
|
||||
runtime_config.tp_uart.uart_port = -1;
|
||||
}
|
||||
used_uarts->insert(uart_port);
|
||||
}
|
||||
knx->setConfig(config.value());
|
||||
knx_router->setConfig(config.value());
|
||||
if (!config->ip_router_enabled) {
|
||||
const bool commissioning_only = !knx_config.has_value();
|
||||
ESP_LOGI(kTag,
|
||||
"gateway=%u KNX/IP start config namespace=%s storedConfig=%d udp=%u tunnel=%d "
|
||||
"multicast=%d multicastGroup=%s mainGroup=%u tpUart=%d tx=%d rx=%d individual=0x%04x",
|
||||
channel.gateway_id, openKnxNamespace().c_str(), !commissioning_only,
|
||||
static_cast<unsigned>(runtime_config.udp_port), runtime_config.tunnel_enabled,
|
||||
runtime_config.multicast_enabled, runtime_config.multicast_address.c_str(),
|
||||
static_cast<unsigned>(runtime_config.main_group), runtime_config.tp_uart.uart_port,
|
||||
runtime_config.tp_uart.tx_pin, runtime_config.tp_uart.rx_pin,
|
||||
runtime_config.individual_address);
|
||||
knx->setConfig(runtime_config);
|
||||
knx_router->setConfig(runtime_config);
|
||||
knx_router->setCommissioningOnly(commissioning_only);
|
||||
if (!runtime_config.ip_router_enabled) {
|
||||
knx_started = false;
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
@@ -2084,8 +2082,18 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
knx_started = err == ESP_OK;
|
||||
if (err != ESP_OK) {
|
||||
knx_last_error = knx_router->lastError().empty()
|
||||
? "failed to start KNX TP-UART router"
|
||||
? "failed to start KNXnet/IP router"
|
||||
: knx_router->lastError();
|
||||
ESP_LOGW(kTag, "gateway=%u KNX/IP start failed err=%s(%d) detail=%s",
|
||||
channel.gateway_id, esp_err_to_name(err), static_cast<int>(err),
|
||||
knx_last_error.c_str());
|
||||
} else {
|
||||
if (knx_router->tpUartOnline() && used_uarts != nullptr) {
|
||||
used_uarts->insert(runtime_config.tp_uart.uart_port);
|
||||
}
|
||||
ESP_LOGI(kTag, "gateway=%u KNX/IP started namespace=%s udp=%u detail=%s",
|
||||
channel.gateway_id, openKnxNamespace().c_str(),
|
||||
static_cast<unsigned>(runtime_config.udp_port), knx_router->lastError().c_str());
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -3014,12 +3022,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
const std::optional<GatewayKnxConfig>& candidate_knx,
|
||||
std::string* error_message = nullptr) const {
|
||||
const int uart_port = config.serial.uart_port;
|
||||
if (uart_port < 0 || uart_port > 2) {
|
||||
if (uart_port < -1 || uart_port > 2) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "Modbus serial UART port must be 0, 1, or 2";
|
||||
*error_message = "Modbus serial UART port must be -1, 0, 1, or 2";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (uart_port < 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (uart_port == 0 && !service_config.allow_modbus_uart0) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message =
|
||||
@@ -3035,7 +3046,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (service_config.knx_enabled && candidate_knx.has_value() &&
|
||||
candidate_knx->ip_router_enabled && candidate_knx->tp_uart.uart_port == uart_port) {
|
||||
GatewayKnxConfigUsesTpUart(candidate_knx.value()) &&
|
||||
candidate_knx->tp_uart.uart_port == uart_port) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "Modbus serial UART" + std::to_string(uart_port) +
|
||||
" conflicts with KNX TP-UART; choose another free UART for RS485";
|
||||
@@ -3048,16 +3060,16 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
esp_err_t validateKnxConfigLocked(const GatewayKnxConfig& config,
|
||||
const std::optional<GatewayModbusConfig>& candidate_modbus,
|
||||
std::string* error_message = nullptr) const {
|
||||
if (!config.ip_router_enabled) {
|
||||
return ESP_OK;
|
||||
}
|
||||
const int uart_port = config.tp_uart.uart_port;
|
||||
if (uart_port < 0 || uart_port > 2) {
|
||||
if (config.tp_uart.uart_port < -1 || config.tp_uart.uart_port > 2) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message = "KNX TP-UART port must be 0, 1, or 2";
|
||||
*error_message = "KNX TP-UART port must be -1, 0, 1, or 2";
|
||||
}
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (!config.ip_router_enabled || !GatewayKnxConfigUsesTpUart(config)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
const int uart_port = config.tp_uart.uart_port;
|
||||
if (uart_port == 0 && !service_config.allow_knx_uart0) {
|
||||
if (error_message != nullptr) {
|
||||
*error_message =
|
||||
@@ -3144,7 +3156,18 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
if (knx_config.has_value()) {
|
||||
return knx_config;
|
||||
}
|
||||
return service_config.default_knx_config;
|
||||
if (!service_config.default_knx_config.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
GatewayKnxConfig config = service_config.default_knx_config.value();
|
||||
const uint8_t channel_index = channel.channel_index;
|
||||
config.main_group = static_cast<uint8_t>(std::min<int>(31, config.main_group + channel_index));
|
||||
const uint16_t device = config.individual_address & 0x00ff;
|
||||
if (device > 0 && device + channel_index <= 0x00ff) {
|
||||
config.individual_address = static_cast<uint16_t>((config.individual_address & 0xff00) |
|
||||
(device + channel_index));
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
esp_err_t saveKnxConfig(const GatewayKnxConfig& config,
|
||||
@@ -3165,12 +3188,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return validation_err;
|
||||
}
|
||||
const bool restart_router = knx_started || (knx_router != nullptr && knx_router->started());
|
||||
if (restart_router && merged_config.ip_router_enabled && used_ports != nullptr &&
|
||||
used_ports->find(merged_config.udp_port) != used_ports->end()) {
|
||||
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(merged_config.udp_port);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (restart_router && merged_config.ip_router_enabled && used_uarts != nullptr &&
|
||||
if (restart_router && GatewayKnxConfigUsesTpUart(merged_config) && used_uarts != nullptr &&
|
||||
used_uarts->find(merged_config.tp_uart.uart_port) != used_uarts->end()) {
|
||||
knx_last_error = "KNX TP-UART UART" + std::to_string(merged_config.tp_uart.uart_port) +
|
||||
" is already used by another runtime";
|
||||
@@ -3195,6 +3213,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
if (knx_router != nullptr) {
|
||||
knx_router->setConfig(merged_config);
|
||||
knx_router->setCommissioningOnly(false);
|
||||
}
|
||||
if (restart_router) {
|
||||
return startKnx(used_ports, used_uarts);
|
||||
@@ -3350,6 +3369,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
if (GatewayModbusTransportIsSerial(config->transport)) {
|
||||
if (config->serial.uart_port < 0) {
|
||||
modbus_last_error = "Modbus serial UART disabled";
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
std::string validation_error;
|
||||
const esp_err_t serial_err = validateSerialModbusConfigLocked(
|
||||
config.value(), activeKnxConfigLocked(), &validation_error);
|
||||
@@ -3532,8 +3555,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
||||
}
|
||||
const int rts_pin = config.serial.rs485.enabled ? config.serial.rs485.de_pin
|
||||
: UART_PIN_NO_CHANGE;
|
||||
err = uart_set_pin(uart_port, config.serial.tx_pin, config.serial.rx_pin, rts_pin,
|
||||
UART_PIN_NO_CHANGE);
|
||||
err = uart_set_pin(uart_port,
|
||||
config.serial.tx_pin < 0 ? UART_PIN_NO_CHANGE : config.serial.tx_pin,
|
||||
config.serial.rx_pin < 0 ? UART_PIN_NO_CHANGE : config.serial.rx_pin,
|
||||
rts_pin < 0 ? UART_PIN_NO_CHANGE : rts_pin, UART_PIN_NO_CHANGE);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
@@ -3794,7 +3819,7 @@ esp_err_t GatewayBridgeService::start() {
|
||||
const auto channels = dali_domain_.channelInfo();
|
||||
runtimes_.reserve(channels.size());
|
||||
for (const auto& channel : channels) {
|
||||
auto runtime = std::make_unique<ChannelRuntime>(dali_domain_, cache_, channel, config_);
|
||||
auto runtime = std::make_unique<ChannelRuntime>(*this, dali_domain_, cache_, channel, config_);
|
||||
const esp_err_t err = runtime->start();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to start bridge runtime gateway=%u: %s", channel.gateway_id,
|
||||
@@ -3820,13 +3845,18 @@ esp_err_t GatewayBridgeService::start() {
|
||||
}
|
||||
|
||||
if (config_.knx_enabled && config_.knx_startup_enabled) {
|
||||
std::set<uint16_t> used_knx_ports;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
const esp_err_t err = runtime->startKnx(&used_knx_ports, &used_serial_uarts);
|
||||
if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) {
|
||||
ESP_LOGW(kTag, "gateway=%u KNX/IP startup skipped: %s", runtime->channel.gateway_id,
|
||||
esp_err_to_name(err));
|
||||
}
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
const esp_err_t err = startKnxEndpoint(owner, &used_serial_uarts);
|
||||
if (err != ESP_OK && err != ESP_ERR_NOT_FOUND && err != ESP_ERR_NOT_SUPPORTED) {
|
||||
const char* detail = owner == nullptr
|
||||
? "no KNX endpoint owner"
|
||||
: (!owner->knx_last_error.empty()
|
||||
? owner->knx_last_error.c_str()
|
||||
: (owner->knx_router == nullptr
|
||||
? "router unavailable"
|
||||
: owner->knx_router->lastError().c_str()));
|
||||
ESP_LOGW(kTag, "KNX/IP startup skipped: %s(%d) detail=%s",
|
||||
esp_err_to_name(err), static_cast<int>(err), detail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3863,20 +3893,170 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GatewayBridgeService::ChannelRuntime* GatewayBridgeService::selectKnxEndpointRuntime() {
|
||||
auto eligible = [](ChannelRuntime* runtime) {
|
||||
if (runtime == nullptr) {
|
||||
return false;
|
||||
}
|
||||
LockGuard guard(runtime->lock);
|
||||
const auto config = runtime->activeKnxConfigLocked();
|
||||
return config.has_value() && config->ip_router_enabled;
|
||||
};
|
||||
|
||||
if (eligible(knx_endpoint_runtime_)) {
|
||||
return knx_endpoint_runtime_;
|
||||
}
|
||||
|
||||
ChannelRuntime* selected = nullptr;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
if (!eligible(runtime.get())) {
|
||||
continue;
|
||||
}
|
||||
if (selected == nullptr || runtime->channel.channel_index < selected->channel.channel_index) {
|
||||
selected = runtime.get();
|
||||
}
|
||||
}
|
||||
knx_endpoint_runtime_ = selected;
|
||||
if (selected != nullptr) {
|
||||
ESP_LOGI(kTag, "gateway=%u owns shared KNXnet/IP endpoint", selected->channel.gateway_id);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
bool GatewayBridgeService::isKnxEndpointRuntime(const ChannelRuntime* runtime) const {
|
||||
return runtime != nullptr && runtime == knx_endpoint_runtime_;
|
||||
}
|
||||
|
||||
esp_err_t GatewayBridgeService::startKnxEndpoint(ChannelRuntime* requested_runtime,
|
||||
std::set<int>* used_uarts) {
|
||||
if (!config_.knx_enabled) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
if (owner == nullptr) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
if (requested_runtime != nullptr && requested_runtime != owner) {
|
||||
ESP_LOGI(kTag, "gateway=%u requested KNX start; shared endpoint remains owned by gateway=%u",
|
||||
requested_runtime->channel.gateway_id, owner->channel.gateway_id);
|
||||
}
|
||||
|
||||
if (used_uarts != nullptr) {
|
||||
LockGuard guard(owner->lock);
|
||||
const auto owner_config = owner->activeKnxConfigLocked();
|
||||
if (owner_config.has_value() && GatewayKnxConfigUsesTpUart(owner_config.value())) {
|
||||
used_uarts->erase(owner_config->tp_uart.uart_port);
|
||||
}
|
||||
}
|
||||
|
||||
std::set<uint16_t> used_knx_ports;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
if (runtime.get() == owner) {
|
||||
continue;
|
||||
}
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx_started ||
|
||||
(runtime->knx_router != nullptr && runtime->knx_router->started())) {
|
||||
const auto config = runtime->activeKnxConfigLocked();
|
||||
if (config.has_value()) {
|
||||
used_knx_ports.insert(config->udp_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
return owner->startKnx(&used_knx_ports, used_uarts);
|
||||
}
|
||||
|
||||
esp_err_t GatewayBridgeService::stopKnxEndpoint(ChannelRuntime* requested_runtime) {
|
||||
ChannelRuntime* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
|
||||
: selectKnxEndpointRuntime();
|
||||
if (owner == nullptr) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
if (requested_runtime != nullptr && requested_runtime != owner) {
|
||||
ESP_LOGI(kTag, "gateway=%u requested KNX stop; stopping shared endpoint owned by gateway=%u",
|
||||
requested_runtime->channel.gateway_id, owner->channel.gateway_id);
|
||||
}
|
||||
return owner->stopKnx();
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayBridgeService::routeKnxCemiFrame(const uint8_t* data, size_t len) {
|
||||
std::vector<ChannelRuntime*> matches;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx != nullptr && runtime->knx->matchesCemiFrame(data, len)) {
|
||||
matches.push_back(runtime.get());
|
||||
}
|
||||
}
|
||||
if (matches.empty()) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "No DALI bridge mapping matched KNX cEMI group write";
|
||||
return result;
|
||||
}
|
||||
if (matches.size() > 1) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "KNX cEMI group write matched multiple DALI bridge channels";
|
||||
ESP_LOGW(kTag, "%s", result.error.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
ChannelRuntime* runtime = matches.front();
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx == nullptr || !runtime->knx->matchesCemiFrame(data, len)) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "DALI bridge mapping changed before KNX cEMI dispatch";
|
||||
return result;
|
||||
}
|
||||
return runtime->knx->handleCemiFrame(data, len);
|
||||
}
|
||||
|
||||
DaliBridgeResult GatewayBridgeService::routeKnxGroupWrite(uint16_t group_address,
|
||||
const uint8_t* data, size_t len) {
|
||||
std::vector<ChannelRuntime*> matches;
|
||||
for (const auto& runtime : runtimes_) {
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx != nullptr && runtime->knx->matchesGroupAddress(group_address)) {
|
||||
matches.push_back(runtime.get());
|
||||
}
|
||||
}
|
||||
if (matches.empty()) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "No DALI bridge mapping matched KNX group " +
|
||||
GatewayKnxGroupAddressString(group_address);
|
||||
return result;
|
||||
}
|
||||
if (matches.size() > 1) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "KNX group " + GatewayKnxGroupAddressString(group_address) +
|
||||
" matched multiple DALI bridge channels";
|
||||
ESP_LOGW(kTag, "%s", result.error.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
ChannelRuntime* runtime = matches.front();
|
||||
LockGuard guard(runtime->lock);
|
||||
if (runtime->knx == nullptr || !runtime->knx->matchesGroupAddress(group_address)) {
|
||||
DaliBridgeResult result;
|
||||
result.error = "DALI bridge mapping changed before KNX group dispatch";
|
||||
return result;
|
||||
}
|
||||
return runtime->knx->handleGroupWrite(group_address, data, len);
|
||||
}
|
||||
|
||||
void GatewayBridgeService::handleDaliRawFrame(const DaliRawFrame& frame) {
|
||||
const auto update = DecodeDaliKnxStatusUpdate(frame);
|
||||
if (!update.has_value()) {
|
||||
return;
|
||||
}
|
||||
auto* runtime = findRuntime(frame.gateway_id);
|
||||
if (runtime == nullptr) {
|
||||
auto* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_
|
||||
: selectKnxEndpointRuntime();
|
||||
if (owner == nullptr || owner->channel.gateway_id != frame.gateway_id) {
|
||||
return;
|
||||
}
|
||||
LockGuard guard(runtime->lock);
|
||||
if (!runtime->knx_started || runtime->knx_router == nullptr) {
|
||||
LockGuard guard(owner->lock);
|
||||
if (!owner->knx_started || owner->knx_router == nullptr) {
|
||||
return;
|
||||
}
|
||||
runtime->knx_router->publishDaliStatus(update->target, update->actual_level);
|
||||
owner->knx_router->publishDaliStatus(update->target, update->actual_level);
|
||||
}
|
||||
|
||||
void GatewayBridgeService::collectUsedRuntimeResources(
|
||||
@@ -3909,7 +4089,8 @@ void GatewayBridgeService::collectUsedRuntimeResources(
|
||||
if (knx_udp_ports != nullptr) {
|
||||
knx_udp_ports->insert(knx_config->udp_port);
|
||||
}
|
||||
if (serial_uarts != nullptr) {
|
||||
if (serial_uarts != nullptr && runtime->knx_router != nullptr &&
|
||||
runtime->knx_router->tpUartOnline()) {
|
||||
serial_uarts->insert(knx_config->tp_uart.uart_port);
|
||||
}
|
||||
}
|
||||
@@ -4214,20 +4395,21 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_start") {
|
||||
std::set<uint16_t> used_knx_ports;
|
||||
ChannelRuntime* owner = selectKnxEndpointRuntime();
|
||||
std::set<int> used_serial_uarts;
|
||||
collectUsedRuntimeResources(gateway_id.value(), nullptr, &used_knx_ports,
|
||||
&used_serial_uarts);
|
||||
const esp_err_t err = runtime->startKnx(&used_knx_ports, &used_serial_uarts);
|
||||
collectUsedRuntimeResources(owner == nullptr ? gateway_id.value() : owner->channel.gateway_id,
|
||||
nullptr, nullptr, &used_serial_uarts);
|
||||
const esp_err_t err = startKnxEndpoint(runtime, &used_serial_uarts);
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, runtime->knx_last_error.empty()
|
||||
auto* owner = knx_endpoint_runtime_ != nullptr ? knx_endpoint_runtime_ : runtime;
|
||||
return ErrorResponse(err, owner->knx_last_error.empty()
|
||||
? "failed to start KNX/IP bridge"
|
||||
: runtime->knx_last_error.c_str());
|
||||
: owner->knx_last_error.c_str());
|
||||
}
|
||||
return handleGet("knx", gateway_id.value());
|
||||
}
|
||||
if (action == "knx_stop") {
|
||||
const esp_err_t err = runtime->stopKnx();
|
||||
const esp_err_t err = stopKnxEndpoint(runtime);
|
||||
if (err != ESP_OK) {
|
||||
return ErrorResponse(err, "failed to stop KNX/IP bridge");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_knx.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES dali_cpp esp_driver_uart freertos log lwip openknx_idf
|
||||
REQUIRES dali_cpp esp_driver_gpio esp_driver_uart esp_hw_support esp_netif freertos log lwip openknx_idf
|
||||
)
|
||||
|
||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||
@@ -10,12 +10,14 @@
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -62,6 +64,10 @@ struct GatewayKnxConfig {
|
||||
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
||||
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
||||
uint16_t individual_address{0x1101};
|
||||
int programming_button_gpio{-1};
|
||||
bool programming_button_active_low{true};
|
||||
int programming_led_gpio{-1};
|
||||
bool programming_led_active_high{true};
|
||||
std::vector<GatewayKnxEtsAssociation> ets_associations;
|
||||
GatewayKnxTpUartConfig tp_uart;
|
||||
};
|
||||
@@ -111,6 +117,7 @@ struct GatewayKnxCommissioningBallast {
|
||||
|
||||
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
||||
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
||||
bool GatewayKnxConfigUsesTpUart(const GatewayKnxConfig& config);
|
||||
|
||||
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode);
|
||||
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value);
|
||||
@@ -132,6 +139,8 @@ class GatewayKnxBridge {
|
||||
size_t etsBindingCount() const;
|
||||
|
||||
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
|
||||
bool matchesCemiFrame(const uint8_t* data, size_t len) const;
|
||||
bool matchesGroupAddress(uint16_t group_address) const;
|
||||
DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len);
|
||||
DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||
size_t len);
|
||||
@@ -186,13 +195,19 @@ class GatewayKnxBridge {
|
||||
class GatewayKnxTpIpRouter {
|
||||
public:
|
||||
using CemiFrameHandler = std::function<DaliBridgeResult(const uint8_t* data, size_t len)>;
|
||||
using GroupWriteHandler = std::function<DaliBridgeResult(uint16_t group_address,
|
||||
const uint8_t* data,
|
||||
size_t len)>;
|
||||
|
||||
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler,
|
||||
std::string openknx_namespace = "openknx");
|
||||
~GatewayKnxTpIpRouter();
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
void setCommissioningOnly(bool enabled);
|
||||
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||
const GatewayKnxConfig& config() const;
|
||||
bool tpUartOnline() const;
|
||||
|
||||
esp_err_t start(uint32_t task_stack_size, UBaseType_t task_priority);
|
||||
esp_err_t stop();
|
||||
@@ -201,17 +216,40 @@ class GatewayKnxTpIpRouter {
|
||||
bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level);
|
||||
|
||||
private:
|
||||
static constexpr size_t kMaxTunnelClients = 16;
|
||||
|
||||
struct TunnelClient {
|
||||
bool connected{false};
|
||||
uint8_t channel_id{0};
|
||||
uint8_t connection_type{0};
|
||||
uint8_t received_sequence{255};
|
||||
uint8_t send_sequence{0};
|
||||
uint16_t individual_address{0};
|
||||
TickType_t last_activity_tick{0};
|
||||
::sockaddr_in control_remote{};
|
||||
::sockaddr_in data_remote{};
|
||||
};
|
||||
|
||||
static void TaskEntry(void* arg);
|
||||
|
||||
esp_err_t initializeRuntime();
|
||||
void taskLoop();
|
||||
void finishTask();
|
||||
void closeSockets();
|
||||
bool configureSocket();
|
||||
bool configureTpUart();
|
||||
bool initializeTpUart();
|
||||
bool configureProgrammingGpio();
|
||||
void refreshNetworkInterfaces(bool force_log = false);
|
||||
void handleUdpDatagram(const uint8_t* data, size_t len, const ::sockaddr_in& remote);
|
||||
void handleSearchRequest(uint16_t service, const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void handleDescriptionRequest(const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void handleRoutingIndication(const uint8_t* body, size_t len);
|
||||
void handleTunnellingRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
|
||||
void handleDeviceConfigurationRequest(const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
void handleConnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
|
||||
void handleConnectionStateRequest(const uint8_t* body, size_t len,
|
||||
const ::sockaddr_in& remote);
|
||||
@@ -220,47 +258,85 @@ class GatewayKnxTpIpRouter {
|
||||
const ::sockaddr_in& remote);
|
||||
void sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendDeviceConfigurationAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendConnectionHeaderAck(uint16_t service, uint8_t channel_id, uint8_t sequence,
|
||||
uint8_t status, const ::sockaddr_in& remote);
|
||||
void sendSecureSessionStatus(uint8_t status, const ::sockaddr_in& remote);
|
||||
void sendTunnelIndication(const uint8_t* data, size_t len);
|
||||
void sendTunnelIndicationToClient(TunnelClient& client, const uint8_t* data, size_t len);
|
||||
void sendConnectionStateResponse(uint8_t channel_id, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendDisconnectResponse(uint8_t channel_id, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
void sendConnectResponse(uint8_t channel_id, uint8_t status,
|
||||
const ::sockaddr_in& remote);
|
||||
const ::sockaddr_in& remote, uint8_t connection_type,
|
||||
uint16_t tunnel_address);
|
||||
void sendRoutingIndication(const uint8_t* data, size_t len);
|
||||
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len);
|
||||
void sendSearchResponse(uint16_t service, const ::sockaddr_in& remote,
|
||||
const std::set<uint8_t>& requested_dibs = {});
|
||||
void sendDescriptionResponse(const ::sockaddr_in& remote);
|
||||
std::array<uint8_t, 8> localHpaiForRemote(const ::sockaddr_in& remote) const;
|
||||
std::vector<uint8_t> buildDeviceInfoDib(const ::sockaddr_in& remote) const;
|
||||
std::vector<uint8_t> buildSupportedServiceDib() const;
|
||||
std::vector<uint8_t> buildExtendedDeviceInfoDib() const;
|
||||
std::vector<uint8_t> buildIpConfigDib(const ::sockaddr_in& remote, bool current) const;
|
||||
std::vector<uint8_t> buildKnxAddressesDib() const;
|
||||
std::vector<uint8_t> buildTunnelingInfoDib() const;
|
||||
TunnelClient* findTunnelClient(uint8_t channel_id);
|
||||
const TunnelClient* findTunnelClient(uint8_t channel_id) const;
|
||||
TunnelClient* allocateTunnelClient(const ::sockaddr_in& control_remote,
|
||||
const ::sockaddr_in& data_remote,
|
||||
uint8_t connection_type);
|
||||
void resetTunnelClient(TunnelClient& client);
|
||||
uint8_t nextTunnelChannelId() const;
|
||||
uint16_t effectiveTunnelAddressForSlot(size_t slot) const;
|
||||
void pruneStaleTunnelClients();
|
||||
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len,
|
||||
TunnelClient* response_client);
|
||||
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
|
||||
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
|
||||
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
|
||||
bool shouldRouteDaliApplicationFrames() const;
|
||||
void syncOpenKnxConfigFromDevice();
|
||||
uint16_t effectiveIndividualAddress() const;
|
||||
uint16_t effectiveTunnelAddress() const;
|
||||
void pollTpUart();
|
||||
void pollProgrammingButton();
|
||||
void updateProgrammingLed();
|
||||
void setProgrammingLed(bool on);
|
||||
void handleTpUartControlByte(uint8_t byte);
|
||||
void handleTpTelegram(const uint8_t* data, size_t len);
|
||||
void forwardCemiToTp(const uint8_t* data, size_t len);
|
||||
|
||||
GatewayKnxBridge& bridge_;
|
||||
CemiFrameHandler handler_;
|
||||
GroupWriteHandler group_write_handler_;
|
||||
std::string openknx_namespace_;
|
||||
GatewayKnxConfig config_;
|
||||
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
|
||||
TaskHandle_t task_handle_{nullptr};
|
||||
SemaphoreHandle_t openknx_lock_{nullptr};
|
||||
SemaphoreHandle_t startup_semaphore_{nullptr};
|
||||
esp_err_t startup_result_{ESP_OK};
|
||||
std::atomic_bool stop_requested_{false};
|
||||
std::atomic_bool started_{false};
|
||||
int udp_sock_{-1};
|
||||
int tp_uart_port_{-1};
|
||||
uint8_t tunnel_channel_id_{1};
|
||||
uint8_t expected_tunnel_sequence_{0};
|
||||
uint8_t tunnel_send_sequence_{0};
|
||||
bool tunnel_connected_{false};
|
||||
::sockaddr_in tunnel_remote_{};
|
||||
std::vector<uint32_t> multicast_joined_interfaces_;
|
||||
TickType_t network_refresh_tick_{0};
|
||||
std::array<TunnelClient, kMaxTunnelClients> tunnel_clients_{};
|
||||
uint8_t last_tunnel_channel_id_{0};
|
||||
std::vector<uint8_t> tp_rx_frame_;
|
||||
std::vector<uint8_t> tp_last_sent_telegram_;
|
||||
TickType_t tp_uart_last_byte_tick_{0};
|
||||
bool tp_uart_extended_frame_{false};
|
||||
bool tp_uart_online_{false};
|
||||
bool commissioning_only_{false};
|
||||
std::atomic_bool openknx_configured_{false};
|
||||
bool programming_button_last_pressed_{false};
|
||||
bool programming_led_state_{false};
|
||||
TickType_t programming_button_last_toggle_tick_{0};
|
||||
std::string last_error_;
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -560,7 +560,7 @@ std::optional<GatewayModbusConfig> GatewayModbusConfigFromValue(const DaliValue*
|
||||
getObjectInt(json, "unitId").value_or(getObjectInt(json, "unit_id").value_or(1))));
|
||||
if (const auto* serial_value = getObjectValue(json, "serial")) {
|
||||
if (const auto* serial = serial_value->asObject()) {
|
||||
config.serial.uart_port = clampedInt(*serial, "uartPort", config.serial.uart_port, 0, 2);
|
||||
config.serial.uart_port = clampedInt(*serial, "uartPort", config.serial.uart_port, -1, 2);
|
||||
config.serial.tx_pin = clampedInt(*serial, "txPin", config.serial.tx_pin, -1, 48);
|
||||
config.serial.rx_pin = clampedInt(*serial, "rxPin", config.serial.rx_pin, -1, 48);
|
||||
config.serial.baudrate = clampedU32(*serial, "baudrate", config.serial.baudrate,
|
||||
|
||||
@@ -56,8 +56,10 @@ struct GatewayNetworkServiceConfig {
|
||||
bool status_led_active_high{true};
|
||||
int boot_button_gpio{-1};
|
||||
bool boot_button_active_low{true};
|
||||
int setup_ap_button_gpio{-1};
|
||||
bool setup_ap_button_active_low{true};
|
||||
uint32_t boot_button_long_press_ms{3000};
|
||||
uint32_t boot_button_task_stack_size{2048};
|
||||
uint32_t boot_button_task_stack_size{8192};
|
||||
UBaseType_t boot_button_task_priority{2};
|
||||
uint32_t udp_task_stack_size{4096};
|
||||
UBaseType_t udp_task_priority{4};
|
||||
|
||||
@@ -909,22 +909,37 @@ esp_err_t GatewayNetworkService::configureStatusLed() {
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::configureBootButton() {
|
||||
if (config_.boot_button_gpio < 0) {
|
||||
if (config_.boot_button_gpio < 0 && config_.setup_ap_button_gpio < 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
gpio_config_t io_config = {};
|
||||
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(config_.boot_button_gpio);
|
||||
io_config.mode = GPIO_MODE_INPUT;
|
||||
io_config.pull_up_en = config_.boot_button_active_low ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
io_config.pull_down_en = config_.boot_button_active_low ? GPIO_PULLDOWN_DISABLE : GPIO_PULLDOWN_ENABLE;
|
||||
io_config.intr_type = GPIO_INTR_DISABLE;
|
||||
const esp_err_t err = gpio_config(&io_config);
|
||||
const auto configure_input = [](int gpio, bool active_low, const char* name) -> esp_err_t {
|
||||
if (gpio < 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
gpio_config_t io_config = {};
|
||||
io_config.pin_bit_mask = 1ULL << static_cast<uint32_t>(gpio);
|
||||
io_config.mode = GPIO_MODE_INPUT;
|
||||
io_config.pull_up_en = active_low ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||
io_config.pull_down_en = active_low ? GPIO_PULLDOWN_DISABLE : GPIO_PULLDOWN_ENABLE;
|
||||
io_config.intr_type = GPIO_INTR_DISABLE;
|
||||
const esp_err_t err = gpio_config(&io_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure %s GPIO%d: %s", name, gpio, esp_err_to_name(err));
|
||||
}
|
||||
return err;
|
||||
};
|
||||
|
||||
esp_err_t err = configure_input(config_.boot_button_gpio, config_.boot_button_active_low,
|
||||
"Wi-Fi reset button");
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(kTag, "failed to configure boot button GPIO%d: %s", config_.boot_button_gpio,
|
||||
esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
return err;
|
||||
if (config_.setup_ap_button_gpio == config_.boot_button_gpio) {
|
||||
return ESP_OK;
|
||||
}
|
||||
return configure_input(config_.setup_ap_button_gpio, config_.setup_ap_button_active_low,
|
||||
"setup AP button");
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startHttpServer() {
|
||||
@@ -985,7 +1000,8 @@ esp_err_t GatewayNetworkService::startUdpTask() {
|
||||
}
|
||||
|
||||
esp_err_t GatewayNetworkService::startBootButtonTask() {
|
||||
if (config_.boot_button_gpio < 0 || boot_button_task_handle_ != nullptr) {
|
||||
if ((config_.boot_button_gpio < 0 && config_.setup_ap_button_gpio < 0) ||
|
||||
boot_button_task_handle_ != nullptr) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -1349,39 +1365,88 @@ void GatewayNetworkService::bootButtonTaskLoop() {
|
||||
const TickType_t poll_ticks = pdMS_TO_TICKS(100);
|
||||
const uint32_t long_press_ms = std::max<uint32_t>(config_.boot_button_long_press_ms, 100);
|
||||
|
||||
auto is_pressed = [this]() {
|
||||
const int level = gpio_get_level(static_cast<gpio_num_t>(config_.boot_button_gpio));
|
||||
return config_.boot_button_active_low ? level == 0 : level != 0;
|
||||
auto is_pressed = [](int gpio, bool active_low) {
|
||||
if (gpio < 0) {
|
||||
return false;
|
||||
}
|
||||
const int level = gpio_get_level(static_cast<gpio_num_t>(gpio));
|
||||
return active_low ? level == 0 : level != 0;
|
||||
};
|
||||
|
||||
while (true) {
|
||||
if (!is_pressed()) {
|
||||
vTaskDelay(poll_ticks);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto wait_release = [&](int gpio, bool active_low) {
|
||||
uint32_t pressed_ms = 0;
|
||||
while (is_pressed()) {
|
||||
while (is_pressed(gpio, active_low)) {
|
||||
vTaskDelay(poll_ticks);
|
||||
pressed_ms += 100;
|
||||
}
|
||||
return pressed_ms;
|
||||
};
|
||||
|
||||
if (pressed_ms >= long_press_ms) {
|
||||
ESP_LOGW(kTag, "BOOT long press clears Wi-Fi credentials and restarts");
|
||||
runtime_.clearWirelessInfo();
|
||||
stopEspNow();
|
||||
if (wifi_started_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
auto enter_setup_ap = [this]() {
|
||||
ESP_LOGI(kTag, "setup AP button enters setup AP mode");
|
||||
const uint32_t stack_size = std::max<uint32_t>(config_.boot_button_task_stack_size, 8192);
|
||||
const BaseType_t created = xTaskCreate(
|
||||
[](void* arg) {
|
||||
auto* service = static_cast<GatewayNetworkService*>(arg);
|
||||
service->handleWifiControl(101);
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"gateway_setup_ap", stack_size, this, config_.boot_button_task_priority, nullptr);
|
||||
if (created != pdPASS) {
|
||||
ESP_LOGE(kTag, "failed to create setup AP task");
|
||||
}
|
||||
};
|
||||
|
||||
auto clear_wifi_and_restart = [this]() {
|
||||
ESP_LOGW(kTag, "Wi-Fi reset button clears Wi-Fi credentials and restarts");
|
||||
runtime_.clearWirelessInfo();
|
||||
stopEspNow();
|
||||
if (wifi_started_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_disconnect());
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_wifi_stop());
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
esp_restart();
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const bool same_button = config_.boot_button_gpio >= 0 &&
|
||||
config_.boot_button_gpio == config_.setup_ap_button_gpio;
|
||||
if (same_button && is_pressed(config_.boot_button_gpio, config_.boot_button_active_low)) {
|
||||
const uint32_t pressed_ms = wait_release(config_.boot_button_gpio,
|
||||
config_.boot_button_active_low);
|
||||
if (pressed_ms >= long_press_ms) {
|
||||
clear_wifi_and_restart();
|
||||
} else {
|
||||
enter_setup_ap();
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
esp_restart();
|
||||
} else {
|
||||
ESP_LOGI(kTag, "BOOT short press enters setup AP mode");
|
||||
handleWifiControl(101);
|
||||
continue;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
if (is_pressed(config_.setup_ap_button_gpio, config_.setup_ap_button_active_low)) {
|
||||
wait_release(config_.setup_ap_button_gpio, config_.setup_ap_button_active_low);
|
||||
enter_setup_ap();
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_pressed(config_.boot_button_gpio, config_.boot_button_active_low)) {
|
||||
const uint32_t pressed_ms = wait_release(config_.boot_button_gpio,
|
||||
config_.boot_button_active_low);
|
||||
if (pressed_ms >= long_press_ms) {
|
||||
clear_wifi_and_restart();
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config_.setup_ap_button_gpio < 0 && config_.boot_button_gpio < 0) {
|
||||
vTaskDelete(nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
vTaskDelay(poll_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,15 @@ class EtsDeviceRuntime {
|
||||
uint16_t individualAddress() const;
|
||||
uint16_t tunnelClientAddress() const;
|
||||
bool configured() const;
|
||||
bool programmingMode() const;
|
||||
void setProgrammingMode(bool enabled);
|
||||
void toggleProgrammingMode();
|
||||
EtsMemorySnapshot snapshot() const;
|
||||
|
||||
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||
FunctionPropertyHandler state_handler);
|
||||
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||
void setNetworkInterface(esp_netif_t* netif);
|
||||
|
||||
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
|
||||
bool handleBusFrame(const uint8_t* data, size_t len);
|
||||
|
||||
@@ -20,12 +20,32 @@ namespace {
|
||||
constexpr const char* kTag = "openknx_idf";
|
||||
constexpr const char* kEepromKey = "eeprom";
|
||||
|
||||
esp_netif_t* findDefaultNetif() {
|
||||
if (auto* sta = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF")) {
|
||||
return sta;
|
||||
bool readBaseMac(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (auto* eth = esp_netif_get_handle_from_ifkey("ETH_DEF")) {
|
||||
return eth;
|
||||
if (esp_efuse_mac_get_default(data) == ESP_OK) {
|
||||
return true;
|
||||
}
|
||||
return esp_read_mac(data, ESP_MAC_WIFI_STA) == ESP_OK;
|
||||
}
|
||||
|
||||
esp_netif_t* findDefaultNetif() {
|
||||
constexpr const char* kPreferredIfKeys[] = {"ETH_DEF", "WIFI_STA_DEF", "WIFI_AP_DEF"};
|
||||
for (const char* key : kPreferredIfKeys) {
|
||||
auto* netif = esp_netif_get_handle_from_ifkey(key);
|
||||
if (netif == nullptr || !esp_netif_is_netif_up(netif)) {
|
||||
continue;
|
||||
}
|
||||
esp_netif_ip_info_t ip_info{};
|
||||
if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK && ip_info.ip.addr != 0) {
|
||||
return netif;
|
||||
}
|
||||
}
|
||||
for (const char* key : kPreferredIfKeys) {
|
||||
if (auto* netif = esp_netif_get_handle_from_ifkey(key)) {
|
||||
return netif;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -103,7 +123,7 @@ void EspIdfPlatform::macAddress(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (esp_read_mac(data, ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||
if (!readBaseMac(data)) {
|
||||
std::memset(data, 0, 6);
|
||||
}
|
||||
}
|
||||
@@ -111,7 +131,7 @@ void EspIdfPlatform::macAddress(uint8_t* data) {
|
||||
uint32_t EspIdfPlatform::uniqueSerialNumber() {
|
||||
uint8_t mac[6]{};
|
||||
macAddress(mac);
|
||||
return (static_cast<uint32_t>(mac[0]) << 24) | (static_cast<uint32_t>(mac[1]) << 16) |
|
||||
return (static_cast<uint32_t>(mac[2]) << 24) | (static_cast<uint32_t>(mac[3]) << 16) |
|
||||
(static_cast<uint32_t>(mac[4]) << 8) | mac[5];
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,13 @@ bool IsUsableIndividualAddress(uint16_t address) {
|
||||
return address != 0 && address != kInvalidIndividualAddress;
|
||||
}
|
||||
|
||||
bool IsErasedMemory(const uint8_t* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return true;
|
||||
}
|
||||
return std::all_of(data, data + size, [](uint8_t value) { return value == 0xff; });
|
||||
}
|
||||
|
||||
void ApplyReg1DaliIdentity(Bau07B0& device, EspIdfPlatform& platform) {
|
||||
device.deviceObject().manufacturerId(kReg1DaliManufacturerId);
|
||||
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||
@@ -58,7 +65,11 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
||||
if (IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
}
|
||||
device_.readMemory();
|
||||
const uint8_t* memory = platform_.getNonVolatileMemoryStart();
|
||||
const size_t memory_size = platform_.getNonVolatileMemorySize();
|
||||
if (!IsErasedMemory(memory, memory_size)) {
|
||||
device_.readMemory();
|
||||
}
|
||||
if (!IsUsableIndividualAddress(device_.deviceObject().individualAddress()) &&
|
||||
IsUsableIndividualAddress(fallback_individual_address)) {
|
||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||
@@ -99,6 +110,16 @@ uint16_t EtsDeviceRuntime::tunnelClientAddress() const {
|
||||
|
||||
bool EtsDeviceRuntime::configured() const { return const_cast<Bau07B0&>(device_).configured(); }
|
||||
|
||||
bool EtsDeviceRuntime::programmingMode() const {
|
||||
return const_cast<Bau07B0&>(device_).deviceObject().progMode();
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setProgrammingMode(bool enabled) {
|
||||
device_.deviceObject().progMode(enabled);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::toggleProgrammingMode() { setProgrammingMode(!programmingMode()); }
|
||||
|
||||
EtsMemorySnapshot EtsDeviceRuntime::snapshot() const {
|
||||
EtsMemorySnapshot out;
|
||||
auto& device = const_cast<Bau07B0&>(device_);
|
||||
@@ -139,6 +160,10 @@ void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) {
|
||||
group_write_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
void EtsDeviceRuntime::setNetworkInterface(esp_netif_t* netif) {
|
||||
platform_.networkInterface(netif);
|
||||
}
|
||||
|
||||
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
||||
CemiFrameSender sender) {
|
||||
auto* server = device_.getCemiServer();
|
||||
@@ -289,6 +314,9 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
||||
case M_FuncPropStateRead_req:
|
||||
return true;
|
||||
case L_data_req:
|
||||
if (!const_cast<Bau07B0&>(device_).configured() || programmingMode()) {
|
||||
return true;
|
||||
}
|
||||
if (frame.addressType() == IndividualAddress &&
|
||||
frame.destinationAddress() == individualAddress()) {
|
||||
return true;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_timer.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
@@ -22,11 +22,13 @@ constexpr const char* kFactoryFdskKey = "factory_fdsk";
|
||||
constexpr size_t kFdskSize = 16;
|
||||
constexpr size_t kSerialSize = 6;
|
||||
constexpr size_t kFdskQrSize = 36;
|
||||
constexpr uint16_t kKnxManufacturerId = 0x00A4;
|
||||
constexpr const char* kProductIdentity = "REG1-Dali";
|
||||
constexpr const char* kManufacturerId = "00A4";
|
||||
constexpr const char* kApplicationNumber = "01";
|
||||
constexpr const char* kApplicationVersion = "05";
|
||||
constexpr const char* kDevelopmentStorage = "plain_nvs_development";
|
||||
constexpr const char* kDevelopmentStorage = "base_mac_derived_plain_nvs_development";
|
||||
constexpr char kFdskDerivationLabel[] = "DaliMaster REG1-Dali deterministic FDSK v1";
|
||||
constexpr uint8_t kCrc4Tab[16] = {
|
||||
0x0, 0x3, 0x6, 0x5, 0xc, 0xf, 0xa, 0x9,
|
||||
0xb, 0x8, 0xd, 0xe, 0x7, 0x4, 0x1, 0x2,
|
||||
@@ -57,10 +59,14 @@ bool plausibleKey(const uint8_t* data) {
|
||||
return !all_zero && !all_ff;
|
||||
}
|
||||
|
||||
void generateKey(uint8_t* data) {
|
||||
do {
|
||||
esp_fill_random(data, kFdskSize);
|
||||
} while (!plausibleKey(data));
|
||||
bool readBaseMac(uint8_t* data) {
|
||||
if (data == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (esp_efuse_mac_get_default(data) == ESP_OK) {
|
||||
return true;
|
||||
}
|
||||
return esp_read_mac(data, ESP_MAC_WIFI_STA) == ESP_OK;
|
||||
}
|
||||
|
||||
void clearOpenKnxFdskCache() {
|
||||
@@ -108,16 +114,60 @@ bool parseHexKey(const std::string& value, uint8_t* out) {
|
||||
return plausibleKey(out);
|
||||
}
|
||||
|
||||
bool storeFactoryFdsk(const uint8_t* data) {
|
||||
if (data == nullptr || !plausibleKey(data) || !ensureNvsReady()) {
|
||||
bool loadKnxSerialNumber(uint8_t* serial) {
|
||||
if (serial == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, kSerialSize> mac{};
|
||||
if (!readBaseMac(mac.data())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
serial[0] = static_cast<uint8_t>((kKnxManufacturerId >> 8) & 0xff);
|
||||
serial[1] = static_cast<uint8_t>(kKnxManufacturerId & 0xff);
|
||||
std::copy(mac.begin() + 2, mac.end(), serial + 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool deriveFactoryFdskFromSerial(const uint8_t* serial, uint8_t* key) {
|
||||
if (serial == nullptr || key == nullptr) {
|
||||
return false;
|
||||
}
|
||||
std::array<uint8_t, sizeof(kFdskDerivationLabel) - 1 + kSerialSize> material{};
|
||||
std::copy(kFdskDerivationLabel, kFdskDerivationLabel + sizeof(kFdskDerivationLabel) - 1,
|
||||
material.begin());
|
||||
std::copy(serial, serial + kSerialSize, material.begin() + sizeof(kFdskDerivationLabel) - 1);
|
||||
|
||||
std::array<uint8_t, 32> digest{};
|
||||
if (mbedtls_sha256(material.data(), material.size(), digest.data(), 0) != 0) {
|
||||
return false;
|
||||
}
|
||||
std::copy(digest.begin(), digest.begin() + kFdskSize, key);
|
||||
if (!plausibleKey(key)) {
|
||||
key[kFdskSize - 1] ^= 0xA5;
|
||||
}
|
||||
return plausibleKey(key);
|
||||
}
|
||||
|
||||
void syncFactoryFdskToNvs(const uint8_t* data) {
|
||||
if (data == nullptr || !plausibleKey(data) || !ensureNvsReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::array<uint8_t, kFdskSize> stored{};
|
||||
size_t stored_size = stored.size();
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t err = nvs_open(kNamespace, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
err = nvs_get_blob(handle, kFactoryFdskKey, stored.data(), &stored_size);
|
||||
if (err == ESP_OK && stored_size == stored.size() &&
|
||||
std::equal(stored.begin(), stored.end(), data)) {
|
||||
nvs_close(handle);
|
||||
return;
|
||||
}
|
||||
err = nvs_set_blob(handle, kFactoryFdskKey, data, kFdskSize);
|
||||
if (err == ESP_OK) {
|
||||
@@ -125,11 +175,10 @@ bool storeFactoryFdsk(const uint8_t* data) {
|
||||
}
|
||||
nvs_close(handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to store KNX factory FDSK: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
ESP_LOGW(kTag, "failed to mirror deterministic KNX factory FDSK: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
clearOpenKnxFdskCache();
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t crc4Array(const uint8_t* data, size_t len) {
|
||||
@@ -219,35 +268,18 @@ std::string fnv1aHex(const std::string& value) {
|
||||
namespace gateway::openknx {
|
||||
|
||||
bool LoadFactoryFdsk(uint8_t* data, size_t len) {
|
||||
if (data == nullptr || len < kFdskSize || !ensureNvsReady()) {
|
||||
if (data == nullptr || len < kFdskSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nvs_handle_t handle = 0;
|
||||
esp_err_t err = nvs_open(kNamespace, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t stored_size = kFdskSize;
|
||||
err = nvs_get_blob(handle, kFactoryFdskKey, data, &stored_size);
|
||||
if (err == ESP_OK && stored_size == kFdskSize && plausibleKey(data)) {
|
||||
nvs_close(handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
generateKey(data);
|
||||
err = nvs_set_blob(handle, kFactoryFdskKey, data, kFdskSize);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(handle);
|
||||
}
|
||||
nvs_close(handle);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(kTag, "failed to store generated KNX factory FDSK: %s", esp_err_to_name(err));
|
||||
std::array<uint8_t, kSerialSize> serial{};
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
if (!loadKnxSerialNumber(serial.data()) ||
|
||||
!deriveFactoryFdskFromSerial(serial.data(), key.data())) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(data, key.data(), kFdskSize);
|
||||
syncFactoryFdskToNvs(key.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,8 +287,7 @@ FactoryFdskInfo LoadFactoryFdskInfo() {
|
||||
FactoryFdskInfo info;
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
std::array<uint8_t, kSerialSize> serial{};
|
||||
if (!LoadFactoryFdsk(key.data(), key.size()) ||
|
||||
esp_read_mac(serial.data(), ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||
if (!loadKnxSerialNumber(serial.data()) || !LoadFactoryFdsk(key.data(), key.size())) {
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -269,8 +300,7 @@ FactoryFdskInfo LoadFactoryFdskInfo() {
|
||||
|
||||
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
|
||||
std::array<uint8_t, kFdskSize> key{};
|
||||
generateKey(key.data());
|
||||
const bool stored = storeFactoryFdsk(key.data());
|
||||
const bool stored = LoadFactoryFdsk(key.data(), key.size());
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
if (!stored) {
|
||||
return false;
|
||||
@@ -286,8 +316,16 @@ bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info) {
|
||||
if (!parseHexKey(hex_key, key.data())) {
|
||||
return false;
|
||||
}
|
||||
const bool stored = storeFactoryFdsk(key.data());
|
||||
std::array<uint8_t, kSerialSize> serial{};
|
||||
std::array<uint8_t, kFdskSize> derived{};
|
||||
const bool stored = loadKnxSerialNumber(serial.data()) &&
|
||||
deriveFactoryFdskFromSerial(serial.data(), derived.data()) &&
|
||||
std::equal(key.begin(), key.end(), derived.begin());
|
||||
if (stored) {
|
||||
syncFactoryFdskToNvs(derived.data());
|
||||
}
|
||||
std::fill(key.begin(), key.end(), 0);
|
||||
std::fill(derived.begin(), derived.end(), 0);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user