2b8ef31263
- Implemented handleUdpDatagram to process incoming UDP datagrams and route them to appropriate handlers based on service type. - Added methods for handling various KNXnet/IP requests including search, description, tunneling, device configuration, connection state, and disconnect requests. - Introduced TunnelClient management for handling multiple tunnel connections, including allocation, resetting, and pruning stale clients. - Implemented secure service handling with appropriate logging for unsupported secure sessions. - Enhanced logging for better traceability of incoming requests and responses. Signed-off-by: Tony <tonylu@tony-cloud.com>
1171 lines
42 KiB
C++
1171 lines
42 KiB
C++
#pragma once
|
|
|
|
#include "gateway_knx.hpp"
|
|
|
|
#include "dali_define.hpp"
|
|
#include "driver/gpio.h"
|
|
#include "driver/uart.h"
|
|
#include "esp_mac.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_log.h"
|
|
#include "lwip/inet.h"
|
|
#include "lwip/sockets.h"
|
|
#include "ets_device_runtime.h"
|
|
#include "gateway_knx_internal.h"
|
|
#include "soc/uart_periph.h"
|
|
#include "tpuart_uart_interface.h"
|
|
|
|
#include "knx/cemi_frame.h"
|
|
#include "knx/interface_object.h"
|
|
#include "knx/knx_ip_connect_request.h"
|
|
#include "knx/knx_ip_connect_response.h"
|
|
#include "knx/knx_ip_config_request.h"
|
|
#include "knx/knx_ip_description_request.h"
|
|
#include "knx/knx_ip_routing_indication.h"
|
|
#include "knx/knx_ip_disconnect_request.h"
|
|
#include "knx/knx_ip_disconnect_response.h"
|
|
#include "knx/knx_ip_search_request.h"
|
|
#include "knx/knx_ip_search_response.h"
|
|
#include "knx/knx_ip_description_response.h"
|
|
#include "knx/knx_ip_state_request.h"
|
|
#include "knx/knx_ip_state_response.h"
|
|
#include "knx/knx_ip_tunneling_ack.h"
|
|
#include "knx/knx_ip_tunneling_request.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cerrno>
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <initializer_list>
|
|
#include <set>
|
|
#include <utility>
|
|
#include <unistd.h>
|
|
|
|
#if defined(__GNUC__)
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
|
#endif
|
|
|
|
namespace gateway {
|
|
namespace {
|
|
|
|
constexpr const char* kTag = "gateway_knx";
|
|
constexpr uint16_t kServiceSearchRequest = 0x0201;
|
|
constexpr uint16_t kServiceSearchResponse = 0x0202;
|
|
constexpr uint16_t kServiceDescriptionRequest = 0x0203;
|
|
constexpr uint16_t kServiceDescriptionResponse = 0x0204;
|
|
constexpr uint16_t kServiceConnectRequest = 0x0205;
|
|
constexpr uint16_t kServiceConnectResponse = 0x0206;
|
|
constexpr uint16_t kServiceConnectionStateRequest = 0x0207;
|
|
constexpr uint16_t kServiceConnectionStateResponse = 0x0208;
|
|
constexpr uint16_t kServiceDisconnectRequest = 0x0209;
|
|
constexpr uint16_t kServiceDisconnectResponse = 0x020a;
|
|
constexpr uint16_t kServiceSearchRequestExt = 0x020b;
|
|
constexpr uint16_t kServiceSearchResponseExt = 0x020c;
|
|
constexpr uint16_t kServiceDeviceConfigurationRequest = 0x0310;
|
|
constexpr uint16_t kServiceDeviceConfigurationAck = 0x0311;
|
|
constexpr uint16_t kServiceTunnellingRequest = 0x0420;
|
|
constexpr uint16_t kServiceTunnellingAck = 0x0421;
|
|
constexpr uint16_t kServiceRoutingIndication = 0x0530;
|
|
constexpr uint16_t kServiceSecureWrapper = 0x0950;
|
|
constexpr uint16_t kServiceSecureSessionRequest = 0x0951;
|
|
constexpr uint16_t kServiceSecureSessionResponse = 0x0952;
|
|
constexpr uint16_t kServiceSecureSessionAuth = 0x0953;
|
|
constexpr uint16_t kServiceSecureSessionStatus = 0x0954;
|
|
constexpr uint16_t kServiceSecureGroupSync = 0x0955;
|
|
constexpr uint8_t kKnxNetIpHeaderSize = 0x06;
|
|
constexpr uint8_t kKnxNetIpVersion10 = 0x10;
|
|
constexpr uint8_t kKnxNoError = 0x00;
|
|
constexpr uint8_t kKnxErrorConnectionId = 0x21;
|
|
constexpr uint8_t kKnxErrorConnectionType = 0x22;
|
|
constexpr uint8_t kKnxErrorNoMoreConnections = 0x24;
|
|
constexpr uint8_t kKnxErrorTunnellingLayer = 0x29;
|
|
constexpr uint8_t kKnxErrorSequenceNumber = 0x04;
|
|
constexpr uint8_t kKnxSecureStatusAuthFailed = 0x01;
|
|
constexpr uint8_t kKnxSecureStatusUnauthenticated = 0x02;
|
|
constexpr uint8_t kKnxConnectionTypeDeviceManagement = 0x03;
|
|
constexpr uint8_t kKnxConnectionTypeTunnel = 0x04;
|
|
constexpr uint8_t kKnxTunnelLayerLink = 0x02;
|
|
constexpr uint8_t kKnxHpaiIpv4Udp = 0x01;
|
|
constexpr uint8_t kKnxHpaiIpv4Tcp = 0x02;
|
|
constexpr uint8_t kKnxDibDeviceInfo = 0x01;
|
|
constexpr uint8_t kKnxDibSupportedServices = 0x02;
|
|
constexpr uint8_t kKnxDibIpConfig = 0x03;
|
|
constexpr uint8_t kKnxDibCurrentIpConfig = 0x04;
|
|
constexpr uint8_t kKnxDibKnxAddresses = 0x05;
|
|
constexpr uint8_t kKnxDibTunnellingInfo = 0x07;
|
|
constexpr uint8_t kKnxDibExtendedDeviceInfo = 0x08;
|
|
constexpr uint8_t kKnxMediumTp1 = 0x02;
|
|
constexpr uint8_t kKnxMediumIp = 0x20;
|
|
constexpr uint8_t kKnxServiceFamilyCore = 0x02;
|
|
constexpr uint8_t kKnxServiceFamilyDeviceManagement = 0x03;
|
|
constexpr uint8_t kKnxServiceFamilyTunnelling = 0x04;
|
|
constexpr uint8_t kKnxServiceFamilyRouting = 0x05;
|
|
constexpr uint16_t kKnxIpOnlyDeviceDescriptor = 0x57b0;
|
|
constexpr uint16_t kKnxTpIpInterfaceDeviceDescriptor = 0x091a;
|
|
constexpr uint8_t kKnxIpAssignmentManual = 0x01;
|
|
constexpr uint8_t kKnxIpCapabilityManual = 0x01;
|
|
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 uint16_t kGwReg1AppKoScene = 5;
|
|
constexpr uint8_t kGwReg1KoSwitch = 0;
|
|
constexpr uint8_t kGwReg1KoDimmRelative = 2;
|
|
constexpr uint8_t kGwReg1KoDimmAbsolute = 3;
|
|
constexpr uint8_t kGwReg1KoColor = 6;
|
|
constexpr uint8_t kGwReg1KoSwitchState = 1;
|
|
constexpr uint8_t kGwReg1KoDimmState = 4;
|
|
constexpr uint8_t kReg1SceneTelegramNumberMask = 0x3f;
|
|
constexpr uint8_t kReg1SceneTelegramStoreMask = 0x80;
|
|
constexpr size_t kReg1SceneEntryCount = 64;
|
|
constexpr uint32_t kReg1SceneParamBlockOffset = 47;
|
|
constexpr uint32_t kReg1SceneParamBlockSize = 4;
|
|
constexpr uint8_t kReg1SceneTypeNone = 0;
|
|
constexpr uint8_t kReg1SceneTypeAddress = 1;
|
|
constexpr uint8_t kReg1SceneTypeGroup = 2;
|
|
constexpr uint8_t kReg1SceneTypeBroadcast = 3;
|
|
constexpr uint8_t kDaliCmdStepDownOff = 0x07;
|
|
constexpr uint8_t kDaliCmdOnStepUp = 0x08;
|
|
constexpr uint8_t kDaliCmdStopFade = 0xff;
|
|
constexpr uint8_t kReg1DaliFunctionObjectIndex = 160;
|
|
constexpr uint8_t kReg1DaliFunctionPropertyId = 1;
|
|
constexpr uint8_t kReg1FunctionType = 2;
|
|
constexpr uint8_t kReg1FunctionScan = 3;
|
|
constexpr uint8_t kReg1FunctionAssign = 4;
|
|
constexpr uint8_t kReg1FunctionEvgWrite = 10;
|
|
constexpr uint8_t kReg1FunctionEvgRead = 11;
|
|
constexpr uint8_t kReg1FunctionSetScene = 12;
|
|
constexpr uint8_t kReg1FunctionGetScene = 13;
|
|
constexpr uint8_t kReg1FunctionIdentify = 14;
|
|
constexpr uint8_t kReg1DeviceTypeDt8 = 8;
|
|
constexpr uint8_t kReg1ColorTypeTw = 1;
|
|
constexpr uint8_t kDaliDeviceTypeNone = 0xfe;
|
|
constexpr uint8_t kDaliDeviceTypeMultiple = 0xff;
|
|
constexpr uint32_t kCommissioningScanTaskStackSize = 8192;
|
|
constexpr uint16_t kGroupObjectTableObjectType = OT_GRP_OBJ_TABLE;
|
|
constexpr uint8_t kPidGoDiagnostics = 0x42;
|
|
constexpr uint8_t kGoDiagnosticsReservedByte = 0x00;
|
|
constexpr uint8_t kGoDiagnosticsGroupWriteService = 0x01;
|
|
constexpr uint8_t kGoDiagnosticsCompactPayloadFlag = 0x80;
|
|
|
|
struct DecodedGroupWrite {
|
|
uint16_t group_address{0};
|
|
std::vector<uint8_t> data;
|
|
};
|
|
|
|
struct DecodedGoDiagnosticsGroupWrite {
|
|
uint16_t group_address{0};
|
|
const uint8_t* payload{nullptr};
|
|
size_t payload_len{0};
|
|
};
|
|
|
|
struct KnxNetifInfo {
|
|
const char* key{nullptr};
|
|
esp_netif_t* netif{nullptr};
|
|
uint32_t address{0};
|
|
uint32_t netmask{0};
|
|
uint32_t gateway{0};
|
|
};
|
|
|
|
class SemaphoreGuard {
|
|
public:
|
|
explicit SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore_(semaphore) {
|
|
if (semaphore_ != nullptr) {
|
|
xSemaphoreTake(semaphore_, portMAX_DELAY);
|
|
locked_ = true;
|
|
}
|
|
}
|
|
|
|
~SemaphoreGuard() {
|
|
if (locked_) {
|
|
xSemaphoreGive(semaphore_);
|
|
}
|
|
}
|
|
|
|
private:
|
|
SemaphoreHandle_t semaphore_{nullptr};
|
|
bool locked_{false};
|
|
};
|
|
|
|
uint8_t DaliArcLevelToDpt5(uint8_t actual_level) {
|
|
return static_cast<uint8_t>(
|
|
std::clamp<int>(static_cast<int>(std::lround(actual_level * 255.0 / 254.0)), 0, 255));
|
|
}
|
|
|
|
uint16_t ReadBe16(const uint8_t* data) {
|
|
return static_cast<uint16_t>((static_cast<uint16_t>(data[0]) << 8) | data[1]);
|
|
}
|
|
|
|
std::string EspErrDetail(const std::string& message, esp_err_t err) {
|
|
return std::string(message) + ": " + esp_err_to_name(err) + "(" + std::to_string(err) + ")";
|
|
}
|
|
|
|
std::string ErrnoDetail(const std::string& message, int err) {
|
|
return std::string(message) + ": errno=" + std::to_string(err) + " (" + std::strerror(err) + ")";
|
|
}
|
|
|
|
bool ResolveUartIoPin(uart_port_t uart_port, int configured_pin, uint32_t pin_index,
|
|
int* resolved_pin) {
|
|
if (resolved_pin == nullptr) {
|
|
return false;
|
|
}
|
|
if (configured_pin >= 0) {
|
|
*resolved_pin = configured_pin;
|
|
return true;
|
|
}
|
|
if (uart_port < 0 || uart_port >= SOC_UART_NUM || pin_index >= SOC_UART_PINS_COUNT) {
|
|
*resolved_pin = UART_PIN_NO_CHANGE;
|
|
return false;
|
|
}
|
|
const int default_pin = uart_periph_signal[uart_port].pins[pin_index].default_gpio;
|
|
if (default_pin < 0) {
|
|
*resolved_pin = UART_PIN_NO_CHANGE;
|
|
return false;
|
|
}
|
|
*resolved_pin = default_pin;
|
|
return true;
|
|
}
|
|
|
|
std::string UartPinDescription(int configured_pin, int resolved_pin) {
|
|
if (configured_pin >= 0) {
|
|
return std::to_string(configured_pin);
|
|
}
|
|
if (resolved_pin >= 0) {
|
|
return std::to_string(resolved_pin) + " (default from -1)";
|
|
}
|
|
return "unrouted (-1 with no target default)";
|
|
}
|
|
|
|
std::string Ipv4String(uint32_t network_address) {
|
|
const uint32_t address = ntohl(network_address);
|
|
char buffer[16]{};
|
|
std::snprintf(buffer, sizeof(buffer), "%u.%u.%u.%u",
|
|
static_cast<unsigned>((address >> 24) & 0xff),
|
|
static_cast<unsigned>((address >> 16) & 0xff),
|
|
static_cast<unsigned>((address >> 8) & 0xff),
|
|
static_cast<unsigned>(address & 0xff));
|
|
return buffer;
|
|
}
|
|
|
|
std::string EndpointString(const sockaddr_in& endpoint) {
|
|
return Ipv4String(endpoint.sin_addr.s_addr) + ":" + std::to_string(ntohs(endpoint.sin_port));
|
|
}
|
|
|
|
bool EndpointEquals(const sockaddr_in& lhs, const sockaddr_in& rhs) {
|
|
return lhs.sin_family == rhs.sin_family && lhs.sin_addr.s_addr == rhs.sin_addr.s_addr &&
|
|
lhs.sin_port == rhs.sin_port;
|
|
}
|
|
|
|
void WriteIp(uint8_t* data, uint32_t network_address) {
|
|
const uint32_t address = ntohl(network_address);
|
|
data[0] = static_cast<uint8_t>((address >> 24) & 0xff);
|
|
data[1] = static_cast<uint8_t>((address >> 16) & 0xff);
|
|
data[2] = static_cast<uint8_t>((address >> 8) & 0xff);
|
|
data[3] = static_cast<uint8_t>(address & 0xff);
|
|
}
|
|
|
|
bool ReadBaseMac(uint8_t* data) {
|
|
if (data == nullptr) {
|
|
return false;
|
|
}
|
|
if (esp_efuse_mac_get_default(data) == ESP_OK) {
|
|
return true;
|
|
}
|
|
return esp_read_mac(data, ESP_MAC_WIFI_STA) == ESP_OK;
|
|
}
|
|
|
|
std::vector<KnxNetifInfo> ActiveKnxNetifs() {
|
|
std::vector<KnxNetifInfo> out;
|
|
constexpr std::array<const char*, 3> kIfKeys{"ETH_DEF", "WIFI_STA_DEF", "WIFI_AP_DEF"};
|
|
for (const char* key : kIfKeys) {
|
|
esp_netif_t* netif = esp_netif_get_handle_from_ifkey(key);
|
|
if (netif == nullptr || !esp_netif_is_netif_up(netif)) {
|
|
continue;
|
|
}
|
|
esp_netif_ip_info_t ip_info{};
|
|
if (esp_netif_get_ip_info(netif, &ip_info) != ESP_OK || ip_info.ip.addr == 0) {
|
|
continue;
|
|
}
|
|
out.push_back(KnxNetifInfo{key, netif, ip_info.ip.addr, ip_info.netmask.addr,
|
|
ip_info.gw.addr});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::optional<KnxNetifInfo> SelectKnxNetifForRemote(const sockaddr_in& remote) {
|
|
const auto netifs = ActiveKnxNetifs();
|
|
if (netifs.empty()) {
|
|
return std::nullopt;
|
|
}
|
|
const uint32_t remote_address = remote.sin_addr.s_addr;
|
|
for (const auto& netif : netifs) {
|
|
if ((remote_address & netif.netmask) == (netif.address & netif.netmask)) {
|
|
return netif;
|
|
}
|
|
}
|
|
return netifs.front();
|
|
}
|
|
|
|
sockaddr_in EndpointFromOpenKnxHpai(const IpHostProtocolAddressInformation& hpai,
|
|
const sockaddr_in& fallback) {
|
|
sockaddr_in out = fallback;
|
|
if (hpai.length() != LEN_IPHPAI ||
|
|
(hpai.code() != IPV4_UDP && hpai.code() != IPV4_TCP)) {
|
|
return out;
|
|
}
|
|
const uint32_t address = hpai.ipAddress();
|
|
const uint16_t port = hpai.ipPortNumber();
|
|
if (address != 0) {
|
|
out.sin_addr.s_addr = htonl(address);
|
|
}
|
|
if (port != 0) {
|
|
out.sin_port = htons(port);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
bool OpenKnxHpaiUsesUnsupportedProtocol(const IpHostProtocolAddressInformation& hpai,
|
|
bool allow_tcp) {
|
|
if (hpai.length() != LEN_IPHPAI) {
|
|
return false;
|
|
}
|
|
const HostProtocolCode protocol = hpai.code();
|
|
return protocol != IPV4_UDP && !(allow_tcp && protocol == IPV4_TCP);
|
|
}
|
|
|
|
void WriteBe16(uint8_t* data, uint16_t value) {
|
|
data[0] = static_cast<uint8_t>((value >> 8) & 0xff);
|
|
data[1] = static_cast<uint8_t>(value & 0xff);
|
|
}
|
|
|
|
std::optional<int> ObjectIntAny(const DaliValue::Object& object,
|
|
std::initializer_list<const char*> keys) {
|
|
for (const char* key : keys) {
|
|
if (const auto value = getObjectInt(object, key)) {
|
|
return value;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<bool> ObjectBoolAny(const DaliValue::Object& object,
|
|
std::initializer_list<const char*> keys) {
|
|
for (const char* key : keys) {
|
|
if (const auto value = getObjectBool(object, key)) {
|
|
return value;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> ObjectStringAny(const DaliValue::Object& object,
|
|
std::initializer_list<const char*> keys) {
|
|
for (const char* key : keys) {
|
|
if (const auto value = getObjectString(object, key)) {
|
|
return value;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
std::string TargetName(const GatewayKnxDaliTarget& target) {
|
|
switch (target.kind) {
|
|
case GatewayKnxDaliTargetKind::kBroadcast:
|
|
return "Broadcast";
|
|
case GatewayKnxDaliTargetKind::kShortAddress:
|
|
return "A" + std::to_string(target.address);
|
|
case GatewayKnxDaliTargetKind::kGroup:
|
|
return "Group " + std::to_string(target.address);
|
|
case GatewayKnxDaliTargetKind::kNone:
|
|
default:
|
|
return "Unmapped";
|
|
}
|
|
}
|
|
|
|
std::string DataTypeName(GatewayKnxDaliDataType data_type) {
|
|
switch (data_type) {
|
|
case GatewayKnxDaliDataType::kSwitch:
|
|
return "Switch";
|
|
case GatewayKnxDaliDataType::kBrightness:
|
|
return "Dimmer";
|
|
case GatewayKnxDaliDataType::kBrightnessRelative:
|
|
return "Dimmer Relative";
|
|
case GatewayKnxDaliDataType::kColorTemperature:
|
|
return "Color Temperature";
|
|
case GatewayKnxDaliDataType::kRgb:
|
|
return "RGB";
|
|
case GatewayKnxDaliDataType::kScene:
|
|
return "Scene";
|
|
case GatewayKnxDaliDataType::kUnknown:
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
const char* DataTypeDpt(GatewayKnxDaliDataType data_type) {
|
|
switch (data_type) {
|
|
case GatewayKnxDaliDataType::kSwitch:
|
|
return "DPST-1-1";
|
|
case GatewayKnxDaliDataType::kBrightness:
|
|
return "DPST-5-1";
|
|
case GatewayKnxDaliDataType::kBrightnessRelative:
|
|
return "DPST-3-7";
|
|
case GatewayKnxDaliDataType::kColorTemperature:
|
|
return "DPST-7-600";
|
|
case GatewayKnxDaliDataType::kRgb:
|
|
return "DPST-232-600";
|
|
case GatewayKnxDaliDataType::kScene:
|
|
return "DPST-17-1";
|
|
case GatewayKnxDaliDataType::kUnknown:
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
std::optional<DecodedGroupWrite> DecodeOpenKnxGroupWrite(const uint8_t* data, size_t len) {
|
|
if (data == nullptr || len < 10) {
|
|
return std::nullopt;
|
|
}
|
|
std::vector<uint8_t> frame_data(data, data + len);
|
|
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
|
if (!frame.valid()) {
|
|
return std::nullopt;
|
|
}
|
|
const MessageCode message_code = frame.messageCode();
|
|
if (message_code != L_data_req && message_code != L_data_ind && message_code != L_data_con) {
|
|
return std::nullopt;
|
|
}
|
|
if (frame.addressType() != GroupAddress) {
|
|
return std::nullopt;
|
|
}
|
|
const TpduType tpdu_type = frame.tpdu().type();
|
|
if (tpdu_type != DataGroup && tpdu_type != DataBroadcast) {
|
|
return std::nullopt;
|
|
}
|
|
if (frame.apdu().type() != GroupValueWrite) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
DecodedGroupWrite out;
|
|
out.group_address = frame.destinationAddress();
|
|
const uint8_t apdu_length = frame.apdu().length();
|
|
const uint8_t* apdu_data = frame.apdu().data();
|
|
if (apdu_data == nullptr || apdu_length == 0) {
|
|
return std::nullopt;
|
|
}
|
|
if (apdu_length == 1U) {
|
|
out.data.push_back(apdu_data[0] & 0x3f);
|
|
} else {
|
|
out.data.assign(apdu_data + 1, apdu_data + apdu_length);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::optional<DecodedGoDiagnosticsGroupWrite> DecodeGoDiagnosticsGroupWrite(
|
|
const uint8_t* data, size_t len) {
|
|
if (data == nullptr || len < 5) {
|
|
return std::nullopt;
|
|
}
|
|
if (data[0] != kGoDiagnosticsReservedByte || data[1] != kGoDiagnosticsGroupWriteService) {
|
|
return std::nullopt;
|
|
}
|
|
const uint8_t encoded_length = data[2];
|
|
size_t payload_len = 0;
|
|
if ((encoded_length & kGoDiagnosticsCompactPayloadFlag) != 0) {
|
|
payload_len = static_cast<size_t>(encoded_length & ~kGoDiagnosticsCompactPayloadFlag);
|
|
if (payload_len == 0 || len != payload_len + 5) {
|
|
return std::nullopt;
|
|
}
|
|
} else {
|
|
const size_t expanded_length = encoded_length;
|
|
if (expanded_length < 2 || len != expanded_length + 3) {
|
|
return std::nullopt;
|
|
}
|
|
payload_len = expanded_length - 2;
|
|
}
|
|
|
|
DecodedGoDiagnosticsGroupWrite out;
|
|
out.group_address = ReadBe16(data + 3);
|
|
out.payload = data + 5;
|
|
out.payload_len = payload_len;
|
|
return out;
|
|
}
|
|
|
|
bool IsOpenKnxGroupValueWrite(const uint8_t* data, size_t len) {
|
|
return DecodeOpenKnxGroupWrite(data, len).has_value();
|
|
}
|
|
|
|
uint8_t Reg1PercentToArc(uint8_t value) {
|
|
if (value == 0 || value == 0xff) {
|
|
return value;
|
|
}
|
|
const double arc = ((253.0 / 3.0) * (std::log10(static_cast<double>(value)) + 1.0)) + 1.0;
|
|
return static_cast<uint8_t>(std::clamp(static_cast<int>(arc + 0.5), 0, 254));
|
|
}
|
|
|
|
uint8_t Reg1ArcToPercent(uint8_t value) {
|
|
if (value == 0 || value == 0xff) {
|
|
return value;
|
|
}
|
|
const double percent = std::pow(10.0, ((static_cast<double>(value) - 1.0) / (253.0 / 3.0)) - 1.0);
|
|
return static_cast<uint8_t>(std::clamp(static_cast<int>(percent + 0.5), 0, 100));
|
|
}
|
|
|
|
GatewayKnxDaliTarget Reg1SceneTarget(uint8_t encoded_target) {
|
|
if ((encoded_target & 0x80) != 0) {
|
|
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
|
|
static_cast<int>(encoded_target & 0x0f)};
|
|
}
|
|
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
|
|
static_cast<int>(encoded_target & 0x3f)};
|
|
}
|
|
|
|
DaliBridgeRequest FunctionRequest(const char* sequence, BridgeOperation operation) {
|
|
DaliBridgeRequest request;
|
|
request.sequence = sequence == nullptr ? "knx-function-property" : sequence;
|
|
request.operation = operation;
|
|
return request;
|
|
}
|
|
|
|
void ApplyTargetToRequest(const GatewayKnxDaliTarget& target, DaliBridgeRequest* request) {
|
|
if (request == nullptr) {
|
|
return;
|
|
}
|
|
switch (target.kind) {
|
|
case GatewayKnxDaliTargetKind::kBroadcast:
|
|
request->metadata["broadcast"] = true;
|
|
break;
|
|
case GatewayKnxDaliTargetKind::kShortAddress:
|
|
request->shortAddress = target.address;
|
|
break;
|
|
case GatewayKnxDaliTargetKind::kGroup:
|
|
request->metadata["group"] = target.address;
|
|
break;
|
|
case GatewayKnxDaliTargetKind::kNone:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
DaliBridgeResult ExecuteRaw(DaliBridgeEngine& engine, BridgeOperation operation, uint8_t addr,
|
|
uint8_t cmd, const char* sequence) {
|
|
DaliBridgeRequest request = FunctionRequest(sequence, operation);
|
|
request.rawAddress = addr;
|
|
request.rawCommand = cmd;
|
|
return engine.execute(request);
|
|
}
|
|
|
|
std::optional<int> QueryShort(DaliBridgeEngine& engine, uint8_t short_address, uint8_t command,
|
|
const char* sequence) {
|
|
const auto result = ExecuteRaw(engine, BridgeOperation::query, DaliComm::toCmdAddr(short_address),
|
|
command, sequence);
|
|
if (!result.ok || !result.data.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
return result.data.value();
|
|
}
|
|
|
|
bool SendRaw(DaliBridgeEngine& engine, uint8_t addr, uint8_t cmd, const char* sequence) {
|
|
return ExecuteRaw(engine, BridgeOperation::send, addr, cmd, sequence).ok;
|
|
}
|
|
|
|
bool SendRawExt(DaliBridgeEngine& engine, uint8_t addr, uint8_t cmd, const char* sequence) {
|
|
return ExecuteRaw(engine, BridgeOperation::sendExt, addr, cmd, sequence).ok;
|
|
}
|
|
|
|
std::optional<int> ExecuteRawQuery(DaliBridgeEngine& engine, uint8_t addr, uint8_t cmd,
|
|
const char* sequence) {
|
|
const auto result = ExecuteRaw(engine, BridgeOperation::query, addr, cmd, sequence);
|
|
if (!result.ok || !result.data.has_value()) {
|
|
ESP_LOGD(kTag, "DALI query failed seq=%s addr=0x%02x cmd=0x%02x error=%s",
|
|
sequence == nullptr ? "" : sequence, static_cast<unsigned>(addr),
|
|
static_cast<unsigned>(cmd),
|
|
result.error.empty() ? "no response" : result.error.c_str());
|
|
return std::nullopt;
|
|
}
|
|
ESP_LOGD(kTag, "DALI query result seq=%s addr=0x%02x cmd=0x%02x data=0x%02x",
|
|
sequence == nullptr ? "" : sequence, static_cast<unsigned>(addr),
|
|
static_cast<unsigned>(cmd),
|
|
static_cast<unsigned>(result.data.value() & 0xff));
|
|
return result.data.value();
|
|
}
|
|
|
|
bool DaliQueryResultHasStatus(const DaliBridgeResult& result, const char* status) {
|
|
if (status == nullptr) return false;
|
|
const auto it = result.metadata.find("queryStatus");
|
|
if (it == result.metadata.end()) return false;
|
|
const auto value = it->second.asString();
|
|
return value.has_value() && value.value() == status;
|
|
}
|
|
|
|
std::optional<uint8_t> RawCommandAddressForTarget(const GatewayKnxDaliTarget& target) {
|
|
switch (target.kind) {
|
|
case GatewayKnxDaliTargetKind::kBroadcast:
|
|
return static_cast<uint8_t>(0xff);
|
|
case GatewayKnxDaliTargetKind::kShortAddress:
|
|
if (target.address < 0 || target.address > 63) {
|
|
return std::nullopt;
|
|
}
|
|
return DaliComm::toCmdAddr(target.address);
|
|
case GatewayKnxDaliTargetKind::kGroup:
|
|
if (target.address < 0 || target.address > 15) {
|
|
return std::nullopt;
|
|
}
|
|
return static_cast<uint8_t>(0x80 + (target.address * 2) + 1);
|
|
case GatewayKnxDaliTargetKind::kNone:
|
|
default:
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
DaliBridgeResult SendRawForTarget(DaliBridgeEngine& engine, uint16_t group_address,
|
|
const GatewayKnxDaliTarget& target, uint8_t cmd) {
|
|
const auto raw_addr = RawCommandAddressForTarget(target);
|
|
if (!raw_addr.has_value()) {
|
|
DaliBridgeResult result;
|
|
result.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
|
|
result.error = "invalid DALI target for raw command";
|
|
return result;
|
|
}
|
|
DaliBridgeRequest request;
|
|
request.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
|
|
request.operation = BridgeOperation::send;
|
|
request.rawAddress = raw_addr.value();
|
|
request.rawCommand = cmd;
|
|
request.metadata["sourceProtocol"] = "knx";
|
|
request.metadata["knxGroupAddress"] = GatewayKnxGroupAddressString(group_address);
|
|
request.metadata["daliTarget"] = TargetName(target);
|
|
return engine.execute(request);
|
|
}
|
|
|
|
DaliBridgeResult SendRawExtForTarget(DaliBridgeEngine& engine, uint16_t group_address,
|
|
const GatewayKnxDaliTarget& target, uint8_t cmd) {
|
|
const auto raw_addr = RawCommandAddressForTarget(target);
|
|
if (!raw_addr.has_value()) {
|
|
DaliBridgeResult result;
|
|
result.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
|
|
result.error = "invalid DALI target for raw command";
|
|
return result;
|
|
}
|
|
DaliBridgeRequest request;
|
|
request.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
|
|
request.operation = BridgeOperation::sendExt;
|
|
request.rawAddress = raw_addr.value();
|
|
request.rawCommand = cmd;
|
|
request.metadata["sourceProtocol"] = "knx";
|
|
request.metadata["knxGroupAddress"] = GatewayKnxGroupAddressString(group_address);
|
|
request.metadata["daliTarget"] = TargetName(target);
|
|
return engine.execute(request);
|
|
}
|
|
|
|
std::optional<int> MetadataInt(const DaliBridgeResult& result, const std::string& key) {
|
|
return getObjectInt(result.metadata, key);
|
|
}
|
|
|
|
uint8_t GoDiagnosticsReturnCode(const DaliBridgeResult& result) {
|
|
if (result.ok) {
|
|
return ReturnCodes::Success;
|
|
}
|
|
if (getObjectBool(result.metadata, "ignored").value_or(false)) {
|
|
return ReturnCodes::TemporarilyNotAvailable;
|
|
}
|
|
|
|
const std::string& error = result.error;
|
|
if (error.find("unmapped") != std::string::npos ||
|
|
error.find("does not match gateway config") != std::string::npos) {
|
|
return ReturnCodes::AddressVoid;
|
|
}
|
|
if (error.find("disabled") != std::string::npos ||
|
|
error.find("commissioning-only") != std::string::npos ||
|
|
error.find("not configured") != std::string::npos) {
|
|
return ReturnCodes::TemporarilyNotAvailable;
|
|
}
|
|
return ReturnCodes::GenericError;
|
|
}
|
|
|
|
std::string HexBytes(const uint8_t* data, size_t len) {
|
|
if (data == nullptr || len == 0) {
|
|
return {};
|
|
}
|
|
std::string out;
|
|
out.reserve(len * 3);
|
|
char buffer[4] = {0};
|
|
for (size_t index = 0; index < len; ++index) {
|
|
std::snprintf(buffer, sizeof(buffer), "%02X", data[index]);
|
|
out += buffer;
|
|
if (index + 1 < len) {
|
|
out.push_back(' ');
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::optional<MessageCode> CemiMessageCode(const uint8_t* data, size_t len) {
|
|
if (data == nullptr || len == 0) {
|
|
return std::nullopt;
|
|
}
|
|
return static_cast<MessageCode>(data[0]);
|
|
}
|
|
|
|
uint16_t KnxIpServiceForCemi(const uint8_t* data, size_t len, uint16_t fallback_service) {
|
|
const auto message_code = CemiMessageCode(data, len);
|
|
if (!message_code.has_value()) {
|
|
return fallback_service;
|
|
}
|
|
switch (message_code.value()) {
|
|
case L_data_req:
|
|
case L_data_con:
|
|
case L_data_ind:
|
|
return kServiceTunnellingRequest;
|
|
default:
|
|
return kServiceDeviceConfigurationRequest;
|
|
}
|
|
}
|
|
|
|
bool MatchesOpenKnxLocalIndividualAddress(const CemiFrame& frame,
|
|
const openknx::EtsDeviceRuntime& ets_device) {
|
|
if (frame.addressType() != IndividualAddress) {
|
|
return false;
|
|
}
|
|
const uint16_t dest = frame.destinationAddress();
|
|
const uint16_t own_address = ets_device.individualAddress();
|
|
const uint16_t client_address = ets_device.tunnelClientAddress();
|
|
const bool commissioning = !ets_device.configured() || ets_device.programmingMode();
|
|
return dest == own_address || dest == client_address || (commissioning && dest == 0xffff);
|
|
}
|
|
|
|
bool BuildLocalRoutingTunnelFrame(const uint8_t* data, size_t len,
|
|
std::vector<uint8_t>* local_frame) {
|
|
if (data == nullptr || local_frame == nullptr || len < 2) {
|
|
return false;
|
|
}
|
|
std::vector<uint8_t> frame_data(data, data + len);
|
|
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
|
if (!frame.valid()) {
|
|
return false;
|
|
}
|
|
switch (frame.messageCode()) {
|
|
case L_data_req:
|
|
*local_frame = std::move(frame_data);
|
|
return true;
|
|
case L_data_ind:
|
|
frame.messageCode(L_data_req);
|
|
*local_frame = std::move(frame_data);
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsLocalRoutingEchoIndication(const uint8_t* response, size_t response_len,
|
|
const uint8_t* original_request,
|
|
size_t original_request_len) {
|
|
if (response == nullptr || original_request == nullptr || response_len < 2 ||
|
|
original_request_len < 2) {
|
|
return false;
|
|
}
|
|
std::vector<uint8_t> response_data(response, response + response_len);
|
|
std::vector<uint8_t> original_data(original_request,
|
|
original_request + original_request_len);
|
|
CemiFrame response_frame(response_data.data(), static_cast<uint16_t>(response_data.size()));
|
|
CemiFrame original_frame(original_data.data(), static_cast<uint16_t>(original_data.size()));
|
|
if (!response_frame.valid() || !original_frame.valid() ||
|
|
response_frame.messageCode() != L_data_ind ||
|
|
original_frame.addressType() != IndividualAddress ||
|
|
response_frame.addressType() != original_frame.addressType() ||
|
|
response_frame.sourceAddress() != original_frame.sourceAddress() ||
|
|
response_frame.destinationAddress() != original_frame.destinationAddress() ||
|
|
response_frame.tpdu().type() != original_frame.tpdu().type() ||
|
|
response_frame.apdu().type() != original_frame.apdu().type()) {
|
|
return false;
|
|
}
|
|
const uint8_t response_apdu_length = response_frame.apdu().length();
|
|
const uint8_t original_apdu_length = original_frame.apdu().length();
|
|
if (response_apdu_length != original_apdu_length) {
|
|
return false;
|
|
}
|
|
const uint8_t* response_apdu = response_frame.apdu().data();
|
|
const uint8_t* original_apdu = original_frame.apdu().data();
|
|
if (response_apdu_length == 0) {
|
|
return true;
|
|
}
|
|
if (response_apdu == nullptr || original_apdu == nullptr) {
|
|
return false;
|
|
}
|
|
return std::memcmp(response_apdu, original_apdu, response_apdu_length) == 0;
|
|
}
|
|
|
|
bool BuildTunnelConfirmationFrame(const uint8_t* data, size_t len,
|
|
std::vector<uint8_t>* confirmation) {
|
|
if (data == nullptr || confirmation == nullptr || len < 2) {
|
|
return false;
|
|
}
|
|
std::vector<uint8_t> frame_data(data, data + len);
|
|
CemiFrame frame(frame_data.data(), static_cast<uint16_t>(frame_data.size()));
|
|
if (!frame.valid() || frame.messageCode() != L_data_req) {
|
|
return false;
|
|
}
|
|
frame.messageCode(L_data_con);
|
|
frame.confirm(ConfirmNoError);
|
|
*confirmation = std::move(frame_data);
|
|
return true;
|
|
}
|
|
|
|
DaliBridgeRequest RequestForTarget(uint16_t group_address,
|
|
const GatewayKnxDaliTarget& target,
|
|
BridgeOperation operation) {
|
|
DaliBridgeRequest request;
|
|
request.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
|
|
request.operation = operation;
|
|
switch (target.kind) {
|
|
case GatewayKnxDaliTargetKind::kBroadcast:
|
|
request.metadata["broadcast"] = true;
|
|
break;
|
|
case GatewayKnxDaliTargetKind::kShortAddress:
|
|
request.shortAddress = target.address;
|
|
break;
|
|
case GatewayKnxDaliTargetKind::kGroup:
|
|
request.metadata["group"] = target.address;
|
|
break;
|
|
case GatewayKnxDaliTargetKind::kNone:
|
|
default:
|
|
break;
|
|
}
|
|
request.metadata["sourceProtocol"] = "knx";
|
|
request.metadata["knxGroupAddress"] = GatewayKnxGroupAddressString(group_address);
|
|
return request;
|
|
}
|
|
|
|
DaliBridgeResult ErrorResult(uint16_t group_address, const char* message) {
|
|
DaliBridgeResult result;
|
|
result.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
|
|
result.error = message == nullptr ? "KNX error" : message;
|
|
return result;
|
|
}
|
|
|
|
DaliBridgeResult IgnoredResult(uint16_t group_address, uint16_t group_object_number,
|
|
const char* reason) {
|
|
DaliBridgeResult result;
|
|
result.sequence = "knx-" + GatewayKnxGroupAddressString(group_address);
|
|
result.ok = true;
|
|
result.metadata["ignored"] = true;
|
|
result.metadata["groupObjectNumber"] = static_cast<int>(group_object_number);
|
|
result.metadata["reason"] = reason == nullptr ? "ignored" : reason;
|
|
return result;
|
|
}
|
|
|
|
bool SetSearchAddress(DaliBridgeEngine& engine, uint32_t search_address, const char* sequence) {
|
|
return SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRH,
|
|
static_cast<uint8_t>((search_address >> 16) & 0xff), sequence) &&
|
|
SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRM,
|
|
static_cast<uint8_t>((search_address >> 8) & 0xff), sequence) &&
|
|
SendRaw(engine, DALI_CMD_SPECIAL_SEARCHADDRL,
|
|
static_cast<uint8_t>(search_address & 0xff), sequence);
|
|
}
|
|
|
|
std::optional<bool> CompareSelectedSearchAddress(DaliBridgeEngine& engine, uint32_t search_address,
|
|
const char* sequence) {
|
|
if (!SetSearchAddress(engine, search_address, sequence)) {
|
|
return std::nullopt;
|
|
}
|
|
const auto result = ExecuteRaw(engine, BridgeOperation::query, DALI_CMD_SPECIAL_COMPARE,
|
|
DALI_CMD_OFF, sequence);
|
|
if (result.ok && result.data.has_value()) {
|
|
return true;
|
|
}
|
|
if (DaliQueryResultHasStatus(result, "noResponse") ||
|
|
DaliQueryResultHasStatus(result, "timeout")) {
|
|
ESP_LOGD(kTag, "DALI compare no match seq=%s search=0x%06x status=%s",
|
|
sequence == nullptr ? "" : sequence, static_cast<unsigned>(search_address),
|
|
DaliQueryResultHasStatus(result, "timeout") ? "timeout" : "noResponse");
|
|
return false;
|
|
}
|
|
if (!result.error.empty()) {
|
|
ESP_LOGW(kTag, "DALI compare failed seq=%s search=0x%06x error=%s",
|
|
sequence == nullptr ? "" : sequence, static_cast<unsigned>(search_address),
|
|
result.error.c_str());
|
|
}
|
|
if (!result.ok || !result.data.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::optional<uint32_t> FindLowestSelectedRandomAddress(DaliBridgeEngine& engine) {
|
|
const auto any = CompareSelectedSearchAddress(engine, 0x00ffffffu,
|
|
"knx-function-scan-compare-any");
|
|
if (!any.has_value() || !any.value()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
uint32_t low = 0;
|
|
uint32_t high = 0x00ffffffu;
|
|
while (low < high) {
|
|
const uint32_t mid = low + ((high - low) / 2);
|
|
const auto match = CompareSelectedSearchAddress(engine, mid,
|
|
"knx-function-scan-compare-binary");
|
|
if (!match.has_value()) {
|
|
return std::nullopt;
|
|
}
|
|
if (match.value()) {
|
|
high = mid;
|
|
} else {
|
|
low = mid + 1;
|
|
}
|
|
}
|
|
if (!SetSearchAddress(engine, low, "knx-function-scan-compare-final")) {
|
|
return std::nullopt;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
std::optional<uint8_t> QuerySelectedShortAddress(DaliBridgeEngine& engine) {
|
|
const auto raw = ExecuteRawQuery(engine, DALI_CMD_SPECIAL_QUERY_SHORT_ADDRESS, DALI_CMD_OFF,
|
|
"knx-function-scan-query-short");
|
|
if (!raw.has_value() || raw.value() < 0 || raw.value() > 0xff || raw.value() == 0xff) {
|
|
return std::nullopt;
|
|
}
|
|
return static_cast<uint8_t>((raw.value() >> 1) & 0x3f);
|
|
}
|
|
|
|
bool VerifyShortAddress(DaliBridgeEngine& engine, uint8_t short_address) {
|
|
const auto raw = ExecuteRawQuery(engine, DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS,
|
|
DaliComm::toCmdAddr(short_address),
|
|
"knx-function-scan-verify-short");
|
|
return raw.has_value() && raw.value() == 0xff;
|
|
}
|
|
|
|
std::array<bool, 64> QueryUsedShortAddresses(DaliBridgeEngine& engine) {
|
|
std::array<bool, 64> used{};
|
|
for (int short_address = 0; short_address < static_cast<int>(used.size()); ++short_address) {
|
|
used[short_address] = QueryShort(engine, static_cast<uint8_t>(short_address),
|
|
DALI_CMD_QUERY_STATUS,
|
|
"knx-function-scan-query-used")
|
|
.has_value();
|
|
}
|
|
return used;
|
|
}
|
|
|
|
std::optional<uint8_t> NextFreeShortAddress(const std::array<bool, 64>& used) {
|
|
for (size_t index = 0; index < used.size(); ++index) {
|
|
if (!used[index]) {
|
|
return static_cast<uint8_t>(index);
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
uint8_t Reg1SceneTypeForEntry(const openknx::EtsDeviceRuntime& runtime, size_t index) {
|
|
const uint32_t addr = kReg1SceneParamBlockOffset +
|
|
(kReg1SceneParamBlockSize * static_cast<uint32_t>(index));
|
|
return static_cast<uint8_t>((runtime.paramByte(addr) >> 6) & 0x03);
|
|
}
|
|
|
|
bool Reg1SceneSaveAllowedForEntry(const openknx::EtsDeviceRuntime& runtime, size_t index) {
|
|
const uint32_t addr = kReg1SceneParamBlockOffset +
|
|
(kReg1SceneParamBlockSize * static_cast<uint32_t>(index));
|
|
return runtime.paramBit(addr, 2);
|
|
}
|
|
|
|
uint8_t Reg1KnxSceneNumberForEntry(const openknx::EtsDeviceRuntime& runtime, size_t index) {
|
|
const uint32_t addr = kReg1SceneParamBlockOffset +
|
|
(kReg1SceneParamBlockSize * static_cast<uint32_t>(index)) + 1;
|
|
return static_cast<uint8_t>((runtime.paramByte(addr) >> 1) & 0x7f);
|
|
}
|
|
|
|
uint8_t Reg1DaliSceneNumberForEntry(const openknx::EtsDeviceRuntime& runtime, size_t index) {
|
|
const uint32_t addr = kReg1SceneParamBlockOffset +
|
|
(kReg1SceneParamBlockSize * static_cast<uint32_t>(index));
|
|
return static_cast<uint8_t>((runtime.paramByte(addr) >> 1) & 0x0f);
|
|
}
|
|
|
|
std::optional<GatewayKnxDaliTarget> Reg1SceneTargetForEntry(
|
|
const openknx::EtsDeviceRuntime& runtime, size_t index) {
|
|
const uint32_t base = kReg1SceneParamBlockOffset +
|
|
(kReg1SceneParamBlockSize * static_cast<uint32_t>(index));
|
|
switch (Reg1SceneTypeForEntry(runtime, index)) {
|
|
case kReg1SceneTypeAddress:
|
|
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kShortAddress,
|
|
static_cast<int>((runtime.paramByte(base + 2) >> 2) & 0x3f)};
|
|
case kReg1SceneTypeGroup:
|
|
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kGroup,
|
|
static_cast<int>((runtime.paramByte(base + 3) >> 4) & 0x0f)};
|
|
case kReg1SceneTypeBroadcast:
|
|
return GatewayKnxDaliTarget{GatewayKnxDaliTargetKind::kBroadcast, 127};
|
|
case kReg1SceneTypeNone:
|
|
default:
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
bool SendAll(int sock, const uint8_t* data, size_t len, const sockaddr_in& remote) {
|
|
return sendto(sock, data, len, 0, reinterpret_cast<const sockaddr*>(&remote),
|
|
sizeof(remote)) == static_cast<int>(len);
|
|
}
|
|
|
|
bool SendStream(int sock, const uint8_t* data, size_t len) {
|
|
size_t sent = 0;
|
|
while (sent < len) {
|
|
const int written = send(sock, data + sent, len - sent, 0);
|
|
if (written <= 0) {
|
|
return false;
|
|
}
|
|
sent += static_cast<size_t>(written);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<uint8_t> OpenKnxIpPacket(uint16_t service, const std::vector<uint8_t>& body) {
|
|
KnxIpFrame frame(static_cast<uint16_t>(LEN_KNXIP_HEADER + body.size()));
|
|
frame.serviceTypeIdentifier(service);
|
|
std::copy(body.begin(), body.end(), frame.data() + LEN_KNXIP_HEADER);
|
|
return std::vector<uint8_t>(frame.data(), frame.data() + frame.totalLength());
|
|
}
|
|
|
|
bool ParseKnxNetIpHeader(const uint8_t* data, size_t len, uint16_t* service,
|
|
uint16_t* total_len) {
|
|
if (data == nullptr || len < 6 || data[0] != kKnxNetIpHeaderSize ||
|
|
data[1] != kKnxNetIpVersion10) {
|
|
return false;
|
|
}
|
|
*service = ReadBe16(data + 2);
|
|
*total_len = ReadBe16(data + 4);
|
|
return *total_len >= 6 && *total_len <= len;
|
|
}
|
|
|
|
bool IsKnxNetIpSecureService(uint16_t service) {
|
|
switch (service) {
|
|
case kServiceSecureWrapper:
|
|
case kServiceSecureSessionRequest:
|
|
case kServiceSecureSessionResponse:
|
|
case kServiceSecureSessionAuth:
|
|
case kServiceSecureSessionStatus:
|
|
case kServiceSecureGroupSync:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace gateway
|
|
|
|
#if defined(__GNUC__)
|
|
#pragma GCC diagnostic pop
|
|
#endif
|