#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 #include "esp_system.h" #include "nvs.h" #include #include #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 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(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(switchValue)); } KNXValue tempValue = 0.0f; if (go.tryValue(tempValue, DPT_Value_Temp)) { WidgetManager::instance().onKnxValue(groupAddr, static_cast(tempValue), TextSource::KNX_DPT_TEMP); } KNXValue percentValue = 0.0f; if (go.tryValue(percentValue, DPT_Scaling)) { WidgetManager::instance().onKnxValue(groupAddr, static_cast(percentValue), TextSource::KNX_DPT_PERCENT); } KNXValue factorValue = (uint8_t)0; if (go.tryValue(factorValue, DPT_DecimalFactor)) { WidgetManager::instance().onKnxValue(groupAddr, static_cast(factorValue), TextSource::KNX_DPT_DECIMALFACTOR); } KNXValue powerValue = 0.0f; if (go.tryValue(powerValue, DPT_Value_Power)) { WidgetManager::instance().onKnxValue(groupAddr, static_cast(powerValue), TextSource::KNX_DPT_POWER); } KNXValue energyValue = (int32_t)0; if (go.tryValue(energyValue, DPT_ActiveEnergy_kWh)) { WidgetManager::instance().onKnxValue(groupAddr, static_cast(energyValue), TextSource::KNX_DPT_ENERGY); } struct tm timeTm = {}; KNXValue timeValue(timeTm); if (go.tryValue(timeValue, DPT_TimeOfDay)) { WidgetManager::instance().onKnxTime(groupAddr, static_cast(timeValue), KnxTimeType::TIME); } struct tm dateTm = {}; KNXValue dateValue(dateTm); if (go.tryValue(dateValue, DPT_Date)) { WidgetManager::instance().onKnxTime(groupAddr, static_cast(dateValue), KnxTimeType::DATE); } struct tm dateTimeTm = {}; KNXValue dateTimeValue(dateTimeTm); if (go.tryValue(dateTimeValue, DPT_DateTime)) { WidgetManager::instance().onKnxTime(groupAddr, static_cast(dateTimeValue), KnxTimeType::DATETIME); } KNXValue textValue = ""; if (go.tryValue(textValue, DPT_String_8859_1) || go.tryValue(textValue, DPT_String_ASCII)) { const char* raw = static_cast(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(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); }