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:
@@ -6,6 +6,7 @@ endif()
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../../components"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../../knx"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/../../../dali_cpp"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
#include "openknx_idf/security_storage.h"
|
||||
#include "security_storage.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
@@ -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)
|
||||
+2
-2
@@ -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
-1
@@ -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"
|
||||
+2
-2
@@ -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"
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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)
|
||||
@@ -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
|
||||
+1
-1
Submodule knx updated: 346b704cbe...dcf565dc03
Reference in New Issue
Block a user