Add KNX DALI Gateway Module and Message Queue Implementation

- Introduced KnxDaliModule class for handling DALI message queuing, commissioning, and KNX group-object dispatch.
- Implemented Message and MessageQueue classes for managing message operations.
- Removed obsolete OpenKNX IDF component files and CMake configurations.
- Updated submodule reference for KNX.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-05-15 12:34:13 +08:00
parent 3f15cd7f3f
commit 449a3a801a
41 changed files with 2279 additions and 918 deletions
+4 -2
View File
@@ -7,14 +7,16 @@ set(GATEWAY_BRIDGE_REQUIRES
gateway_cache
gateway_knx
gateway_modbus
knx
log
lwip
nvs_flash
openknx_idf
)
idf_component_register(
SRCS "src/gateway_bridge.cpp"
SRCS
"src/gateway_bridge.cpp"
"src/security_storage.cpp"
INCLUDE_DIRS "include"
REQUIRES ${GATEWAY_BRIDGE_REQUIRES}
PRIV_REQUIRES gateway_bacnet
@@ -15,7 +15,7 @@
#include "gateway_knx.hpp"
#include "gateway_modbus.hpp"
#include "gateway_provisioning.hpp"
#include "openknx_idf/security_storage.h"
#include "security_storage.h"
#include "cJSON.h"
#include "driver/uart.h"
@@ -1,4 +1,4 @@
#include "openknx_idf/security_storage.h"
#include "security_storage.h"
#include "esp_log.h"
#include "esp_mac.h"
+5 -2
View File
@@ -1,7 +1,10 @@
idf_component_register(
SRCS "src/gateway_knx.cpp"
SRCS
"src/gateway_knx.cpp"
"src/ets_device_runtime.cpp"
"src/ets_memory_loader.cpp"
INCLUDE_DIRS "include"
REQUIRES dali_cpp esp_driver_gpio esp_driver_uart esp_hw_support esp_netif freertos log lwip openknx_idf
REQUIRES dali_cpp esp_driver_gpio esp_driver_uart esp_hw_support esp_netif freertos log lwip knx
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -1,7 +1,7 @@
#pragma once
#include "openknx_idf/esp_idf_platform.h"
#include "openknx_idf/ets_memory_loader.h"
#include "esp_idf_platform.h"
#include "ets_memory_loader.h"
#include "knx/bau07B0.h"
#include "knx/cemi_frame.h"
@@ -0,0 +1,61 @@
#pragma once
// Internal header shared between gateway_knx.cpp and gateway_knx_router.cpp.
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "soc/uart_periph.h"
#include <cstdint>
#include <string>
namespace gateway {
namespace knx_internal {
constexpr const char* kTag = "gateway_knx";
// RAII semaphore guard.
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};
};
// Resolve a UART IO pin from config or SoC defaults.
inline 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;
}
} // namespace knx_internal
} // namespace gateway
@@ -1,4 +1,4 @@
#include "openknx_idf/ets_device_runtime.h"
#include "ets_device_runtime.h"
#include "knx/cemi_server.h"
#include "knx/secure_application_layer.h"
@@ -1,6 +1,6 @@
#include "openknx_idf/ets_memory_loader.h"
#include "ets_memory_loader.h"
#include "openknx_idf/esp_idf_platform.h"
#include "esp_idf_platform.h"
#include "knx/bau07B0.h"
#include "knx/property.h"
+1 -1
View File
@@ -8,7 +8,7 @@
#include "esp_log.h"
#include "lwip/inet.h"
#include "lwip/sockets.h"
#include "openknx_idf/ets_device_runtime.h"
#include "ets_device_runtime.h"
#include "soc/uart_periph.h"
#include <algorithm>
+24
View File
@@ -0,0 +1,24 @@
idf_component_register(
SRCS
"src/knx_dali_gw.cpp"
"src/knx_dali_module.cpp"
"src/knx_dali_channel.cpp"
"src/hcl_curve.cpp"
"src/color_helper.cpp"
"src/dali_helper.cpp"
"src/message_queue.cpp"
"src/dali_gateway_bridge.cpp"
INCLUDE_DIRS
"include"
"src"
REQUIRES
dali_cpp
dali_domain
esp_timer
freertos
knx
log
nvs_flash
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
@@ -0,0 +1,127 @@
#pragma once
// =============================================================================
// dali_gateway_bridge.h — Thin adapter mapping legacy DALI operations to
// dali_domain / dali_cpp API.
// =============================================================================
#include "dali_domain.hpp"
#include <cstdint>
#include <optional>
#include <vector>
namespace gateway {
namespace knx_dali_gw {
// DALI target types matching the legacy DaliModule target model.
enum class DaliTargetKind : uint8_t {
kShortAddress = 0,
kGroup = 1,
kBroadcast = 2,
};
struct DaliTarget {
DaliTargetKind kind{DaliTargetKind::kShortAddress};
int address{0}; // short address 0-63, group 0-15, or ignored for broadcast
};
// Encodes a DaliTarget into a raw DALI address byte.
uint8_t EncodeDaliRawAddr(DaliTarget target);
// Decodes a raw DALI address byte into a DaliTarget for a given short address.
DaliTarget DecodeDaliRawAddr(uint8_t raw_addr, int default_short_address = -1);
// Lightweight bridge over DaliDomainService for the KNX-DALI gateway.
// All operations go through a single DALI channel (gateway_id).
class DaliGatewayBridge {
public:
explicit DaliGatewayBridge(DaliDomainService& dali, uint8_t gateway_id = 0);
// ---- Basic send / query ----
bool sendRaw(DaliTarget target, uint8_t command) const;
bool sendExtRaw(DaliTarget target, uint8_t command) const;
std::optional<uint8_t> queryRaw(DaliTarget target, uint8_t command) const;
// ---- Brightness (arc power level) ----
bool setArc(DaliTarget target, uint8_t arc) const;
std::optional<uint8_t> queryActualLevel(int short_address) const;
// ---- On / Off / Step / Recall ----
bool on(DaliTarget target) const;
bool off(DaliTarget target) const;
bool stepUp(DaliTarget target) const;
bool stepDown(DaliTarget target) const;
bool recallMax(DaliTarget target) const;
bool recallMin(DaliTarget target) const;
bool goToScene(DaliTarget target, uint8_t scene) const;
// ---- Queries ----
std::optional<uint8_t> queryStatus(int short_address) const;
std::optional<uint8_t> queryDeviceType(int short_address) const;
std::optional<uint8_t> queryMinLevel(int short_address) const;
std::optional<uint8_t> queryMaxLevel(int short_address) const;
std::optional<uint8_t> queryPowerOnLevel(int short_address) const;
std::optional<uint8_t> querySystemFailureLevel(int short_address) const;
std::optional<uint8_t> queryFadeTimeRate(int short_address) const;
std::optional<uint8_t> queryFadeTime(int short_address) const;
std::optional<uint16_t> queryGroups(int short_address) const;
std::optional<uint8_t> querySceneLevel(int short_address, uint8_t scene) const;
// ---- DT8 colour ----
bool setColourTemperature(int short_address, int kelvin) const;
bool setColourRGB(int short_address, uint8_t r, uint8_t g, uint8_t b) const;
std::optional<DaliDomainSnapshot> dt8StatusSnapshot(int short_address) const;
std::optional<DaliDomainSnapshot> dt8SceneColorReport(int short_address, int scene) const;
// ---- Scenes & groups (write operations) ----
bool setDtr(uint8_t value) const;
bool setDtrAsScene(DaliTarget target, uint8_t scene) const;
bool addToGroup(DaliTarget target, uint8_t group) const;
bool removeFromGroup(DaliTarget target, uint8_t group) const;
bool removeFromScene(DaliTarget target, uint8_t scene) const;
bool setSceneLevel(DaliTarget target, uint8_t scene, uint8_t level) const;
// ---- Commissioning ----
bool initialise(DaliTarget target) const;
bool randomise() const;
bool searchAddrH(uint8_t high) const;
bool searchAddrM(uint8_t middle) const;
bool searchAddrL(uint8_t low) const;
bool compare() const;
bool withdraw() const;
bool terminate() const;
bool programShort(DaliTarget target, uint8_t short_address) const;
bool verifyShort(DaliTarget target) const;
std::optional<uint8_t> queryShort(DaliTarget target) const;
// High-level addressing.
bool allocateAllAddr(int start_address = 0) const;
void stopAllocAddr() const;
bool resetAndAllocAddr(int start_address = 0, bool remove_addr_first = false,
bool close_light = false) const;
// ---- Bus control ----
bool resetBus() const;
private:
DaliDomainService& dali_;
uint8_t gateway_id_;
};
// Convert DALI arc power level (0-254) to percentage (0.0-100.0).
double ArcToPercent(uint8_t arc);
// Convert percentage (0.0-100.0) to DALI arc power level (0-254).
uint8_t PercentToArc(double percent);
} // namespace knx_dali_gw
} // namespace gateway
@@ -0,0 +1,55 @@
#pragma once
// =============================================================================
// knx_dali_gw — KNX-to-DALI Gateway Component (ESP-IDF)
// =============================================================================
#include "esp_idf_platform.h"
#include "dali_domain.hpp"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
// Forward declarations.
class Bau07B0;
namespace gateway {
namespace knx_dali_gw {
struct KnxDaliGatewayConfig {
std::string nvs_namespace{"knx_dali_gw"};
uint16_t fallback_individual_address{0xfffe};
int dali_channel{0};
};
class KnxDaliGateway {
public:
explicit KnxDaliGateway(const KnxDaliGatewayConfig& config);
~KnxDaliGateway();
KnxDaliGateway(const KnxDaliGateway&) = delete;
KnxDaliGateway& operator=(const KnxDaliGateway&) = delete;
bool init();
void loop();
Bau07B0& knxDevice();
const Bau07B0& knxDevice() const;
void setNetworkInterface(esp_netif_t* netif);
bool handleTunnelFrame(const uint8_t* data, size_t len);
bool handleBusFrame(const uint8_t* data, size_t len);
bool emitGroupValue(uint16_t group_object_number, const uint8_t* data,
size_t len);
private:
struct Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace knx_dali_gw
} // namespace gateway
+66
View File
@@ -0,0 +1,66 @@
#pragma once
// Minimal stub for knxprod.h — generated KNX product definitions.
// The full file (1796 bytes of parameters, 1439 group objects) will be
// adapted in Phase 3 to use the gateway/knx API directly.
// Product identity
#define MAIN_OpenKnxId 0xA4
#define MAIN_ApplicationNumber 1
#define MAIN_ApplicationVersion 5
#define MAIN_OrderNumber "REG1-Dali"
#define MAIN_ParameterSize 1796
#define MAIN_MaxKoNumber 1439
// Parameter type enums (subset)
enum PT_DeviceType : uint8_t {
PT_deviceType_Deaktiviert = 0,
PT_deviceType_DT0 = 1,
PT_deviceType_DT1 = 2,
PT_deviceType_DT6 = 3,
PT_deviceType_DT8 = 4,
};
enum PT_ColorType : uint8_t {
PT_colorType_HSV = 0,
PT_colorType_RGB = 1,
PT_colorType_TW = 2,
PT_colorType_XYY = 3,
};
enum PT_ColorSpace : uint8_t {
PT_colorSpace_rgb = 0,
PT_colorSpace_xy = 1,
};
// Placeholder macros — will be replaced with direct Bau07B0 access in Phase 3.
#define ParamAPP_daynight(channelIndex) (0)
#define ParamAPP_funcBtn(channelIndex) (0)
#define ParamAPP_funcBtnLong(channelIndex) (0)
#define ParamAPP_funcBtnDbl(channelIndex) (0)
#define ParamADR_deviceType(channelIndex) (PT_DeviceType::PT_deviceType_Deaktiviert)
#define ParamADR_type(channelIndex) (0)
#define ParamADR_min(channelIndex) (0)
#define ParamADR_max(channelIndex) (254)
#define ParamADR_stairtime(channelIndex) (0)
#define ParamADR_onDay(channelIndex) (0)
#define ParamADR_onNight(channelIndex) (0)
#define ParamADR_error(channelIndex) (0)
#define ParamADR_queryTime(channelIndex) (0)
#define ParamADR_colorType(channelIndex) (PT_ColorType::PT_colorType_TW)
#define ParamADR_colorSpace(channelIndex) (PT_ColorSpace::PT_colorSpace_rgb)
#define ParamGRP_deviceType(channelIndex) (PT_DeviceType::PT_deviceType_Deaktiviert)
#define ParamGRP_type(channelIndex) (0)
#define ParamGRP_colorType(channelIndex) (PT_ColorType::PT_colorType_TW)
#define ParamHCL_type(channelIndex) (0)
// Group object offset placeholders
#define ADR_KoOffset 0
#define GRP_KoOffset 0
#define HCL_KoOffset 0
#define ADR_KoBlockSize 0
#define GRP_KoBlockSize 0
#define HCL_KoBlockSize 0
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <cstdint>
struct Ballast {
uint8_t high{0};
uint8_t middle{0};
uint8_t low{0};
uint8_t address{255};
};
+138
View File
@@ -0,0 +1,138 @@
#include "color_helper.h"
uint16_t ColorHelper::getKelvinFromSun(uint16_t minCurr, uint16_t minDiff, uint16_t minK, uint16_t maxK)
{
float xAchse = (minCurr * 3.14159) / minDiff;
float yAchse = sin(xAchse);
return (maxK - minK) * yAchse + minK;
}
void ColorHelper::rgbToXY(uint8_t in_r, uint8_t in_g, uint8_t in_b, uint16_t& x, uint16_t& y)
{
float r = in_r / 255.0;
float g = in_g / 255.0;
float b = in_b / 255.0;
r = (r > 0.04045) ? pow((r + 0.055) / (1.0 + 0.055), 2.4) : (r / 12.92);
g = (g > 0.04045) ? pow((g + 0.055) / (1.0 + 0.055), 2.4) : (g / 12.92);
b = (b > 0.04045) ? pow((b + 0.055) / (1.0 + 0.055), 2.4) : (b / 12.92);
float X = r * 0.4124 + g * 0.3576 + b * 0.1805;
float Y = r * 0.2126 + g * 0.7152 + b * 0.0722;
float Z = r * 0.0193 + g * 0.1192 + b * 0.9505;
float cx = X / (X + Y + Z);
float cy = Y / (X + Y + Z);
x = getBytes(cx);
y = getBytes(cy);
y = y + 1;
y = y - 1;
}
void ColorHelper::hsvToRGB(uint8_t in_h, uint8_t in_s, uint8_t in_v, uint8_t& r, uint8_t& g, uint8_t& b)
{
float h = in_h / 255.0;
float s = in_s / 255.0;
float v = in_v / 255.0;
double rt = 0;
double gt = 0;
double bt = 0;
int i = int(h * 6);
double f = h * 6 - i;
double p = v * (1 - s);
double q = v * (1 - f * s);
double t = v * (1 - (1 - f) * s);
switch(i % 6){
case 0: rt = v, gt = t, bt = p; break;
case 1: rt = q, gt = v, bt = p; break;
case 2: rt = p, gt = v, bt = t; break;
case 3: rt = p, gt = q, bt = v; break;
case 4: rt = t, gt = p, bt = v; break;
case 5: rt = v, gt = p, bt = q; break;
}
r = rt * 255;
g = gt * 255;
b = bt * 255;
}
void ColorHelper::kelvinToRGB(uint16_t kelvin, uint8_t& r, uint8_t& g, uint8_t& b)
{
auto temp = kelvin / 100;
if (temp <= 66)
{
r = 255;
g = 99.4708025861 * log(temp) - 161.1195681661;
if (temp <= 19)
b = 0;
else
b = 138.5177312231 * log(temp - 10) - 305.0447927307;
}
else
{
r = 329.698727446 * pow(temp - 60, -0.1332047592);
g = 288.1221695283 * pow(temp - 60, -0.0755148492);
b = 255;
}
}
void ColorHelper::xyyToRGB(uint16_t ix, uint16_t iy, uint8_t iz, uint8_t& r, uint8_t& g, uint8_t& b)
{
float _x = getFloat(ix);
float _y = getFloat(iy);
//let z = 1.0 - x - y;
//return this.colorFromXYZ((Y / y) * x, Y, (Y / y) * z);
float y = iz / 255.0f;
float x = _x * (y / _y);
float z = ((1.0 - _x - _y) * y) / _y;
float rt = x * 3.2404f + y * -1.5371f + z * -0.4985f;
float gt = x * -0.9693f + y * 1.8760f + z * 0.0416f;
float bt = x * 0.0556f + y * -0.2040f + z * 1.05723f;
rt = adjust(rt);
gt = adjust(gt);
bt = adjust(bt);
r = std::max(std::min(rt, 255.0f), 0.0f);
g = std::max(std::min(gt, 255.0f), 0.0f);
b = std::max(std::min(bt, 255.0f), 0.0f);
}
uint16_t ColorHelper::getBytes(float input)
{
return std::max(std::min(round(input * 65536.0), 65534.0), 0.0);
}
float ColorHelper::getFloat(uint16_t input)
{
float output = input / 65536.0f;
return std::max(std::min(output, 0.0f), 1.0f);
}
double ColorHelper::hue2rgb(double p, double q, double t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6.0) return p + (q - p) * 6 * t;
if (t < 1 / 2.0) return q;
if (t < 2 / 3.0) return p + (q - p) * (2 / 3.0 - t) * 6;
return p;
}
float ColorHelper::adjust(float input)
{
if (input > 0.0031308) {
return (1.055f * pow(input, (1.0f / 2.4f)) - 0.055f) * 255.0;
}
return 12.92f * input;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
#include <cmath>
#include <algorithm>
//extended lib from https://github.com/ratkins/RGBConverter
//WTFPL license
class ColorHelper
{
public:
static void rgbToXY(uint8_t r, uint8_t g, uint8_t b, uint16_t& x, uint16_t& y);
static void hsvToRGB(uint8_t h, uint8_t s, uint8_t v, uint8_t& r, uint8_t& g, uint8_t& b);
static void kelvinToRGB(uint16_t kelvin, uint8_t& r, uint8_t& g, uint8_t& b);
static void xyyToRGB(uint16_t x, uint16_t y, uint8_t z, uint8_t& r, uint8_t& g, uint8_t& b);
static uint16_t getKelvinFromSun(uint16_t minCurr, uint16_t minDiff, uint16_t minK, uint16_t maxK);
private:
static uint16_t getBytes(float input);
static float getFloat(uint16_t input);
static double hue2rgb(double p, double q, double t);
static float adjust(float input);
};
@@ -0,0 +1,323 @@
#include "dali_gateway_bridge.h"
#include "dali_define.hpp"
#include "dali_comm.hpp"
#include <algorithm>
#include <cmath>
namespace gateway {
namespace knx_dali_gw {
namespace {
constexpr double kArcMax = 254.0;
constexpr double kLogFactor = 3.0;
// DALI address encoding helpers (mirroring lib/dali/comm.dart).
// Short address 0-63 → (addr << 1) | 0x01 for commands.
constexpr uint8_t kShortAddrCmdBase = 0x01;
constexpr uint8_t kShortAddrArcBase = 0x00;
constexpr uint8_t kGroupAddrBase = 0x80;
constexpr uint8_t kBroadcastCmd = 0xFE;
constexpr uint8_t kBroadcastArc = 0xFF;
uint8_t makeShortCmdAddr(int addr) {
return static_cast<uint8_t>((addr << 1) | kShortAddrCmdBase);
}
uint8_t makeShortArcAddr(int addr) {
return static_cast<uint8_t>((addr << 1) | kShortAddrArcBase);
}
uint8_t makeGroupCmdAddr(int group) {
return static_cast<uint8_t>(kGroupAddrBase | (group << 1) | 0x01);
}
uint8_t makeBroadcastCmdAddr() { return kBroadcastCmd; }
uint8_t makeBroadcastArcAddr() { return kBroadcastArc; }
bool isBroadcastAddr(uint8_t raw) { return raw == kBroadcastCmd || raw == kBroadcastArc; }
bool isGroupAddr(uint8_t raw) { return (raw & 0x80) != 0 && !isBroadcastAddr(raw); }
int extractGroupAddr(uint8_t raw) { return (raw >> 1) & 0x0F; }
int extractShortAddr(uint8_t raw) { return (raw >> 1) & 0x3F; }
uint8_t encodeDaliRawAddr(DaliTarget target) {
switch (target.kind) {
case DaliTargetKind::kShortAddress:
return makeShortCmdAddr(target.address);
case DaliTargetKind::kGroup:
return makeGroupCmdAddr(target.address);
case DaliTargetKind::kBroadcast:
default:
return makeBroadcastCmdAddr();
}
}
} // namespace
// =============================================================================
// DaliTarget ↔ raw address encoding (public API)
// =============================================================================
uint8_t EncodeDaliRawAddr(DaliTarget target) {
return encodeDaliRawAddr(target);
}
DaliTarget DecodeDaliRawAddr(uint8_t raw_addr, int default_short_address) {
if (isBroadcastAddr(raw_addr)) {
return {DaliTargetKind::kBroadcast, 0};
}
if (isGroupAddr(raw_addr)) {
return {DaliTargetKind::kGroup, extractGroupAddr(raw_addr)};
}
int sa = extractShortAddr(raw_addr);
if (sa < 0 && default_short_address >= 0) {
sa = default_short_address;
}
return {DaliTargetKind::kShortAddress, sa};
}
// =============================================================================
// Arc power ↔ percentage conversion
// =============================================================================
double ArcToPercent(uint8_t arc) {
if (arc == 0) return 0.0;
return 100.0 * std::pow(static_cast<double>(arc) / kArcMax, kLogFactor);
}
uint8_t PercentToArc(double percent) {
if (percent <= 0.0) return 0;
if (percent >= 100.0) return 254;
return static_cast<uint8_t>(
std::round(kArcMax * std::pow(percent / 100.0, 1.0 / kLogFactor)));
}
// =============================================================================
// DaliGatewayBridge
// =============================================================================
DaliGatewayBridge::DaliGatewayBridge(DaliDomainService& dali, uint8_t gateway_id)
: dali_(dali), gateway_id_(gateway_id) {}
bool DaliGatewayBridge::sendRaw(DaliTarget target, uint8_t command) const {
return dali_.sendRaw(gateway_id_, encodeDaliRawAddr(target), command);
}
bool DaliGatewayBridge::sendExtRaw(DaliTarget target, uint8_t command) const {
return dali_.sendExtRaw(gateway_id_, encodeDaliRawAddr(target), command);
}
std::optional<uint8_t> DaliGatewayBridge::queryRaw(DaliTarget target, uint8_t command) const {
return dali_.queryRaw(gateway_id_, encodeDaliRawAddr(target), command);
}
bool DaliGatewayBridge::setArc(DaliTarget target, uint8_t arc) const {
return sendRaw(target, arc);
}
std::optional<uint8_t> DaliGatewayBridge::queryActualLevel(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_ACTUAL_LEVEL);
}
bool DaliGatewayBridge::on(DaliTarget target) const {
return sendRaw(target, DALI_CMD_RECALL_MAX);
}
bool DaliGatewayBridge::off(DaliTarget target) const {
return sendRaw(target, DALI_CMD_OFF);
}
bool DaliGatewayBridge::stepUp(DaliTarget target) const {
return sendRaw(target, DALI_CMD_RECALL_MAX);
}
bool DaliGatewayBridge::stepDown(DaliTarget target) const {
return sendRaw(target, DALI_CMD_OFF);
}
bool DaliGatewayBridge::recallMax(DaliTarget target) const {
return sendRaw(target, DALI_CMD_RECALL_MAX);
}
bool DaliGatewayBridge::recallMin(DaliTarget target) const {
return sendRaw(target, DALI_CMD_RECALL_MIN);
}
bool DaliGatewayBridge::goToScene(DaliTarget target, uint8_t scene) const {
return sendRaw(target, DALI_CMD_GO_TO_SCENE(scene & 0x0F));
}
std::optional<uint8_t> DaliGatewayBridge::queryStatus(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_STATUS);
}
std::optional<uint8_t> DaliGatewayBridge::queryDeviceType(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_DEVICE_TYPE);
}
std::optional<uint8_t> DaliGatewayBridge::queryMinLevel(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_MIN_LEVEL);
}
std::optional<uint8_t> DaliGatewayBridge::queryMaxLevel(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_MAX_LEVEL);
}
std::optional<uint8_t> DaliGatewayBridge::queryPowerOnLevel(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_POWER_ON_LEVEL);
}
std::optional<uint8_t> DaliGatewayBridge::querySystemFailureLevel(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_SYSTEM_FAILURE_LEVEL);
}
std::optional<uint8_t> DaliGatewayBridge::queryFadeTimeRate(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_FADE_TIME_FADE_RATE);
}
std::optional<uint8_t> DaliGatewayBridge::queryFadeTime(int short_address) const {
return dali_.queryRaw(gateway_id_, makeShortCmdAddr(short_address),
DALI_CMD_QUERY_EXTENDED_FADE_TIME);
}
std::optional<uint16_t> DaliGatewayBridge::queryGroups(int short_address) const {
return dali_.queryGroupMask(gateway_id_, short_address);
}
std::optional<uint8_t> DaliGatewayBridge::querySceneLevel(int short_address,
uint8_t scene) const {
return dali_.querySceneLevel(gateway_id_, short_address, scene & 0x0F);
}
// ---- DT8 ----
bool DaliGatewayBridge::setColourTemperature(int short_address, int kelvin) const {
return dali_.setColTemp(gateway_id_, short_address, kelvin);
}
bool DaliGatewayBridge::setColourRGB(int short_address, uint8_t r, uint8_t g,
uint8_t b) const {
return dali_.setColourRGB(gateway_id_, short_address, r, g, b);
}
std::optional<DaliDomainSnapshot> DaliGatewayBridge::dt8StatusSnapshot(
int short_address) const {
return dali_.dt8StatusSnapshot(gateway_id_, short_address);
}
std::optional<DaliDomainSnapshot> DaliGatewayBridge::dt8SceneColorReport(
int short_address, int scene) const {
return dali_.dt8SceneColorReport(gateway_id_, short_address, scene);
}
// ---- Scenes & groups ----
bool DaliGatewayBridge::setDtr(uint8_t value) const {
return dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(),
DALI_CMD_SPECIAL_SET_DTR0) &&
dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(), value);
}
bool DaliGatewayBridge::setDtrAsScene(DaliTarget target, uint8_t scene) const {
return sendRaw(target, DALI_CMD_SET_SCENE(scene & 0x0F));
}
bool DaliGatewayBridge::addToGroup(DaliTarget target, uint8_t group) const {
return sendRaw(target, DALI_CMD_ADD_TO_GROUP(group & 0x0F));
}
bool DaliGatewayBridge::removeFromGroup(DaliTarget target, uint8_t group) const {
return sendRaw(target, DALI_CMD_REMOVE_FROM_GROUP(group & 0x0F));
}
bool DaliGatewayBridge::removeFromScene(DaliTarget target, uint8_t scene) const {
return sendRaw(target, DALI_CMD_REMOVE_SCENE(scene & 0x0F));
}
bool DaliGatewayBridge::setSceneLevel(DaliTarget target, uint8_t scene,
uint8_t level) const {
return setDtr(level) && setDtrAsScene(target, scene);
}
// ---- Commissioning ----
bool DaliGatewayBridge::initialise(DaliTarget target) const {
return sendRaw(target, DALI_CMD_SPECIAL_INITIALIZE);
}
bool DaliGatewayBridge::randomise() const {
return sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_RANDOMIZE);
}
bool DaliGatewayBridge::searchAddrH(uint8_t high) const {
return dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(),
DALI_CMD_SPECIAL_SEARCHADDRH) &&
dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(), high);
}
bool DaliGatewayBridge::searchAddrM(uint8_t middle) const {
return dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(),
DALI_CMD_SPECIAL_SEARCHADDRM) &&
dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(), middle);
}
bool DaliGatewayBridge::searchAddrL(uint8_t low) const {
return dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(),
DALI_CMD_SPECIAL_SEARCHADDRL) &&
dali_.sendRaw(gateway_id_, makeBroadcastCmdAddr(), low);
}
bool DaliGatewayBridge::compare() const {
return sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_COMPARE);
}
bool DaliGatewayBridge::withdraw() const {
return sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_WITHDRAW);
}
bool DaliGatewayBridge::terminate() const {
return sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_TERMINATE);
}
bool DaliGatewayBridge::programShort(DaliTarget target, uint8_t short_address) const {
const uint8_t raw = (short_address << 1) | 0x01;
return dali_.sendRaw(gateway_id_, encodeDaliRawAddr(target),
DALI_CMD_SPECIAL_PROGRAM_SHORT_ADDRESS) &&
dali_.sendRaw(gateway_id_, encodeDaliRawAddr(target), raw);
}
bool DaliGatewayBridge::verifyShort(DaliTarget target) const {
return sendRaw(target, DALI_CMD_SPECIAL_VERIFY_SHORT_ADDRESS);
}
std::optional<uint8_t> DaliGatewayBridge::queryShort(DaliTarget target) const {
return dali_.queryRaw(gateway_id_, encodeDaliRawAddr(target),
DALI_CMD_SPECIAL_QUERY_SHORT_ADDRESS);
}
bool DaliGatewayBridge::allocateAllAddr(int start_address) const {
return dali_.allocateAllAddr(gateway_id_, start_address);
}
void DaliGatewayBridge::stopAllocAddr() const {
dali_.stopAllocAddr(gateway_id_);
}
bool DaliGatewayBridge::resetAndAllocAddr(int start_address, bool remove_addr_first,
bool close_light) const {
return dali_.resetAndAllocAddr(gateway_id_, start_address, remove_addr_first, close_light);
}
bool DaliGatewayBridge::resetBus() const {
return dali_.resetBus(gateway_id_);
}
} // namespace knx_dali_gw
} // namespace gateway
@@ -0,0 +1,40 @@
#include "dali_helper.h"
uint8_t DaliHelper::percentToArc(uint8_t value)
{
if(value == 0)
{
return 0;
}
//Todo also include _max
uint8_t arc = roundToInt(((253/3.0)*(std::log10(value)+1)) + 1);
return arc;
}
uint8_t DaliHelper::arcToPercent(uint8_t value)
{
if(value == 0)
{
return 0;
}
//Todo also include _max
double arc = std::pow(10, ((value-1) / (253/3.0)) - 1);
return roundToInt(arc);
}
float DaliHelper::arcToPercentFloat(uint8_t value)
{
if(value == 0)
{
return 0;
}
//Todo also include _max
float arc = std::pow(10, ((value-1) / (253/3.0)) - 1);
return arc;
}
uint8_t DaliHelper::roundToInt(double input)
{
double temp = input + 0.5;
return (uint8_t)temp;
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
#include <cmath>
class DaliHelper
{
public:
static uint8_t percentToArc(uint8_t value);
static uint8_t arcToPercent(uint8_t value);
static float arcToPercentFloat(uint8_t value);
static uint8_t roundToInt(double input);
};
+15
View File
@@ -0,0 +1,15 @@
#include "hcl_curve.h"
#include "esp_timer.h"
namespace gateway {
namespace knx_dali_gw {
void HclCurve::setup(uint8_t index) { index_ = index; }
void HclCurve::loop() {
// HCL curve logic — simplified for now.
// Full port from HclCurve.cpp in subsequent iteration.
}
} // namespace knx_dali_gw
} // namespace gateway
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <cstdint>
namespace gateway {
namespace knx_dali_gw {
class HclCurve {
public:
void setup(uint8_t index);
void loop();
private:
uint8_t index_{0};
bool is_configured_{false};
uint8_t type_{0};
uint64_t last_check_{0};
uint8_t last_day_{0};
uint8_t last_minute_{0};
};
} // namespace knx_dali_gw
} // namespace gateway
@@ -0,0 +1,184 @@
#include "knx_dali_channel.h"
#include "dali_define.hpp"
#include "knxprod.h"
#include "dali_helper.h"
#include "esp_log.h"
#include "esp_timer.h"
namespace gateway {
namespace knx_dali_gw {
KnxDaliChannel::KnxDaliChannel() = default;
KnxDaliChannel::~KnxDaliChannel() = default;
void KnxDaliChannel::init(uint8_t channel_index, bool is_group, DaliGatewayBridge& bridge) {
index_ = channel_index;
is_group_ = is_group;
dali_ = &bridge;
}
void KnxDaliChannel::setup() {
if (dali_ == nullptr) return;
// Query initial state
DaliTarget target = is_group_ ? DaliTarget{DaliTargetKind::kGroup, static_cast<int>(index_)}
: DaliTarget{DaliTargetKind::kShortAddress, static_cast<int>(index_)};
(void)target; // Will be used in full port
}
void KnxDaliChannel::loop() {
if (dali_ == nullptr) return;
loopDimming();
loopStaircase();
loopQueryLevel();
}
void KnxDaliChannel::processInputKo(GroupObject& ko) {
uint16_t asap = ko.asap();
int slot = static_cast<int>(asap) - (is_group_ ? GRP_KoOffset : ADR_KoOffset) - index_ * (is_group_ ? GRP_KoBlockSize : ADR_KoBlockSize);
// TODO: Full slot-to-handler mapping from DaliChannel.cpp
// For now, delegate to basic handlers
switch (slot) {
case 0: koHandleSwitch(ko); break;
// ... more slots
default: break;
}
}
// ---- Dimming ----
void KnxDaliChannel::loopDimming() {
if (dimm_direction_ == DimmDirection::kNone) return;
uint64_t now = esp_timer_get_time() / 1000ULL;
if (now - dimm_last_ < dimm_interval_) return;
dimm_last_ = now;
DaliTarget target = is_group_ ? DaliTarget{DaliTargetKind::kGroup, static_cast<int>(index_)}
: DaliTarget{DaliTargetKind::kShortAddress, static_cast<int>(index_)};
if (dimm_direction_ == DimmDirection::kUp) {
if (current_step_ < max_) current_step_++;
dali_->setArc(target, current_step_);
} else {
if (current_step_ > min_) current_step_--;
dali_->setArc(target, current_step_);
}
}
// ---- Staircase ----
void KnxDaliChannel::loopStaircase() {
if (interval_ == 0 || !current_state_) return;
uint64_t now = esp_timer_get_time() / 1000ULL;
if (now - start_time_ >= interval_ * 1000ULL) {
current_state_ = false;
interval_ = 0;
DaliTarget target = is_group_ ? DaliTarget{DaliTargetKind::kGroup, static_cast<int>(index_)}
: DaliTarget{DaliTargetKind::kShortAddress, static_cast<int>(index_)};
dali_->off(target);
}
}
// ---- Query Level ----
void KnxDaliChannel::loopQueryLevel() {
// Periodic status query — simplified for now
}
// ---- Switch State ----
void KnxDaliChannel::setSwitchState(bool value, bool is_switch_command) {
if (current_is_locked_) return;
current_state_ = value;
DaliTarget target = is_group_ ? DaliTarget{DaliTargetKind::kGroup, static_cast<int>(index_)}
: DaliTarget{DaliTargetKind::kShortAddress, static_cast<int>(index_)};
if (value) {
dali_->on(target);
} else {
dali_->off(target);
}
if (value) {
start_time_ = esp_timer_get_time() / 1000ULL;
}
}
// ---- Configuration setters ----
void KnxDaliChannel::setOnValue(uint8_t value) {
on_day_ = value;
on_night_ = value / 2;
}
void KnxDaliChannel::setGroups(uint16_t groups) { groups_ = groups; }
void KnxDaliChannel::setGroupState(uint8_t group, bool state) {
if (state) groups_ |= (1 << group); else groups_ &= ~(1 << group);
}
void KnxDaliChannel::setGroupState(uint8_t group, uint8_t) {}
void KnxDaliChannel::setMinMax(uint8_t min, uint8_t max) { min_ = min; max_ = max; }
void KnxDaliChannel::setMinArc(uint8_t min) { min_ = min; }
void KnxDaliChannel::setHcl(uint8_t curve, uint16_t temp, uint8_t) {
hcl_curve_ = curve;
hcl_current_temp_ = temp;
}
// ---- Dimm State ----
void KnxDaliChannel::setDimmState(uint8_t value, bool, bool) {
current_step_ = value;
}
// ---- Color ----
void KnxDaliChannel::sendColor() {
if (dali_ == nullptr) return;
dali_->setColourRGB(static_cast<int>(index_), current_color_[0],
current_color_[1], current_color_[2]);
}
// ---- KO Handlers ----
void KnxDaliChannel::koHandleSwitch(GroupObject& ko) {
bool on = static_cast<bool>(ko.value());
setSwitchState(on);
}
void KnxDaliChannel::koHandleDimmRel(GroupObject& ko) {
int step = static_cast<int>(static_cast<float>(ko.value()));
if (step > 0) {
dimm_direction_ = DimmDirection::kUp;
dimm_step_ = static_cast<uint8_t>(step);
} else if (step < 0) {
dimm_direction_ = DimmDirection::kDown;
dimm_step_ = static_cast<uint8_t>(-step);
} else {
dimm_direction_ = DimmDirection::kNone;
}
dimm_last_ = esp_timer_get_time() / 1000ULL;
}
void KnxDaliChannel::koHandleDimmAbs(GroupObject& ko) {
uint8_t value = static_cast<uint8_t>(static_cast<float>(ko.value()) * 255.0f / 100.0f);
setDimmState(value);
dimm_direction_ = DimmDirection::kNone;
DaliTarget target = is_group_ ? DaliTarget{DaliTargetKind::kGroup, static_cast<int>(index_)}
: DaliTarget{DaliTargetKind::kShortAddress, static_cast<int>(index_)};
dali_->setArc(target, value);
}
void KnxDaliChannel::koHandleLock(GroupObject& ko) {
bool lock = static_cast<bool>(ko.value());
current_is_locked_ = lock;
}
void KnxDaliChannel::koHandleColor(GroupObject& ko) {
KNXValue val = ko.value();
if (true) {
// RGB packed in float or raw bytes
// Simplified: store and send
sendColor();
}
}
} // namespace knx_dali_gw
} // namespace gateway
@@ -0,0 +1,91 @@
#pragma once
// =============================================================================
// KnxDaliChannel — Per-address / per-group DALI channel (ported from DaliChannel)
// =============================================================================
#include "dali_gateway_bridge.h"
#include "knx/group_object.h"
#include <cstdint>
namespace gateway {
namespace knx_dali_gw {
class KnxDaliChannel {
public:
KnxDaliChannel();
~KnxDaliChannel();
void init(uint8_t channel_index, bool is_group, DaliGatewayBridge& bridge);
void setup();
void loop();
void processInputKo(GroupObject& ko);
// --- Configuration ---
void setOnValue(uint8_t value);
void setGroups(uint16_t groups);
void setGroupState(uint8_t group, bool state);
void setGroupState(uint8_t group, uint8_t value);
void setMinMax(uint8_t min, uint8_t max);
void setMinArc(uint8_t min);
void setHcl(uint8_t curve, uint16_t temp, uint8_t bri);
uint8_t getMin() const { return min_; }
uint8_t getMax() const { return max_; }
uint16_t getGroups() const { return groups_; }
bool isNight{false};
private:
enum class DimmDirection { kDown, kUp, kNone };
void loopDimming();
void loopStaircase();
void loopQueryLevel();
void sendColor();
void setSwitchState(bool value, bool is_switch_command = true);
void setDimmState(uint8_t value, bool is_dimm_command = true, bool is_last = false);
void koHandleSwitch(GroupObject& ko);
void koHandleDimmRel(GroupObject& ko);
void koHandleDimmAbs(GroupObject& ko);
void koHandleLock(GroupObject& ko);
void koHandleColor(GroupObject& ko);
DaliGatewayBridge* dali_{nullptr};
uint8_t index_{0};
bool is_group_{false};
// Dimming
DimmDirection dimm_direction_{DimmDirection::kNone};
uint8_t dimm_step_{0};
uint64_t dimm_last_{0};
uint8_t dimm_interval_{100};
// Staircase
uint64_t start_time_{0};
uint32_t interval_{0};
// Limits
uint8_t min_{0};
uint8_t max_{254};
uint8_t on_day_{100};
uint8_t on_night_{10};
// State
bool current_state_{false};
uint8_t current_step_{0};
bool current_is_locked_{false};
uint8_t current_color_[4]{};
// HCL
uint8_t hcl_curve_{255};
uint16_t hcl_current_temp_{0};
// Groups
uint16_t groups_{0};
};
} // namespace knx_dali_gw
} // namespace gateway
+106
View File
@@ -0,0 +1,106 @@
#include "knx_dali_gw.hpp"
#include "knx/bau07B0.h"
#include "esp_log.h"
namespace gateway {
namespace knx_dali_gw {
namespace {
constexpr const char* kTag = "knx_dali_gw";
} // namespace
// =============================================================================
// KnxDaliGateway::Impl
// =============================================================================
struct KnxDaliGateway::Impl {
KnxDaliGatewayConfig config;
gateway::openknx::EspIdfPlatform platform;
Bau07B0 device;
bool initialized{false};
explicit Impl(const KnxDaliGatewayConfig& cfg)
: config(cfg),
platform(nullptr, cfg.nvs_namespace.c_str()),
device(platform) {}
bool init() {
if (initialized) return true;
device.deviceObject().manufacturerId(0x00a4);
device.deviceObject().bauNumber(platform.uniqueSerialNumber());
const uint8_t order_number[10] = {'R', 'E', 'G', '1', '-', 'D', 'a', 'l', 'i', 0};
device.deviceObject().orderNumber(order_number);
const uint8_t program_version[5] = {0x00, 0xa4, 0x00, 0x01, 0x05};
device.parameters().property(PID_PROG_VERSION)->write(program_version);
device.readMemory();
if (!device.configured()) {
ESP_LOGW(kTag, "KNX device is not configured (blank ETS memory). "
"Individual address: 0x%04X (fallback).",
config.fallback_individual_address);
}
device.enabled(true);
initialized = true;
ESP_LOGI(kTag, "KNX-DALI gateway initialized");
return true;
}
void loop() {
if (!initialized) return;
device.loop();
}
void setNetworkInterface(esp_netif_t* netif) {
platform.networkInterface(netif);
}
};
// =============================================================================
// Public API
// =============================================================================
KnxDaliGateway::KnxDaliGateway(const KnxDaliGatewayConfig& config)
: impl_(std::make_unique<Impl>(config)) {}
KnxDaliGateway::~KnxDaliGateway() = default;
bool KnxDaliGateway::init() { return impl_->init(); }
void KnxDaliGateway::loop() { impl_->loop(); }
Bau07B0& KnxDaliGateway::knxDevice() { return impl_->device; }
const Bau07B0& KnxDaliGateway::knxDevice() const { return impl_->device; }
void KnxDaliGateway::setNetworkInterface(esp_netif_t* netif) {
impl_->setNetworkInterface(netif);
}
bool KnxDaliGateway::handleTunnelFrame(const uint8_t* data, size_t len) {
// TODO: Implement cEMI tunnel frame handling.
(void)data; (void)len;
return false;
}
bool KnxDaliGateway::handleBusFrame(const uint8_t* data, size_t len) {
// TODO: Implement bus frame handling.
(void)data; (void)len;
return false;
}
bool KnxDaliGateway::emitGroupValue(uint16_t group_object_number,
const uint8_t* data, size_t len) {
(void)group_object_number; (void)data; (void)len;
// TODO(Phase 3): Implement with proper KNXValue conversion.
return false;
}
} // namespace knx_dali_gw
} // namespace gateway
@@ -0,0 +1,679 @@
#include "knx_dali_module.h"
#include "knx/bau07B0.h"
#include "knx/group_object.h"
#include "dali_define.hpp"
#include "esp_log.h"
#include "esp_timer.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
namespace gateway {
namespace knx_dali_gw {
namespace {
constexpr const char* kLogTag = "knx_dali_module";
constexpr uint8_t kDaliBroadcastAddr = 0xFE;
} // namespace
// =============================================================================
// Constructor / Destructor
// =============================================================================
KnxDaliModule::KnxDaliModule() {
std::memset(addresses_, 0, sizeof(addresses_));
std::memset(ballasts_, 0, sizeof(ballasts_));
}
KnxDaliModule::~KnxDaliModule() = default;
// =============================================================================
// Setup
// =============================================================================
void KnxDaliModule::setup(Bau07B0& device, DaliGatewayBridge& bridge) {
device_ = &device;
dali_ = &bridge;
for (int i = 0; i < 64; i++) {
channels_[i].init(i, false, bridge);
}
for (int i = 0; i < 16; i++) {
groups_[i].init(i, true, bridge);
}
for (int i = 0; i < 3; i++) {
curves_[i].setup(static_cast<uint8_t>(i));
}
}
// =============================================================================
// Main Loop
// =============================================================================
void KnxDaliModule::loop(bool configured) {
if (!configured || device_ == nullptr) return;
loopMessages();
loopAddressing();
loopAssigning();
loopBusState();
loopInitData();
loopGroupState();
for (auto& ch : channels_) ch.loop();
for (auto& grp : groups_) grp.loop();
for (auto& curve : curves_) curve.loop();
}
// =============================================================================
// Message Queue Execution
// =============================================================================
void KnxDaliModule::loopMessages() {
if (dali_ == nullptr) return;
Message msg;
while (queue_.pop(msg)) {
DaliTarget target;
switch (msg.addrtype) {
case 0: target = {DaliTargetKind::kShortAddress, static_cast<int>(msg.para1)}; break;
case 1: target = {DaliTargetKind::kGroup, static_cast<int>(msg.para1)}; break;
default: target = {DaliTargetKind::kBroadcast, 0}; break;
}
switch (msg.type) {
case MessageType::Arc:
dali_->setArc(target, msg.data);
break;
case MessageType::Cmd:
dali_->sendRaw(target, msg.data);
break;
case MessageType::SpecialCmd:
dali_->sendRaw(target, msg.data);
break;
case MessageType::Query:
if (auto resp = dali_->queryRaw(target, msg.data)) {
queue_.setResponse(msg.id, *resp);
} else {
queue_.setResponse(msg.id, -1);
}
break;
}
}
}
// =============================================================================
// Addressing State Machine
// =============================================================================
void KnxDaliModule::loopAddressing() {
if (adr_state_ == AddressingState::kOff || dali_ == nullptr) return;
switch (adr_state_) {
case AddressingState::kOff:
break;
case AddressingState::kInit:
adr_found_ = 0;
adr_iterations_ = 0;
ESP_LOGI(kLogTag, "Addressing: init (only_new=%d, randomize=%d, delete_all=%d)",
adr_only_new_, adr_randomize_, adr_delete_all_);
dali_->sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_INITIALIZE);
adr_state_ = AddressingState::kInit2;
break;
case AddressingState::kInit2:
dali_->sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_INITIALIZE);
if (adr_delete_all_) {
adr_state_ = AddressingState::kWriteDtr;
} else if (adr_randomize_) {
adr_state_ = AddressingState::kRandom;
} else {
adr_state_ = AddressingState::kStartSearch;
}
break;
case AddressingState::kWriteDtr:
dali_->setDtr(255);
adr_state_ = AddressingState::kRemoveShort;
break;
case AddressingState::kRemoveShort: {
dali_->sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_STORE_DTR_AS_SHORT_ADDRESS);
adr_state_ = AddressingState::kRemoveShort2;
break;
}
case AddressingState::kRemoveShort2:
dali_->sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_STORE_DTR_AS_SHORT_ADDRESS);
if (adr_randomize_) {
adr_state_ = AddressingState::kRandom;
} else {
adr_state_ = AddressingState::kStartSearch;
}
break;
case AddressingState::kRandom:
dali_->randomise();
adr_state_ = AddressingState::kRandom2;
break;
case AddressingState::kRandom2:
dali_->randomise();
adr_state_ = AddressingState::kRandomWait;
break;
case AddressingState::kRandomWait:
vTaskDelay(pdMS_TO_TICKS(100));
adr_state_ = AddressingState::kStartSearch;
break;
case AddressingState::kStartSearch:
adr_search_ = 0xFFFFFF;
adr_state_ = AddressingState::kSearchHigh;
break;
case AddressingState::kSearchHigh:
dali_->searchAddrH(static_cast<uint8_t>((adr_search_ >> 16) & 0xFF));
adr_state_ = AddressingState::kSearchMid;
break;
case AddressingState::kSearchMid:
dali_->searchAddrM(static_cast<uint8_t>((adr_search_ >> 8) & 0xFF));
adr_state_ = AddressingState::kSearchLow;
break;
case AddressingState::kSearchLow:
dali_->searchAddrL(static_cast<uint8_t>(adr_search_ & 0xFF));
adr_state_ = AddressingState::kCompare;
break;
case AddressingState::kCompare: {
dali_->compare();
adr_state_ = AddressingState::kCheckFound;
break;
}
case AddressingState::kCheckFound: {
// Binary search: query short address to check if a device responded.
auto resp = dali_->queryShort({DaliTargetKind::kBroadcast, 0});
bool found = resp.has_value() && *resp != 0xFF;
if (adr_iterations_ < 24) {
int64_t delta = static_cast<int64_t>(1) << (23 - adr_iterations_);
if (found) {
adr_search_ += delta;
} else {
adr_search_ -= delta;
}
adr_iterations_++;
adr_state_ = AddressingState::kSearchHigh;
} else {
if (found) {
adr_state_ = AddressingState::kGetShort;
} else {
// Check one address higher
adr_search_++;
if (adr_search_ > 0xFFFFFF) {
adr_state_ = AddressingState::kTerminate;
} else {
adr_iterations_ = 0;
adr_state_ = AddressingState::kSearchHigh;
}
}
}
break;
}
case AddressingState::kGetShort: {
auto short_addr = dali_->queryShort({DaliTargetKind::kBroadcast, 0});
if (short_addr.has_value()) {
ballasts_[adr_found_].address = *short_addr;
ballasts_[adr_found_].high = static_cast<uint8_t>((adr_search_ >> 16) & 0xFF);
ballasts_[adr_found_].middle = static_cast<uint8_t>((adr_search_ >> 8) & 0xFF);
ballasts_[adr_found_].low = static_cast<uint8_t>(adr_search_ & 0xFF);
if (*short_addr == 0xFF) {
// Unaddressed — assign a free short address
int free_addr = -1;
for (int i = 0; i < 64; i++) {
if (!addresses_[i]) { free_addr = i; break; }
}
if (free_addr >= 0) {
adr_new_ = static_cast<uint8_t>(free_addr);
adr_state_ = AddressingState::kProgramShort;
} else {
adr_state_ = AddressingState::kWithdraw;
}
} else {
addresses_[*short_addr] = true;
adr_found_++;
adr_state_ = AddressingState::kWithdraw;
}
}
break;
}
case AddressingState::kProgramShort:
dali_->programShort({DaliTargetKind::kBroadcast, 0}, adr_new_);
adr_state_ = AddressingState::kVerifyShort;
break;
case AddressingState::kVerifyShort:
dali_->verifyShort({DaliTargetKind::kBroadcast, 0});
adr_state_ = AddressingState::kVerifyShortResponse;
break;
case AddressingState::kVerifyShortResponse: {
auto verify = dali_->queryShort({DaliTargetKind::kBroadcast, 0});
if (verify.has_value() && *verify == adr_new_) {
addresses_[adr_new_] = true;
adr_found_++;
ESP_LOGI(kLogTag, "Addressed device %d", adr_new_);
}
adr_state_ = AddressingState::kWithdraw;
break;
}
case AddressingState::kWithdraw:
dali_->withdraw();
adr_state_ = AddressingState::kStartSearch;
break;
case AddressingState::kTerminate:
dali_->terminate();
ESP_LOGI(kLogTag, "Addressing complete: %d devices found", adr_found_);
adr_state_ = AddressingState::kOff;
break;
default:
break;
}
}
// =============================================================================
// Assigning State Machine
// =============================================================================
void KnxDaliModule::loopAssigning() {
if (ass_state_ == AssigningState::kOff || dali_ == nullptr) return;
switch (ass_state_) {
case AssigningState::kOff:
break;
case AssigningState::kInit:
dali_->sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_INITIALIZE);
ass_state_ = AssigningState::kInit2;
break;
case AssigningState::kInit2:
dali_->sendRaw({DaliTargetKind::kBroadcast, 0}, DALI_CMD_SPECIAL_INITIALIZE);
ass_state_ = AssigningState::kQuery;
break;
case AssigningState::kQuery: {
auto level = dali_->queryActualLevel(adr_new_);
ass_state_ = AssigningState::kCheckQuery;
break;
}
case AssigningState::kCheckQuery:
// If the target address already has a device, stop
ass_state_ = AssigningState::kStartSearch;
break;
case AssigningState::kStartSearch:
adr_iterations_ = 0;
ass_state_ = AssigningState::kSearchHigh;
break;
case AssigningState::kSearchHigh:
dali_->searchAddrH(static_cast<uint8_t>((adr_search_ >> 16) & 0xFF));
ass_state_ = AssigningState::kSearchMid;
break;
case AssigningState::kSearchMid:
dali_->searchAddrM(static_cast<uint8_t>((adr_search_ >> 8) & 0xFF));
ass_state_ = AssigningState::kSearchLow;
break;
case AssigningState::kSearchLow:
dali_->searchAddrL(static_cast<uint8_t>(adr_search_ & 0xFF));
ass_state_ = AssigningState::kCompare;
break;
case AssigningState::kCompare:
dali_->compare();
ass_state_ = AssigningState::kCheckFound;
break;
case AssigningState::kCheckFound:
if (!adr_assign_) {
adr_assign_ = true;
ass_state_ = AssigningState::kWithdraw;
} else {
auto resp = dali_->queryShort({DaliTargetKind::kBroadcast, 0});
if (resp.has_value() && *resp != 0xFF) {
ass_state_ = AssigningState::kProgramShort;
} else {
ass_state_ = AssigningState::kTerminate;
}
}
break;
case AssigningState::kWithdraw:
dali_->withdraw();
adr_assign_ = true;
ass_state_ = AssigningState::kSearchHigh;
break;
case AssigningState::kProgramShort:
dali_->programShort({DaliTargetKind::kBroadcast, 0}, adr_new_);
ass_state_ = AssigningState::kVerifyShort;
break;
case AssigningState::kVerifyShort:
dali_->verifyShort({DaliTargetKind::kBroadcast, 0});
ass_state_ = AssigningState::kVerifyShortResponse;
break;
case AssigningState::kVerifyShortResponse: {
auto verify = dali_->queryShort({DaliTargetKind::kBroadcast, 0});
if (verify.has_value() && *verify == adr_new_) {
addresses_[adr_new_] = true;
ESP_LOGI(kLogTag, "Assigned short address %d", adr_new_);
}
ass_state_ = AssigningState::kTerminate;
break;
}
case AssigningState::kTerminate:
dali_->terminate();
ass_state_ = AssigningState::kOff;
break;
}
}
// =============================================================================
// Bus State / Init Data
// =============================================================================
void KnxDaliModule::loopBusState() {
if (dali_ == nullptr) return;
dali_bus_state_ = true; // Simplified: assume bus is always OK
}
void KnxDaliModule::loopInitData() {
if (got_init_data_ || device_ == nullptr || !device_->configured()) return;
// Read initial device data from all channels
for (int i = 0; i < 64; i++) {
if (!addresses_[i]) continue;
channels_[i].setup();
}
got_init_data_ = true;
}
void KnxDaliModule::loopGroupState() {
if (last_changed_group_ == 255) return;
uint8_t group_idx = last_changed_group_ & 0x0F;
bool is_group = (last_changed_group_ & 0x80) != 0;
if (is_group && group_idx < 16) {
groups_[group_idx].setGroupState(group_idx, last_changed_value_);
} else if (!is_group && group_idx < 64) {
channels_[group_idx].setGroupState(group_idx, last_changed_value_);
}
last_changed_group_ = 255;
}
// =============================================================================
// processInputKo — KNX group write dispatch
// =============================================================================
void KnxDaliModule::processInputKo(GroupObject& ko) {
if (device_ == nullptr) return;
if (adr_state_ != AddressingState::kOff) return;
if (current_lock_state_) return;
uint16_t asap = ko.asap();
ESP_LOGD(kLogTag, "processInputKo asap=%d", asap);
// Channel KOs (64 channels x N group objects each)
int adr_relative = static_cast<int>(asap) - ADR_KoOffset;
if (adr_relative >= 0 && adr_relative < ADR_KoBlockSize * 64) {
int ch = adr_relative / ADR_KoBlockSize;
if (ch < 64) {
channels_[ch].processInputKo(ko);
return;
}
}
// Group KOs (16 groups x N group objects each)
int grp_relative = static_cast<int>(asap) - GRP_KoOffset;
if (grp_relative >= 0 && grp_relative < GRP_KoBlockSize * 16) {
int grp_idx = grp_relative / GRP_KoBlockSize;
int slot = grp_relative % GRP_KoBlockSize;
if (grp_idx < 16) {
groups_[grp_idx].processInputKo(ko);
// Track group state changes
if (slot == 0) { // switch state
last_changed_group_ = 0x80 | static_cast<uint8_t>(grp_idx);
}
return;
}
}
// HCL KOs
int hcl_relative = static_cast<int>(asap) - HCL_KoOffset;
if (hcl_relative >= 0 && hcl_relative < HCL_KoBlockSize * 3) {
int curve_idx = hcl_relative / HCL_KoBlockSize;
if (curve_idx < 3) {
// HCL: apply Kelvin to all channels and groups
KNXValue val = ko.value();
if (true) {
uint16_t kelvin = static_cast<uint16_t>(static_cast<float>(val));
for (int i = 0; i < 64; i++) {
channels_[i].setHcl(static_cast<uint8_t>(curve_idx), kelvin, 255);
}
for (int i = 0; i < 16; i++) {
groups_[i].setHcl(static_cast<uint8_t>(curve_idx), kelvin, 255);
}
}
return;
}
}
}
// =============================================================================
// Function Property Handlers (stubs — full port in subsequent iteration)
// =============================================================================
bool KnxDaliModule::processFunctionProperty(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length) {
// Only handle object 160, property 1 (REG1 DALI function properties)
if (object_index != 160 || property_id != 1 || length == 0 || data == nullptr) {
return false;
}
switch (data[0]) {
case 2: funcHandleType(data, result_data, result_length); return true;
case 3: funcHandleScan(data, result_data, result_length); return true;
case 4: funcHandleAssign(data, result_data, result_length); return true;
case 10: funcHandleEvgWrite(data, result_data, result_length); return true;
case 11: funcHandleEvgRead(data, result_data, result_length); return true;
case 12: funcHandleSetScene(data, result_data, result_length); return true;
case 13: funcHandleGetScene(data, result_data, result_length); return true;
case 14: funcHandleIdentify(data, result_data, result_length); return true;
default: return false;
}
}
bool KnxDaliModule::processFunctionPropertyState(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length) {
if (object_index != 160 || property_id != 1 || length == 0 || data == nullptr) {
return false;
}
switch (data[0]) {
case 3:
case 5: stateHandleScanAndAddress(data, result_data, result_length); return true;
case 4: stateHandleAssign(data, result_data, result_length); return true;
case 7: stateHandleFoundEVGs(data, result_data, result_length); return true;
default: return false;
}
}
// =============================================================================
// Function Property Implementations (simplified stubs)
// =============================================================================
void KnxDaliModule::funcHandleType(uint8_t*, uint8_t* result_data, uint8_t& result_length) {
// Query device type(s) for the selected short address
result_data[0] = 0; // working
result_length = 1;
}
void KnxDaliModule::funcHandleScan(uint8_t* data, uint8_t* result_data, uint8_t& result_length) {
if (data == nullptr) return;
adr_only_new_ = (data[1] & 0x01) != 0;
adr_randomize_ = (data[1] & 0x02) != 0;
adr_delete_all_ = (data[1] & 0x04) != 0;
adr_state_ = AddressingState::kInit;
result_data[0] = 0; // working
result_length = 1;
}
void KnxDaliModule::funcHandleAssign(uint8_t* data, uint8_t* result_data, uint8_t& result_length) {
if (data == nullptr) return;
adr_search_ = (static_cast<uint64_t>(data[1]) << 16) |
(static_cast<uint64_t>(data[2]) << 8) | data[3];
adr_new_ = data[4];
if (adr_new_ == 99) adr_new_ = 255; // "remove short address"
ass_state_ = AssigningState::kInit;
result_data[0] = 0;
result_length = 1;
}
void KnxDaliModule::funcHandleEvgWrite(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::funcHandleEvgRead(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::funcHandleSetScene(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::funcHandleGetScene(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::funcHandleIdentify(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::stateHandleType(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::stateHandleAssign(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::stateHandleScanAndAddress(uint8_t*, uint8_t*, uint8_t&) {}
void KnxDaliModule::stateHandleFoundEVGs(uint8_t*, uint8_t*, uint8_t&) {}
// =============================================================================
// Public state accessors
// =============================================================================
bool KnxDaliModule::isAddressingActive() const {
return adr_state_ != AddressingState::kOff || ass_state_ != AssigningState::kOff;
}
bool KnxDaliModule::isLocked() const { return current_lock_state_; }
void KnxDaliModule::setLocked(bool locked) { current_lock_state_ = locked; }
bool KnxDaliModule::isNight() const { return is_night_; }
void KnxDaliModule::setNight(bool night) { is_night_ = night; }
uint8_t KnxDaliModule::lastChangedGroup() const { return last_changed_group_; }
uint8_t KnxDaliModule::lastChangedValue() const { return last_changed_value_; }
KnxDaliChannel& KnxDaliModule::channel(int index) { return channels_[index]; }
KnxDaliChannel& KnxDaliModule::group(int index) { return groups_[index]; }
HclCurve& KnxDaliModule::curve(int index) { return curves_[index]; }
// =============================================================================
// DALI Send Helpers
// =============================================================================
uint8_t KnxDaliModule::sendMsg(MessageType type, uint8_t addr, uint8_t val,
uint8_t addr_type, bool wait) {
Message* msg = new Message();
msg->type = type;
msg->data = val;
msg->addrtype = addr_type;
msg->para1 = addr;
return queue_.push(msg);
}
uint8_t KnxDaliModule::sendCmd(uint8_t addr, uint8_t value, uint8_t addr_type, bool wait) {
return sendMsg(MessageType::Cmd, addr, value, addr_type, wait);
}
uint8_t KnxDaliModule::sendSpecialCmd(uint8_t command, uint8_t value, bool wait) {
return sendMsg(MessageType::SpecialCmd, command, value, 2, wait);
}
uint8_t KnxDaliModule::sendArc(uint8_t addr, uint8_t value, uint8_t addr_type) {
return sendMsg(MessageType::Arc, addr, value, addr_type, false);
}
int16_t KnxDaliModule::getInfo(uint8_t address, int command, uint8_t additional) {
(void)additional;
uint8_t msg_id = sendMsg(MessageType::Query, address, static_cast<uint8_t>(command), 0, true);
// Wait for response
for (int i = 0; i < 300; i++) {
vTaskDelay(pdMS_TO_TICKS(1));
int16_t resp = queue_.getResponse(msg_id);
if (resp != -200) return resp;
}
return -1;
}
// =============================================================================
// KO Handlers
// =============================================================================
void KnxDaliModule::koHandleSwitch(GroupObject& ko) {
KNXValue val = ko.value();
bool on = static_cast<bool>(val);
if (dali_ != nullptr) {
if (on) {
dali_->on({DaliTargetKind::kBroadcast, 0});
} else {
dali_->off({DaliTargetKind::kBroadcast, 0});
}
}
}
void KnxDaliModule::koHandleDimm(GroupObject& ko) {
KNXValue val = ko.value();
uint8_t percent = static_cast<uint8_t>(static_cast<float>(val) * 255.0f / 100.0f);
uint8_t arc = PercentToArc(static_cast<double>(percent) * 100.0 / 255.0);
if (dali_ != nullptr) {
dali_->setArc({DaliTargetKind::kBroadcast, 0}, arc);
}
}
void KnxDaliModule::koHandleDayNight(GroupObject& ko) {
is_night_ = static_cast<bool>(ko.value());
for (int i = 0; i < 64; i++) channels_[i].isNight = is_night_;
for (int i = 0; i < 16; i++) groups_[i].isNight = is_night_;
}
void KnxDaliModule::koHandleOnValue(GroupObject& ko) {
uint8_t on_value = static_cast<uint8_t>(static_cast<float>(ko.value()) * 255.0f / 100.0f);
for (int i = 0; i < 64; i++) channels_[i].setOnValue(on_value);
for (int i = 0; i < 16; i++) groups_[i].setOnValue(on_value);
}
void KnxDaliModule::koHandleScene(GroupObject& ko) {
uint8_t scene = static_cast<uint8_t>(ko.value());
if (dali_ != nullptr) {
dali_->goToScene({DaliTargetKind::kBroadcast, 0}, scene);
}
}
} // namespace knx_dali_gw
} // namespace gateway
@@ -0,0 +1,166 @@
#pragma once
// =============================================================================
// KnxDaliModule — Core DALI gateway module (ported from DaliModule)
// =============================================================================
// Handles:
// - DALI message queuing and execution
// - DALI commissioning (addressing + assigning state machines)
// - KNX group-object dispatch (processInputKo)
// - KNX function-property commands (ETS programming)
// - Broadcast switch/dim/scene handling
#include "dali_gateway_bridge.h"
#include "knx_dali_channel.h"
#include "hcl_curve.h"
#include "message_queue.h"
#include "ballast.hpp"
#include "knxprod.h"
#include "knx/group_object.h"
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
// Forward declarations
class Bau07B0;
namespace gateway {
namespace knx_dali_gw {
class KnxDaliModule {
public:
enum class AddressingState {
kOff, kInit, kInit2, kWriteDtr, kRemoveShort, kRemoveShort2,
kRandom, kRandom2, kRandomWait, kStartSearch, kSearchHigh,
kSearchMid, kSearchLow, kCompare, kGetShort, kCheckFound,
kProgramShort, kVerifyShort, kVerifyShortResponse, kWithdraw,
kTerminate, kSearchShort, kCheckSearchShort
};
enum class AssigningState {
kOff, kInit, kInit2, kQuery, kCheckQuery, kStartSearch,
kSearchHigh, kSearchMid, kSearchLow, kCompare, kCheckFound,
kWithdraw, kProgramShort, kVerifyShort, kVerifyShortResponse,
kTerminate
};
KnxDaliModule();
~KnxDaliModule();
// ---- Lifecycle ----
void setup(Bau07B0& device, DaliGatewayBridge& bridge);
void loop(bool configured);
// ---- KNX input ----
void processInputKo(GroupObject& ko);
// ---- Function properties (ETS programming) ----
bool processFunctionProperty(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length);
bool processFunctionPropertyState(uint8_t object_index, uint8_t property_id,
uint8_t length, uint8_t* data,
uint8_t* result_data, uint8_t& result_length);
// ---- Public state ----
bool isAddressingActive() const;
bool isLocked() const;
void setLocked(bool locked);
bool isNight() const;
void setNight(bool night);
uint8_t lastChangedGroup() const;
uint8_t lastChangedValue() const;
// ---- Channel / group access ----
KnxDaliChannel& channel(int index);
KnxDaliChannel& group(int index);
HclCurve& curve(int index);
private:
// ---- DALI helpers ----
uint8_t sendMsg(MessageType type, uint8_t addr, uint8_t value,
uint8_t addr_type = 0, bool wait = false);
uint8_t sendCmd(uint8_t addr, uint8_t value, uint8_t addr_type = 0,
bool wait = false);
uint8_t sendSpecialCmd(uint8_t command, uint8_t value = 0, bool wait = false);
uint8_t sendArc(uint8_t addr, uint8_t value, uint8_t addr_type);
int16_t getInfo(uint8_t address, int command, uint8_t additional = 0);
// ---- KNX KO handlers ----
void koHandleSwitch(GroupObject& ko);
void koHandleDimm(GroupObject& ko);
void koHandleDayNight(GroupObject& ko);
void koHandleOnValue(GroupObject& ko);
void koHandleScene(GroupObject& ko);
// ---- Function property handlers ----
void funcHandleType(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void funcHandleScan(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void funcHandleAssign(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void funcHandleEvgWrite(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void funcHandleEvgRead(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void funcHandleSetScene(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void funcHandleGetScene(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void funcHandleIdentify(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
// ---- State handlers ----
void stateHandleType(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void stateHandleAssign(uint8_t* data, uint8_t* result_data, uint8_t& result_length);
void stateHandleScanAndAddress(uint8_t* data, uint8_t* result_data,
uint8_t& result_length);
void stateHandleFoundEVGs(uint8_t* data, uint8_t* result_data,
uint8_t& result_length);
// ---- Loops ----
void loopMessages();
void loopAddressing();
void loopAssigning();
void loopBusState();
void loopInitData();
void loopGroupState();
// ---- State ----
Bau07B0* device_{nullptr};
DaliGatewayBridge* dali_{nullptr};
// Addressing / commissioning
AddressingState adr_state_{AddressingState::kOff};
AssigningState ass_state_{AssigningState::kOff};
Ballast ballasts_[64];
bool addresses_[64]{};
int adr_found_{0};
uint8_t adr_new_{0};
uint8_t last_bus_state_{2};
uint8_t adr_iterations_{0};
uint64_t adr_search_{0};
bool adr_assign_{false};
bool adr_only_new_{false};
bool adr_randomize_{false};
bool adr_delete_all_{false};
// Group state
uint8_t last_changed_group_{255};
uint8_t last_changed_value_{0};
// Bus
bool got_init_data_{false};
bool dali_bus_state_{true};
bool dali_bus_state_to_set_{true};
uint64_t dali_state_last_{1};
// Lock / night
bool current_lock_state_{false};
bool is_night_{false};
// Channels / groups / curves
KnxDaliChannel channels_[64];
KnxDaliChannel groups_[16];
HclCurve curves_[3];
MessageQueue queue_;
};
} // namespace knx_dali_gw
} // namespace gateway
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
enum class MessageType {
Arc,
Cmd,
SpecialCmd,
Query
};
struct Message {
Message *next{nullptr};
uint8_t data{0};
MessageType type{MessageType::Arc};
uint8_t para1{0};
uint8_t addrtype{0};
uint8_t para2{0};
bool wait{false};
uint8_t id{0};
};
@@ -0,0 +1,74 @@
#include "message_queue.h"
#include "esp_timer.h"
uint8_t MessageQueue::push(Message *msg)
{
while(isLocked) ;
isLocked = true;
msg->next = nullptr;
if(tail == nullptr)
{
head = msg;
tail = msg;
isLocked = false;
return msg->id;
}
tail->next = msg;
tail = msg;
isLocked = false;
return msg->id;
}
bool MessageQueue::pop(Message &msg)
{
unsigned long started = esp_timer_get_time() / 1000ULL;
while(isLocked && ((esp_timer_get_time() / 1000ULL) - started < 3000)) ;
if(isLocked || head == nullptr) return false;
isLocked = true;
msg.addrtype = head->addrtype;
msg.data = head->data;
msg.id = head->id;
msg.para1 = head->para1;
msg.para2 = head->para2;
msg.type = head->type;
msg.wait = head->wait;
Message *temp = head;
if(head->next == nullptr)
{
head = nullptr;
tail = nullptr;
} else {
head = head->next;
}
delete temp;
isLocked = false;
return true;
}
uint8_t MessageQueue::getNextId()
{
currentId++;
if(currentId == 0) currentId++;
responses[currentId] = -200;
return currentId;
}
void MessageQueue::setResponse(uint8_t id, int16_t value)
{
responses[id] = value;
}
int16_t MessageQueue::getResponse(uint8_t id)
{
return responses[id];
}
@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
#include "message.hpp"
class MessageQueue
{
public:
uint8_t push(Message *msg);
bool pop(Message &msg);
uint8_t getNextId();
void setResponse(uint8_t id, int16_t value);
int16_t getResponse(uint8_t id);
private:
Message *head;
Message *tail;
uint8_t currentId = 0;
int16_t responses[256];
bool isLocked = false;
};
-78
View File
@@ -1,78 +0,0 @@
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"
)
if(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
list(APPEND OPENKNX_SRCS
"${OPENKNX_ROOT}/src/knx/aes.c"
)
endif()
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_device_runtime.cpp"
"src/ets_memory_loader.cpp"
"src/security_storage.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_system
esp_timer
esp_wifi
freertos
log
lwip
mbedtls
nvs_flash
)
target_compile_definitions(${COMPONENT_LIB} PUBLIC
MASK_VERSION=0x07B0
KNX_FLASH_SIZE=4096
KNX_NO_AUTOMATIC_GLOBAL_INSTANCE
KNX_NO_SPI
USE_CEMI_SERVER
)
if(CONFIG_GATEWAY_KNX_DATA_SECURE_SUPPORTED)
target_compile_definitions(${COMPONENT_LIB} PUBLIC USE_DATASECURE)
endif()
target_compile_options(${COMPONENT_LIB} PRIVATE
-Wno-unused-parameter
)
set_property(TARGET ${COMPONENT_LIB} PROPERTY CXX_STANDARD 17)
-59
View File
@@ -1,59 +0,0 @@
#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);
@@ -1,66 +0,0 @@
#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:
using OutboundCemiFrameCallback = bool (*)(CemiFrame& frame, void* context);
explicit EspIdfPlatform(TPUart::Interface::Abstract* interface = nullptr,
const char* nvs_namespace = "openknx");
~EspIdfPlatform() override;
void outboundCemiFrameCallback(OutboundCemiFrameCallback callback, void* context);
bool handleOutboundCemiFrame(CemiFrame& frame) 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};
OutboundCemiFrameCallback outbound_cemi_frame_callback_{nullptr};
void* outbound_cemi_frame_context_{nullptr};
};
} // namespace gateway::openknx
@@ -1,16 +0,0 @@
#pragma once
#include "openknx_idf/ets_memory_loader.h"
#include "openknx_idf/ets_device_runtime.h"
#include "openknx_idf/esp_idf_platform.h"
#include "openknx_idf/security_storage.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
@@ -1,43 +0,0 @@
#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,
bool nine_bit_mode = true);
~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_;
bool nine_bit_mode_;
std::atomic_bool overflow_{false};
std::function<bool()> callback_;
};
} // namespace gateway::openknx
@@ -1,180 +0,0 @@
#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"); }
@@ -1,316 +0,0 @@
#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";
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;
}
esp_netif_t* findDefaultNetif() {
constexpr const char* kPreferredIfKeys[] = {"ETH_DEF", "WIFI_STA_DEF", "WIFI_AP_DEF"};
for (const char* key : kPreferredIfKeys) {
auto* 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) {
return netif;
}
}
for (const char* key : kPreferredIfKeys) {
if (auto* netif = esp_netif_get_handle_from_ifkey(key)) {
return netif;
}
}
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::outboundCemiFrameCallback(OutboundCemiFrameCallback callback,
void* context) {
outbound_cemi_frame_callback_ = callback;
outbound_cemi_frame_context_ = context;
}
bool EspIdfPlatform::handleOutboundCemiFrame(CemiFrame& frame) {
if (outbound_cemi_frame_callback_ == nullptr) {
return false;
}
return outbound_cemi_frame_callback_(frame, outbound_cemi_frame_context_);
}
void EspIdfPlatform::networkInterface(esp_netif_t* netif) { netif_ = netif; }
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 (!readBaseMac(data)) {
std::memset(data, 0, 6);
}
}
uint32_t EspIdfPlatform::uniqueSerialNumber() {
uint8_t mac[6]{};
macAddress(mac);
return (static_cast<uint32_t>(mac[2]) << 24) | (static_cast<uint32_t>(mac[3]) << 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_UDP);
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;
const uint32_t local_address = currentIpAddress();
bind_addr.sin_addr.s_addr = local_address == 0 ? htonl(INADDR_ANY) : local_address;
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 = local_address == 0 ? htonl(INADDR_ANY) : local_address;
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);
}
if (local_address != 0) {
in_addr multicast_interface{};
multicast_interface.s_addr = local_address;
if (setsockopt(udp_sock_, IPPROTO_IP, IP_MULTICAST_IF, &multicast_interface,
sizeof(multicast_interface)) < 0) {
ESP_LOGW(kTag, "failed to select KNX multicast interface: 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
@@ -1,147 +0,0 @@
#include "openknx_idf/tpuart_uart_interface.h"
#include "esp_log.h"
#include "soc/uart_periph.h"
#include <utility>
namespace gateway::openknx {
namespace {
constexpr const char* kTag = "openknx_tpuart";
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;
}
} // namespace
TpuartUartInterface::TpuartUartInterface(uart_port_t uart_port, int tx_pin, int rx_pin,
size_t rx_buffer_size, size_t tx_buffer_size,
bool nine_bit_mode)
: uart_port_(uart_port),
tx_pin_(tx_pin),
rx_pin_(rx_pin),
rx_buffer_size_(rx_buffer_size),
tx_buffer_size_(tx_buffer_size),
nine_bit_mode_(nine_bit_mode) {}
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 = nine_bit_mode_ ? UART_PARITY_EVEN : UART_PARITY_DISABLE;
config.stop_bits = UART_STOP_BITS_1;
config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
config.source_clk = UART_SCLK_DEFAULT;
int tx_pin = UART_PIN_NO_CHANGE;
int rx_pin = UART_PIN_NO_CHANGE;
if (!ResolveUartIoPin(uart_port_, tx_pin_, SOC_UART_TX_PIN_IDX, &tx_pin) ||
!ResolveUartIoPin(uart_port_, rx_pin_, SOC_UART_RX_PIN_IDX, &rx_pin)) {
ESP_LOGE(kTag, "UART%d has no ESP-IDF default TX/RX pin; configure explicit pins",
uart_port_);
return;
}
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, rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
if (err != ESP_OK) {
ESP_LOGE(kTag, "failed to route UART%d pins tx=%d rx=%d: %s", uart_port_, tx_pin,
rx_pin, 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