feat(gateway): enhance DALI and KNX settings management with instance support
Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
menu "Gateway App"
|
menu "Gateway App"
|
||||||
|
|
||||||
|
menu "DALI Settings"
|
||||||
|
|
||||||
config GATEWAY_CHANNEL_COUNT
|
config GATEWAY_CHANNEL_COUNT
|
||||||
int "Gateway channel count"
|
int "Gateway channel count"
|
||||||
range 1 2
|
range 1 2
|
||||||
@@ -329,6 +331,8 @@ config GATEWAY_DALI_BAUDRATE
|
|||||||
help
|
help
|
||||||
Runtime baudrate used when initializing the local DALI bus.
|
Runtime baudrate used when initializing the local DALI bus.
|
||||||
|
|
||||||
|
endmenu
|
||||||
|
|
||||||
menu "Gateway Startup Services"
|
menu "Gateway Startup Services"
|
||||||
|
|
||||||
config GATEWAY_BLE_SUPPORTED
|
config GATEWAY_BLE_SUPPORTED
|
||||||
@@ -620,6 +624,17 @@ config GATEWAY_START_BACNET_BRIDGE_ENABLED
|
|||||||
help
|
help
|
||||||
Starts configured BACnet/IP object bindings at boot. Disabled by default so the UDP BACnet/IP port is opened only after provisioning or explicit runtime start.
|
Starts configured BACnet/IP object bindings at boot. Disabled by default so the UDP BACnet/IP port is opened only after provisioning or explicit runtime start.
|
||||||
|
|
||||||
|
menu "KNX Settings"
|
||||||
|
|
||||||
|
config GATEWAY_KNX_INSTANCE_COUNT
|
||||||
|
int "Enabled KNX instance count"
|
||||||
|
range 1 16
|
||||||
|
default 1
|
||||||
|
help
|
||||||
|
Number of enabled KNX logical instances exposed by management APIs.
|
||||||
|
Instance 0 preserves the legacy serial/FDSK identity; each additional
|
||||||
|
instance derives its serial/FDSK from the base identity plus instance id.
|
||||||
|
|
||||||
config GATEWAY_KNX_BRIDGE_SUPPORTED
|
config GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||||
bool "KNX to DALI bridge is supported"
|
bool "KNX to DALI bridge is supported"
|
||||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||||
@@ -956,7 +971,7 @@ config GATEWAY_KNX_TP_UART_9BIT_MODE
|
|||||||
mode commonly described as 19200 baud 9-bit UART. Disable only for
|
mode commonly described as 19200 baud 9-bit UART. Disable only for
|
||||||
hardware wired for 8N1 host UART mode.
|
hardware wired for 8N1 host UART mode.
|
||||||
|
|
||||||
config GATEWAY_KNX_TP_FULL_IP_FORWARD
|
config GATEWAY_KNX_TP_FULL_IP_FORWARD
|
||||||
bool "Mirror all physical KNX TP telegrams to KNXnet/IP"
|
bool "Mirror all physical KNX TP telegrams to KNXnet/IP"
|
||||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_TP_UART_PORT >= 0
|
depends on GATEWAY_KNX_BRIDGE_SUPPORTED && GATEWAY_KNX_TP_UART_PORT >= 0
|
||||||
default n
|
default n
|
||||||
@@ -979,6 +994,8 @@ config GATEWAY_BRIDGE_KNX_TASK_PRIORITY
|
|||||||
range 1 10
|
range 1 10
|
||||||
default 5
|
default 5
|
||||||
|
|
||||||
|
endmenu
|
||||||
|
|
||||||
config GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
config GATEWAY_CLOUD_BRIDGE_SUPPORTED
|
||||||
bool "MQTT cloud bridge is supported"
|
bool "MQTT cloud bridge is supported"
|
||||||
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
depends on GATEWAY_BRIDGE_SUPPORTED && (GATEWAY_WIFI_SUPPORTED || GATEWAY_ETHERNET_SUPPORTED)
|
||||||
|
|||||||
@@ -596,6 +596,10 @@ CONFIG_PARTITION_TABLE_MD5=y
|
|||||||
#
|
#
|
||||||
# Gateway App
|
# Gateway App
|
||||||
#
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# DALI Settings
|
||||||
|
#
|
||||||
CONFIG_GATEWAY_CHANNEL_COUNT=2
|
CONFIG_GATEWAY_CHANNEL_COUNT=2
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -640,6 +644,7 @@ CONFIG_GATEWAY_CACHE_OUTSIDE_BUS_FIRST=y
|
|||||||
# end of Gateway Cache
|
# end of Gateway Cache
|
||||||
|
|
||||||
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
|
# CONFIG_GATEWAY_ENABLE_DALI_BUS is not set
|
||||||
|
# end of DALI Settings
|
||||||
|
|
||||||
#
|
#
|
||||||
# Gateway Startup Services
|
# Gateway Startup Services
|
||||||
@@ -683,6 +688,10 @@ CONFIG_GATEWAY_MODBUS_TCP_PORT=1502
|
|||||||
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
|
CONFIG_GATEWAY_MODBUS_UNIT_ID=1
|
||||||
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
||||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||||
|
|
||||||
|
#
|
||||||
|
# KNX Settings
|
||||||
|
#
|
||||||
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
|
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
|
||||||
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
|
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
|
||||||
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||||
@@ -716,6 +725,8 @@ CONFIG_GATEWAY_KNX_TP_UART_9BIT_MODE=y
|
|||||||
CONFIG_GATEWAY_KNX_TP_FULL_IP_FORWARD=y
|
CONFIG_GATEWAY_KNX_TP_FULL_IP_FORWARD=y
|
||||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
|
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=12288
|
||||||
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
|
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
|
||||||
|
# end of KNX Settings
|
||||||
|
|
||||||
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
CONFIG_GATEWAY_CLOUD_BRIDGE_SUPPORTED=y
|
||||||
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
# CONFIG_GATEWAY_START_CLOUD_BRIDGE_ENABLED is not set
|
||||||
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
|
CONFIG_GATEWAY_BRIDGE_MODBUS_TASK_STACK_SIZE=6144
|
||||||
|
|||||||
@@ -40,10 +40,14 @@ struct IpSecureCredentialStatus {
|
|||||||
|
|
||||||
bool LoadFactoryFdsk(uint8_t* data, size_t len);
|
bool LoadFactoryFdsk(uint8_t* data, size_t len);
|
||||||
FactoryFdskInfo LoadFactoryFdskInfo();
|
FactoryFdskInfo LoadFactoryFdskInfo();
|
||||||
|
bool LoadFactoryFdskForInstance(uint32_t instance_id, uint8_t* data, size_t len);
|
||||||
|
FactoryFdskInfo LoadFactoryFdskInfoForInstance(uint32_t instance_id);
|
||||||
bool GenerateFactoryFdsk(FactoryFdskInfo* info = nullptr);
|
bool GenerateFactoryFdsk(FactoryFdskInfo* info = nullptr);
|
||||||
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info = nullptr);
|
bool WriteFactoryFdskHex(const std::string& hex_key, FactoryFdskInfo* info = nullptr);
|
||||||
bool ResetFactoryFdskCache(FactoryFdskInfo* info = nullptr);
|
bool ResetFactoryFdskCache(FactoryFdskInfo* info = nullptr);
|
||||||
|
bool ResetFactorySecurityForInstance(uint32_t instance_id, FactoryFdskInfo* info = nullptr);
|
||||||
FactoryCertificatePayload BuildFactoryCertificatePayload();
|
FactoryCertificatePayload BuildFactoryCertificatePayload();
|
||||||
|
FactoryCertificatePayload BuildFactoryCertificatePayloadForInstance(uint32_t instance_id);
|
||||||
|
|
||||||
bool LoadOamFactoryFdsk(uint8_t* data, size_t len);
|
bool LoadOamFactoryFdsk(uint8_t* data, size_t len);
|
||||||
FactoryFdskInfo LoadOamFactoryFdskInfo();
|
FactoryFdskInfo LoadOamFactoryFdskInfo();
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||||
#endif
|
#endif
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "nvs.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "freertos/semphr.h"
|
#include "freertos/semphr.h"
|
||||||
#include "lwip/inet.h"
|
#include "lwip/inet.h"
|
||||||
@@ -64,6 +66,10 @@ constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
|
|||||||
constexpr const char* kModbusManagementPrefix = "@DALIGW";
|
constexpr const char* kModbusManagementPrefix = "@DALIGW";
|
||||||
constexpr uint8_t kDaliGroupRawMin = 0x80;
|
constexpr uint8_t kDaliGroupRawMin = 0x80;
|
||||||
constexpr uint8_t kDaliGroupRawMax = 0x9F;
|
constexpr uint8_t kDaliGroupRawMax = 0x9F;
|
||||||
|
#ifndef CONFIG_GATEWAY_KNX_INSTANCE_COUNT
|
||||||
|
#define CONFIG_GATEWAY_KNX_INSTANCE_COUNT 1
|
||||||
|
#endif
|
||||||
|
constexpr uint32_t kKnxEnabledInstanceCount = CONFIG_GATEWAY_KNX_INSTANCE_COUNT;
|
||||||
|
|
||||||
void ConfigureDaliCppLogging() {
|
void ConfigureDaliCppLogging() {
|
||||||
static bool configured = false;
|
static bool configured = false;
|
||||||
@@ -199,7 +205,7 @@ cJSON* FactoryCertificateToCjson(const openknx::FactoryCertificatePayload& certi
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON* IpSecureCredentialStatusToCjson(
|
[[maybe_unused]] cJSON* IpSecureCredentialStatusToCjson(
|
||||||
const openknx::IpSecureCredentialStatus& status) {
|
const openknx::IpSecureCredentialStatus& status) {
|
||||||
cJSON* root = cJSON_CreateObject();
|
cJSON* root = cJSON_CreateObject();
|
||||||
if (root == nullptr) {
|
if (root == nullptr) {
|
||||||
@@ -393,6 +399,50 @@ std::optional<int> QueryInt(std::string_view query, std::string_view primary,
|
|||||||
return ParseInt(QueryValue(query, fallback));
|
return ParseInt(QueryValue(query, fallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint32_t> ValidKnxInstanceId(int value) {
|
||||||
|
if (value < 0 || static_cast<uint32_t>(value) >= kKnxEnabledInstanceCount) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return static_cast<uint32_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string KnxInstanceIdRangeMessage() {
|
||||||
|
return "KNX instanceId must be in range 0.." +
|
||||||
|
std::to_string(kKnxEnabledInstanceCount - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uint32_t> QueryKnxInstanceId(std::string_view query) {
|
||||||
|
return ValidKnxInstanceId(QueryInt(query, "instanceId", "instance").value_or(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EnsureBridgeNvsReady() {
|
||||||
|
const esp_err_t err = nvs_flash_init();
|
||||||
|
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||||
|
if (nvs_flash_erase() != ESP_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return nvs_flash_init() == ESP_OK;
|
||||||
|
}
|
||||||
|
return err == ESP_OK || err == ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EraseOpenKnxEeprom(const std::string& nvs_namespace) {
|
||||||
|
if (nvs_namespace.empty() || !EnsureBridgeNvsReady()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
nvs_handle_t handle = 0;
|
||||||
|
esp_err_t err = nvs_open(nvs_namespace.c_str(), NVS_READWRITE, &handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err == ESP_ERR_NVS_NOT_FOUND;
|
||||||
|
}
|
||||||
|
err = nvs_erase_key(handle, "eeprom");
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
err = nvs_commit(handle);
|
||||||
|
}
|
||||||
|
nvs_close(handle);
|
||||||
|
return err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<int> JsonIntAny(const cJSON* parent, const char* primary, const char* fallback) {
|
std::optional<int> JsonIntAny(const cJSON* parent, const char* primary, const char* fallback) {
|
||||||
auto value = JsonInt(parent, primary);
|
auto value = JsonInt(parent, primary);
|
||||||
if (value.has_value() || fallback == nullptr) {
|
if (value.has_value() || fallback == nullptr) {
|
||||||
@@ -401,6 +451,10 @@ std::optional<int> JsonIntAny(const cJSON* parent, const char* primary, const ch
|
|||||||
return JsonInt(parent, fallback);
|
return JsonInt(parent, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint32_t> JsonKnxInstanceId(const cJSON* parent) {
|
||||||
|
return ValidKnxInstanceId(JsonIntAny(parent, "instanceId", "instance").value_or(0));
|
||||||
|
}
|
||||||
|
|
||||||
bool ValidDaliAddress(int address) {
|
bool ValidDaliAddress(int address) {
|
||||||
return address >= 0 && address <= 127;
|
return address >= 0 && address <= 127;
|
||||||
}
|
}
|
||||||
@@ -2229,7 +2283,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
return JsonOk(BridgeResultToCjson(result));
|
return JsonOk(BridgeResultToCjson(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
cJSON* knxStatusCjson() const {
|
cJSON* knxStatusCjson(uint32_t instance_id = 0) const {
|
||||||
cJSON* knx_json = cJSON_CreateObject();
|
cJSON* knx_json = cJSON_CreateObject();
|
||||||
if (knx_json == nullptr) {
|
if (knx_json == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -2254,6 +2308,9 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
}
|
}
|
||||||
const auto effective_knx =
|
const auto effective_knx =
|
||||||
knx_config.has_value() ? knx_config : service_config.default_knx_config;
|
knx_config.has_value() ? knx_config : service_config.default_knx_config;
|
||||||
|
cJSON_AddNumberToObject(knx_json, "instanceId", static_cast<double>(instance_id));
|
||||||
|
cJSON_AddNumberToObject(knx_json, "enabledInstanceCount",
|
||||||
|
static_cast<double>(kKnxEnabledInstanceCount));
|
||||||
cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled);
|
cJSON_AddBoolToObject(knx_json, "enabled", service_config.knx_enabled);
|
||||||
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
|
cJSON_AddBoolToObject(knx_json, "startupEnabled", service_config.knx_startup_enabled);
|
||||||
cJSON_AddBoolToObject(knx_json, "started", knx_started);
|
cJSON_AddBoolToObject(knx_json, "started", knx_started);
|
||||||
@@ -2307,13 +2364,14 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
cJSON_AddStringToObject(security_json, "storage", "none");
|
cJSON_AddStringToObject(security_json, "storage", "none");
|
||||||
#endif
|
#endif
|
||||||
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||||
const auto fdsk_info = openknx::LoadFactoryFdskInfo();
|
const auto fdsk_info = openknx::LoadFactoryFdskInfoForInstance(instance_id);
|
||||||
cJSON* fdsk_json = FactoryFdskInfoToCjson(fdsk_info, true);
|
cJSON* fdsk_json = FactoryFdskInfoToCjson(fdsk_info, true);
|
||||||
if (fdsk_json != nullptr) {
|
if (fdsk_json != nullptr) {
|
||||||
cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json);
|
cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json);
|
||||||
}
|
}
|
||||||
cJSON* certificate_json =
|
cJSON* certificate_json =
|
||||||
FactoryCertificateToCjson(openknx::BuildFactoryCertificatePayload(), false);
|
FactoryCertificateToCjson(
|
||||||
|
openknx::BuildFactoryCertificatePayloadForInstance(instance_id), false);
|
||||||
if (certificate_json != nullptr) {
|
if (certificate_json != nullptr) {
|
||||||
cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json);
|
cJSON_AddItemToObject(security_json, "factoryCertificate", certificate_json);
|
||||||
}
|
}
|
||||||
@@ -2322,27 +2380,6 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
cJSON_AddItemToObject(security_json, "failures", failures_json);
|
cJSON_AddItemToObject(security_json, "failures", failures_json);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
cJSON* oam_security_json = cJSON_CreateObject();
|
|
||||||
if (oam_security_json != nullptr) {
|
|
||||||
cJSON* oam_fdsk_json = FactoryFdskInfoToCjson(
|
|
||||||
openknx::LoadOamFactoryFdskInfo(), false);
|
|
||||||
if (oam_fdsk_json != nullptr) {
|
|
||||||
cJSON_AddItemToObject(oam_security_json, "factorySetupKey", oam_fdsk_json);
|
|
||||||
}
|
|
||||||
cJSON* oam_certificate_json = FactoryCertificateToCjson(
|
|
||||||
openknx::BuildOamFactoryCertificatePayload(), false);
|
|
||||||
if (oam_certificate_json != nullptr) {
|
|
||||||
cJSON_AddItemToObject(oam_security_json, "factoryCertificate",
|
|
||||||
oam_certificate_json);
|
|
||||||
}
|
|
||||||
cJSON* oam_credentials_json = IpSecureCredentialStatusToCjson(
|
|
||||||
openknx::LoadOamIpSecureCredentialStatus());
|
|
||||||
if (oam_credentials_json != nullptr) {
|
|
||||||
cJSON_AddItemToObject(oam_security_json, "ipSecureCredentials",
|
|
||||||
oam_credentials_json);
|
|
||||||
}
|
|
||||||
cJSON_AddItemToObject(security_json, "oamRouter", oam_security_json);
|
|
||||||
}
|
|
||||||
cJSON_AddItemToObject(knx_json, "security", security_json);
|
cJSON_AddItemToObject(knx_json, "security", security_json);
|
||||||
}
|
}
|
||||||
if (effective_knx.has_value()) {
|
if (effective_knx.has_value()) {
|
||||||
@@ -2491,13 +2528,13 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayBridgeHttpResponse knxStatusJson() const {
|
GatewayBridgeHttpResponse knxStatusJson(uint32_t instance_id = 0) const {
|
||||||
cJSON* root = cJSON_CreateObject();
|
cJSON* root = cJSON_CreateObject();
|
||||||
if (root == nullptr) {
|
if (root == nullptr) {
|
||||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON");
|
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON");
|
||||||
}
|
}
|
||||||
cJSON_AddNumberToObject(root, "gatewayId", channel.gateway_id);
|
cJSON_AddNumberToObject(root, "gatewayId", channel.gateway_id);
|
||||||
cJSON* knx_json = knxStatusCjson();
|
cJSON* knx_json = knxStatusCjson(instance_id);
|
||||||
if (knx_json == nullptr) {
|
if (knx_json == nullptr) {
|
||||||
cJSON_Delete(root);
|
cJSON_Delete(root);
|
||||||
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON");
|
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate KNX status JSON");
|
||||||
@@ -3035,14 +3072,17 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
switch (point.generated_kind) {
|
switch (point.generated_kind) {
|
||||||
case GatewayModbusGeneratedKind::kShortOn:
|
case GatewayModbusGeneratedKind::kShortOn:
|
||||||
case GatewayModbusGeneratedKind::kShortRecallMax:
|
case GatewayModbusGeneratedKind::kShortRecallMax:
|
||||||
|
domain.markHostCommandFrame(channel.gateway_id, raw_command_address, DALI_CMD_RECALL_MAX);
|
||||||
sent = domain.on(channel.gateway_id, point.short_address);
|
sent = domain.on(channel.gateway_id, point.short_address);
|
||||||
mirrored_command = DALI_CMD_RECALL_MAX;
|
mirrored_command = DALI_CMD_RECALL_MAX;
|
||||||
break;
|
break;
|
||||||
case GatewayModbusGeneratedKind::kShortOff:
|
case GatewayModbusGeneratedKind::kShortOff:
|
||||||
|
domain.markHostCommandFrame(channel.gateway_id, raw_command_address, DALI_CMD_OFF);
|
||||||
sent = domain.off(channel.gateway_id, point.short_address);
|
sent = domain.off(channel.gateway_id, point.short_address);
|
||||||
mirrored_command = DALI_CMD_OFF;
|
mirrored_command = DALI_CMD_OFF;
|
||||||
break;
|
break;
|
||||||
case GatewayModbusGeneratedKind::kShortRecallMin:
|
case GatewayModbusGeneratedKind::kShortRecallMin:
|
||||||
|
domain.markHostCommandFrame(channel.gateway_id, raw_command_address, DALI_CMD_RECALL_MIN);
|
||||||
sent = domain.sendRaw(channel.gateway_id, raw_command_address, DALI_CMD_RECALL_MIN);
|
sent = domain.sendRaw(channel.gateway_id, raw_command_address, DALI_CMD_RECALL_MIN);
|
||||||
mirrored_command = DALI_CMD_RECALL_MIN;
|
mirrored_command = DALI_CMD_RECALL_MIN;
|
||||||
break;
|
break;
|
||||||
@@ -3065,6 +3105,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
if (value > 254) {
|
if (value > 254) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
domain.markHostCommandFrame(channel.gateway_id, RawArcAddressFromDec(point.short_address),
|
||||||
|
static_cast<uint8_t>(value));
|
||||||
if (domain.setBright(channel.gateway_id, point.short_address, value)) {
|
if (domain.setBright(channel.gateway_id, point.short_address, value)) {
|
||||||
cache.mirrorDaliCommand(channel.gateway_id, RawArcAddressFromDec(point.short_address),
|
cache.mirrorDaliCommand(channel.gateway_id, RawArcAddressFromDec(point.short_address),
|
||||||
static_cast<uint8_t>(value));
|
static_cast<uint8_t>(value));
|
||||||
@@ -3072,8 +3114,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case GatewayModbusGeneratedKind::kShortColorTemperature:
|
case GatewayModbusGeneratedKind::kShortColorTemperature:
|
||||||
|
domain.markHostActivity(channel.gateway_id);
|
||||||
return domain.setColTemp(channel.gateway_id, point.short_address, value);
|
return domain.setColTemp(channel.gateway_id, point.short_address, value);
|
||||||
case GatewayModbusGeneratedKind::kShortGroupMask:
|
case GatewayModbusGeneratedKind::kShortGroupMask:
|
||||||
|
domain.markHostActivity(channel.gateway_id);
|
||||||
if (domain.applyGroupMask(channel.gateway_id, point.short_address, value)) {
|
if (domain.applyGroupMask(channel.gateway_id, point.short_address, value)) {
|
||||||
cache.setDaliGroupMask(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
cache.setDaliGroupMask(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
||||||
value);
|
value);
|
||||||
@@ -3120,6 +3164,7 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
domain_settings.max_level = current.max_level;
|
domain_settings.max_level = current.max_level;
|
||||||
domain_settings.fade_time = current.fade_time;
|
domain_settings.fade_time = current.fade_time;
|
||||||
domain_settings.fade_rate = current.fade_rate;
|
domain_settings.fade_rate = current.fade_rate;
|
||||||
|
domain.markHostActivity(channel.gateway_id);
|
||||||
if (domain.applyAddressSettings(channel.gateway_id, point.short_address, domain_settings)) {
|
if (domain.applyAddressSettings(channel.gateway_id, point.short_address, domain_settings)) {
|
||||||
cache.setDaliSettings(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
cache.setDaliSettings(channel.gateway_id, static_cast<uint8_t>(point.short_address),
|
||||||
current);
|
current);
|
||||||
@@ -4415,7 +4460,12 @@ GatewayBridgeHttpResponse GatewayBridgeService::handleGet(
|
|||||||
return JsonOk(runtime->statusCjson());
|
return JsonOk(runtime->statusCjson());
|
||||||
}
|
}
|
||||||
if (action == "knx_status") {
|
if (action == "knx_status") {
|
||||||
return runtime->knxStatusJson();
|
const auto instance_id = QueryKnxInstanceId(query);
|
||||||
|
if (!instance_id.has_value()) {
|
||||||
|
const std::string message = KnxInstanceIdRangeMessage();
|
||||||
|
return ErrorResponse(ESP_ERR_INVALID_ARG, message.c_str());
|
||||||
|
}
|
||||||
|
return runtime->knxStatusJson(instance_id.value());
|
||||||
}
|
}
|
||||||
if (action == "config") {
|
if (action == "config") {
|
||||||
return runtime->configJson();
|
return runtime->configJson();
|
||||||
@@ -4762,6 +4812,61 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
|||||||
}
|
}
|
||||||
return handleGet("status", gateway_id.value());
|
return handleGet("status", gateway_id.value());
|
||||||
}
|
}
|
||||||
|
if (action == "knx_factory_reset") {
|
||||||
|
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||||
|
if (body_root == nullptr) {
|
||||||
|
return ErrorResponse(ESP_ERR_INVALID_ARG, "KNX factory reset confirmation JSON is required");
|
||||||
|
}
|
||||||
|
const char* confirm = JsonString(body_root, "confirm");
|
||||||
|
const bool confirmed = confirm != nullptr &&
|
||||||
|
std::string_view(confirm) == "reset-knx-factory-defaults";
|
||||||
|
const auto instance_id = JsonKnxInstanceId(body_root);
|
||||||
|
cJSON_Delete(body_root);
|
||||||
|
if (!confirmed) {
|
||||||
|
return ErrorResponse(ESP_ERR_INVALID_ARG, "KNX factory reset confirmation is required");
|
||||||
|
}
|
||||||
|
if (!instance_id.has_value()) {
|
||||||
|
const std::string message = KnxInstanceIdRangeMessage();
|
||||||
|
return ErrorResponse(ESP_ERR_INVALID_ARG, message.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool reset_runtime = instance_id.value() == 0;
|
||||||
|
const bool restart_router = reset_runtime &&
|
||||||
|
(runtime->knx_started ||
|
||||||
|
(runtime->knx_router != nullptr && runtime->knx_router->started()));
|
||||||
|
if (restart_router) {
|
||||||
|
const esp_err_t stop_err = stopKnxEndpoint(runtime);
|
||||||
|
if (stop_err != ESP_OK) {
|
||||||
|
return ErrorResponse(stop_err, "failed to stop KNX/IP bridge before factory reset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string nvs_namespace = runtime->openKnxNamespace();
|
||||||
|
if (instance_id.value() != 0) {
|
||||||
|
nvs_namespace += "_" + std::to_string(instance_id.value());
|
||||||
|
}
|
||||||
|
if (!EraseOpenKnxEeprom(nvs_namespace)) {
|
||||||
|
return ErrorResponse(ESP_FAIL, "failed to erase KNX runtime data");
|
||||||
|
}
|
||||||
|
|
||||||
|
openknx::FactoryFdskInfo info;
|
||||||
|
if (!openknx::ResetFactorySecurityForInstance(instance_id.value(), &info)) {
|
||||||
|
return ErrorResponse(ESP_FAIL, "failed to restore KNX factory security key");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restart_router) {
|
||||||
|
std::set<int> used_serial_uarts;
|
||||||
|
collectUsedRuntimeResources(runtime->channel.gateway_id, nullptr, nullptr, &used_serial_uarts);
|
||||||
|
const esp_err_t start_err = startKnxEndpoint(runtime, &used_serial_uarts);
|
||||||
|
if (start_err != ESP_OK) {
|
||||||
|
return ErrorResponse(start_err, runtime->knx_last_error.empty()
|
||||||
|
? "failed to restart KNX/IP bridge after factory reset"
|
||||||
|
: runtime->knx_last_error.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return runtime->knxStatusJson(instance_id.value());
|
||||||
|
}
|
||||||
if (action == "knx_security_read_factory_key") {
|
if (action == "knx_security_read_factory_key") {
|
||||||
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
|
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
|
||||||
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|||||||
constexpr char kHexAlphabet[] = "0123456789ABCDEF";
|
constexpr char kHexAlphabet[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
struct FactoryFdskContext {
|
struct FactoryFdskContext {
|
||||||
const char* nvsNamespace;
|
std::string nvsNamespace;
|
||||||
const char* productIdentity;
|
const char* productIdentity;
|
||||||
const char* derivationLabel;
|
const char* derivationLabel;
|
||||||
uint16_t manufacturerId;
|
uint16_t manufacturerId;
|
||||||
@@ -55,7 +55,7 @@ struct FactoryFdskContext {
|
|||||||
bool clearOpenKnxCache;
|
bool clearOpenKnxCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr FactoryFdskContext kReg1Context{
|
const FactoryFdskContext kReg1Context{
|
||||||
kNamespace,
|
kNamespace,
|
||||||
kProductIdentity,
|
kProductIdentity,
|
||||||
kFdskDerivationLabel,
|
kFdskDerivationLabel,
|
||||||
@@ -65,7 +65,7 @@ constexpr FactoryFdskContext kReg1Context{
|
|||||||
gateway::knx_internal::kReg1DaliSerialMacIncrement,
|
gateway::knx_internal::kReg1DaliSerialMacIncrement,
|
||||||
true};
|
true};
|
||||||
|
|
||||||
constexpr FactoryFdskContext kOamContext{
|
const FactoryFdskContext kOamContext{
|
||||||
kOamNamespace,
|
kOamNamespace,
|
||||||
kOamProductIdentity,
|
kOamProductIdentity,
|
||||||
kOamFdskDerivationLabel,
|
kOamFdskDerivationLabel,
|
||||||
@@ -77,6 +77,16 @@ constexpr FactoryFdskContext kOamContext{
|
|||||||
|
|
||||||
extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak));
|
extern "C" void knx_platform_clear_cached_fdsk() __attribute__((weak));
|
||||||
|
|
||||||
|
FactoryFdskContext reg1ContextForInstance(uint32_t instance_id) {
|
||||||
|
FactoryFdskContext context = kReg1Context;
|
||||||
|
context.serialMacIncrement = gateway::knx_internal::kReg1DaliSerialMacIncrement + instance_id;
|
||||||
|
context.clearOpenKnxCache = instance_id == 0;
|
||||||
|
if (instance_id != 0) {
|
||||||
|
context.nvsNamespace = std::string(kNamespace) + "_" + std::to_string(instance_id);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
std::string hexValue(uint32_t value, int width) {
|
std::string hexValue(uint32_t value, int width) {
|
||||||
std::array<char, 9> buffer{};
|
std::array<char, 9> buffer{};
|
||||||
std::snprintf(buffer.data(), buffer.size(), "%0*" PRIX32, width, value);
|
std::snprintf(buffer.data(), buffer.size(), "%0*" PRIX32, width, value);
|
||||||
@@ -213,7 +223,7 @@ void syncFactoryFdskToNvs(const FactoryFdskContext& context, const uint8_t* data
|
|||||||
size_t stored_size = stored.size();
|
size_t stored_size = stored.size();
|
||||||
|
|
||||||
nvs_handle_t handle = 0;
|
nvs_handle_t handle = 0;
|
||||||
esp_err_t err = nvs_open(context.nvsNamespace, NVS_READWRITE, &handle);
|
esp_err_t err = nvs_open(context.nvsNamespace.c_str(), NVS_READWRITE, &handle);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
|
ESP_LOGW(kTag, "failed to open KNX security NVS namespace: %s", esp_err_to_name(err));
|
||||||
return;
|
return;
|
||||||
@@ -483,6 +493,16 @@ FactoryFdskInfo LoadFactoryFdskInfo() {
|
|||||||
return LoadFactoryFdskInfoForContext(kReg1Context);
|
return LoadFactoryFdskInfoForContext(kReg1Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LoadFactoryFdskForInstance(uint32_t instance_id, uint8_t* data, size_t len) {
|
||||||
|
const auto context = reg1ContextForInstance(instance_id);
|
||||||
|
return LoadFactoryFdskForContext(context, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
FactoryFdskInfo LoadFactoryFdskInfoForInstance(uint32_t instance_id) {
|
||||||
|
const auto context = reg1ContextForInstance(instance_id);
|
||||||
|
return LoadFactoryFdskInfoForContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
|
bool GenerateFactoryFdsk(FactoryFdskInfo* info) {
|
||||||
return GenerateFactoryFdskForContext(kReg1Context, info);
|
return GenerateFactoryFdskForContext(kReg1Context, info);
|
||||||
}
|
}
|
||||||
@@ -495,10 +515,28 @@ bool ResetFactoryFdskCache(FactoryFdskInfo* info) {
|
|||||||
return ResetFactoryFdskCacheForContext(kReg1Context, info);
|
return ResetFactoryFdskCacheForContext(kReg1Context, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ResetFactorySecurityForInstance(uint32_t instance_id, FactoryFdskInfo* info) {
|
||||||
|
const auto context = reg1ContextForInstance(instance_id);
|
||||||
|
if (ensureNvsReady()) {
|
||||||
|
nvs_handle_t handle = 0;
|
||||||
|
if (nvs_open(context.nvsNamespace.c_str(), NVS_READWRITE, &handle) == ESP_OK) {
|
||||||
|
nvs_erase_key(handle, kFactoryFdskKey);
|
||||||
|
nvs_commit(handle);
|
||||||
|
nvs_close(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResetFactoryFdskCacheForContext(context, info);
|
||||||
|
}
|
||||||
|
|
||||||
FactoryCertificatePayload BuildFactoryCertificatePayload() {
|
FactoryCertificatePayload BuildFactoryCertificatePayload() {
|
||||||
return BuildFactoryCertificatePayloadForContext(kReg1Context);
|
return BuildFactoryCertificatePayloadForContext(kReg1Context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FactoryCertificatePayload BuildFactoryCertificatePayloadForInstance(uint32_t instance_id) {
|
||||||
|
const auto context = reg1ContextForInstance(instance_id);
|
||||||
|
return BuildFactoryCertificatePayloadForContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
bool LoadOamFactoryFdsk(uint8_t* data, size_t len) {
|
bool LoadOamFactoryFdsk(uint8_t* data, size_t len) {
|
||||||
return LoadFactoryFdskForContext(kOamContext, data, len);
|
return LoadFactoryFdskForContext(kOamContext, data, len);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user