mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-24 22:36:10 +02:00
Merge branch 'development03' into eth
This commit is contained in:
commit
0be01a91a1
23 changed files with 876 additions and 412 deletions
|
@ -217,6 +217,14 @@ Once your Ahoy DTU is running, you can use the Over The Air (OTA) capabilities t
|
||||||
|
|
||||||
! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data!
|
! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data!
|
||||||
|
|
||||||
|
#### Flashing on Linux with `esptool.py` (ESP32)
|
||||||
|
1. install [esptool.py](https://docs.espressif.com/projects/esptool/en/latest/esp32/) if you haven't already.
|
||||||
|
2. download and extract the latest release bin-file from [ahoy_](https://github.com/grindylow/ahoy/releases)
|
||||||
|
3. `cd ahoy_v<XXX> && cp *esp32.bin esp32.bin`
|
||||||
|
4. Perhaps you need to replace `/dev/ttyUSB0` to match your acual device in the following command. Execute it afterwards: `esptool.py --port /dev/ttyUSB0 --chip esp32 --before default_reset --after hard_reset write_flash --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 esp32.bin`
|
||||||
|
5. Unplug and replug your device.
|
||||||
|
6. Open a serial monitor (e.g. Putty) @ 115200 Baud. You should see some messages regarding wifi.
|
||||||
|
|
||||||
## Connect to your Ahoy DTU
|
## Connect to your Ahoy DTU
|
||||||
|
|
||||||
When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU.
|
When everything is wired up and the firmware is flashed, it is time to connect to your Ahoy DTU.
|
||||||
|
|
|
@ -321,6 +321,19 @@ Send Power Limit:
|
||||||
- A persistent limit is only needed if you want to throttle your inverter permanently or you can use it to set a start value on the battery, which is then always the switch-on limit when switching on, otherwise it would ramp up to 100% without regulation, which is continuous load is not healthy.
|
- A persistent limit is only needed if you want to throttle your inverter permanently or you can use it to set a start value on the battery, which is then always the switch-on limit when switching on, otherwise it would ramp up to 100% without regulation, which is continuous load is not healthy.
|
||||||
- You can set a new limit in the turn-off state, which is then used for on (switching on again), otherwise the last limit from before the turn-off is used, but of course this only applies if DC voltage is applied the whole time.
|
- You can set a new limit in the turn-off state, which is then used for on (switching on again), otherwise the last limit from before the turn-off is used, but of course this only applies if DC voltage is applied the whole time.
|
||||||
- If the DC voltage is missing for a few seconds, the microcontroller in the inverter goes off and forgets everything that was temporary/non-persistent in the RAM: YieldDay, error memory, non-persistent limit.
|
- If the DC voltage is missing for a few seconds, the microcontroller in the inverter goes off and forgets everything that was temporary/non-persistent in the RAM: YieldDay, error memory, non-persistent limit.
|
||||||
|
### Update your AHOY-DTU Firmware
|
||||||
|
To update your AHOY-DTU, you have to download the latest firmware package.
|
||||||
|
Here are the [latest stable releases](https://github.com/lumapu/ahoy/releases/) and [latest development builds](https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip) available for download.
|
||||||
|
As soon as you have downloaded the firmware package, unzip it. On the WebUI, navigate to Update and press on select firmware file.
|
||||||
|
From the unzipped files, select the right .bin file for your hardware and needs.
|
||||||
|
- If you use an ESP8266, select the file ending with esp8266.bin
|
||||||
|
- If you use an ESP8266 with prometheus, select the file ending with esp8266_prometheus.bin
|
||||||
|
- If you use an ESP32, select the file ending with esp32.bin
|
||||||
|
- If you use an ESP32 with prometheus, select the file ending with esp32_prometheus.bin
|
||||||
|
|
||||||
|
Note: if you want to use prometheus, the usage of an ESP32 is recommended, since the ESP8266 is at its performance limits and therefore can cause stability issues.
|
||||||
|
|
||||||
|
After selecting the right firmware file, press update. Your AHOY-DTU will now install the new firmware and reboot.
|
||||||
|
|
||||||
## Additional Notes
|
## Additional Notes
|
||||||
### MI Inverters
|
### MI Inverters
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
# 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
|
||||||
|
* merge PR #934 (fix JSON API) and #944 (update manual)
|
||||||
|
|
||||||
## 0.6.12 - 2023-04-28
|
## 0.6.12 - 2023-04-28
|
||||||
* improved MqTT
|
* improved MqTT
|
||||||
* fix menu active item
|
* fix menu active item
|
||||||
|
|
21
src/app.cpp
21
src/app.cpp
|
@ -325,6 +325,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);
|
||||||
|
@ -332,7 +333,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -340,15 +345,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -359,16 +370,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 12
|
#define VERSION_PATCH 15
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -93,17 +93,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++) {
|
||||||
|
@ -111,15 +101,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,7 +98,7 @@ 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++17
|
build_flags = -D RELEASE -std=gnu++17
|
||||||
build_unflags = -std=gnu++11
|
build_unflags = -std=gnu++11
|
||||||
|
@ -109,7 +109,7 @@ 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++17
|
-std=gnu++17
|
||||||
|
@ -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
|
||||||
|
@ -166,7 +166,7 @@ monitor_filters =
|
||||||
esp32_exception_decoder
|
esp32_exception_decoder
|
||||||
|
|
||||||
[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
|
||||||
|
|
|
@ -7,108 +7,126 @@
|
||||||
#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>
|
||||||
class Display {
|
class Display {
|
||||||
public:
|
public:
|
||||||
Display() {}
|
Display() {}
|
||||||
|
|
||||||
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) {
|
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) {
|
||||||
mCfg = cfg;
|
mCfg = cfg;
|
||||||
mSys = sys;
|
mSys = sys;
|
||||||
mUtcTs = utcTs;
|
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;
|
mNewPayload = false;
|
||||||
mLoopCnt = 0;
|
mLoopCnt = 0;
|
||||||
mVersion = version;
|
DataScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mCfg->type == 0)
|
private:
|
||||||
return;
|
void DataScreen() {
|
||||||
|
if (mCfg->type == 0)
|
||||||
|
return;
|
||||||
|
if (*mUtcTs == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if ((0 < mCfg->type) && (mCfg->type < 10)) {
|
float totalPower = 0;
|
||||||
mMono.config(mCfg->pwrSaveAtIvOffline, mCfg->pxShift, mCfg->contrast);
|
float totalYieldDay = 0;
|
||||||
mMono.init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
|
float totalYieldTotal = 0;
|
||||||
} else if (mCfg->type >= 10) {
|
|
||||||
#if defined(ESP32)
|
uint8_t isprod = 0;
|
||||||
mRefreshCycle = 0;
|
|
||||||
mEpaper.config(mCfg->rot);
|
Inverter<> *iv;
|
||||||
mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mVersion);
|
record_t<> *rec;
|
||||||
#endif
|
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) {
|
if ((0 < mCfg->type) && (mCfg->type < 10) && (mMono != NULL)) {
|
||||||
mNewPayload = true;
|
mMono->disp(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
||||||
|
} else if (mCfg->type >= 10) {
|
||||||
|
#if defined(ESP32)
|
||||||
|
mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod);
|
||||||
|
mRefreshCycle++;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void tickerSecond() {
|
#if defined(ESP32)
|
||||||
mMono.loop();
|
if (mRefreshCycle > 480) {
|
||||||
if (mNewPayload || ((++mLoopCnt % 10) == 0)) {
|
mEpaper.fullRefresh();
|
||||||
mNewPayload = false;
|
mRefreshCycle = 0;
|
||||||
mLoopCnt = 0;
|
|
||||||
DataScreen();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
// private member variables
|
||||||
void DataScreen() {
|
bool mNewPayload;
|
||||||
if (mCfg->type == 0)
|
uint8_t mLoopCnt;
|
||||||
return;
|
uint32_t *mUtcTs;
|
||||||
if (*mUtcTs == 0)
|
const char *mVersion;
|
||||||
return;
|
display_t *mCfg;
|
||||||
|
HMSYSTEM *mSys;
|
||||||
|
uint16_t mRefreshCycle;
|
||||||
|
|
||||||
float totalPower = 0;
|
#if defined(ESP32)
|
||||||
float totalYieldDay = 0;
|
DisplayEPaper mEpaper;
|
||||||
float totalYieldTotal = 0;
|
#endif
|
||||||
|
DisplayMono *mMono;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#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,47 +153,52 @@ 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");
|
||||||
}
|
}
|
||||||
_display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh);
|
if (totalPower == 0){
|
||||||
x = ((_display->width() - tbw) / 2) - tbx;
|
_display->fillRect(0, mHeadFootPadding, 200,200, GxEPD_BLACK);
|
||||||
_display->setCursor(x, mHeadFootPadding + tbh + 10);
|
_display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE);
|
||||||
_display->print(_fmtText);
|
} 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);
|
_display->setFont(&FreeSans12pt7b);
|
||||||
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);
|
||||||
_display->print(_fmtText);
|
_display->print(_fmtText);
|
||||||
_display->setCursor(_display->width() - 38, y);
|
_display->setCursor(_display->width() - 38, y);
|
||||||
_display->println("Wh");
|
_display->println("Wh");
|
||||||
|
|
||||||
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);
|
||||||
_display->print(_fmtText);
|
_display->print(_fmtText);
|
||||||
_display->setCursor(_display->width() - 50, y);
|
_display->setCursor(_display->width() - 50, y);
|
||||||
_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;
|
||||||
|
|
|
@ -563,7 +563,7 @@ class RestApi {
|
||||||
|
|
||||||
if(F("power") == jsonIn[F("cmd")])
|
if(F("power") == jsonIn[F("cmd")])
|
||||||
accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff);
|
accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff);
|
||||||
else if(F("restart") == jsonIn[F("restart")])
|
else if(F("restart") == jsonIn[F("cmd")])
|
||||||
accepted = iv->setDevControlRequest(Restart);
|
accepted = iv->setDevControlRequest(Restart);
|
||||||
else if(0 == strncmp("limit_", jsonIn[F("cmd")].as<const char*>(), 6)) {
|
else if(0 == strncmp("limit_", jsonIn[F("cmd")].as<const char*>(), 6)) {
|
||||||
iv->powerLimit[0] = jsonIn["val"];
|
iv->powerLimit[0] = jsonIn["val"];
|
||||||
|
|
|
@ -733,7 +733,7 @@
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts = [[0, "None"], [1, "SSD1306 0.96\""], [2, "SH1106 1.3\""], [3, "Nokia5110"]];
|
var opts = [[0, "None"], [1, "SSD1306 0.96\" 128X64"], [2, "SH1106 1.3\""], [3, "Nokia5110"], [4, "SSD1306 0.96\" 128X32"]];
|
||||||
if("ESP32" == type)
|
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"]);
|
||||||
|
@ -769,7 +769,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
|
||||||
|
|
226
src/web/web.h
226
src/web/web.h
|
@ -639,14 +639,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
|
||||||
|
@ -659,7 +667,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");
|
||||||
|
@ -688,93 +700,138 @@ class Web {
|
||||||
metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt);
|
metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt);
|
||||||
|
|
||||||
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",
|
|
||||||
iv->config->name, iv->config->serial.u64);
|
|
||||||
metrics = String(type) + String(topic);
|
|
||||||
|
|
||||||
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_enabled gauge\n");
|
|
||||||
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",iv->config->name,iv->config->enabled);
|
|
||||||
metrics += String(type) + String(topic);
|
|
||||||
|
|
||||||
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_available gauge\n");
|
|
||||||
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",iv->config->name,iv->isAvailable(mApp->getTimestamp()));
|
|
||||||
metrics += String(type) + String(topic);
|
|
||||||
|
|
||||||
snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_producing gauge\n");
|
|
||||||
snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",iv->config->name,iv->isProducing(mApp->getTimestamp()));
|
|
||||||
metrics += String(type) + String(topic);
|
|
||||||
|
|
||||||
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
|
||||||
|
|
||||||
// Start Realtime Data Channel loop for this inverter
|
|
||||||
metricsChannelId = 0;
|
|
||||||
metricsStep = metricStateRealtimeData;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metricsStep = metricsStateEnd;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case metricStateRealtimeData: // Realtime Data Channel loop
|
case metricsStateInverter2: // Information about all inverters configured : fit to one packet
|
||||||
iv = mSys->getInverterByPos(metricsInverterId);
|
metrics += "# TYPE ahoy_solar_inverter_is_enabled gauge\n";
|
||||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",
|
||||||
if (metricsChannelId < rec->length) {
|
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->enabled;});
|
||||||
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
metricsChannelId++;
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
metricsStep = metricsStateInverter3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case metricsStateInverter3: // Information about all inverters configured : fit to one packet
|
||||||
|
metrics += "# TYPE ahoy_solar_inverter_is_available gauge\n";
|
||||||
|
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",
|
||||||
|
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable(mApp->getTimestamp());});
|
||||||
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
metricsStep = metricsStateInverter4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case metricsStateInverter4: // Information about all inverters configured : fit to one packet
|
||||||
|
metrics += "# TYPE ahoy_solar_inverter_is_producing gauge\n";
|
||||||
|
metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",
|
||||||
|
[](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing(mApp->getTimestamp());});
|
||||||
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
|
// Start Realtime Field loop
|
||||||
|
metricsFieldId = FLD_UDC;
|
||||||
|
metricsStep = metricStateRealtimeFieldId;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case metricStateRealtimeFieldId: // Iterate over all defined fields
|
||||||
|
if (metricsFieldId < FLD_LAST_ALARM_CODE) {
|
||||||
|
metrics = "# Info: processing realtime field #"+String(metricsFieldId)+"\n";
|
||||||
|
metricDeclared = false;
|
||||||
|
|
||||||
|
metricsInverterId = 0;
|
||||||
|
metricsStep = metricStateRealtimeInverterId;
|
||||||
} else {
|
} else {
|
||||||
len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends.
|
metrics = "# Info: all realtime fields processed\n";
|
||||||
|
|
||||||
// All realtime data channels processed --> try alarm data
|
|
||||||
metricsStep = metricsStateAlarmData;
|
metricsStep = metricsStateAlarmData;
|
||||||
}
|
}
|
||||||
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case metricsStateAlarmData: // Alarm Info loop
|
case metricStateRealtimeInverterId: // Iterate over all inverters for this field
|
||||||
iv = mSys->getInverterByPos(metricsInverterId);
|
metrics = "";
|
||||||
rec = iv->getRecordStruct(AlarmData);
|
if (metricsInverterId < mSys->getNumInverters()) {
|
||||||
// simple hack : there is only one channel with alarm data
|
// process all channels of this inverter
|
||||||
// TODO: find the right one channel with the alarm id
|
|
||||||
alarmChannelId = 0;
|
iv = mSys->getInverterByPos(metricsInverterId);
|
||||||
// printf("AlarmData Length %d\n",rec->length);
|
if (NULL != iv) {
|
||||||
if (alarmChannelId < rec->length) {
|
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
//uint8_t channel = rec->assign[alarmChannelId].ch;
|
for (metricsChannelId=0; metricsChannelId < rec->length;metricsChannelId++) {
|
||||||
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
|
uint8_t channel = rec->assign[metricsChannelId].ch;
|
||||||
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);
|
// Try inverter channel (channel 0) or any channel with maxPwr > 0
|
||||||
snprintf(val, sizeof(val), "%.3f", iv->getValue(alarmChannelId, rec));
|
if (0 == channel || 0 != iv->config->chMaxPwr[channel-1]) {
|
||||||
len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val);
|
|
||||||
|
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 {
|
} 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
|
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
|
||||||
metricsInverterId++;
|
break;
|
||||||
metricsStep = metricsStateInverter;
|
|
||||||
|
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;
|
break;
|
||||||
|
|
||||||
case metricsStateEnd:
|
case metricsStateEnd:
|
||||||
|
@ -787,6 +844,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());
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
|
|
||||||
ahoy:
|
ahoy:
|
||||||
interval: 5
|
interval: 5
|
||||||
|
transmit_retries: 5
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
filename: 'hoymiles.log'
|
filename: 'hoymiles.log'
|
||||||
# DEBUG, INFO, WARNING, ERROR, FATAL
|
# DEBUG, INFO, WARNING, ERROR, FATAL
|
||||||
level: 'INFO'
|
level: 'INFO'
|
||||||
|
max_log_filesize: 1000000
|
||||||
|
max_log_files: 1
|
||||||
|
|
||||||
sunset:
|
sunset:
|
||||||
disabled: false
|
disabled: false
|
||||||
|
|
|
@ -297,8 +297,8 @@ class InverterPacketFragment:
|
||||||
|
|
||||||
class HoymilesNRF:
|
class HoymilesNRF:
|
||||||
"""Hoymiles NRF24 Interface"""
|
"""Hoymiles NRF24 Interface"""
|
||||||
tx_channel_id = 0
|
tx_channel_id = 2
|
||||||
tx_channel_list = [40]
|
tx_channel_list = [3,23,40,61,75]
|
||||||
rx_channel_id = 0
|
rx_channel_id = 0
|
||||||
rx_channel_list = [3,23,40,61,75]
|
rx_channel_list = [3,23,40,61,75]
|
||||||
rx_channel_ack = False
|
rx_channel_ack = False
|
||||||
|
@ -332,6 +332,12 @@ class HoymilesNRF:
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.next_tx_channel()
|
||||||
|
|
||||||
|
if HOYMILES_TRANSACTION_LOGGING:
|
||||||
|
c_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||||
|
logging.debug(f'{c_datetime} Transmit {len(packet)} bytes channel {self.tx_channel}: {hexify_payload(packet)}')
|
||||||
|
|
||||||
if not txpower:
|
if not txpower:
|
||||||
txpower = self.txpower
|
txpower = self.txpower
|
||||||
|
|
||||||
|
@ -363,13 +369,13 @@ class HoymilesNRF:
|
||||||
"""
|
"""
|
||||||
Receive Packets
|
Receive Packets
|
||||||
|
|
||||||
:param timeout: receive timeout in nanoseconds (default: 12e8)
|
:param timeout: receive timeout in nanoseconds (default: 5e8)
|
||||||
:type timeout: int
|
:type timeout: int
|
||||||
:yields: fragment
|
:yields: fragment
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not timeout:
|
if not timeout:
|
||||||
timeout=12e8
|
timeout=5e8
|
||||||
|
|
||||||
self.radio.setChannel(self.rx_channel)
|
self.radio.setChannel(self.rx_channel)
|
||||||
self.radio.setAutoAck(False)
|
self.radio.setAutoAck(False)
|
||||||
|
@ -415,7 +421,7 @@ class HoymilesNRF:
|
||||||
self.radio.setChannel(self.rx_channel)
|
self.radio.setChannel(self.rx_channel)
|
||||||
self.radio.startListening()
|
self.radio.startListening()
|
||||||
|
|
||||||
time.sleep(0.005)
|
time.sleep(0.004)
|
||||||
|
|
||||||
def next_rx_channel(self):
|
def next_rx_channel(self):
|
||||||
"""
|
"""
|
||||||
|
@ -433,6 +439,15 @@ class HoymilesNRF:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def next_tx_channel(self):
|
||||||
|
"""
|
||||||
|
Select next channel from hop list
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.tx_channel_id = self.tx_channel_id + 1
|
||||||
|
if self.tx_channel_id >= len(self.tx_channel_list):
|
||||||
|
self.tx_channel_id = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tx_channel(self):
|
def tx_channel(self):
|
||||||
"""
|
"""
|
||||||
|
@ -612,10 +627,6 @@ class InverterTransaction:
|
||||||
|
|
||||||
packet = self.tx_queue.pop(0)
|
packet = self.tx_queue.pop(0)
|
||||||
|
|
||||||
if HOYMILES_TRANSACTION_LOGGING:
|
|
||||||
c_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
||||||
logging.debug(f'{c_datetime} Transmit {len(packet)} | {hexify_payload(packet)}')
|
|
||||||
|
|
||||||
self.radio.transmit(packet, txpower=self.txpower)
|
self.radio.transmit(packet, txpower=self.txpower)
|
||||||
|
|
||||||
wait = False
|
wait = False
|
||||||
|
|
|
@ -19,6 +19,7 @@ import yaml
|
||||||
from yaml.loader import SafeLoader
|
from yaml.loader import SafeLoader
|
||||||
import hoymiles
|
import hoymiles
|
||||||
import logging
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
""" Signal Handler """
|
""" Signal Handler """
|
||||||
|
@ -127,6 +128,7 @@ def main_loop(ahoy_config):
|
||||||
dtu_name = ahoy_config.get('dtu', {}).get('name', 'hoymiles-dtu')
|
dtu_name = ahoy_config.get('dtu', {}).get('name', 'hoymiles-dtu')
|
||||||
sunset.sun_status2mqtt(dtu_ser, dtu_name)
|
sunset.sun_status2mqtt(dtu_ser, dtu_name)
|
||||||
loop_interval = ahoy_config.get('interval', 1)
|
loop_interval = ahoy_config.get('interval', 1)
|
||||||
|
transmit_retries = ahoy_config.get('transmit_retries', 5)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
do_init = True
|
do_init = True
|
||||||
|
@ -143,7 +145,7 @@ def main_loop(ahoy_config):
|
||||||
sys.exit(999)
|
sys.exit(999)
|
||||||
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
if hoymiles.HOYMILES_DEBUG_LOGGING:
|
||||||
logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}')
|
logging.info(f'Poll inverter name={inverter["name"]} ser={inverter["serial"]}')
|
||||||
poll_inverter(inverter, dtu_ser, do_init, 3)
|
poll_inverter(inverter, dtu_ser, do_init, transmit_retries)
|
||||||
do_init = False
|
do_init = False
|
||||||
|
|
||||||
if loop_interval > 0:
|
if loop_interval > 0:
|
||||||
|
@ -298,6 +300,8 @@ def init_logging(ahoy_config):
|
||||||
log_config = ahoy_config.get('logging')
|
log_config = ahoy_config.get('logging')
|
||||||
fn = 'hoymiles.log'
|
fn = 'hoymiles.log'
|
||||||
lvl = logging.ERROR
|
lvl = logging.ERROR
|
||||||
|
max_log_filesize = 1000000
|
||||||
|
max_log_files = 1
|
||||||
if log_config:
|
if log_config:
|
||||||
fn = log_config.get('filename', fn)
|
fn = log_config.get('filename', fn)
|
||||||
level = log_config.get('level', 'ERROR')
|
level = log_config.get('level', 'ERROR')
|
||||||
|
@ -311,9 +315,11 @@ def init_logging(ahoy_config):
|
||||||
lvl = logging.ERROR
|
lvl = logging.ERROR
|
||||||
elif level == 'FATAL':
|
elif level == 'FATAL':
|
||||||
lvl = logging.FATAL
|
lvl = logging.FATAL
|
||||||
|
max_log_filesize = log_config.get('max_log_filesize', max_log_filesize)
|
||||||
|
max_log_files = log_config.get('max_log_files', max_log_files)
|
||||||
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
if hoymiles.HOYMILES_TRANSACTION_LOGGING:
|
||||||
lvl = logging.DEBUG
|
lvl = logging.DEBUG
|
||||||
logging.basicConfig(filename=fn, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl)
|
logging.basicConfig(handlers=[RotatingFileHandler(fn, maxBytes=max_log_filesize, backupCount=max_log_files)], format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=lvl)
|
||||||
dtu_name = ahoy_config.get('dtu',{}).get('name','hoymiles-dtu')
|
dtu_name = ahoy_config.get('dtu',{}).get('name','hoymiles-dtu')
|
||||||
logging.info(f'start logging for {dtu_name} with level: {logging.root.level}')
|
logging.info(f'start logging for {dtu_name} with level: {logging.root.level}')
|
||||||
|
|
||||||
|
|
|
@ -515,9 +515,17 @@ class Hm300Decode0B(StatusResponse):
|
||||||
""" reactive power """
|
""" reactive power """
|
||||||
return self.unpack('>H', 20)[0]/10
|
return self.unpack('>H', 20)[0]/10
|
||||||
@property
|
@property
|
||||||
|
def powerfactor(self):
|
||||||
|
""" Powerfactor """
|
||||||
|
return self.unpack('>H', 24)[0]/1000
|
||||||
|
@property
|
||||||
def temperature(self):
|
def temperature(self):
|
||||||
""" Inverter temperature in °C """
|
""" Inverter temperature in °C """
|
||||||
return self.unpack('>h', 26)[0]/10
|
return self.unpack('>h', 26)[0]/10
|
||||||
|
@property
|
||||||
|
def event_count(self):
|
||||||
|
""" Event counter """
|
||||||
|
return self.unpack('>H', 28)[0]
|
||||||
|
|
||||||
class Hm300Decode0C(Hm300Decode0B):
|
class Hm300Decode0C(Hm300Decode0B):
|
||||||
""" 1121-series mirco-inverters status data """
|
""" 1121-series mirco-inverters status data """
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue