Files
gateway/components/gateway_knx/include/gateway_knx.hpp
T

372 lines
15 KiB
C++

#pragma once
#include "bridge.hpp"
#include "model_value.hpp"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "lwip/sockets.h"
#include <atomic>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
namespace gateway {
namespace openknx {
class EtsDeviceRuntime;
}
constexpr uint16_t kGatewayKnxDefaultUdpPort = 3671;
constexpr const char* kGatewayKnxDefaultMulticastAddress = "224.0.23.12";
constexpr uint32_t kGatewayKnxDefaultTpBaudrate = 19200;
struct GatewayKnxTpUartConfig {
int uart_port{1};
int tx_pin{-1};
int rx_pin{-1};
uint32_t baudrate{kGatewayKnxDefaultTpBaudrate};
size_t rx_buffer_size{1024};
size_t tx_buffer_size{1024};
uint32_t read_timeout_ms{20};
bool nine_bit_mode{true};
};
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 ip_interface_individual_address{0xff01};
uint16_t individual_address{0xfffe};
int programming_button_gpio{-1};
bool programming_button_active_low{true};
int programming_led_gpio{-1};
bool programming_led_active_high{true};
std::vector<GatewayKnxEtsAssociation> ets_associations;
GatewayKnxTpUartConfig tp_uart;
};
enum class GatewayKnxDaliDataType : uint8_t {
kUnknown = 0,
kSwitch = 1,
kBrightness = 2,
kColorTemperature = 3,
kRgb = 4,
};
enum class GatewayKnxDaliTargetKind : uint8_t {
kNone = 0,
kBroadcast = 1,
kShortAddress = 2,
kGroup = 3,
};
struct GatewayKnxDaliTarget {
GatewayKnxDaliTargetKind kind{GatewayKnxDaliTargetKind::kNone};
int address{-1};
};
struct GatewayKnxDaliBinding {
uint16_t group_address{0};
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;
};
struct GatewayKnxCommissioningBallast {
uint8_t high{0};
uint8_t middle{0};
uint8_t low{0};
uint8_t short_address{0xff};
};
std::optional<GatewayKnxConfig> GatewayKnxConfigFromValue(const DaliValue* value);
DaliValue GatewayKnxConfigToValue(const GatewayKnxConfig& config);
bool GatewayKnxConfigUsesTpUart(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(
uint8_t middle_group);
std::optional<GatewayKnxDaliTarget> GatewayKnxDaliTargetForSubgroup(uint8_t sub_group);
uint16_t GatewayKnxGroupAddress(uint8_t main_group, uint8_t middle_group,
uint8_t sub_group);
std::string GatewayKnxGroupAddressString(uint16_t group_address);
class GatewayKnxBridge {
public:
explicit GatewayKnxBridge(DaliBridgeEngine& engine);
void setConfig(const GatewayKnxConfig& config);
const GatewayKnxConfig& config() const;
size_t etsBindingCount() const;
std::vector<GatewayKnxDaliBinding> describeDaliBindings() const;
bool matchesCemiFrame(const uint8_t* data, size_t len) const;
bool matchesGroupAddress(uint16_t group_address) const;
DaliBridgeResult handleCemiFrame(const uint8_t* data, size_t len);
DaliBridgeResult handleGroupWrite(uint16_t group_address, const uint8_t* data,
size_t len);
bool handleFunctionPropertyCommand(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleFunctionPropertyState(uint8_t object_index, uint8_t property_id,
const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
private:
DaliBridgeResult executeForDecodedWrite(uint16_t group_address,
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();
bool handleReg1TypeCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1ScanCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1AssignCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1EvgWriteCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1EvgReadCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1SetSceneCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1GetSceneCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1IdentifyCommand(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1ScanState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1AssignState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
bool handleReg1FoundEvgsState(const uint8_t* data, size_t len,
std::vector<uint8_t>* response);
DaliBridgeEngine& engine_;
GatewayKnxConfig config_;
std::map<uint16_t, std::vector<GatewayKnxDaliBinding>> ets_bindings_by_group_address_;
bool commissioning_scan_done_{true};
bool commissioning_assign_done_{true};
std::vector<GatewayKnxCommissioningBallast> commissioning_found_ballasts_;
};
class GatewayKnxTpIpRouter {
public:
using CemiFrameHandler = std::function<DaliBridgeResult(const uint8_t* data, size_t len)>;
using GroupWriteHandler = std::function<DaliBridgeResult(uint16_t group_address,
const uint8_t* data,
size_t len)>;
GatewayKnxTpIpRouter(GatewayKnxBridge& bridge, CemiFrameHandler handler,
std::string openknx_namespace = "openknx");
~GatewayKnxTpIpRouter();
void setConfig(const GatewayKnxConfig& config);
void setCommissioningOnly(bool enabled);
void setGroupWriteHandler(GroupWriteHandler handler);
const GatewayKnxConfig& config() const;
bool tpUartOnline() const;
bool programmingMode();
esp_err_t setProgrammingMode(bool enabled);
esp_err_t toggleProgrammingMode();
esp_err_t start(uint32_t task_stack_size, UBaseType_t task_priority);
esp_err_t stop();
bool started() const;
const std::string& lastError() const;
bool publishDaliStatus(const GatewayKnxDaliTarget& target, uint8_t actual_level);
private:
static constexpr size_t kMaxTunnelClients = 16;
static constexpr size_t kMaxTcpClients = 4;
struct TcpClient {
int sock{-1};
::sockaddr_in remote{};
std::vector<uint8_t> rx_buffer;
TickType_t last_activity_tick{0};
};
struct TunnelClient {
bool connected{false};
uint8_t channel_id{0};
uint8_t connection_type{0};
uint8_t received_sequence{255};
uint8_t send_sequence{0};
uint16_t individual_address{0};
int tcp_sock{-1};
TickType_t last_activity_tick{0};
::sockaddr_in control_remote{};
::sockaddr_in data_remote{};
};
static void TaskEntry(void* arg);
esp_err_t initializeRuntime();
void taskLoop();
void finishTask();
void closeSockets();
bool configureSocket();
void handleTcpAccept();
void handleTcpClient(TcpClient& client);
void closeTcpClient(TcpClient& client);
bool configureTpUart();
bool initializeTpUart();
bool configureProgrammingGpio();
void refreshNetworkInterfaces(bool force_log = false);
void handleUdpDatagram(const uint8_t* data, size_t len, const ::sockaddr_in& remote);
void handleSearchRequest(uint16_t service, const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleDescriptionRequest(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleRoutingIndication(const uint8_t* body, size_t len);
void handleTunnellingRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
void handleDeviceConfigurationRequest(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleConnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
void handleConnectionStateRequest(const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void handleDisconnectRequest(const uint8_t* body, size_t len, const ::sockaddr_in& remote);
void handleSecureService(uint16_t service, const uint8_t* body, size_t len,
const ::sockaddr_in& remote);
void sendTunnellingAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
const ::sockaddr_in& remote);
void sendDeviceConfigurationAck(uint8_t channel_id, uint8_t sequence, uint8_t status,
const ::sockaddr_in& remote);
void sendConnectionHeaderAck(uint16_t service, uint8_t channel_id, uint8_t sequence,
uint8_t status, const ::sockaddr_in& remote);
void sendSecureSessionStatus(uint8_t status, const ::sockaddr_in& remote);
void sendTunnelIndication(const uint8_t* data, size_t len);
void sendTunnelIndicationToClient(TunnelClient& client, const uint8_t* data, size_t len);
void sendConnectionStateResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote);
void sendDisconnectResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote);
void sendConnectResponse(uint8_t channel_id, uint8_t status,
const ::sockaddr_in& remote, uint8_t connection_type,
uint16_t tunnel_address);
void sendRoutingIndication(const uint8_t* data, size_t len);
void sendSearchResponse(uint16_t service, const ::sockaddr_in& remote,
const std::set<uint8_t>& requested_dibs = {});
void sendDescriptionResponse(const ::sockaddr_in& remote);
bool sendPacket(const std::vector<uint8_t>& packet, const ::sockaddr_in& remote) const;
bool sendPacketToTunnelClient(const TunnelClient& client,
const std::vector<uint8_t>& packet) const;
bool currentTransportAllowsTcpHpai() const;
std::optional<std::array<uint8_t, 8>> localHpaiForRemote(const ::sockaddr_in& remote,
bool tcp = false) const;
std::vector<uint8_t> buildDeviceInfoDib(const ::sockaddr_in& remote) const;
std::vector<uint8_t> buildSupportedServiceDib() const;
std::vector<uint8_t> buildExtendedDeviceInfoDib() const;
std::vector<uint8_t> buildIpConfigDib(const ::sockaddr_in& remote, bool current) const;
std::vector<uint8_t> buildKnxAddressesDib() const;
std::vector<uint8_t> buildTunnelingInfoDib() const;
TunnelClient* findTunnelClient(uint8_t channel_id);
const TunnelClient* findTunnelClient(uint8_t channel_id) const;
TunnelClient* allocateTunnelClient(const ::sockaddr_in& control_remote,
const ::sockaddr_in& data_remote,
uint8_t connection_type);
void resetTunnelClient(TunnelClient& client);
uint8_t nextTunnelChannelId() const;
uint16_t effectiveTunnelAddressForSlot(size_t slot) const;
void pruneStaleTunnelClients();
bool handleOpenKnxTunnelFrame(const uint8_t* data, size_t len,
TunnelClient* response_client);
bool handleOpenKnxBusFrame(const uint8_t* data, size_t len);
void selectOpenKnxNetworkInterface(const ::sockaddr_in& remote);
bool emitOpenKnxGroupValue(uint16_t group_object_number, const uint8_t* data, size_t len);
bool shouldRouteDaliApplicationFrames() const;
uint8_t advertisedMedium() const;
void syncOpenKnxConfigFromDevice();
uint16_t effectiveIpInterfaceIndividualAddress() const;
uint16_t effectiveKnxDeviceIndividualAddress() const;
uint16_t effectiveTunnelAddress() const;
void pollTpUart();
void pollProgrammingButton();
void updateProgrammingLed();
void setProgrammingLed(bool on);
void handleTpUartControlByte(uint8_t byte);
void handleTpTelegram(const uint8_t* data, size_t len);
void forwardCemiToTp(const uint8_t* data, size_t len);
GatewayKnxBridge& bridge_;
CemiFrameHandler handler_;
GroupWriteHandler group_write_handler_;
std::string openknx_namespace_;
GatewayKnxConfig config_;
std::unique_ptr<openknx::EtsDeviceRuntime> ets_device_;
TaskHandle_t task_handle_{nullptr};
SemaphoreHandle_t openknx_lock_{nullptr};
SemaphoreHandle_t startup_semaphore_{nullptr};
esp_err_t startup_result_{ESP_OK};
std::atomic_bool stop_requested_{false};
std::atomic_bool started_{false};
int udp_sock_{-1};
int tcp_sock_{-1};
int active_tcp_sock_{-1};
int tp_uart_port_{-1};
int tp_uart_tx_pin_{-1};
int tp_uart_rx_pin_{-1};
std::vector<uint32_t> multicast_joined_interfaces_;
TickType_t network_refresh_tick_{0};
std::array<TcpClient, kMaxTcpClients> tcp_clients_{};
std::array<TunnelClient, kMaxTunnelClients> tunnel_clients_{};
uint8_t last_tunnel_channel_id_{0};
std::vector<uint8_t> tp_rx_frame_;
std::vector<uint8_t> tp_last_sent_telegram_;
TickType_t tp_uart_last_byte_tick_{0};
bool tp_uart_extended_frame_{false};
bool tp_uart_online_{false};
bool commissioning_only_{false};
std::atomic_bool openknx_configured_{false};
bool programming_button_last_pressed_{false};
bool programming_led_state_{false};
TickType_t programming_button_last_toggle_tick_{0};
std::string last_error_;
};
} // namespace gateway