knxdisplay/main/KnxWorker.cpp
2026-01-30 11:15:56 +01:00

343 lines
10 KiB
C++

#include "KnxWorker.hpp"
#include "WidgetManager.hpp"
#include "esp32_idf_platform.h"
#include "knx_facade.h"
#include "knx/bau07B0.h"
#include "knx/group_object.h"
#include "knx/group_object_table_object.h"
#include "knx/dpt.h"
#include "esp_log.h"
#include <esp_timer.h>
#include "esp_system.h"
#include "nvs.h"
#include <cstdio>
#include <cstring>
#define TAG "KNXWORKER"
#define MASK_VERSION 0x07B0
// Set to true to enable raw UART debug mode (bypasses KNX library)
#define UART_DEBUG_MODE false
Esp32IdfPlatform knxPlatform(UART_NUM_1); // Use UART_NUM_1, change if needed
Bau07B0 knxBau(knxPlatform);
KnxFacade<Esp32IdfPlatform, Bau07B0> knx(knxBau);
KnxWorker::KnxWorker() {}
namespace {
constexpr char kKnxNvsNamespace[] = "knx";
constexpr char kKnxSerialKey[] = "serial_bau";
constexpr uint8_t kKnxHardwareType[6] = {0x00, 0x00, 0xAB, 0xCE, 0x04, 0x00};
constexpr uint16_t kKnxHardwareVersion = 1;
bool loadKnxBauNumber(uint32_t& outValue) {
nvs_handle_t handle;
esp_err_t err = nvs_open(kKnxNvsNamespace, NVS_READONLY, &handle);
if (err != ESP_OK) {
return false;
}
uint32_t value = 0;
err = nvs_get_u32(handle, kKnxSerialKey, &value);
nvs_close(handle);
if (err != ESP_OK) {
return false;
}
outValue = value;
return true;
}
bool saveKnxBauNumber(uint32_t value) {
nvs_handle_t handle;
esp_err_t err = nvs_open(kKnxNvsNamespace, NVS_READWRITE, &handle);
if (err != ESP_OK) {
return false;
}
err = nvs_set_u32(handle, kKnxSerialKey, value);
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
return err == ESP_OK;
}
uint32_t generateRandomBauNumber() {
uint32_t value = esp_random();
if (value == 0) {
value = 1;
}
return value;
}
uint16_t resolveGroupAddress(uint16_t asap) {
if (!knxBau.configured()) {
return 0;
}
int32_t tsap = knxBau.associationTable().translateAsap(asap);
if (tsap < 0) {
return 0;
}
return knxBau.addressTable().getGroupAddress(static_cast<uint16_t>(tsap));
}
} // namespace
void KnxWorker::init() {
ESP_LOGI(TAG, "INIT");
knxPlatform.knxUartPins(48, 53); // Set RX=48, TX=53
knxPlatform.knxUartBaudRate(19200);
knxPlatform.setupUart();
#if !UART_DEBUG_MODE
knx.bau().deviceObject().hardwareType(kKnxHardwareType);
knx.bau().deviceObject().version(kKnxHardwareVersion);
knx.readMemory();
uint32_t bauNumberOverride = 0;
if (loadKnxBauNumber(bauNumberOverride)) {
knx.bau().deviceObject().manufacturerId(0x00FA);
knx.bau().deviceObject().bauNumber(bauNumberOverride);
ESP_LOGI(TAG, "Applied KNX serial override: %04X%08lX", 0x00FA, (unsigned long)bauNumberOverride);
}
// Register callbacks for all group objects to forward updates to the GUI
size_t goCount = getGroupObjectCount();
if (goCount == 0) {
ESP_LOGW(TAG, "No KNX group objects configured; skipping callbacks");
} else {
for (size_t i = 1; i <= goCount; i++) {
GroupObject& go = knx.getGroupObject(i);
go.callback([](GroupObject& go) {
uint16_t groupAddr = resolveGroupAddress(go.asap());
if (groupAddr == 0) {
return;
}
KNXValue switchValue = false;
if (go.tryValue(switchValue, DPT_Switch)) {
WidgetManager::instance().onKnxSwitch(groupAddr, static_cast<bool>(switchValue));
}
KNXValue tempValue = 0.0f;
if (go.tryValue(tempValue, DPT_Value_Temp)) {
WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(tempValue),
TextSource::KNX_DPT_TEMP);
}
KNXValue percentValue = 0.0f;
if (go.tryValue(percentValue, DPT_Scaling)) {
WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(percentValue),
TextSource::KNX_DPT_PERCENT);
}
KNXValue factorValue = (uint8_t)0;
if (go.tryValue(factorValue, DPT_DecimalFactor)) {
WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(factorValue),
TextSource::KNX_DPT_DECIMALFACTOR);
}
KNXValue powerValue = 0.0f;
if (go.tryValue(powerValue, DPT_Value_Power)) {
WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(powerValue),
TextSource::KNX_DPT_POWER);
}
KNXValue energyValue = (int32_t)0;
if (go.tryValue(energyValue, DPT_ActiveEnergy_kWh)) {
WidgetManager::instance().onKnxValue(groupAddr, static_cast<float>(energyValue),
TextSource::KNX_DPT_ENERGY);
}
struct tm timeTm = {};
KNXValue timeValue(timeTm);
if (go.tryValue(timeValue, DPT_TimeOfDay)) {
WidgetManager::instance().onKnxTime(groupAddr, static_cast<struct tm>(timeValue),
KnxTimeType::TIME);
}
struct tm dateTm = {};
KNXValue dateValue(dateTm);
if (go.tryValue(dateValue, DPT_Date)) {
WidgetManager::instance().onKnxTime(groupAddr, static_cast<struct tm>(dateValue),
KnxTimeType::DATE);
}
struct tm dateTimeTm = {};
KNXValue dateTimeValue(dateTimeTm);
if (go.tryValue(dateTimeValue, DPT_DateTime)) {
WidgetManager::instance().onKnxTime(groupAddr, static_cast<struct tm>(dateTimeValue),
KnxTimeType::DATETIME);
}
KNXValue textValue = "";
if (go.tryValue(textValue, DPT_String_8859_1) ||
go.tryValue(textValue, DPT_String_ASCII)) {
const char* raw = static_cast<const char*>(textValue);
size_t maxLen = go.valueSize();
if (maxLen > 14) {
maxLen = 14;
}
size_t len = strnlen(raw, maxLen);
char buf[15];
memcpy(buf, raw, len);
buf[len] = '\0';
WidgetManager::instance().onKnxText(groupAddr, buf);
}
});
}
}
knx.start();
ESP_LOGI(TAG, "FINISH");
#else
ESP_LOGI(TAG, "UART DEBUG MODE - KNX library disabled");
ESP_LOGI(TAG, "Sending U_STATE_REQ (0x02) to NCN5130...");
knxPlatform.writeUart(0x02); // U_STATE_REQ command
ESP_LOGI(TAG, "Waiting for response...");
#endif
}
static uint32_t lastStateReq = 0;
void KnxWorker::toggleProgMode() {
#if !UART_DEBUG_MODE
knx.toggleProgMode();
#endif
}
bool KnxWorker::getProgMode() {
#if !UART_DEBUG_MODE
return knx.progMode();
#else
return false;
#endif
}
void KnxWorker::setProgMode(bool enabled) {
#if !UART_DEBUG_MODE
knx.progMode(enabled);
#else
(void)enabled;
#endif
}
void KnxWorker::clearSettings() {
#if !UART_DEBUG_MODE
if (knxResetState_ == 0) {
uint32_t bauNumber = generateRandomBauNumber();
bool stored = saveKnxBauNumber(bauNumber);
knx.bau().deviceObject().manufacturerId(0x00FA);
knx.bau().deviceObject().bauNumber(bauNumber);
if (stored) {
ESP_LOGI(TAG, "KNX serial randomized to %04X%08lX", 0x00FA, (unsigned long)bauNumber);
} else {
ESP_LOGW(TAG, "Failed to persist randomized KNX serial");
}
knxResetState_ = 1;
}
#endif
}
#include "HistoryStore.hpp"
// ... imports ...
void KnxWorker::loop() {
// Check for auto-save (handled by HistoryStore logic)
HistoryStore::instance().performAutoSave();
#if UART_DEBUG_MODE
// Periodically send U_STATE_REQ to test TX direction
uint32_t now = millis();
if (now - lastStateReq > 2000) {
lastStateReq = now;
ESP_LOGI(TAG, "TX: Sending U_STATE_REQ (0x02)");
knxPlatform.writeUart(0x02);
}
// Raw UART debug - read and print all incoming bytes
int available = knxPlatform.uartAvailable();
if (available > 0) {
ESP_LOGI(TAG, "UART RX: %d bytes available", available);
char hexbuf[128];
int pos = 0;
while (knxPlatform.uartAvailable() > 0 && pos < 120) {
int byte = knxPlatform.readUart();
if (byte >= 0) {
pos += snprintf(hexbuf + pos, sizeof(hexbuf) - pos, "%02X ", byte);
}
}
if (pos > 0) {
ESP_LOGI(TAG, "Data: %s", hexbuf);
}
}
#else
if (knxResetState_ != 0) {
uint32_t nowMs = (uint32_t)(esp_timer_get_time() / 1000);
if (knxResetState_ == 1) {
knx.bau().memory().clearMemory();
knxResetAtMs_ = nowMs + 300;
knxResetState_ = 2;
} else if (knxResetState_ == 2) {
if ((int32_t)(nowMs - knxResetAtMs_) >= 0) {
knxResetState_ = 0;
knx.bau().platform().restart();
}
}
}
knx.loop();
#endif
}
size_t KnxWorker::getGroupObjectCount() {
#if !UART_DEBUG_MODE
// Get the group object table from BAU
GroupObjectTableObject& goTable = knxBau.groupObjectTable();
return goTable.entryCount();
#else
return 0;
#endif
}
bool KnxWorker::getGroupObjectInfo(size_t index, KnxGroupObjectInfo& info) {
#if !UART_DEBUG_MODE
GroupObjectTableObject& goTable = knxBau.groupObjectTable();
if (index == 0 || index > goTable.entryCount()) {
return false;
}
// Get group object (1-based index)
GroupObject& go = knx.getGroupObject(index);
info.goIndex = index;
info.dptMain = 0;
info.dptSub = 0;
info.commFlag = go.commFlag();
info.readFlag = go.readEnable();
info.writeFlag = go.writeEnable();
// Resolve the primary group address via association/address tables
info.groupAddress = resolveGroupAddress(static_cast<uint16_t>(index));
return true;
#else
(void)index;
(void)info;
return false;
#endif
}
void KnxWorker::formatGroupAddress(uint16_t addr, char* buf, size_t bufSize) {
// Format: main/middle/sub (5/3/8 bit)
uint8_t main = (addr >> 11) & 0x1F;
uint8_t middle = (addr >> 8) & 0x07;
uint8_t sub = addr & 0xFF;
snprintf(buf, bufSize, "%d/%d/%d", main, middle, sub);
}