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:
@@ -1,7 +1,7 @@
|
||||
idf_component_register(
|
||||
SRCS "src/gateway_knx.cpp"
|
||||
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)
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -32,15 +33,30 @@ struct GatewayKnxTpUartConfig {
|
||||
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 {
|
||||
bool dali_router_enabled{true};
|
||||
bool ip_router_enabled{false};
|
||||
bool tunnel_enabled{true};
|
||||
bool multicast_enabled{true};
|
||||
bool ets_database_enabled{true};
|
||||
GatewayKnxMappingMode mapping_mode{GatewayKnxMappingMode::kFormula};
|
||||
uint8_t main_group{0};
|
||||
uint16_t udp_port{kGatewayKnxDefaultUdpPort};
|
||||
std::string multicast_address{kGatewayKnxDefaultMulticastAddress};
|
||||
uint16_t individual_address{0x1101};
|
||||
std::vector<GatewayKnxEtsAssociation> ets_associations;
|
||||
GatewayKnxTpUartConfig tp_uart;
|
||||
};
|
||||
|
||||
@@ -69,8 +85,12 @@ struct GatewayKnxDaliBinding {
|
||||
uint8_t main_group{0};
|
||||
uint8_t middle_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 name;
|
||||
std::string object_role;
|
||||
std::string datapoint_type;
|
||||
GatewayKnxDaliDataType data_type{GatewayKnxDaliDataType::kUnknown};
|
||||
GatewayKnxDaliTarget target;
|
||||
@@ -79,6 +99,8 @@ struct GatewayKnxDaliBinding {
|
||||
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
|
||||
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
|
||||
|
||||
const char* GatewayKnxMappingModeToString(GatewayKnxMappingMode mode);
|
||||
GatewayKnxMappingMode GatewayKnxMappingModeFromString(const std::string& value);
|
||||
const char* GatewayKnxDataTypeToString(GatewayKnxDaliDataType data_type);
|
||||
const char* GatewayKnxTargetKindToString(GatewayKnxDaliTargetKind kind);
|
||||
std::optional<GatewayKnxDaliDataType> GatewayKnxDaliDataTypeForMiddleGroup(
|
||||
@@ -94,6 +116,7 @@ class GatewayKnxBridge {
|
||||
|
||||
void setConfig(const GatewayKnxConfig& config);
|
||||
const GatewayKnxConfig& config() const;
|
||||
size_t etsBindingCount() const;
|
||||
|
||||
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
|
||||
DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len);
|
||||
@@ -105,9 +128,14 @@ class GatewayKnxBridge {
|
||||
GatewayKnxDaliDataType data_type,
|
||||
GatewayKnxDaliTarget target,
|
||||
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_;
|
||||
GatewayKnxConfig config_;
|
||||
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
|
||||
};
|
||||
|
||||
class GatewayKnxTpIpRouter {
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -49,6 +53,15 @@ constexpr uint8_t kTpUartLDataConfirmNegative = 0x0b;
|
||||
constexpr uint8_t kTpUartLDataStart = 0x80;
|
||||
constexpr uint8_t kTpUartLDataEnd = 0x40;
|
||||
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 {
|
||||
uint16_t group_address{0};
|
||||
@@ -94,6 +107,108 @@ std::optional<std::string> ObjectStringAny(const DaliValue::Object& object,
|
||||
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) {
|
||||
switch (target.kind) {
|
||||
case GatewayKnxDaliTargetKind::kBroadcast:
|
||||
@@ -386,6 +501,12 @@ std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value
|
||||
.value_or(config.tunnel_enabled);
|
||||
config.multicast_enabled = ObjectBoolAny(object, {"multicastEnabled", "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>(
|
||||
std::clamp(ObjectIntAny(object, {"mainGroup", "main_group"}).value_or(config.main_group),
|
||||
0, 31));
|
||||
@@ -431,6 +552,8 @@ DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config) {
|
||||
out["ipRouterEnabled"] = config.ip_router_enabled;
|
||||
out["tunnelEnabled"] = config.tunnel_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["udpPort"] = static_cast<int>(config.udp_port);
|
||||
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["readTimeoutMs"] = static_cast<int>(config.tp_uart.read_timeout_ms);
|
||||
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));
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (data_type) {
|
||||
case GatewayKnxDaliDataType::kSwitch:
|
||||
@@ -522,14 +683,170 @@ std::string GatewayKnxGroupAddressString(uint16_t group_address) {
|
||||
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) {}
|
||||
|
||||
void GatewayKnxBridge::setConfig(const GatewayKnxConfig& config) { config_ = config; }
|
||||
void GatewayKnxBridge::setConfig(const GatewayKnxConfig& config) {
|
||||
config_ = config;
|
||||
rebuildEtsBindings();
|
||||
}
|
||||
|
||||
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> 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);
|
||||
for (uint8_t middle = 1; middle <= 4; ++middle) {
|
||||
const auto data_type = GatewayKnxDaliDataTypeForMiddleGroup(middle);
|
||||
@@ -542,6 +859,7 @@ std::vector<GatewayKnxDaliBinding> GatewayKnxBridge::describeDaliBindings() cons
|
||||
continue;
|
||||
}
|
||||
GatewayKnxDaliBinding binding;
|
||||
binding.mapping_mode = GatewayKnxMappingMode::kFormula;
|
||||
binding.main_group = config_.main_group;
|
||||
binding.middle_group = middle;
|
||||
binding.sub_group = sub;
|
||||
@@ -549,6 +867,10 @@ std::vector<GatewayKnxDaliBinding> GatewayKnxBridge::describeDaliBindings() cons
|
||||
binding.address = GatewayKnxGroupAddressString(binding.group_address);
|
||||
binding.data_type = data_type.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.name = TargetName(target.value()) + " - " + DataTypeName(data_type.value());
|
||||
bindings.push_back(std::move(binding));
|
||||
@@ -570,12 +892,29 @@ DaliBridgeResult GatewayKnxBridge::handleGroupWrite(uint16_t group_address, cons
|
||||
if (!config_.dali_router_enabled) {
|
||||
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 middle = static_cast<uint8_t>((group_address >> 8) & 0x07);
|
||||
const uint8_t sub = static_cast<uint8_t>(group_address & 0xff);
|
||||
if (main != config_.main_group) {
|
||||
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 target = GatewayKnxDaliTargetForSubgroup(sub);
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
GatewayKnxDaliDataType data_type,
|
||||
GatewayKnxDaliTarget target,
|
||||
|
||||
Reference in New Issue
Block a user