343 lines
10 KiB
C++
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);
|
|
}
|