mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-02 11:45:54 +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
|
# 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
|
## 0.6.13 - 2023-05-16
|
||||||
* merge PR #934 (fix JSON API) and #944 (update manual)
|
* 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) {
|
void app::tickZeroValues(void) {
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
|
bool changed = false;
|
||||||
// set values to zero, except yields
|
// set values to zero, except yields
|
||||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||||
iv = mSys.getInverterByPos(id);
|
iv = mSys.getInverterByPos(id);
|
||||||
|
@ -329,7 +330,11 @@ void app::tickZeroValues(void) {
|
||||||
continue; // skip to next inverter
|
continue; // skip to next inverter
|
||||||
|
|
||||||
mPayload.zeroInverterValues(iv);
|
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'
|
// only triggered if 'reset values on no avail is enabled'
|
||||||
|
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
|
bool changed = false;
|
||||||
// set values to zero, except yields
|
// set values to zero, except yields
|
||||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||||
iv = mSys.getInverterByPos(id);
|
iv = mSys.getInverterByPos(id);
|
||||||
if (NULL == iv)
|
if (NULL == iv)
|
||||||
continue; // skip to next inverter
|
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);
|
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");
|
onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2");
|
||||||
|
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
|
bool changed = false;
|
||||||
// set values to zero, except yield total
|
// set values to zero, except yield total
|
||||||
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
|
||||||
iv = mSys.getInverterByPos(id);
|
iv = mSys.getInverterByPos(id);
|
||||||
if (NULL == iv)
|
if (NULL == iv)
|
||||||
continue; // skip to next inverter
|
continue; // skip to next inverter
|
||||||
|
|
||||||
mPayload.zeroInverterValues(iv);
|
mPayload.zeroInverterValues(iv, false);
|
||||||
mPayload.zeroYieldDay(iv);
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(changed)
|
||||||
|
payloadEventListener(RealTimeRunData_Debug);
|
||||||
|
|
||||||
if (mMqttEnabled)
|
if (mMqttEnabled)
|
||||||
mMqtt.tickerMidnight();
|
mMqtt.tickerMidnight();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 6
|
#define VERSION_MINOR 6
|
||||||
#define VERSION_PATCH 13
|
#define VERSION_PATCH 15
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -95,17 +95,7 @@ class HmPayload {
|
||||||
notify(0x0b);
|
notify(0x0b);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
void zeroYieldDay(Inverter<> *iv) {
|
void zeroInverterValues(Inverter<> *iv, bool skipYieldDay = true) {
|
||||||
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) {
|
|
||||||
DPRINTLN(DBG_DEBUG, F("zeroInverterValues"));
|
DPRINTLN(DBG_DEBUG, F("zeroInverterValues"));
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
for(uint8_t ch = 0; ch <= iv->channels; ch++) {
|
for(uint8_t ch = 0; ch <= iv->channels; ch++) {
|
||||||
|
@ -113,15 +103,18 @@ class HmPayload {
|
||||||
for(uint8_t fld = 0; fld < FLD_EVT; fld++) {
|
for(uint8_t fld = 0; fld < FLD_EVT; fld++) {
|
||||||
switch(fld) {
|
switch(fld) {
|
||||||
case FLD_YD:
|
case FLD_YD:
|
||||||
|
if(skipYieldDay)
|
||||||
|
continue;
|
||||||
|
else
|
||||||
|
break;
|
||||||
case FLD_YT:
|
case FLD_YT:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pos = iv->getPosByChFld(ch, fld, rec);
|
pos = iv->getPosByChFld(ch, fld, rec);
|
||||||
iv->setValue(pos, rec, 0.0f);
|
iv->setValue(pos, rec, 0.0f);
|
||||||
}
|
}
|
||||||
|
iv->doCalculations();
|
||||||
}
|
}
|
||||||
|
|
||||||
notify(RealTimeRunData_Debug);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ivSendHighPrio(Inverter<> *iv) {
|
void ivSendHighPrio(Inverter<> *iv) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ lib_deps =
|
||||||
bblanchon/ArduinoJson @ ^6.21.2
|
bblanchon/ArduinoJson @ ^6.21.2
|
||||||
https://github.com/JChristensen/Timezone @ ^1.2.4
|
https://github.com/JChristensen/Timezone @ ^1.2.4
|
||||||
olikraus/U8g2 @ ^2.34.17
|
olikraus/U8g2 @ ^2.34.17
|
||||||
zinggjm/GxEPD2 @ ^1.5.0
|
zinggjm/GxEPD2 @ ^1.5.2
|
||||||
|
|
||||||
|
|
||||||
[env:esp8266-release]
|
[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
|
log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||||
|
|
||||||
[env:esp32-wroom32-release]
|
[env:esp32-wroom32-release]
|
||||||
platform = espressif32@>=6.1.0
|
platform = espressif32@6.1.0
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -D RELEASE -std=gnu++14
|
build_flags = -D RELEASE -std=gnu++17
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;default ; Remove typical terminal control codes from input
|
||||||
|
@ -109,10 +109,10 @@ monitor_filters =
|
||||||
esp32_exception_decoder
|
esp32_exception_decoder
|
||||||
|
|
||||||
[env:esp32-wroom32-release-prometheus]
|
[env:esp32-wroom32-release-prometheus]
|
||||||
platform = espressif32@>=6.1.0
|
platform = espressif32@6.1.0
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -D RELEASE
|
build_flags = -D RELEASE
|
||||||
-std=gnu++14
|
-std=gnu++17
|
||||||
-DENABLE_PROMETHEUS_EP
|
-DENABLE_PROMETHEUS_EP
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
|
@ -122,7 +122,7 @@ monitor_filters =
|
||||||
esp32_exception_decoder
|
esp32_exception_decoder
|
||||||
|
|
||||||
[env:esp32-wroom32-debug]
|
[env:esp32-wroom32-debug]
|
||||||
platform = espressif32@>=6.1.0
|
platform = espressif32@6.1.0
|
||||||
board = lolin_d32
|
board = lolin_d32
|
||||||
build_flags = -DDEBUG_LEVEL=DBG_DEBUG
|
build_flags = -DDEBUG_LEVEL=DBG_DEBUG
|
||||||
-DDEBUG_ESP_CORE
|
-DDEBUG_ESP_CORE
|
||||||
|
@ -131,7 +131,7 @@ build_flags = -DDEBUG_LEVEL=DBG_DEBUG
|
||||||
-DDEBUG_ESP_HTTP_SERVER
|
-DDEBUG_ESP_HTTP_SERVER
|
||||||
-DDEBUG_ESP_OOM
|
-DDEBUG_ESP_OOM
|
||||||
-DDEBUG_ESP_PORT=Serial
|
-DDEBUG_ESP_PORT=Serial
|
||||||
-std=gnu++14
|
-std=gnu++17
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
build_type = debug
|
build_type = debug
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
|
@ -140,13 +140,13 @@ monitor_filters =
|
||||||
log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
|
||||||
|
|
||||||
[env:opendtufusionv1-release]
|
[env:opendtufusionv1-release]
|
||||||
platform = espressif32@>=6.1.0
|
platform = espressif32@6.1.0
|
||||||
board = esp32-s3-devkitc-1
|
board = esp32-s3-devkitc-1
|
||||||
upload_protocol = esp-builtin
|
upload_protocol = esp-builtin
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
debug_tool = esp-builtin
|
debug_tool = esp-builtin
|
||||||
debug_speed = 12000
|
debug_speed = 12000
|
||||||
build_flags = -D RELEASE -std=gnu++14
|
build_flags = -D RELEASE -std=gnu++17
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
monitor_filters =
|
monitor_filters =
|
||||||
;default ; Remove typical terminal control codes from input
|
;default ; Remove typical terminal control codes from input
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
#include "../../hm/hmSystem.h"
|
#include "../../hm/hmSystem.h"
|
||||||
#include "../../utils/helper.h"
|
#include "../../utils/helper.h"
|
||||||
#include "Display_Mono.h"
|
#include "Display_Mono.h"
|
||||||
|
#include "Display_Mono_128X32.h"
|
||||||
|
#include "Display_Mono_128X64.h"
|
||||||
|
#include "Display_Mono_84X48.h"
|
||||||
#include "Display_ePaper.h"
|
#include "Display_ePaper.h"
|
||||||
|
|
||||||
template <class HMSYSTEM>
|
template <class HMSYSTEM>
|
||||||
|
@ -26,14 +29,27 @@ class Display {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ((0 < mCfg->type) && (mCfg->type < 10)) {
|
if ((0 < mCfg->type) && (mCfg->type < 10)) {
|
||||||
mMono.config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast);
|
switch (mCfg->type) {
|
||||||
mMono.init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
|
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) {
|
} else if (mCfg->type >= 10) {
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
mRefreshCycle = 0;
|
mRefreshCycle = 0;
|
||||||
mEpaper.config(mCfg->rot);
|
mEpaper.config(mCfg->rot, mCfg->pwrSaveAtIvOffline);
|
||||||
mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
|
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
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +58,9 @@ class Display {
|
||||||
}
|
}
|
||||||
|
|
||||||
void tickerSecond() {
|
void tickerSecond() {
|
||||||
mMono.loop();
|
if (mMono != NULL)
|
||||||
|
mMono->loop();
|
||||||
|
|
||||||
if (mNewPayload || ((++mLoopCnt % 10) == 0)) {
|
if (mNewPayload || ((++mLoopCnt % 10) == 0)) {
|
||||||
mNewPayload = false;
|
mNewPayload = false;
|
||||||
mLoopCnt = 0;
|
mLoopCnt = 0;
|
||||||
|
@ -79,21 +97,21 @@ class Display {
|
||||||
totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
|
totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((0 < mCfg->type) && (mCfg->type < 10)) {
|
if ((0 < mCfg->type) && (mCfg->type < 10) && (mMono != NULL)) {
|
||||||
mMono.disp(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
mMono->disp(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
||||||
} else if (mCfg->type >= 10) {
|
} else if (mCfg->type >= 10) {
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
||||||
mRefreshCycle++;
|
mRefreshCycle++;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
if (mRefreshCycle > 480) {
|
if (mRefreshCycle > 480) {
|
||||||
mEpaper.fullRefresh();
|
mEpaper.fullRefresh();
|
||||||
mRefreshCycle = 0;
|
mRefreshCycle = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// private member variables
|
// private member variables
|
||||||
|
@ -105,10 +123,10 @@ class Display {
|
||||||
HMSYSTEM *mSys;
|
HMSYSTEM *mSys;
|
||||||
uint16_t mRefreshCycle;
|
uint16_t mRefreshCycle;
|
||||||
|
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
DisplayEPaper mEpaper;
|
DisplayEPaper mEpaper;
|
||||||
#endif
|
#endif
|
||||||
DisplayMono mMono;
|
DisplayMono *mMono;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__DISPLAY__*/
|
#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>
|
#include <U8g2lib.h>
|
||||||
#define DISP_DEFAULT_TIMEOUT 60 // in seconds
|
#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 {
|
class DisplayMono {
|
||||||
public:
|
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);
|
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;
|
||||||
void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum);
|
virtual void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) = 0;
|
||||||
void loop(void);
|
virtual void loop(void) = 0;
|
||||||
void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod);
|
virtual void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) = 0;
|
||||||
|
|
||||||
private:
|
|
||||||
void calcLineHeights();
|
|
||||||
void setFont(uint8_t line);
|
|
||||||
void printText(const char* text, uint8_t line, uint8_t dispX = 5);
|
|
||||||
|
|
||||||
|
protected:
|
||||||
U8G2* mDisplay;
|
U8G2* mDisplay;
|
||||||
|
|
||||||
uint8_t mType;
|
uint8_t mType;
|
||||||
bool mEnPowerSafe, mEnScreenSaver;
|
bool mEnPowerSafe, mEnScreenSaver;
|
||||||
uint8_t mLuminance;
|
uint8_t mLuminance;
|
||||||
|
|
||||||
bool mIsLarge = false;
|
|
||||||
uint8_t mLoopCnt;
|
uint8_t mLoopCnt;
|
||||||
uint32_t* mUtcTs;
|
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;
|
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;
|
mDisplayRotation = rotation;
|
||||||
|
mEnPowerSafe = enPowerSafe;
|
||||||
}
|
}
|
||||||
|
|
||||||
//***************************************************************************
|
//***************************************************************************
|
||||||
|
@ -120,7 +121,29 @@ void DisplayEPaper::lastUpdatePaged() {
|
||||||
} while (_display->nextPage());
|
} 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;
|
int16_t tbx, tby;
|
||||||
uint16_t tbw, tbh, x, y;
|
uint16_t tbw, tbh, x, y;
|
||||||
|
|
||||||
|
@ -130,15 +153,19 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl
|
||||||
_display->setPartialWindow(0, mHeadFootPadding, _display->width(), _display->height() - (mHeadFootPadding * 2));
|
_display->setPartialWindow(0, mHeadFootPadding, _display->width(), _display->height() - (mHeadFootPadding * 2));
|
||||||
_display->fillScreen(GxEPD_WHITE);
|
_display->fillScreen(GxEPD_WHITE);
|
||||||
do {
|
do {
|
||||||
if (_totalPower > 9999) {
|
if (totalPower > 9999) {
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (_totalPower / 10000));
|
snprintf(_fmtText, sizeof(_fmtText), "%.1f kW", (totalPower / 10000));
|
||||||
_changed = true;
|
_changed = true;
|
||||||
} else if ((_totalPower > 0) && (_totalPower <= 9999)) {
|
} else if ((totalPower > 0) && (totalPower <= 9999)) {
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "%.0f W", _totalPower);
|
snprintf(_fmtText, sizeof(_fmtText), "%.0f W", totalPower);
|
||||||
_changed = true;
|
_changed = true;
|
||||||
} else {
|
} else {
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "offline");
|
snprintf(_fmtText, sizeof(_fmtText), "offline");
|
||||||
}
|
}
|
||||||
|
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);
|
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
|
||||||
x = ((_display->width() - tbw) / 2) - tbx;
|
x = ((_display->width() - tbw) / 2) - tbx;
|
||||||
_display->setCursor(x, mHeadFootPadding + tbh + 10);
|
_display->setCursor(x, mHeadFootPadding + tbh + 10);
|
||||||
|
@ -148,7 +175,7 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl
|
||||||
y = _display->height() / 2;
|
y = _display->height() / 2;
|
||||||
_display->setCursor(5, y);
|
_display->setCursor(5, y);
|
||||||
_display->print("today:");
|
_display->print("today:");
|
||||||
snprintf(_fmtText, _display->width(), "%.0f", _totalYieldDay);
|
snprintf(_fmtText, _display->width(), "%.0f", totalYieldDay);
|
||||||
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
|
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
|
||||||
x = ((_display->width() - tbw) / 2) - tbx;
|
x = ((_display->width() - tbw) / 2) - tbx;
|
||||||
_display->setCursor(x, y);
|
_display->setCursor(x, y);
|
||||||
|
@ -159,7 +186,7 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl
|
||||||
y = y + tbh + 7;
|
y = y + tbh + 7;
|
||||||
_display->setCursor(5, y);
|
_display->setCursor(5, y);
|
||||||
_display->print("total:");
|
_display->print("total:");
|
||||||
snprintf(_fmtText, _display->width(), "%.1f", _totalYieldTotal);
|
snprintf(_fmtText, _display->width(), "%.1f", totalYieldTotal);
|
||||||
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
|
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
|
||||||
x = ((_display->width() - tbw) / 2) - tbx;
|
x = ((_display->width() - tbw) / 2) - tbx;
|
||||||
_display->setCursor(x, y);
|
_display->setCursor(x, y);
|
||||||
|
@ -168,9 +195,10 @@ void DisplayEPaper::actualPowerPaged(float _totalPower, float _totalYieldDay, fl
|
||||||
_display->println("kWh");
|
_display->println("kWh");
|
||||||
|
|
||||||
_display->setCursor(10, _display->height() - (mHeadFootPadding + 10));
|
_display->setCursor(10, _display->height() - (mHeadFootPadding + 10));
|
||||||
snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", _isprod);
|
snprintf(_fmtText, sizeof(_fmtText), "%d Inverter online", isprod);
|
||||||
_display->println(_fmtText);
|
_display->println(_fmtText);
|
||||||
|
|
||||||
|
}
|
||||||
} while (_display->nextPage());
|
} 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
|
// call the PowerPage to change the PV Power Values
|
||||||
actualPowerPaged(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
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)) {
|
if ((isprod > 0) && (_changed)) {
|
||||||
_changed = false;
|
_changed = false;
|
||||||
lastUpdatePaged();
|
lastUpdatePaged();
|
||||||
}
|
} else if((0 == totalPower) && (mEnPowerSafe))
|
||||||
|
offlineFooter();
|
||||||
|
|
||||||
_display->powerOff();
|
_display->powerOff();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class DisplayEPaper {
|
||||||
DisplayEPaper();
|
DisplayEPaper();
|
||||||
void fullRefresh();
|
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 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);
|
void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod);
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ class DisplayEPaper {
|
||||||
void headlineIP();
|
void headlineIP();
|
||||||
void actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod);
|
void actualPowerPaged(float _totalPower, float _totalYieldDay, float _totalYieldTotal, uint8_t _isprod);
|
||||||
void lastUpdatePaged();
|
void lastUpdatePaged();
|
||||||
|
void offlineFooter();
|
||||||
|
|
||||||
uint8_t mDisplayRotation;
|
uint8_t mDisplayRotation;
|
||||||
bool _changed = false;
|
bool _changed = false;
|
||||||
|
@ -47,6 +48,7 @@ class DisplayEPaper {
|
||||||
uint8_t mHeadFootPadding;
|
uint8_t mHeadFootPadding;
|
||||||
GxEPD2_GFX* _display;
|
GxEPD2_GFX* _display;
|
||||||
uint32_t *mUtcTs;
|
uint32_t *mUtcTs;
|
||||||
|
bool mEnPowerSafe;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ESP32
|
#endif // ESP32
|
||||||
|
|
|
@ -564,7 +564,7 @@ class PubMqtt {
|
||||||
void sendIvData() {
|
void sendIvData() {
|
||||||
bool anyAvail = processIvStatus();
|
bool anyAvail = processIvStatus();
|
||||||
if (mLastAnyAvail != anyAvail)
|
if (mLastAnyAvail != anyAvail)
|
||||||
mSendList.push(RealTimeRunData_Debug); // makes shure that total values are calculated
|
mSendList.push(RealTimeRunData_Debug); // makes sure that total values are calculated
|
||||||
|
|
||||||
if(mSendList.empty())
|
if(mSendList.empty())
|
||||||
return;
|
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)
|
if("ESP32" == type)
|
||||||
opts.push([10, "ePaper"]);
|
opts.push([10, "ePaper"]);
|
||||||
var dispType = sel("disp_typ", opts, obj["disp_typ"]);
|
var dispType = sel("disp_typ", opts, obj["disp_typ"]);
|
||||||
|
@ -820,7 +820,7 @@
|
||||||
|
|
||||||
if(0 == dispType)
|
if(0 == dispType)
|
||||||
cl.add("hide");
|
cl.add("hide");
|
||||||
else if(dispType <= 2) { // OLED
|
else if(dispType <= 2 || dispType == 4) { // OLED
|
||||||
if(i < 2)
|
if(i < 2)
|
||||||
cl.remove("hide");
|
cl.remove("hide");
|
||||||
else
|
else
|
||||||
|
|
189
src/web/web.h
189
src/web/web.h
|
@ -642,14 +642,22 @@ class Web {
|
||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_PROMETHEUS_EP
|
#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 {
|
enum {
|
||||||
metricsStateStart, metricsStateInverter, metricStateRealtimeData,metricsStateAlarmData,metricsStateEnd
|
metricsStateStart,
|
||||||
|
metricsStateInverter1, metricsStateInverter2, metricsStateInverter3, metricsStateInverter4,
|
||||||
|
metricStateRealtimeFieldId, metricStateRealtimeInverterId,
|
||||||
|
metricsStateAlarmData,
|
||||||
|
metricsStateEnd
|
||||||
} metricsStep;
|
} metricsStep;
|
||||||
int metricsInverterId,metricsChannelId;
|
int metricsInverterId;
|
||||||
|
uint8_t metricsFieldId;
|
||||||
|
bool metricDeclared;
|
||||||
|
|
||||||
void showMetrics(AsyncWebServerRequest *request) {
|
void showMetrics(AsyncWebServerRequest *request) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("web::showMetrics"));
|
DPRINTLN(DBG_VERBOSE, F("web::showMetrics"));
|
||||||
|
|
||||||
metricsStep = metricsStateStart;
|
metricsStep = metricsStateStart;
|
||||||
AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"),
|
AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"),
|
||||||
[this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t
|
[this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t
|
||||||
|
@ -662,7 +670,11 @@ class Web {
|
||||||
char type[60], topic[100], val[25];
|
char type[60], topic[100], val[25];
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
int alarmChannelId;
|
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) {
|
switch (metricsStep) {
|
||||||
case metricsStateStart: // System Info & NRF Statistics : fit to one packet
|
case metricsStateStart: // System Info & NRF Statistics : fit to one packet
|
||||||
snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n");
|
snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n");
|
||||||
|
@ -694,93 +706,137 @@ class Web {
|
||||||
metrics += radioStatistic(F("retrans_cnt"), *nrfRetransmits);
|
metrics += radioStatistic(F("retrans_cnt"), *nrfRetransmits);
|
||||||
|
|
||||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
// Start Inverter loop
|
// Next is Inverter information
|
||||||
metricsInverterId = 0;
|
metricsInverterId = 0;
|
||||||
metricsStep = metricsStateInverter;
|
metricsStep = metricsStateInverter1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case metricsStateInverter: // Inverter loop
|
case metricsStateInverter1: // Information about all inverters configured : fit to one packet
|
||||||
if (metricsInverterId < mSys->getNumInverters()) {
|
metrics = "# TYPE ahoy_solar_inverter_info gauge\n";
|
||||||
iv = mSys->getInverterByPos(metricsInverterId);
|
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n",
|
||||||
if(NULL != iv) {
|
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->serial.u64;});
|
||||||
// Inverter info : fit to one packet
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_info gauge\n");
|
metricsStep = metricsStateInverter2;
|
||||||
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n",
|
break;
|
||||||
iv->config->name, iv->config->serial.u64);
|
|
||||||
metrics = String(type) + String(topic);
|
|
||||||
|
|
||||||
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_enabled gauge\n");
|
case metricsStateInverter2: // Information about all inverters configured : fit to one packet
|
||||||
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",iv->config->name,iv->config->enabled);
|
metrics += "# TYPE ahoy_solar_inverter_is_enabled gauge\n";
|
||||||
metrics += String(type) + String(topic);
|
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);
|
|
||||||
|
|
||||||
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_producing gauge\n");
|
|
||||||
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",iv->config->name,iv->isProducing(mApp->getTimestamp()));
|
|
||||||
metrics += String(type) + String(topic);
|
|
||||||
|
|
||||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
metricsStep = metricsStateInverter3;
|
||||||
// Start Realtime Data Channel loop for this inverter
|
|
||||||
metricsChannelId = 0;
|
|
||||||
metricsStep = metricStateRealtimeData;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metricsStep = metricsStateEnd;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case metricStateRealtimeData: // Realtime Data Channel loop
|
case metricsStateInverter3: // Information about all inverters configured : fit to one packet
|
||||||
|
metrics += "# TYPE ahoy_solar_inverter_is_available gauge\n";
|
||||||
|
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",
|
||||||
|
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable(mApp->getTimestamp());});
|
||||||
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
metricsStep = metricsStateInverter4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case metricsStateInverter4: // Information about all inverters configured : fit to one packet
|
||||||
|
metrics += "# TYPE ahoy_solar_inverter_is_producing gauge\n";
|
||||||
|
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",
|
||||||
|
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing(mApp->getTimestamp());});
|
||||||
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
// Start Realtime Field loop
|
||||||
|
metricsFieldId = FLD_UDC;
|
||||||
|
metricsStep = metricStateRealtimeFieldId;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case metricStateRealtimeFieldId: // Iterate over all defined fields
|
||||||
|
if (metricsFieldId < FLD_LAST_ALARM_CODE) {
|
||||||
|
metrics = "# Info: processing realtime field #"+String(metricsFieldId)+"\n";
|
||||||
|
metricDeclared = false;
|
||||||
|
|
||||||
|
metricsInverterId = 0;
|
||||||
|
metricsStep = metricStateRealtimeInverterId;
|
||||||
|
} else {
|
||||||
|
metrics = "# Info: all realtime fields processed\n";
|
||||||
|
metricsStep = metricsStateAlarmData;
|
||||||
|
}
|
||||||
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case metricStateRealtimeInverterId: // Iterate over all inverters for this field
|
||||||
|
metrics = "";
|
||||||
|
if (metricsInverterId < mSys->getNumInverters()) {
|
||||||
|
// process all channels of this inverter
|
||||||
|
|
||||||
iv = mSys->getInverterByPos(metricsInverterId);
|
iv = mSys->getInverterByPos(metricsInverterId);
|
||||||
|
if (NULL != iv) {
|
||||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
if (metricsChannelId < rec->length) {
|
for (metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) {
|
||||||
uint8_t channel = rec->assign[metricsChannelId].ch;
|
uint8_t channel = rec->assign[metricsChannelId].ch;
|
||||||
// Skip entry if maxPwr is 0 and it's not the inverter channel (channel 0)
|
|
||||||
|
// Try inverter channel (channel 0) or any channel with maxPwr > 0
|
||||||
if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) {
|
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));
|
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());
|
// 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) {
|
if (0 == channel) {
|
||||||
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name);
|
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name);
|
||||||
} else {
|
} 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(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));
|
snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec));
|
||||||
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val);
|
metrics += topic;
|
||||||
} else {
|
metrics += val;
|
||||||
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsChannelId++;
|
|
||||||
} 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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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;
|
break;
|
||||||
|
|
||||||
case metricsStateAlarmData: // Alarm Info loop
|
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);
|
iv = mSys->getInverterByPos(metricsInverterId);
|
||||||
|
if (NULL != iv) {
|
||||||
rec = iv->getRecordStruct(AlarmData);
|
rec = iv->getRecordStruct(AlarmData);
|
||||||
// simple hack : there is only one channel with alarm data
|
// simple hack : there is only one channel with alarm data
|
||||||
// TODO: find the right one channel with the alarm id
|
// TODO: find the right one channel with the alarm id
|
||||||
alarmChannelId = 0;
|
alarmChannelId = 0;
|
||||||
// printf("AlarmData Length %d\n",rec->length);
|
|
||||||
if (alarmChannelId < rec->length) {
|
if (alarmChannelId < rec->length) {
|
||||||
//uint8_t channel = rec->assign[alarmChannelId].ch;
|
|
||||||
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
|
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(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));
|
snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec));
|
||||||
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val);
|
metrics += topic;
|
||||||
} else {
|
metrics += val;
|
||||||
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends.
|
|
||||||
}
|
}
|
||||||
// alarm channel processed --> try next inverter
|
}
|
||||||
metricsInverterId++;
|
}
|
||||||
metricsStep = metricsStateInverter;
|
len = snprintf((char*)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
metricsStep = metricsStateEnd;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case metricsStateEnd:
|
case metricsStateEnd:
|
||||||
|
@ -793,6 +849,21 @@ class Web {
|
||||||
request->send(response);
|
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) {
|
String radioStatistic(String statistic, uint32_t value) {
|
||||||
char type[60], topic[80], val[25];
|
char type[60], topic[80], val[25];
|
||||||
snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s counter",statistic.c_str());
|
snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s counter",statistic.c_str());
|
||||||
|
|
Loading…
Add table
Reference in a new issue