Compare commits
2 Commits
f3fdd5c4e9
...
de0edd5ad9
| Author | SHA1 | Date | |
|---|---|---|---|
| de0edd5ad9 | |||
| e58115d303 |
@@ -621,6 +621,41 @@ config GATEWAY_START_KNX_BRIDGE_ENABLED
|
|||||||
Starts the KNXnet/IP tunneling/multicast listener at boot. Disabled by
|
Starts the KNXnet/IP tunneling/multicast listener at boot. Disabled by
|
||||||
default so UDP port 3671 is opened only after provisioning or explicit start.
|
default so UDP port 3671 is opened only after provisioning or explicit start.
|
||||||
|
|
||||||
|
config GATEWAY_KNX_DATA_SECURE_SUPPORTED
|
||||||
|
bool "Enable KNX Data Secure support"
|
||||||
|
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Compiles the OpenKNX SecurityInterfaceObject and SecureApplicationLayer
|
||||||
|
into the ETS runtime. This is the application-layer security path used
|
||||||
|
for secure KNX group-object and ETS tool traffic.
|
||||||
|
|
||||||
|
config GATEWAY_KNX_IP_SECURE_SUPPORTED
|
||||||
|
bool "Enable KNXnet/IP Secure support"
|
||||||
|
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Builds gateway support for KNXnet/IP Secure tunneling and routing. The
|
||||||
|
secure session transport is implemented by the gateway-owned KNX/IP
|
||||||
|
router and is separate from KNX Data Secure APDU handling.
|
||||||
|
|
||||||
|
config GATEWAY_KNX_SECURITY_DEV_ENDPOINTS
|
||||||
|
bool "Enable KNX security development HTTP endpoints"
|
||||||
|
depends on GATEWAY_KNX_DATA_SECURE_SUPPORTED || GATEWAY_KNX_IP_SECURE_SUPPORTED
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
Exposes development-only HTTP actions for reading, writing, generating,
|
||||||
|
and resetting KNX security material. Disable this for production builds.
|
||||||
|
|
||||||
|
config GATEWAY_KNX_SECURITY_PLAIN_NVS
|
||||||
|
bool "Store KNX security material in plain NVS"
|
||||||
|
depends on GATEWAY_KNX_DATA_SECURE_SUPPORTED || GATEWAY_KNX_IP_SECURE_SUPPORTED
|
||||||
|
default y
|
||||||
|
help
|
||||||
|
Stores development KNX security material in normal NVS. This is useful
|
||||||
|
during bring-up, but production builds should replace it with encrypted
|
||||||
|
NVS, flash encryption, and secure boot before exposing real keys.
|
||||||
|
|
||||||
config GATEWAY_KNX_MAIN_GROUP
|
config GATEWAY_KNX_MAIN_GROUP
|
||||||
int "KNX DALI main group"
|
int "KNX DALI main group"
|
||||||
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
depends on GATEWAY_KNX_BRIDGE_SUPPORTED
|
||||||
|
|||||||
@@ -688,6 +688,10 @@ CONFIG_GATEWAY_BACNET_BRIDGE_SUPPORTED=y
|
|||||||
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
# CONFIG_GATEWAY_START_BACNET_BRIDGE_ENABLED is not set
|
||||||
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_IP_SECURE_SUPPORTED is not set
|
||||||
|
# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set
|
||||||
|
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
|
||||||
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
|
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
|
||||||
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
||||||
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
||||||
@@ -1071,12 +1075,12 @@ CONFIG_BT_CTRL_RX_ANTENNA_INDEX_EFF=0
|
|||||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
|
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_N0 is not set
|
||||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set
|
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P3 is not set
|
||||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set
|
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P6 is not set
|
||||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9=y
|
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P9 is not set
|
||||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set
|
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P12 is not set
|
||||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set
|
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P15 is not set
|
||||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set
|
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P18 is not set
|
||||||
# CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20 is not set
|
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_P20=y
|
||||||
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=11
|
CONFIG_BT_CTRL_DFT_TX_POWER_LEVEL_EFF=15
|
||||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
|
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y
|
||||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
|
CONFIG_BT_CTRL_BLE_ADV_REPORT_FLOW_CTRL_NUM=100
|
||||||
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
|
CONFIG_BT_CTRL_BLE_ADV_REPORT_DISCARD_THRSHOLD=20
|
||||||
|
|||||||
@@ -16,3 +16,4 @@ CONFIG_ETH_SPI_ETHERNET_W5500=y
|
|||||||
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
||||||
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
||||||
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||||
|
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||||
|
|||||||
+39
-11
@@ -656,6 +656,26 @@ CONFIG_GATEWAY_SMARTCONFIG_SUPPORTED=y
|
|||||||
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
|
# CONFIG_GATEWAY_START_ESPNOW_SETUP_ENABLED is not set
|
||||||
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
|
# CONFIG_GATEWAY_START_SMARTCONFIG_ENABLED is not set
|
||||||
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
CONFIG_GATEWAY_SMARTCONFIG_TIMEOUT_SEC=60
|
||||||
|
CONFIG_GATEWAY_ETHERNET_SUPPORTED=y
|
||||||
|
CONFIG_GATEWAY_START_ETHERNET_ENABLED=y
|
||||||
|
CONFIG_GATEWAY_ETHERNET_IGNORE_INIT_FAILURE=y
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gateway Wired Ethernet
|
||||||
|
#
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_SPI_HOST=1
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_SCLK_GPIO=14
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_MOSI_GPIO=13
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_MISO_GPIO=12
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_CS_GPIO=15
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_INT_GPIO=4
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_POLL_PERIOD_MS=0
|
||||||
|
CONFIG_GATEWAY_ETHERNET_W5500_CLOCK_MHZ=36
|
||||||
|
CONFIG_GATEWAY_ETHERNET_PHY_RESET_GPIO=5
|
||||||
|
CONFIG_GATEWAY_ETHERNET_PHY_ADDR=1
|
||||||
|
CONFIG_GATEWAY_ETHERNET_RX_TASK_STACK_SIZE=3072
|
||||||
|
# end of Gateway Wired Ethernet
|
||||||
|
|
||||||
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
CONFIG_GATEWAY_BRIDGE_SUPPORTED=y
|
||||||
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
|
CONFIG_GATEWAY_MODBUS_BRIDGE_SUPPORTED=y
|
||||||
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
|
# CONFIG_GATEWAY_START_MODBUS_BRIDGE_ENABLED is not set
|
||||||
@@ -666,7 +686,24 @@ 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
|
||||||
# CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED is not set
|
CONFIG_GATEWAY_KNX_BRIDGE_SUPPORTED=y
|
||||||
|
CONFIG_GATEWAY_START_KNX_BRIDGE_ENABLED=y
|
||||||
|
CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED=y
|
||||||
|
# CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED is not set
|
||||||
|
# CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS is not set
|
||||||
|
CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS=y
|
||||||
|
CONFIG_GATEWAY_KNX_MAIN_GROUP=0
|
||||||
|
CONFIG_GATEWAY_KNX_TUNNEL_ENABLED=y
|
||||||
|
CONFIG_GATEWAY_KNX_MULTICAST_ENABLED=y
|
||||||
|
CONFIG_GATEWAY_KNX_UDP_PORT=3671
|
||||||
|
CONFIG_GATEWAY_KNX_MULTICAST_ADDRESS="224.0.23.12"
|
||||||
|
CONFIG_GATEWAY_KNX_INDIVIDUAL_ADDRESS=4353
|
||||||
|
CONFIG_GATEWAY_KNX_TP_UART_PORT=0
|
||||||
|
CONFIG_GATEWAY_KNX_TP_TX_PIN=-1
|
||||||
|
CONFIG_GATEWAY_KNX_TP_RX_PIN=-1
|
||||||
|
CONFIG_GATEWAY_KNX_TP_BAUDRATE=19200
|
||||||
|
CONFIG_GATEWAY_BRIDGE_KNX_TASK_STACK_SIZE=8192
|
||||||
|
CONFIG_GATEWAY_BRIDGE_KNX_TASK_PRIORITY=5
|
||||||
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
|
||||||
@@ -675,16 +712,7 @@ CONFIG_GATEWAY_BRIDGE_BACNET_TASK_STACK_SIZE=8192
|
|||||||
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
|
CONFIG_GATEWAY_BRIDGE_BACNET_TASK_PRIORITY=5
|
||||||
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
CONFIG_GATEWAY_USB_STARTUP_DEBUG_JTAG=y
|
||||||
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
# CONFIG_GATEWAY_USB_STARTUP_SETUP_SERIAL is not set
|
||||||
CONFIG_GATEWAY_485_CONTROL_ENABLED=y
|
# CONFIG_GATEWAY_485_CONTROL_ENABLED is not set
|
||||||
CONFIG_GATEWAY_485_CONTROL_BAUDRATE=9600
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_TX_PIN=-1
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_RX_PIN=-1
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_RX_BUFFER=256
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_TX_BUFFER=256
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_READ_TIMEOUT_MS=20
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_WRITE_TIMEOUT_MS=20
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_TASK_STACK_SIZE=4096
|
|
||||||
CONFIG_GATEWAY_485_CONTROL_TASK_PRIORITY=4
|
|
||||||
# end of Gateway Startup Services
|
# end of Gateway Startup Services
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
namespace gateway {
|
namespace gateway {
|
||||||
|
|
||||||
class DaliDomainService;
|
class DaliDomainService;
|
||||||
|
struct DaliRawFrame;
|
||||||
class GatewayCache;
|
class GatewayCache;
|
||||||
|
|
||||||
struct GatewayBridgeServiceConfig {
|
struct GatewayBridgeServiceConfig {
|
||||||
@@ -65,6 +66,7 @@ class GatewayBridgeService {
|
|||||||
|
|
||||||
ChannelRuntime* findRuntime(uint8_t gateway_id);
|
ChannelRuntime* findRuntime(uint8_t gateway_id);
|
||||||
const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
|
const ChannelRuntime* findRuntime(uint8_t gateway_id) const;
|
||||||
|
void handleDaliRawFrame(const DaliRawFrame& frame);
|
||||||
void collectUsedRuntimeResources(uint8_t except_gateway_id,
|
void collectUsedRuntimeResources(uint8_t except_gateway_id,
|
||||||
std::set<uint16_t>* modbus_tcp_ports,
|
std::set<uint16_t>* modbus_tcp_ports,
|
||||||
std::set<uint16_t>* knx_udp_ports,
|
std::set<uint16_t>* knx_udp_ports,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "gateway_modbus.hpp"
|
#include "gateway_modbus.hpp"
|
||||||
#include "gateway_provisioning.hpp"
|
#include "gateway_provisioning.hpp"
|
||||||
#include "openknx_idf/ets_memory_loader.h"
|
#include "openknx_idf/ets_memory_loader.h"
|
||||||
|
#include "openknx_idf/security_storage.h"
|
||||||
|
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "driver/uart.h"
|
#include "driver/uart.h"
|
||||||
@@ -56,6 +57,10 @@ constexpr uint32_t kBacnetMaxObjectInstance = 4194303;
|
|||||||
constexpr uint32_t kBacnetReliabilityNoFaultDetected = 0;
|
constexpr uint32_t kBacnetReliabilityNoFaultDetected = 0;
|
||||||
constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
|
constexpr uint32_t kBacnetReliabilityCommunicationFailure = 12;
|
||||||
constexpr const char* kModbusManagementPrefix = "@DALIGW";
|
constexpr const char* kModbusManagementPrefix = "@DALIGW";
|
||||||
|
constexpr uint8_t kDaliGroupRawMin = 0x80;
|
||||||
|
constexpr uint8_t kDaliGroupRawMax = 0x9F;
|
||||||
|
constexpr uint8_t kDaliCmdOff = 0x00;
|
||||||
|
constexpr uint8_t kDaliCmdRecallMax = 0x05;
|
||||||
|
|
||||||
struct GatewayBridgeStoredConfig {
|
struct GatewayBridgeStoredConfig {
|
||||||
BridgeRuntimeConfig bridge;
|
BridgeRuntimeConfig bridge;
|
||||||
@@ -70,6 +75,11 @@ struct BridgeDiscoveryEntry {
|
|||||||
DaliDomainSnapshot discovery;
|
DaliDomainSnapshot discovery;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DaliKnxStatusUpdate {
|
||||||
|
GatewayKnxDaliTarget target;
|
||||||
|
uint8_t actual_level{0};
|
||||||
|
};
|
||||||
|
|
||||||
using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>;
|
using BridgeDiscoveryInventory = std::map<int, BridgeDiscoveryEntry>;
|
||||||
|
|
||||||
class LockGuard {
|
class LockGuard {
|
||||||
@@ -121,6 +131,25 @@ GatewayBridgeHttpResponse ErrorResponse(esp_err_t err, const char* message) {
|
|||||||
return GatewayBridgeHttpResponse{err, body};
|
return GatewayBridgeHttpResponse{err, body};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cJSON* FactoryFdskInfoToCjson(const openknx::FactoryFdskInfo& fdsk_info,
|
||||||
|
bool include_secret_strings) {
|
||||||
|
cJSON* root = cJSON_CreateObject();
|
||||||
|
if (root == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
cJSON_AddBoolToObject(root, "available", fdsk_info.available);
|
||||||
|
if (fdsk_info.available) {
|
||||||
|
cJSON_AddStringToObject(root, "serialNumber", fdsk_info.serialNumber.c_str());
|
||||||
|
cJSON_AddNumberToObject(root, "labelLength", static_cast<double>(fdsk_info.label.size()));
|
||||||
|
cJSON_AddNumberToObject(root, "qrCodeLength", static_cast<double>(fdsk_info.qrCode.size()));
|
||||||
|
if (include_secret_strings) {
|
||||||
|
cJSON_AddStringToObject(root, "label", fdsk_info.label.c_str());
|
||||||
|
cJSON_AddStringToObject(root, "qrCode", fdsk_info.qrCode.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
const char* JsonString(const cJSON* parent, const char* name) {
|
const char* JsonString(const cJSON* parent, const char* name) {
|
||||||
const cJSON* item = cJSON_GetObjectItemCaseSensitive(parent, name);
|
const cJSON* item = cJSON_GetObjectItemCaseSensitive(parent, name);
|
||||||
return cJSON_IsString(item) && item->valuestring != nullptr ? item->valuestring : nullptr;
|
return cJSON_IsString(item) && item->valuestring != nullptr ? item->valuestring : nullptr;
|
||||||
@@ -200,6 +229,51 @@ bool ValidDaliAddress(int address) {
|
|||||||
return address >= 0 && address <= 127;
|
return address >= 0 && address <= 127;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<GatewayKnxDaliTarget> DecodeKnxDaliTarget(uint8_t raw_addr) {
|
||||||
|
if (raw_addr <= 0x7F) {
|
||||||
|
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
|
||||||
|
static_cast<int>(raw_addr >> 1)};
|
||||||
|
}
|
||||||
|
if (raw_addr >= kDaliGroupRawMin && raw_addr <= kDaliGroupRawMax) {
|
||||||
|
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
|
||||||
|
static_cast<int>((raw_addr - kDaliGroupRawMin) >> 1)};
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DaliKnxStatusUpdate> DecodeDaliKnxStatusUpdate(const DaliRawFrame& frame) {
|
||||||
|
if (frame.data.size() != 2 && frame.data.size() != 3) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
uint8_t raw_addr = 0;
|
||||||
|
uint8_t command = 0;
|
||||||
|
if (frame.data.size() == 2) {
|
||||||
|
raw_addr = frame.data[0];
|
||||||
|
command = frame.data[1];
|
||||||
|
if (raw_addr == 0xBE) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
raw_addr = frame.data[1];
|
||||||
|
command = frame.data[2];
|
||||||
|
}
|
||||||
|
auto target = DecodeKnxDaliTarget(raw_addr);
|
||||||
|
if (!target.has_value()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if ((raw_addr & 0x01U) == 0) {
|
||||||
|
if (command > 254) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return DaliKnxStatusUpdate{*target, command};
|
||||||
|
}
|
||||||
|
if (command == kDaliCmdOff || command == kDaliCmdRecallMax) {
|
||||||
|
return DaliKnxStatusUpdate{*target,
|
||||||
|
static_cast<uint8_t>(command == kDaliCmdOff ? 0 : 254)};
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
bool ValidShortAddress(int address) {
|
bool ValidShortAddress(int address) {
|
||||||
return address >= 0 && address <= kMaxDaliShortAddress;
|
return address >= 0 && address <= kMaxDaliShortAddress;
|
||||||
}
|
}
|
||||||
@@ -2041,6 +2115,40 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
cJSON_AddStringToObject(knx_json, "lastError",
|
cJSON_AddStringToObject(knx_json, "lastError",
|
||||||
knx_last_error.empty() ? router_error.c_str()
|
knx_last_error.empty() ? router_error.c_str()
|
||||||
: knx_last_error.c_str());
|
: knx_last_error.c_str());
|
||||||
|
cJSON* security_json = cJSON_CreateObject();
|
||||||
|
if (security_json != nullptr) {
|
||||||
|
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||||
|
cJSON_AddBoolToObject(security_json, "dataSecureCompiled", true);
|
||||||
|
#else
|
||||||
|
cJSON_AddBoolToObject(security_json, "dataSecureCompiled", false);
|
||||||
|
#endif
|
||||||
|
#if defined(CONFIG_GATEWAY_KNX_IP_SECURE_SUPPORTED)
|
||||||
|
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", true);
|
||||||
|
#else
|
||||||
|
cJSON_AddBoolToObject(security_json, "knxnetIpSecureCompiled", false);
|
||||||
|
#endif
|
||||||
|
cJSON_AddBoolToObject(security_json, "knxnetIpSecureImplemented", false);
|
||||||
|
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS)
|
||||||
|
cJSON_AddBoolToObject(security_json, "developmentEndpointsEnabled", true);
|
||||||
|
#else
|
||||||
|
cJSON_AddBoolToObject(security_json, "developmentEndpointsEnabled", false);
|
||||||
|
#endif
|
||||||
|
#if defined(CONFIG_GATEWAY_KNX_SECURITY_PLAIN_NVS)
|
||||||
|
cJSON_AddBoolToObject(security_json, "plainNvsStorage", true);
|
||||||
|
cJSON_AddStringToObject(security_json, "storage", "plain_nvs_development");
|
||||||
|
#else
|
||||||
|
cJSON_AddBoolToObject(security_json, "plainNvsStorage", false);
|
||||||
|
cJSON_AddStringToObject(security_json, "storage", "none");
|
||||||
|
#endif
|
||||||
|
#if defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||||
|
const auto fdsk_info = openknx::LoadFactoryFdskInfo();
|
||||||
|
cJSON* fdsk_json = FactoryFdskInfoToCjson(fdsk_info, false);
|
||||||
|
if (fdsk_json != nullptr) {
|
||||||
|
cJSON_AddItemToObject(security_json, "factorySetupKey", fdsk_json);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
cJSON_AddItemToObject(knx_json, "security", security_json);
|
||||||
|
}
|
||||||
if (effective_knx.has_value()) {
|
if (effective_knx.has_value()) {
|
||||||
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", effective_knx->dali_router_enabled);
|
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", effective_knx->dali_router_enabled);
|
||||||
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled", effective_knx->ip_router_enabled);
|
cJSON_AddBoolToObject(knx_json, "ipRouterEnabled", effective_knx->ip_router_enabled);
|
||||||
@@ -3598,6 +3706,9 @@ esp_err_t GatewayBridgeService::start() {
|
|||||||
runtimes_.push_back(std::move(runtime));
|
runtimes_.push_back(std::move(runtime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dali_domain_.addRawFrameSink(
|
||||||
|
[this](const DaliRawFrame& frame) { handleDaliRawFrame(frame); });
|
||||||
|
|
||||||
std::set<int> used_serial_uarts;
|
std::set<int> used_serial_uarts;
|
||||||
if (config_.modbus_enabled && config_.modbus_startup_enabled) {
|
if (config_.modbus_enabled && config_.modbus_startup_enabled) {
|
||||||
std::set<uint16_t> used_modbus_ports;
|
std::set<uint16_t> used_modbus_ports;
|
||||||
@@ -3654,6 +3765,22 @@ const GatewayBridgeService::ChannelRuntime* GatewayBridgeService::findRuntime(
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LockGuard guard(runtime->lock);
|
||||||
|
if (!runtime->knx_started || runtime->knx_router == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runtime->knx_router->publishDaliStatus(update->target, update->actual_level);
|
||||||
|
}
|
||||||
|
|
||||||
void GatewayBridgeService::collectUsedRuntimeResources(
|
void GatewayBridgeService::collectUsedRuntimeResources(
|
||||||
uint8_t except_gateway_id,
|
uint8_t except_gateway_id,
|
||||||
std::set<uint16_t>* modbus_tcp_ports,
|
std::set<uint16_t>* modbus_tcp_ports,
|
||||||
@@ -4008,6 +4135,36 @@ GatewayBridgeHttpResponse GatewayBridgeService::handlePost(
|
|||||||
}
|
}
|
||||||
return handleGet("knx", gateway_id.value());
|
return handleGet("knx", gateway_id.value());
|
||||||
}
|
}
|
||||||
|
if (action == "knx_security_read_factory_key") {
|
||||||
|
#if defined(CONFIG_GATEWAY_KNX_SECURITY_DEV_ENDPOINTS) && \
|
||||||
|
defined(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||||
|
cJSON* body_root = body.empty() ? nullptr : cJSON_ParseWithLength(body.data(), body.size());
|
||||||
|
if (body_root == nullptr) {
|
||||||
|
return ErrorResponse(ESP_ERR_INVALID_ARG, "confirmation JSON is required");
|
||||||
|
}
|
||||||
|
const char* confirm = JsonString(body_root, "confirm");
|
||||||
|
const bool confirmed = confirm != nullptr &&
|
||||||
|
std::string_view(confirm) == "read-factory-setup-key";
|
||||||
|
cJSON_Delete(body_root);
|
||||||
|
if (!confirmed) {
|
||||||
|
return ErrorResponse(ESP_ERR_INVALID_ARG, "factory setup key read confirmation is required");
|
||||||
|
}
|
||||||
|
cJSON* response = cJSON_CreateObject();
|
||||||
|
if (response == nullptr) {
|
||||||
|
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate security response");
|
||||||
|
}
|
||||||
|
cJSON* fdsk_json = FactoryFdskInfoToCjson(openknx::LoadFactoryFdskInfo(), true);
|
||||||
|
if (fdsk_json == nullptr) {
|
||||||
|
cJSON_Delete(response);
|
||||||
|
return ErrorResponse(ESP_ERR_NO_MEM, "failed to allocate factory setup key response");
|
||||||
|
}
|
||||||
|
cJSON_AddItemToObject(response, "factorySetupKey", fdsk_json);
|
||||||
|
return JsonOk(response);
|
||||||
|
#else
|
||||||
|
return ErrorResponse(ESP_ERR_NOT_SUPPORTED,
|
||||||
|
"KNX security development endpoints are disabled");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
if (action == "bacnet_start") {
|
if (action == "bacnet_start") {
|
||||||
const esp_err_t err = runtime->startBacnet();
|
const esp_err_t err = runtime->startBacnet();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "lwip/sockets.h"
|
#include "lwip/sockets.h"
|
||||||
|
|
||||||
@@ -197,6 +198,7 @@ class GatewayKnxTpIpRouter {
|
|||||||
esp_err_t stop();
|
esp_err_t stop();
|
||||||
bool started() const;
|
bool started() const;
|
||||||
const std::string& lastError() const;
|
const std::string& lastError() const;
|
||||||
|
bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void TaskEntry(void* arg);
|
static void TaskEntry(void* arg);
|
||||||
@@ -225,6 +227,8 @@ class GatewayKnxTpIpRouter {
|
|||||||
const ::sockaddr_in& remote);
|
const ::sockaddr_in& remote);
|
||||||
void sendRoutingIndication(const uint8_t* data, size_t len);
|
void sendRoutingIndication(const uint8_t* data, size_t len);
|
||||||
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len);
|
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len);
|
||||||
|
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
|
||||||
|
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
|
||||||
void syncOpenKnxConfigFromDevice();
|
void syncOpenKnxConfigFromDevice();
|
||||||
uint16_t effectiveIndividualAddress() const;
|
uint16_t effectiveIndividualAddress() const;
|
||||||
uint16_t effectiveTunnelAddress() const;
|
uint16_t effectiveTunnelAddress() const;
|
||||||
@@ -239,6 +243,7 @@ class GatewayKnxTpIpRouter {
|
|||||||
GatewayKnxConfig config_;
|
GatewayKnxConfig config_;
|
||||||
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
|
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
|
||||||
TaskHandle_t task_handle_{nullptr};
|
TaskHandle_t task_handle_{nullptr};
|
||||||
|
SemaphoreHandle_t openknx_lock_{nullptr};
|
||||||
std::atomic_bool stop_requested_{false};
|
std::atomic_bool stop_requested_{false};
|
||||||
std::atomic_bool started_{false};
|
std::atomic_bool started_{false};
|
||||||
int udp_sock_{-1};
|
int udp_sock_{-1};
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ constexpr uint16_t kGwReg1AppKoBroadcastDimm = 2;
|
|||||||
constexpr uint8_t kGwReg1KoSwitch = 0;
|
constexpr uint8_t kGwReg1KoSwitch = 0;
|
||||||
constexpr uint8_t kGwReg1KoDimmAbsolute = 3;
|
constexpr uint8_t kGwReg1KoDimmAbsolute = 3;
|
||||||
constexpr uint8_t kGwReg1KoColor = 6;
|
constexpr uint8_t kGwReg1KoColor = 6;
|
||||||
|
constexpr uint8_t kGwReg1KoSwitchState = 1;
|
||||||
|
constexpr uint8_t kGwReg1KoDimmState = 4;
|
||||||
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
|
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
|
||||||
constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
|
constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
|
||||||
constexpr uint8_t kReg1FunctionType = 2;
|
constexpr uint8_t kReg1FunctionType = 2;
|
||||||
@@ -84,6 +86,31 @@ struct DecodedGroupWrite {
|
|||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SemaphoreGuard {
|
||||||
|
public:
|
||||||
|
explicit SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore_(semaphore) {
|
||||||
|
if (semaphore_ != nullptr) {
|
||||||
|
xSemaphoreTake(semaphore_, portMAX_DELAY);
|
||||||
|
locked_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SemaphoreGuard() {
|
||||||
|
if (locked_) {
|
||||||
|
xSemaphoreGive(semaphore_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t semaphore_{nullptr};
|
||||||
|
bool locked_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t DaliArcLevelToDpt5(uint8_t actual_level) {
|
||||||
|
return static_cast<uint8_t>(
|
||||||
|
std::clamp<int>(static_cast<int>(std::lround(actual_level * 255.0 / 254.0)), 0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t ReadBe16(const uint8_t* data) {
|
uint16_t ReadBe16(const uint8_t* data) {
|
||||||
return static_cast<uint16_t>((static_cast<uint16_t>(data[0]) << 8) | data[1]);
|
return static_cast<uint16_t>((static_cast<uint16_t>(data[0]) << 8) | data[1]);
|
||||||
}
|
}
|
||||||
@@ -309,6 +336,22 @@ std::optional<DecodedGroupWrite> DecodeCemiGroupWrite(const uint8_t* data, size_
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsCemiGroupFrame(const uint8_t* data, size_t len) {
|
||||||
|
if (data == nullptr || len < 10) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint8_t message_code = data[0];
|
||||||
|
if (message_code != kCemiLDataReq && message_code != kCemiLDataInd &&
|
||||||
|
message_code != kCemiLDataCon) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const size_t base = 2U + data[1];
|
||||||
|
if (len < base + 8U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (data[base + 1] & 0x80) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t Reg1PercentToArc(uint8_t value) {
|
uint8_t Reg1PercentToArc(uint8_t value) {
|
||||||
if (value == 0 || value == 0xff) {
|
if (value == 0 || value == 0xff) {
|
||||||
return value;
|
return value;
|
||||||
@@ -1528,9 +1571,17 @@ GatewayKnxTpIpRouter::GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHa
|
|||||||
std::string openknx_namespace)
|
std::string openknx_namespace)
|
||||||
: bridge_(bridge),
|
: bridge_(bridge),
|
||||||
handler_(std::move(handler)),
|
handler_(std::move(handler)),
|
||||||
openknx_namespace_(std::move(openknx_namespace)) {}
|
openknx_namespace_(std::move(openknx_namespace)) {
|
||||||
|
openknx_lock_ = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
|
||||||
GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() { stop(); }
|
GatewayKnxTpIpRouter::~GatewayKnxTpIpRouter() {
|
||||||
|
stop();
|
||||||
|
if (openknx_lock_ != nullptr) {
|
||||||
|
vSemaphoreDelete(openknx_lock_);
|
||||||
|
openknx_lock_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GatewayKnxTpIpRouter::setConfig(const GatewayKnxConfig& config) { config_ = config; }
|
void GatewayKnxTpIpRouter::setConfig(const GatewayKnxConfig& config) { config_ = config; }
|
||||||
|
|
||||||
@@ -1559,6 +1610,13 @@ esp_err_t GatewayKnxTpIpRouter::start(uint32_t task_stack_size, UBaseType_t task
|
|||||||
std::vector<uint8_t>* response) {
|
std::vector<uint8_t>* response) {
|
||||||
return bridge_.handleFunctionPropertyState(object_index, property_id, data, len, response);
|
return bridge_.handleFunctionPropertyState(object_index, property_id, data, len, response);
|
||||||
});
|
});
|
||||||
|
ets_device_->setGroupWriteHandler(
|
||||||
|
[this](uint16_t group_address, const uint8_t* data, size_t len) {
|
||||||
|
const DaliBridgeResult result = bridge_.handleGroupWrite(group_address, data, len);
|
||||||
|
if (!result.ok && !result.error.empty()) {
|
||||||
|
ESP_LOGD(kTag, "secure KNX group write not routed to DALI: %s", result.error.c_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
if (!configureTpUart()) {
|
if (!configureTpUart()) {
|
||||||
ets_device_.reset();
|
ets_device_.reset();
|
||||||
closeSockets();
|
closeSockets();
|
||||||
@@ -1590,6 +1648,40 @@ bool GatewayKnxTpIpRouter::started() const { return started_; }
|
|||||||
|
|
||||||
const std::string& GatewayKnxTpIpRouter::lastError() const { return last_error_; }
|
const std::string& GatewayKnxTpIpRouter::lastError() const { return last_error_; }
|
||||||
|
|
||||||
|
bool GatewayKnxTpIpRouter::publishDaliStatus(const GatewayKnxDaliTarget& target,
|
||||||
|
uint8_t actual_level) {
|
||||||
|
if (!started_ || !config_.ip_router_enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint16_t switch_object = 0;
|
||||||
|
uint16_t dimm_object = 0;
|
||||||
|
if (target.kind == GatewayKnxDaliTargetKind::kShortAddress) {
|
||||||
|
if (target.address < 0 || target.address > 63) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint16_t base = kGwReg1AdrKoOffset +
|
||||||
|
kGwReg1AdrKoBlockSize * static_cast<uint16_t>(target.address);
|
||||||
|
switch_object = base + kGwReg1KoSwitchState;
|
||||||
|
dimm_object = base + kGwReg1KoDimmState;
|
||||||
|
} else if (target.kind == GatewayKnxDaliTargetKind::kGroup) {
|
||||||
|
if (target.address < 0 || target.address > 15) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const uint16_t base = kGwReg1GrpKoOffset +
|
||||||
|
kGwReg1GrpKoBlockSize * static_cast<uint16_t>(target.address);
|
||||||
|
switch_object = base + kGwReg1KoSwitchState;
|
||||||
|
dimm_object = base + kGwReg1KoDimmState;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t switch_value = actual_level > 0 ? 1 : 0;
|
||||||
|
const uint8_t dimm_value = DaliArcLevelToDpt5(actual_level);
|
||||||
|
bool emitted = emitOpenKnxGroupValue(switch_object, &switch_value, 1);
|
||||||
|
emitted = emitOpenKnxGroupValue(dimm_object, &dimm_value, 1) || emitted;
|
||||||
|
return emitted;
|
||||||
|
}
|
||||||
|
|
||||||
void GatewayKnxTpIpRouter::TaskEntry(void* arg) {
|
void GatewayKnxTpIpRouter::TaskEntry(void* arg) {
|
||||||
static_cast<GatewayKnxTpIpRouter*>(arg)->taskLoop();
|
static_cast<GatewayKnxTpIpRouter*>(arg)->taskLoop();
|
||||||
}
|
}
|
||||||
@@ -1603,9 +1695,12 @@ void GatewayKnxTpIpRouter::taskLoop() {
|
|||||||
reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
reinterpret_cast<sockaddr*>(&remote), &remote_len);
|
||||||
if (received <= 0) {
|
if (received <= 0) {
|
||||||
pollTpUart();
|
pollTpUart();
|
||||||
|
{
|
||||||
|
SemaphoreGuard guard(openknx_lock_);
|
||||||
if (ets_device_ != nullptr) {
|
if (ets_device_ != nullptr) {
|
||||||
ets_device_->loop();
|
ets_device_->loop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!stop_requested_) {
|
if (!stop_requested_) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
@@ -1613,16 +1708,22 @@ void GatewayKnxTpIpRouter::taskLoop() {
|
|||||||
}
|
}
|
||||||
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
|
handleUdpDatagram(buffer.data(), static_cast<size_t>(received), remote);
|
||||||
pollTpUart();
|
pollTpUart();
|
||||||
|
{
|
||||||
|
SemaphoreGuard guard(openknx_lock_);
|
||||||
if (ets_device_ != nullptr) {
|
if (ets_device_ != nullptr) {
|
||||||
ets_device_->loop();
|
ets_device_->loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
finishTask();
|
finishTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayKnxTpIpRouter::finishTask() {
|
void GatewayKnxTpIpRouter::finishTask() {
|
||||||
closeSockets();
|
closeSockets();
|
||||||
|
{
|
||||||
|
SemaphoreGuard guard(openknx_lock_);
|
||||||
ets_device_.reset();
|
ets_device_.reset();
|
||||||
|
}
|
||||||
started_ = false;
|
started_ = false;
|
||||||
task_handle_ = nullptr;
|
task_handle_ = nullptr;
|
||||||
vTaskDelete(nullptr);
|
vTaskDelete(nullptr);
|
||||||
@@ -1805,10 +1906,13 @@ void GatewayKnxTpIpRouter::handleRoutingIndication(const uint8_t* body, size_t l
|
|||||||
if (body == nullptr || len == 0) {
|
if (body == nullptr || len == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const bool consumed_by_openknx = handleOpenKnxBusFrame(body, len);
|
||||||
|
if (!consumed_by_openknx) {
|
||||||
const DaliBridgeResult result = handler_(body, len);
|
const DaliBridgeResult result = handler_(body, len);
|
||||||
if (!result.ok && !result.error.empty()) {
|
if (!result.ok && !result.error.empty()) {
|
||||||
ESP_LOGD(kTag, "KNX routing indication ignored: %s", result.error.c_str());
|
ESP_LOGD(kTag, "KNX routing indication ignored: %s", result.error.c_str());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
forwardCemiToTp(body, len);
|
forwardCemiToTp(body, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1831,8 +1935,12 @@ void GatewayKnxTpIpRouter::handleTunnellingRequest(const uint8_t* body, size_t l
|
|||||||
sendTunnellingAck(channel_id, sequence, kKnxNoError, remote);
|
sendTunnellingAck(channel_id, sequence, kKnxNoError, remote);
|
||||||
const uint8_t* cemi = body + 4;
|
const uint8_t* cemi = body + 4;
|
||||||
const size_t cemi_len = len - 4;
|
const size_t cemi_len = len - 4;
|
||||||
|
const bool group_frame = IsCemiGroupFrame(cemi, cemi_len);
|
||||||
const bool consumed_by_openknx = handleOpenKnxTunnelFrame(cemi, cemi_len);
|
const bool consumed_by_openknx = handleOpenKnxTunnelFrame(cemi, cemi_len);
|
||||||
if (consumed_by_openknx) {
|
if (consumed_by_openknx) {
|
||||||
|
if (group_frame) {
|
||||||
|
forwardCemiToTp(cemi, cemi_len);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const DaliBridgeResult result = handler_(cemi, cemi_len);
|
const DaliBridgeResult result = handler_(cemi, cemi_len);
|
||||||
@@ -1959,6 +2067,7 @@ void GatewayKnxTpIpRouter::sendRoutingIndication(const uint8_t* data, size_t len
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t len) {
|
bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t len) {
|
||||||
|
SemaphoreGuard guard(openknx_lock_);
|
||||||
if (ets_device_ == nullptr) {
|
if (ets_device_ == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1970,6 +2079,32 @@ bool GatewayKnxTpIpRouter::handleOpenKnxTunnelFrame(const uint8_t* data, size_t
|
|||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GatewayKnxTpIpRouter::handleOpenKnxBusFrame(const uint8_t* data, size_t len) {
|
||||||
|
SemaphoreGuard guard(openknx_lock_);
|
||||||
|
if (ets_device_ == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool consumed = ets_device_->handleBusFrame(data, len);
|
||||||
|
syncOpenKnxConfigFromDevice();
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GatewayKnxTpIpRouter::emitOpenKnxGroupValue(uint16_t group_object_number,
|
||||||
|
const uint8_t* data, size_t len) {
|
||||||
|
SemaphoreGuard guard(openknx_lock_);
|
||||||
|
if (ets_device_ == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool emitted = ets_device_->emitGroupValue(
|
||||||
|
group_object_number, data, len, [this](const uint8_t* frame_data, size_t frame_len) {
|
||||||
|
sendRoutingIndication(frame_data, frame_len);
|
||||||
|
sendTunnelIndication(frame_data, frame_len);
|
||||||
|
forwardCemiToTp(frame_data, frame_len);
|
||||||
|
});
|
||||||
|
syncOpenKnxConfigFromDevice();
|
||||||
|
return emitted;
|
||||||
|
}
|
||||||
|
|
||||||
void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
|
void GatewayKnxTpIpRouter::syncOpenKnxConfigFromDevice() {
|
||||||
if (ets_device_ == nullptr) {
|
if (ets_device_ == nullptr) {
|
||||||
return;
|
return;
|
||||||
@@ -2122,10 +2257,13 @@ void GatewayKnxTpIpRouter::handleTpTelegram(const uint8_t* data, size_t len) {
|
|||||||
if (!cemi.has_value()) {
|
if (!cemi.has_value()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const bool consumed_by_openknx = handleOpenKnxBusFrame(cemi->data(), cemi->size());
|
||||||
|
if (!consumed_by_openknx) {
|
||||||
const DaliBridgeResult result = handler_(cemi->data(), cemi->size());
|
const DaliBridgeResult result = handler_(cemi->data(), cemi->size());
|
||||||
if (!result.ok && !result.error.empty()) {
|
if (!result.ok && !result.error.empty()) {
|
||||||
ESP_LOGD(kTag, "KNX TP frame not routed to DALI: %s", result.error.c_str());
|
ESP_LOGD(kTag, "KNX TP frame not routed to DALI: %s", result.error.c_str());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
sendTunnelIndication(cemi->data(), cemi->size());
|
sendTunnelIndication(cemi->data(), cemi->size());
|
||||||
sendRoutingIndication(cemi->data(), cemi->size());
|
sendRoutingIndication(cemi->data(), cemi->size());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ file(GLOB OPENKNX_SRCS
|
|||||||
"${OPENKNX_ROOT}/src/knx/*.cpp"
|
"${OPENKNX_ROOT}/src/knx/*.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||||
|
list(APPEND OPENKNX_SRCS
|
||||||
|
"${OPENKNX_ROOT}/src/knx/aes.c"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(TPUART_SRCS
|
set(TPUART_SRCS
|
||||||
"${TPUART_ROOT}/src/TPUart/DataLinkLayer.cpp"
|
"${TPUART_ROOT}/src/TPUart/DataLinkLayer.cpp"
|
||||||
"${TPUART_ROOT}/src/TPUart/Receiver.cpp"
|
"${TPUART_ROOT}/src/TPUart/Receiver.cpp"
|
||||||
@@ -31,6 +37,7 @@ idf_component_register(
|
|||||||
"src/esp_idf_platform.cpp"
|
"src/esp_idf_platform.cpp"
|
||||||
"src/ets_device_runtime.cpp"
|
"src/ets_device_runtime.cpp"
|
||||||
"src/ets_memory_loader.cpp"
|
"src/ets_memory_loader.cpp"
|
||||||
|
"src/security_storage.cpp"
|
||||||
"src/tpuart_uart_interface.cpp"
|
"src/tpuart_uart_interface.cpp"
|
||||||
${OPENKNX_SRCS}
|
${OPENKNX_SRCS}
|
||||||
${TPUART_SRCS}
|
${TPUART_SRCS}
|
||||||
@@ -42,11 +49,13 @@ idf_component_register(
|
|||||||
esp_driver_gpio
|
esp_driver_gpio
|
||||||
esp_driver_uart
|
esp_driver_uart
|
||||||
esp_netif
|
esp_netif
|
||||||
|
esp_system
|
||||||
esp_timer
|
esp_timer
|
||||||
esp_wifi
|
esp_wifi
|
||||||
freertos
|
freertos
|
||||||
log
|
log
|
||||||
lwip
|
lwip
|
||||||
|
mbedtls
|
||||||
nvs_flash
|
nvs_flash
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,6 +67,10 @@ target_compile_definitions(${COMPONENT_LIB} PUBLIC
|
|||||||
USE_CEMI_SERVER
|
USE_CEMI_SERVER
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
|
||||||
|
target_compile_definitions(${COMPONENT_LIB} PUBLIC USE_DATASECURE)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_compile_options(${COMPONENT_LIB} PRIVATE
|
target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||||
-Wno-unused-parameter
|
-Wno-unused-parameter
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,10 +14,15 @@ namespace gateway::openknx {
|
|||||||
|
|
||||||
class EspIdfPlatform : public Platform {
|
class EspIdfPlatform : public Platform {
|
||||||
public:
|
public:
|
||||||
|
using OutboundCemiFrameCallback = bool (*)(CemiFrame& frame, void* context);
|
||||||
|
|
||||||
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
|
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
|
||||||
const char* nvs_namespace = "openknx");
|
const char* nvs_namespace = "openknx");
|
||||||
~EspIdfPlatform() override;
|
~EspIdfPlatform() override;
|
||||||
|
|
||||||
|
void outboundCemiFrameCallback(OutboundCemiFrameCallback callback, void* context);
|
||||||
|
bool handleOutboundCemiFrame(CemiFrame& frame) override;
|
||||||
|
|
||||||
void networkInterface(esp_netif_t* netif);
|
void networkInterface(esp_netif_t* netif);
|
||||||
esp_netif_t* networkInterface() const;
|
esp_netif_t* networkInterface() const;
|
||||||
|
|
||||||
@@ -54,6 +59,8 @@ class EspIdfPlatform : public Platform {
|
|||||||
std::vector<uint8_t> eeprom_;
|
std::vector<uint8_t> eeprom_;
|
||||||
std::string nvs_namespace_;
|
std::string nvs_namespace_;
|
||||||
bool eeprom_loaded_{false};
|
bool eeprom_loaded_{false};
|
||||||
|
OutboundCemiFrameCallback outbound_cemi_frame_callback_{nullptr};
|
||||||
|
void* outbound_cemi_frame_context_{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gateway::openknx
|
} // namespace gateway::openknx
|
||||||
@@ -17,6 +17,8 @@ namespace gateway::openknx {
|
|||||||
class EtsDeviceRuntime {
|
class EtsDeviceRuntime {
|
||||||
public:
|
public:
|
||||||
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
using CemiFrameSender = std::function<void(const uint8_t* data, size_t len)>;
|
||||||
|
using GroupWriteHandler = std::function<void(uint16_t group_address, const uint8_t* data,
|
||||||
|
size_t len)>;
|
||||||
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
|
using FunctionPropertyHandler = std::function<bool(uint8_t object_index, uint8_t property_id,
|
||||||
const uint8_t* data, size_t len,
|
const uint8_t* data, size_t len,
|
||||||
std::vector<uint8_t>* response)>;
|
std::vector<uint8_t>* response)>;
|
||||||
@@ -31,12 +33,19 @@ class EtsDeviceRuntime {
|
|||||||
|
|
||||||
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
void setFunctionPropertyHandlers(FunctionPropertyHandler command_handler,
|
||||||
FunctionPropertyHandler state_handler);
|
FunctionPropertyHandler state_handler);
|
||||||
|
void setGroupWriteHandler(GroupWriteHandler handler);
|
||||||
|
|
||||||
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
|
bool handleTunnelFrame(const uint8_t* data, size_t len, CemiFrameSender sender);
|
||||||
|
bool handleBusFrame(const uint8_t* data, size_t len);
|
||||||
|
bool emitGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len,
|
||||||
|
CemiFrameSender sender);
|
||||||
void loop();
|
void loop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static bool HandleOutboundCemiFrame(CemiFrame& frame, void* context);
|
||||||
static void EmitTunnelFrame(CemiFrame& frame, void* context);
|
static void EmitTunnelFrame(CemiFrame& frame, void* context);
|
||||||
|
static void HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||||
|
uint8_t data_length, void* context);
|
||||||
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
static bool HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||||
uint8_t length, uint8_t* data,
|
uint8_t length, uint8_t* data,
|
||||||
uint8_t* result_data, uint8_t& result_length);
|
uint8_t* result_data, uint8_t& result_length);
|
||||||
@@ -48,11 +57,13 @@ class EtsDeviceRuntime {
|
|||||||
uint8_t property_id, uint8_t length, uint8_t* data,
|
uint8_t property_id, uint8_t length, uint8_t* data,
|
||||||
uint8_t* result_data, uint8_t& result_length);
|
uint8_t* result_data, uint8_t& result_length);
|
||||||
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
|
bool shouldConsumeTunnelFrame(CemiFrame& frame) const;
|
||||||
|
bool shouldConsumeBusFrame(CemiFrame& frame) const;
|
||||||
|
|
||||||
std::string nvs_namespace_;
|
std::string nvs_namespace_;
|
||||||
EspIdfPlatform platform_;
|
EspIdfPlatform platform_;
|
||||||
Bau07B0 device_;
|
Bau07B0 device_;
|
||||||
CemiFrameSender sender_;
|
CemiFrameSender sender_;
|
||||||
|
GroupWriteHandler group_write_handler_;
|
||||||
FunctionPropertyHandler command_handler_;
|
FunctionPropertyHandler command_handler_;
|
||||||
FunctionPropertyHandler state_handler_;
|
FunctionPropertyHandler state_handler_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "openknx_idf/ets_memory_loader.h"
|
#include "openknx_idf/ets_memory_loader.h"
|
||||||
#include "openknx_idf/ets_device_runtime.h"
|
#include "openknx_idf/ets_device_runtime.h"
|
||||||
#include "openknx_idf/esp_idf_platform.h"
|
#include "openknx_idf/esp_idf_platform.h"
|
||||||
|
#include "openknx_idf/security_storage.h"
|
||||||
#include "openknx_idf/tpuart_uart_interface.h"
|
#include "openknx_idf/tpuart_uart_interface.h"
|
||||||
|
|
||||||
#include "knx/bau07B0.h"
|
#include "knx/bau07B0.h"
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
|
||||||
|
struct FactoryFdskInfo {
|
||||||
|
bool available{false};
|
||||||
|
std::string serialNumber;
|
||||||
|
std::string label;
|
||||||
|
std::string qrCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool LoadFactoryFdsk(uint8_t* data, size_t len);
|
||||||
|
FactoryFdskInfo LoadFactoryFdskInfo();
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
@@ -51,6 +51,19 @@ EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface,
|
|||||||
|
|
||||||
EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); }
|
EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); }
|
||||||
|
|
||||||
|
void EspIdfPlatform::outboundCemiFrameCallback(OutboundCemiFrameCallback callback,
|
||||||
|
void* context) {
|
||||||
|
outbound_cemi_frame_callback_ = callback;
|
||||||
|
outbound_cemi_frame_context_ = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspIdfPlatform::handleOutboundCemiFrame(CemiFrame& frame) {
|
||||||
|
if (outbound_cemi_frame_callback_ == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return outbound_cemi_frame_callback_(frame, outbound_cemi_frame_context_);
|
||||||
|
}
|
||||||
|
|
||||||
void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
|
void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
|
||||||
|
|
||||||
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
|
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "openknx_idf/ets_device_runtime.h"
|
#include "openknx_idf/ets_device_runtime.h"
|
||||||
|
|
||||||
#include "knx/cemi_server.h"
|
#include "knx/cemi_server.h"
|
||||||
|
#include "knx/secure_application_layer.h"
|
||||||
#include "knx/property.h"
|
#include "knx/property.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -52,6 +53,7 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
|||||||
: nvs_namespace_(std::move(nvs_namespace)),
|
: nvs_namespace_(std::move(nvs_namespace)),
|
||||||
platform_(nullptr, nvs_namespace_.c_str()),
|
platform_(nullptr, nvs_namespace_.c_str()),
|
||||||
device_(platform_) {
|
device_(platform_) {
|
||||||
|
platform_.outboundCemiFrameCallback(&EtsDeviceRuntime::HandleOutboundCemiFrame, this);
|
||||||
ApplyReg1DaliIdentity(device_, platform_);
|
ApplyReg1DaliIdentity(device_, platform_);
|
||||||
if (IsUsableIndividualAddress(fallback_individual_address)) {
|
if (IsUsableIndividualAddress(fallback_individual_address)) {
|
||||||
device_.deviceObject().individualAddress(fallback_individual_address);
|
device_.deviceObject().individualAddress(fallback_individual_address);
|
||||||
@@ -67,9 +69,16 @@ EtsDeviceRuntime::EtsDeviceRuntime(std::string nvs_namespace,
|
|||||||
}
|
}
|
||||||
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
|
device_.functionPropertyCallback(&EtsDeviceRuntime::HandleFunctionPropertyCommand);
|
||||||
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
|
device_.functionPropertyStateCallback(&EtsDeviceRuntime::HandleFunctionPropertyState);
|
||||||
|
#ifdef USE_DATASECURE
|
||||||
|
device_.secureGroupWriteCallback(&EtsDeviceRuntime::HandleSecureGroupWrite, this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
EtsDeviceRuntime::~EtsDeviceRuntime() {
|
EtsDeviceRuntime::~EtsDeviceRuntime() {
|
||||||
|
platform_.outboundCemiFrameCallback(nullptr, nullptr);
|
||||||
|
#ifdef USE_DATASECURE
|
||||||
|
device_.secureGroupWriteCallback(nullptr, nullptr);
|
||||||
|
#endif
|
||||||
device_.functionPropertyCallback(nullptr);
|
device_.functionPropertyCallback(nullptr);
|
||||||
device_.functionPropertyStateCallback(nullptr);
|
device_.functionPropertyStateCallback(nullptr);
|
||||||
if (auto* server = device_.getCemiServer()) {
|
if (auto* server = device_.getCemiServer()) {
|
||||||
@@ -126,6 +135,10 @@ void EtsDeviceRuntime::setFunctionPropertyHandlers(FunctionPropertyHandler comma
|
|||||||
state_handler_ = std::move(state_handler);
|
state_handler_ = std::move(state_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EtsDeviceRuntime::setGroupWriteHandler(GroupWriteHandler handler) {
|
||||||
|
group_write_handler_ = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
||||||
CemiFrameSender sender) {
|
CemiFrameSender sender) {
|
||||||
auto* server = device_.getCemiServer();
|
auto* server = device_.getCemiServer();
|
||||||
@@ -146,8 +159,58 @@ bool EtsDeviceRuntime::handleTunnelFrame(const uint8_t* data, size_t len,
|
|||||||
return consumed;
|
return consumed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EtsDeviceRuntime::handleBusFrame(const uint8_t* data, size_t len) {
|
||||||
|
auto* data_link_layer = device_.getDataLinkLayer();
|
||||||
|
if (data_link_layer == nullptr || data == nullptr || len < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<uint8_t> frame_data(data, data + len);
|
||||||
|
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
||||||
|
const bool consumed = shouldConsumeBusFrame(frame);
|
||||||
|
if (!consumed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data_link_layer->externalFrameReceived(frame);
|
||||||
|
loop();
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EtsDeviceRuntime::emitGroupValue(uint16_t group_object_number, const uint8_t* data,
|
||||||
|
size_t len, CemiFrameSender sender) {
|
||||||
|
if (group_object_number == 0 || data == nullptr || !sender || !device_.configured()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& table = device_.groupObjectTable();
|
||||||
|
if (group_object_number > table.entryCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& group_object = table.get(group_object_number);
|
||||||
|
if (len != group_object.valueSize() || group_object.valueRef() == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (group_object.sizeInTelegram() == 0) {
|
||||||
|
group_object.valueRef()[0] = data[0] & 0x01;
|
||||||
|
} else {
|
||||||
|
std::copy_n(data, len, group_object.valueRef());
|
||||||
|
}
|
||||||
|
sender_ = std::move(sender);
|
||||||
|
group_object.objectWritten();
|
||||||
|
loop();
|
||||||
|
sender_ = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void EtsDeviceRuntime::loop() { device_.loop(); }
|
void EtsDeviceRuntime::loop() { device_.loop(); }
|
||||||
|
|
||||||
|
bool EtsDeviceRuntime::HandleOutboundCemiFrame(CemiFrame& frame, void* context) {
|
||||||
|
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||||
|
if (self == nullptr || !self->sender_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self->sender_(frame.data(), frame.dataLength());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
||||||
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||||
if (self == nullptr || !self->sender_) {
|
if (self == nullptr || !self->sender_) {
|
||||||
@@ -156,6 +219,15 @@ void EtsDeviceRuntime::EmitTunnelFrame(CemiFrame& frame, void* context) {
|
|||||||
self->sender_(frame.data(), frame.dataLength());
|
self->sender_(frame.data(), frame.dataLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EtsDeviceRuntime::HandleSecureGroupWrite(uint16_t group_address, const uint8_t* data,
|
||||||
|
uint8_t data_length, void* context) {
|
||||||
|
auto* self = static_cast<EtsDeviceRuntime*>(context);
|
||||||
|
if (self == nullptr || !self->group_write_handler_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self->group_write_handler_(group_address, data, data_length);
|
||||||
|
}
|
||||||
|
|
||||||
bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
bool EtsDeviceRuntime::HandleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
|
||||||
uint8_t length, uint8_t* data,
|
uint8_t length, uint8_t* data,
|
||||||
uint8_t* result_data,
|
uint8_t* result_data,
|
||||||
@@ -217,11 +289,27 @@ bool EtsDeviceRuntime::shouldConsumeTunnelFrame(CemiFrame& frame) const {
|
|||||||
case M_FuncPropStateRead_req:
|
case M_FuncPropStateRead_req:
|
||||||
return true;
|
return true;
|
||||||
case L_data_req:
|
case L_data_req:
|
||||||
return frame.addressType() == IndividualAddress &&
|
if (frame.addressType() == IndividualAddress &&
|
||||||
frame.destinationAddress() == individualAddress();
|
frame.destinationAddress() == individualAddress()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#ifdef USE_DATASECURE
|
||||||
|
return frame.addressType() == GroupAddress && frame.apdu().type() == SecureService;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EtsDeviceRuntime::shouldConsumeBusFrame(CemiFrame& frame) const {
|
||||||
|
#ifdef USE_DATASECURE
|
||||||
|
return frame.messageCode() == L_data_ind && frame.addressType() == GroupAddress &&
|
||||||
|
frame.apdu().type() == SecureService;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace gateway::openknx
|
} // namespace gateway::openknx
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
#include "openknx_idf/security_storage.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_mac.h"
|
||||||
|
#include "esp_random.h"
|
||||||
|
#include "nvs.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kTag = "openknx_sec";
|
||||||
|
constexpr const char* kNamespace = "knx_sec";
|
||||||
|
constexpr const char* kFactoryFdskKey = "factory_fdsk";
|
||||||
|
constexpr size_t kFdskSize = 16;
|
||||||
|
constexpr size_t kSerialSize = 6;
|
||||||
|
constexpr size_t kFdskQrSize = 36;
|
||||||
|
constexpr uint8_t kCrc4Tab[16] = {
|
||||||
|
0x0, 0x3, 0x6, 0x5, 0xc, 0xf, 0xa, 0x9,
|
||||||
|
0xb, 0x8, 0xd, 0xe, 0x7, 0x4, 0x1, 0x2,
|
||||||
|
};
|
||||||
|
constexpr char kBase32Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
constexpr char kHexAlphabet[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
|
bool ensureNvsReady() {
|
||||||
|
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 plausibleKey(const uint8_t* data) {
|
||||||
|
const bool all_zero = std::all_of(data, data + kFdskSize, [](uint8_t value) {
|
||||||
|
return value == 0x00;
|
||||||
|
});
|
||||||
|
const bool all_ff = std::all_of(data, data + kFdskSize, [](uint8_t value) {
|
||||||
|
return value == 0xff;
|
||||||
|
});
|
||||||
|
return !all_zero && !all_ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
void generateKey(uint8_t* data) {
|
||||||
|
do {
|
||||||
|
esp_fill_random(data, kFdskSize);
|
||||||
|
} while (!plausibleKey(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t crc4Array(const uint8_t* data, size_t len) {
|
||||||
|
uint8_t crc = 0;
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
crc = kCrc4Tab[crc ^ (data[i] >> 4)];
|
||||||
|
crc = kCrc4Tab[crc ^ (data[i] & 0x0f)];
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toBase32NoPadding(const uint8_t* data, size_t len) {
|
||||||
|
std::string result;
|
||||||
|
result.reserve(((len * 8) + 4) / 5);
|
||||||
|
|
||||||
|
uint32_t buffer = 0;
|
||||||
|
int bits_left = 0;
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
buffer = (buffer << 8) | data[i];
|
||||||
|
bits_left += 8;
|
||||||
|
while (bits_left >= 5) {
|
||||||
|
const uint8_t index = static_cast<uint8_t>((buffer >> (bits_left - 5)) & 0x1f);
|
||||||
|
result.push_back(kBase32Alphabet[index]);
|
||||||
|
bits_left -= 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bits_left > 0) {
|
||||||
|
const uint8_t index = static_cast<uint8_t>((buffer << (5 - bits_left)) & 0x1f);
|
||||||
|
result.push_back(kBase32Alphabet[index]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string toHex(const uint8_t* data, size_t len) {
|
||||||
|
std::string result;
|
||||||
|
result.reserve(len * 2);
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
result.push_back(kHexAlphabet[(data[i] >> 4) & 0x0f]);
|
||||||
|
result.push_back(kHexAlphabet[data[i] & 0x0f]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string generateFdskQrCode(const uint8_t* serial, const uint8_t* key) {
|
||||||
|
std::array<uint8_t, kSerialSize + kFdskSize + 1> buffer{};
|
||||||
|
std::copy(serial, serial + kSerialSize, buffer.begin());
|
||||||
|
std::copy(key, key + kFdskSize, buffer.begin() + kSerialSize);
|
||||||
|
buffer[kSerialSize + kFdskSize] = static_cast<uint8_t>((crc4Array(buffer.data(), buffer.size() - 1) << 4) & 0xff);
|
||||||
|
|
||||||
|
std::string encoded = toBase32NoPadding(buffer.data(), buffer.size());
|
||||||
|
if (encoded.size() > kFdskQrSize) {
|
||||||
|
encoded.resize(kFdskQrSize);
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string formatFdskLabel(const std::string& qr_code) {
|
||||||
|
std::string label;
|
||||||
|
label.reserve(qr_code.size() + (qr_code.size() / 6));
|
||||||
|
for (size_t i = 0; i < qr_code.size(); ++i) {
|
||||||
|
if (i != 0 && (i % 6) == 0) {
|
||||||
|
label.push_back('-');
|
||||||
|
}
|
||||||
|
label.push_back(qr_code[i]);
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
|
||||||
|
bool LoadFactoryFdsk(uint8_t* data, size_t len) {
|
||||||
|
if (data == nullptr || len < kFdskSize || !ensureNvsReady()) {
|
||||||
|
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));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.available = true;
|
||||||
|
info.serialNumber = toHex(serial.data(), serial.size());
|
||||||
|
info.qrCode = generateFdskQrCode(serial.data(), key.data());
|
||||||
|
info.label = formatFdskLabel(info.qrCode);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
|
|
||||||
|
extern "C" bool knx_platform_get_fdsk(uint8_t* data, size_t len) {
|
||||||
|
return gateway::openknx::LoadFactoryFdsk(data, len);
|
||||||
|
}
|
||||||
+1
-1
Submodule knx updated: 339d8472e7...1549366447
Reference in New Issue
Block a user