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:
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user