diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 61c920b3..a7e3511d 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -77,7 +77,7 @@ jobs: VERSION: ${{ steps.rename-binary-files.outputs.name }} - name: Create Artifact - run: zip --junk-paths ${{ steps.rename-binary-files.outputs.name }}.zip src/firmware/* rc/firmware/s3/* User_Manual.md + run: zip --junk-paths ${{ steps.rename-binary-files.outputs.name }}.zip src/firmware/* User_Manual.md - name: Upload Release id: upload-release diff --git a/src/CHANGES.md b/src/CHANGES.md index 8c1a56f0..7c243356 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,17 @@ # Development Changes +## 0.8.35 - 2023-12-30 +* added dim option for LEDS +* changed reload time for opendtufusion after update to 5s +* fix default interval and gap for communication +* fix serial number in exported json (was decimal, now correct as hexdecimal number) +* beautified factory reset +* added second stage for erase settings +* increased maximal number of inverters to 32 for opendtufusion board (ESP32-S3) +* fixed crash if CMT inverter is enabled, but CMT isn't configured + +# RELEASE 0.8.34 - 2023-12-29 + ## 0.8.33 - 2023-12-29 * improved communication thx @rejoe2 diff --git a/src/app.cpp b/src/app.cpp index 22e2f045..0bba14da 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -102,14 +102,14 @@ void app::setup() { //----------------------------------------------------------------------------- void app::loop(void) { - ah::Scheduler::loop(); - if(mConfig->nrf.enabled) mNrfRadio.loop(); #if defined(ESP32) if(mConfig->cmt.enabled) mCmtRadio.loop(); #endif + + ah::Scheduler::loop(); mCommunication.loop(); if (mMqttEnabled && mNetworkConnected) @@ -354,6 +354,9 @@ void app::tickSend(void) { continue; } + if(!iv->radio->isChipConnected()) + continue; + iv->tickSend([this, iv](uint8_t cmd, bool isDevControl) { if(isDevControl) mCommunication.addImportant(iv, cmd); @@ -449,22 +452,22 @@ void app::mqttSubRxCb(JsonObject obj) { //----------------------------------------------------------------------------- void app::setupLed(void) { - uint8_t led_off = (mConfig->led.led_high_active) ? LOW : HIGH; + uint8_t led_off = (mConfig->led.high_active) ? 0 : 255; if (mConfig->led.led0 != DEF_PIN_OFF) { pinMode(mConfig->led.led0, OUTPUT); - digitalWrite(mConfig->led.led0, led_off); + analogWrite(mConfig->led.led0, led_off); } if (mConfig->led.led1 != DEF_PIN_OFF) { pinMode(mConfig->led.led1, OUTPUT); - digitalWrite(mConfig->led.led1, led_off); + analogWrite(mConfig->led.led1, led_off); } } //----------------------------------------------------------------------------- void app::updateLed(void) { - uint8_t led_off = (mConfig->led.led_high_active) ? LOW : HIGH; - uint8_t led_on = (mConfig->led.led_high_active) ? HIGH : LOW; + uint8_t led_off = (mConfig->led.high_active) ? 0 : 255; + uint8_t led_on = (mConfig->led.high_active) ? (mConfig->led.luminance) : (255-mConfig->led.luminance); if (mConfig->led.led0 != DEF_PIN_OFF) { Inverter<> *iv; @@ -473,20 +476,20 @@ void app::updateLed(void) { if (NULL != iv) { if (iv->isProducing()) { // turn on when at least one inverter is producing - digitalWrite(mConfig->led.led0, led_on); + analogWrite(mConfig->led.led0, led_on); break; } else if(iv->config->enabled) - digitalWrite(mConfig->led.led0, led_off); + analogWrite(mConfig->led.led0, led_off); } } } if (mConfig->led.led1 != DEF_PIN_OFF) { if (getMqttIsConnected()) { - digitalWrite(mConfig->led.led1, led_on); + analogWrite(mConfig->led.led1, led_on); } else { - digitalWrite(mConfig->led.led1, led_off); + analogWrite(mConfig->led.led1, led_off); } } } diff --git a/src/config/config.h b/src/config/config.h index c8fd86dc..2cb9bcd3 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -153,7 +153,11 @@ // number of configurable inverters #if defined(ESP32) - #define MAX_NUM_INVERTERS 16 + #if defined(CONFIG_IDF_TARGET_ESP32S3) + #define MAX_NUM_INVERTERS 32 + #else + #define MAX_NUM_INVERTERS 16 + #endif #else #define MAX_NUM_INVERTERS 4 #endif diff --git a/src/config/settings.h b/src/config/settings.h index 8acb0942..fe2ad1b0 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -30,7 +30,7 @@ * https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout * */ -#define CONFIG_VERSION 6 +#define CONFIG_VERSION 7 #define PROT_MASK_INDEX 0x0001 @@ -117,9 +117,10 @@ typedef struct { } cfgSerial_t; typedef struct { - uint8_t led0; // first LED pin - uint8_t led1; // second LED pin - bool led_high_active; // determines if LEDs are high or low active + uint8_t led0; // first LED pin + uint8_t led1; // second LED pin + bool high_active; // determines if LEDs are high or low active + uint8_t luminance; // luminance of LED } cfgLed_t; typedef struct { @@ -424,7 +425,7 @@ class settings { mCfg.serial.showIv = false; mCfg.serial.debug = false; mCfg.serial.privacyLog = true; - mCfg.serial.printWholeTrace = true; + mCfg.serial.printWholeTrace = false; mCfg.mqtt.port = DEF_MQTT_PORT; snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER); @@ -440,7 +441,7 @@ class settings { mCfg.inst.startWithoutTime = false; mCfg.inst.rstMaxValsMidNight = false; mCfg.inst.yieldEffiency = 1.0f; - mCfg.inst.gapMs = 2000; + mCfg.inst.gapMs = 500; mCfg.inst.readGrid = true; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { @@ -450,11 +451,10 @@ class settings { mCfg.inst.iv[i].add2Total = true; } - mCfg.led.led0 = DEF_LED0; - mCfg.led.led1 = DEF_LED1; - mCfg.led.led_high_active = LED_HIGH_ACTIVE; - - memset(&mCfg.inst, 0, sizeof(cfgInst_t)); + mCfg.led.led0 = DEF_LED0; + mCfg.led.led1 = DEF_LED1; + mCfg.led.high_active = LED_HIGH_ACTIVE; + mCfg.led.luminance = 255; mCfg.plugin.display.pwrSaveAtIvOffline = false; mCfg.plugin.display.contrast = 60; @@ -493,6 +493,9 @@ class settings { mCfg.inst.gapMs = 500; mCfg.inst.readGrid = true; } + if(mCfg.configVersion < 7) { + mCfg.led.luminance = 255; + } } } @@ -667,13 +670,15 @@ class settings { void jsonLed(JsonObject obj, bool set = false) { if(set) { - obj[F("0")] = mCfg.led.led0; - obj[F("1")] = mCfg.led.led1; - obj[F("act_high")] = mCfg.led.led_high_active; + obj[F("0")] = mCfg.led.led0; + obj[F("1")] = mCfg.led.led1; + obj[F("act_high")] = mCfg.led.high_active; + obj[F("lum")] = mCfg.led.luminance; } else { getVal(obj, F("0"), &mCfg.led.led0); getVal(obj, F("1"), &mCfg.led.led1); - getVal(obj, F("act_high"), &mCfg.led.led_high_active); + getVal(obj, F("act_high"), &mCfg.led.high_active); + getVal(obj, F("lum"), &mCfg.led.luminance); } } diff --git a/src/defines.h b/src/defines.h index 6eadb705..2b0909cd 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 33 +#define VERSION_PATCH 35 //------------------------------------- typedef struct { diff --git a/src/web/RestApi.h b/src/web/RestApi.h index f3d70d26..a74f4f14 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -88,6 +88,10 @@ class RestApi { else if(path == "html/logout") getHtmlLogout(request, root); else if(path == "html/reboot") getHtmlReboot(request, root); else if(path == "html/save") getHtmlSave(request, root); + else if(path == "html/erase") getHtmlErase(request, root); + else if(path == "html/erasetrue") getHtmlEraseTrue(request, root); + else if(path == "html/factory") getHtmlFactory(request, root); + else if(path == "html/factorytrue") getHtmlFactoryTrue(request, root); else if(path == "system") getSysInfo(request, root); else if(path == "generic") getGeneric(request, root); else if(path == "reboot") getReboot(request, root); @@ -214,6 +218,16 @@ class RestApi { tmp.remove(i, tmp.indexOf("\"", i)-i); } } + i = 0; + // convert all serial numbers to hexadecimal + while (i != -1) { + i = tmp.indexOf("\"sn\":", i); + if(-1 != i) { + i+=5; + String sn = tmp.substring(i, tmp.indexOf("\"", i)-1); + tmp.replace(sn, String(atoll(sn.c_str()), HEX)); + } + } response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp); } @@ -337,6 +351,40 @@ class RestApi { #endif } + void getHtmlErase(AsyncWebServerRequest *request, JsonObject obj) { + getGeneric(request, obj.createNestedObject(F("generic"))); + obj[F("html")] = F("Erase settings (not WiFi)? yes no"); + } + + void getHtmlEraseTrue(AsyncWebServerRequest *request, JsonObject obj) { + getGeneric(request, obj.createNestedObject(F("generic"))); + mApp->eraseSettings(false); + mApp->setRebootFlag(); + obj[F("html")] = F("Erase settings: success"); + #if defined(ETHERNET) && defined(CONFIG_IDF_TARGET_ESP32S3) + obj[F("reload")] = 5; + #else + obj[F("reload")] = 20; + #endif + } + + void getHtmlFactory(AsyncWebServerRequest *request, JsonObject obj) { + getGeneric(request, obj.createNestedObject(F("generic"))); + obj[F("html")] = F("Factory reset? yes no"); + } + + void getHtmlFactoryTrue(AsyncWebServerRequest *request, JsonObject obj) { + getGeneric(request, obj.createNestedObject(F("generic"))); + mApp->eraseSettings(true); + mApp->setRebootFlag(); + obj[F("html")] = F("Factory reset: success"); + #if defined(ETHERNET) && defined(CONFIG_IDF_TARGET_ESP32S3) + obj[F("reload")] = 5; + #else + obj[F("reload")] = 20; + #endif + } + void getReboot(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = 10; @@ -564,7 +612,8 @@ class RestApi { obj[F("miso")] = mConfig->nrf.pinMiso; obj[F("led0")] = mConfig->led.led0; obj[F("led1")] = mConfig->led.led1; - obj[F("led_high_active")] = mConfig->led.led_high_active; + obj[F("led_high_active")] = mConfig->led.high_active; + obj[F("led_lum")] = mConfig->led.luminance; } #if defined(ESP32) diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 8e415c2c..f172f925 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -918,8 +918,12 @@ ml("div", { class: "row mb-3" }, [ ml("div", { class: "col-12 col-sm-3 my-2" }, "LED polarity"), ml("div", { class: "col-12 col-sm-9" }, - sel('pinLedHighActive', led_high_active, obj['led_high_active']) + sel('pinLedHighActive', led_high_active, obj.led_high_active) ) + ]), + ml("div", { class: "row mb-3" }, [ + ml("div", { class: "col-12 col-sm-3 my-2" }, "LED luminance (0-255)"), + ml("div", { class: "col-12 col-sm-9" }, ml("input", {class: "text", type: "number", name: "pinLedLum", value: obj.led_lum, min: 0, max: 255}, null)) ]) ) } diff --git a/src/web/html/system.html b/src/web/html/system.html index ef487ad5..eb25d5fa 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -126,8 +126,7 @@ meta.httpEquiv = "refresh" meta.content = obj.refresh + "; URL=" + obj.refresh_url; document.getElementsByTagName('head')[0].appendChild(meta); - } - else { + } else if(null != obj.system) { parseRadio(obj.system); parseMqtt(obj.system.mqtt); parseSysInfo(obj.system); diff --git a/src/web/web.h b/src/web/web.h index 451dc656..1e87547b 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -39,7 +39,7 @@ #define WEB_SERIAL_BUF_SIZE 2048 -const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinCmtSclk", "pinSdio", "pinCsb", "pinFcsb", "pinGpio3"}; +const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinLedLum", "pinCmtSclk", "pinSdio", "pinCsb", "pinFcsb", "pinGpio3"}; template class Web { @@ -71,14 +71,15 @@ class Web { mWeb.onNotFound ( std::bind(&Web::showNotFound, this, std::placeholders::_1)); mWeb.on("/reboot", HTTP_ANY, std::bind(&Web::onReboot, this, std::placeholders::_1)); mWeb.on("/system", HTTP_ANY, std::bind(&Web::onSystem, this, std::placeholders::_1)); - mWeb.on("/erase", HTTP_ANY, std::bind(&Web::showErase, this, std::placeholders::_1)); - mWeb.on("/factory", HTTP_ANY, std::bind(&Web::showFactoryRst, this, std::placeholders::_1)); + mWeb.on("/erase", HTTP_ANY, std::bind(&Web::showHtml, this, std::placeholders::_1)); + mWeb.on("/erasetrue", HTTP_ANY, std::bind(&Web::showHtml, this, std::placeholders::_1)); + mWeb.on("/factory", HTTP_ANY, std::bind(&Web::showHtml, this, std::placeholders::_1)); + mWeb.on("/factorytrue", HTTP_ANY, std::bind(&Web::showHtml, this, std::placeholders::_1)); mWeb.on("/setup", HTTP_GET, std::bind(&Web::onSetup, this, std::placeholders::_1)); mWeb.on("/save", HTTP_POST, std::bind(&Web::showSave, this, std::placeholders::_1)); mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1)); - //mWeb.on("/api1", HTTP_POST, std::bind(&Web::showWebApi, this, std::placeholders::_1)); #ifdef ENABLE_PROMETHEUS_EP mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1)); @@ -197,6 +198,11 @@ class Web { #if !defined(ETHERNET) strncpy(mConfig->sys.stationPwd, pwd, PWD_LEN); // restore WiFi PWD #endif + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { + if((mConfig->inst.iv[i].serial.u64 != 0) && (mConfig->inst.iv[i].serial.u64 < 138999999999)) { // hexadecimal + mConfig->inst.iv[i].serial.u64 = ah::Serial2u64(String(mConfig->inst.iv[i].serial.u64).c_str()); + } + } mApp->saveSettings(true); } if (!mUploadFail) @@ -284,12 +290,18 @@ class Web { bool reboot = (!Update.hasError()); - String html = F("UpdateUpdate: "); + String html = F("UpdateUpdate: "); if (reboot) html += "success"; else html += "failed"; - html += F("

