Browse Source

Merge branch 'development03' into hms

pull/985/head
lumapu 1 year ago
parent
commit
2c1a32f9e8
  1. 8
      src/CHANGES.md
  2. 21
      src/app.cpp
  3. 2
      src/defines.h
  4. 19
      src/hm/hmPayload.h
  5. 18
      src/platformio.ini
  6. 194
      src/plugins/Display/Display.h
  7. 157
      src/plugins/Display/Display_Mono.cpp
  8. 45
      src/plugins/Display/Display_Mono.h
  9. 155
      src/plugins/Display/Display_Mono_128X32.h
  10. 138
      src/plugins/Display/Display_Mono_128X64.h
  11. 132
      src/plugins/Display/Display_Mono_84X48.h
  12. 107
      src/plugins/Display/Display_ePaper.cpp
  13. 4
      src/plugins/Display/Display_ePaper.h
  14. 2
      src/publisher/pubMqtt.h
  15. 4
      src/web/html/setup.html
  16. 213
      src/web/web.h

8
src/CHANGES.md

@ -1,5 +1,13 @@
# Development Changes
## 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
## 0.6.13 - 2023-05-16
* merge PR #934 (fix JSON API) and #944 (update manual)

21
src/app.cpp

@ -322,6 +322,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);
@ -329,7 +330,11 @@ void app::tickZeroValues(void) {
continue; // skip to next inverter
mPayload.zeroInverterValues(iv);
changed = true;
}
if(changed)
payloadEventListener(RealTimeRunData_Debug);
}
//-----------------------------------------------------------------------------
@ -337,15 +342,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);
}
//-----------------------------------------------------------------------------
@ -356,16 +367,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();
}

2
src/defines.h

@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 6
#define VERSION_PATCH 13
#define VERSION_PATCH 15
//-------------------------------------
typedef struct {

19
src/hm/hmPayload.h

@ -95,17 +95,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++) {
@ -113,15 +103,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) {

18
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

194
src/plugins/Display/Display.h

@ -7,108 +7,126 @@
#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 HMSYSTEM>
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, 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
}
}
void payloadEventListener(uint8_t cmd) {
mNewPayload = true;
}
void tickerSecond() {
if (mMono != NULL)
mMono->loop();
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) {
mCfg = cfg;
mSys = sys;
mUtcTs = utcTs;
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 != NULL)) {
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__*/

157
src/plugins/Display/Display_Mono.cpp

@ -1,157 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Display_Mono.h"
#ifdef ESP8266
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include "../../utils/helper.h"
//#ifdef U8X8_HAVE_HW_SPI
//#include <SPI.h>
//#endif
//#ifdef U8X8_HAVE_HW_I2C
//#include <Wire.h>
//#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 < 4)) {
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;
}
mUtcTs = utcTs;
mDisplay->begin();
mIsLarge = (mDisplay->getWidth() > 120);
calcLineHeights();
mDisplay->clearBuffer();
if (3 != mType)
mDisplay->setContrast(mLuminance);
printText("AHOY!", 0, 35);
printText("ahoydtu.de", 2, 20);
printText(version, 3, 46);
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, 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(mIsLarge && (NULL != mUtcTs))
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::calcLineHeights() {
uint8_t yOff = 0;
for (uint8_t i = 0; i < 4; i++) {
setFont(i);
yOff += (mDisplay->getMaxCharHeight());
mLineOffsets[i] = yOff;
}
}
inline void DisplayMono::setFont(uint8_t line) {
switch (line) {
case 0:
mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_logisoso16_tr);
break;
case 3:
mDisplay->setFont(u8g2_font_5x8_tr);
break;
default:
mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr);
break;
}
}
void DisplayMono::printText(const char* text, uint8_t line, uint8_t dispX) {
if (!mIsLarge) {
dispX = (line == 0) ? 10 : 5;
}
setFont(line);
dispX += (mEnScreenSaver) ? (_mExtra % 7) : 0;
mDisplay->drawStr(dispX, mLineOffsets[line], text);
}

45
src/plugins/Display/Display_Mono.h

