diff --git a/scripts/buildManifest.py b/scripts/buildManifest.py index 9a5411d5..db29b352 100644 --- a/scripts/buildManifest.py +++ b/scripts/buildManifest.py @@ -36,13 +36,13 @@ def buildManifest(path, infile, outfile): esp32["parts"].append({"path": "bootloader.bin", "offset": 4096}) esp32["parts"].append({"path": "partitions.bin", "offset": 32768}) esp32["parts"].append({"path": "ota.bin", "offset": 57344}) - esp32["parts"].append({"path": version[1] + "_esp32_" + sha + ".bin", "offset": 65536}) + esp32["parts"].append({"path": version[1] + "_" + sha + "_esp32.bin", "offset": 65536}) data["builds"].append(esp32) esp8266 = {} esp8266["chipFamily"] = "ESP8266" esp8266["parts"] = [] - esp8266["parts"].append({"path": version[1] + "_esp8266_" + sha + ".bin", "offset": 0}) + esp8266["parts"].append({"path": version[1] + "_" + sha + "_esp8266.bin", "offset": 0}) data["builds"].append(esp8266) jsonString = json.dumps(data, indent=2) diff --git a/src/CHANGES.md b/src/CHANGES.md index 1d359129..9405fb15 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,11 @@ (starting from release version `0.5.66`) +## 0.5.77 +* fix wrong filename for automatically created manifest (online installer) #620 +* added rotate display feature #619 +* improved Prometheus endpoint #615, thx to fsck-block + ## 0.5.76 * reduce MQTT retry interval from maximum speed to one second * fixed homeassistant autodiscovery #565 diff --git a/src/config/settings.h b/src/config/settings.h index dbf39d6c..4a922962 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -122,6 +122,7 @@ typedef struct { bool pwrSaveAtIvOffline; bool logoEn; bool pxShift; + bool rot180; uint16_t wakeUp; uint16_t sleepAt; uint8_t contrast; @@ -340,6 +341,7 @@ class settings { mCfg.plugin.display.contrast = 60; mCfg.plugin.display.logoEn = true; mCfg.plugin.display.pxShift = true; + mCfg.plugin.display.rot180 = false; mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA } @@ -471,6 +473,7 @@ class settings { disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; disp[F("logo")] = (bool)mCfg.plugin.display.logoEn; disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; + disp[F("rot180")] = (bool)mCfg.plugin.display.rot180; disp[F("wake")] = mCfg.plugin.display.wakeUp; disp[F("sleep")] = mCfg.plugin.display.sleepAt; disp[F("contrast")] = mCfg.plugin.display.contrast; @@ -482,6 +485,7 @@ class settings { mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")]; mCfg.plugin.display.logoEn = (bool) disp[F("logo")]; mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")]; + mCfg.plugin.display.rot180 = (bool) disp[F("rot180")]; mCfg.plugin.display.wakeUp = disp[F("wake")]; mCfg.plugin.display.sleepAt = disp[F("sleep")]; mCfg.plugin.display.contrast = disp[F("contrast")]; diff --git a/src/defines.h b/src/defines.h index a5f108de..ecb161bc 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 76 +#define VERSION_PATCH 77 //------------------------------------- typedef struct { diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index f4e1a69c..5cfd85f0 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -51,18 +51,20 @@ class MonochromeDisplay { mUtcTs = utcTs; mNewPayload = false; mLoopCnt = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // power off timeout (after inverters go offline) + + u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot180) ? U8G2_R2 : U8G2_R0); + if(mCfg->type) { switch(mCfg->type) { case 1: - mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(U8G2_R0, mCfg->pin0, mCfg->pin1, disp_reset); + mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, mCfg->pin0, mCfg->pin1, disp_reset); break; case 2: - mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, disp_reset, mCfg->pin0, mCfg->pin1); + mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1); break; case 3: - mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, disp_reset, mCfg->pin0, mCfg->pin1); + mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1); break; } mDisplay->begin(); diff --git a/src/web/RestApi.h b/src/web/RestApi.h index c8025464..ec4fd61f 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -359,6 +359,7 @@ class RestApi { obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn; obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift; + obj[F("rot180")] = (bool)mConfig->plugin.display.rot180; obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast; obj[F("pinDisp0")] = mConfig->plugin.display.pin0; obj[F("pinDisp1")] = mConfig->plugin.display.pin1; diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 3bd8ef6e..fe514449 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -192,6 +192,8 @@

