From cdc6fef292aefc8b433547380c19df271295bf9b Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 5 Mar 2023 09:00:59 +0100 Subject: [PATCH 01/43] Code cleanup: remove obsolete JSON_EP --- src/config/config_override_example.h | 3 --- src/web/web.h | 33 ---------------------------- 2 files changed, 36 deletions(-) diff --git a/src/config/config_override_example.h b/src/config/config_override_example.h index b56214c8..ed976ceb 100644 --- a/src/config/config_override_example.h +++ b/src/config/config_override_example.h @@ -30,9 +30,6 @@ #undef MIDNIGHTTICKER_OFFSET #define MIDNIGHTTICKER_OFFSET (mCalculatedTimezoneOffset + 1) -// To enable the json endpoint at /json -// #define ENABLE_JSON_EP - // To enable the endpoint for prometheus to scrape data from at /metrics // #define ENABLE_PROMETHEUS_EP diff --git a/src/web/web.h b/src/web/web.h index d79ace82..2c4f1ad8 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -75,9 +75,6 @@ class Web { 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_JSON_EP - mWeb.on("/json", HTTP_ANY, std::bind(&Web::showJson, this, std::placeholders::_1)); - #endif #ifdef ENABLE_PROMETHEUS_EP mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1)); #endif @@ -730,36 +727,6 @@ class Web { request->send(response); } -#ifdef ENABLE_JSON_EP - void showJson(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("web::showJson")); - String modJson; - Inverter<> *iv; - record_t<> *rec; - char topic[40], val[25]; - - modJson = F("{\n"); - for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { - iv = mSys->getInverterByPos(id); - if(NULL == iv) - continue; - - rec = iv->getRecordStruct(RealTimeRunData_Debug); - snprintf(topic, 30, "\"%s\": {\n", iv->config->name); - modJson += String(topic); - for(uint8_t i = 0; i < rec->length; i++) { - snprintf(topic, 40, "\t\"ch%d/%s\"", rec->assign[i].ch, iv->getFieldName(i, rec)); - snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec)); - modJson += String(topic) + ": " + String(val) + F(",\n"); - } - modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(rec->ts) + F("\"\n\t},\n"); - } - modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mApp->getTimestamp())) + F("\"\n}\n"); - - AsyncWebServerResponse *response = request->beginResponse(200, F("application/json"), modJson); - request->send(response); - } -#endif #ifdef ENABLE_PROMETHEUS_EP enum { From 0334898e3d038c2c14c70bd722e7b831d70f3b8e Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 7 Apr 2023 09:15:55 +0200 Subject: [PATCH 02/43] Do not send prometheus metric if channel is disabled in configuration --- doc/prometheus_ep_description.md | 2 +- src/web/web.h | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/doc/prometheus_ep_description.md b/doc/prometheus_ep_description.md index 8fb9e002..755fd1e4 100644 --- a/doc/prometheus_ep_description.md +++ b/doc/prometheus_ep_description.md @@ -12,7 +12,7 @@ Prometheus metrics provided at `/metrics`. | name | Inverter name from setup | | serial | Serial number of inverter | | inverter | Inverter name from setup | -| channel | Channel name from setup | +| channel | Channel (Module) name from setup. Label only available if max power level of module is set to non-zero. Be sure to have a cannel name set in configuration. | ## Exported Metrics | Metric name | Type | Description | Labels | diff --git a/src/web/web.h b/src/web/web.h index f8abc0f7..4b7cbb39 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -719,15 +719,20 @@ class Web { rec = iv->getRecordStruct(RealTimeRunData_Debug); if (metricsChannelId < rec->length) { uint8_t channel = rec->assign[metricsChannelId].ch; - std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); - if (0 == channel) { - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); + // Skip entry if maxPwr is 0 and it's not the inverter channel (channel 0) + if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) { + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); + if (0 == channel) { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); + } else { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]); + } + snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec)); + len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); } else { - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]); + len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. } - snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec)); - len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); metricsChannelId++; } else { From e17f6afc8621a33857800c5a82f5024de1ee5447 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 7 Apr 2023 17:13:44 +0200 Subject: [PATCH 03/43] Removed /json endpoint availablity from documentation --- Getting_Started.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Getting_Started.md b/Getting_Started.md index 3a031af0..4d35903c 100644 --- a/Getting_Started.md +++ b/Getting_Started.md @@ -264,9 +264,8 @@ When everything is wired up and the firmware is flashed, it is time to connect t | /cmdstat | show stat from the home page | | yes | | /visualization | displays the information from your converter | | yes | | /livedata | displays the live data | | yes | -| /json | gets live-data in JSON format | json output from the livedata | no - enable via config_override.h | | /metrics | gets live-data for prometheus | prometheus metrics from the livedata | no - enable via config_override.h | -| /api | | | yes | +| /api | gets configuration and live-data in JSON format | json output from the configuration or livedata | yes | ## MQTT command to set the DTU without webinterface From e8e1fd768133e2d3fde9d8cf0f1561ba24b66c63 Mon Sep 17 00:00:00 2001 From: tastendruecker123 <111116980+tastendruecker123@users.noreply.github.com> Date: Mon, 10 Apr 2023 14:17:05 +0200 Subject: [PATCH 04/43] Don't load JSON settings that don't exist --- src/config/settings.h | 136 +++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 68 deletions(-) diff --git a/src/config/settings.h b/src/config/settings.h index e895a8a9..5f254bcf 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -245,15 +245,15 @@ class settings { root.shrinkToFit(); if(!err && (root.size() > 0)) { mCfg.valid = true; - jsonWifi(root[F("wifi")]); - jsonNrf(root[F("nrf")]); - jsonNtp(root[F("ntp")]); - jsonSun(root[F("sun")]); - jsonSerial(root[F("serial")]); - jsonMqtt(root[F("mqtt")]); - jsonLed(root[F("led")]); - jsonPlugin(root[F("plugin")]); - jsonInst(root[F("inst")]); + if(root.containsKey(F("wifi"))) jsonWifi(root[F("wifi")]); + if(root.containsKey(F("nrf"))) jsonNrf(root[F("nrf")]); + if(root.containsKey(F("ntp"))) jsonNtp(root[F("ntp")]); + if(root.containsKey(F("sun"))) jsonSun(root[F("sun")]); + if(root.containsKey(F("serial"))) jsonSerial(root[F("serial")]); + if(root.containsKey(F("mqtt"))) jsonMqtt(root[F("mqtt")]); + if(root.containsKey(F("led"))) jsonLed(root[F("led")]); + if(root.containsKey(F("plugin"))) jsonPlugin(root[F("plugin")]); + if(root.containsKey(F("inst"))) jsonInst(root[F("inst")]); } else { Serial.println(F("failed to parse json, using default config")); @@ -410,17 +410,17 @@ class settings { ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf); ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf); } else { - snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as()); - snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as()); - snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as()); - snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as()); - mCfg.sys.protectionMask = obj[F("prot_mask")]; - mCfg.sys.darkMode = obj[F("dark")]; - ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); - ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as()); - ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as()); - ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as()); - ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as()); + if(obj.containsKey(F("ssid"))) snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as()); + if(obj.containsKey(F("pwd"))) snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as()); + if(obj.containsKey(F("dev"))) snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as()); + if(obj.containsKey(F("adm"))) snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as()); + if(obj.containsKey(F("prot_mask"))) mCfg.sys.protectionMask = obj[F("prot_mask")]; + if(obj.containsKey(F("dark"))) mCfg.sys.darkMode = obj[F("dark")]; + if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); + if(obj.containsKey(F("mask"))) ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as()); + if(obj.containsKey(F("dns1"))) ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as()); + if(obj.containsKey(F("dns2"))) ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as()); + if(obj.containsKey(F("gtwy"))) ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as()); if(mCfg.sys.protectionMask == 0) mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP @@ -440,15 +440,15 @@ class settings { obj[F("miso")] = mCfg.nrf.pinMiso; obj[F("pwr")] = mCfg.nrf.amplifierPower; } else { - mCfg.nrf.sendInterval = obj[F("intvl")]; - mCfg.nrf.maxRetransPerPyld = obj[F("maxRetry")]; - mCfg.nrf.pinCs = obj[F("cs")]; - mCfg.nrf.pinCe = obj[F("ce")]; - mCfg.nrf.pinIrq = obj[F("irq")]; - mCfg.nrf.pinSclk = obj[F("sclk")]; - mCfg.nrf.pinMosi = obj[F("mosi")]; - mCfg.nrf.pinMiso = obj[F("miso")]; - mCfg.nrf.amplifierPower = obj[F("pwr")]; + if(obj.containsKey(F("ssid"))) mCfg.nrf.sendInterval = obj[F("intvl")]; + if(obj.containsKey(F("maxRetry"))) mCfg.nrf.maxRetransPerPyld = obj[F("maxRetry")]; + if(obj.containsKey(F("cs"))) mCfg.nrf.pinCs = obj[F("cs")]; + if(obj.containsKey(F("ce"))) mCfg.nrf.pinCe = obj[F("ce")]; + if(obj.containsKey(F("irq"))) mCfg.nrf.pinIrq = obj[F("irq")]; + if(obj.containsKey(F("sclk"))) mCfg.nrf.pinSclk = obj[F("sclk")]; + if(obj.containsKey(F("mosi"))) mCfg.nrf.pinMosi = obj[F("mosi")]; + if(obj.containsKey(F("miso"))) mCfg.nrf.pinMiso = obj[F("miso")]; + if(obj.containsKey(F("pwr"))) mCfg.nrf.amplifierPower = obj[F("pwr")]; if((obj[F("cs")] == obj[F("ce")])) { mCfg.nrf.pinCs = DEF_CS_PIN; mCfg.nrf.pinCe = DEF_CE_PIN; @@ -465,8 +465,8 @@ class settings { obj[F("addr")] = mCfg.ntp.addr; obj[F("port")] = mCfg.ntp.port; } else { - snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as()); - mCfg.ntp.port = obj[F("port")]; + if(obj.containsKey(F("addr"))) snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as()); + if(obj.containsKey(F("port"))) mCfg.ntp.port = obj[F("port")]; } } @@ -477,10 +477,10 @@ class settings { obj[F("dis")] = mCfg.sun.disNightCom; obj[F("offs")] = mCfg.sun.offsetSec; } else { - mCfg.sun.lat = obj[F("lat")]; - mCfg.sun.lon = obj[F("lon")]; - mCfg.sun.disNightCom = obj[F("dis")]; - mCfg.sun.offsetSec = obj[F("offs")]; + if(obj.containsKey(F("lat"))) mCfg.sun.lat = obj[F("lat")]; + if(obj.containsKey(F("lon"))) mCfg.sun.lon = obj[F("lon")]; + if(obj.containsKey(F("dis"))) mCfg.sun.disNightCom = obj[F("dis")]; + if(obj.containsKey(F("offs"))) mCfg.sun.offsetSec = obj[F("offs")]; } } @@ -490,9 +490,9 @@ class settings { obj[F("show")] = mCfg.serial.showIv; obj[F("debug")] = mCfg.serial.debug; } else { - mCfg.serial.interval = obj[F("intvl")]; - mCfg.serial.showIv = obj[F("show")]; - mCfg.serial.debug = obj[F("debug")]; + if(obj.containsKey(F("intvl"))) mCfg.serial.interval = obj[F("intvl")]; + if(obj.containsKey(F("show"))) mCfg.serial.showIv = obj[F("show")]; + if(obj.containsKey(F("debug"))) mCfg.serial.debug = obj[F("debug")]; } } @@ -506,12 +506,12 @@ class settings { obj[F("intvl")] = mCfg.mqtt.interval; } else { - mCfg.mqtt.port = obj[F("port")]; - mCfg.mqtt.interval = obj[F("intvl")]; - snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); - snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); - snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); - snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as()); + if(obj.containsKey(F("port"))) mCfg.mqtt.port = obj[F("port")]; + if(obj.containsKey(F("intvl"))) mCfg.mqtt.interval = obj[F("intvl")]; + if(obj.containsKey(F("broker"))) snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); + if(obj.containsKey(F("user"))) snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); + if(obj.containsKey(F("pwd"))) snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); + if(obj.containsKey(F("topic"))) snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as()); } } @@ -521,9 +521,9 @@ class settings { obj[F("1")] = mCfg.led.led1; obj[F("led_high_active")] = mCfg.led.led_high_active; } else { - mCfg.led.led0 = obj[F("0")]; - mCfg.led.led1 = obj[F("1")]; - mCfg.led.led_high_active = obj[F("led_high_active")]; + if(obj.containsKey(F("0"))) mCfg.led.led0 = obj[F("0")]; + if(obj.containsKey(F("1"))) mCfg.led.led1 = obj[F("1")]; + if(obj.containsKey(F("led_high_active"))) mCfg.led.led_high_active = obj[F("led_high_active")]; } } @@ -545,19 +545,19 @@ class settings { disp[F("dc")] = mCfg.plugin.display.disp_dc; } else { JsonObject disp = obj["disp"]; - mCfg.plugin.display.type = disp[F("type")]; - mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")]; - mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")]; - mCfg.plugin.display.rot = disp[F("rotation")]; + if(disp.containsKey(F("type"))) mCfg.plugin.display.type = disp[F("type")]; + if(disp.containsKey(F("pwrSafe"))) mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")]; + if(disp.containsKey(F("pxShift"))) mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")]; + if(disp.containsKey(F("rotation"))) mCfg.plugin.display.rot = disp[F("rotation")]; //mCfg.plugin.display.wakeUp = disp[F("wake")]; //mCfg.plugin.display.sleepAt = disp[F("sleep")]; - mCfg.plugin.display.contrast = disp[F("contrast")]; - mCfg.plugin.display.disp_data = disp[F("data")]; - mCfg.plugin.display.disp_clk = disp[F("clock")]; - mCfg.plugin.display.disp_cs = disp[F("cs")]; - mCfg.plugin.display.disp_reset = disp[F("reset")]; - mCfg.plugin.display.disp_busy = disp[F("busy")]; - mCfg.plugin.display.disp_dc = disp[F("dc")]; + if(disp.containsKey(F("contrast"))) mCfg.plugin.display.contrast = disp[F("contrast")]; + if(disp.containsKey(F("data"))) mCfg.plugin.display.disp_data = disp[F("data")]; + if(disp.containsKey(F("clock"))) mCfg.plugin.display.disp_clk = disp[F("clock")]; + if(disp.containsKey(F("cs"))) mCfg.plugin.display.disp_cs = disp[F("cs")]; + if(disp.containsKey(F("reset"))) mCfg.plugin.display.disp_reset = disp[F("reset")]; + if(disp.containsKey(F("busy"))) mCfg.plugin.display.disp_busy = disp[F("busy")]; + if(disp.containsKey(F("dc"))) mCfg.plugin.display.disp_dc = disp[F("dc")]; } } @@ -569,10 +569,10 @@ class settings { obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop; } else { - mCfg.inst.enabled = (bool)obj[F("en")]; - mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"]; - mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"]; - mCfg.inst.rstValsCommStop = (bool)obj["rstComStop"]; + if(obj.containsKey(F("en"))) mCfg.inst.enabled = (bool)obj[F("en")]; + if(obj.containsKey(F("rstMidNight"))) mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"]; + if(obj.containsKey(F("rstNotAvail"))) mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"]; + if(obj.containsKey(F("rstComStop"))) mCfg.inst.rstValsCommStop = (bool)obj["rstComStop"]; } JsonArray ivArr; @@ -601,13 +601,13 @@ class settings { obj[F("chName")][i] = cfg->chName[i]; } } else { - cfg->enabled = (bool)obj[F("en")]; - snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); - cfg->serial.u64 = obj[F("sn")]; + if(obj.containsKey(F("en"))) cfg->enabled = (bool)obj[F("en")]; + if(obj.containsKey(F("name"))) snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); + if(obj.containsKey(F("sn"))) cfg->serial.u64 = obj[F("sn")]; for(uint8_t i = 0; i < 4; i++) { - cfg->yieldCor[i] = obj[F("yield")][i]; - cfg->chMaxPwr[i] = obj[F("pwr")][i]; - snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); + if(obj.containsKey(F("yield"))) cfg->yieldCor[i] = obj[F("yield")][i]; + if(obj.containsKey(F("pwr"))) cfg->chMaxPwr[i] = obj[F("pwr")][i]; + if(obj.containsKey(F("chName"))) snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); } } } From f946b35d062ba6c3b6ec6b35d8c21d23981803a1 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 11 Apr 2023 20:54:33 +0200 Subject: [PATCH 05/43] 0.6.5 * fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing --- src/CHANGES.md | 3 +++ src/publisher/pubMqtt.h | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/CHANGES.md b/src/CHANGES.md index c9b426b9..cc9d8a4b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.6.5 - 2023-04-11 +* fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing + ## 0.6.4 - 2023-04-06 * merge PR #846, improved NRF24 communication and MI, thx @beegee3 & @rejoe2 * merge PR #859, fix burger menu height, thx @ThomasPohl diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index e717ee7c..f9bf6f4d 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -247,6 +247,8 @@ class PubMqtt { subscribe(mVal); snprintf(mVal, 20, "ctrl/restart/%d", i); subscribe(mVal); + snprintf(mVal, 20, "ctrl/power/%d", i); + subscribe(mVal); } subscribe(subscr[MQTT_SUBS_SET_TIME]); } From 33186a9833a307a97347de7a52c19372ff2457aa Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 11 Apr 2023 22:39:08 +0200 Subject: [PATCH 06/43] 0.6.5 - 2023-04-11 * merge PR #876, check JSON settings during read for existance * **NOTE:** incompatible change: renamed `led_high_active` to `act_high`, maybe setting must be changed after update --- src/CHANGES.md | 2 + src/config/settings.h | 146 +++++++++++++++++++++++++----------------- src/utils/dbg.h | 2 +- 3 files changed, 89 insertions(+), 61 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index cc9d8a4b..69de48ff 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,8 @@ ## 0.6.5 - 2023-04-11 * fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing +* merge PR #876, check JSON settings during read for existance +* **NOTE:** incompatible change: renamed `led_high_active` to `act_high`, maybe setting must be changed after update ## 0.6.4 - 2023-04-06 * merge PR #846, improved NRF24 communication and MI, thx @beegee3 & @rejoe2 diff --git a/src/config/settings.h b/src/config/settings.h index 5f254bcf..2f20510b 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -6,6 +6,11 @@ #ifndef __SETTINGS_H__ #define __SETTINGS_H__ +#if defined(F) && defined(ESP32) + #undef F + #define F(sl) (sl) +#endif + #include #include #include @@ -410,12 +415,12 @@ class settings { ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf); ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf); } else { - if(obj.containsKey(F("ssid"))) snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as()); - if(obj.containsKey(F("pwd"))) snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as()); - if(obj.containsKey(F("dev"))) snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as()); - if(obj.containsKey(F("adm"))) snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as()); - if(obj.containsKey(F("prot_mask"))) mCfg.sys.protectionMask = obj[F("prot_mask")]; - if(obj.containsKey(F("dark"))) mCfg.sys.darkMode = obj[F("dark")]; + getChar(obj, F("ssid"), mCfg.sys.stationSsid, SSID_LEN); + getChar(obj, F("pwd"), mCfg.sys.stationPwd, PWD_LEN); + getChar(obj, F("adm"), mCfg.sys.deviceName, DEVNAME_LEN); + getChar(obj, F("dev"), mCfg.sys.adminPwd, PWD_LEN); + getVal(obj, F("prot_mask"), &mCfg.sys.protectionMask); + getVal(obj, F("dark"), &mCfg.sys.darkMode); if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); if(obj.containsKey(F("mask"))) ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as()); if(obj.containsKey(F("dns1"))) ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as()); @@ -440,15 +445,15 @@ class settings { obj[F("miso")] = mCfg.nrf.pinMiso; obj[F("pwr")] = mCfg.nrf.amplifierPower; } else { - if(obj.containsKey(F("ssid"))) mCfg.nrf.sendInterval = obj[F("intvl")]; - if(obj.containsKey(F("maxRetry"))) mCfg.nrf.maxRetransPerPyld = obj[F("maxRetry")]; - if(obj.containsKey(F("cs"))) mCfg.nrf.pinCs = obj[F("cs")]; - if(obj.containsKey(F("ce"))) mCfg.nrf.pinCe = obj[F("ce")]; - if(obj.containsKey(F("irq"))) mCfg.nrf.pinIrq = obj[F("irq")]; - if(obj.containsKey(F("sclk"))) mCfg.nrf.pinSclk = obj[F("sclk")]; - if(obj.containsKey(F("mosi"))) mCfg.nrf.pinMosi = obj[F("mosi")]; - if(obj.containsKey(F("miso"))) mCfg.nrf.pinMiso = obj[F("miso")]; - if(obj.containsKey(F("pwr"))) mCfg.nrf.amplifierPower = obj[F("pwr")]; + getVal(obj, F("intvl"), &mCfg.nrf.sendInterval); + getVal(obj, F("maxRetry"), &mCfg.nrf.maxRetransPerPyld); + getVal(obj, F("cs"), &mCfg.nrf.pinCs); + getVal(obj, F("ce"), &mCfg.nrf.pinCe); + getVal(obj, F("irq"), &mCfg.nrf.pinIrq); + getVal(obj, F("sclk"), &mCfg.nrf.pinSclk); + getVal(obj, F("mosi"), &mCfg.nrf.pinMosi); + getVal(obj, F("miso"), &mCfg.nrf.pinMiso); + getVal(obj, F("pwr"), &mCfg.nrf.amplifierPower); if((obj[F("cs")] == obj[F("ce")])) { mCfg.nrf.pinCs = DEF_CS_PIN; mCfg.nrf.pinCe = DEF_CE_PIN; @@ -465,8 +470,8 @@ class settings { obj[F("addr")] = mCfg.ntp.addr; obj[F("port")] = mCfg.ntp.port; } else { - if(obj.containsKey(F("addr"))) snprintf(mCfg.ntp.addr, NTP_ADDR_LEN, "%s", obj[F("addr")].as()); - if(obj.containsKey(F("port"))) mCfg.ntp.port = obj[F("port")]; + getChar(obj, F("addr"), mCfg.ntp.addr, NTP_ADDR_LEN); + getVal(obj, F("port"), &mCfg.ntp.port); } } @@ -477,10 +482,10 @@ class settings { obj[F("dis")] = mCfg.sun.disNightCom; obj[F("offs")] = mCfg.sun.offsetSec; } else { - if(obj.containsKey(F("lat"))) mCfg.sun.lat = obj[F("lat")]; - if(obj.containsKey(F("lon"))) mCfg.sun.lon = obj[F("lon")]; - if(obj.containsKey(F("dis"))) mCfg.sun.disNightCom = obj[F("dis")]; - if(obj.containsKey(F("offs"))) mCfg.sun.offsetSec = obj[F("offs")]; + getVal(obj, F("lat"), &mCfg.sun.lat); + getVal(obj, F("lon"), &mCfg.sun.lon); + getVal(obj, F("dis"), &mCfg.sun.disNightCom); + getVal(obj, F("offs"), &mCfg.sun.offsetSec); } } @@ -490,9 +495,9 @@ class settings { obj[F("show")] = mCfg.serial.showIv; obj[F("debug")] = mCfg.serial.debug; } else { - if(obj.containsKey(F("intvl"))) mCfg.serial.interval = obj[F("intvl")]; - if(obj.containsKey(F("show"))) mCfg.serial.showIv = obj[F("show")]; - if(obj.containsKey(F("debug"))) mCfg.serial.debug = obj[F("debug")]; + getVal(obj, F("intvl"), &mCfg.serial.interval); + getVal(obj, F("show"), &mCfg.serial.showIv); + getVal(obj, F("debug"), &mCfg.serial.debug); } } @@ -506,12 +511,12 @@ class settings { obj[F("intvl")] = mCfg.mqtt.interval; } else { - if(obj.containsKey(F("port"))) mCfg.mqtt.port = obj[F("port")]; - if(obj.containsKey(F("intvl"))) mCfg.mqtt.interval = obj[F("intvl")]; - if(obj.containsKey(F("broker"))) snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); - if(obj.containsKey(F("user"))) snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); - if(obj.containsKey(F("pwd"))) snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); - if(obj.containsKey(F("topic"))) snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", obj[F("topic")].as()); + getVal(obj, F("port"), &mCfg.mqtt.port); + getVal(obj, F("intvl"), &mCfg.mqtt.interval); + getChar(obj, F("broker"), mCfg.mqtt.broker, MQTT_ADDR_LEN); + getChar(obj, F("user"), mCfg.mqtt.user, MQTT_USER_LEN); + getChar(obj, F("pwd"), mCfg.mqtt.pwd, MQTT_PWD_LEN); + getChar(obj, F("topic"), mCfg.mqtt.topic, MQTT_TOPIC_LEN); } } @@ -519,11 +524,11 @@ class settings { if(set) { obj[F("0")] = mCfg.led.led0; obj[F("1")] = mCfg.led.led1; - obj[F("led_high_active")] = mCfg.led.led_high_active; + obj[F("act_high")] = mCfg.led.led_high_active; } else { - if(obj.containsKey(F("0"))) mCfg.led.led0 = obj[F("0")]; - if(obj.containsKey(F("1"))) mCfg.led.led1 = obj[F("1")]; - if(obj.containsKey(F("led_high_active"))) mCfg.led.led_high_active = obj[F("led_high_active")]; + getVal(obj, F("0"), &mCfg.led.led0); + getVal(obj, F("1"), &mCfg.led.led1); + getVal(obj, F("act_high"), &mCfg.led.led_high_active); } } @@ -545,19 +550,19 @@ class settings { disp[F("dc")] = mCfg.plugin.display.disp_dc; } else { JsonObject disp = obj["disp"]; - if(disp.containsKey(F("type"))) mCfg.plugin.display.type = disp[F("type")]; - if(disp.containsKey(F("pwrSafe"))) mCfg.plugin.display.pwrSaveAtIvOffline = (bool)disp[F("pwrSafe")]; - if(disp.containsKey(F("pxShift"))) mCfg.plugin.display.pxShift = (bool)disp[F("pxShift")]; - if(disp.containsKey(F("rotation"))) mCfg.plugin.display.rot = disp[F("rotation")]; + getVal(disp, F("type"), &mCfg.plugin.display.type); + getVal(disp, F("pwrSafe"), &mCfg.plugin.display.pwrSaveAtIvOffline); + getVal(disp, F("pxShift"), &mCfg.plugin.display.pxShift); + getVal(disp, F("rotation"), &mCfg.plugin.display.rot); //mCfg.plugin.display.wakeUp = disp[F("wake")]; //mCfg.plugin.display.sleepAt = disp[F("sleep")]; - if(disp.containsKey(F("contrast"))) mCfg.plugin.display.contrast = disp[F("contrast")]; - if(disp.containsKey(F("data"))) mCfg.plugin.display.disp_data = disp[F("data")]; - if(disp.containsKey(F("clock"))) mCfg.plugin.display.disp_clk = disp[F("clock")]; - if(disp.containsKey(F("cs"))) mCfg.plugin.display.disp_cs = disp[F("cs")]; - if(disp.containsKey(F("reset"))) mCfg.plugin.display.disp_reset = disp[F("reset")]; - if(disp.containsKey(F("busy"))) mCfg.plugin.display.disp_busy = disp[F("busy")]; - if(disp.containsKey(F("dc"))) mCfg.plugin.display.disp_dc = disp[F("dc")]; + getVal(disp, F("contrast"), &mCfg.plugin.display.contrast); + getVal(disp, F("data"), &mCfg.plugin.display.disp_data); + getVal(disp, F("clock"), &mCfg.plugin.display.disp_clk); + getVal(disp, F("cs"), &mCfg.plugin.display.disp_cs); + getVal(disp, F("reset"), &mCfg.plugin.display.disp_reset); + getVal(disp, F("busy"), &mCfg.plugin.display.disp_busy); + getVal(disp, F("dc"), &mCfg.plugin.display.disp_dc); } } @@ -569,10 +574,10 @@ class settings { obj[F("rstComStop")] = (bool)mCfg.inst.rstValsCommStop; } else { - if(obj.containsKey(F("en"))) mCfg.inst.enabled = (bool)obj[F("en")]; - if(obj.containsKey(F("rstMidNight"))) mCfg.inst.rstYieldMidNight = (bool)obj["rstMidNight"]; - if(obj.containsKey(F("rstNotAvail"))) mCfg.inst.rstValsNotAvail = (bool)obj["rstNotAvail"]; - if(obj.containsKey(F("rstComStop"))) mCfg.inst.rstValsCommStop = (bool)obj["rstComStop"]; + getVal(obj, F("en"), &mCfg.inst.enabled); + getVal(obj, F("rstMidNight"), &mCfg.inst.rstYieldMidNight); + getVal(obj, F("rstNotAvail"), &mCfg.inst.rstValsNotAvail); + getVal(obj, F("rstComStop"), &mCfg.inst.rstValsCommStop); } JsonArray ivArr; @@ -582,11 +587,8 @@ class settings { if(set) { if(mCfg.inst.iv[i].serial.u64 != 0ULL) jsonIv(ivArr.createNestedObject(), &mCfg.inst.iv[i], true); - } - else { - if(!obj[F("iv")][i].isNull()) - jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); - } + } else if(!obj[F("iv")][i].isNull()) + jsonIv(obj[F("iv")][i], &mCfg.inst.iv[i]); } } @@ -601,17 +603,41 @@ class settings { obj[F("chName")][i] = cfg->chName[i]; } } else { - if(obj.containsKey(F("en"))) cfg->enabled = (bool)obj[F("en")]; - if(obj.containsKey(F("name"))) snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); - if(obj.containsKey(F("sn"))) cfg->serial.u64 = obj[F("sn")]; + getVal(obj, F("en"), &cfg->enabled); + getChar(obj, F("name"), cfg->name, MAX_NAME_LENGTH); + getVal(obj, F("sn"), &cfg->serial.u64); for(uint8_t i = 0; i < 4; i++) { - if(obj.containsKey(F("yield"))) cfg->yieldCor[i] = obj[F("yield")][i]; - if(obj.containsKey(F("pwr"))) cfg->chMaxPwr[i] = obj[F("pwr")][i]; - if(obj.containsKey(F("chName"))) snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); + getVal(obj, F("yield"), &cfg->yieldCor[i]); + getVal(obj, F("pwr"), &cfg->chMaxPwr[i]); + getChar(obj, F("chName"), cfg->chName[i], MAX_NAME_LENGTH); } } } + #if defined(ESP32) + void getChar(JsonObject obj, const char *key, char *dst, int maxLen) { + if(obj.containsKey(key)) + snprintf(dst, maxLen, "%s", obj[key].as()); + } + + template + void getVal(JsonObject obj, const char *key, T *dst) { + if(obj.containsKey(key)) + *dst = obj[key]; + } + #else + void getChar(JsonObject obj, const __FlashStringHelper *key, char *dst, int maxLen) { + if(obj.containsKey(key)) + snprintf(dst, maxLen, "%s", obj[key].as()); + } + + template + void getVal(JsonObject obj, const __FlashStringHelper *key, T *dst) { + if(obj.containsKey(key)) + *dst = obj[key]; + } + #endif + settings_t mCfg; bool mLastSaveSucceed; }; diff --git a/src/utils/dbg.h b/src/utils/dbg.h index 4716d7ae..a18d2f77 100644 --- a/src/utils/dbg.h +++ b/src/utils/dbg.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- // 2023 Ahoy, https://www.mikrocontroller.net/topic/525778 -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __DBG_H__ From 563550d42cc3e74ca77c374a52ec05b3f767e62d Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 11 Apr 2023 22:44:05 +0200 Subject: [PATCH 07/43] 0.6.5 * merge PR #861 do not send channel metric if channel is disabled --- src/CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CHANGES.md b/src/CHANGES.md index 69de48ff..bfe6eb31 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,7 @@ * fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing * merge PR #876, check JSON settings during read for existance * **NOTE:** incompatible change: renamed `led_high_active` to `act_high`, maybe setting must be changed after update +* merge PR #861 do not send channel metric if channel is disabled ## 0.6.4 - 2023-04-06 * merge PR #846, improved NRF24 communication and MI, thx @beegee3 & @rejoe2 From d953dbfa37433c05ebcd500f326333f002de86b4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 11 Apr 2023 23:38:27 +0200 Subject: [PATCH 08/43] 0.6.5 * fix small issue during loading settings --- src/config/settings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/settings.h b/src/config/settings.h index 2f20510b..078c88c3 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -609,7 +609,7 @@ class settings { for(uint8_t i = 0; i < 4; i++) { getVal(obj, F("yield"), &cfg->yieldCor[i]); getVal(obj, F("pwr"), &cfg->chMaxPwr[i]); - getChar(obj, F("chName"), cfg->chName[i], MAX_NAME_LENGTH); + if(obj.containsKey(F("chName"))) snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); } } } From f38bee88a163ebb535acd3aa341f3c755be9fddf Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 11 Apr 2023 23:44:34 +0200 Subject: [PATCH 09/43] another fix --- src/config/settings.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/settings.h b/src/config/settings.h index 078c88c3..7d924c4e 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -417,8 +417,8 @@ class settings { } else { getChar(obj, F("ssid"), mCfg.sys.stationSsid, SSID_LEN); getChar(obj, F("pwd"), mCfg.sys.stationPwd, PWD_LEN); - getChar(obj, F("adm"), mCfg.sys.deviceName, DEVNAME_LEN); - getChar(obj, F("dev"), mCfg.sys.adminPwd, PWD_LEN); + getChar(obj, F("dev"), mCfg.sys.deviceName, DEVNAME_LEN); + getChar(obj, F("adm"), mCfg.sys.adminPwd, PWD_LEN); getVal(obj, F("prot_mask"), &mCfg.sys.protectionMask); getVal(obj, F("dark"), &mCfg.sys.darkMode); if(obj.containsKey(F("ip"))) ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); From a0659968cecba561f334d7c1f2e300bba03a5cbd Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 11 Apr 2023 23:47:32 +0200 Subject: [PATCH 10/43] 0.6.5 Version number increase --- src/defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defines.h b/src/defines.h index 1ff3e1b7..f4adc55e 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 4 +#define VERSION_PATCH 5 //------------------------------------- typedef struct { From 1e1696ca15c0d6777856d9e800da6ba73c92309b Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 11 Apr 2023 23:52:54 +0200 Subject: [PATCH 11/43] 0.6.5 fix max module power read --- src/config/settings.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/settings.h b/src/config/settings.h index 7d924c4e..dc5a4408 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -607,8 +607,8 @@ class settings { getChar(obj, F("name"), cfg->name, MAX_NAME_LENGTH); getVal(obj, F("sn"), &cfg->serial.u64); for(uint8_t i = 0; i < 4; i++) { - getVal(obj, F("yield"), &cfg->yieldCor[i]); - getVal(obj, F("pwr"), &cfg->chMaxPwr[i]); + if(obj.containsKey(F("yield"))) cfg->yieldCor[i] = obj[F("yield")][i]; + if(obj.containsKey(F("pwr"))) cfg->chMaxPwr[i] = obj[F("pwr")][i]; if(obj.containsKey(F("chName"))) snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); } } From 48ce948ede2c657680ea60a0acfadc668abc159a Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 12 Apr 2023 22:50:37 +0200 Subject: [PATCH 12/43] 0.6.6 * increased distance for `import` button in mobile view #879 * changed `led_high_active` to `bool` #879 --- src/CHANGES.md | 4 ++++ src/app.cpp | 8 +++----- src/config/settings.h | 6 +++--- src/defines.h | 2 +- src/web/html/includes/header.html | 3 ++- src/web/html/setup.html | 2 +- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index bfe6eb31..4e5900ad 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.6.6 - 2023-04-12 +* increased distance for `import` button in mobile view #879 +* changed `led_high_active` to `bool` #879 + ## 0.6.5 - 2023-04-11 * fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing * merge PR #876, check JSON settings during read for existance diff --git a/src/app.cpp b/src/app.cpp index 7a0e5f2a..d2d87f50 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -389,8 +389,7 @@ void app::mqttSubRxCb(JsonObject obj) { //----------------------------------------------------------------------------- void app::setupLed(void) { - - uint8_t led_off = (mConfig->led.led_high_active != 0) ? LOW : HIGH; + uint8_t led_off = (mConfig->led.led_high_active) ? LOW : HIGH; if (mConfig->led.led0 != 0xff) { pinMode(mConfig->led.led0, OUTPUT); @@ -404,9 +403,8 @@ void app::setupLed(void) { //----------------------------------------------------------------------------- void app::updateLed(void) { - - uint8_t led_off = (mConfig->led.led_high_active != 0) ? LOW : HIGH; - uint8_t led_on = (mConfig->led.led_high_active != 0) ? HIGH : LOW; + uint8_t led_off = (mConfig->led.led_high_active) ? LOW : HIGH; + uint8_t led_on = (mConfig->led.led_high_active) ? HIGH : LOW; if (mConfig->led.led0 != 0xff) { Inverter<> *iv = mSys.getInverterByPos(0); diff --git a/src/config/settings.h b/src/config/settings.h index dc5a4408..d04fdce5 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -105,7 +105,7 @@ typedef struct { typedef struct { uint8_t led0; // first LED pin uint8_t led1; // second LED pin - uint8_t led_high_active; // determines if LEDs are high or low active + bool led_high_active; // determines if LEDs are high or low active } cfgLed_t; typedef struct { @@ -384,7 +384,7 @@ class settings { mCfg.led.led0 = DEF_PIN_OFF; mCfg.led.led1 = DEF_PIN_OFF; - mCfg.led.led_high_active = LOW; + mCfg.led.led_high_active = false; memset(&mCfg.inst, 0, sizeof(cfgInst_t)); @@ -528,7 +528,7 @@ class settings { } 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.led_high_active); } } diff --git a/src/defines.h b/src/defines.h index f4adc55e..b3a961a1 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 5 +#define VERSION_PATCH 6 //------------------------------------- typedef struct { diff --git a/src/web/html/includes/header.html b/src/web/html/includes/header.html index d591eb42..ab3b0545 100644 --- a/src/web/html/includes/header.html +++ b/src/web/html/includes/header.html @@ -2,4 +2,5 @@ - \ No newline at end of file + + diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 97bf5608..32484308 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -292,7 +292,7 @@ Import / Export JSON Settings
Import
-
+
From 2c0e1bbe57af18da6b980d6a1ca6fd2e6678f6eb Mon Sep 17 00:00:00 2001 From: tastendruecker123 <111116980+tastendruecker123@users.noreply.github.com> Date: Thu, 13 Apr 2023 17:42:54 +0200 Subject: [PATCH 13/43] Bugfixes around saving settings --- src/app.h | 4 ++++ src/appInterface.h | 1 + src/web/RestApi.h | 1 + src/web/html/api.js | 8 +++++--- src/web/html/save.html | 39 +++++++++++++++++++++++++-------------- src/web/html/setup.html | 1 + src/web/web.h | 2 +- 7 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/app.h b/src/app.h index 52f5591d..b5f30346 100644 --- a/src/app.h +++ b/src/app.h @@ -92,6 +92,10 @@ class app : public IApp, public ah::Scheduler { return mSettings.getLastSaveSucceed(); } + bool getShouldReboot() { + return mSaveReboot; + } + statistics_t *getStatistics() { return &mStat; } diff --git a/src/appInterface.h b/src/appInterface.h index 44491d91..0c67415e 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -20,6 +20,7 @@ class IApp { virtual bool eraseSettings(bool eraseWifi) = 0; virtual bool getSavePending() = 0; virtual bool getLastSaveSucceed() = 0; + virtual bool getShouldReboot() = 0; virtual void setOnUpdate() = 0; virtual void setRebootFlag() = 0; virtual const char *getVersion() = 0; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 2d617847..fde8c7a4 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -276,6 +276,7 @@ class RestApi { getGeneric(request, obj.createNestedObject(F("generic"))); obj["pending"] = (bool)mApp->getSavePending(); obj["success"] = (bool)mApp->getLastSaveSucceed(); + obj["reboot"] = (bool)mApp->getShouldReboot(); } void getReboot(AsyncWebServerRequest *request, JsonObject obj) { diff --git a/src/web/html/api.js b/src/web/html/api.js index 5ccb0e15..cdc9e748 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -103,9 +103,11 @@ function parseVersion(obj) { } function parseESP(obj) { - document.getElementById("esp_type").append( - document.createTextNode("Board: " + obj["esp_type"]) - ); + if(document.getElementById("esp_type").childNodes.length < 1) { + document.getElementById("esp_type").append( + document.createTextNode("Board: " + obj["esp_type"]) + ); + } } function parseRssi(obj) { diff --git a/src/web/html/save.html b/src/web/html/save.html index 54d43d7f..f3b1896d 100644 --- a/src/web/html/save.html +++ b/src/web/html/save.html @@ -8,11 +8,14 @@ {#HTML_NAV}
-
+
Saving settings...
{#HTML_FOOTER} diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 32484308..00ea8101 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -31,6 +31,7 @@
Dark Mode
+
(empty browser cache or use Shift + F5 after reboot to apply this setting)
diff --git a/src/web/web.h b/src/web/web.h index 4b7cbb39..056b9e7c 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -515,7 +515,7 @@ class Web { // pinout uint8_t pin; - for (uint8_t i = 0; i < 8; i++) { + for (uint8_t i = 0; i < 9; i++) { pin = request->arg(String(pinArgNames[i])).toInt(); switch(i) { default: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break; From 492cc5a8aa50ed3e62b41008eadd7ea123bfbc2b Mon Sep 17 00:00:00 2001 From: tastendruecker123 <111116980+tastendruecker123@users.noreply.github.com> Date: Thu, 13 Apr 2023 20:59:12 +0200 Subject: [PATCH 14/43] fixed typo --- src/web/html/setup.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 00ea8101..77769fdd 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -31,7 +31,7 @@
Dark Mode
-
(empty browser cache or use Shift + F5 after reboot to apply this setting)
+
(empty browser cache or use CTRL + F5 after reboot to apply this setting)
From 12a3d503c8328be21339650a5c7287949f0b46b8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 13 Apr 2023 22:58:59 +0200 Subject: [PATCH 15/43] 0.6.7 * merge PR #883, improved store of settings and javascript, thx @tastendruecker123 * support `.` and `,` as floating point seperator in setup #881 --- src/CHANGES.md | 4 +++ src/defines.h | 2 +- src/web/html/api.js | 8 ++---- src/web/html/save.html | 11 ++------ src/web/html/serial.html | 2 +- src/web/html/setup.html | 61 ++++++++++++++++++++++------------------ 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 4e5900ad..f4c89d23 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.6.7 - 2023-04-13 +* merge PR #883, improved store of settings and javascript, thx @tastendruecker123 +* support `.` and `,` as floating point seperator in setup #881 + ## 0.6.6 - 2023-04-12 * increased distance for `import` button in mobile view #879 * changed `led_high_active` to `bool` #879 diff --git a/src/defines.h b/src/defines.h index b3a961a1..1ace50df 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 6 +#define VERSION_PATCH 7 //------------------------------------- typedef struct { diff --git a/src/web/html/api.js b/src/web/html/api.js index cdc9e748..13ca50b5 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -103,11 +103,9 @@ function parseVersion(obj) { } function parseESP(obj) { - if(document.getElementById("esp_type").childNodes.length < 1) { - document.getElementById("esp_type").append( - document.createTextNode("Board: " + obj["esp_type"]) - ); - } + document.getElementById("esp_type").replaceChildren( + document.createTextNode("Board: " + obj["esp_type"]) + ); } function parseRssi(obj) { diff --git a/src/web/html/save.html b/src/web/html/save.html index f3b1896d..4c924c40 100644 --- a/src/web/html/save.html +++ b/src/web/html/save.html @@ -13,7 +13,6 @@
{#HTML_FOOTER} - + {#HTML_NAV}
- +
@@ -147,11 +136,11 @@
Interval [s]
-
+
Max retries per Payload
-
+
Reset values and YieldDay at midnight
@@ -178,7 +167,7 @@
NTP Port
-
+
set system time
@@ -195,15 +184,13 @@
Sunrise & Sunset -

Use a decimal separator: '.' (dot) for Latitude and Longitude

-
Latitude (decimal)
-
+
Longitude (decimal)
-
+
Offset (pre sunrise, post sunset)
@@ -226,7 +213,7 @@
Port
-
+
Username (optional)
@@ -243,7 +230,7 @@

Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)

Interval [s]
-
+
Discovery Config (homeassistant)
@@ -292,11 +279,13 @@
Import / Export JSON Settings
-
Import
-
+
Import
+
- - +
+
+
+
@@ -419,6 +408,24 @@ const re = /11[2,4,6]1.*/; + window.onload = function() { + for(it of document.getElementsByClassName("s_collapsible")) { + it.addEventListener("click", function() { + this.classList.toggle("active"); + var content = this.nextElementSibling; + content.style.display = (content.style.display === "block") ? "none" : "block"; + }); + } + + document.getElementById("settings").addEventListener("submit", function() { + var inputs = document.querySelectorAll("input[type='number']"); + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].value.indexOf(",") != -1) + inputs[i].value = inputs[i].value.replace(",", "."); + } + }); + } + document.getElementById("btnAdd").addEventListener("click", function() { if(highestId <= (maxInv-1)) { ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_pwr":[0,0,0,0],"ch_name":["","","",""],"ch_yield_cor":[0,0,0,0]}'), highestId); From 1103374abd0e919ba1f75830595c4d369d548a77 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 19 Apr 2023 21:41:13 +0200 Subject: [PATCH 16/43] started to convert for and while loop to async loops --- src/publisher/pubMqtt.h | 117 ++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index f9bf6f4d..7b766c92 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -34,13 +34,6 @@ struct alarm_t { alarm_t(uint16_t c, uint32_t s, uint32_t e) : code(c), start(s), end(e) {} }; -typedef struct { - bool running; - uint8_t lastIvId; - uint8_t sub; - uint8_t foundIvCnt; -} discovery_t; - template class PubMqtt { public: @@ -64,6 +57,7 @@ class PubMqtt { mIntervalTimeout = 1; mDiscovery.running = false; + mSendIvData.running = false; snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); @@ -93,6 +87,11 @@ class PubMqtt { yield(); #endif + if(mSendIvData.running) { + sendIvDataLoop(); + return; + } + if(mDiscovery.running) discoveryConfigLoop(); } @@ -107,13 +106,14 @@ class PubMqtt { return; // next try in a second } - if(0 == mCfgMqtt->interval) // no fixed interval, publish once new data were received (from inverter) - sendIvData(); + if(0 == mCfgMqtt->interval) { // no fixed interval, publish once new data were received (from inverter) + sendIvDataStart(); + } else { // send mqtt data in a fixed interval if(mIntervalTimeout == 0) { mIntervalTimeout = mCfgMqtt->interval; mSendList.push(RealTimeRunData_Debug); - sendIvData(); + sendIvDataStart(); } } } @@ -554,55 +554,65 @@ class PubMqtt { } } - void sendIvData() { + void sendIvDataStart() { + mSendIvData.RTRDataHasBeenSent = false; + memset(mSendIvData.total, 0, sizeof(float) * 4); + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL != iv) { + if (iv->config->enabled) { + mSendIvData.lastIvId = id; + mSendIvData.running = true; + mSendIvData.lastIvReached = false; + mSendIvData.sendTotals = false; + break; + } + } + } + } + + void sendIvDataLoop(void) { bool anyAvail = processIvStatus(); if (mLastAnyAvail != anyAvail) - mSendList.push(RealTimeRunData_Debug); // makes shure that total values are calculated + mSendList.push(RealTimeRunData_Debug); // makes sure that total values are calculated - if(mSendList.empty()) + if(mSendList.empty()) { + mSendIvData.running = false; return; + } - float total[4]; - bool RTRDataHasBeenSent = false; - while(!mSendList.empty()) { - memset(total, 0, sizeof(float) * 4); + //while(!mSendList.empty()) { uint8_t curInfoCmd = mSendList.front(); + if ((curInfoCmd != RealTimeRunData_Debug) || !mSendIvData.RTRDataHasBeenSent) { // send RTR Data only once + mSendIvData.sendTotals = (curInfoCmd == RealTimeRunData_Debug); - if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once - bool sendTotals = (curInfoCmd == RealTimeRunData_Debug); - - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL == iv) - continue; // skip to next inverter - if (!iv->config->enabled) - continue; // skip to next inverter - + if(!mSendIvData.lastIvReached) { + Inverter<> *iv = mSys->getInverterByPos(mSendIvData.lastIvId); // send RTR Data only if status is available - if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[id])) + if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[mSendIvData.lastIvId])) sendData(iv, curInfoCmd); // calculate total values for RealTimeRunData_Debug - if (sendTotals) { + if (mSendIvData.sendTotals) { record_t<> *rec = iv->getRecordStruct(curInfoCmd); - sendTotals &= (iv->getLastTs(rec) > 0); - if (sendTotals) { + mSendIvData.sendTotals &= (iv->getLastTs(rec) > 0); + if (mSendIvData.sendTotals) { for (uint8_t i = 0; i < rec->length; i++) { if (CH0 == rec->assign[i].ch) { switch (rec->assign[i].fieldId) { case FLD_PAC: - total[0] += iv->getValue(i, rec); + mSendIvData.total[0] += iv->getValue(i, rec); break; case FLD_YT: - total[1] += iv->getValue(i, rec); + mSendIvData.total[1] += iv->getValue(i, rec); break; case FLD_YD: - total[2] += iv->getValue(i, rec); + mSendIvData.total[2] += iv->getValue(i, rec); break; case FLD_PDC: - total[3] += iv->getValue(i, rec); + mSendIvData.total[3] += iv->getValue(i, rec); break; } } @@ -610,9 +620,21 @@ class PubMqtt { } } yield(); + + // get next inverter + for (uint8_t id = mSendIvData.lastIvId; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL != iv) { + if (iv->config->enabled) { + mSendIvData.lastIvId = id; + return; + } + } + } + mSendIvData.lastIvReached = true; } - if (sendTotals) { + if (mSendIvData.sendTotals) { uint8_t fieldId; for (uint8_t i = 0; i < 4; i++) { bool retained = true; @@ -634,20 +656,36 @@ class PubMqtt { break; } snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); - snprintf(mVal, 40, "%g", ah::round3(total[i])); + snprintf(mVal, 40, "%g", ah::round3(mSendIvData.total[i])); publish(mSubTopic, mVal, retained); } - RTRDataHasBeenSent = true; + mSendIvData.RTRDataHasBeenSent = true; yield(); } } mSendList.pop(); // remove from list once all inverters were processed - } + //} // end while mLastAnyAvail = anyAvail; } + typedef struct { + bool running; + uint8_t lastIvId; + bool lastIvReached; + bool sendTotals; + float total[4]; + bool RTRDataHasBeenSent; + } publish_t; + + typedef struct { + bool running; + uint8_t lastIvId; + uint8_t sub; + uint8_t foundIvCnt; + } discovery_t; + espMqttClient mClient; cfgMqtt_t *mCfgMqtt; #if defined(ESP8266) @@ -674,6 +712,7 @@ class PubMqtt { char mSubTopic[32 + MAX_NAME_LENGTH + 1]; char mVal[40]; discovery_t mDiscovery; + publish_t mSendIvData; }; #endif /*__PUB_MQTT_H__*/ From b6531180df6bea425fe0da0ea70fea13a055ffa3 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 19 Apr 2023 21:56:25 +0200 Subject: [PATCH 17/43] 0.6.8 * fix #892 `zeroYieldDay` loop was not applied to all channels --- src/CHANGES.md | 3 +++ src/defines.h | 2 +- src/hm/hmPayload.h | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f4c89d23..0b0d3676 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.6.8 - 2023-04-19 +* fix #892 `zeroYieldDay` loop was not applied to all channels + ## 0.6.7 - 2023-04-13 * merge PR #883, improved store of settings and javascript, thx @tastendruecker123 * support `.` and `,` as floating point seperator in setup #881 diff --git a/src/defines.h b/src/defines.h index 1ace50df..4313ee71 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 7 +#define VERSION_PATCH 8 //------------------------------------- typedef struct { diff --git a/src/hm/hmPayload.h b/src/hm/hmPayload.h index b04ee8f3..29f380fa 100644 --- a/src/hm/hmPayload.h +++ b/src/hm/hmPayload.h @@ -74,8 +74,8 @@ class HmPayload { DPRINTLN(DBG_DEBUG, F("zeroYieldDay")); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); uint8_t pos; - for(uint8_t ch = 0; ch < iv->channels; ch++) { - pos = iv->getPosByChFld(CH0, FLD_YD, rec); + for(uint8_t ch = 0; ch <= iv->channels; ch++) { + pos = iv->getPosByChFld(ch, FLD_YD, rec); iv->setValue(pos, rec, 0.0f); } } From 7e1f1cece4ba1fc4aee4698f7ac448f1ebaa5225 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 19 Apr 2023 23:00:24 +0200 Subject: [PATCH 18/43] 0.6.9 release --- src/CHANGES.md | 52 +++++++++++++--------------------------------- src/defines.h | 2 +- src/platformio.ini | 4 ++-- 3 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 0b0d3676..ba8f2d25 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,37 +1,15 @@ -# Development Changes - -## 0.6.8 - 2023-04-19 -* fix #892 `zeroYieldDay` loop was not applied to all channels - -## 0.6.7 - 2023-04-13 -* merge PR #883, improved store of settings and javascript, thx @tastendruecker123 -* support `.` and `,` as floating point seperator in setup #881 - -## 0.6.6 - 2023-04-12 -* increased distance for `import` button in mobile view #879 -* changed `led_high_active` to `bool` #879 - -## 0.6.5 - 2023-04-11 -* fix #845 MqTT subscription for `ctrl/power/[IV-ID]` was missing -* merge PR #876, check JSON settings during read for existance -* **NOTE:** incompatible change: renamed `led_high_active` to `act_high`, maybe setting must be changed after update -* merge PR #861 do not send channel metric if channel is disabled - -## 0.6.4 - 2023-04-06 -* merge PR #846, improved NRF24 communication and MI, thx @beegee3 & @rejoe2 -* merge PR #859, fix burger menu height, thx @ThomasPohl - -## 0.6.3 - 2023-04-04 -* fix login, password length was not checked #852 -* merge PR #854 optimize browser caching, thx @tastendruecker123 #828 -* fix WiFi reconnect not working #851 -* updated issue templates #822 - -## 0.6.2 - 2023-04-04 -* fix login from multiple clients #819 -* fix login screen on small displays - -## 0.6.1 - 2023-04-01 -* merge LED fix - LED1 shows MqTT state, LED configureable active high/low #839 -* only publish new inverter data #826 -* potential fix of WiFi hostname during boot up #752 +Changelog v0.6.9 + +* LEDs are now configurable to show if 1st inverter is available and if MqTT is connected +* LED are configurable to active high or low +* improved MqTT +* fix WiFi hostname during boot up +* improved login: only one session at the same time is possible +* fix UI: login screen for small displays; burger menu height; small modifications for import button (in setup) +* improved WiFi reconnect +* optimized performance: browser caching was improved to reduce requests from ESP +* improved NRF24 communication for more stable data transmission +* added / fixed MqTT subscription `ctrl/power/[IV-ID]` +* improved save settings +* improved UI in setup: now `.` and `,` are allowed as floating point seperator +* fix zero yield day functionality diff --git a/src/defines.h b/src/defines.h index 4313ee71..93722b6c 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 8 +#define VERSION_PATCH 9 //------------------------------------- typedef struct { diff --git a/src/platformio.ini b/src/platformio.ini index 5d4c655d..7b627f73 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -38,9 +38,9 @@ lib_deps = nrf24/RF24 @ ^1.4.5 paulstoffregen/Time @ ^1.6.1 https://github.com/bertmelis/espMqttClient#v1.4.2 - bblanchon/ArduinoJson @ ^6.21.0 + bblanchon/ArduinoJson @ ^6.21.2 https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.34.16 + olikraus/U8g2 @ ^2.34.17 zinggjm/GxEPD2 @ ^1.5.0 From 05d1eb607e1d54906ecd8287e088b0829014de61 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 19 Apr 2023 23:06:02 +0200 Subject: [PATCH 19/43] 0.6.8 try to fix esp32s3 build --- src/platformio.ini | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/platformio.ini b/src/platformio.ini index 5d4c655d..087efad3 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -16,17 +16,6 @@ include_dir = . framework = arduino board_build.filesystem = littlefs upload_speed = 921600 - -;build_flags = -; ;;;;; Possible Debug options ;;;;;; -; https://docs.platformio.org/en/latest/platforms/espressif8266.html#debug-level - ;-DDEBUG_ESP_PORT=Serial - ;-DDEBUG_ESP_CORE - ;-DDEBUG_ESP_WIFI - ;-DDEBUG_ESP_HTTP_CLIENT - ;-DDEBUG_ESP_HTTP_SERVER - ;-DDEBUG_ESP_OOM - monitor_speed = 115200 extra_scripts = @@ -95,7 +84,13 @@ platform = espressif8266 board = esp8285 board_build.ldscript = eagle.flash.1m64.ld board_build.f_cpu = 80000000L -build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial +build_flags = -DDEBUG_LEVEL=DBG_DEBUG + -DDEBUG_ESP_CORE + -DDEBUG_ESP_WIFI + -DDEBUG_ESP_HTTP_CLIENT + -DDEBUG_ESP_HTTP_SERVER + -DDEBUG_ESP_OOM + -DDEBUG_ESP_PORT=Serial build_type = debug monitor_filters = ;default ; Remove typical terminal control codes from input @@ -103,7 +98,7 @@ monitor_filters = log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory [env:esp32-wroom32-release] -platform = espressif32 +platform = espressif32@>=6.1.0 board = lolin_d32 build_flags = -D RELEASE -std=gnu++14 build_unflags = -std=gnu++11 @@ -114,9 +109,11 @@ monitor_filters = esp32_exception_decoder [env:esp32-wroom32-release-prometheus] -platform = espressif32 +platform = espressif32@>=6.1.0 board = lolin_d32 -build_flags = -D RELEASE -std=gnu++14 -DENABLE_PROMETHEUS_EP +build_flags = -D RELEASE + -std=gnu++14 + -DENABLE_PROMETHEUS_EP build_unflags = -std=gnu++11 monitor_filters = ;default ; Remove typical terminal control codes from input @@ -125,9 +122,16 @@ monitor_filters = esp32_exception_decoder [env:esp32-wroom32-debug] -platform = espressif32 +platform = espressif32@>=6.1.0 board = lolin_d32 -build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -std=gnu++14 +build_flags = -DDEBUG_LEVEL=DBG_DEBUG + -DDEBUG_ESP_CORE + -DDEBUG_ESP_WIFI + -DDEBUG_ESP_HTTP_CLIENT + -DDEBUG_ESP_HTTP_SERVER + -DDEBUG_ESP_OOM + -DDEBUG_ESP_PORT=Serial + -std=gnu++14 build_unflags = -std=gnu++11 build_type = debug monitor_filters = @@ -136,7 +140,7 @@ monitor_filters = log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory [env:opendtufusionv1-release] -platform = espressif32 +platform = espressif32@>=6.1.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin upload_speed = 115200 From 15ec6a0608dd1ad7e754b7b291c99cc0f15c4e67 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 19 Apr 2023 23:21:42 +0200 Subject: [PATCH 20/43] 0.6.9 release --- src/CHANGES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index ba8f2d25..281dc784 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,7 +1,5 @@ Changelog v0.6.9 -* LEDs are now configurable to show if 1st inverter is available and if MqTT is connected -* LED are configurable to active high or low * improved MqTT * fix WiFi hostname during boot up * improved login: only one session at the same time is possible @@ -13,3 +11,5 @@ Changelog v0.6.9 * improved save settings * improved UI in setup: now `.` and `,` are allowed as floating point seperator * fix zero yield day functionality +* LEDs are now configurable to show if 1st inverter is available and if MqTT is connected +* LED are configurable to active high or low From df40286c04db2c775fc6d3196e40b7348e2f5aa2 Mon Sep 17 00:00:00 2001 From: Thomas Pohl Date: Sun, 23 Apr 2023 11:14:13 +0200 Subject: [PATCH 21/43] Add support for 128x32 display --- src/plugins/Display/Display_Mono.cpp | 104 +++++++++++++++++++++------ src/plugins/Display/Display_Mono.h | 13 ++-- src/web/html/setup.html | 4 +- 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/src/plugins/Display/Display_Mono.cpp b/src/plugins/Display/Display_Mono.cpp index d55b6061..26da4142 100644 --- a/src/plugins/Display/Display_Mono.cpp +++ b/src/plugins/Display/Display_Mono.cpp @@ -28,7 +28,7 @@ DisplayMono::DisplayMono() { void DisplayMono::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version) { - if ((0 < type) && (type < 4)) { + if ((0 < type) && (type < 5)) { u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); mType = type; switch(type) { @@ -42,21 +42,25 @@ void DisplayMono::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, u case 3: mDisplay = new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset); break; + case 4: + mDisplay = new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, reset, clock, data); + break; } mUtcTs = utcTs; mDisplay->begin(); - mIsLarge = (mDisplay->getWidth() > 120); - calcLineHeights(); + mIsWide = (mDisplay->getWidth() > 120); + mIsTall = (mDisplay->getHeight() > 60); + calcLinePositions(); mDisplay->clearBuffer(); if (3 != mType) mDisplay->setContrast(mLuminance); - printText("AHOY!", 0, 35); - printText("ahoydtu.de", 2, 20); - printText(version, 3, 46); + printText("AHOY!", 0); + printText("ahoydtu.de", 2); + printText(version, 3); mDisplay->sendBuffer(); } } @@ -92,7 +96,7 @@ void DisplayMono::disp(float totalPower, float totalYieldDay, float totalYieldTo } printText(_fmtText, 0); } else { - printText("offline", 0, 25); + printText("offline", 0); // check if it's time to enter power saving mode if (mTimeout == 0) mDisplay->setPowerSave(mEnPowerSafe); @@ -111,10 +115,12 @@ void DisplayMono::disp(float totalPower, float totalYieldDay, float totalYieldTo snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); printText(_fmtText, 3); } else { - if(mIsLarge && (NULL != mUtcTs)) - printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - else - printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + if (NULL != mUtcTs){ + if(mIsWide && mIsTall) + printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + else + printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + } } mDisplay->sendBuffer(); @@ -123,35 +129,87 @@ void DisplayMono::disp(float totalPower, float totalYieldDay, float totalYieldTo _mExtra++; } -void DisplayMono::calcLineHeights() { - uint8_t yOff = 0; +void DisplayMono::calcLinePositions() { + uint8_t yOff[] = {0,0}; for (uint8_t i = 0; i < 4; i++) { setFont(i); - yOff += (mDisplay->getMaxCharHeight()); - mLineOffsets[i] = yOff; + yOff[getColumn(i)] += (mDisplay->getMaxCharHeight()); + mLineYOffsets[i] = yOff[getColumn(i)]; + if (isTwoRowLine(i)){ + yOff[getColumn(i)] += mDisplay->getMaxCharHeight(); + } + yOff[getColumn(i)]+= BOTTOM_MARGIN; + if (mIsTall && mIsWide){ + mLineXOffsets[i] = (i == 0) ? 10 : 5 + (getColumn(i)==1? 80 : 0); + } else { + mLineXOffsets[i] = (getColumn(i)==1? 80 : 0); + } } } inline void DisplayMono::setFont(uint8_t line) { switch (line) { case 0: - mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_logisoso16_tr); + if (mIsWide && mIsTall){ + mDisplay->setFont(u8g2_font_ncenB14_tr); + } else if (mIsWide && ! mIsTall){ + mDisplay->setFont(u8g2_font_9x15_tf); + } else { + mDisplay->setFont(u8g2_font_logisoso16_tr); + } break; case 3: - mDisplay->setFont(u8g2_font_5x8_tr); + if (mIsWide && ! mIsTall){ + mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); + } else { + mDisplay->setFont(u8g2_font_5x8_tr); + } break; default: - mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); + if (mIsTall){ + mDisplay->setFont(u8g2_font_ncenB10_tr); + } else if (mIsWide){ + mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); + } else { + mDisplay->setFont(u8g2_font_5x8_tr); + } break; } } -void DisplayMono::printText(const char* text, uint8_t line, uint8_t dispX) { - if (!mIsLarge) { - dispX = (line == 0) ? 10 : 5; +inline uint8_t DisplayMono::getColumn(uint8_t line) { + if (mIsTall){ + return 0; + } else if (line>=1 && line<=2){ + return 1; + } else { + return 0; + } +} + +inline bool DisplayMono::isTwoRowLine(uint8_t line) { + if (mIsTall){ + return false; + } else if (line>=1 && line<=2){ + return true; + } else { + return false; } +} +void DisplayMono::printText(const char* text, uint8_t line) { setFont(line); - dispX += (mEnScreenSaver) ? (_mExtra % 7) : 0; - mDisplay->drawStr(dispX, mLineOffsets[line], text); + uint8_t dispX = mLineXOffsets[line] + ((mEnScreenSaver) ? (_mExtra % 7) : 0); + + if (isTwoRowLine(line)){ + String stringText = String(text); + int space = stringText.indexOf(" "); + mDisplay->drawStr(dispX, mLineYOffsets[line], stringText.substring(0,space).c_str()); + if (space>0) + mDisplay->drawStr(dispX, mLineYOffsets[line] + mDisplay->getMaxCharHeight(), stringText.substring(space+1).c_str()); + } else { + mDisplay->drawStr(dispX, mLineYOffsets[line], text); + } } + + diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index ad04c9f4..a2b03212 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -4,6 +4,7 @@ #include #define DISP_DEFAULT_TIMEOUT 60 // in seconds #define DISP_FMT_TEXT_LEN 32 +#define BOTTOM_MARGIN 5 class DisplayMono { public: @@ -15,9 +16,11 @@ class DisplayMono { void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); private: - void calcLineHeights(); + void calcLinePositions(); void setFont(uint8_t line); - void printText(const char* text, uint8_t line, uint8_t dispX = 5); + uint8_t getColumn(uint8_t line); + bool isTwoRowLine(uint8_t line); + void printText(const char* text, uint8_t line); U8G2* mDisplay; @@ -25,10 +28,12 @@ class DisplayMono { bool mEnPowerSafe, mEnScreenSaver; uint8_t mLuminance; - bool mIsLarge = false; + bool mIsTall = false; + bool mIsWide = false; uint8_t mLoopCnt; uint32_t* mUtcTs; - uint8_t mLineOffsets[5]; + uint8_t mLineXOffsets[5]; + uint8_t mLineYOffsets[5]; uint16_t _dispY; diff --git a/src/web/html/setup.html b/src/web/html/setup.html index c86ee980..427152e3 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -733,7 +733,7 @@ ); } - var opts = [[0, "None"], [1, "SSD1306 0.96\""], [2, "SH1106 1.3\""], [3, "Nokia5110"]]; + var opts = [[0, "None"], [1, "SSD1306 0.96\" 128X64"], [2, "SH1106 1.3\""], [3, "Nokia5110"], [4, "SSD1306 0.96\" 128X32"]]; if("ESP32" == type) opts.push([10, "ePaper"]); var dispType = sel("disp_typ", opts, obj["disp_typ"]); @@ -769,7 +769,7 @@ if(0 == dispType) cl.add("hide"); - else if(dispType <= 2) { // OLED + else if(dispType <= 2 || dispType == 4) { // OLED if(i < 2) cl.remove("hide"); else From dc310a2a488b20e5e660bcb7d773763d0aee2aa9 Mon Sep 17 00:00:00 2001 From: FelixM01 <83169342+FelixM01@users.noreply.github.com> Date: Sun, 23 Apr 2023 13:21:15 +0200 Subject: [PATCH 22/43] addition to the topic firmware update I have added the topic Update your AHOY-DTU Firmware. Based on own experience and help from discord server. --- User_Manual.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/User_Manual.md b/User_Manual.md index 242a2809..a7be8f80 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -321,6 +321,19 @@ Send Power Limit: - A persistent limit is only needed if you want to throttle your inverter permanently or you can use it to set a start value on the battery, which is then always the switch-on limit when switching on, otherwise it would ramp up to 100% without regulation, which is continuous load is not healthy. - You can set a new limit in the turn-off state, which is then used for on (switching on again), otherwise the last limit from before the turn-off is used, but of course this only applies if DC voltage is applied the whole time. - If the DC voltage is missing for a few seconds, the microcontroller in the inverter goes off and forgets everything that was temporary/non-persistent in the RAM: YieldDay, error memory, non-persistent limit. +### Update your AHOY-DTU Firmware +To update your AHOY-DTU, you have to download the latest firmware package. +Here are the [latest stable releases](https://github.com/lumapu/ahoy/releases/) and [latest development builds](https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip) available for download. +As soon as you have downloaded the firmware package, unzip it. On the WebUI, navigate to Update and press on select firmware file. +From the unzipped files, select the right .bin file for your hardware and needs. +- If you use an ESP8266, select the file ending with esp8266.bin +- If you use an ESP8266 with prometheus, select the file ending with esp8266_prometheus.bin +- If you use an ESP32, select the file ending with esp32.bin +- If you use an ESP32 with prometheus, select the file ending with esp32_prometheus.bin + +Note: if you want to use prometheus, the usage of an ESP32 is recommended, since the ESP8266 is at its performance limits and therefore can cause stability issues. + +After selecting the right firmware file, press update. Your AHOY-DTU will now install the new firmware and reboot. ## Additional Notes ### MI Inverters From c004c83bf92733cbef943c7a5199a156506cbef2 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 25 Apr 2023 22:50:23 +0200 Subject: [PATCH 23/43] added statemachine --- src/publisher/pubMqtt.h | 123 ++++++++-------------- src/publisher/pubMqttIvData.h | 187 ++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 78 deletions(-) create mode 100644 src/publisher/pubMqttIvData.h diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 7b766c92..e2bb96f5 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -22,6 +22,7 @@ #include "../hm/hmSystem.h" #include "pubMqttDefs.h" +#include "pubMqttIvData.h" #define QOS_0 0 @@ -34,6 +35,13 @@ struct alarm_t { alarm_t(uint16_t c, uint32_t s, uint32_t e) : code(c), start(s), end(e) {} }; +typedef struct { + bool running; + uint8_t lastIvId; + uint8_t sub; + uint8_t foundIvCnt; +} discovery_t; + template class PubMqtt { public: @@ -56,8 +64,8 @@ class PubMqtt { mUtcTimestamp = utcTs; mIntervalTimeout = 1; + mSendIvData.setup(sys, utcTs); mDiscovery.running = false; - mSendIvData.running = false; snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); @@ -82,16 +90,13 @@ class PubMqtt { } void loop() { + mSendIvData.loop(); + #if defined(ESP8266) mClient.loop(); yield(); #endif - if(mSendIvData.running) { - sendIvDataLoop(); - return; - } - if(mDiscovery.running) discoveryConfigLoop(); } @@ -106,14 +111,13 @@ class PubMqtt { return; // next try in a second } - if(0 == mCfgMqtt->interval) { // no fixed interval, publish once new data were received (from inverter) - sendIvDataStart(); - } + if(0 == mCfgMqtt->interval) // no fixed interval, publish once new data were received (from inverter) + sendIvData(); else { // send mqtt data in a fixed interval if(mIntervalTimeout == 0) { mIntervalTimeout = mCfgMqtt->interval; mSendList.push(RealTimeRunData_Debug); - sendIvDataStart(); + sendIvData(); } } } @@ -554,65 +558,55 @@ class PubMqtt { } } - void sendIvDataStart() { - mSendIvData.RTRDataHasBeenSent = false; - memset(mSendIvData.total, 0, sizeof(float) * 4); - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL != iv) { - if (iv->config->enabled) { - mSendIvData.lastIvId = id; - mSendIvData.running = true; - mSendIvData.lastIvReached = false; - mSendIvData.sendTotals = false; - break; - } - } - } - } - - void sendIvDataLoop(void) { + void sendIvData() { bool anyAvail = processIvStatus(); if (mLastAnyAvail != anyAvail) - mSendList.push(RealTimeRunData_Debug); // makes sure that total values are calculated + mSendList.push(RealTimeRunData_Debug); // makes shure that total values are calculated - if(mSendList.empty()) { - mSendIvData.running = false; + if(mSendList.empty()) return; - } + float total[4]; + bool RTRDataHasBeenSent = false; - //while(!mSendList.empty()) { + while(!mSendList.empty()) { + memset(total, 0, sizeof(float) * 4); uint8_t curInfoCmd = mSendList.front(); - if ((curInfoCmd != RealTimeRunData_Debug) || !mSendIvData.RTRDataHasBeenSent) { // send RTR Data only once - mSendIvData.sendTotals = (curInfoCmd == RealTimeRunData_Debug); - if(!mSendIvData.lastIvReached) { - Inverter<> *iv = mSys->getInverterByPos(mSendIvData.lastIvId); + if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once + bool sendTotals = (curInfoCmd == RealTimeRunData_Debug); + + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter + if (!iv->config->enabled) + continue; // skip to next inverter + // send RTR Data only if status is available - if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[mSendIvData.lastIvId])) + if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[id])) sendData(iv, curInfoCmd); // calculate total values for RealTimeRunData_Debug - if (mSendIvData.sendTotals) { + if (sendTotals) { record_t<> *rec = iv->getRecordStruct(curInfoCmd); - mSendIvData.sendTotals &= (iv->getLastTs(rec) > 0); - if (mSendIvData.sendTotals) { + sendTotals &= (iv->getLastTs(rec) > 0); + if (sendTotals) { for (uint8_t i = 0; i < rec->length; i++) { if (CH0 == rec->assign[i].ch) { switch (rec->assign[i].fieldId) { case FLD_PAC: - mSendIvData.total[0] += iv->getValue(i, rec); + total[0] += iv->getValue(i, rec); break; case FLD_YT: - mSendIvData.total[1] += iv->getValue(i, rec); + total[1] += iv->getValue(i, rec); break; case FLD_YD: - mSendIvData.total[2] += iv->getValue(i, rec); + total[2] += iv->getValue(i, rec); break; case FLD_PDC: - mSendIvData.total[3] += iv->getValue(i, rec); + total[3] += iv->getValue(i, rec); break; } } @@ -620,21 +614,9 @@ class PubMqtt { } } yield(); - - // get next inverter - for (uint8_t id = mSendIvData.lastIvId; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL != iv) { - if (iv->config->enabled) { - mSendIvData.lastIvId = id; - return; - } - } - } - mSendIvData.lastIvReached = true; } - if (mSendIvData.sendTotals) { + if (sendTotals) { uint8_t fieldId; for (uint8_t i = 0; i < 4; i++) { bool retained = true; @@ -656,36 +638,20 @@ class PubMqtt { break; } snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); - snprintf(mVal, 40, "%g", ah::round3(mSendIvData.total[i])); + snprintf(mVal, 40, "%g", ah::round3(total[i])); publish(mSubTopic, mVal, retained); } - mSendIvData.RTRDataHasBeenSent = true; + RTRDataHasBeenSent = true; yield(); } } mSendList.pop(); // remove from list once all inverters were processed - //} // end while + } mLastAnyAvail = anyAvail; } - typedef struct { - bool running; - uint8_t lastIvId; - bool lastIvReached; - bool sendTotals; - float total[4]; - bool RTRDataHasBeenSent; - } publish_t; - - typedef struct { - bool running; - uint8_t lastIvId; - uint8_t sub; - uint8_t foundIvCnt; - } discovery_t; - espMqttClient mClient; cfgMqtt_t *mCfgMqtt; #if defined(ESP8266) @@ -693,6 +659,8 @@ class PubMqtt { #endif HMSYSTEM *mSys; + PubMqttIvData mSendIvData; + uint32_t *mUtcTimestamp; uint32_t mRxCnt, mTxCnt; std::queue mSendList; @@ -712,7 +680,6 @@ class PubMqtt { char mSubTopic[32 + MAX_NAME_LENGTH + 1]; char mVal[40]; discovery_t mDiscovery; - publish_t mSendIvData; }; #endif /*__PUB_MQTT_H__*/ diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h new file mode 100644 index 00000000..361ce307 --- /dev/null +++ b/src/publisher/pubMqttIvData.h @@ -0,0 +1,187 @@ +#ifndef __PUB_MQTT_IV_DATA_H__ +#define __PUB_MQTT_IV_DATA_H__ + +#include "../utils/dbg.h" +#include "../hm/hmSystem.h" +#include "pubMqttDefs.h" + +typedef std::function pubMqttPublisherType; + +template +class PubMqttIvData { + public: + void setup(HMSYSTEM *sys, uint32_t *utcTs) { + mSys = sys; + mUtcTimestamp = utcTs; + mState = IDLE; + + memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4); + + mTable[IDLE] = &PubMqttIvData::stateIdle; + mTable[START] = &PubMqttIvData::stateStart; + mTable[FIND_NXT_IV] = &PubMqttIvData::stateFindNxtIv; + mTable[SEND_DATA] = &PubMqttIvData::stateSend; + mTable[SEND_TOTALS] = &PubMqttIvData::stateSendTotals; + } + + void loop() { + (this->*mTable[mState])(); + yield(); + } + + bool start(uint8_t cmd) { + if(IDLE != mState) + return false; + + mCmd = cmd; + mState = START; + return true; + } + + void setPublishFunc(pubMqttPublisherType cb) { + mPublish = cb; + } + + private: + enum State {IDLE, START, FIND_NXT_IV, SEND_DATA, SEND_TOTALS, NUM_STATES}; + typedef void (PubMqttIvData::*StateFunction)(); + + void stateIdle() { + ; // nothing to do + } + + void stateStart() { + mLastIvId = 0; + mSendTotals = (RealTimeRunData_Debug == mCmd); + memset(mTotal, 0, sizeof(float) * 4); + + mState = FIND_NXT_IV; + } + + void stateFindNxtIv() { + bool found = false; + + for (; mLastIvId < mSys->getNumInverters(); mLastIvId++) { + mIv = mSys->getInverterByPos(mLastIvId); + if (NULL != mIv) { + if (mIv->config->enabled) { + found = true; + break; + } + } + } + + mPos = 0; + if(found) + mState = SEND_DATA; + else if(mSendTotals) + mState = SEND_TOTALS; + else + mState = IDLE; + } + + void stateSend() { + record_t<> *rec = mIv->getRecordStruct(mCmd); + uint32_t lastTs = mIv->getLastTs(rec); + bool pubData = (lastTs > 0); + if (mCmd == RealTimeRunData_Debug) + pubData &= (lastTs != mIvLastRTRpub[mIv->id]); + + if (pubData) { + mIvLastRTRpub[mIv->id] = lastTs; + //for (uint8_t i = 0; i < rec->length; i++) { + if(mPos < rec->length) { + bool retained = false; + if (mCmd == RealTimeRunData_Debug) { + switch (rec->assign[mPos].fieldId) { + case FLD_YT: + case FLD_YD: + if ((rec->assign[mPos].ch == CH0) && (!mIv->isProducing(*mUtcTimestamp))) { // avoids returns to 0 on restart + mPos++; + return; + } + retained = true; + break; + } + + // calculate total values for RealTimeRunData_Debug + if (CH0 == rec->assign[mPos].ch) { + switch (rec->assign[mPos].fieldId) { + case FLD_PAC: + mTotal[0] += mIv->getValue(mPos, rec); + break; + case FLD_YT: + mTotal[1] += mIv->getValue(mPos, rec); + break; + case FLD_YD: + mTotal[2] += mIv->getValue(mPos, rec); + break; + case FLD_PDC: + mTotal[3] += mIv->getValue(mPos, rec); + break; + } + } + } + + snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); + snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec))); + mPublish(mSubTopic, mVal, retained); + mPos++; + } else + mState = FIND_NXT_IV; + } else + mState = FIND_NXT_IV; + } + + void stateSendTotals() { + uint8_t fieldId; + //for (uint8_t i = 0; i < 4; i++) { + if(mPos < 4) { + bool retained = true; + switch (mPos) { + default: + case 0: + fieldId = FLD_PAC; + retained = false; + break; + case 1: + fieldId = FLD_YT; + break; + case 2: + fieldId = FLD_YD; + break; + case 3: + fieldId = FLD_PDC; + retained = false; + break; + } + snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); + snprintf(mVal, 40, "%g", ah::round3(mTotal[mPos])); + mPublish(mSubTopic, mVal, retained); + mPos++; + } else + mState = IDLE; + + RTRDataHasBeenSent = true; + } + + HMSYSTEM *mSys; + uint32_t *mUtcTimestamp; + pubMqttPublisherType mPublish; + State mState; + StateFunction mTable[NUM_STATES]; + + uint8_t mCmd; + uint8_t mLastIvId; + bool mSendTotals; + float mTotal[4]; + + Inverter<> *mIv; + uint8_t mPos; + uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS]; + + char mSubTopic[32 + MAX_NAME_LENGTH + 1]; + char mVal[40]; +}; + +#endif /*__PUB_MQTT_IV_DATA_H__*/ From 2108a13fa715cf4cb72e4e5719f70b3ed54c9a1c Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 27 Apr 2023 21:38:01 +0200 Subject: [PATCH 24/43] pub mqtt loop in extra class --- src/publisher/pubMqtt.h | 89 ++--------------------------------- src/publisher/pubMqttIvData.h | 37 +++++++++++---- 2 files changed, 33 insertions(+), 93 deletions(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index e2bb96f5..049554d7 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -64,7 +64,10 @@ class PubMqtt { mUtcTimestamp = utcTs; mIntervalTimeout = 1; - mSendIvData.setup(sys, utcTs); + mSendIvData.setup(sys, utcTs, &mSendList); + mSendIvData.setPublishFunc([this](const char *subTopic, const char *payload, bool retained) { + publish(subTopic, payload, retained); + }); mDiscovery.running = false; snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); @@ -566,89 +569,7 @@ class PubMqtt { if(mSendList.empty()) return; - float total[4]; - bool RTRDataHasBeenSent = false; - - while(!mSendList.empty()) { - memset(total, 0, sizeof(float) * 4); - uint8_t curInfoCmd = mSendList.front(); - - if ((curInfoCmd != RealTimeRunData_Debug) || !RTRDataHasBeenSent) { // send RTR Data only once - bool sendTotals = (curInfoCmd == RealTimeRunData_Debug); - - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL == iv) - continue; // skip to next inverter - if (!iv->config->enabled) - continue; // skip to next inverter - - // send RTR Data only if status is available - if ((curInfoCmd != RealTimeRunData_Debug) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD != mLastIvState[id])) - sendData(iv, curInfoCmd); - - // calculate total values for RealTimeRunData_Debug - if (sendTotals) { - record_t<> *rec = iv->getRecordStruct(curInfoCmd); - - sendTotals &= (iv->getLastTs(rec) > 0); - if (sendTotals) { - for (uint8_t i = 0; i < rec->length; i++) { - if (CH0 == rec->assign[i].ch) { - switch (rec->assign[i].fieldId) { - case FLD_PAC: - total[0] += iv->getValue(i, rec); - break; - case FLD_YT: - total[1] += iv->getValue(i, rec); - break; - case FLD_YD: - total[2] += iv->getValue(i, rec); - break; - case FLD_PDC: - total[3] += iv->getValue(i, rec); - break; - } - } - } - } - } - yield(); - } - - if (sendTotals) { - uint8_t fieldId; - for (uint8_t i = 0; i < 4; i++) { - bool retained = true; - switch (i) { - default: - case 0: - fieldId = FLD_PAC; - retained = false; - break; - case 1: - fieldId = FLD_YT; - break; - case 2: - fieldId = FLD_YD; - break; - case 3: - fieldId = FLD_PDC; - retained = false; - break; - } - snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); - snprintf(mVal, 40, "%g", ah::round3(total[i])); - publish(mSubTopic, mVal, retained); - } - RTRDataHasBeenSent = true; - yield(); - } - } - - mSendList.pop(); // remove from list once all inverters were processed - } - + mSendIvData.start(); mLastAnyAvail = anyAvail; } diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 361ce307..4c574118 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -1,3 +1,8 @@ +//----------------------------------------------------------------------------- +// 2023 Ahoy, https://ahoydtu.de +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + #ifndef __PUB_MQTT_IV_DATA_H__ #define __PUB_MQTT_IV_DATA_H__ @@ -10,12 +15,14 @@ typedef std::function class PubMqttIvData { public: - void setup(HMSYSTEM *sys, uint32_t *utcTs) { + void setup(HMSYSTEM *sys, uint32_t *utcTs, std::queue *sendList) { mSys = sys; mUtcTimestamp = utcTs; + mSendList = sendList; mState = IDLE; memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * 4); + mRTRDataHasBeenSent = false; mTable[IDLE] = &PubMqttIvData::stateIdle; mTable[START] = &PubMqttIvData::stateStart; @@ -29,11 +36,11 @@ class PubMqttIvData { yield(); } - bool start(uint8_t cmd) { + bool start(void) { if(IDLE != mState) return false; - mCmd = cmd; + mRTRDataHasBeenSent = false; mState = START; return true; } @@ -52,10 +59,17 @@ class PubMqttIvData { void stateStart() { mLastIvId = 0; - mSendTotals = (RealTimeRunData_Debug == mCmd); - memset(mTotal, 0, sizeof(float) * 4); + if(!mSendList->empty()) { + mCmd = mSendList->front(); - mState = FIND_NXT_IV; + if((RealTimeRunData_Debug != mCmd) || !mRTRDataHasBeenSent) { + mSendTotals = (RealTimeRunData_Debug == mCmd); + memset(mTotal, 0, sizeof(float) * 4); + mState = FIND_NXT_IV; + } else + mSendList->pop(); + } else + mState = IDLE; } void stateFindNxtIv() { @@ -159,10 +173,12 @@ class PubMqttIvData { snprintf(mVal, 40, "%g", ah::round3(mTotal[mPos])); mPublish(mSubTopic, mVal, retained); mPos++; - } else - mState = IDLE; + } else { + mSendList->pop(); + mState = START; + } - RTRDataHasBeenSent = true; + mRTRDataHasBeenSent = true; } HMSYSTEM *mSys; @@ -179,9 +195,12 @@ class PubMqttIvData { Inverter<> *mIv; uint8_t mPos; uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS]; + bool mRTRDataHasBeenSent; char mSubTopic[32 + MAX_NAME_LENGTH + 1]; char mVal[40]; + + std::queue *mSendList; }; #endif /*__PUB_MQTT_IV_DATA_H__*/ From 8ead358f4cd4602e00a7f6fc38df8118f2e4f928 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 27 Apr 2023 23:09:49 +0200 Subject: [PATCH 25/43] fix publish loop --- src/app.cpp | 4 +++- src/defines.h | 4 ++-- src/hm/hmPayload.h | 23 +++++++++++++++++++++++ src/publisher/pubMqttIvData.h | 8 ++++---- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index d2d87f50..c61c81aa 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -32,7 +32,7 @@ void app::setup() { mSys.enableDebug(); mSys.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs, mConfig->nrf.pinSclk, mConfig->nrf.pinMosi, mConfig->nrf.pinMiso); -#if defined(AP_ONLY) + #if defined(AP_ONLY) mInnerLoopCb = std::bind(&app::loopStandard, this); #else mInnerLoopCb = std::bind(&app::loopWifi, this); @@ -171,6 +171,8 @@ void app::regularTickers(void) { if (mConfig->plugin.display.type != 0) everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp"); every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart"); + + // every([this]() {mPayload.simulation();}, 15, "simul"); } //----------------------------------------------------------------------------- diff --git a/src/defines.h b/src/defines.h index 93722b6c..7260d766 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2023 Ahoy, https://ahoydtu.de // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 9 +#define VERSION_PATCH 11 //------------------------------------- typedef struct { diff --git a/src/hm/hmPayload.h b/src/hm/hmPayload.h index 29f380fa..0ec40d22 100644 --- a/src/hm/hmPayload.h +++ b/src/hm/hmPayload.h @@ -70,6 +70,29 @@ class HmPayload { } } + /*void simulation() { + uint8_t pay[] = { + 0x00, 0x01, 0x01, 0x24, 0x02, 0x28, 0x02, 0x33, + 0x06, 0x49, 0x06, 0x6a, 0x00, 0x05, 0x5f, 0x1b, + 0x00, 0x06, 0x66, 0x9a, 0x03, 0xfd, 0x04, 0x0b, + 0x01, 0x23, 0x02, 0x28, 0x02, 0x28, 0x06, 0x41, + 0x06, 0x43, 0x00, 0x05, 0xdc, 0x2c, 0x00, 0x06, + 0x2e, 0x3f, 0x04, 0x01, 0x03, 0xfb, 0x09, 0x78, + 0x13, 0x86, 0x18, 0x15, 0x00, 0xcf, 0x00, 0xfe, + 0x03, 0xe7, 0x01, 0x42, 0x00, 0x03 + }; + + Inverter<> *iv = mSys->getInverterByPos(0); + record_t<> *rec = iv->getRecordStruct(0x0b); + rec->ts = *mTimestamp; + for (uint8_t i = 0; i < rec->length; i++) { + iv->addValue(i, pay, rec); + yield(); + } + iv->doCalculations(); + notify(0x0b); + }*/ + void zeroYieldDay(Inverter<> *iv) { DPRINTLN(DBG_DEBUG, F("zeroYieldDay")); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 4c574118..ba4cbbbf 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -85,6 +85,8 @@ class PubMqttIvData { } } + mLastIvId++; + mPos = 0; if(found) mState = SEND_DATA; @@ -102,8 +104,6 @@ class PubMqttIvData { pubData &= (lastTs != mIvLastRTRpub[mIv->id]); if (pubData) { - mIvLastRTRpub[mIv->id] = lastTs; - //for (uint8_t i = 0; i < rec->length; i++) { if(mPos < rec->length) { bool retained = false; if (mCmd == RealTimeRunData_Debug) { @@ -135,7 +135,8 @@ class PubMqttIvData { break; } } - } + } else + mIvLastRTRpub[mIv->id] = lastTs; snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec))); @@ -149,7 +150,6 @@ class PubMqttIvData { void stateSendTotals() { uint8_t fieldId; - //for (uint8_t i = 0; i < 4; i++) { if(mPos < 4) { bool retained = true; switch (mPos) { From ad9fda4e28ed0635cd5aca0431e9fc19a9837247 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 27 Apr 2023 23:14:29 +0200 Subject: [PATCH 26/43] 0.6.11 * added MqTT class for publishing all values in Arduino `loop` --- src/CHANGES.md | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 281dc784..a4d12226 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,15 +1,10 @@ -Changelog v0.6.9 +# Development Changes -* improved MqTT -* fix WiFi hostname during boot up -* improved login: only one session at the same time is possible -* fix UI: login screen for small displays; burger menu height; small modifications for import button (in setup) -* improved WiFi reconnect -* optimized performance: browser caching was improved to reduce requests from ESP -* improved NRF24 communication for more stable data transmission -* added / fixed MqTT subscription `ctrl/power/[IV-ID]` -* improved save settings -* improved UI in setup: now `.` and `,` are allowed as floating point seperator -* fix zero yield day functionality -* LEDs are now configurable to show if 1st inverter is available and if MqTT is connected -* LED are configurable to active high or low +## 0.6.11 - 2023-04-27 +* added MqTT class for publishing all values in Arduino `loop` + +## 0.6.10 - HMS +* Version available in `HMS` branch + +## 0.6.9 +* last Relaese From f67a68e852306fb16b7287231256432c42843de4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 28 Apr 2023 23:43:00 +0200 Subject: [PATCH 27/43] 0.6.12 * improved MqTT * fix menu active item --- src/CHANGES.md | 4 ++++ src/defines.h | 2 +- src/publisher/pubMqttIvData.h | 6 ++++-- src/web/html/api.js | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index a4d12226..9f83f7e7 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.6.12 - 2023-04-28 +* improved MqTT +* fix menu active item + ## 0.6.11 - 2023-04-27 * added MqTT class for publishing all values in Arduino `loop` diff --git a/src/defines.h b/src/defines.h index 7260d766..cf9395d5 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 11 +#define VERSION_PATCH 12 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index ba4cbbbf..947cd2f4 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -92,8 +92,10 @@ class PubMqttIvData { mState = SEND_DATA; else if(mSendTotals) mState = SEND_TOTALS; - else - mState = IDLE; + else { + mSendList->pop(); + mState = START; + } } void stateSend() { diff --git a/src/web/html/api.js b/src/web/html/api.js index 13ca50b5..1dd5422e 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -78,7 +78,7 @@ function parseNav(obj) { if(i == 2) continue; var l = document.getElementById("nav"+i); - if(window.location.pathname == "/" + l.href.split('/').pop()) + if(window.location.pathname == "/" + l.href.substring(0, l.href.indexOf("?")).split('/').pop()) l.classList.add("active"); if(obj["menu_protEn"]) { From ac207e0d37c5431e2645a70bad62023a3858f9e1 Mon Sep 17 00:00:00 2001 From: Betacentauri Date: Sat, 29 Apr 2023 13:13:22 +0200 Subject: [PATCH 28/43] [rpi] Add log file rotation so that log file doesn't grow infinitely --- tools/rpi/ahoy.yml.example | 2 ++ tools/rpi/hoymiles/__main__.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/rpi/ahoy.yml.example b/tools/rpi/ahoy.yml.example index 9301067f..6efb28ae 100644 --- a/tools/rpi/ahoy.yml.example +++ b/tools/rpi/ahoy.yml.example @@ -7,6 +7,8 @@ ahoy: filename: 'hoymiles.log' # DEBUG, INFO, WARNING, ERROR, FATAL level: 'INFO' + max_log_filesize: 1000000 + max_log_files: 1 sunset: disabled: false diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py index 7de4a1a2..cb5ecf2b 100644 --- a/tools/rpi/hoymiles/__main__.py +++ b/tools/rpi/hoymiles/__main__.py @@ -19,6 +19,7 @@ import yaml from yaml.loader import SafeLoader import hoymiles import logging +from logging.handlers import RotatingFileHandler ################################################################################ """ Signal Handler """ @@ -298,6 +299,8 @@ def init_logging(ahoy_config): log_config = ahoy_config.get('logging') fn = 'hoymiles.log' lvl = logging.ERROR + max_log_filesize = 1000000 + max_log_files = 1 if log_config: fn = log_config.get('filename', fn) level = log_config.get('level', 'ERROR') @@ -311,9 +314,11 @@ def init_logging(ahoy_config): lvl = logging.ERROR elif level == 'FATAL': lvl = logging.FATAL + max_log_filesize = log_config.get('max_log_filesize', max_log_filesize) + max_log_files = log_config.get('max_log_files', max_log_files) if hoymiles.HOYMILES_TRANSACTION_LOGGING: lvl = logging.DEBUG - logging.basicConfig(filename=fn, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl) + logging.basicConfig(handlers=[RotatingFileHandler(fn, maxBytes=max_log_filesize, backupCount=max_log_files)], format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl) dtu_name = ahoy_config.get('dtu',{}).get('name','hoymiles-dtu') logging.info(f'start logging for {dtu_name} with level: {logging.root.level}') From 2005348f544e440815ee76df267d27c3f70248cc Mon Sep 17 00:00:00 2001 From: Betacentauri Date: Sat, 29 Apr 2023 14:11:18 +0200 Subject: [PATCH 29/43] [rpi] Add powerfactor and event_count decoder for HM300, HM350, HM400 Without event_count program crashes immediately in __main__.py line 233. --- tools/rpi/hoymiles/decoders/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/rpi/hoymiles/decoders/__init__.py b/tools/rpi/hoymiles/decoders/__init__.py index bb32fb07..ad49d664 100644 --- a/tools/rpi/hoymiles/decoders/__init__.py +++ b/tools/rpi/hoymiles/decoders/__init__.py @@ -515,9 +515,17 @@ class Hm300Decode0B(StatusResponse): """ reactive power """ return self.unpack('>H', 20)[0]/10 @property + def powerfactor(self): + """ Powerfactor """ + return self.unpack('>H', 24)[0]/1000 + @property def temperature(self): """ Inverter temperature in °C """ return self.unpack('>h', 26)[0]/10 + @property + def event_count(self): + """ Event counter """ + return self.unpack('>H', 28)[0] class Hm300Decode0C(Hm300Decode0B): """ 1121-series mirco-inverters status data """ From 8960fa6650d9e4893cb90fc3a8018948d94d718d Mon Sep 17 00:00:00 2001 From: Thomas Pohl Date: Mon, 1 May 2023 12:15:33 +0200 Subject: [PATCH 30/43] Refactored DisplayMono class to improve maintainability --- src/plugins/Display/Display.h | 192 +++++++++-------- src/plugins/Display/Display_Mono.cpp | 215 -------------------- src/plugins/Display/Display_Mono.h | 38 +--- src/plugins/Display/Display_Mono_128X32.cpp | 169 +++++++++++++++ src/plugins/Display/Display_Mono_128X32.h | 37 ++++ src/plugins/Display/Display_Mono_128X64.cpp | 146 +++++++++++++ src/plugins/Display/Display_Mono_128X64.h | 36 ++++ src/plugins/Display/Display_Mono_84X48.cpp | 140 +++++++++++++ src/plugins/Display/Display_Mono_84X48.h | 34 ++++ 9 files changed, 673 insertions(+), 334 deletions(-) delete mode 100644 src/plugins/Display/Display_Mono.cpp create mode 100644 src/plugins/Display/Display_Mono_128X32.cpp create mode 100644 src/plugins/Display/Display_Mono_128X32.h create mode 100644 src/plugins/Display/Display_Mono_128X64.cpp create mode 100644 src/plugins/Display/Display_Mono_128X64.h create mode 100644 src/plugins/Display/Display_Mono_84X48.cpp create mode 100644 src/plugins/Display/Display_Mono_84X48.h diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 1a0222b2..a16b3bb4 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -7,108 +7,124 @@ #include "../../hm/hmSystem.h" #include "../../utils/helper.h" #include "Display_Mono.h" +#include "Display_Mono_128X32.h" +#include "Display_Mono_128X64.h" +#include "Display_Mono_84X48.h" #include "Display_ePaper.h" template class Display { public: - Display() {} + Display() {} + + void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) { + mCfg = cfg; + mSys = sys; + mUtcTs = utcTs; + mNewPayload = false; + mLoopCnt = 0; + mVersion = version; + + if (mCfg->type == 0) + return; + + if ((0 < mCfg->type) && (mCfg->type < 10)) { + switch (mCfg->type) { + case 2: + case 1: + default: + mMono = new DisplayMono128X64(); + break; + case 3: + mMono = new DisplayMono84X48(); + break; + case 4: + mMono = new DisplayMono128X32(); + break; + } + mMono->config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast); + mMono->init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); + } else if (mCfg->type >= 10) { +#if defined(ESP32) + mRefreshCycle = 0; + mEpaper.config(mCfg->rot); + mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); +#endif + } + } + + void payloadEventListener(uint8_t cmd) { + mNewPayload = true; + } - void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) { - mCfg = cfg; - mSys = sys; - mUtcTs = utcTs; + void tickerSecond() { + mMono->loop(); + if (mNewPayload || ((++mLoopCnt % 10) == 0)) { mNewPayload = false; mLoopCnt = 0; - mVersion = version; - - if (mCfg->type == 0) - return; - - if ((0 < mCfg->type) && (mCfg->type < 10)) { - mMono.config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast); - mMono.init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); - } else if (mCfg->type >= 10) { - #if defined(ESP32) - mRefreshCycle = 0; - mEpaper.config(mCfg->rot); - mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); - #endif - } + DataScreen(); } - - void payloadEventListener(uint8_t cmd) { - mNewPayload = true; + } + + private: + void DataScreen() { + if (mCfg->type == 0) + return; + if (*mUtcTs == 0) + return; + + float totalPower = 0; + float totalYieldDay = 0; + float totalYieldTotal = 0; + + uint8_t isprod = 0; + + Inverter<> *iv; + record_t<> *rec; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv == NULL) + continue; + + if (iv->isProducing(*mUtcTs)) + isprod++; + + totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); + totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); + totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); } - void tickerSecond() { - mMono.loop(); - if (mNewPayload || ((++mLoopCnt % 10) == 0)) { - mNewPayload = false; - mLoopCnt = 0; - DataScreen(); - } + if ((0 < mCfg->type) && (mCfg->type < 10)) { + mMono->disp(totalPower, totalYieldDay, totalYieldTotal, isprod); + } else if (mCfg->type >= 10) { +#if defined(ESP32) + mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod); + mRefreshCycle++; +#endif } - private: - void DataScreen() { - if (mCfg->type == 0) - return; - if (*mUtcTs == 0) - return; - - float totalPower = 0; - float totalYieldDay = 0; - float totalYieldTotal = 0; - - uint8_t isprod = 0; - - Inverter<> *iv; - record_t<> *rec; - for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { - iv = mSys->getInverterByPos(i); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (iv == NULL) - continue; - - if (iv->isProducing(*mUtcTs)) - isprod++; - - totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); - totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); - totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); - } - - if ((0 < mCfg->type) && (mCfg->type < 10)) { - mMono.disp(totalPower, totalYieldDay, totalYieldTotal, isprod); - } else if (mCfg->type >= 10) { - #if defined(ESP32) - mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod); - mRefreshCycle++; - #endif - } - - #if defined(ESP32) - if (mRefreshCycle > 480) { - mEpaper.fullRefresh(); - mRefreshCycle = 0; - } - #endif +#if defined(ESP32) + if (mRefreshCycle > 480) { + mEpaper.fullRefresh(); + mRefreshCycle = 0; } - - // private member variables - bool mNewPayload; - uint8_t mLoopCnt; - uint32_t *mUtcTs; - const char *mVersion; - display_t *mCfg; - HMSYSTEM *mSys; - uint16_t mRefreshCycle; - - #if defined(ESP32) - DisplayEPaper mEpaper; - #endif - DisplayMono mMono; +#endif + } + + // private member variables + bool mNewPayload; + uint8_t mLoopCnt; + uint32_t *mUtcTs; + const char *mVersion; + display_t *mCfg; + HMSYSTEM *mSys; + uint16_t mRefreshCycle; + +#if defined(ESP32) + DisplayEPaper mEpaper; +#endif + DisplayMono *mMono; }; #endif /*__DISPLAY__*/ diff --git a/src/plugins/Display/Display_Mono.cpp b/src/plugins/Display/Display_Mono.cpp deleted file mode 100644 index 26da4142..00000000 --- a/src/plugins/Display/Display_Mono.cpp +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include "Display_Mono.h" - -#ifdef ESP8266 - #include -#elif defined(ESP32) - #include -#endif -#include "../../utils/helper.h" - -//#ifdef U8X8_HAVE_HW_SPI -//#include -//#endif -//#ifdef U8X8_HAVE_HW_I2C -//#include -//#endif - -DisplayMono::DisplayMono() { - mEnPowerSafe = true; - mEnScreenSaver = true; - mLuminance = 60; - _dispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) - mUtcTs = NULL; - mType = 0; -} - - - -void DisplayMono::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version) { - if ((0 < type) && (type < 5)) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); - mType = type; - switch(type) { - case 1: - mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); - break; - default: - case 2: - mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); - break; - case 3: - mDisplay = new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset); - break; - case 4: - mDisplay = new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, reset, clock, data); - break; - } - - mUtcTs = utcTs; - - mDisplay->begin(); - - mIsWide = (mDisplay->getWidth() > 120); - mIsTall = (mDisplay->getHeight() > 60); - calcLinePositions(); - - mDisplay->clearBuffer(); - if (3 != mType) - mDisplay->setContrast(mLuminance); - printText("AHOY!", 0); - printText("ahoydtu.de", 2); - printText(version, 3); - mDisplay->sendBuffer(); - } -} - -void DisplayMono::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { - mEnPowerSafe = enPowerSafe; - mEnScreenSaver = enScreenSaver; - mLuminance = lum; -} - -void DisplayMono::loop(void) { - if (mEnPowerSafe) - if(mTimeout != 0) - mTimeout--; -} - -void DisplayMono::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { - - - mDisplay->clearBuffer(); - - // set Contrast of the Display to raise the lifetime - if (3 != mType) - mDisplay->setContrast(mLuminance); - - if ((totalPower > 0) && (isprod > 0)) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); - if (totalPower > 999) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); - } else { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); - } - printText(_fmtText, 0); - } else { - printText("offline", 0); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSafe); - } - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); - printText(_fmtText, 1); - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); - printText(_fmtText, 2); - - IPAddress ip = WiFi.localIP(); - if (!(_mExtra % 10) && (ip)) { - printText(ip.toString().c_str(), 3); - } else if (!(_mExtra % 5)) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); - printText(_fmtText, 3); - } else { - if (NULL != mUtcTs){ - if(mIsWide && mIsTall) - printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - else - printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - } - } - - mDisplay->sendBuffer(); - - _dispY = 0; - _mExtra++; -} - -void DisplayMono::calcLinePositions() { - uint8_t yOff[] = {0,0}; - for (uint8_t i = 0; i < 4; i++) { - setFont(i); - yOff[getColumn(i)] += (mDisplay->getMaxCharHeight()); - mLineYOffsets[i] = yOff[getColumn(i)]; - if (isTwoRowLine(i)){ - yOff[getColumn(i)] += mDisplay->getMaxCharHeight(); - } - yOff[getColumn(i)]+= BOTTOM_MARGIN; - if (mIsTall && mIsWide){ - mLineXOffsets[i] = (i == 0) ? 10 : 5 + (getColumn(i)==1? 80 : 0); - } else { - mLineXOffsets[i] = (getColumn(i)==1? 80 : 0); - } - } -} - -inline void DisplayMono::setFont(uint8_t line) { - switch (line) { - case 0: - if (mIsWide && mIsTall){ - mDisplay->setFont(u8g2_font_ncenB14_tr); - } else if (mIsWide && ! mIsTall){ - mDisplay->setFont(u8g2_font_9x15_tf); - } else { - mDisplay->setFont(u8g2_font_logisoso16_tr); - } - break; - case 3: - if (mIsWide && ! mIsTall){ - mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); - } else { - mDisplay->setFont(u8g2_font_5x8_tr); - } - break; - default: - if (mIsTall){ - mDisplay->setFont(u8g2_font_ncenB10_tr); - } else if (mIsWide){ - mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); - } else { - mDisplay->setFont(u8g2_font_5x8_tr); - } - break; - } -} - -inline uint8_t DisplayMono::getColumn(uint8_t line) { - if (mIsTall){ - return 0; - } else if (line>=1 && line<=2){ - return 1; - } else { - return 0; - } -} - -inline bool DisplayMono::isTwoRowLine(uint8_t line) { - if (mIsTall){ - return false; - } else if (line>=1 && line<=2){ - return true; - } else { - return false; - } -} -void DisplayMono::printText(const char* text, uint8_t line) { - setFont(line); - - uint8_t dispX = mLineXOffsets[line] + ((mEnScreenSaver) ? (_mExtra % 7) : 0); - - if (isTwoRowLine(line)){ - String stringText = String(text); - int space = stringText.indexOf(" "); - mDisplay->drawStr(dispX, mLineYOffsets[line], stringText.substring(0,space).c_str()); - if (space>0) - mDisplay->drawStr(dispX, mLineYOffsets[line] + mDisplay->getMaxCharHeight(), stringText.substring(space+1).c_str()); - } else { - mDisplay->drawStr(dispX, mLineYOffsets[line], text); - } -} - - diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index a2b03212..5a825d55 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -3,41 +3,17 @@ #include #define DISP_DEFAULT_TIMEOUT 60 // in seconds -#define DISP_FMT_TEXT_LEN 32 -#define BOTTOM_MARGIN 5 +#define DISP_FMT_TEXT_LEN 32 +#define BOTTOM_MARGIN 5 class DisplayMono { public: - DisplayMono(); + DisplayMono(){}; - void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char* version); - void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); - void loop(void); - void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version) = 0; + virtual void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) = 0; + virtual void loop(void) = 0; + virtual void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) = 0; private: - void calcLinePositions(); - void setFont(uint8_t line); - uint8_t getColumn(uint8_t line); - bool isTwoRowLine(uint8_t line); - void printText(const char* text, uint8_t line); - - U8G2* mDisplay; - - uint8_t mType; - bool mEnPowerSafe, mEnScreenSaver; - uint8_t mLuminance; - - bool mIsTall = false; - bool mIsWide = false; - uint8_t mLoopCnt; - uint32_t* mUtcTs; - uint8_t mLineXOffsets[5]; - uint8_t mLineYOffsets[5]; - - uint16_t _dispY; - - uint8_t _mExtra; - uint16_t mTimeout; - char _fmtText[DISP_FMT_TEXT_LEN]; }; diff --git a/src/plugins/Display/Display_Mono_128X32.cpp b/src/plugins/Display/Display_Mono_128X32.cpp new file mode 100644 index 00000000..46a012ec --- /dev/null +++ b/src/plugins/Display/Display_Mono_128X32.cpp @@ -0,0 +1,169 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Mono_128X32.h" + +#include "Display_Mono.h" + +#ifdef ESP8266 +#include +#elif defined(ESP32) +#include +#endif +#include "../../utils/helper.h" + +// #ifdef U8X8_HAVE_HW_SPI +// #include +// #endif +// #ifdef U8X8_HAVE_HW_I2C +// #include +// #endif + +DisplayMono128X32::DisplayMono128X32() : DisplayMono::DisplayMono() { + mEnPowerSafe = true; + mEnScreenSaver = true; + mLuminance = 60; + _dispY = 0; + mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mUtcTs = NULL; + mType = 0; +} + +void DisplayMono128X32::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { + if ((0 < type) && (type < 5)) { + u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); + mType = type; + mDisplay = new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, reset, clock, data); + + mUtcTs = utcTs; + + mDisplay->begin(); + + calcLinePositions(); + + mDisplay->clearBuffer(); + mDisplay->setContrast(mLuminance); + printText("AHOY!", 0); + printText("ahoydtu.de", 2); + printText(version, 3); + mDisplay->sendBuffer(); + } +} + +void DisplayMono128X32::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { + mEnPowerSafe = enPowerSafe; + mEnScreenSaver = enScreenSaver; + mLuminance = lum; +} + +void DisplayMono128X32::loop(void) { + if (mEnPowerSafe) + if (mTimeout != 0) + mTimeout--; +} + +void DisplayMono128X32::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + mDisplay->clearBuffer(); + + // set Contrast of the Display to raise the lifetime + if (3 != mType) + mDisplay->setContrast(mLuminance); + + if ((totalPower > 0) && (isprod > 0)) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + if (totalPower > 999) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); + } else { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); + } + printText(_fmtText, 0); + } else { + printText("offline", 0); + // check if it's time to enter power saving mode + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); + } + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); + printText(_fmtText, 1); + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); + printText(_fmtText, 2); + + IPAddress ip = WiFi.localIP(); + if (!(_mExtra % 10) && (ip)) { + printText(ip.toString().c_str(), 3); + } else if (!(_mExtra % 5)) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); + printText(_fmtText, 3); + } else { + if (NULL != mUtcTs) { + printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + } + } + + mDisplay->sendBuffer(); + + _dispY = 0; + _mExtra++; +} + +void DisplayMono128X32::calcLinePositions() { + uint8_t yOff[] = {0, 0}; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff[getColumn(i)] += (mDisplay->getMaxCharHeight()); + mLineYOffsets[i] = yOff[getColumn(i)]; + if (isTwoRowLine(i)) { + yOff[getColumn(i)] += mDisplay->getMaxCharHeight(); + } + yOff[getColumn(i)] += BOTTOM_MARGIN; + mLineXOffsets[i] = (getColumn(i) == 1 ? 80 : 0); + } +} + +inline void DisplayMono128X32::setFont(uint8_t line) { + switch (line) { + case 0: + + mDisplay->setFont(u8g2_font_9x15_tf); + break; + case 3: + mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); + break; + default: + mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); + break; + } +} + +inline uint8_t DisplayMono128X32::getColumn(uint8_t line) { + if (line >= 1 && line <= 2) { + return 1; + } else { + return 0; + } +} + +inline bool DisplayMono128X32::isTwoRowLine(uint8_t line) { + if (line >= 1 && line <= 2) { + return true; + } else { + return false; + } +} +void DisplayMono128X32::printText(const char *text, uint8_t line) { + setFont(line); + + uint8_t dispX = mLineXOffsets[line] + ((mEnScreenSaver) ? (_mExtra % 7) : 0); + + if (isTwoRowLine(line)) { + String stringText = String(text); + int space = stringText.indexOf(" "); + mDisplay->drawStr(dispX, mLineYOffsets[line], stringText.substring(0, space).c_str()); + if (space > 0) + mDisplay->drawStr(dispX, mLineYOffsets[line] + mDisplay->getMaxCharHeight(), stringText.substring(space + 1).c_str()); + } else { + mDisplay->drawStr(dispX, mLineYOffsets[line], text); + } +} diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h new file mode 100644 index 00000000..00dbf904 --- /dev/null +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -0,0 +1,37 @@ +#pragma once +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Mono.h" + +class DisplayMono128X32 : public DisplayMono { + public: + DisplayMono128X32(); + + void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version); + void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); + void loop(void); + void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + + private: + void calcLinePositions(); + void setFont(uint8_t line); + uint8_t getColumn(uint8_t line); + bool isTwoRowLine(uint8_t line); + void printText(const char* text, uint8_t line); + + U8G2* mDisplay; + + uint8_t mType; + bool mEnPowerSafe, mEnScreenSaver; + uint8_t mLuminance; + + uint8_t mLoopCnt; + uint32_t* mUtcTs; + uint8_t mLineXOffsets[5]; + uint8_t mLineYOffsets[5]; + + uint16_t _dispY; + + uint8_t _mExtra; + uint16_t mTimeout; + char _fmtText[DISP_FMT_TEXT_LEN]; +}; diff --git a/src/plugins/Display/Display_Mono_128X64.cpp b/src/plugins/Display/Display_Mono_128X64.cpp new file mode 100644 index 00000000..0224f50a --- /dev/null +++ b/src/plugins/Display/Display_Mono_128X64.cpp @@ -0,0 +1,146 @@ + +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Mono_128X64.h" + +#include "Display_Mono.h" + +#ifdef ESP8266 +#include +#elif defined(ESP32) +#include +#endif +#include "../../utils/helper.h" + +// #ifdef U8X8_HAVE_HW_SPI +// #include +// #endif +// #ifdef U8X8_HAVE_HW_I2C +// #include +// #endif + +DisplayMono128X64::DisplayMono128X64() : DisplayMono::DisplayMono() { + mEnPowerSafe = true; + mEnScreenSaver = true; + mLuminance = 60; + _dispY = 0; + mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mUtcTs = NULL; + mType = 0; +} + +void DisplayMono128X64::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { + if ((0 < type) && (type < 5)) { + u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); + mType = type; + switch (type) { + case 1: + mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); + break; + default: + case 2: + mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); + break; + } + + mUtcTs = utcTs; + + mDisplay->begin(); + + calcLineHeights(); + + mDisplay->clearBuffer(); + mDisplay->setContrast(mLuminance); + printText("AHOY!", 0, 35); + printText("ahoydtu.de", 2, 20); + printText(version, 3, 46); + mDisplay->sendBuffer(); + } +} + +void DisplayMono128X64::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { + mEnPowerSafe = enPowerSafe; + mEnScreenSaver = enScreenSaver; + mLuminance = lum; +} + +void DisplayMono128X64::loop(void) { + if (mEnPowerSafe) + if (mTimeout != 0) + mTimeout--; +} + +void DisplayMono128X64::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + mDisplay->clearBuffer(); + + // set Contrast of the Display to raise the lifetime + if (3 != mType) + mDisplay->setContrast(mLuminance); + + if ((totalPower > 0) && (isprod > 0)) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + if (totalPower > 999) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); + } else { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); + } + printText(_fmtText, 0); + } else { + printText("offline", 0, 25); + // check if it's time to enter power saving mode + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); + } + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); + printText(_fmtText, 1); + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); + printText(_fmtText, 2); + + IPAddress ip = WiFi.localIP(); + if (!(_mExtra % 10) && (ip)) { + printText(ip.toString().c_str(), 3); + } else if (!(_mExtra % 5)) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); + printText(_fmtText, 3); + } else { + if (NULL != mUtcTs) { + printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + } + } + + mDisplay->sendBuffer(); + + _dispY = 0; + _mExtra++; +} + +void DisplayMono128X64::calcLineHeights() { + uint8_t yOff = 0; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff += (mDisplay->getMaxCharHeight()); + mLineOffsets[i] = yOff; + } +} + +inline void DisplayMono128X64::setFont(uint8_t line) { + switch (line) { + case 0: + mDisplay->setFont(u8g2_font_ncenB14_tr); + break; + case 3: + mDisplay->setFont(u8g2_font_5x8_tr); + break; + default: + mDisplay->setFont(u8g2_font_ncenB10_tr); + break; + } +} +void DisplayMono128X64::printText(const char *text, uint8_t line, uint8_t dispX) { + setFont(line); + + dispX += (mEnScreenSaver) ? (_mExtra % 7) : 0; + mDisplay->drawStr(dispX, mLineOffsets[line], text); +} diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h new file mode 100644 index 00000000..4902d14b --- /dev/null +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -0,0 +1,36 @@ +#pragma once +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Mono.h" + +class DisplayMono128X64 : public DisplayMono { + public: + DisplayMono128X64(); + + void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version); + void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); + void loop(void); + void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + + private: + void calcLineHeights(); + void setFont(uint8_t line); + uint8_t getColumn(uint8_t line); + bool isTwoRowLine(uint8_t line); + void printText(const char* text, uint8_t line, uint8_t dispX = 5); + + U8G2* mDisplay; + + uint8_t mType; + bool mEnPowerSafe, mEnScreenSaver; + uint8_t mLuminance; + + uint8_t mLoopCnt; + uint32_t* mUtcTs; + uint8_t mLineOffsets[5]; + + uint16_t _dispY; + + uint8_t _mExtra; + uint16_t mTimeout; + char _fmtText[DISP_FMT_TEXT_LEN]; +}; diff --git a/src/plugins/Display/Display_Mono_84X48.cpp b/src/plugins/Display/Display_Mono_84X48.cpp new file mode 100644 index 00000000..a84f6037 --- /dev/null +++ b/src/plugins/Display/Display_Mono_84X48.cpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Mono_84X48.h" + +#include "Display_Mono.h" + +#ifdef ESP8266 +#include +#elif defined(ESP32) +#include +#endif +#include "../../utils/helper.h" + +// #ifdef U8X8_HAVE_HW_SPI +// #include +// #endif +// #ifdef U8X8_HAVE_HW_I2C +// #include +// #endif + +DisplayMono84X48::DisplayMono84X48() : DisplayMono::DisplayMono() { + mEnPowerSafe = true; + mEnScreenSaver = true; + mLuminance = 60; + _dispY = 0; + mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mUtcTs = NULL; + mType = 0; +} + +void DisplayMono84X48::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { + if ((0 < type) && (type < 5)) { + u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); + mType = type; + mDisplay = new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset); + + mUtcTs = utcTs; + + mDisplay->begin(); + + calcLineHeights(); + + mDisplay->clearBuffer(); + if (3 != mType) + mDisplay->setContrast(mLuminance); + printText("AHOY!", 0); + printText("ahoydtu.de", 2); + printText(version, 3); + mDisplay->sendBuffer(); + } +} + +void DisplayMono84X48::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { + mEnPowerSafe = enPowerSafe; + mEnScreenSaver = enScreenSaver; + mLuminance = lum; +} + +void DisplayMono84X48::loop(void) { + if (mEnPowerSafe) + if (mTimeout != 0) + mTimeout--; +} + +void DisplayMono84X48::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + mDisplay->clearBuffer(); + + // set Contrast of the Display to raise the lifetime + if (3 != mType) + mDisplay->setContrast(mLuminance); + + if ((totalPower > 0) && (isprod > 0)) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + if (totalPower > 999) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); + } else { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); + } + printText(_fmtText, 0); + } else { + printText("offline", 0); + // check if it's time to enter power saving mode + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); + } + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); + printText(_fmtText, 1); + + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); + printText(_fmtText, 2); + + IPAddress ip = WiFi.localIP(); + if (!(_mExtra % 10) && (ip)) { + printText(ip.toString().c_str(), 3); + } else if (!(_mExtra % 5)) { + snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); + printText(_fmtText, 3); + } else { + if (NULL != mUtcTs) { + printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + } + } + + mDisplay->sendBuffer(); + + _dispY = 0; + _mExtra++; +} + +void DisplayMono84X48::calcLineHeights() { + uint8_t yOff = 0; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff += (mDisplay->getMaxCharHeight()); + mLineOffsets[i] = yOff; + } +} + +inline void DisplayMono84X48::setFont(uint8_t line) { + switch (line) { + case 0: + mDisplay->setFont(u8g2_font_logisoso16_tr); + break; + case 3: + mDisplay->setFont(u8g2_font_5x8_tr); + break; + default: + mDisplay->setFont(u8g2_font_5x8_tr); + break; + } +} + +void DisplayMono84X48::printText(const char *text, uint8_t line) { + uint8_t dispX = (line == 0) ? 10 : 5; + setFont(line); + + dispX += (mEnScreenSaver) ? (_mExtra % 7) : 0; + mDisplay->drawStr(dispX, mLineOffsets[line], text); +} diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h new file mode 100644 index 00000000..1ad140e9 --- /dev/null +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -0,0 +1,34 @@ +#pragma once +// SPDX-License-Identifier: GPL-2.0-or-later +#include "Display_Mono.h" + +class DisplayMono84X48 : public DisplayMono { + public: + DisplayMono84X48(); + + void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version); + void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); + void loop(void); + void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + + private: + void calcLineHeights(); + void setFont(uint8_t line); + void printText(const char* text, uint8_t line); + + U8G2* mDisplay; + + uint8_t mType; + bool mEnPowerSafe, mEnScreenSaver; + uint8_t mLuminance; + + uint8_t mLoopCnt; + uint32_t* mUtcTs; + uint8_t mLineOffsets[5]; + + uint16_t _dispY; + + uint8_t _mExtra; + uint16_t mTimeout; + char _fmtText[DISP_FMT_TEXT_LEN]; +}; From da4a347c49d7de97cd7f7209a4c34a55c85fb59f Mon Sep 17 00:00:00 2001 From: Thomas Pohl Date: Mon, 1 May 2023 12:50:19 +0200 Subject: [PATCH 31/43] not NULL chheck --- src/plugins/Display/Display.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index a16b3bb4..e2c0a040 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -58,7 +58,9 @@ class Display { } void tickerSecond() { - mMono->loop(); + if (mMono!=NULL){ + mMono->loop(); + } if (mNewPayload || ((++mLoopCnt % 10) == 0)) { mNewPayload = false; mLoopCnt = 0; @@ -95,7 +97,7 @@ class Display { totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); } - if ((0 < mCfg->type) && (mCfg->type < 10)) { + if ((0 < mCfg->type) && (mCfg->type < 10) && mMono!=NULL) { mMono->disp(totalPower, totalYieldDay, totalYieldTotal, isprod); } else if (mCfg->type >= 10) { #if defined(ESP32) From ded680bc80a2bca2a811f38dde63a01e3fcf91c4 Mon Sep 17 00:00:00 2001 From: Thomas Pohl Date: Mon, 1 May 2023 19:18:34 +0200 Subject: [PATCH 32/43] For ePaper display, show ahoy logo on offline screen Solves #897 --- src/plugins/Display/Display_ePaper.cpp | 94 +++++++++++++++++--------- src/plugins/Display/Display_ePaper.h | 1 + 2 files changed, 63 insertions(+), 32 deletions(-) diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp index 99d35ed8..7a000c2d 100644 --- a/src/plugins/Display/Display_ePaper.cpp +++ b/src/plugins/Display/Display_ePaper.cpp @@ -120,6 +120,28 @@ void DisplayEPaper::lastUpdatePaged() { } while (_display->nextPage()); } //*************************************************************************** +void DisplayEPaper::offlineFooter() { + int16_t tbx, tby; + uint16_t tbw, tbh; + + _display->setFont(&FreeSans9pt7b); + _display->setTextColor(GxEPD_WHITE); + + _display->setPartialWindow(0, _display->height() - mHeadFootPadding, _display->width(), mHeadFootPadding); + _display->fillScreen(GxEPD_BLACK); + do { + if (NULL != mUtcTs) { + snprintf(_fmtText, sizeof(_fmtText), "offline"); + + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + uint16_t x = ((_display->width() - tbw) / 2) - tbx; + + _display->setCursor(x, (_display->height() - 3)); + _display->println(_fmtText); + } + } while (_display->nextPage()); +} +//*************************************************************************** void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod) { int16_t tbx, tby; uint16_t tbw, tbh, x, y; @@ -139,38 +161,44 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl } else { snprintf(_fmtText, sizeof(_fmtText), "offline"); } - _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); - x = ((_display->width() - tbw) / 2) - tbx; - _display->setCursor(x, mHeadFootPadding + tbh + 10); - _display->print(_fmtText); - - _display->setFont(&FreeSans12pt7b); - y = _display->height() / 2; - _display->setCursor(5, y); - _display->print("today:"); - snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay); - _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); - x = ((_display->width() - tbw) / 2) - tbx; - _display->setCursor(x, y); - _display->print(_fmtText); - _display->setCursor(_display->width() - 38, y); - _display->println("Wh"); - - y = y + tbh + 7; - _display->setCursor(5, y); - _display->print("total:"); - snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal); - _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); - x = ((_display->width() - tbw) / 2) - tbx; - _display->setCursor(x, y); - _display->print(_fmtText); - _display->setCursor(_display->width() - 50, y); - _display->println("kWh"); - - _display->setCursor(10, _display->height() - (mHeadFootPadding + 10)); - snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", _isprod); - _display->println(_fmtText); + if (_totalPower == 0){ + _display->fillRect(0, mHeadFootPadding, 200,200, GxEPD_BLACK); + _display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE); + } else { + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + x = ((_display->width() - tbw) / 2) - tbx; + _display->setCursor(x, mHeadFootPadding + tbh + 10); + _display->print(_fmtText); + + _display->setFont(&FreeSans12pt7b); + y = _display->height() / 2; + _display->setCursor(5, y); + _display->print("today:"); + snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay); + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + x = ((_display->width() - tbw) / 2) - tbx; + _display->setCursor(x, y); + _display->print(_fmtText); + _display->setCursor(_display->width() - 38, y); + _display->println("Wh"); + + y = y + tbh + 7; + _display->setCursor(5, y); + _display->print("total:"); + snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal); + _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); + x = ((_display->width() - tbw) / 2) - tbx; + _display->setCursor(x, y); + _display->print(_fmtText); + _display->setCursor(_display->width() - 50, y); + _display->println("kWh"); + + _display->setCursor(10, _display->height() - (mHeadFootPadding + 10)); + snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", _isprod); + _display->println(_fmtText); + + } } while (_display->nextPage()); } //*************************************************************************** @@ -185,10 +213,12 @@ void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYield // call the PowerPage to change the PV Power Values actualPowerPaged(totalPower, totalYieldDay, totalYieldTotal, isprod); - // if there was an change and the Inverter is producing set a new Timestam in the footline + // if there was an change and the Inverter is producing set a new Timestamp in the footline if ((isprod > 0) && (_changed)) { _changed = false; lastUpdatePaged(); + } else if(totalPower==0) { + offlineFooter(); } _display->powerOff(); diff --git a/src/plugins/Display/Display_ePaper.h b/src/plugins/Display/Display_ePaper.h index b2729f25..1e8d34b8 100644 --- a/src/plugins/Display/Display_ePaper.h +++ b/src/plugins/Display/Display_ePaper.h @@ -39,6 +39,7 @@ class DisplayEPaper { void headlineIP(); void actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod); void lastUpdatePaged(); + void offlineFooter(); uint8_t mDisplayRotation; bool _changed = false; From 9acd8c59cd662d329e2aa6431f43e8939eae22b1 Mon Sep 17 00:00:00 2001 From: Betacentauri Date: Sat, 6 May 2023 12:03:52 +0200 Subject: [PATCH 33/43] [rpi] Use tx channel hopping and decrease timeout Using only channel 40 for transmits led in some cases to no responses. --- tools/rpi/hoymiles/__init__.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/tools/rpi/hoymiles/__init__.py b/tools/rpi/hoymiles/__init__.py index 210bed65..688e271d 100644 --- a/tools/rpi/hoymiles/__init__.py +++ b/tools/rpi/hoymiles/__init__.py @@ -297,8 +297,8 @@ class InverterPacketFragment: class HoymilesNRF: """Hoymiles NRF24 Interface""" - tx_channel_id = 0 - tx_channel_list = [40] + tx_channel_id = 2 + tx_channel_list = [3,23,40,61,75] rx_channel_id = 0 rx_channel_list = [3,23,40,61,75] rx_channel_ack = False @@ -332,6 +332,12 @@ class HoymilesNRF: :rtype: bool """ + self.next_tx_channel() + + if HOYMILES_TRANSACTION_LOGGING: + c_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + logging.debug(f'{c_datetime} Transmit {len(packet)} bytes channel {self.tx_channel}: {hexify_payload(packet)}') + if not txpower: txpower = self.txpower @@ -363,13 +369,13 @@ class HoymilesNRF: """ Receive Packets - :param timeout: receive timeout in nanoseconds (default: 12e8) + :param timeout: receive timeout in nanoseconds (default: 5e8) :type timeout: int :yields: fragment """ if not timeout: - timeout=12e8 + timeout=5e8 self.radio.setChannel(self.rx_channel) self.radio.setAutoAck(False) @@ -415,7 +421,7 @@ class HoymilesNRF: self.radio.setChannel(self.rx_channel) self.radio.startListening() - time.sleep(0.005) + time.sleep(0.004) def next_rx_channel(self): """ @@ -433,6 +439,15 @@ class HoymilesNRF: return True return False + def next_tx_channel(self): + """ + Select next channel from hop list + + """ + self.tx_channel_id = self.tx_channel_id + 1 + if self.tx_channel_id >= len(self.tx_channel_list): + self.tx_channel_id = 0 + @property def tx_channel(self): """ @@ -612,10 +627,6 @@ class InverterTransaction: packet = self.tx_queue.pop(0) - if HOYMILES_TRANSACTION_LOGGING: - c_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") - logging.debug(f'{c_datetime} Transmit {len(packet)} | {hexify_payload(packet)}') - self.radio.transmit(packet, txpower=self.txpower) wait = False From 72d044bcf647a3dab8fb43742449f7ad3bcc6c44 Mon Sep 17 00:00:00 2001 From: Betacentauri Date: Sat, 6 May 2023 12:05:21 +0200 Subject: [PATCH 34/43] [rpi] Add config for transmit retries and increase to 5 so that all channels are tried once --- tools/rpi/ahoy.yml.example | 1 + tools/rpi/hoymiles/__main__.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/rpi/ahoy.yml.example b/tools/rpi/ahoy.yml.example index 6efb28ae..00d52511 100644 --- a/tools/rpi/ahoy.yml.example +++ b/tools/rpi/ahoy.yml.example @@ -2,6 +2,7 @@ ahoy: interval: 5 + transmit_retries: 5 logging: filename: 'hoymiles.log' diff --git a/tools/rpi/hoymiles/__main__.py b/tools/rpi/hoymiles/__main__.py index cb5ecf2b..3589de1a 100644 --- a/tools/rpi/hoymiles/__main__.py +++ b/tools/rpi/hoymiles/__main__.py @@ -128,6 +128,7 @@ def main_loop(ahoy_config): dtu_name = ahoy_config.get('dtu', {}).get('name', 'hoymiles-dtu') sunset.sun_status2mqtt(dtu_ser, dtu_name) loop_interval = ahoy_config.get('interval', 1) + transmit_retries = ahoy_config.get('transmit_retries', 5) try: do_init = True @@ -144,7 +145,7 @@ def main_loop(ahoy_config): sys.exit(999) if hoymiles.HOYMILES_DEBUG_LOGGING: logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}') - poll_inverter(inverter, dtu_ser, do_init, 3) + poll_inverter(inverter, dtu_ser, do_init, transmit_retries) do_init = False if loop_interval > 0: From a0955044e5ee38b2997ce33167d35432aa8047fa Mon Sep 17 00:00:00 2001 From: sri-elgora <110235170+sri-elgora@users.noreply.github.com> Date: Thu, 11 May 2023 14:11:10 +0200 Subject: [PATCH 35/43] Update RestApi.h, Bugfix JSON-Param Reset-Command Bugfix JSON-Param Reset-Command --- src/web/RestApi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/RestApi.h b/src/web/RestApi.h index fde8c7a4..812bb640 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -553,7 +553,7 @@ class RestApi { if(F("power") == jsonIn[F("cmd")]) accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); - else if(F("restart") == jsonIn[F("restart")]) + else if(F("restart") == jsonIn[F("cmd")]) accepted = iv->setDevControlRequest(Restart); else if(0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { iv->powerLimit[0] = jsonIn["val"]; From 02c54c85f20a2d465e2c0a4fa403aa46a4fc2ca9 Mon Sep 17 00:00:00 2001 From: movcmpret <43320190+movcmpret@users.noreply.github.com> Date: Sun, 14 May 2023 11:13:11 +0200 Subject: [PATCH 36/43] Update Getting_Started.md Added esptool instructions for linux and esp32 --- Getting_Started.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Getting_Started.md b/Getting_Started.md index 4d35903c..d66aeca0 100644 --- a/Getting_Started.md +++ b/Getting_Started.md @@ -217,6 +217,14 @@ Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities t ! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data! +#### Flashing on Linux with `esptool.py` (ESP32) +1. install [esptool.py](https://docs.espressif.com/projects/esptool/en/latest/esp32/) if you haven't already. +2. download and extract the latest release bin-file from [ahoy_](https://github.com/grindylow/ahoy/releases) +3. `cd ahoy_v && cp *esp32.bin esp32.bin` +4. Perhaps you need to replace `/dev/ttyUSB0` to match your acual device in the following command. Execute it afterwards: `esptool.py --port /dev/ttyUSB0 --chip esp32 --before default_reset --after hard_reset write_flash --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 esp32.bin` +5. Unplug and replug your device. +6. Open a serial monitor (e.g. Putty) @ 115200 Baud. You should see some messages regarding wifi. + ## Connect to your Ahoy DTU When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU. From 4e16129430b77cf6eecfc7fe7d3374e810a433b0 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 16 May 2023 20:50:35 +0200 Subject: [PATCH 37/43] merge PR #934 (fix JSON API) and #944 (update manual) --- src/CHANGES.md | 3 +++ src/defines.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 9f83f7e7..94b1d10a 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.6.13 - 2023-05-16 +* merge PR #934 (fix JSON API) and #944 (update manual) + ## 0.6.12 - 2023-04-28 * improved MqTT * fix menu active item diff --git a/src/defines.h b/src/defines.h index cf9395d5..22f5c8fb 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 12 +#define VERSION_PATCH 13 //------------------------------------- typedef struct { From 2e71401d093e815ece1a64189998ddf2dcd5ce00 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 17 May 2023 22:58:59 +0200 Subject: [PATCH 38/43] another try to compile successfully --- src/platformio.ini | 18 +++++++++--------- src/publisher/pubMqtt.h | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/platformio.ini b/src/platformio.ini index a8fa0d03..904f6120 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -30,7 +30,7 @@ lib_deps = bblanchon/ArduinoJson @ ^6.21.2 https://github.com/JChristensen/Timezone @ ^1.2.4 olikraus/U8g2 @ ^2.34.17 - zinggjm/GxEPD2 @ ^1.5.0 + zinggjm/GxEPD2 @ ^1.5.2 [env:esp8266-release] @@ -98,9 +98,9 @@ monitor_filters = log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory [env:esp32-wroom32-release] -platform = espressif32@>=6.1.0 +platform = espressif32@6.1.0 board = lolin_d32 -build_flags = -D RELEASE -std=gnu++14 +build_flags = -D RELEASE -std=gnu++17 build_unflags = -std=gnu++11 monitor_filters = ;default ; Remove typical terminal control codes from input @@ -109,10 +109,10 @@ monitor_filters = esp32_exception_decoder [env:esp32-wroom32-release-prometheus] -platform = espressif32@>=6.1.0 +platform = espressif32@6.1.0 board = lolin_d32 build_flags = -D RELEASE - -std=gnu++14 + -std=gnu++17 -DENABLE_PROMETHEUS_EP build_unflags = -std=gnu++11 monitor_filters = @@ -122,7 +122,7 @@ monitor_filters = esp32_exception_decoder [env:esp32-wroom32-debug] -platform = espressif32@>=6.1.0 +platform = espressif32@6.1.0 board = lolin_d32 build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE @@ -131,7 +131,7 @@ build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial - -std=gnu++14 + -std=gnu++17 build_unflags = -std=gnu++11 build_type = debug monitor_filters = @@ -140,13 +140,13 @@ monitor_filters = log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory [env:opendtufusionv1-release] -platform = espressif32@>=6.1.0 +platform = espressif32@6.1.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin upload_speed = 115200 debug_tool = esp-builtin debug_speed = 12000 -build_flags = -D RELEASE -std=gnu++14 +build_flags = -D RELEASE -std=gnu++17 build_unflags = -std=gnu++11 monitor_filters = ;default ; Remove typical terminal control codes from input diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 049554d7..1f8519f5 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -564,7 +564,7 @@ class PubMqtt { void sendIvData() { bool anyAvail = processIvStatus(); if (mLastAnyAvail != anyAvail) - mSendList.push(RealTimeRunData_Debug); // makes shure that total values are calculated + mSendList.push(RealTimeRunData_Debug); // makes sure that total values are calculated if(mSendList.empty()) return; From 78b421dcb56e225e0f6b4559aaac1b5479844541 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 21 May 2023 14:50:19 +0200 Subject: [PATCH 39/43] 0.6.14 Update Mono Display #902 --- src/defines.h | 2 +- src/plugins/Display/Display.h | 6 +- src/plugins/Display/Display_Mono.h | 44 +++-- src/plugins/Display/Display_Mono_128X32.cpp | 169 -------------------- src/plugins/Display/Display_Mono_128X32.h | 168 ++++++++++++++++--- src/plugins/Display/Display_Mono_128X64.cpp | 146 ----------------- src/plugins/Display/Display_Mono_128X64.h | 150 ++++++++++++++--- src/plugins/Display/Display_Mono_84X48.cpp | 140 ---------------- src/plugins/Display/Display_Mono_84X48.h | 142 +++++++++++++--- 9 files changed, 428 insertions(+), 539 deletions(-) delete mode 100644 src/plugins/Display/Display_Mono_128X32.cpp delete mode 100644 src/plugins/Display/Display_Mono_128X64.cpp delete mode 100644 src/plugins/Display/Display_Mono_84X48.cpp diff --git a/src/defines.h b/src/defines.h index 93722b6c..5e282f46 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 9 +#define VERSION_PATCH 14 //------------------------------------- typedef struct { diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index e2c0a040..8ce4ed7a 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -58,9 +58,9 @@ class Display { } void tickerSecond() { - if (mMono!=NULL){ + if (mMono != NULL) mMono->loop(); - } + if (mNewPayload || ((++mLoopCnt % 10) == 0)) { mNewPayload = false; mLoopCnt = 0; @@ -97,7 +97,7 @@ class Display { totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); } - if ((0 < mCfg->type) && (mCfg->type < 10) && mMono!=NULL) { + if ((0 < mCfg->type) && (mCfg->type < 10) && (mMono != NULL)) { mMono->disp(totalPower, totalYieldDay, totalYieldTotal, isprod); } else if (mCfg->type >= 10) { #if defined(ESP32) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index 5a825d55..ed9154af 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -1,19 +1,45 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#pragma once +//----------------------------------------------------------------------------- +// 2023 Ahoy, https://ahoydtu.de +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- +#pragma once #include #define DISP_DEFAULT_TIMEOUT 60 // in seconds #define DISP_FMT_TEXT_LEN 32 #define BOTTOM_MARGIN 5 + +#ifdef ESP8266 +#include +#elif defined(ESP32) +#include +#endif +#include "../../utils/helper.h" + class DisplayMono { public: - DisplayMono(){}; + DisplayMono() {}; + + virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version) = 0; + virtual void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) = 0; + virtual void loop(void) = 0; + virtual void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) = 0; + + protected: + U8G2* mDisplay; + + uint8_t mType; + bool mEnPowerSafe, mEnScreenSaver; + uint8_t mLuminance; + + uint8_t mLoopCnt; + uint32_t* mUtcTs; + uint8_t mLineXOffsets[5]; + uint8_t mLineYOffsets[5]; - virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version) = 0; - virtual void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) = 0; - virtual void loop(void) = 0; - virtual void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) = 0; + uint16_t mDispY; - private: -}; + uint8_t mExtra; + uint16_t mTimeout; + char mFmtText[DISP_FMT_TEXT_LEN];}; diff --git a/src/plugins/Display/Display_Mono_128X32.cpp b/src/plugins/Display/Display_Mono_128X32.cpp deleted file mode 100644 index 46a012ec..00000000 --- a/src/plugins/Display/Display_Mono_128X32.cpp +++ /dev/null @@ -1,169 +0,0 @@ - -// SPDX-License-Identifier: GPL-2.0-or-later -#include "Display_Mono_128X32.h" - -#include "Display_Mono.h" - -#ifdef ESP8266 -#include -#elif defined(ESP32) -#include -#endif -#include "../../utils/helper.h" - -// #ifdef U8X8_HAVE_HW_SPI -// #include -// #endif -// #ifdef U8X8_HAVE_HW_I2C -// #include -// #endif - -DisplayMono128X32::DisplayMono128X32() : DisplayMono::DisplayMono() { - mEnPowerSafe = true; - mEnScreenSaver = true; - mLuminance = 60; - _dispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) - mUtcTs = NULL; - mType = 0; -} - -void DisplayMono128X32::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { - if ((0 < type) && (type < 5)) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); - mType = type; - mDisplay = new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, reset, clock, data); - - mUtcTs = utcTs; - - mDisplay->begin(); - - calcLinePositions(); - - mDisplay->clearBuffer(); - mDisplay->setContrast(mLuminance); - printText("AHOY!", 0); - printText("ahoydtu.de", 2); - printText(version, 3); - mDisplay->sendBuffer(); - } -} - -void DisplayMono128X32::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { - mEnPowerSafe = enPowerSafe; - mEnScreenSaver = enScreenSaver; - mLuminance = lum; -} - -void DisplayMono128X32::loop(void) { - if (mEnPowerSafe) - if (mTimeout != 0) - mTimeout--; -} - -void DisplayMono128X32::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { - mDisplay->clearBuffer(); - - // set Contrast of the Display to raise the lifetime - if (3 != mType) - mDisplay->setContrast(mLuminance); - - if ((totalPower > 0) && (isprod > 0)) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); - if (totalPower > 999) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); - } else { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); - } - printText(_fmtText, 0); - } else { - printText("offline", 0); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSafe); - } - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); - printText(_fmtText, 1); - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); - printText(_fmtText, 2); - - IPAddress ip = WiFi.localIP(); - if (!(_mExtra % 10) && (ip)) { - printText(ip.toString().c_str(), 3); - } else if (!(_mExtra % 5)) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); - printText(_fmtText, 3); - } else { - if (NULL != mUtcTs) { - printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - } - } - - mDisplay->sendBuffer(); - - _dispY = 0; - _mExtra++; -} - -void DisplayMono128X32::calcLinePositions() { - uint8_t yOff[] = {0, 0}; - for (uint8_t i = 0; i < 4; i++) { - setFont(i); - yOff[getColumn(i)] += (mDisplay->getMaxCharHeight()); - mLineYOffsets[i] = yOff[getColumn(i)]; - if (isTwoRowLine(i)) { - yOff[getColumn(i)] += mDisplay->getMaxCharHeight(); - } - yOff[getColumn(i)] += BOTTOM_MARGIN; - mLineXOffsets[i] = (getColumn(i) == 1 ? 80 : 0); - } -} - -inline void DisplayMono128X32::setFont(uint8_t line) { - switch (line) { - case 0: - - mDisplay->setFont(u8g2_font_9x15_tf); - break; - case 3: - mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); - break; - default: - mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); - break; - } -} - -inline uint8_t DisplayMono128X32::getColumn(uint8_t line) { - if (line >= 1 && line <= 2) { - return 1; - } else { - return 0; - } -} - -inline bool DisplayMono128X32::isTwoRowLine(uint8_t line) { - if (line >= 1 && line <= 2) { - return true; - } else { - return false; - } -} -void DisplayMono128X32::printText(const char *text, uint8_t line) { - setFont(line); - - uint8_t dispX = mLineXOffsets[line] + ((mEnScreenSaver) ? (_mExtra % 7) : 0); - - if (isTwoRowLine(line)) { - String stringText = String(text); - int space = stringText.indexOf(" "); - mDisplay->drawStr(dispX, mLineYOffsets[line], stringText.substring(0, space).c_str()); - if (space > 0) - mDisplay->drawStr(dispX, mLineYOffsets[line] + mDisplay->getMaxCharHeight(), stringText.substring(space + 1).c_str()); - } else { - mDisplay->drawStr(dispX, mLineYOffsets[line], text); - } -} diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index 00dbf904..9d5ade7e 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -1,37 +1,155 @@ +//----------------------------------------------------------------------------- +// 2023 Ahoy, https://ahoydtu.de +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + #pragma once -// SPDX-License-Identifier: GPL-2.0-or-later #include "Display_Mono.h" class DisplayMono128X32 : public DisplayMono { - public: - DisplayMono128X32(); + public: + DisplayMono128X32() : DisplayMono() { + mEnPowerSafe = true; + mEnScreenSaver = true; + mLuminance = 60; + mExtra = 0; + mDispY = 0; + mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mUtcTs = NULL; + mType = 0; + } + + + void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { + if((0 == type) || (type > 4)) + return; + + u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); + mType = type; + mDisplay = new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, reset, clock, data); + + mUtcTs = utcTs; + + mDisplay->begin(); + + calcLinePositions(); + + mDisplay->clearBuffer(); + mDisplay->setContrast(mLuminance); + printText("AHOY!", 0); + printText("ahoydtu.de", 2); + printText(version, 3); + mDisplay->sendBuffer(); + } + + void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { + mEnPowerSafe = enPowerSafe; + mEnScreenSaver = enScreenSaver; + mLuminance = lum; + } + + void loop(void) { + if (mEnPowerSafe) { + if (mTimeout != 0) + mTimeout--; + } + } + + void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + mDisplay->clearBuffer(); + + // set Contrast of the Display to raise the lifetime + if (3 != mType) + mDisplay->setContrast(mLuminance); + + if ((totalPower > 0) && (isprod > 0)) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + if (totalPower > 999) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); + + printText(mFmtText, 0); + } else { + printText("offline", 0); + // check if it's time to enter power saving mode + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); + } + + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); + printText(mFmtText, 1); + + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); + printText(mFmtText, 2); + + IPAddress ip = WiFi.localIP(); + if (!(mExtra % 10) && (ip)) + printText(ip.toString().c_str(), 3); + else if (!(mExtra % 5)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); + printText(mFmtText, 3); + } else if (NULL != mUtcTs) + printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); + + mDisplay->sendBuffer(); + + mDispY = 0; + mExtra++; + } - void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version); - void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); - void loop(void); - void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + private: + void calcLinePositions() { + uint8_t yOff[] = {0, 0}; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff[getColumn(i)] += (mDisplay->getMaxCharHeight()); + mLineYOffsets[i] = yOff[getColumn(i)]; + if (isTwoRowLine(i)) + yOff[getColumn(i)] += mDisplay->getMaxCharHeight(); + yOff[getColumn(i)] += BOTTOM_MARGIN; + mLineXOffsets[i] = (getColumn(i) == 1 ? 80 : 0); + } + } - private: - void calcLinePositions(); - void setFont(uint8_t line); - uint8_t getColumn(uint8_t line); - bool isTwoRowLine(uint8_t line); - void printText(const char* text, uint8_t line); + inline void setFont(uint8_t line) { + switch (line) { + case 0: + mDisplay->setFont(u8g2_font_9x15_tf); + break; + case 3: + mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); + break; + default: + mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf); + break; + } + } - U8G2* mDisplay; + inline uint8_t getColumn(uint8_t line) { + if (line >= 1 && line <= 2) + return 1; + else + return 0; + } - uint8_t mType; - bool mEnPowerSafe, mEnScreenSaver; - uint8_t mLuminance; + inline bool isTwoRowLine(uint8_t line) { + return ((line >= 1) && (line <= 2)); + } - uint8_t mLoopCnt; - uint32_t* mUtcTs; - uint8_t mLineXOffsets[5]; - uint8_t mLineYOffsets[5]; + void printText(const char *text, uint8_t line) { + setFont(line); - uint16_t _dispY; + uint8_t dispX = mLineXOffsets[line] + ((mEnScreenSaver) ? (mExtra % 7) : 0); - uint8_t _mExtra; - uint16_t mTimeout; - char _fmtText[DISP_FMT_TEXT_LEN]; + if (isTwoRowLine(line)) { + String stringText = String(text); + int space = stringText.indexOf(" "); + mDisplay->drawStr(dispX, mLineYOffsets[line], stringText.substring(0, space).c_str()); + if (space > 0) + mDisplay->drawStr(dispX, mLineYOffsets[line] + mDisplay->getMaxCharHeight(), stringText.substring(space + 1).c_str()); + } else + mDisplay->drawStr(dispX, mLineYOffsets[line], text); + } }; diff --git a/src/plugins/Display/Display_Mono_128X64.cpp b/src/plugins/Display/Display_Mono_128X64.cpp deleted file mode 100644 index 0224f50a..00000000 --- a/src/plugins/Display/Display_Mono_128X64.cpp +++ /dev/null @@ -1,146 +0,0 @@ - -// SPDX-License-Identifier: GPL-2.0-or-later -#include "Display_Mono_128X64.h" - -#include "Display_Mono.h" - -#ifdef ESP8266 -#include -#elif defined(ESP32) -#include -#endif -#include "../../utils/helper.h" - -// #ifdef U8X8_HAVE_HW_SPI -// #include -// #endif -// #ifdef U8X8_HAVE_HW_I2C -// #include -// #endif - -DisplayMono128X64::DisplayMono128X64() : DisplayMono::DisplayMono() { - mEnPowerSafe = true; - mEnScreenSaver = true; - mLuminance = 60; - _dispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) - mUtcTs = NULL; - mType = 0; -} - -void DisplayMono128X64::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { - if ((0 < type) && (type < 5)) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); - mType = type; - switch (type) { - case 1: - mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); - break; - default: - case 2: - mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); - break; - } - - mUtcTs = utcTs; - - mDisplay->begin(); - - calcLineHeights(); - - mDisplay->clearBuffer(); - mDisplay->setContrast(mLuminance); - printText("AHOY!", 0, 35); - printText("ahoydtu.de", 2, 20); - printText(version, 3, 46); - mDisplay->sendBuffer(); - } -} - -void DisplayMono128X64::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { - mEnPowerSafe = enPowerSafe; - mEnScreenSaver = enScreenSaver; - mLuminance = lum; -} - -void DisplayMono128X64::loop(void) { - if (mEnPowerSafe) - if (mTimeout != 0) - mTimeout--; -} - -void DisplayMono128X64::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { - mDisplay->clearBuffer(); - - // set Contrast of the Display to raise the lifetime - if (3 != mType) - mDisplay->setContrast(mLuminance); - - if ((totalPower > 0) && (isprod > 0)) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); - if (totalPower > 999) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); - } else { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); - } - printText(_fmtText, 0); - } else { - printText("offline", 0, 25); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSafe); - } - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); - printText(_fmtText, 1); - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); - printText(_fmtText, 2); - - IPAddress ip = WiFi.localIP(); - if (!(_mExtra % 10) && (ip)) { - printText(ip.toString().c_str(), 3); - } else if (!(_mExtra % 5)) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); - printText(_fmtText, 3); - } else { - if (NULL != mUtcTs) { - printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - } - } - - mDisplay->sendBuffer(); - - _dispY = 0; - _mExtra++; -} - -void DisplayMono128X64::calcLineHeights() { - uint8_t yOff = 0; - for (uint8_t i = 0; i < 4; i++) { - setFont(i); - yOff += (mDisplay->getMaxCharHeight()); - mLineOffsets[i] = yOff; - } -} - -inline void DisplayMono128X64::setFont(uint8_t line) { - switch (line) { - case 0: - mDisplay->setFont(u8g2_font_ncenB14_tr); - break; - case 3: - mDisplay->setFont(u8g2_font_5x8_tr); - break; - default: - mDisplay->setFont(u8g2_font_ncenB10_tr); - break; - } -} -void DisplayMono128X64::printText(const char *text, uint8_t line, uint8_t dispX) { - setFont(line); - - dispX += (mEnScreenSaver) ? (_mExtra % 7) : 0; - mDisplay->drawStr(dispX, mLineOffsets[line], text); -} diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 4902d14b..3d4f91ee 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -1,36 +1,138 @@ +//----------------------------------------------------------------------------- +// 2023 Ahoy, https://ahoydtu.de +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + #pragma once -// SPDX-License-Identifier: GPL-2.0-or-later #include "Display_Mono.h" class DisplayMono128X64 : public DisplayMono { - public: - DisplayMono128X64(); + public: + DisplayMono128X64() : DisplayMono() { + mEnPowerSafe = true; + mEnScreenSaver = true; + mLuminance = 60; + mDispY = 0; + mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mUtcTs = NULL; + mType = 0; + } + + void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { + if((0 == type) || (type > 4)) + return; + + u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); + mType = type; + + switch (type) { + case 1: + mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); + break; + default: + case 2: + mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data); + break; + } + + mUtcTs = utcTs; + + mDisplay->begin(); + calcLinePositions(); + + mDisplay->clearBuffer(); + mDisplay->setContrast(mLuminance); + printText("AHOY!", 0, 35); + printText("ahoydtu.de", 2, 20); + printText(version, 3, 46); + mDisplay->sendBuffer(); + } + + void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { + mEnPowerSafe = enPowerSafe; + mEnScreenSaver = enScreenSaver; + mLuminance = lum; + } + + void loop(void) { + if (mEnPowerSafe) { + if (mTimeout != 0) + mTimeout--; + } + } + + void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + mDisplay->clearBuffer(); + + // set Contrast of the Display to raise the lifetime + if (3 != mType) + mDisplay->setContrast(mLuminance); + + if ((totalPower > 0) && (isprod > 0)) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + + if (totalPower > 999) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); + + printText(mFmtText, 0); + } else { + printText("offline", 0, 25); + // check if it's time to enter power saving mode + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); + } + + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); + printText(mFmtText, 1); - void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version); - void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); - void loop(void); - void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); + printText(mFmtText, 2); - private: - void calcLineHeights(); - void setFont(uint8_t line); - uint8_t getColumn(uint8_t line); - bool isTwoRowLine(uint8_t line); - void printText(const char* text, uint8_t line, uint8_t dispX = 5); + IPAddress ip = WiFi.localIP(); + if (!(mExtra % 10) && (ip)) + printText(ip.toString().c_str(), 3); + else if (!(mExtra % 5)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); + printText(mFmtText, 3); + } else if (NULL != mUtcTs) + printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - U8G2* mDisplay; + mDisplay->sendBuffer(); - uint8_t mType; - bool mEnPowerSafe, mEnScreenSaver; - uint8_t mLuminance; + mDispY = 0; + mExtra++; + } - uint8_t mLoopCnt; - uint32_t* mUtcTs; - uint8_t mLineOffsets[5]; + private: + void calcLinePositions() { + uint8_t yOff = 0; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff += (mDisplay->getMaxCharHeight()); + mLineYOffsets[i] = yOff; + } + } - uint16_t _dispY; + inline void setFont(uint8_t line) { + switch (line) { + case 0: + mDisplay->setFont(u8g2_font_ncenB14_tr); + break; + case 3: + mDisplay->setFont(u8g2_font_5x8_tr); + break; + default: + mDisplay->setFont(u8g2_font_ncenB10_tr); + break; + } + } + void printText(const char *text, uint8_t line, uint8_t dispX = 5) { + setFont(line); - uint8_t _mExtra; - uint16_t mTimeout; - char _fmtText[DISP_FMT_TEXT_LEN]; + dispX += (mEnScreenSaver) ? (mExtra % 7) : 0; + mDisplay->drawStr(dispX, mLineYOffsets[line], text); + } }; diff --git a/src/plugins/Display/Display_Mono_84X48.cpp b/src/plugins/Display/Display_Mono_84X48.cpp deleted file mode 100644 index a84f6037..00000000 --- a/src/plugins/Display/Display_Mono_84X48.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include "Display_Mono_84X48.h" - -#include "Display_Mono.h" - -#ifdef ESP8266 -#include -#elif defined(ESP32) -#include -#endif -#include "../../utils/helper.h" - -// #ifdef U8X8_HAVE_HW_SPI -// #include -// #endif -// #ifdef U8X8_HAVE_HW_I2C -// #include -// #endif - -DisplayMono84X48::DisplayMono84X48() : DisplayMono::DisplayMono() { - mEnPowerSafe = true; - mEnScreenSaver = true; - mLuminance = 60; - _dispY = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) - mUtcTs = NULL; - mType = 0; -} - -void DisplayMono84X48::init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { - if ((0 < type) && (type < 5)) { - u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); - mType = type; - mDisplay = new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset); - - mUtcTs = utcTs; - - mDisplay->begin(); - - calcLineHeights(); - - mDisplay->clearBuffer(); - if (3 != mType) - mDisplay->setContrast(mLuminance); - printText("AHOY!", 0); - printText("ahoydtu.de", 2); - printText(version, 3); - mDisplay->sendBuffer(); - } -} - -void DisplayMono84X48::config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { - mEnPowerSafe = enPowerSafe; - mEnScreenSaver = enScreenSaver; - mLuminance = lum; -} - -void DisplayMono84X48::loop(void) { - if (mEnPowerSafe) - if (mTimeout != 0) - mTimeout--; -} - -void DisplayMono84X48::disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { - mDisplay->clearBuffer(); - - // set Contrast of the Display to raise the lifetime - if (3 != mType) - mDisplay->setContrast(mLuminance); - - if ((totalPower > 0) && (isprod > 0)) { - mTimeout = DISP_DEFAULT_TIMEOUT; - mDisplay->setPowerSave(false); - if (totalPower > 999) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); - } else { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); - } - printText(_fmtText, 0); - } else { - printText("offline", 0); - // check if it's time to enter power saving mode - if (mTimeout == 0) - mDisplay->setPowerSave(mEnPowerSafe); - } - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); - printText(_fmtText, 1); - - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); - printText(_fmtText, 2); - - IPAddress ip = WiFi.localIP(); - if (!(_mExtra % 10) && (ip)) { - printText(ip.toString().c_str(), 3); - } else if (!(_mExtra % 5)) { - snprintf(_fmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); - printText(_fmtText, 3); - } else { - if (NULL != mUtcTs) { - printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - } - } - - mDisplay->sendBuffer(); - - _dispY = 0; - _mExtra++; -} - -void DisplayMono84X48::calcLineHeights() { - uint8_t yOff = 0; - for (uint8_t i = 0; i < 4; i++) { - setFont(i); - yOff += (mDisplay->getMaxCharHeight()); - mLineOffsets[i] = yOff; - } -} - -inline void DisplayMono84X48::setFont(uint8_t line) { - switch (line) { - case 0: - mDisplay->setFont(u8g2_font_logisoso16_tr); - break; - case 3: - mDisplay->setFont(u8g2_font_5x8_tr); - break; - default: - mDisplay->setFont(u8g2_font_5x8_tr); - break; - } -} - -void DisplayMono84X48::printText(const char *text, uint8_t line) { - uint8_t dispX = (line == 0) ? 10 : 5; - setFont(line); - - dispX += (mEnScreenSaver) ? (_mExtra % 7) : 0; - mDisplay->drawStr(dispX, mLineOffsets[line], text); -} diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index 1ad140e9..82aa83fa 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -1,34 +1,132 @@ +//----------------------------------------------------------------------------- +// 2023 Ahoy, https://ahoydtu.de +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + #pragma once -// SPDX-License-Identifier: GPL-2.0-or-later #include "Display_Mono.h" class DisplayMono84X48 : public DisplayMono { - public: - DisplayMono84X48(); + public: + DisplayMono84X48() : DisplayMono() { + mEnPowerSafe = true; + mEnScreenSaver = true; + mLuminance = 60; + mExtra = 0; + mDispY = 0; + mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds) + mUtcTs = NULL; + mType = 0; + } + + void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t *utcTs, const char *version) { + if((0 == type) || (type > 4)) + return; + + u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0); + mType = type; + mDisplay = new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset); + + mUtcTs = utcTs; + + mDisplay->begin(); + calcLinePositions(); + + mDisplay->clearBuffer(); + if (3 != mType) + mDisplay->setContrast(mLuminance); + printText("AHOY!", 0); + printText("ahoydtu.de", 2); + printText(version, 3); + mDisplay->sendBuffer(); + } + + void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) { + mEnPowerSafe = enPowerSafe; + mEnScreenSaver = enScreenSaver; + mLuminance = lum; + } + + void loop(void) { + if (mEnPowerSafe) { + if (mTimeout != 0) + mTimeout--; + } + } + + void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { + mDisplay->clearBuffer(); + + // set Contrast of the Display to raise the lifetime + if (3 != mType) + mDisplay->setContrast(mLuminance); + + if ((totalPower > 0) && (isprod > 0)) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + + if (totalPower > 999) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower); + + printText(mFmtText, 0); + } else { + printText("offline", 0); + // check if it's time to enter power saving mode + if (mTimeout == 0) + mDisplay->setPowerSave(mEnPowerSafe); + } + + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay); + printText(mFmtText, 1); + + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal); + printText(mFmtText, 2); - void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version); - void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum); - void loop(void); - void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + IPAddress ip = WiFi.localIP(); + if (!(mExtra % 10) && (ip)) + printText(ip.toString().c_str(), 3); + else if (!(mExtra % 5)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", isprod); + printText(mFmtText, 3); + } else if (NULL != mUtcTs) + printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3); - private: - void calcLineHeights(); - void setFont(uint8_t line); - void printText(const char* text, uint8_t line); + mDisplay->sendBuffer(); - U8G2* mDisplay; + mExtra = 1; + } - uint8_t mType; - bool mEnPowerSafe, mEnScreenSaver; - uint8_t mLuminance; + private: + void calcLinePositions() { + uint8_t yOff = 0; + for (uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff += (mDisplay->getMaxCharHeight()); + mLineYOffsets[i] = yOff; + } + } - uint8_t mLoopCnt; - uint32_t* mUtcTs; - uint8_t mLineOffsets[5]; + inline void setFont(uint8_t line) { + switch (line) { + case 0: + mDisplay->setFont(u8g2_font_logisoso16_tr); + break; + case 3: + mDisplay->setFont(u8g2_font_5x8_tr); + break; + default: + mDisplay->setFont(u8g2_font_5x8_tr); + break; + } + } - uint16_t _dispY; + void printText(const char *text, uint8_t line) { + uint8_t dispX = (line == 0) ? 10 : 5; + setFont(line); - uint8_t _mExtra; - uint16_t mTimeout; - char _fmtText[DISP_FMT_TEXT_LEN]; + dispX += (mEnScreenSaver) ? (mExtra % 7) : 0; + mDisplay->drawStr(dispX, mLineYOffsets[line], text); + } }; From e6d8e2ee978d14736580c9a2cddde0b3bd5268ab Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 21 May 2023 14:57:17 +0200 Subject: [PATCH 40/43] 0.6.14 merge PR #920 --- src/plugins/Display/Display_ePaper.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp index 7a000c2d..eccf118c 100644 --- a/src/plugins/Display/Display_ePaper.cpp +++ b/src/plugins/Display/Display_ePaper.cpp @@ -197,7 +197,6 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", _isprod); _display->println(_fmtText); - } } while (_display->nextPage()); } @@ -217,9 +216,8 @@ void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYield if ((isprod > 0) && (_changed)) { _changed = false; lastUpdatePaged(); - } else if(totalPower==0) { + } else if(totalPower==0) offlineFooter(); - } _display->powerOff(); } From 5ae1f4b86fcc2e595f1347bbfee8874d9ace3a4e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 24 May 2023 11:09:33 +0200 Subject: [PATCH 41/43] Declare metric type only once. Add grouping of metrics. Send realtime data for each inverter in a separate chunk. --- src/web/web.h | 222 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 147 insertions(+), 75 deletions(-) diff --git a/src/web/web.h b/src/web/web.h index 056b9e7c..d8804b54 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -627,14 +627,22 @@ class Web { #ifdef ENABLE_PROMETHEUS_EP + // Note + // Prometheus exposition format is defined here: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md + // TODO: Check packetsize for MAX_NUM_INVERTERS. Successfull Tested with 4 Inverters (each with 4 channels) enum { - metricsStateStart, metricsStateInverter, metricStateRealtimeData,metricsStateAlarmData,metricsStateEnd + metricsStateStart, + metricsStateInverter1, metricsStateInverter2, metricsStateInverter3, metricsStateInverter4, + metricStateRealtimeFieldId, metricStateRealtimeInverterId, + metricsStateAlarmData, + metricsStateEnd } metricsStep; - int metricsInverterId,metricsChannelId; + int metricsInverterId; + uint8_t metricsFieldId; + bool metricDeclared; void showMetrics(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); - metricsStep = metricsStateStart; AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"), [this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t @@ -647,7 +655,11 @@ class Web { char type[60], topic[100], val[25]; size_t len = 0; int alarmChannelId; + int metricsChannelId; + // Perform grouping on metrics according to format specification + // Each step must return at least one character. Otherwise the processing of AsyncWebServerResponse stops. + // So several "Info:" blocks are used to keep the transmission going switch (metricsStep) { case metricsStateStart: // System Info & NRF Statistics : fit to one packet snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n"); @@ -676,93 +688,138 @@ class Web { metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - // Start Inverter loop + // Next is Inverter information metricsInverterId = 0; - metricsStep = metricsStateInverter; + metricsStep = metricsStateInverter1; break; - case metricsStateInverter: // Inverter loop - if (metricsInverterId < mSys->getNumInverters()) { - iv = mSys->getInverterByPos(metricsInverterId); - if(NULL != iv) { - // Inverter info : fit to one packet - snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_info gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", - iv->config->name, iv->config->serial.u64); - metrics = String(type) + String(topic); - - snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_enabled gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",iv->config->name,iv->config->enabled); - metrics += String(type) + String(topic); - - snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_available gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",iv->config->name,iv->isAvailable(mApp->getTimestamp())); - metrics += String(type) + String(topic); - - snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_producing gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",iv->config->name,iv->isProducing(mApp->getTimestamp())); - metrics += String(type) + String(topic); - - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - - // Start Realtime Data Channel loop for this inverter - metricsChannelId = 0; - metricsStep = metricStateRealtimeData; - } + case metricsStateInverter1: // Information about all inverters configured : fit to one packet + metrics = "# TYPE ahoy_solar_inverter_info gauge\n"; + metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", + [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->serial.u64;}); + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + metricsStep = metricsStateInverter2; + break; + + case metricsStateInverter2: // Information about all inverters configured : fit to one packet + metrics += "# TYPE ahoy_solar_inverter_is_enabled gauge\n"; + metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n", + [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->enabled;}); + + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + metricsStep = metricsStateInverter3; + break; + + case metricsStateInverter3: // Information about all inverters configured : fit to one packet + metrics += "# TYPE ahoy_solar_inverter_is_available gauge\n"; + metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n", + [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable(mApp->getTimestamp());}); + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + metricsStep = metricsStateInverter4; + break; + + case metricsStateInverter4: // Information about all inverters configured : fit to one packet + metrics += "# TYPE ahoy_solar_inverter_is_producing gauge\n"; + metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n", + [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing(mApp->getTimestamp());}); + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + // Start Realtime Field loop + metricsFieldId = FLD_UDC; + metricsStep = metricStateRealtimeFieldId; + break; + + case metricStateRealtimeFieldId: // Iterate over all defined fields + if (metricsFieldId < FLD_LAST_ALARM_CODE) { + metrics = "# Info: processing realtime field #"+String(metricsFieldId)+"\n"; + metricDeclared = false; + + metricsInverterId = 0; + metricsStep = metricStateRealtimeInverterId; } else { - metricsStep = metricsStateEnd; + metrics = "# Info: all realtime fields processed\n"; + metricsStep = metricsStateAlarmData; } + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); break; - case metricStateRealtimeData: // Realtime Data Channel loop - iv = mSys->getInverterByPos(metricsInverterId); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (metricsChannelId < rec->length) { - uint8_t channel = rec->assign[metricsChannelId].ch; - // Skip entry if maxPwr is 0 and it's not the inverter channel (channel 0) - if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) { - std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); - if (0 == channel) { - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); - } else { - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]); + case metricStateRealtimeInverterId: // Iterate over all inverters for this field + metrics = ""; + if (metricsInverterId < mSys->getNumInverters()) { + // process all channels of this inverter + + iv = mSys->getInverterByPos(metricsInverterId); + if (NULL != iv) { + rec = iv->getRecordStruct(RealTimeRunData_Debug); + for (metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) { + uint8_t channel = rec->assign[metricsChannelId].ch; + + // Try inverter channel (channel 0) or any channel with maxPwr > 0 + if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) { + + if (metricsFieldId == iv->getByteAssign(metricsChannelId, rec)->fieldId) { + // This is the correct field to report + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); + // Declare metric only once + if (!metricDeclared) { + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s\n", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); + metrics += type; + metricDeclared = true; + } + // report value + if (0 == channel) { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); + } else { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]); + } + snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec)); + metrics += topic; + metrics += val; + } + } + } + if (metrics.length() < 1) { + metrics = "# Info: Field #"+String(metricsFieldId)+" not available for inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; + metricsFieldId++; // Process next field Id + metricsStep = metricStateRealtimeFieldId; } - snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec)); - len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); } else { - len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. + metrics = "# Info: No data for field #"+String(metricsFieldId)+" of inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; + metricsFieldId++; // Process next field Id + metricsStep = metricStateRealtimeFieldId; } - - metricsChannelId++; + // Stay in this state and try next inverter + metricsInverterId++; } else { - len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. - - // All realtime data channels processed --> try alarm data - metricsStep = metricsStateAlarmData; + metrics = "# Info: All inverters for field #"+String(metricsFieldId)+" processed.\n"; + metricsFieldId++; // Process next field Id + metricsStep = metricStateRealtimeFieldId; } + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); break; - case metricsStateAlarmData: // Alarm Info loop - iv = mSys->getInverterByPos(metricsInverterId); - rec = iv->getRecordStruct(AlarmData); - // simple hack : there is only one channel with alarm data - // TODO: find the right one channel with the alarm id - alarmChannelId = 0; - // printf("AlarmData Length %d\n",rec->length); - if (alarmChannelId < rec->length) { - //uint8_t channel = rec->assign[alarmChannelId].ch; - std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str()); - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); - snprintf(val, sizeof(val), "%.3f", iv->getValue(alarmChannelId, rec)); - len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); - } else { - len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. + case metricsStateAlarmData: // Alarm Info loop : fit to one packet + // Perform grouping on metrics according to Prometheus exposition format specification + snprintf(type, sizeof(type),"# TYPE ahoy_solar_%s gauge\n",fields[FLD_LAST_ALARM_CODE]); + metrics = type; + + for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { + iv = mSys->getInverterByPos(metricsInverterId); + if (NULL != iv) { + rec = iv->getRecordStruct(AlarmData); + // simple hack : there is only one channel with alarm data + // TODO: find the right one channel with the alarm id + alarmChannelId = 0; + if (alarmChannelId < rec->length) { + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec)); + metrics += topic; + metrics += val; + } + } } - // alarm channel processed --> try next inverter - metricsInverterId++; - metricsStep = metricsStateInverter; + len = snprintf((char*)buffer,maxLen,"%s",metrics.c_str()); + metricsStep = metricsStateEnd; break; case metricsStateEnd: @@ -775,6 +832,21 @@ class Web { request->send(response); } + + // Traverse all inverters and collect the metric via valueFunc + String inverterMetric(char *buffer, size_t len, const char *format, std::function *iv, IApp *mApp)> valueFunc) { + Inverter<> *iv; + String metric = ""; + for (int metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { + iv = mSys->getInverterByPos(metricsInverterId); + if (NULL != iv) { + snprintf(buffer,len,format,iv->config->name, valueFunc(iv,mApp)); + metric += String(buffer); + } + } + return metric; + } + String radioStatistic(String statistic, uint32_t value) { char type[60], topic[80], val[25]; snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s counter",statistic.c_str()); From 5b82d1aa8b69a5f140a1b4684cdc08045b908475 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 25 May 2023 21:41:56 +0200 Subject: [PATCH 42/43] 0.6.15 * improved Prometheus Endpoint PR #958 * fix turn off ePaper only if setting was set #956 --- src/CHANGES.md | 4 ++++ src/defines.h | 2 +- src/plugins/Display/Display.h | 2 +- src/plugins/Display/Display_ePaper.cpp | 23 ++++++++++++----------- src/plugins/Display/Display_ePaper.h | 3 ++- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f3e60193..f789c234 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.6.15 - 2023-05-25 +* improved Prometheus Endpoint PR #958 +* fix turn off ePaper only if setting was set #956 + ## 0.6.14 - 2023-05-21 * merge PR #902 Mono-Display diff --git a/src/defines.h b/src/defines.h index e82047d0..baaea8b6 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 6 -#define VERSION_PATCH 14 +#define VERSION_PATCH 15 //------------------------------------- typedef struct { diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 8ce4ed7a..ba187c7d 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -47,7 +47,7 @@ class Display { } else if (mCfg->type >= 10) { #if defined(ESP32) mRefreshCycle = 0; - mEpaper.config(mCfg->rot); + mEpaper.config(mCfg->rot, mCfg->pwrSaveAtIvOffline); mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion); #endif } diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp index eccf118c..924961a3 100644 --- a/src/plugins/Display/Display_ePaper.cpp +++ b/src/plugins/Display/Display_ePaper.cpp @@ -57,8 +57,9 @@ void DisplayEPaper::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, u } } -void DisplayEPaper::config(uint8_t rotation) { +void DisplayEPaper::config(uint8_t rotation, bool enPowerSafe) { mDisplayRotation = rotation; + mEnPowerSafe = enPowerSafe; } //*************************************************************************** @@ -142,7 +143,7 @@ void DisplayEPaper::offlineFooter() { } while (_display->nextPage()); } //*************************************************************************** -void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod) { +void DisplayEPaper::actualPowerPaged(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { int16_t tbx, tby; uint16_t tbw, tbh, x, y; @@ -152,16 +153,16 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl _display->setPartialWindow(0, mHeadFootPadding, _display->width(), _display->height() - (mHeadFootPadding * 2)); _display->fillScreen(GxEPD_WHITE); do { - if (_totalPower > 9999) { - snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (_totalPower / 10000)); + if (totalPower > 9999) { + snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (totalPower / 10000)); _changed = true; - } else if ((_totalPower > 0) && (_totalPower <= 9999)) { - snprintf(_fmtText, sizeof(_fmtText), "%.0f W", _totalPower); + } else if ((totalPower > 0) && (totalPower <= 9999)) { + snprintf(_fmtText, sizeof(_fmtText), "%.0f W", totalPower); _changed = true; } else { snprintf(_fmtText, sizeof(_fmtText), "offline"); } - if (_totalPower == 0){ + if (totalPower == 0){ _display->fillRect(0, mHeadFootPadding, 200,200, GxEPD_BLACK); _display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE); } else { @@ -174,7 +175,7 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl y = _display->height() / 2; _display->setCursor(5, y); _display->print("today:"); - snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay); + snprintf(_fmtText, _display->width(), "%.0f", totalYieldDay); _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((_display->width() - tbw) / 2) - tbx; _display->setCursor(x, y); @@ -185,7 +186,7 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl y = y + tbh + 7; _display->setCursor(5, y); _display->print("total:"); - snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal); + snprintf(_fmtText, _display->width(), "%.1f", totalYieldTotal); _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); x = ((_display->width() - tbw) / 2) - tbx; _display->setCursor(x, y); @@ -194,7 +195,7 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl _display->println("kWh"); _display->setCursor(10, _display->height() - (mHeadFootPadding + 10)); - snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", _isprod); + snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", isprod); _display->println(_fmtText); } @@ -216,7 +217,7 @@ void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYield if ((isprod > 0) && (_changed)) { _changed = false; lastUpdatePaged(); - } else if(totalPower==0) + } else if((0 == totalPower) && (mEnPowerSafe)) offlineFooter(); _display->powerOff(); diff --git a/src/plugins/Display/Display_ePaper.h b/src/plugins/Display/Display_ePaper.h index 1e8d34b8..ad422b26 100644 --- a/src/plugins/Display/Display_ePaper.h +++ b/src/plugins/Display/Display_ePaper.h @@ -31,7 +31,7 @@ class DisplayEPaper { DisplayEPaper(); void fullRefresh(); void init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t *utcTs, const char* version); - void config(uint8_t rotation); + void config(uint8_t rotation, bool enPowerSafe); void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); @@ -48,6 +48,7 @@ class DisplayEPaper { uint8_t mHeadFootPadding; GxEPD2_GFX* _display; uint32_t *mUtcTs; + bool mEnPowerSafe; }; #endif // ESP32 From 6dcf5ee9aa9b0fe15fc32a0843656010535d928e Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 25 May 2023 22:26:33 +0200 Subject: [PATCH 43/43] 0.6.15 * improved reset values and update MqTT #957 --- src/CHANGES.md | 1 + src/app.cpp | 21 ++++++++++++++++++--- src/hm/hmPayload.h | 19 ++++++------------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f789c234..6b97e319 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,6 +3,7 @@ ## 0.6.15 - 2023-05-25 * improved Prometheus Endpoint PR #958 * fix turn off ePaper only if setting was set #956 +* improved reset values and update MqTT #957 ## 0.6.14 - 2023-05-21 * merge PR #902 Mono-Display diff --git a/src/app.cpp b/src/app.cpp index c61c81aa..780e16f4 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -274,6 +274,7 @@ void app::tickComm(void) { //----------------------------------------------------------------------------- void app::tickZeroValues(void) { Inverter<> *iv; + bool changed = false; // set values to zero, except yields for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { iv = mSys.getInverterByPos(id); @@ -281,7 +282,11 @@ void app::tickZeroValues(void) { continue; // skip to next inverter mPayload.zeroInverterValues(iv); + changed = true; } + + if(changed) + payloadEventListener(RealTimeRunData_Debug); } //----------------------------------------------------------------------------- @@ -289,15 +294,21 @@ void app::tickMinute(void) { // only triggered if 'reset values on no avail is enabled' Inverter<> *iv; + bool changed = false; // set values to zero, except yields for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { iv = mSys.getInverterByPos(id); if (NULL == iv) continue; // skip to next inverter - if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled) + if (!iv->isAvailable(mTimestamp) && !iv->isProducing(mTimestamp) && iv->config->enabled) { mPayload.zeroInverterValues(iv); + changed = true; + } } + + if(changed) + payloadEventListener(RealTimeRunData_Debug); } //----------------------------------------------------------------------------- @@ -308,16 +319,20 @@ void app::tickMidnight(void) { onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2"); Inverter<> *iv; + bool changed = false; // set values to zero, except yield total for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { iv = mSys.getInverterByPos(id); if (NULL == iv) continue; // skip to next inverter - mPayload.zeroInverterValues(iv); - mPayload.zeroYieldDay(iv); + mPayload.zeroInverterValues(iv, false); + changed = true; } + if(changed) + payloadEventListener(RealTimeRunData_Debug); + if (mMqttEnabled) mMqtt.tickerMidnight(); } diff --git a/src/hm/hmPayload.h b/src/hm/hmPayload.h index 0ec40d22..5c66f1f8 100644 --- a/src/hm/hmPayload.h +++ b/src/hm/hmPayload.h @@ -93,17 +93,7 @@ class HmPayload { notify(0x0b); }*/ - void zeroYieldDay(Inverter<> *iv) { - DPRINTLN(DBG_DEBUG, F("zeroYieldDay")); - record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - uint8_t pos; - for(uint8_t ch = 0; ch <= iv->channels; ch++) { - pos = iv->getPosByChFld(ch, FLD_YD, rec); - iv->setValue(pos, rec, 0.0f); - } - } - - void zeroInverterValues(Inverter<> *iv) { + void zeroInverterValues(Inverter<> *iv, bool skipYieldDay = true) { DPRINTLN(DBG_DEBUG, F("zeroInverterValues")); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); for(uint8_t ch = 0; ch <= iv->channels; ch++) { @@ -111,15 +101,18 @@ class HmPayload { for(uint8_t fld = 0; fld < FLD_EVT; fld++) { switch(fld) { case FLD_YD: + if(skipYieldDay) + continue; + else + break; case FLD_YT: continue; } pos = iv->getPosByChFld(ch, fld, rec); iv->setValue(pos, rec, 0.0f); } + iv->doCalculations(); } - - notify(RealTimeRunData_Debug); } void ivSendHighPrio(Inverter<> *iv) {