rebooting ... auto reload after 20s"); + html += F("

rebooting ..."); AsyncWebServerResponse *response = request->beginResponse(200, F("text/html; charset=UTF-8"), html); response->addHeader("Connection", "close"); @@ -420,39 +432,12 @@ class Web { request->send(response); } - void showErase(AsyncWebServerRequest *request) { - checkProtection(request); - - DPRINTLN(DBG_VERBOSE, F("showErase")); - mApp->eraseSettings(false); - onReboot(request); - } - - void showFactoryRst(AsyncWebServerRequest *request) { + void showHtml(AsyncWebServerRequest *request) { checkProtection(request); - DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); - String content = ""; - int refresh = 3; - if (request->args() > 0) { - if (request->arg("reset").toInt() == 1) { - refresh = 10; - if (mApp->eraseSettings(true)) - content = F("factory reset: success\n\nrebooting ... "); - else - content = F("factory reset: failed\n\nrebooting ... "); - } else { - content = F("factory reset: aborted"); - refresh = 3; - } - } else { - content = F("

Factory Reset

" - "

RESET

CANCEL

"); - refresh = 120; - } - request->send(200, F("text/html; charset=UTF-8"), F("Factory Reset") + content + F("")); - if (refresh == 10) - onReboot(request); + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); } void onSetup(AsyncWebServerRequest *request) { @@ -521,7 +506,7 @@ class Web { // pinout uint8_t pin; - for (uint8_t i = 0; i < 14; i++) { + for (uint8_t i = 0; i < 15; i++) { pin = request->arg(String(pinArgNames[i])).toInt(); switch(i) { case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_NRF_CS_PIN); break; @@ -530,14 +515,15 @@ class Web { case 3: mConfig->nrf.pinSclk = ((pin != 0xff) ? pin : DEF_NRF_SCLK_PIN); break; case 4: mConfig->nrf.pinMosi = ((pin != 0xff) ? pin : DEF_NRF_MOSI_PIN); break; case 5: mConfig->nrf.pinMiso = ((pin != 0xff) ? pin : DEF_NRF_MISO_PIN); break; - case 6: mConfig->led.led0 = pin; break; - case 7: mConfig->led.led1 = pin; break; - case 8: mConfig->led.led_high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense - case 9: mConfig->cmt.pinSclk = pin; break; - case 10: mConfig->cmt.pinSdio = pin; break; - case 11: mConfig->cmt.pinCsb = pin; break; - case 12: mConfig->cmt.pinFcsb = pin; break; - case 13: mConfig->cmt.pinIrq = pin; break; + case 6: mConfig->led.led0 = pin; break; + case 7: mConfig->led.led1 = pin; break; + case 8: mConfig->led.high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense + case 9: mConfig->led.luminance = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense + case 10: mConfig->cmt.pinSclk = pin; break; + case 11: mConfig->cmt.pinSdio = pin; break; + case 12: mConfig->cmt.pinCsb = pin; break; + case 13: mConfig->cmt.pinFcsb = pin; break; + case 14: mConfig->cmt.pinIrq = pin; break; } }