Add OpenKNX IDF component with TPUart integration
- Created CMakeLists.txt for the OpenKNX IDF component, ensuring dependencies on OpenKNX and TPUart submodules. - Implemented Arduino compatibility header for basic functions like millis, delay, pinMode, and digitalRead. - Developed EspIdfPlatform class for network interface management and multicast communication. - Added EtsMemoryLoader for loading ETS memory snapshots and managing associations. - Introduced TpuartUartInterface for UART communication with methods for reading, writing, and managing callbacks. - Implemented arduino_compat.cpp for Arduino-like functionality on ESP-IDF. - Created source files for platform and memory loader implementations. - Updated submodules for knx, knx_dali_gw, and tpuart. Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
@@ -5,3 +5,11 @@
|
|||||||
path = knx
|
path = knx
|
||||||
url = https://git.tonycloud.org/knx/knx.git
|
url = https://git.tonycloud.org/knx/knx.git
|
||||||
branch = v1
|
branch = v1
|
||||||
|
[submodule "knx_dali_gw"]
|
||||||
|
path = knx_dali_gw
|
||||||
|
url = https://git.tonycloud.org/knx/GW-REG1-Dali.git
|
||||||
|
branch = v1
|
||||||
|
[submodule "tpuart"]
|
||||||
|
path = tpuart
|
||||||
|
url = https://git.tonycloud.org/knx/tpuart.git
|
||||||
|
branch = main
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ This folder hosts the native ESP-IDF C++ rewrite of the Lua DALI gateway.
|
|||||||
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
|
- `dali_domain/`: native DALI domain facade over `dali_cpp` and raw frame sinks.
|
||||||
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
|
- `gateway_cache/`: DALI scene/group/settings/runtime cache used by controller reconciliation and protocol bridges.
|
||||||
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
|
- `gateway_bridge/`: per-channel bridge provisioning, command execution, protocol startup, and HTTP bridge actions.
|
||||||
|
- `openknx_idf/`: ESP-IDF port layer for the OpenKNX `gateway/knx` and `gateway/tpuart` submodules, including NVS-backed OpenKNX memory, UDP multicast/unicast plumbing, and a native TP-UART interface without the Arduino framework.
|
||||||
- `gateway_modbus/`: gateway-owned Modbus TCP/RTU/ASCII config, generated DALI point tables, and provisioned Modbus model override dispatch.
|
- `gateway_modbus/`: gateway-owned Modbus TCP/RTU/ASCII config, generated DALI point tables, and provisioned Modbus model override dispatch.
|
||||||
- `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack, including the gateway-owned BACnet bridge model adapter.
|
- `gateway_bacnet/`: BACnet/IP server adapter backed by bacnet-stack, including the gateway-owned BACnet bridge model adapter.
|
||||||
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
|
- `gateway_ble/`: NimBLE GATT bridge for BLE transport parity on `FFF1`/`FFF2`/`FFF3`, including raw DALI notifications.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ set(GATEWAY_BRIDGE_REQUIRES
|
|||||||
log
|
log
|
||||||
lwip
|
lwip
|
||||||
nvs_flash
|
nvs_flash
|
||||||
|
openknx_idf
|
||||||
)
|
)
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "gateway_knx.hpp"
|
#include "gateway_knx.hpp"
|
||||||
#include "gateway_modbus.hpp"
|
#include "gateway_modbus.hpp"
|
||||||
#include "gateway_provisioning.hpp"
|
#include "gateway_provisioning.hpp"
|
||||||
|
#include "openknx_idf/ets_memory_loader.h"
|
||||||
|
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
#include "driver/uart.h"
|
#include "driver/uart.h"
|
||||||
@@ -1206,6 +1207,10 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
return "dali_cloud_" + std::to_string(channel.gateway_id);
|
return "dali_cloud_" + std::to_string(channel.gateway_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string openKnxNamespace() const {
|
||||||
|
return "openknx_" + std::to_string(channel.gateway_id);
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t start() {
|
esp_err_t start() {
|
||||||
comm = std::make_unique<DaliComm>(
|
comm = std::make_unique<DaliComm>(
|
||||||
[this](const uint8_t* data, size_t len) {
|
[this](const uint8_t* data, size_t len) {
|
||||||
@@ -1254,6 +1259,8 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
engine->upsertModel(model);
|
engine->upsertModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refreshOpenKnxEtsAssociationsLocked();
|
||||||
|
|
||||||
modbus = std::make_unique<GatewayModbusBridge>(*engine);
|
modbus = std::make_unique<GatewayModbusBridge>(*engine);
|
||||||
if (modbus_config.has_value()) {
|
if (modbus_config.has_value()) {
|
||||||
modbus->setConfig(modbus_config.value());
|
modbus->setConfig(modbus_config.value());
|
||||||
@@ -1292,6 +1299,31 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
diagnostic_snapshot_cache.clear();
|
diagnostic_snapshot_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void refreshOpenKnxEtsAssociationsLocked() {
|
||||||
|
if (!service_config.knx_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto active_config = activeKnxConfigLocked();
|
||||||
|
if (!active_config.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto snapshot = openknx::LoadEtsMemorySnapshot(openKnxNamespace());
|
||||||
|
if (snapshot.associations.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GatewayKnxConfig updated = active_config.value();
|
||||||
|
updated.ets_associations.clear();
|
||||||
|
updated.ets_associations.reserve(snapshot.associations.size());
|
||||||
|
for (const auto& association : snapshot.associations) {
|
||||||
|
updated.ets_associations.push_back(GatewayKnxEtsAssociation{
|
||||||
|
association.group_address, association.group_object_number});
|
||||||
|
}
|
||||||
|
knx_config = std::move(updated);
|
||||||
|
ESP_LOGI(kTag, "gateway=%u loaded %u OpenKNX ETS associations from NVS namespace %s",
|
||||||
|
channel.gateway_id, static_cast<unsigned>(snapshot.associations.size()),
|
||||||
|
openKnxNamespace().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
|
std::optional<DaliDomainSnapshot> diagnosticSnapshotLocked(int short_address,
|
||||||
std::string_view kind) {
|
std::string_view kind) {
|
||||||
if (!ValidShortAddress(short_address) || kind.empty()) {
|
if (!ValidShortAddress(short_address) || kind.empty()) {
|
||||||
@@ -2004,13 +2036,15 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
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());
|
||||||
if (effective_knx.has_value()) {
|
if (effective_knx.has_value()) {
|
||||||
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled",
|
cJSON_AddBoolToObject(knx_json, "daliRouterEnabled", effective_knx->dali_router_enabled);
|
||||||
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);
|
|
||||||
cJSON_AddBoolToObject(knx_json, "tunnelEnabled", effective_knx->tunnel_enabled);
|
cJSON_AddBoolToObject(knx_json, "tunnelEnabled", effective_knx->tunnel_enabled);
|
||||||
cJSON_AddBoolToObject(knx_json, "multicastEnabled",
|
cJSON_AddBoolToObject(knx_json, "multicastEnabled", effective_knx->multicast_enabled);
|
||||||
effective_knx->multicast_enabled);
|
cJSON_AddBoolToObject(knx_json, "etsDatabaseEnabled", effective_knx->ets_database_enabled);
|
||||||
|
cJSON_AddNumberToObject(knx_json, "etsBindingCount",
|
||||||
|
knx == nullptr ? 0 : knx->etsBindingCount());
|
||||||
|
cJSON_AddStringToObject(knx_json, "mappingMode",
|
||||||
|
GatewayKnxMappingModeToString(effective_knx->mapping_mode));
|
||||||
cJSON_AddNumberToObject(knx_json, "mainGroup", effective_knx->main_group);
|
cJSON_AddNumberToObject(knx_json, "mainGroup", effective_knx->main_group);
|
||||||
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
|
cJSON_AddNumberToObject(knx_json, "udpPort", effective_knx->udp_port);
|
||||||
cJSON_AddStringToObject(knx_json, "multicastAddress",
|
cJSON_AddStringToObject(knx_json, "multicastAddress",
|
||||||
@@ -2262,6 +2296,17 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
cJSON_AddNumberToObject(item, "mainGroup", binding.main_group);
|
cJSON_AddNumberToObject(item, "mainGroup", binding.main_group);
|
||||||
cJSON_AddNumberToObject(item, "middleGroup", binding.middle_group);
|
cJSON_AddNumberToObject(item, "middleGroup", binding.middle_group);
|
||||||
cJSON_AddNumberToObject(item, "subGroup", binding.sub_group);
|
cJSON_AddNumberToObject(item, "subGroup", binding.sub_group);
|
||||||
|
cJSON_AddStringToObject(item, "mappingMode",
|
||||||
|
GatewayKnxMappingModeToString(binding.mapping_mode));
|
||||||
|
if (binding.group_object_number >= 0) {
|
||||||
|
cJSON_AddNumberToObject(item, "objectNumber", binding.group_object_number);
|
||||||
|
}
|
||||||
|
if (binding.channel_index >= 0) {
|
||||||
|
cJSON_AddNumberToObject(item, "channelIndex", binding.channel_index);
|
||||||
|
}
|
||||||
|
if (!binding.object_role.empty()) {
|
||||||
|
cJSON_AddStringToObject(item, "objectRole", binding.object_role.c_str());
|
||||||
|
}
|
||||||
cJSON_AddStringToObject(item, "name", binding.name.c_str());
|
cJSON_AddStringToObject(item, "name", binding.name.c_str());
|
||||||
cJSON_AddStringToObject(item, "datapointType", binding.datapoint_type.c_str());
|
cJSON_AddStringToObject(item, "datapointType", binding.datapoint_type.c_str());
|
||||||
cJSON_AddStringToObject(item, "dataType",
|
cJSON_AddStringToObject(item, "dataType",
|
||||||
@@ -2894,22 +2939,28 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
std::set<uint16_t>* used_ports = nullptr,
|
std::set<uint16_t>* used_ports = nullptr,
|
||||||
std::set<int>* used_uarts = nullptr) {
|
std::set<int>* used_uarts = nullptr) {
|
||||||
LockGuard guard(lock);
|
LockGuard guard(lock);
|
||||||
|
GatewayKnxConfig merged_config = config;
|
||||||
|
const auto previous_knx = activeKnxConfigLocked();
|
||||||
|
if (merged_config.ets_associations.empty() && previous_knx.has_value() &&
|
||||||
|
!previous_knx->ets_associations.empty()) {
|
||||||
|
merged_config.ets_associations = previous_knx->ets_associations;
|
||||||
|
}
|
||||||
std::string validation_error;
|
std::string validation_error;
|
||||||
const esp_err_t validation_err = validateKnxConfigLocked(
|
const esp_err_t validation_err = validateKnxConfigLocked(
|
||||||
config, activeModbusConfigLocked(), &validation_error);
|
merged_config, activeModbusConfigLocked(), &validation_error);
|
||||||
if (validation_err != ESP_OK) {
|
if (validation_err != ESP_OK) {
|
||||||
knx_last_error = validation_error;
|
knx_last_error = validation_error;
|
||||||
return validation_err;
|
return validation_err;
|
||||||
}
|
}
|
||||||
const bool restart_router = knx_started || (knx_router != nullptr && knx_router->started());
|
const bool restart_router = knx_started || (knx_router != nullptr && knx_router->started());
|
||||||
if (restart_router && config.ip_router_enabled && used_ports != nullptr &&
|
if (restart_router && merged_config.ip_router_enabled && used_ports != nullptr &&
|
||||||
used_ports->find(config.udp_port) != used_ports->end()) {
|
used_ports->find(merged_config.udp_port) != used_ports->end()) {
|
||||||
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(config.udp_port);
|
knx_last_error = "duplicate KNXnet/IP UDP port " + std::to_string(merged_config.udp_port);
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
if (restart_router && config.ip_router_enabled && used_uarts != nullptr &&
|
if (restart_router && merged_config.ip_router_enabled && used_uarts != nullptr &&
|
||||||
used_uarts->find(config.tp_uart.uart_port) != used_uarts->end()) {
|
used_uarts->find(merged_config.tp_uart.uart_port) != used_uarts->end()) {
|
||||||
knx_last_error = "KNX TP-UART UART" + std::to_string(config.tp_uart.uart_port) +
|
knx_last_error = "KNX TP-UART UART" + std::to_string(merged_config.tp_uart.uart_port) +
|
||||||
" is already used by another runtime";
|
" is already used by another runtime";
|
||||||
return ESP_ERR_INVALID_STATE;
|
return ESP_ERR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
@@ -2920,18 +2971,18 @@ struct GatewayBridgeService::ChannelRuntime {
|
|||||||
BridgeProvisioningStore store(bridgeNamespace());
|
BridgeProvisioningStore store(bridgeNamespace());
|
||||||
const esp_err_t err = store.saveObject(
|
const esp_err_t err = store.saveObject(
|
||||||
kBridgeConfigKey,
|
kBridgeConfigKey,
|
||||||
GatewayBridgeStoredConfigToValue(bridge_config, modbus_config, config,
|
GatewayBridgeStoredConfigToValue(bridge_config, modbus_config, merged_config,
|
||||||
bacnet_server_config));
|
bacnet_server_config));
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
knx_config = config;
|
knx_config = merged_config;
|
||||||
bridge_config_loaded = true;
|
bridge_config_loaded = true;
|
||||||
if (knx != nullptr) {
|
if (knx != nullptr) {
|
||||||
knx->setConfig(config);
|
knx->setConfig(merged_config);
|
||||||
}
|
}
|
||||||
if (knx_router != nullptr) {
|
if (knx_router != nullptr) {
|
||||||
knx_router->setConfig(config);
|
knx_router->setConfig(merged_config);
|
||||||
}
|
}
|
||||||
if (restart_router) {
|
if (restart_router) {
|
||||||
return startKnx(used_ports, used_uarts);
|
return startKnx(used_ports, used_uarts);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS "src/gateway_knx.cpp"
|
SRCS "src/gateway_knx.cpp"
|
||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
REQUIRES dali_cpp esp_driver_uart freertos log lwip
|
REQUIRES dali_cpp esp_driver_uart freertos log lwip openknx_idf
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -32,15 +33,30 @@ struct GatewayKnxTpUartConfig {
|
|||||||
uint32_t read_timeout_ms{20};
|
uint32_t read_timeout_ms{20};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class GatewayKnxMappingMode : uint8_t {
|
||||||
|
kFormula = 0,
|
||||||
|
kGwReg1Direct = 1,
|
||||||
|
kManual = 2,
|
||||||
|
kEtsDatabase = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GatewayKnxEtsAssociation {
|
||||||
|
uint16_t group_address{0};
|
||||||
|
uint16_t group_object_number{0};
|
||||||
|
};
|
||||||
|
|
||||||
struct GatewayKnxConfig {
|
struct GatewayKnxConfig {
|
||||||
bool dali_router_enabled{true};
|
bool dali_router_enabled{true};
|
||||||
bool ip_router_enabled{false};
|
bool ip_router_enabled{false};
|
||||||
bool tunnel_enabled{true};
|
bool tunnel_enabled{true};
|
||||||
bool multicast_enabled{true};
|
bool multicast_enabled{true};
|
||||||
|
bool ets_database_enabled{true};
|
||||||
|
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
|
||||||
uint8_t main_group{0};
|
uint8_t main_group{0};
|
||||||
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
||||||
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
||||||
uint16_t individual_address{0x1101};
|
uint16_t individual_address{0x1101};
|
||||||
|
std::vector<GatewayKnxEtsAssociation> ets_associations;
|
||||||
GatewayKnxTpUartConfig tp_uart;
|
GatewayKnxTpUartConfig tp_uart;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,8 +85,12 @@ struct GatewayKnxDaliBinding {
|
|||||||
uint8_t main_group{0};
|
uint8_t main_group{0};
|
||||||
uint8_t middle_group{0};
|
uint8_t middle_group{0};
|
||||||
uint8_t sub_group{0};
|
uint8_t sub_group{0};
|
||||||
|
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
|
||||||
|
int group_object_number{-1};
|
||||||
|
int channel_index{-1};
|
||||||
std::string address;
|
std::string address;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
std::string object_role;
|
||||||
std::string datapoint_type;
|
std::string datapoint_type;
|
||||||
GatewayKnxDaliDataType data_type{GatewayKnxDaliDataType::kUnknown};
|
GatewayKnxDaliDataType data_type{GatewayKnxDaliDataType::kUnknown};
|
||||||
GatewayKnxDaliTarget target;
|
GatewayKnxDaliTarget target;
|
||||||
@@ -79,6 +99,8 @@ struct GatewayKnxDaliBinding {
|
|||||||
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
||||||
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
||||||
|
|
||||||
|
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode);
|
||||||
|
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value);
|
||||||
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type);
|
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type);
|
||||||
const char* GatewayKnxTargetKindToString(GatewayKnxDaliTargetKind kind);
|
const char* GatewayKnxTargetKindToString(GatewayKnxDaliTargetKind kind);
|
||||||
std::optional<GatewayKnxDaliDataType> GatewayKnxDaliDataTypeForMiddleGroup(
|
std::optional<GatewayKnxDaliDataType> GatewayKnxDaliDataTypeForMiddleGroup(
|
||||||
@@ -94,6 +116,7 @@ class GatewayKnxBridge {
|
|||||||
|
|
||||||
void setConfig(const GatewayKnxConfig& config);
|
void setConfig(const GatewayKnxConfig& config);
|
||||||
const GatewayKnxConfig& config() const;
|
const GatewayKnxConfig& config() const;
|
||||||
|
size_t etsBindingCount() const;
|
||||||
|
|
||||||
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
|
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
|
||||||
DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len);
|
DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len);
|
||||||
@@ -105,9 +128,14 @@ class GatewayKnxBridge {
|
|||||||
GatewayKnxDaliDataType data_type,
|
GatewayKnxDaliDataType data_type,
|
||||||
GatewayKnxDaliTarget target,
|
GatewayKnxDaliTarget target,
|
||||||
const uint8_t* data, size_t len);
|
const uint8_t* data, size_t len);
|
||||||
|
DaliBridgeResult executeEtsBindings(uint16_t group_address,
|
||||||
|
const std::vector<GatewayKnxDaliBinding>& bindings,
|
||||||
|
const uint8_t* data, size_t len);
|
||||||
|
void rebuildEtsBindings();
|
||||||
|
|
||||||
DaliBridgeEngine& engine_;
|
DaliBridgeEngine& engine_;
|
||||||
GatewayKnxConfig config_;
|
GatewayKnxConfig config_;
|
||||||
|
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GatewayKnxTpIpRouter {
|
class GatewayKnxTpIpRouter {
|
||||||
|
|||||||
@@ -7,9 +7,13 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cctype>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
|
#include <set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -49,6 +53,15 @@ constexpr uint8_t kTpUartLDataConfirmNegative = 0x0b;
|
|||||||
constexpr uint8_t kTpUartLDataStart = 0x80;
|
constexpr uint8_t kTpUartLDataStart = 0x80;
|
||||||
constexpr uint8_t kTpUartLDataEnd = 0x40;
|
constexpr uint8_t kTpUartLDataEnd = 0x40;
|
||||||
constexpr uint8_t kTpUartBusy = 0xc0;
|
constexpr uint8_t kTpUartBusy = 0xc0;
|
||||||
|
constexpr uint16_t kGwReg1AdrKoOffset = 12;
|
||||||
|
constexpr uint16_t kGwReg1AdrKoBlockSize = 18;
|
||||||
|
constexpr uint16_t kGwReg1GrpKoOffset = 1164;
|
||||||
|
constexpr uint16_t kGwReg1GrpKoBlockSize = 17;
|
||||||
|
constexpr uint16_t kGwReg1AppKoBroadcastSwitch = 1;
|
||||||
|
constexpr uint16_t kGwReg1AppKoBroadcastDimm = 2;
|
||||||
|
constexpr uint8_t kGwReg1KoSwitch = 0;
|
||||||
|
constexpr uint8_t kGwReg1KoDimmAbsolute = 3;
|
||||||
|
constexpr uint8_t kGwReg1KoColor = 6;
|
||||||
|
|
||||||
struct DecodedGroupWrite {
|
struct DecodedGroupWrite {
|
||||||
uint16_t group_address{0};
|
uint16_t group_address{0};
|
||||||
@@ -94,6 +107,108 @@ std::optional<std::string> ObjectStringAny(const DaliValue::Object& object,
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DaliValue* ObjectValueAny(const DaliValue::Object& object,
|
||||||
|
std::initializer_list<const char*> keys) {
|
||||||
|
for (const char* key : keys) {
|
||||||
|
if (const auto* value = getObjectValue(object, key)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string NormalizeModeString(std::string value) {
|
||||||
|
value.erase(std::remove_if(value.begin(), value.end(), [](unsigned char ch) {
|
||||||
|
return ch == '_' || ch == '-' || std::isspace(ch) != 0;
|
||||||
|
}),
|
||||||
|
value.end());
|
||||||
|
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
|
||||||
|
return static_cast<char>(std::tolower(ch));
|
||||||
|
});
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uint16_t> ParseGroupAddressString(const std::string& value) {
|
||||||
|
int parts[3] = {-1, -1, -1};
|
||||||
|
size_t start = 0;
|
||||||
|
for (int index = 0; index < 3; ++index) {
|
||||||
|
const size_t slash = value.find('/', start);
|
||||||
|
const bool last = index == 2;
|
||||||
|
if ((slash == std::string::npos) != last) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const std::string token = value.substr(start, last ? std::string::npos : slash - start);
|
||||||
|
if (token.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
char* end = nullptr;
|
||||||
|
errno = 0;
|
||||||
|
const long parsed = std::strtol(token.c_str(), &end, 10);
|
||||||
|
if (errno != 0 || end == token.c_str() || *end != '\0') {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
parts[index] = static_cast<int>(parsed);
|
||||||
|
start = slash + 1;
|
||||||
|
}
|
||||||
|
if (parts[0] < 0 || parts[0] > 31 || parts[1] < 0 || parts[1] > 7 || parts[2] < 0 ||
|
||||||
|
parts[2] > 255) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return static_cast<uint16_t>(((parts[0] & 0x1f) << 11) | ((parts[1] & 0x07) << 8) |
|
||||||
|
(parts[2] & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uint16_t> ObjectGroupAddressAny(const DaliValue::Object& object,
|
||||||
|
std::initializer_list<const char*> keys) {
|
||||||
|
for (const char* key : keys) {
|
||||||
|
const auto* value = getObjectValue(object, key);
|
||||||
|
if (value == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const auto raw = value->asInt()) {
|
||||||
|
if (raw.value() >= 0 && raw.value() <= 0xffff) {
|
||||||
|
return static_cast<uint16_t>(raw.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (const auto raw = value->asString()) {
|
||||||
|
if (const auto parsed = ParseGroupAddressString(raw.value())) {
|
||||||
|
return parsed.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<GatewayKnxEtsAssociation> ParseEtsAssociations(const DaliValue::Object& object) {
|
||||||
|
std::vector<GatewayKnxEtsAssociation> associations;
|
||||||
|
const auto* raw_associations = ObjectValueAny(
|
||||||
|
object, {"etsAssociations", "ets_associations", "etsBindings", "ets_bindings",
|
||||||
|
"associationTable", "association_table"});
|
||||||
|
const auto* array = raw_associations == nullptr ? nullptr : raw_associations->asArray();
|
||||||
|
if (array == nullptr) {
|
||||||
|
return associations;
|
||||||
|
}
|
||||||
|
associations.reserve(array->size());
|
||||||
|
for (const auto& item : *array) {
|
||||||
|
const auto* entry = item.asObject();
|
||||||
|
if (entry == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto group_address = ObjectGroupAddressAny(
|
||||||
|
*entry, {"groupAddress", "group_address", "address", "rawAddress", "raw_address"});
|
||||||
|
const auto object_number = ObjectIntAny(
|
||||||
|
*entry, {"objectNumber", "object_number", "groupObjectNumber", "group_object_number",
|
||||||
|
"ko", "asap"});
|
||||||
|
if (!group_address.has_value() || !object_number.has_value() || object_number.value() < 0 ||
|
||||||
|
object_number.value() > kGwReg1GrpKoOffset + (kGwReg1GrpKoBlockSize * 16)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
associations.push_back(GatewayKnxEtsAssociation{
|
||||||
|
group_address.value(), static_cast<uint16_t>(object_number.value())});
|
||||||
|
}
|
||||||
|
return associations;
|
||||||
|
}
|
||||||
|
|
||||||
std::string TargetName(const GatewayKnxDaliTarget& target) {
|
std::string TargetName(const GatewayKnxDaliTarget& target) {
|
||||||
switch (target.kind) {
|
switch (target.kind) {
|
||||||
case GatewayKnxDaliTargetKind::kBroadcast:
|
case GatewayKnxDaliTargetKind::kBroadcast:
|
||||||
@@ -386,6 +501,12 @@ std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value
|
|||||||
.value_or(config.tunnel_enabled);
|
.value_or(config.tunnel_enabled);
|
||||||
config.multicast_enabled = ObjectBoolAny(object, {"multicastEnabled", "multicast_enabled"})
|
config.multicast_enabled = ObjectBoolAny(object, {"multicastEnabled", "multicast_enabled"})
|
||||||
.value_or(config.multicast_enabled);
|
.value_or(config.multicast_enabled);
|
||||||
|
if (const auto mode = ObjectStringAny(object, {"mappingMode", "mapping_mode"})) {
|
||||||
|
config.mapping_mode = GatewayKnxMappingModeFromString(mode.value());
|
||||||
|
}
|
||||||
|
config.ets_database_enabled = ObjectBoolAny(object, {"etsDatabaseEnabled", "ets_database_enabled"})
|
||||||
|
.value_or(config.ets_database_enabled);
|
||||||
|
config.ets_associations = ParseEtsAssociations(object);
|
||||||
config.main_group = static_cast<uint8_t>(
|
config.main_group = static_cast<uint8_t>(
|
||||||
std::clamp(ObjectIntAny(object, {"mainGroup", "main_group"}).value_or(config.main_group),
|
std::clamp(ObjectIntAny(object, {"mainGroup", "main_group"}).value_or(config.main_group),
|
||||||
0, 31));
|
0, 31));
|
||||||
@@ -431,6 +552,8 @@ DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config) {
|
|||||||
out["ipRouterEnabled"] = config.ip_router_enabled;
|
out["ipRouterEnabled"] = config.ip_router_enabled;
|
||||||
out["tunnelEnabled"] = config.tunnel_enabled;
|
out["tunnelEnabled"] = config.tunnel_enabled;
|
||||||
out["multicastEnabled"] = config.multicast_enabled;
|
out["multicastEnabled"] = config.multicast_enabled;
|
||||||
|
out["etsDatabaseEnabled"] = config.ets_database_enabled;
|
||||||
|
out["mappingMode"] = GatewayKnxMappingModeToString(config.mapping_mode);
|
||||||
out["mainGroup"] = static_cast<int>(config.main_group);
|
out["mainGroup"] = static_cast<int>(config.main_group);
|
||||||
out["udpPort"] = static_cast<int>(config.udp_port);
|
out["udpPort"] = static_cast<int>(config.udp_port);
|
||||||
out["multicastAddress"] = config.multicast_address;
|
out["multicastAddress"] = config.multicast_address;
|
||||||
@@ -444,9 +567,47 @@ DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config) {
|
|||||||
serial["txBufferSize"] = static_cast<int>(config.tp_uart.tx_buffer_size);
|
serial["txBufferSize"] = static_cast<int>(config.tp_uart.tx_buffer_size);
|
||||||
serial["readTimeoutMs"] = static_cast<int>(config.tp_uart.read_timeout_ms);
|
serial["readTimeoutMs"] = static_cast<int>(config.tp_uart.read_timeout_ms);
|
||||||
out["tpUart"] = std::move(serial);
|
out["tpUart"] = std::move(serial);
|
||||||
|
DaliValue::Array ets_associations;
|
||||||
|
ets_associations.reserve(config.ets_associations.size());
|
||||||
|
for (const auto& association : config.ets_associations) {
|
||||||
|
DaliValue::Object entry;
|
||||||
|
entry["groupAddress"] = static_cast<int>(association.group_address);
|
||||||
|
entry["groupObjectNumber"] = static_cast<int>(association.group_object_number);
|
||||||
|
ets_associations.emplace_back(std::move(entry));
|
||||||
|
}
|
||||||
|
out["etsAssociations"] = std::move(ets_associations);
|
||||||
return DaliValue(std::move(out));
|
return DaliValue(std::move(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case GatewayKnxMappingMode::kEtsDatabase:
|
||||||
|
return "ets_database";
|
||||||
|
case GatewayKnxMappingMode::kGwReg1Direct:
|
||||||
|
return "gw_reg1_direct";
|
||||||
|
case GatewayKnxMappingMode::kManual:
|
||||||
|
return "manual";
|
||||||
|
case GatewayKnxMappingMode::kFormula:
|
||||||
|
default:
|
||||||
|
return "formula";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value) {
|
||||||
|
const std::string normalized = NormalizeModeString(value);
|
||||||
|
if (normalized == "gwreg1direct" || normalized == "gwreg1" ||
|
||||||
|
normalized == "gwreg1channel" || normalized == "channelindex") {
|
||||||
|
return GatewayKnxMappingMode::kGwReg1Direct;
|
||||||
|
}
|
||||||
|
if (normalized == "manual" || normalized == "database" || normalized == "db") {
|
||||||
|
return GatewayKnxMappingMode::kManual;
|
||||||
|
}
|
||||||
|
if (normalized == "etsdatabase" || normalized == "ets" || normalized == "openknx") {
|
||||||
|
return GatewayKnxMappingMode::kEtsDatabase;
|
||||||
|
}
|
||||||
|
return GatewayKnxMappingMode::kFormula;
|
||||||
|
}
|
||||||
|
|
||||||
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type) {
|
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type) {
|
||||||
switch (data_type) {
|
switch (data_type) {
|
||||||
case GatewayKnxDaliDataType::kSwitch:
|
case GatewayKnxDaliDataType::kSwitch:
|
||||||
@@ -522,14 +683,170 @@ std::string GatewayKnxGroupAddressString(uint16_t group_address) {
|
|||||||
std::to_string(sub);
|
std::to_string(sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
uint16_t GwReg1GroupAddressForObject(uint8_t main_group, uint16_t object_number) {
|
||||||
|
return GatewayKnxGroupAddress(main_group, static_cast<uint8_t>(object_number >> 8),
|
||||||
|
static_cast<uint8_t>(object_number & 0xff));
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayKnxDaliBinding MakeGwReg1Binding(uint8_t main_group, uint16_t object_number,
|
||||||
|
int channel_index, const char* object_role,
|
||||||
|
GatewayKnxDaliDataType data_type,
|
||||||
|
GatewayKnxDaliTarget target) {
|
||||||
|
GatewayKnxDaliBinding binding;
|
||||||
|
binding.mapping_mode = GatewayKnxMappingMode::kGwReg1Direct;
|
||||||
|
binding.group_object_number = static_cast<int>(object_number);
|
||||||
|
binding.channel_index = channel_index;
|
||||||
|
binding.object_role = object_role;
|
||||||
|
binding.main_group = main_group;
|
||||||
|
binding.middle_group = static_cast<uint8_t>((object_number >> 8) & 0x07);
|
||||||
|
binding.sub_group = static_cast<uint8_t>(object_number & 0xff);
|
||||||
|
binding.group_address = GwReg1GroupAddressForObject(main_group, object_number);
|
||||||
|
binding.address = GatewayKnxGroupAddressString(binding.group_address);
|
||||||
|
binding.data_type = data_type;
|
||||||
|
binding.target = target;
|
||||||
|
binding.datapoint_type = DataTypeDpt(data_type);
|
||||||
|
binding.name = std::string("GW-REG1 ") + TargetName(target) + " - " +
|
||||||
|
DataTypeName(data_type);
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GatewayKnxDaliBinding> GwReg1BindingForObject(uint8_t main_group,
|
||||||
|
uint16_t object_number) {
|
||||||
|
if (object_number == kGwReg1AppKoBroadcastSwitch) {
|
||||||
|
return MakeGwReg1Binding(
|
||||||
|
main_group, object_number, -1, "broadcast_switch", GatewayKnxDaliDataType::kSwitch,
|
||||||
|
GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kBroadcast, 127});
|
||||||
|
}
|
||||||
|
if (object_number == kGwReg1AppKoBroadcastDimm) {
|
||||||
|
return MakeGwReg1Binding(
|
||||||
|
main_group, object_number, -1, "broadcast_dimm_absolute",
|
||||||
|
GatewayKnxDaliDataType::kBrightness,
|
||||||
|
GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kBroadcast, 127});
|
||||||
|
}
|
||||||
|
|
||||||
|
const int adr_relative = static_cast<int>(object_number) - kGwReg1AdrKoOffset;
|
||||||
|
if (adr_relative >= 0 && adr_relative < kGwReg1AdrKoBlockSize * 64) {
|
||||||
|
const int channel = adr_relative / kGwReg1AdrKoBlockSize;
|
||||||
|
const int slot = adr_relative % kGwReg1AdrKoBlockSize;
|
||||||
|
const GatewayKnxDaliTarget target{GatewayKnxDaliTargetKind::kShortAddress, channel};
|
||||||
|
if (slot == kGwReg1KoSwitch) {
|
||||||
|
return MakeGwReg1Binding(main_group, object_number, channel, "switch",
|
||||||
|
GatewayKnxDaliDataType::kSwitch, target);
|
||||||
|
}
|
||||||
|
if (slot == kGwReg1KoDimmAbsolute) {
|
||||||
|
return MakeGwReg1Binding(main_group, object_number, channel, "dimm_absolute",
|
||||||
|
GatewayKnxDaliDataType::kBrightness, target);
|
||||||
|
}
|
||||||
|
if (slot == kGwReg1KoColor) {
|
||||||
|
return MakeGwReg1Binding(main_group, object_number, channel, "color",
|
||||||
|
GatewayKnxDaliDataType::kRgb, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int group_relative = static_cast<int>(object_number) - kGwReg1GrpKoOffset;
|
||||||
|
if (group_relative >= 0 && group_relative < kGwReg1GrpKoBlockSize * 16) {
|
||||||
|
const int group = group_relative / kGwReg1GrpKoBlockSize;
|
||||||
|
const int slot = group_relative % kGwReg1GrpKoBlockSize;
|
||||||
|
const GatewayKnxDaliTarget target{GatewayKnxDaliTargetKind::kGroup, group};
|
||||||
|
if (slot == kGwReg1KoSwitch) {
|
||||||
|
return MakeGwReg1Binding(main_group, object_number, group, "switch",
|
||||||
|
GatewayKnxDaliDataType::kSwitch, target);
|
||||||
|
}
|
||||||
|
if (slot == kGwReg1KoDimmAbsolute) {
|
||||||
|
return MakeGwReg1Binding(main_group, object_number, group, "dimm_absolute",
|
||||||
|
GatewayKnxDaliDataType::kBrightness, target);
|
||||||
|
}
|
||||||
|
if (slot == kGwReg1KoColor) {
|
||||||
|
return MakeGwReg1Binding(main_group, object_number, group, "color",
|
||||||
|
GatewayKnxDaliDataType::kRgb, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<GatewayKnxDaliBinding> EtsBindingForAssociation(uint8_t main_group,
|
||||||
|
const GatewayKnxEtsAssociation& association) {
|
||||||
|
auto binding = GwReg1BindingForObject(main_group, association.group_object_number);
|
||||||
|
if (!binding.has_value()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
binding->mapping_mode = GatewayKnxMappingMode::kEtsDatabase;
|
||||||
|
binding->group_address = association.group_address;
|
||||||
|
binding->address = GatewayKnxGroupAddressString(association.group_address);
|
||||||
|
binding->name = std::string("ETS ") + binding->name;
|
||||||
|
return binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
GatewayKnxBridge::GatewayKnxBridge(DaliBridgeEngine& engine) : engine_(engine) {}
|
GatewayKnxBridge::GatewayKnxBridge(DaliBridgeEngine& engine) : engine_(engine) {}
|
||||||
|
|
||||||
void GatewayKnxBridge::setConfig(const GatewayKnxConfig& config) { config_ = config; }
|
void GatewayKnxBridge::setConfig(const GatewayKnxConfig& config) {
|
||||||
|
config_ = config;
|
||||||
|
rebuildEtsBindings();
|
||||||
|
}
|
||||||
|
|
||||||
const GatewayKnxConfig& GatewayKnxBridge::config() const { return config_; }
|
const GatewayKnxConfig& GatewayKnxBridge::config() const { return config_; }
|
||||||
|
|
||||||
|
size_t GatewayKnxBridge::etsBindingCount() const {
|
||||||
|
size_t count = 0;
|
||||||
|
for (const auto& entry : ets_bindings_by_group_address_) {
|
||||||
|
count += entry.second.size();
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<GatewayKnxDaliBinding> GatewayKnxBridge::describeDaliBindings() const {
|
std::vector<GatewayKnxDaliBinding> GatewayKnxBridge::describeDaliBindings() const {
|
||||||
std::vector<GatewayKnxDaliBinding> bindings;
|
std::vector<GatewayKnxDaliBinding> bindings;
|
||||||
|
std::set<uint16_t> ets_group_addresses;
|
||||||
|
if (config_.ets_database_enabled) {
|
||||||
|
for (const auto& entry : ets_bindings_by_group_address_) {
|
||||||
|
ets_group_addresses.insert(entry.first);
|
||||||
|
bindings.insert(bindings.end(), entry.second.begin(), entry.second.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config_.mapping_mode == GatewayKnxMappingMode::kGwReg1Direct) {
|
||||||
|
bindings.reserve(2 + (64 * 3) + (16 * 3));
|
||||||
|
if (const auto binding = GwReg1BindingForObject(config_.main_group,
|
||||||
|
kGwReg1AppKoBroadcastSwitch)) {
|
||||||
|
if (ets_group_addresses.count(binding->group_address) == 0) {
|
||||||
|
bindings.push_back(binding.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (const auto binding = GwReg1BindingForObject(config_.main_group,
|
||||||
|
kGwReg1AppKoBroadcastDimm)) {
|
||||||
|
if (ets_group_addresses.count(binding->group_address) == 0) {
|
||||||
|
bindings.push_back(binding.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int address = 0; address < 64; ++address) {
|
||||||
|
const uint16_t base = static_cast<uint16_t>(kGwReg1AdrKoOffset +
|
||||||
|
(address * kGwReg1AdrKoBlockSize));
|
||||||
|
for (const uint8_t slot : {kGwReg1KoSwitch, kGwReg1KoDimmAbsolute, kGwReg1KoColor}) {
|
||||||
|
if (const auto binding = GwReg1BindingForObject(config_.main_group, base + slot)) {
|
||||||
|
if (ets_group_addresses.count(binding->group_address) == 0) {
|
||||||
|
bindings.push_back(binding.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int group = 0; group < 16; ++group) {
|
||||||
|
const uint16_t base = static_cast<uint16_t>(kGwReg1GrpKoOffset +
|
||||||
|
(group * kGwReg1GrpKoBlockSize));
|
||||||
|
for (const uint8_t slot : {kGwReg1KoSwitch, kGwReg1KoDimmAbsolute, kGwReg1KoColor}) {
|
||||||
|
if (const auto binding = GwReg1BindingForObject(config_.main_group, base + slot)) {
|
||||||
|
if (ets_group_addresses.count(binding->group_address) == 0) {
|
||||||
|
bindings.push_back(binding.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
bindings.reserve(4 * 81);
|
bindings.reserve(4 * 81);
|
||||||
for (uint8_t middle = 1; middle <= 4; ++middle) {
|
for (uint8_t middle = 1; middle <= 4; ++middle) {
|
||||||
const auto data_type = GatewayKnxDaliDataTypeForMiddleGroup(middle);
|
const auto data_type = GatewayKnxDaliDataTypeForMiddleGroup(middle);
|
||||||
@@ -542,6 +859,7 @@ std::vector<GatewayKnxDaliBinding> GatewayKnxBridge::describeDaliBindings() cons
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
GatewayKnxDaliBinding binding;
|
GatewayKnxDaliBinding binding;
|
||||||
|
binding.mapping_mode = GatewayKnxMappingMode::kFormula;
|
||||||
binding.main_group = config_.main_group;
|
binding.main_group = config_.main_group;
|
||||||
binding.middle_group = middle;
|
binding.middle_group = middle;
|
||||||
binding.sub_group = sub;
|
binding.sub_group = sub;
|
||||||
@@ -549,6 +867,10 @@ std::vector<GatewayKnxDaliBinding> GatewayKnxBridge::describeDaliBindings() cons
|
|||||||
binding.address = GatewayKnxGroupAddressString(binding.group_address);
|
binding.address = GatewayKnxGroupAddressString(binding.group_address);
|
||||||
binding.data_type = data_type.value();
|
binding.data_type = data_type.value();
|
||||||
binding.target = target.value();
|
binding.target = target.value();
|
||||||
|
if (ets_group_addresses.count(binding.group_address) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
binding.object_role = GatewayKnxDataTypeToString(data_type.value());
|
||||||
binding.datapoint_type = DataTypeDpt(data_type.value());
|
binding.datapoint_type = DataTypeDpt(data_type.value());
|
||||||
binding.name = TargetName(target.value()) + " - " + DataTypeName(data_type.value());
|
binding.name = TargetName(target.value()) + " - " + DataTypeName(data_type.value());
|
||||||
bindings.push_back(std::move(binding));
|
bindings.push_back(std::move(binding));
|
||||||
@@ -570,12 +892,29 @@ DaliBridgeResult GatewayKnxBridge::handleGroupWrite(uint16_t group_address, cons
|
|||||||
if (!config_.dali_router_enabled) {
|
if (!config_.dali_router_enabled) {
|
||||||
return ErrorResult(group_address, "KNX to DALI router disabled");
|
return ErrorResult(group_address, "KNX to DALI router disabled");
|
||||||
}
|
}
|
||||||
|
if (config_.ets_database_enabled) {
|
||||||
|
const auto ets_bindings = ets_bindings_by_group_address_.find(group_address);
|
||||||
|
if (ets_bindings != ets_bindings_by_group_address_.end()) {
|
||||||
|
return executeEtsBindings(group_address, ets_bindings->second, data, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
const uint8_t main = static_cast<uint8_t>((group_address >> 11) & 0x1f);
|
const uint8_t main = static_cast<uint8_t>((group_address >> 11) & 0x1f);
|
||||||
const uint8_t middle = static_cast<uint8_t>((group_address >> 8) & 0x07);
|
const uint8_t middle = static_cast<uint8_t>((group_address >> 8) & 0x07);
|
||||||
const uint8_t sub = static_cast<uint8_t>(group_address & 0xff);
|
const uint8_t sub = static_cast<uint8_t>(group_address & 0xff);
|
||||||
if (main != config_.main_group) {
|
if (main != config_.main_group) {
|
||||||
return ErrorResult(group_address, "KNX main group does not match gateway config");
|
return ErrorResult(group_address, "KNX main group does not match gateway config");
|
||||||
}
|
}
|
||||||
|
if (config_.mapping_mode == GatewayKnxMappingMode::kGwReg1Direct) {
|
||||||
|
const uint16_t object_number = static_cast<uint16_t>((middle << 8) | sub);
|
||||||
|
const auto binding = GwReg1BindingForObject(config_.main_group, object_number);
|
||||||
|
if (!binding.has_value()) {
|
||||||
|
return ErrorResult(group_address, "unmapped GW-REG1 KNX object address");
|
||||||
|
}
|
||||||
|
return executeForDecodedWrite(group_address, binding->data_type, binding->target, data, len);
|
||||||
|
}
|
||||||
|
if (config_.mapping_mode == GatewayKnxMappingMode::kManual) {
|
||||||
|
return ErrorResult(group_address, "manual KNX mapping dataset is not configured");
|
||||||
|
}
|
||||||
const auto data_type = GatewayKnxDaliDataTypeForMiddleGroup(middle);
|
const auto data_type = GatewayKnxDaliDataTypeForMiddleGroup(middle);
|
||||||
const auto target = GatewayKnxDaliTargetForSubgroup(sub);
|
const auto target = GatewayKnxDaliTargetForSubgroup(sub);
|
||||||
if (!data_type.has_value() || !target.has_value()) {
|
if (!data_type.has_value() || !target.has_value()) {
|
||||||
@@ -584,6 +923,41 @@ DaliBridgeResult GatewayKnxBridge::handleGroupWrite(uint16_t group_address, cons
|
|||||||
return executeForDecodedWrite(group_address, data_type.value(), target.value(), data, len);
|
return executeForDecodedWrite(group_address, data_type.value(), target.value(), data, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DaliBridgeResult GatewayKnxBridge::executeEtsBindings(
|
||||||
|
uint16_t group_address, const std::vector<GatewayKnxDaliBinding>& bindings,
|
||||||
|
const uint8_t* data, size_t len) {
|
||||||
|
if (bindings.empty()) {
|
||||||
|
return ErrorResult(group_address, "unmapped ETS KNX group address");
|
||||||
|
}
|
||||||
|
DaliBridgeResult result;
|
||||||
|
result.ok = true;
|
||||||
|
result.metadata["source"] = "ets_database";
|
||||||
|
result.metadata["groupAddress"] = GatewayKnxGroupAddressString(group_address);
|
||||||
|
result.metadata["bindingCount"] = static_cast<int>(bindings.size());
|
||||||
|
for (const auto& binding : bindings) {
|
||||||
|
DaliBridgeResult child = executeForDecodedWrite(group_address, binding.data_type,
|
||||||
|
binding.target, data, len);
|
||||||
|
result.ok = result.ok && child.ok;
|
||||||
|
result.results.emplace_back(child.toJson());
|
||||||
|
}
|
||||||
|
result.data = static_cast<int>(result.results.size());
|
||||||
|
if (!result.ok) {
|
||||||
|
result.error = "one or more ETS KNX bindings failed";
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayKnxBridge::rebuildEtsBindings() {
|
||||||
|
ets_bindings_by_group_address_.clear();
|
||||||
|
for (const auto& association : config_.ets_associations) {
|
||||||
|
const auto binding = EtsBindingForAssociation(config_.main_group, association);
|
||||||
|
if (!binding.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ets_bindings_by_group_address_[association.group_address].push_back(binding.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DaliBridgeResult GatewayKnxBridge::executeForDecodedWrite(uint16_t group_address,
|
DaliBridgeResult GatewayKnxBridge::executeForDecodedWrite(uint16_t group_address,
|
||||||
GatewayKnxDaliDataType data_type,
|
GatewayKnxDaliDataType data_type,
|
||||||
GatewayKnxDaliTarget target,
|
GatewayKnxDaliTarget target,
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
set(OPENKNX_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../knx")
|
||||||
|
set(TPUART_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../tpuart")
|
||||||
|
|
||||||
|
if(NOT EXISTS "${OPENKNX_ROOT}/src/knx/platform.h")
|
||||||
|
message(FATAL_ERROR "OpenKNX submodule is missing at ${OPENKNX_ROOT}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${TPUART_ROOT}/src/TPUart/DataLinkLayer.h")
|
||||||
|
message(FATAL_ERROR "TPUart submodule is missing at ${TPUART_ROOT}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
file(GLOB OPENKNX_SRCS
|
||||||
|
"${OPENKNX_ROOT}/src/knx/*.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(TPUART_SRCS
|
||||||
|
"${TPUART_ROOT}/src/TPUart/DataLinkLayer.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart/Receiver.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart/RepetitionFilter.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart/RingBuffer.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart/SearchBuffer.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart/Statistics.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart/SystemState.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart/Transmitter.cpp"
|
||||||
|
"${TPUART_ROOT}/src/TPUart.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
idf_component_register(
|
||||||
|
SRCS
|
||||||
|
"src/arduino_compat.cpp"
|
||||||
|
"src/esp_idf_platform.cpp"
|
||||||
|
"src/ets_memory_loader.cpp"
|
||||||
|
"src/tpuart_uart_interface.cpp"
|
||||||
|
${OPENKNX_SRCS}
|
||||||
|
${TPUART_SRCS}
|
||||||
|
INCLUDE_DIRS
|
||||||
|
"include"
|
||||||
|
"${OPENKNX_ROOT}/src"
|
||||||
|
"${TPUART_ROOT}/src"
|
||||||
|
REQUIRES
|
||||||
|
esp_driver_gpio
|
||||||
|
esp_driver_uart
|
||||||
|
esp_netif
|
||||||
|
esp_timer
|
||||||
|
esp_wifi
|
||||||
|
freertos
|
||||||
|
log
|
||||||
|
lwip
|
||||||
|
nvs_flash
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(${COMPONENT_LIB} PUBLIC
|
||||||
|
MASK_VERSION=0x07B0
|
||||||
|
KNX_FLASH_SIZE=4096
|
||||||
|
KNX_NO_AUTOMATIC_GLOBAL_INSTANCE
|
||||||
|
KNX_NO_SPI
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(${COMPONENT_LIB} PRIVATE
|
||||||
|
-Wno-unused-parameter
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifndef DEC
|
||||||
|
#define DEC 10
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HEX
|
||||||
|
#define HEX 16
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef INPUT
|
||||||
|
#define INPUT 0x0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OUTPUT
|
||||||
|
#define OUTPUT 0x1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef INPUT_PULLUP
|
||||||
|
#define INPUT_PULLUP 0x2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef INPUT_PULLDOWN
|
||||||
|
#define INPUT_PULLDOWN 0x3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef LOW
|
||||||
|
#define LOW 0x0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HIGH
|
||||||
|
#define HIGH 0x1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CHANGE
|
||||||
|
#define CHANGE 2
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef FALLING
|
||||||
|
#define FALLING 3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef RISING
|
||||||
|
#define RISING 4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using uint = unsigned int;
|
||||||
|
|
||||||
|
uint32_t millis();
|
||||||
|
uint32_t micros();
|
||||||
|
void delay(uint32_t millis);
|
||||||
|
void delayMicroseconds(unsigned int howLong);
|
||||||
|
void pinMode(uint32_t pin, uint32_t mode);
|
||||||
|
void digitalWrite(uint32_t pin, uint32_t value);
|
||||||
|
uint32_t digitalRead(uint32_t pin);
|
||||||
|
typedef void (*voidFuncPtr)(void);
|
||||||
|
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode);
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "knx/platform.h"
|
||||||
|
|
||||||
|
#include "esp_netif.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
|
||||||
|
class EspIdfPlatform : public Platform {
|
||||||
|
public:
|
||||||
|
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
|
||||||
|
const char* nvs_namespace = "openknx");
|
||||||
|
~EspIdfPlatform() override;
|
||||||
|
|
||||||
|
void networkInterface(esp_netif_t* netif);
|
||||||
|
esp_netif_t* networkInterface() const;
|
||||||
|
|
||||||
|
uint32_t currentIpAddress() override;
|
||||||
|
uint32_t currentSubnetMask() override;
|
||||||
|
uint32_t currentDefaultGateway() override;
|
||||||
|
void macAddress(uint8_t* data) override;
|
||||||
|
uint32_t uniqueSerialNumber() override;
|
||||||
|
|
||||||
|
void restart() override;
|
||||||
|
void fatalError() override;
|
||||||
|
|
||||||
|
void setupMultiCast(uint32_t addr, uint16_t port) override;
|
||||||
|
void closeMultiCast() override;
|
||||||
|
bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override;
|
||||||
|
int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override;
|
||||||
|
int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr,
|
||||||
|
uint16_t& src_port) override;
|
||||||
|
bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer,
|
||||||
|
uint16_t len) override;
|
||||||
|
|
||||||
|
uint8_t* getEepromBuffer(uint32_t size) override;
|
||||||
|
void commitToEeprom() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
esp_netif_t* effectiveNetif() const;
|
||||||
|
void loadEeprom(size_t size);
|
||||||
|
|
||||||
|
esp_netif_t* netif_{nullptr};
|
||||||
|
int udp_sock_{-1};
|
||||||
|
sockaddr_in multicast_remote_{};
|
||||||
|
sockaddr_in last_remote_{};
|
||||||
|
bool has_last_remote_{false};
|
||||||
|
std::vector<uint8_t> eeprom_;
|
||||||
|
std::string nvs_namespace_;
|
||||||
|
bool eeprom_loaded_{false};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
|
||||||
|
struct EtsAssociation {
|
||||||
|
uint16_t group_address{0};
|
||||||
|
uint16_t group_object_number{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EtsMemorySnapshot {
|
||||||
|
bool configured{false};
|
||||||
|
std::vector<EtsAssociation> associations;
|
||||||
|
};
|
||||||
|
|
||||||
|
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace);
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "openknx_idf/ets_memory_loader.h"
|
||||||
|
#include "openknx_idf/esp_idf_platform.h"
|
||||||
|
#include "openknx_idf/tpuart_uart_interface.h"
|
||||||
|
|
||||||
|
#include "knx/bau07B0.h"
|
||||||
|
#include "knx_facade.h"
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
|
||||||
|
using DaliGatewayDevice = KnxFacade<EspIdfPlatform, Bau07B0>;
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TPUart/Interface/Abstract.h"
|
||||||
|
|
||||||
|
#include "driver/uart.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
|
||||||
|
class TpuartUartInterface : public TPUart::Interface::Abstract {
|
||||||
|
public:
|
||||||
|
TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
|
||||||
|
size_t rx_buffer_size = 512, size_t tx_buffer_size = 512);
|
||||||
|
~TpuartUartInterface();
|
||||||
|
|
||||||
|
void begin(int baud) override;
|
||||||
|
void end() override;
|
||||||
|
bool available() override;
|
||||||
|
bool availableForWrite() override;
|
||||||
|
bool write(char value) override;
|
||||||
|
int read() override;
|
||||||
|
bool overflow() override;
|
||||||
|
void flush() override;
|
||||||
|
bool hasCallback() override;
|
||||||
|
void registerCallback(std::function<bool()> callback) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uart_port_t uart_port_;
|
||||||
|
int tx_pin_;
|
||||||
|
int rx_pin_;
|
||||||
|
size_t rx_buffer_size_;
|
||||||
|
size_t tx_buffer_size_;
|
||||||
|
std::atomic_bool overflow_{false};
|
||||||
|
std::function<bool()> callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::array<voidFuncPtr, GPIO_NUM_MAX> g_gpio_callbacks{};
|
||||||
|
bool g_isr_service_installed = false;
|
||||||
|
|
||||||
|
void IRAM_ATTR gpioIsrThunk(void* arg) {
|
||||||
|
const auto pin = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(arg));
|
||||||
|
if (pin < g_gpio_callbacks.size() && g_gpio_callbacks[pin] != nullptr) {
|
||||||
|
g_gpio_callbacks[pin]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_int_type_t toGpioInterrupt(uint32_t mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case RISING:
|
||||||
|
return GPIO_INTR_POSEDGE;
|
||||||
|
case FALLING:
|
||||||
|
return GPIO_INTR_NEGEDGE;
|
||||||
|
case CHANGE:
|
||||||
|
return GPIO_INTR_ANYEDGE;
|
||||||
|
default:
|
||||||
|
return GPIO_INTR_DISABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printUnsigned(unsigned long long value, int base) {
|
||||||
|
if (base == HEX) {
|
||||||
|
std::printf("%llX", value);
|
||||||
|
} else {
|
||||||
|
std::printf("%llu", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printSigned(long long value, int base) {
|
||||||
|
if (base == HEX) {
|
||||||
|
std::printf("%llX", static_cast<unsigned long long>(value));
|
||||||
|
} else {
|
||||||
|
std::printf("%lld", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
uint32_t millis() { return static_cast<uint32_t>(esp_timer_get_time() / 1000ULL); }
|
||||||
|
|
||||||
|
uint32_t micros() { return static_cast<uint32_t>(esp_timer_get_time()); }
|
||||||
|
|
||||||
|
void delay(uint32_t millis) { vTaskDelay(pdMS_TO_TICKS(millis)); }
|
||||||
|
|
||||||
|
void delayMicroseconds(unsigned int howLong) { esp_rom_delay_us(howLong); }
|
||||||
|
|
||||||
|
void pinMode(uint32_t pin, uint32_t mode) {
|
||||||
|
if (pin >= GPIO_NUM_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpio_config_t config{};
|
||||||
|
config.pin_bit_mask = 1ULL << pin;
|
||||||
|
config.mode = mode == OUTPUT ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
|
||||||
|
config.pull_up_en = mode == INPUT_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
|
||||||
|
config.pull_down_en = mode == INPUT_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
|
||||||
|
config.intr_type = GPIO_INTR_DISABLE;
|
||||||
|
gpio_config(&config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void digitalWrite(uint32_t pin, uint32_t value) {
|
||||||
|
if (pin < GPIO_NUM_MAX) {
|
||||||
|
gpio_set_level(static_cast<gpio_num_t>(pin), value == LOW ? 0 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t digitalRead(uint32_t pin) {
|
||||||
|
if (pin >= GPIO_NUM_MAX) {
|
||||||
|
return LOW;
|
||||||
|
}
|
||||||
|
return gpio_get_level(static_cast<gpio_num_t>(pin)) == 0 ? LOW : HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode) {
|
||||||
|
if (pin >= GPIO_NUM_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!g_isr_service_installed) {
|
||||||
|
const esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
|
||||||
|
g_isr_service_installed = err == ESP_OK || err == ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
if (!g_isr_service_installed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gpio_set_intr_type(static_cast<gpio_num_t>(pin), toGpioInterrupt(mode));
|
||||||
|
gpio_isr_handler_remove(static_cast<gpio_num_t>(pin));
|
||||||
|
g_gpio_callbacks[pin] = callback;
|
||||||
|
if (callback != nullptr) {
|
||||||
|
gpio_isr_handler_add(static_cast<gpio_num_t>(pin), gpioIsrThunk,
|
||||||
|
reinterpret_cast<void*>(static_cast<uintptr_t>(pin)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(const char value[]) { std::printf("%s", value == nullptr ? "" : value); }
|
||||||
|
|
||||||
|
void print(char value) { std::printf("%c", value); }
|
||||||
|
|
||||||
|
void print(unsigned char value, int base) { printUnsigned(value, base); }
|
||||||
|
|
||||||
|
void print(int value, int base) { printSigned(value, base); }
|
||||||
|
|
||||||
|
void print(unsigned int value, int base) { printUnsigned(value, base); }
|
||||||
|
|
||||||
|
void print(long value, int base) { printSigned(value, base); }
|
||||||
|
|
||||||
|
void print(unsigned long value, int base) { printUnsigned(value, base); }
|
||||||
|
|
||||||
|
void print(long long value, int base) { printSigned(value, base); }
|
||||||
|
|
||||||
|
void print(unsigned long long value, int base) { printUnsigned(value, base); }
|
||||||
|
|
||||||
|
void print(double value) { std::printf("%f", value); }
|
||||||
|
|
||||||
|
void println(const char value[]) {
|
||||||
|
print(value);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(char value) {
|
||||||
|
print(value);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(unsigned char value, int base) {
|
||||||
|
print(value, base);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(int value, int base) {
|
||||||
|
print(value, base);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(unsigned int value, int base) {
|
||||||
|
print(value, base);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(long value, int base) {
|
||||||
|
print(value, base);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(unsigned long value, int base) {
|
||||||
|
print(value, base);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(long long value, int base) {
|
||||||
|
print(value, base);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(unsigned long long value, int base) {
|
||||||
|
print(value, base);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(double value) {
|
||||||
|
print(value);
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void println(void) { std::printf("\n"); }
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
#include "openknx_idf/esp_idf_platform.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_mac.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "lwip/inet.h"
|
||||||
|
#include "nvs.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kTag = "openknx_idf";
|
||||||
|
constexpr const char* kEepromKey = "eeprom";
|
||||||
|
|
||||||
|
esp_netif_t* findDefaultNetif() {
|
||||||
|
if (auto* sta = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF")) {
|
||||||
|
return sta;
|
||||||
|
}
|
||||||
|
if (auto* eth = esp_netif_get_handle_from_ifkey("ETH_DEF")) {
|
||||||
|
return eth;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
EspIdfPlatform::EspIdfPlatform(TPUart::Interface::Abstract* interface,
|
||||||
|
const char* nvs_namespace)
|
||||||
|
: nvs_namespace_(nvs_namespace == nullptr ? "openknx" : nvs_namespace) {
|
||||||
|
this->interface(interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
EspIdfPlatform::~EspIdfPlatform() { closeMultiCast(); }
|
||||||
|
|
||||||
|
void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
|
||||||
|
|
||||||
|
esp_netif_t* EspIdfPlatform::networkInterface() const { return netif_; }
|
||||||
|
|
||||||
|
esp_netif_t* EspIdfPlatform::effectiveNetif() const {
|
||||||
|
return netif_ == nullptr ? findDefaultNetif() : netif_;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspIdfPlatform::currentIpAddress() {
|
||||||
|
esp_netif_ip_info_t ip_info{};
|
||||||
|
esp_netif_t* netif = effectiveNetif();
|
||||||
|
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ip_info.ip.addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspIdfPlatform::currentSubnetMask() {
|
||||||
|
esp_netif_ip_info_t ip_info{};
|
||||||
|
esp_netif_t* netif = effectiveNetif();
|
||||||
|
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ip_info.netmask.addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspIdfPlatform::currentDefaultGateway() {
|
||||||
|
esp_netif_ip_info_t ip_info{};
|
||||||
|
esp_netif_t* netif = effectiveNetif();
|
||||||
|
if (netif == nullptr || esp_netif_get_ip_info(netif, &ip_info) != ESP_OK) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return ip_info.gw.addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspIdfPlatform::macAddress(uint8_t* data) {
|
||||||
|
if (data == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (esp_read_mac(data, ESP_MAC_WIFI_STA) != ESP_OK) {
|
||||||
|
std::memset(data, 0, 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspIdfPlatform::uniqueSerialNumber() {
|
||||||
|
uint8_t mac[6]{};
|
||||||
|
macAddress(mac);
|
||||||
|
return (static_cast<uint32_t>(mac[0]) << 24) | (static_cast<uint32_t>(mac[1]) << 16) |
|
||||||
|
(static_cast<uint32_t>(mac[4]) << 8) | mac[5];
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspIdfPlatform::restart() { esp_restart(); }
|
||||||
|
|
||||||
|
void EspIdfPlatform::fatalError() {
|
||||||
|
ESP_LOGE(kTag, "OpenKNX fatal error");
|
||||||
|
while (true) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspIdfPlatform::setupMultiCast(uint32_t addr, uint16_t port) {
|
||||||
|
closeMultiCast();
|
||||||
|
udp_sock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||||
|
if (udp_sock_ < 0) {
|
||||||
|
ESP_LOGE(kTag, "failed to create UDP socket: errno=%d", errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int reuse = 1;
|
||||||
|
setsockopt(udp_sock_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||||
|
|
||||||
|
sockaddr_in bind_addr{};
|
||||||
|
bind_addr.sin_family = AF_INET;
|
||||||
|
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
bind_addr.sin_port = htons(port);
|
||||||
|
if (bind(udp_sock_, reinterpret_cast<sockaddr*>(&bind_addr), sizeof(bind_addr)) < 0) {
|
||||||
|
ESP_LOGE(kTag, "failed to bind UDP socket: errno=%d", errno);
|
||||||
|
closeMultiCast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeval timeout{};
|
||||||
|
timeout.tv_sec = 0;
|
||||||
|
timeout.tv_usec = 1000;
|
||||||
|
setsockopt(udp_sock_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||||
|
|
||||||
|
ip_mreq mreq{};
|
||||||
|
mreq.imr_multiaddr.s_addr = htonl(addr);
|
||||||
|
mreq.imr_interface.s_addr = currentIpAddress();
|
||||||
|
if (setsockopt(udp_sock_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
|
||||||
|
ESP_LOGW(kTag, "failed to join KNX multicast group: errno=%d", errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t loop = 0;
|
||||||
|
setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
|
||||||
|
|
||||||
|
multicast_remote_ = {};
|
||||||
|
multicast_remote_.sin_family = AF_INET;
|
||||||
|
multicast_remote_.sin_addr.s_addr = htonl(addr);
|
||||||
|
multicast_remote_.sin_port = htons(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspIdfPlatform::closeMultiCast() {
|
||||||
|
if (udp_sock_ >= 0) {
|
||||||
|
shutdown(udp_sock_, SHUT_RDWR);
|
||||||
|
close(udp_sock_);
|
||||||
|
udp_sock_ = -1;
|
||||||
|
}
|
||||||
|
has_last_remote_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspIdfPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) {
|
||||||
|
if (udp_sock_ < 0 || buffer == nullptr || len == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const int sent = sendto(udp_sock_, buffer, len, 0, reinterpret_cast<sockaddr*>(&multicast_remote_),
|
||||||
|
sizeof(multicast_remote_));
|
||||||
|
return sent == len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EspIdfPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) {
|
||||||
|
uint32_t src_addr = 0;
|
||||||
|
uint16_t src_port = 0;
|
||||||
|
return readBytesMultiCast(buffer, maxLen, src_addr, src_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
int EspIdfPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr,
|
||||||
|
uint16_t& src_port) {
|
||||||
|
if (udp_sock_ < 0 || buffer == nullptr || maxLen == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
sockaddr_in remote{};
|
||||||
|
socklen_t remote_len = sizeof(remote);
|
||||||
|
const int len = recvfrom(udp_sock_, buffer, maxLen, 0, reinterpret_cast<sockaddr*>(&remote),
|
||||||
|
&remote_len);
|
||||||
|
if (len <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
last_remote_ = remote;
|
||||||
|
has_last_remote_ = true;
|
||||||
|
src_addr = ntohl(remote.sin_addr.s_addr);
|
||||||
|
src_port = ntohs(remote.sin_port);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspIdfPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer,
|
||||||
|
uint16_t len) {
|
||||||
|
if (udp_sock_ < 0 || buffer == nullptr || len == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sockaddr_in remote{};
|
||||||
|
if (addr == 0 && port == 0 && has_last_remote_) {
|
||||||
|
remote = last_remote_;
|
||||||
|
} else {
|
||||||
|
remote.sin_family = AF_INET;
|
||||||
|
remote.sin_addr.s_addr = htonl(addr);
|
||||||
|
remote.sin_port = htons(port);
|
||||||
|
}
|
||||||
|
const int sent = sendto(udp_sock_, buffer, len, 0, reinterpret_cast<sockaddr*>(&remote),
|
||||||
|
sizeof(remote));
|
||||||
|
return sent == len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspIdfPlatform::loadEeprom(size_t size) {
|
||||||
|
if (eeprom_loaded_ && eeprom_.size() == size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
eeprom_.assign(size, 0xff);
|
||||||
|
eeprom_loaded_ = true;
|
||||||
|
|
||||||
|
if (!ensureNvsReady()) {
|
||||||
|
ESP_LOGW(kTag, "NVS is not ready for OpenKNX EEPROM load");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvs_handle_t handle = 0;
|
||||||
|
if (nvs_open(nvs_namespace_.c_str(), NVS_READONLY, &handle) != ESP_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t stored_size = 0;
|
||||||
|
if (nvs_get_blob(handle, kEepromKey, nullptr, &stored_size) == ESP_OK && stored_size > 0) {
|
||||||
|
std::vector<uint8_t> stored(stored_size);
|
||||||
|
if (nvs_get_blob(handle, kEepromKey, stored.data(), &stored_size) == ESP_OK) {
|
||||||
|
std::memcpy(eeprom_.data(), stored.data(), std::min(eeprom_.size(), stored.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nvs_close(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* EspIdfPlatform::getEepromBuffer(uint32_t size) {
|
||||||
|
loadEeprom(size);
|
||||||
|
return eeprom_.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspIdfPlatform::commitToEeprom() {
|
||||||
|
if (eeprom_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ensureNvsReady()) {
|
||||||
|
ESP_LOGW(kTag, "NVS is not ready for OpenKNX EEPROM commit");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvs_handle_t handle = 0;
|
||||||
|
esp_err_t err = nvs_open(nvs_namespace_.c_str(), NVS_READWRITE, &handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(kTag, "failed to open OpenKNX NVS namespace: %s", esp_err_to_name(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
err = nvs_set_blob(handle, kEepromKey, eeprom_.data(), eeprom_.size());
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
err = nvs_commit(handle);
|
||||||
|
}
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGW(kTag, "failed to commit OpenKNX EEPROM: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
nvs_close(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#include "openknx_idf/ets_memory_loader.h"
|
||||||
|
|
||||||
|
#include "openknx_idf/esp_idf_platform.h"
|
||||||
|
|
||||||
|
#include "knx/bau07B0.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void CollectAssociation(uint16_t group_address, uint16_t group_object_number,
|
||||||
|
void* context) {
|
||||||
|
auto* associations = static_cast<std::vector<EtsAssociation>*>(context);
|
||||||
|
if (associations == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
associations->push_back(EtsAssociation{group_address, group_object_number});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
EtsMemorySnapshot LoadEtsMemorySnapshot(const std::string& nvs_namespace) {
|
||||||
|
EspIdfPlatform platform(nullptr, nvs_namespace.c_str());
|
||||||
|
Bau07B0 device(platform);
|
||||||
|
device.deviceObject().manufacturerId(0xfa);
|
||||||
|
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
|
||||||
|
device.readMemory();
|
||||||
|
|
||||||
|
EtsMemorySnapshot snapshot;
|
||||||
|
snapshot.configured = device.configured();
|
||||||
|
device.forEachEtsAssociation(CollectAssociation, &snapshot.associations);
|
||||||
|
std::sort(snapshot.associations.begin(), snapshot.associations.end(),
|
||||||
|
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||||
|
if (lhs.group_address != rhs.group_address) {
|
||||||
|
return lhs.group_address < rhs.group_address;
|
||||||
|
}
|
||||||
|
return lhs.group_object_number < rhs.group_object_number;
|
||||||
|
});
|
||||||
|
snapshot.associations.erase(
|
||||||
|
std::unique(snapshot.associations.begin(), snapshot.associations.end(),
|
||||||
|
[](const EtsAssociation& lhs, const EtsAssociation& rhs) {
|
||||||
|
return lhs.group_address == rhs.group_address &&
|
||||||
|
lhs.group_object_number == rhs.group_object_number;
|
||||||
|
}),
|
||||||
|
snapshot.associations.end());
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
#include "openknx_idf/tpuart_uart_interface.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace gateway::openknx {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kTag = "openknx_tpuart";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TpuartUartInterface::TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
|
||||||
|
size_t rx_buffer_size, size_t tx_buffer_size)
|
||||||
|
: uart_port_(uart_port),
|
||||||
|
tx_pin_(tx_pin),
|
||||||
|
rx_pin_(rx_pin),
|
||||||
|
rx_buffer_size_(rx_buffer_size),
|
||||||
|
tx_buffer_size_(tx_buffer_size) {}
|
||||||
|
|
||||||
|
TpuartUartInterface::~TpuartUartInterface() { end(); }
|
||||||
|
|
||||||
|
void TpuartUartInterface::begin(int baud) {
|
||||||
|
if (_running) {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
uart_config_t config{};
|
||||||
|
config.baud_rate = baud;
|
||||||
|
config.data_bits = UART_DATA_8_BITS;
|
||||||
|
config.parity = UART_PARITY_EVEN;
|
||||||
|
config.stop_bits = UART_STOP_BITS_1;
|
||||||
|
config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||||
|
config.source_clk = UART_SCLK_DEFAULT;
|
||||||
|
|
||||||
|
esp_err_t err = uart_param_config(uart_port_, &config);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "failed to configure UART%d: %s", uart_port_, esp_err_to_name(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = uart_set_pin(uart_port_, tx_pin_ < 0 ? UART_PIN_NO_CHANGE : tx_pin_,
|
||||||
|
rx_pin_ < 0 ? UART_PIN_NO_CHANGE : rx_pin_, UART_PIN_NO_CHANGE,
|
||||||
|
UART_PIN_NO_CHANGE);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "failed to route UART%d pins: %s", uart_port_, esp_err_to_name(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = uart_driver_install(uart_port_, rx_buffer_size_, tx_buffer_size_, 0, nullptr, 0);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(kTag, "failed to install UART%d driver: %s", uart_port_, esp_err_to_name(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uart_set_rx_full_threshold(uart_port_, 1);
|
||||||
|
_running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TpuartUartInterface::end() {
|
||||||
|
if (!_running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_running = false;
|
||||||
|
uart_driver_delete(uart_port_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpuartUartInterface::available() {
|
||||||
|
if (!_running) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t len = 0;
|
||||||
|
return uart_get_buffered_data_len(uart_port_, &len) == ESP_OK && len > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpuartUartInterface::availableForWrite() {
|
||||||
|
if (!_running) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t len = 0;
|
||||||
|
return uart_get_tx_buffer_free_size(uart_port_, &len) == ESP_OK && len > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpuartUartInterface::write(char value) {
|
||||||
|
if (!_running) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return uart_write_bytes(uart_port_, &value, 1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TpuartUartInterface::read() {
|
||||||
|
if (!_running) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t value = 0;
|
||||||
|
return uart_read_bytes(uart_port_, &value, 1, 0) == 1 ? value : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpuartUartInterface::overflow() { return overflow_.exchange(false); }
|
||||||
|
|
||||||
|
void TpuartUartInterface::flush() {
|
||||||
|
if (_running) {
|
||||||
|
uart_flush(uart_port_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TpuartUartInterface::hasCallback() { return false; }
|
||||||
|
|
||||||
|
void TpuartUartInterface::registerCallback(std::function<bool()> callback) {
|
||||||
|
callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gateway::openknx
|
||||||
+1
-1
Submodule knx updated: 7124a6435d...b747f6284d
Submodule
+1
Submodule knx_dali_gw added at 5cd7e66bf0
Submodule
+1
Submodule tpuart added at f8c01e6a32
Reference in New Issue
Block a user