+ +
@@ -534,7 +536,7 @@ } function parseDisplay(obj, type) { - for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"]]) + for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"], ["disp180", "rot180"]]) document.getElementsByName(i[0])[0].checked = obj[i[1]]; var e = document.getElementById("dispPins"); diff --git a/src/web/web.h b/src/web/web.h index 4c96f8b9..a661c987 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -565,12 +565,13 @@ class Web { // display mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on"); - mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on"); + mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on"); mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on"); - mConfig->plugin.display.type = request->arg("dispType").toInt(); - mConfig->plugin.display.contrast = request->arg("dispCont").toInt(); - mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt(); - mConfig->plugin.display.pin1 = request->arg("pinDisp1").toInt(); + mConfig->plugin.display.rot180 = (request->arg("disp180") == "on"); + mConfig->plugin.display.type = request->arg("dispType").toInt(); + mConfig->plugin.display.contrast = request->arg("dispCont").toInt(); + mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt(); + mConfig->plugin.display.pin1 = request->arg("pinDisp1").toInt(); mApp->saveSettings(); @@ -735,36 +736,78 @@ class Web { void showMetrics(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); String metrics; - char headline[80]; + char infoline[90]; - snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mConfig->sys.deviceName); - metrics += "# TYPE ahoy_solar_info gauge\n" + String(headline) + "\n"; + // System info + snprintf(infoline, sizeof(infoline), "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mConfig->sys.deviceName); + metrics += "# TYPE ahoy_solar_info gauge\n" + String(infoline) + "\n"; Inverter<> *iv; record_t<> *rec; - char type[60], topic[60], val[25]; + char type[60], topic[80], val[25]; for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { iv = mSys->getInverterByPos(id); if(NULL == iv) continue; + // Inverter info + snprintf(infoline, sizeof(infoline), "ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\",enabled=\"%d\"} 1", + iv->config->name, iv->config->serial.u64,iv->config->enabled); + metrics += "# TYPE ahoy_solar_inverter_info gauge\n" + String(infoline) + "\n"; + // AC rec = iv->getRecordStruct(RealTimeRunData_Debug); for(uint8_t i = 0; i < rec->length; i++) { uint8_t channel = rec->assign[i].ch; if(channel == 0) { String promUnit, promType; std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(i, rec)); - snprintf(type, 60, "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i, rec), promUnit.c_str(), promType.c_str()); - snprintf(topic, 60, "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i, rec), promUnit.c_str(), iv->config->name); - snprintf(val, 25, "%.3f", iv->getValue(i, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i, rec), promUnit.c_str(), promType.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i, rec), promUnit.c_str(), iv->config->name); + snprintf(val, sizeof(val), "%.3f", iv->getValue(i, rec)); + metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; + } + } + // channels DC + for(uint8_t j = 1; j <= iv->channels; j ++) { + uint8_t pos; + for (uint8_t k = 0; k < 6; k++) { + switch(k) { + default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; + case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; + case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; + case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; + case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; + case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; + } + String promUnit, promType; + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(pos, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(pos, rec), promUnit.c_str(), promType.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_%s_%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(pos, rec), promUnit.c_str(), iv->config->name, iv->config->chName[j-1]); + snprintf(val, sizeof(val), "%.3f", iv->getValue(pos, rec)); metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; } } } - AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), metrics); + // NRF Statistics + statistics_t *stat = mApp->getStatistics(); + metrics += radioStatistic(F("rx_success"), stat->rxSuccess); + metrics += radioStatistic(F("rx_fail"), stat->rxFail); + metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); + metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); + metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); + + AsyncWebServerResponse *response = request->beginResponse(200, F("text/plain"), metrics); request->send(response); } + String radioStatistic(String statistic, uint32_t value) { + char type[60], topic[80], val[25]; + snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s gauge",statistic.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_radio_%s",statistic.c_str()); + snprintf(val, sizeof(val), "%d", value); + return ( String(type) + "\n" + String(topic) + " " + String(val) + "\n"); + } + std::pair convertToPromUnits(String shortUnit) { if(shortUnit == "A") return {"ampere", "gauge"}; if(shortUnit == "V") return {"volt", "gauge"};