@ -1,38 +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 <U8g2lib.h>
#define DISP_DEFAULT_TIMEOUT 60 // in seconds
#define DISP_FMT_TEXT_LEN 32
#define DISP_FMT_TEXT_LEN 32
#define BOTTOM_MARGIN 5
#ifdef ESP8266
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include "../../utils/helper.h"
class DisplayMono {
public:
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);
DisplayMono() {};
private:
void calcLineHeights();
void setFont(uint8_t line);
void printText(const char* text, uint8_t line, uint8_t dispX = 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;
protected:
U8G2* mDisplay;
uint8_t mType;
bool mEnPowerSafe, mEnScreenSaver;
uint8_t mLuminance;
bool mIsLarge = false;
uint8_t mLoopCnt;
uint32_t* mUtcTs;
uint8_t mLineOffsets[5];
uint8_t mLineXOffsets[5];
uint8_t mLineYOffsets[5];
uint16_t _dispY;
uint16_t mDispY;
uint8_t _mExtra;
uint8_t mExtra;
uint16_t mTimeout;
char _fmtText[DISP_FMT_TEXT_LEN];
};
char mFmtText[DISP_FMT_TEXT_LEN];};

155
src/plugins/Display/Display_Mono_128X32.h

@ -0,0 +1,155 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#pragma once
#include "Display_Mono.h"
class DisplayMono128X32 : public DisplayMono {
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++;
}
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);
}
}
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;
}
}
inline uint8_t getColumn(uint8_t line) {
if (line >= 1 && line <= 2)
return 1;
else
return 0;
}
inline bool isTwoRowLine(uint8_t line) {
return ((line >= 1) && (line <= 2));
}
void 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);
}
};

138
src/plugins/Display/Display_Mono_128X64.h

@ -0,0 +1,138 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#pragma once
#include "Display_Mono.h"
class DisplayMono128X64 : public DisplayMono {
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);
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::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
mDisplay->sendBuffer();
mDispY = 0;
mExtra++;
}
private:
void calcLinePositions() {
uint8_t yOff = 0;
for (uint8_t i = 0; i < 4; i++) {
setFont(i);
yOff += (mDisplay->getMaxCharHeight());
mLineYOffsets[i] = yOff;
}
}
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);
dispX += (mEnScreenSaver) ? (mExtra % 7) : 0;
mDisplay->drawStr(dispX, mLineYOffsets[line], text);
}
};

132
src/plugins/Display/Display_Mono_84X48.h

@ -0,0 +1,132 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#pragma once
#include "Display_Mono.h"
class DisplayMono84X48 : public DisplayMono {
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);
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();
mExtra = 1;
}
private:
void calcLinePositions() {
uint8_t yOff = 0;
for (uint8_t i = 0; i < 4; i++) {
setFont(i);
yOff += (mDisplay->getMaxCharHeight());
mLineYOffsets[i] = yOff;
}
}
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;
}
}
void printText(const char *text, uint8_t line) {
uint8_t dispX = (line == 0) ? 10 : 5;
setFont(line);
dispX += (mEnScreenSaver) ? (mExtra % 7) : 0;
mDisplay->drawStr(dispX, mLineYOffsets[line], text);
}
};

107
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;
}
//***************************************************************************
@ -120,7 +121,29 @@ void DisplayEPaper::lastUpdatePaged() {
} while (_display->nextPage());
}
//***************************************************************************
void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod) {
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;
@ -130,47 +153,52 @@ 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");
}
_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,11 +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((0 == totalPower) && (mEnPowerSafe))
offlineFooter();
_display->powerOff();
}

4
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);
@ -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;
@ -47,6 +48,7 @@ class DisplayEPaper {
uint8_t mHeadFootPadding;
GxEPD2_GFX* _display;
uint32_t *mUtcTs;
bool mEnPowerSafe;
};
#endif // ESP32

2
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;

4
src/web/html/setup.html

@ -784,7 +784,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"]);
@ -820,7 +820,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

213
src/web/web.h

@ -642,14 +642,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
@ -662,12 +670,16 @@ 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");
snprintf(topic,sizeof(topic),"ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",
mApp->getVersion(), mConfig->sys.deviceName);
mApp->getVersion(), mConfig->sys.deviceName);
metrics = String(type) + String(topic);
snprintf(type,sizeof(type),"# TYPE ahoy_solar_freeheap gauge\n");
@ -694,93 +706,137 @@ class Web {
metrics += radioStatistic(F("retrans_cnt"), *nrfRetransmits);
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);
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;
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);
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;});
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);
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
metricsStep = metricsStateInverter3;
break;
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);
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;
// Start Realtime Data Channel loop for this inverter
metricsChannelId = 0;
metricsStep = metricStateRealtimeData;
}
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]);
}
snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec));
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val);
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;
}
} 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:
@ -793,6 +849,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<uint64_t(Inverter<> *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());

Loading…
Cancel
Save