mirror of
https://github.com/lumapu/ahoy.git
synced 2025-04-29 10:16:21 +02:00
Merge branch 'development03' into hms
This commit is contained in:
commit
2c1a32f9e8
16 changed files with 811 additions and 400 deletions
|
@ -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
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();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 6
|
||||
#define VERSION_PATCH 13
|
||||
#define VERSION_PATCH 15
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
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();
|
||||
|
||||
if (mNewPayload || ((++mLoopCnt % 10) == 0)) {
|
||||
mNewPayload = false;
|
||||
mLoopCnt = 0;
|
||||
mVersion = version;
|
||||
DataScreen();
|
||||
}
|
||||
}
|
||||
|
||||
if (mCfg->type == 0)
|
||||
return;
|
||||
private:
|
||||
void DataScreen() {
|
||||
if (mCfg->type == 0)
|
||||
return;
|
||||
if (*mUtcTs == 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
|
||||
}
|
||||
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 payloadEventListener(uint8_t cmd) {
|
||||
mNewPayload = true;
|
||||
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
|
||||
}
|
||||
|
||||
void tickerSecond() {
|
||||
mMono.loop();
|
||||
if (mNewPayload || ((++mLoopCnt % 10) == 0)) {
|
||||
mNewPayload = false;
|
||||
mLoopCnt = 0;
|
||||
DataScreen();
|
||||
}
|
||||
#if defined(ESP32)
|
||||
if (mRefreshCycle > 480) {
|
||||
mEpaper.fullRefresh();
|
||||
mRefreshCycle = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void DataScreen() {
|
||||
if (mCfg->type == 0)
|
||||
return;
|
||||
if (*mUtcTs == 0)
|
||||
return;
|
||||
// private member variables
|
||||
bool mNewPayload;
|
||||
uint8_t mLoopCnt;
|
||||
uint32_t *mUtcTs;
|
||||
const char *mVersion;
|
||||
display_t *mCfg;
|
||||
HMSYSTEM *mSys;
|
||||
uint16_t mRefreshCycle;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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;
|
||||
#if defined(ESP32)
|
||||
DisplayEPaper mEpaper;
|
||||
#endif
|
||||
DisplayMono *mMono;
|
||||
};
|
||||
|
||||
#endif /*__DISPLAY__*/
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
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);
|
||||
|
||||
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
Normal file
155
src/plugins/Display/Display_Mono_128X32.h
Normal file
|
@ -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
Normal file
138
src/plugins/Display/Display_Mono_128X64.h
Normal file
|
@ -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
Normal file
132
src/plugins/Display/Display_Mono_84X48.h
Normal file
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
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");
|
||||
_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");
|
||||
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);
|
||||
_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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
217
src/web/web.h
217
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 Data Channel loop for this inverter
|
||||
metricsChannelId = 0;
|
||||
metricsStep = metricStateRealtimeData;
|
||||
}
|
||||
} else {
|
||||
metricsStep = metricsStateEnd;
|
||||
}
|
||||
// Start Realtime Field loop
|
||||
metricsFieldId = FLD_UDC;
|
||||
metricsStep = metricStateRealtimeFieldId;
|
||||
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);
|
||||
} else {
|
||||
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends.
|
||||
}
|
||||
case metricStateRealtimeFieldId: // Iterate over all defined fields
|
||||
if (metricsFieldId < FLD_LAST_ALARM_CODE) {
|
||||
metrics = "# Info: processing realtime field #"+String(metricsFieldId)+"\n";
|
||||
metricDeclared = false;
|
||||
|
||||
metricsChannelId++;
|
||||
metricsInverterId = 0;
|
||||
metricsStep = metricStateRealtimeInverterId;
|
||||
} 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
|
||||
metrics = "# Info: all realtime fields processed\n";
|
||||
metricsStep = metricsStateAlarmData;
|
||||
}
|
||||
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);
|
||||
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 {
|
||||
metrics = "# Info: No data for field #"+String(metricsFieldId)+" of inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n";
|
||||
metricsFieldId++; // Process next field Id
|
||||
metricsStep = metricStateRealtimeFieldId;
|
||||
}
|
||||
// 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.
|
||||
metrics = "# Info: All inverters for field #"+String(metricsFieldId)+" processed.\n";
|
||||
metricsFieldId++; // Process next field Id
|
||||
metricsStep = metricStateRealtimeFieldId;
|
||||
}
|
||||
// alarm channel processed --> try next inverter
|
||||
metricsInverterId++;
|
||||
metricsStep = metricsStateInverter;
|
||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
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…
Add table
Reference in a new issue