Browse Source

Merge branch 'development03' into Zero-Export

pull/1155/head
DanielR92 2 years ago
committed by GitHub
parent
commit
d647316146
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/workflows/compile_development.yml
  2. 4
      .github/workflows/compile_release.yml
  3. 69
      doc/prometheus_ep_description.md
  4. BIN
      pics/PXL_20230824_204200660.jpg
  5. BIN
      pics/PXL_20230901_061927908.jpg
  6. 38
      scripts/convertHtml.py
  7. 14
      src/CHANGES.md
  8. 6
      src/app.cpp
  9. 26
      src/app.h
  10. 10
      src/appInterface.h
  11. 12
      src/config/config.h
  12. 6
      src/config/settings.h
  13. 2
      src/defines.h
  14. 99
      src/hm/hmPayload.h
  15. 4
      src/hm/hmRadio.h
  16. 501
      src/hm/miPayload.h
  17. 6
      src/hms/cmt2300a.h
  18. 21
      src/hms/esp32_3wSpi.h
  19. 9
      src/hms/hmsRadio.h
  20. 2
      src/platformio.ini
  21. 52
      src/plugins/Display/Display.h
  22. 96
      src/plugins/Display/Display_Mono.h
  23. 63
      src/plugins/Display/Display_Mono_128X32.h
  24. 63
      src/plugins/Display/Display_Mono_128X64.h
  25. 61
      src/plugins/Display/Display_Mono_64X48.h
  26. 151
      src/plugins/Display/Display_Mono_84X48.h
  27. 22
      src/plugins/Display/Display_data.h
  28. 8
      src/plugins/Display/Display_ePaper.cpp
  29. 4
      src/plugins/Display/Display_ePaper.h
  30. 84
      src/web/RestApi.h
  31. 8
      src/web/html/index.html
  32. 54
      src/web/html/setup.html
  33. 6
      src/web/html/style.css
  34. 92
      src/web/html/system.html
  35. 2
      src/web/html/visualization.html
  36. 31
      src/web/web.h
  37. 6
      tools/fonts/fontconv.bat
  38. 11
      tools/fonts/u8g2 font-sources.txt
  39. 1328
      tools/fonts/u8g2_font_5x8_symbols_ahoy.bdf
  40. 40
      tools/fonts/u8g2_font_5x8_symbols_ahoy.c_
  41. BIN
      tools/fonts/u8g2_font_5x8_symbols_ahoy.fon
  42. 166
      tools/fonts/u8g2_font_ncenB08_symbols8_ahoy.bdf
  43. 13
      tools/fonts/u8g2_font_ncenB08_symbols8_ahoy.c_
  44. BIN
      tools/fonts/u8g2_font_ncenB08_symbols8_ahoy.fon
  45. 188
      tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.bdf
  46. 14
      tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.c_
  47. BIN
      tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.fon
  48. 16
      tools/fonts/used_fonts.txt

4
.github/workflows/compile_development.yml

@ -42,10 +42,6 @@ jobs:
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Convert HTML files
working-directory: src/web/html
run: python convert.py
- name: Run PlatformIO
run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment opendtufusion

4
.github/workflows/compile_release.yml

@ -46,10 +46,6 @@ jobs:
python -m pip install --upgrade pip
pip install --upgrade platformio
- name: Convert HTML files
working-directory: src/web/html
run: python convert.py
- name: Run PlatformIO
run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment opendtufusion

69
doc/prometheus_ep_description.md

@ -15,37 +15,40 @@ Prometheus metrics provided at `/metrics`.
| channel | Channel (Module) name from setup. Label only available if max power level of module is set to non-zero. Be sure to have a cannel name set in configuration. |
## Exported Metrics
| Metric name | Type | Description | Labels |
|----------------------------------------|---------|--------------------------------------------------------|--------------|
| `ahoy_solar_info` | Gauge | Information about the AhoyDTU device | version, image, devicename |
| `ahoy_solar_uptime` | Counter | Seconds since boot of the AhoyDTU device | devicename |
| `ahoy_solar_rssi_db` | Gauge | Quality of the Wifi STA connection | devicename |
| `ahoy_solar_inverter_info` | Gauge | Information about the configured inverter(s) | name, serial |
| `ahoy_solar_inverter_enabled` | Gauge | Is the inverter enabled? | inverter |
| `ahoy_solar_inverter_is_available` | Gauge | is the inverter available? | inverter |
| `ahoy_solar_inverter_is_producing` | Gauge | Is the inverter producing? | inverter |
| `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter |
| `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter |
| `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter |
| `ahoy_solar_Q_AC_var` | Gauge | AC reactive power[var] | inverter |
| `ahoy_solar_F_AC_hertz` | Gauge | AC frequency [Hz] | inverter |
| `ahoy_solar_PF_AC` | Gauge | AC Power factor | inverter |
| `ahoy_solar_Temp_celsius` | Gauge | Temperature of inverter | inverter |
| `ahoy_solar_ALARM_MES_ID` | Gauge | Alarm message index of inverter | inverter |
| `ahoy_solar_LastAlarmCode` | Gauge | Last alarm code from inverter | inverter |
| `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter |
| `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter |
| `ahoy_solar_P_DC_watt` | Gauge | DC power of inverter [W] | inverter |
| `ahoy_solar_Efficiency_ratio` | Gauge | ration AC Power over DC Power [%] | inverter |
| `ahoy_solar_U_DC_volt` | Gauge | DC voltage of channel [V] | inverter, channel |
| `ahoy_solar_I_DC_ampere` | Gauge | DC current of channel [A] | inverter, channel |
| `ahoy_solar_P_DC_watt` | Gauge | DC power of channel [P] | inverter, channel |
| `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter, channel |
| `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter, channel |
| `ahoy_solar_Irradiation_ratio` | Gauge | ratio DC Power over set maximum power per channel [%] | inverter, channel |
| `ahoy_solar_radio_rx_success` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_rx_fail` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_rx_fail_answer` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_frame_cnt` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_tx_cnt` | Gauge | NRF24 statistic | |
| Metric name | Type | Description | Labels |
|----------------------------------------------|---------|----------------------------------------------------------|--------------|
| `ahoy_solar_info` | Gauge | Information about the AhoyDTU device | version, image, devicename |
| `ahoy_solar_uptime` | Counter | Seconds since boot of the AhoyDTU device | devicename |
| `ahoy_solar_rssi_db` | Gauge | Quality of the Wifi STA connection | devicename |
| `ahoy_solar_inverter_info` | Gauge | Information about the configured inverter(s) | name, serial |
| `ahoy_solar_inverter_enabled` | Gauge | Is the inverter enabled? | inverter |
| `ahoy_solar_inverter_is_available` | Gauge | is the inverter available? | inverter |
| `ahoy_solar_inverter_is_producing` | Gauge | Is the inverter producing? | inverter |
| `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter |
| `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter |
| `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter |
| `ahoy_solar_Q_AC_var` | Gauge | AC reactive power[var] | inverter |
| `ahoy_solar_F_AC_hertz` | Gauge | AC frequency [Hz] | inverter |
| `ahoy_solar_PF_AC` | Gauge | AC Power factor | inverter |
| `ahoy_solar_Temp_celsius` | Gauge | Temperature of inverter | inverter |
| `ahoy_solar_ALARM_MES_ID` | Gauge | Alarm message index of inverter | inverter |
| `ahoy_solar_LastAlarmCode` | Gauge | Last alarm code from inverter | inverter |
| `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter,channel |
| `ahoy_solar_YieldDay_wattHours_total` | Counter | Energy converted to AC per day [Wh] for all moduls | inverter |
| `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter,channel |
| `ahoy_solar_YieldTotal_kilowattHours_total` | Counter | Energy converted to AC since reset [kWh] for all modules | inverter |
| `ahoy_solar_P_DC_watt` | Gauge | DC power of inverter [W] | inverter |
| `ahoy_solar_Efficiency_ratio` | Gauge | ration AC Power over DC Power [%] | inverter |
| `ahoy_solar_U_DC_volt` | Gauge | DC voltage of channel [V] | inverter, channel |
| `ahoy_solar_I_DC_ampere` | Gauge | DC current of channel [A] | inverter, channel |
| `ahoy_solar_P_DC_watt` | Gauge | DC power of channel [P] | inverter, channel |
| `ahoy_solar_P_DC_watt_total` | Gauge | DC power of inverter [P] | inverter |
| `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter, channel |
| `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter, channel |
| `ahoy_solar_Irradiation_ratio` | Gauge | ratio DC Power over set maximum power per channel [%] | inverter, channel |
| `ahoy_solar_radio_rx_success` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_rx_fail` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_rx_fail_answer` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_frame_cnt` | Gauge | NRF24 statistic | |
| `ahoy_solar_radio_tx_cnt` | Gauge | NRF24 statistic | |

BIN
pics/PXL_20230824_204200660.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
pics/PXL_20230901_061927908.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

38
src/web/html/convert.py → scripts/convertHtml.py

@ -6,6 +6,7 @@ import shutil
from datetime import date
from pathlib import Path
import subprocess
Import("env")
def get_git_sha():
@ -60,13 +61,41 @@ def htmlParts(file, header, nav, footer, version):
link = '<a target="_blank" href="https://github.com/lumapu/ahoy/commits/' + get_git_sha() + '">GIT SHA: ' + get_git_sha() + ' :: ' + version + '</a>'
p = p.replace("{#VERSION}", version)
p = p.replace("{#VERSION_GIT}", link)
# remove if - endif ESP32
p = checkIf(p)
f = open("tmp/" + file, "w")
f.write(p);
f.close();
return p
def checkIf(data):
if (env['PIOENV'][0:5] == "esp32") or env['PIOENV'][0:4] == "open":
data = data.replace("<!--IF_ESP32-->", "")
data = data.replace("<!--ENDIF_ESP32-->", "")
data = data.replace("/*IF_ESP32*/", "")
data = data.replace("/*ENDIF_ESP32*/", "")
else:
while 1:
start = data.find("<!--IF_ESP32-->")
end = data.find("<!--ENDIF_ESP32-->")+18
if -1 == start:
break
else:
data = data[0:start] + data[end:]
while 1:
start = data.find("/*IF_ESP32*/")
end = data.find("/*ENDIF_ESP32*/")+15
if -1 == start:
break
else:
data = data[0:start] + data[end:]
return data
def convert2Header(inFile, version):
fileType = inFile.split(".")[1]
fileType = inFile.split(".")[1]
define = inFile.split(".")[0].upper()
define2 = inFile.split(".")[1].upper()
inFileVarName = inFile.replace(".", "_")
@ -118,9 +147,7 @@ def convert2Header(inFile, version):
f.close()
# delete all files in the 'h' dir
wd = 'h'
if os.getcwd()[-4:] != "html":
wd = "web/html/" + wd
wd = 'web/html/h'
if os.path.exists(wd):
for f in os.listdir(wd):
@ -131,8 +158,7 @@ if os.path.exists(wd):
os.remove(os.path.join(wd, f))
# grab all files with following extensions
if os.getcwd()[-4:] != "html":
os.chdir('./web/html')
os.chdir('./web/html')
types = ('*.html', '*.css', '*.js', '*.ico') # the tuple of file types
files_grabbed = []
for files in types:

14
src/CHANGES.md

@ -1,5 +1,19 @@
# Development Changes
## 0.7.50 - 2023-09-12
* moved MqTT info to `system`
* added CMT info for ESP32 devices
* improved CMT settings, now `SCLK` and `SDIO` are configurable #1046, #1150
* changed `Power-Limit` in live-view to `Active Power Control`
* increase length of update file selector #1132
## 0.7.49 - 2023-09-11
* merge PR: symbolic icons for mono displays, PR #1136
* merge MI code restructuring PR #1145
* merge Prometheus PR #1148
* add option to strip webUI for ESP8266 (reduce code size, add ESP32 special features; `IF_ESP32` directives)
* started to get CMT info into `system` - not finished
## 0.7.48 - 2023-09-10
* fix SSD1309 2.42" display pinout
* improved setup page: save and delete of inverters

6
src/app.cpp

@ -42,7 +42,7 @@ void app::setup() {
}
#if defined(ESP32)
if(mConfig->cmt.enabled) {
mCmtRadio.setup(mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb, false);
mCmtRadio.setup(mConfig->cmt.pinSclk, mConfig->cmt.pinSdio, mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb, false);
mCmtRadio.enableDebug();
}
#endif
@ -111,11 +111,11 @@ void app::setup() {
mWeb.setup(this, &mSys, mConfig);
mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0);
mApi.setup(this, &mSys, &mNrfRadio, mWeb.getWebSrvPtr(), mConfig);
mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig);
// Plugins
if (mConfig->plugin.display.type != 0)
mDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, mVersion);
mDisplay.setup(this, &mConfig->plugin.display, &mSys, &mNrfRadio, &mTimestamp);
mPubSerial.setup(mConfig, &mSys, &mTimestamp);

26
src/app.h

@ -9,9 +9,9 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include "appInterface.h"
#include "config/settings.h"
#include "defines.h"
#include "appInterface.h"
#include "hm/hmPayload.h"
#include "hm/hmSystem.h"
#include "hm/hmRadio.h"
@ -45,21 +45,23 @@ typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
typedef HmPayload<HmSystemType, HmRadio<>> PayloadType;
typedef MiPayload<HmSystemType, HmRadio<>> MiPayloadType;
#ifdef ESP32
typedef CmtRadio<esp32_3wSpi<>> CmtRadioType;
typedef CmtRadio<esp32_3wSpi> CmtRadioType;
typedef HmsPayload<HmSystemType, CmtRadioType> HmsPayloadType;
#endif
typedef Web<HmSystemType> WebType;
typedef RestApi<HmSystemType, HmRadio<>> RestApiType;
typedef RestApi<HmSystemType> RestApiType;
typedef PubMqtt<HmSystemType> PubMqttType;
typedef PubSerial<HmSystemType> PubSerialType;
// PLUGINS
#include "plugins/Display/Display.h"
#include "plugins/zeroExport/zeroExport.h"
#include "plugins/Display/Display_data.h"
typedef Display<HmSystemType, HmRadio<>> DisplayType;
typedef Display<HmSystemType> DisplayType;
#include "plugins/zeroExport/zeroExport.h"
typedef ZeroExport<HmSystemType> ZeroExportType;
class app : public IApp, public ah::Scheduler {
public:
app();
@ -77,7 +79,17 @@ class app : public IApp, public ah::Scheduler {
void handleIntr(void) {
mNrfRadio.handleIntr();
}
void* getRadioObj(bool nrf) {
if(nrf)
return (void*)&mNrfRadio;
else {
#ifdef ESP32
return (void*)&mCmtRadio;
#else
return NULL;
#endif
}
}
#ifdef ESP32
void handleHmsIntr(void) {
mCmtRadio.handleIntr();
@ -369,6 +381,8 @@ class app : public IApp, public ah::Scheduler {
// plugins
DisplayType mDisplay;
DisplayData mDispData;
ZeroExportType mzExport;
};

10
src/appInterface.h

@ -14,6 +14,11 @@
#include "ESPAsyncWebServer.h"
#endif
//#include "hms/hmsRadio.h"
#if defined(ESP32)
//typedef CmtRadio<esp32_3wSpi<>> CmtRadioType;
#endif
// abstract interface to App. Make members of App accessible from child class
// like web or API without forward declaration
class IApp {
@ -62,6 +67,11 @@ class IApp {
virtual void getNrfRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
//virtual void getCmtRadioCounters(uint32_t *sendCnt, uint32_t *retransmits) = 0;
virtual void* getRadioObj(bool nrf) = 0;
#if defined(ESP32)
//virtual const CmtRadioType& getCmtRadioObj(void) const = 0;
#endif
};
#endif /*__IAPP_H__*/

12
src/config/config.h

@ -74,15 +74,21 @@
#define DEF_NRF_IRQ_PIN 16
#endif
#ifndef DEF_NRF_MISO_PIN
#define DEF_NRF_MISO_PIN 19
#define DEF_NRF_MISO_PIN 12
#endif
#ifndef DEF_NRF_MOSI_PIN
#define DEF_NRF_MOSI_PIN 23
#define DEF_NRF_MOSI_PIN 13
#endif
#ifndef DEF_NRF_SCLK_PIN
#define DEF_NRF_SCLK_PIN 18
#define DEF_NRF_SCLK_PIN 14
#endif
#ifndef DEF_CMT_SCLK
#define DEF_CMT_SCLK 18
#endif
#ifndef DEF_CMT_SDIO
#define DEF_CMT_SDIO 23
#endif
#ifndef DEF_CMT_CSB
#define DEF_CMT_CSB 27
#endif

6
src/config/settings.h

@ -94,6 +94,8 @@ typedef struct {
typedef struct {
bool enabled;
uint8_t pinSclk;
uint8_t pinSdio;
uint8_t pinCsb;
uint8_t pinFcsb;
uint8_t pinIrq;
@ -419,10 +421,14 @@ class settings {
mCfg.nrf.enabled = true;
#if defined(ESP32)
mCfg.cmt.pinSclk = DEF_CMT_SCLK;
mCfg.cmt.pinSdio = DEF_CMT_SDIO;
mCfg.cmt.pinCsb = DEF_CMT_CSB;
mCfg.cmt.pinFcsb = DEF_CMT_FCSB;
mCfg.cmt.pinIrq = DEF_CMT_IRQ;
#else
mCfg.cmt.pinSclk = DEF_PIN_OFF;
mCfg.cmt.pinSdio = DEF_PIN_OFF;
mCfg.cmt.pinCsb = DEF_PIN_OFF;
mCfg.cmt.pinFcsb = DEF_PIN_OFF;
mCfg.cmt.pinIrq = DEF_PIN_OFF;

2
src/defines.h

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

99
src/hm/hmPayload.h

@ -232,8 +232,9 @@ class HmPayload {
}
if (!mPayload[iv->id].complete) {
bool crcPass, pyldComplete;
crcPass = build(iv->id, &pyldComplete);
bool crcPass, pyldComplete, fastNext;
crcPass = build(iv, &pyldComplete, &fastNext);
if (!crcPass && !pyldComplete) { // payload not complete
if ((mPayload[iv->id].requested) && (retransmit)) {
if (mPayload[iv->id].retransmits < mMaxRetrans) {
@ -260,10 +261,12 @@ class HmPayload {
} else {
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) {
if (mPayload[iv->id].len[i] == 0) {
DPRINT_IVID(DBG_WARN, iv->id);
DBGPRINT(F("Frame "));
DBGPRINT(String(i + 1));
DBGPRINTLN(F(" missing: Request Retransmit"));
if (mSerialDebug) {
DPRINT_IVID(DBG_WARN, iv->id);
DBGPRINT(F("Frame "));
DBGPRINT(String(i + 1));
DBGPRINTLN(F(" missing: Request Retransmit"));
}
mRadio->sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
break; // only request retransmit one frame per loop
}
@ -276,20 +279,25 @@ class HmPayload {
} else if(!crcPass && pyldComplete) { // crc error on complete Payload
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit"));
mPayload[iv->id].txCmd = iv->getQueuedCmd();
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
if (mSerialDebug) {
DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit"));
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
}
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
}
} else { // payload complete
DPRINT(DBG_INFO, F("procPyld: cmd: 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
//DPRINT(DBG_DEBUG, F("procPyld: txid: 0x"));
//DBGHEXLN(mPayload[iv->id].txId);
DPRINT(DBG_DEBUG, F("procPyld: max: "));
DPRINTLN(DBG_DEBUG, String(mPayload[iv->id].maxPackId));
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("procPyld: cmd: 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
//DPRINT(DBG_DEBUG, F("procPyld: txid: 0x"));
//DBGHEXLN(mPayload[iv->id].txId);
DPRINT(DBG_DEBUG, F("procPyld: max: "));
DPRINTLN(DBG_DEBUG, String(mPayload[iv->id].maxPackId));
}
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
mPayload[iv->id].complete = true;
@ -347,13 +355,25 @@ class HmPayload {
yield();
}
}
if( (InverterDevInform_All == mPayload[iv->id].txCmd) && (mHighPrioIv == NULL) ) // process next request immediately if possible
mHighPrioIv = iv;
if (fastNext) {
uint8_t cmd = iv->getQueuedCmd();
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("fast mode "));
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
mStat->rxSuccess++;
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
mPayload[iv->id].txCmd = cmd;
}
} else {
DPRINT(DBG_ERROR, F("plausibility check failed, expected "));
DBGPRINT(String(rec->pyldLen));
DBGPRINTLN(F(" bytes"));
if (mSerialDebug) {
DPRINT(DBG_ERROR, F("plausibility check failed, expected "));
DBGPRINT(String(rec->pyldLen));
DBGPRINTLN(F(" bytes"));
}
mStat->rxFail++;
}
@ -370,33 +390,46 @@ class HmPayload {
(mCbPayload)(val, iv);
}
bool build(uint8_t id, bool *complete) {
bool build(Inverter<> *iv, bool *complete, bool *fastNext ) {
DPRINTLN(DBG_VERBOSE, F("build"));
uint16_t crc = 0xffff, crcRcv = 0x0000;
if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES)
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
if (mPayload[iv->id].maxPackId > MAX_PAYLOAD_ENTRIES)
mPayload[iv->id].maxPackId = MAX_PAYLOAD_ENTRIES;
// check if all fragments are there
*complete = true;
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
if(mPayload[id].len[i] == 0)
*fastNext = false;
for (uint8_t i = 0; i < mPayload[iv->id].maxPackId; i++) {
if(mPayload[iv->id].len[i] == 0) {
*complete = false;
}
}
if(!*complete)
return false;
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
if (mPayload[id].len[i] > 0) {
if (i == (mPayload[id].maxPackId - 1)) {
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc);
crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]);
for (uint8_t i = 0; i < mPayload[iv->id].maxPackId; i++) {
if (mPayload[iv->id].len[i] > 0) {
if (i == (mPayload[iv->id].maxPackId - 1)) {
crc = ah::crc16(mPayload[iv->id].data[i], mPayload[iv->id].len[i] - 2, crc);
crcRcv = (mPayload[iv->id].data[i][mPayload[iv->id].len[i] - 2] << 8) | (mPayload[iv->id].data[i][mPayload[iv->id].len[i] - 1]);
} else
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
crc = ah::crc16(mPayload[iv->id].data[i], mPayload[iv->id].len[i], crc);
}
yield();
}
return (crc == crcRcv) ? true : false;
//return (crc == crcRcv) ? true : false;
if (crc != crcRcv)
return false;
//requests to cause the next request to be executed immediately
if ( mPayload[iv->id].txCmd < 11 || mPayload[iv->id].txCmd > 18 ) {
*fastNext = true;
//DPRINT_IVID(DBG_INFO, iv->id);
//DBGPRINTLN(F("fast next req"));
}
return true;
}
void reset(uint8_t id) {

4
src/hm/hmRadio.h

@ -272,8 +272,8 @@ class HmRadio {
isLastPackage = true; // response from dev control command
}
}
yield();
}
yield();
}
return isLastPackage;
}

501
src/hm/miPayload.h

@ -26,10 +26,8 @@ typedef struct {
uint8_t invId;
uint8_t retransmits;
bool gotFragment;
/*
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
uint8_t maxPackId;
bool lastFound;*/
uint8_t rtrRes; // for limiting resets
uint8_t multi_parts; // for quality
} miPayload_t;
@ -91,21 +89,22 @@ class MiPayload {
DPRINT_IVID(DBG_INFO, iv->id);
if (!mPayload[iv->id].gotFragment) {
mStat->rxFailNoAnser++; // got nothing
if (mSerialDebug)
DBGPRINTLN(F("enqueued cmd failed/timeout"));
if (mSerialDebug)
DBGPRINTLN(F("enqueued cmd failed/timeout"));
} else {
mStat->rxFail++; // got "fragments" (part of the required messages)
// but no complete set of responses
if (mSerialDebug) {
if (mSerialDebug) {
DBGPRINT(F("no complete Payload received! (retransmits: "));
DBGPRINT(String(mPayload[iv->id].retransmits));
DBGPRINTLN(F(")"));
DBGPRINT(String(mPayload[iv->id].retransmits));
DBGPRINTLN(F(")"));
}
}
}
mPayload[iv->id].complete = true;
iv->setQueuedCmdFinished(); // command failed
}
}
}
}
reset(iv->id);
mPayload[iv->id].requested = true;
@ -131,12 +130,9 @@ class MiPayload {
mPayload[iv->id].limitrequested = true;
iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // try to read back power limit
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit is not possible with MI
} else {
uint8_t cmd = iv->getQueuedCmd();
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
uint8_t cmd2 = cmd;
if ( cmd == SystemConfigPara ) { //0x05 for HM-types
if (!mPayload[iv->id].limitrequested) { // only do once at startup
@ -148,12 +144,15 @@ class MiPayload {
}
if (cmd == 0x01 || cmd == SystemConfigPara ) { //0x1 and 0x05 for HM-types
cmd = 0x0f; // for MI, these seem to make part of the Polling the device software and hardware version number command
cmd2 = cmd == SystemConfigPara ? 0x01 : 0x00; //perhaps we can only try to get second frame?
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
} else {
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
};
cmd = 0x0f; // for MI, these seem to make part of polling the device software and hardware version number command
}
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
mPayload[iv->id].txCmd = cmd;
if (iv->type == INV_TYPE_1CH || iv->type == INV_TYPE_2CH) {
@ -161,11 +160,10 @@ class MiPayload {
mPayload[iv->id].stsAB[CH1] = false;
mPayload[iv->id].dataAB[CH0] = false;
mPayload[iv->id].stsAB[CH0] = false;
}
if (iv->type == INV_TYPE_2CH) {
mPayload[iv->id].dataAB[CH2] = false;
mPayload[iv->id].stsAB[CH2] = false;
if (iv->type == INV_TYPE_2CH) {
mPayload[iv->id].dataAB[CH2] = false;
mPayload[iv->id].stsAB[CH2] = false;
}
}
}
}
@ -190,125 +188,8 @@ class MiPayload {
else if (p->packet[0] == ( 0x0f + ALL_FRAMES)) {
// MI response from get hardware information request
record_t<> *rec = iv->getRecordStruct(InverterDevInform_All); // choose the record structure
rec->ts = mPayload[iv->id].ts;
mPayload[iv->id].gotFragment = true;
if(mHighPrioIv == NULL) // process next request immediately if possible
mHighPrioIv = iv;
/*
Polling the device software and hardware version number command
start byte Command word routing address target address User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12]
0x7e 0x0f xx xx xx xx YY YY YY YY 0x00 CRC 0x7f
Command Receipt - First Frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x00 USFWBuild_VER APPFWBuild_VER APPFWBuild_YYYY APPFWBuild_MMDD APPFWBuild_HHMM APPFW_PN HW_VER CRC 0x7f
Command Receipt - Second Frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x01 HW_PN HW_FB_TLmValue HW_FB_ReSPRT HW_GridSamp_ResValule HW_ECapValue Matching_APPFW_PN CRC 0x7f
Command receipt - third frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[15] byte[16] byte[17] byte[18]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x12 APPFW_MINVER HWInfoAddr PNInfoCRC_gusv PNInfoCRC_gusv CRC 0x7f
*/
/*
case InverterDevInform_All:
rec->length = (uint8_t)(HMINFO_LIST_LEN);
rec->assign = (byteAssign_t *)InfoAssignment;
rec->pyldLen = HMINFO_PAYLOAD_LEN;
break;
const byteAssign_t InfoAssignment[] = {
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 },
{ FLD_BOOTLOADER_VER, UNIT_NONE, CH0, 8, 2, 1 }
};
*/
if ( p->packet[9] == 0x00 ) {//first frame
//FLD_FW_VERSION
for (uint8_t i = 0; i < 5; i++) {
iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1);
}
iv->isConnected = true;
mPayload[iv->id].gotFragment = true;
if(mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DPRINT(DBG_INFO,F("HW_VER is "));
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
}
record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
rec->ts = mPayload[iv->id].ts;
iv->setValue(1, rec, (uint32_t) ((p->packet[24] << 8) + p->packet[25])/1);
//notify(InverterDevInform_All, iv);
//28737
} else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10
DPRINT_IVID(DBG_INFO, iv->id);
if ( p->packet[9] == 0x01 ) {
DBGPRINTLN(F("got 2nd frame (hw info)"));
/* according to xlsx (different start byte -1!)
byte[11] to byte[14] HW_PN
byte[15] byte[16] HW_FB_TLmValue
byte[17] byte[18] HW_FB_ReSPRT
byte[19] byte[20] HW_GridSamp_ResValule
byte[21] byte[22] HW_ECapValue
byte[23] to byte[26] Matching_APPFW_PN
*/
DPRINT(DBG_INFO,F("HW_PartNo "));
DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13]));
mPayload[iv->id].gotFragment = true;
record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
rec->ts = mPayload[iv->id].ts;
iv->setValue(0, rec, (uint32_t) ((((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])/1);
if(mSerialDebug) {
DPRINT(DBG_INFO,F("HW_FB_TLmValue "));
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
DPRINT(DBG_INFO,F("HW_FB_ReSPRT "));
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
DPRINT(DBG_INFO,F("HW_GridSamp_ResValule "));
DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19]));
DPRINT(DBG_INFO,F("HW_ECapValue "));
DBGPRINTLN(String((p->packet[20] << 8) + p->packet[21]));
DPRINT(DBG_INFO,F("Matching_APPFW_PN "));
DBGPRINTLN(String((uint32_t) (((p->packet[22] << 8) | p->packet[23]) << 8 | p->packet[24]) << 8 | p->packet[25]));
}
//notify(InverterDevInform_Simple, iv);
notify(InverterDevInform_All, iv);
} else {
DBGPRINTLN(F("3rd gen. inverter!")); // see table in OpenDTU code, DevInfoParser.cpp devInfo[]
}
} else if ( p->packet[9] == 0x12 ) {//3rd frame
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("got 3rd frame (hw info)"));
/* according to xlsx (different start byte -1!)
byte[11] byte[12] APPFW_MINVER
byte[13] byte[14] HWInfoAddr
byte[15] byte[16] PNInfoCRC_gusv
byte[15] byte[16] PNInfoCRC_gusv
*/
if(mSerialDebug) {
DPRINT(DBG_INFO,F("APPFW_MINVER "));
DBGPRINTLN(String((p->packet[10] << 8) + p->packet[11]));
DPRINT(DBG_INFO,F("HWInfoAddr "));
DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13]));
DPRINT(DBG_INFO,F("PNInfoCRC_gusv "));
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
DPRINT(DBG_INFO,F("PNInfoCRC_gusv (pt. 2?) "));
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
}
iv->setQueuedCmdFinished();
mPayload[iv->id].complete = true;
mStat->rxSuccess++;
}
miHwDecode(iv, p);
mPayload[iv->id].txId = p->packet[0];
} else if ( p->packet[0] == (TX_REQ_INFO + ALL_FRAMES) // response from get information command
|| (p->packet[0] == 0xB6 && mPayload[iv->id].txCmd != 0x36)) { // strange short response from MI-1500 3rd gen; might be misleading!
@ -326,23 +207,7 @@ const byteAssign_t InfoAssignment[] = {
iv->ivGen = IV_HM;
iv->setQueuedCmdFinished();
iv->clearCmdQueue();
//DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX));
/* (old else-tree)
if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) {^
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], p->len - 11);
mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->len - 11;
mPayload[iv->id].gotFragment = true;
}
if ((*pid & ALL_FRAMES) == ALL_FRAMES) {
// Last packet
if (((*pid & 0x7f) > mPayload[iv->id].maxPackId) || (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)) {
mPayload[iv->id].maxPackId = (*pid & 0x7f);
if (*pid > 0x81)
mPayload[iv->id].lastFound = true;
}
}*/
}
//}
} else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES ) // response from dev control command
|| p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES -1)) { // response from DRED instruction
DPRINT_IVID(DBG_DEBUG, iv->id);
@ -354,23 +219,27 @@ const byteAssign_t InfoAssignment[] = {
if ((p->packet[9] == 0x5a) && (p->packet[10] == 0x5a)) {
mApp->setMqttPowerLimitAck(iv);
iv->powerLimitAck = true;
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("has accepted power limit set point "));
DBGPRINT(String(iv->powerLimit[0]));
DBGPRINT(F(" with PowerLimitControl "));
DBGPRINTLN(String(iv->powerLimit[1]));
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("has accepted power limit set point "));
DBGPRINT(String(iv->powerLimit[0]));
DBGPRINT(F(" with PowerLimitControl "));
DBGPRINTLN(String(iv->powerLimit[1]));
}
iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
}
iv->devControlCmd = Init;
} else { // some other response; copied from hmPayload:process; might not be correct to do that here!!!
DPRINT(DBG_INFO, F("procPyld: cmd: 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
DPRINT(DBG_INFO, F("procPyld: txid: 0x"));
DBGHEXLN(mPayload[iv->id].txId);
//DPRINT(DBG_DEBUG, F("procPyld: max: "));
//DBGPRINTLN(String(mPayload[iv->id].maxPackId));
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO,iv->id);
DBGPRINT(F("procPyld: cmd: 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
DPRINT_IVID(DBG_INFO,iv->id);
DBGPRINT(F("procPyld: txid: 0x"));
DBGHEXLN(mPayload[iv->id].txId);
}
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
mPayload[iv->id].complete = true;
@ -379,11 +248,6 @@ const byteAssign_t InfoAssignment[] = {
memset(payload, 0, 128);
/*for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) {
memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
payloadLen += (mPayload[iv->id].len[i]);
yield();
}*/
payloadLen -= 2;
if (mSerialDebug) {
@ -432,7 +296,7 @@ const byteAssign_t InfoAssignment[] = {
if (NULL == iv)
continue; // skip to next inverter
if (IV_HM == iv->ivGen) // only process MI inverters
if (IV_MI != iv->ivGen) // only process MI inverters
continue; // skip to next inverter
if ( !mPayload[iv->id].complete &&
@ -443,24 +307,17 @@ const byteAssign_t InfoAssignment[] = {
(mPayload[iv->id].txId != (0x11 + ALL_FRAMES)) &&
(mPayload[iv->id].txId != (0x88)) &&
(mPayload[iv->id].txId != (0x92)) &&
(mPayload[iv->id].txId != 0 )) {
(mPayload[iv->id].txId != 0 &&
mPayload[iv->id].txCmd != 0x0f)) {
// no processing needed if txId is not one of 0x95, 0x88, 0x89, 0x91, 0x92 or response to 0x36ff
mPayload[iv->id].complete = true;
continue; // skip to next inverter
}
//delayed next message?
//mPayload[iv->id].skipfirstrepeat++;
/*if (mPayload[iv->id].skipfirstrepeat) {
mPayload[iv->id].skipfirstrepeat = 0; //reset counter
continue; // skip to next inverter
}*/
if (!mPayload[iv->id].complete) {
//DPRINTLN(DBG_INFO, F("Pyld incompl code")); //info for testing only
bool crcPass, pyldComplete;
crcPass = build(iv->id, &pyldComplete);
if (!crcPass && !pyldComplete) { // payload not complete
bool gotAllMsgParts, pyldComplete, fastNext;
gotAllMsgParts = build(iv, &pyldComplete, &fastNext);
if (!gotAllMsgParts && !pyldComplete) { // payload not complete
if ((mPayload[iv->id].requested) && (retransmit)) {
if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) {
// This is required to prevent retransmissions without answer.
@ -482,8 +339,7 @@ const byteAssign_t InfoAssignment[] = {
} else if ( cmd == 0x0f ) {
//hard/firmware request
mRadio->sendCmdPacket(iv->radioId.u64, 0x0f, 0x00, true, false);
//iv->setQueuedCmdFinished();
//cmd = iv->getQueuedCmd();
mPayload[id].multi_parts = 0;
} else {
bool change = false;
if ( cmd >= 0x36 && cmd < 0x39 ) { // MI-1500 Data command
@ -496,7 +352,8 @@ const byteAssign_t InfoAssignment[] = {
else if (!mPayload[iv->id].stsAB[CH2] || !mPayload[iv->id].dataAB[CH2] ) {
cmd = 0x11;
change = true;
mPayload[iv->id].retransmits = 0; //reset counter
if (mPayload[iv->id].rtrRes < 3) //only get back to first channel twice
mPayload[iv->id].retransmits = 0; //reset counter
}
}
} else if ( cmd == 0x11) {
@ -510,32 +367,48 @@ const byteAssign_t InfoAssignment[] = {
DPRINT_IVID(DBG_INFO, iv->id);
if (change) {
DBGPRINT(F("next request is"));
//mPayload[iv->id].skipfirstrepeat = 0;
mPayload[iv->id].txCmd = cmd;
mPayload[iv->id].rtrRes++;
} else {
DBGPRINT(F("sth."));
DBGPRINT(F(" missing: Request Retransmit"));
}
DBGPRINT(F(" 0x"));
DBGHEXLN(cmd);
mPayload[id].multi_parts = 0;
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd, true, false);
yield();
}
}
}
}
} else if(!crcPass && pyldComplete) { // crc error on complete Payload
} else if(!gotAllMsgParts && pyldComplete) { // crc error on complete Payload
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
DPRINT_IVID(DBG_WARN, iv->id);
DBGPRINTLN(F("CRC Error: Request Complete Retransmit"));
mPayload[iv->id].txCmd = iv->getQueuedCmd();
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
mPayload[id].multi_parts = 0;
if (mSerialDebug) {
DPRINT_IVID(DBG_WARN, iv->id);
DBGPRINTLN(F("CRC Error: Request Complete Retransmit"));
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
}
mRadio->sendCmdPacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].txCmd, false, false);
}
} else {
if (fastNext) {
uint8_t cmd = iv->getQueuedCmd();
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("fast mode "));
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
mStat->rxSuccess++;
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
mPayload[iv->id].txCmd = cmd;
}
}
}
@ -550,19 +423,15 @@ const byteAssign_t InfoAssignment[] = {
}
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t stschan = CH1) {
//DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") status msg 0x") + String(p->packet[0], HEX));
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure
rec->ts = mPayload[iv->id].ts;
mPayload[iv->id].gotFragment = true;
mPayload[iv->id].multi_parts += 3;
mPayload[iv->id].txId = p->packet[0];
miStsConsolidate(iv, stschan, rec, p->packet[10], p->packet[12], p->packet[9], p->packet[11]);
mPayload[iv->id].stsAB[stschan] = true;
if (mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].stsAB[CH2])
mPayload[iv->id].stsAB[CH0] = true;
//mPayload[iv->id].skipfirstrepeat = 1;
if (mPayload[iv->id].stsAB[CH0] && mPayload[iv->id].dataAB[CH0] && !mPayload[iv->id].complete) {
miComplete(iv);
}
}
void miStsConsolidate(Inverter<> *iv, uint8_t stschan, record_t<> *rec, uint8_t uState, uint8_t uEnum, uint8_t lState = 0, uint8_t lEnum = 0) {
@ -588,10 +457,12 @@ const byteAssign_t InfoAssignment[] = {
uint16_t prntsts = statusMi == 3 ? 1 : statusMi;
if ( statusMi != mPayload[iv->id].sts[stschan] ) { //sth.'s changed?
mPayload[iv->id].sts[stschan] = statusMi;
DPRINT(DBG_WARN, F("Status change for CH"));
DBGPRINT(String(stschan)); DBGPRINT(F(" ("));
DBGPRINT(String(prntsts)); DBGPRINT(F("): "));
DBGPRINTLN(iv->getAlarmStr(prntsts));
if (mSerialDebug) {
DPRINT(DBG_WARN, F("New state on CH"));
DBGPRINT(String(stschan)); DBGPRINT(F(" ("));
DBGPRINT(String(prntsts)); DBGPRINT(F("): "));
DBGPRINTLN(iv->getAlarmStr(prntsts));
}
}
if ( !mPayload[iv->id].sts[0] || prntsts < mPayload[iv->id].sts[0] ) {
@ -599,12 +470,13 @@ const byteAssign_t InfoAssignment[] = {
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, prntsts);
}
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]){
if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]) {
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; // seems there's no status per channel in 3rd gen. models?!?
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("alarm ID incremented to "));
DBGPRINTLN(String(iv->alarmMesIndex));
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("alarm ID incremented to "));
DBGPRINTLN(String(iv->alarmMesIndex));
}
}
/*if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0;
@ -625,6 +497,7 @@ const byteAssign_t InfoAssignment[] = {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser
rec->ts = mPayload[iv->id].ts;
mPayload[iv->id].gotFragment = true;
mPayload[iv->id].multi_parts += 4;
uint8_t datachan = ( p->packet[0] == 0x89 || p->packet[0] == (0x36 + ALL_FRAMES) ) ? CH1 :
( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
@ -653,50 +526,23 @@ const byteAssign_t InfoAssignment[] = {
}
if (p->packet[0] >= (0x36 + ALL_FRAMES) ) {
/*For MI1500:
if (MI1500) {
STAT = (uint8_t)(p->packet[25] );
FCNT = (uint8_t)(p->packet[26]);
FCODE = (uint8_t)(p->packet[27]);
}*/
/*uint16_t status = (uint8_t)(p->packet[23]);
mPayload[iv->id].sts[datachan] = status;
if ( !mPayload[iv->id].sts[0] || status < mPayload[iv->id].sts[0]) {
mPayload[iv->id].sts[0] = status;
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, status);
}*/
miStsConsolidate(iv, datachan, rec, p->packet[23], p->packet[24]);
if (p->packet[0] < (0x39 + ALL_FRAMES) ) {
mPayload[iv->id].txCmd++;
mPayload[iv->id].retransmits = 0; // reserve retransmissions for each response
mPayload[iv->id].complete = false;
} else {
miComplete(iv);
}
}
/*
if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0;
uint16_t code;
uint32_t start, end;
while(1) {
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end);
if(0 == code)
break;
if (NULL != mCbAlarm)
(mCbAl { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC },
}*/
//if ( mPayload[iv->id].complete || //4ch device
if ( p->packet[0] == (0x39 + ALL_FRAMES) || //4ch device - last message
(iv->type != INV_TYPE_4CH //other devices
&& mPayload[iv->id].dataAB[CH0]
&& mPayload[iv->id].stsAB[CH0])) {
miComplete(iv);
}
}
void miComplete(Inverter<> *iv) {
@ -729,51 +575,166 @@ const byteAssign_t InfoAssignment[] = {
notify(RealTimeRunData_Debug, iv);
}
bool build(uint8_t id, bool *complete) {
bool build(Inverter<> *iv, bool *complete, bool *fastNext ) {
DPRINTLN(DBG_VERBOSE, F("build"));
// check if all messages are there
*complete = mPayload[id].complete;
uint8_t txCmd = mPayload[id].txCmd;
*complete = mPayload[iv->id].complete;
*fastNext = false;
uint8_t txCmd = mPayload[iv->id].txCmd;
if(!*complete) {
DPRINTLN(DBG_VERBOSE, F("incomplete, txCmd is 0x") + String(txCmd, HEX));
//DBGHEXLN(txCmd);
if (txCmd == 0x09 || txCmd == 0x11 || (txCmd >= 0x36 && txCmd <= 0x39))
//we got some delayed status msgs?!?
if ((txCmd == 0x09) || (txCmd == 0x11)) {
if (mPayload[iv->id].stsAB[CH0] && mPayload[iv->id].dataAB[CH0]) {
miComplete(iv);
return true;
}
return false;
}
DPRINTLN(DBG_VERBOSE, F("incomlete, txCmd is 0x") + String(txCmd, HEX));
if (txCmd >= 0x36 && txCmd <= 0x39) {
return false;
}
if (txCmd == 0x0f) { //hw info request, at least hw part nr. and version have to be there...
bool gotRelevant = iv->getFwVersion()
&& iv->getChannelFieldValue(CH0, FLD_PART_NUM, iv->getRecordStruct(InverterDevInform_Simple));
if (gotRelevant)
*fastNext = true;
return gotRelevant;
}
}
//check if we want the next request to be executed faster
if (txCmd == 0x0f)
*fastNext = true;
return true;
}
/* uint16_t mParseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len, uint32_t *start, uint32_t *endTime) {
uint8_t startOff = 2 + id * ALARM_LOG_ENTRY_SIZE;
if((startOff + ALARM_LOG_ENTRY_SIZE) > len)
return 0;
void miHwDecode(Inverter<> *iv, packet_t *p ) {
record_t<> *rec = iv->getRecordStruct(InverterDevInform_All); // choose the record structure
rec->ts = mPayload[iv->id].ts;
mPayload[iv->id].gotFragment = true;
/*
Polling the device software and hardware version number command
start byte Command word routing address target address User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12]
0x7e 0x0f xx xx xx xx YY YY YY YY 0x00 CRC 0x7f
Command Receipt - First Frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x00 USFWBuild_VER APPFWBuild_VER APPFWBuild_YYYY APPFWBuild_MMDD APPFWBuild_HHMM APPFW_PN HW_VER CRC 0x7f
Command Receipt - Second Frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x01 HW_PN HW_FB_TLmValue HW_FB_ReSPRT HW_GridSamp_ResValule HW_ECapValue Matching_APPFW_PN CRC 0x7f
Command receipt - third frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[15] byte[16] byte[17] byte[18]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x12 APPFW_MINVER HWInfoAddr PNInfoCRC_gusv PNInfoCRC_gusv CRC 0x7f
*/
uint16_t wCode = ((uint16_t)pyld[startOff]) << 8 | pyld[startOff+1];
uint32_t startTimeOffset = 0, endTimeOffset = 0;
/*
case InverterDevInform_All:
rec->length = (uint8_t)(HMINFO_LIST_LEN);
rec->assign = (byteAssign_t *)InfoAssignment;
rec->pyldLen = HMINFO_PAYLOAD_LEN;
break;
if (((wCode >> 13) & 0x01) == 1) // check if is AM or PM
startTimeOffset = 12 * 60 * 60;
if (((wCode >> 12) & 0x01) == 1) // check if is AM or PM
endTimeOffset = 12 * 60 * 60;
const byteAssign_t InfoAssignment[] = {
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 },
{ FLD_BOOTLOADER_VER, UNIT_NONE, CH0, 8, 2, 1 }
};
*/
*start = (((uint16_t)pyld[startOff + 4] << 8) | ((uint16_t)pyld[startOff + 5])) + startTimeOffset;
*endTime = (((uint16_t)pyld[startOff + 6] << 8) | ((uint16_t)pyld[startOff + 7])) + endTimeOffset;
if ( p->packet[9] == 0x00 ) {//first frame
//FLD_FW_VERSION
for (uint8_t i = 0; i < 5; i++) {
iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1);
}
iv->isConnected = true;
if(mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DPRINT(DBG_INFO,F("HW_VER is "));
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
}
record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
rec->ts = mPayload[iv->id].ts;
iv->setValue(1, rec, (uint32_t) ((p->packet[24] << 8) + p->packet[25])/1);
mPayload[iv->id].multi_parts +=4;
} else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10
DPRINT_IVID(DBG_INFO, iv->id);
if ( p->packet[9] == 0x01 ) {
DBGPRINTLN(F("got 2nd frame (hw info)"));
/* according to xlsx (different start byte -1!)
byte[11] to byte[14] HW_PN
byte[15] byte[16] HW_FB_TLmValue
byte[17] byte[18] HW_FB_ReSPRT
byte[19] byte[20] HW_GridSamp_ResValule
byte[21] byte[22] HW_ECapValue
byte[23] to byte[26] Matching_APPFW_PN*/
DPRINT(DBG_INFO,F("HW_PartNo "));
DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13]));
record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
rec->ts = mPayload[iv->id].ts;
iv->setValue(0, rec, (uint32_t) ((((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])/1);
DPRINTLN(DBG_INFO, "Alarm #" + String(pyld[startOff+1]) + " '" + String(getAlarmStr(pyld[startOff+1])) + "' start: " + ah::getTimeStr(*start) + ", end: " + ah::getTimeStr(*endTime));
return pyld[startOff+1];
if(mSerialDebug) {
DPRINT(DBG_INFO,F("HW_FB_TLmValue "));
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
DPRINT(DBG_INFO,F("HW_FB_ReSPRT "));
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
DPRINT(DBG_INFO,F("HW_GridSamp_ResValule "));
DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19]));
DPRINT(DBG_INFO,F("HW_ECapValue "));
DBGPRINTLN(String((p->packet[20] << 8) + p->packet[21]));
DPRINT(DBG_INFO,F("Matching_APPFW_PN "));
DBGPRINTLN(String((uint32_t) (((p->packet[22] << 8) | p->packet[23]) << 8 | p->packet[24]) << 8 | p->packet[25]));
}
//notify(InverterDevInform_Simple, iv);
mPayload[iv->id].multi_parts +=2;
notify(InverterDevInform_All, iv);
} else {
DBGPRINTLN(F("3rd gen. inverter!"));
}
} else if ( p->packet[9] == 0x12 ) {//3rd frame
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("got 3rd frame (hw info)"));
/* according to xlsx (different start byte -1!)
byte[11] byte[12] APPFW_MINVER
byte[13] byte[14] HWInfoAddr
byte[15] byte[16] PNInfoCRC_gusv
byte[15] byte[16] PNInfoCRC_gusv (this really is double mentionned in xlsx...)
*/
if(mSerialDebug) {
DPRINT(DBG_INFO,F("APPFW_MINVER "));
DBGPRINTLN(String((p->packet[10] << 8) + p->packet[11]));
DPRINT(DBG_INFO,F("HWInfoAddr "));
DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13]));
DPRINT(DBG_INFO,F("PNInfoCRC_gusv "));
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
}
mPayload[iv->id].multi_parts++;
}
if (mPayload[iv->id].multi_parts > 5) {
iv->setQueuedCmdFinished();
mPayload[iv->id].complete = true;
mPayload[iv->id].requested= false;
mStat->rxSuccess++;
}
}
*/
void reset(uint8_t id, bool clrSts = false) {
//DPRINT_IVID(DBG_INFO, id);
//DBGPRINTLN(F("resetPayload"));
memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES);
mPayload[id].gotFragment = false;
/*mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
mPayload[id].lastFound = false;*/
mPayload[id].rtrRes = 0;
mPayload[id].multi_parts = 0;
mPayload[id].retransmits = 0;
mPayload[id].complete = false;
mPayload[id].dataAB[CH0] = true; //required for 1CH and 2CH devices
@ -796,8 +757,6 @@ const byteAssign_t InfoAssignment[] = {
}
}
IApp *mApp;
HMSYSTEM *mSys;
HMRADIO *mRadio;

6
src/hms/cmt2300a.h

@ -184,8 +184,8 @@ class Cmt2300a {
public:
Cmt2300a() {}
void setup(uint8_t pinCsb, uint8_t pinFcsb) {
mSpi.setup(pinCsb, pinFcsb);
void setup(uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb) {
mSpi.setup(pinSclk, pinSdio, pinCsb, pinFcsb);
init();
}
@ -315,6 +315,8 @@ class Cmt2300a {
mSpi.writeReg(CMT2300A_CUS_MODE_STA, 0x52);
mSpi.writeReg(0x62, 0x20);
if(mSpi.readReg(0x62) != 0x20)
return false; // not connected!
for(uint8_t i = 0; i < 0x60; i++) {
mSpi.writeReg(i, cmtConfig[i]);

21
src/hms/esp32_3wSpi.h

@ -11,14 +11,6 @@
#include "driver/spi_master.h"
#include "esp_rom_gpio.h" // for esp_rom_gpio_connect_out_signal
#if CONFIG_IDF_TARGET_ESP32S3
#define CLK_PIN 6
#define MOSI_PIN 5
#else
#define CLK_PIN 18
#define MOSI_PIN 23
#endif
#define SPI_CLK 1 * 1000 * 1000 // 1MHz
#define SPI_PARAM_LOCK() \
@ -31,19 +23,18 @@
// it is simply the first externally usable hardware SPI master controller
#define SPI_CMT SPI2_HOST
template<uint8_t CSB_PIN=5, uint8_t FCSB_PIN=4> //, uint8_t GPIO3_PIN=15>
class esp32_3wSpi {
public:
esp32_3wSpi() {
mInitialized = false;
}
void setup(uint8_t pinCsb = CSB_PIN, uint8_t pinFcsb = FCSB_PIN) { //, uint8_t pinGpio3 = GPIO3_PIN) {
void setup(uint8_t pinSclk = DEF_CMT_SCLK, uint8_t pinSdio = DEF_CMT_SDIO, uint8_t pinCsb = DEF_CMT_CSB, uint8_t pinFcsb = DEF_CMT_FCSB) {
paramLock = xSemaphoreCreateMutex();
spi_bus_config_t buscfg = {
.mosi_io_num = MOSI_PIN,
.mosi_io_num = pinSdio,
.miso_io_num = -1, // single wire MOSI/MISO
.sclk_io_num = CLK_PIN,
.sclk_io_num = pinSclk,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 32,
@ -83,7 +74,7 @@ class esp32_3wSpi {
};
ESP_ERROR_CHECK(spi_bus_add_device(SPI_CMT, &devcfg2, &spi_fifo));
esp_rom_gpio_connect_out_signal(MOSI_PIN, spi_periph_signal[SPI_CMT].spid_out, true, false);
esp_rom_gpio_connect_out_signal(pinSdio, spi_periph_signal[SPI_CMT].spid_out, true, false);
delay(100);
//pinMode(pinGpio3, INPUT);
@ -162,13 +153,13 @@ class esp32_3wSpi {
.rx_buffer = &rx_data
};
SPI_PARAM_LOCK();
SPI_PARAM_LOCK();
for(uint8_t i = 0; i < len; i++) {
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
delayMicroseconds(4); // > 4 us
buf[i] = rx_data;
}
SPI_PARAM_UNLOCK();
SPI_PARAM_UNLOCK();
}
private:

9
src/hms/hmsRadio.h

@ -29,8 +29,8 @@ class CmtRadio {
mCmtAvail = false;
}
void setup(uint8_t pinCsb, uint8_t pinFcsb, bool genDtuSn = true) {
mCmt.setup(pinCsb, pinFcsb);
void setup(uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb, bool genDtuSn = true) {
mCmt.setup(pinSclk, pinSdio, pinCsb, pinFcsb);
reset(genDtuSn);
}
@ -64,7 +64,7 @@ class CmtRadio {
mSerialDebug = true;
}
bool cmtIsAvail() {
bool isConnected() {
return mCmtAvail;
}
@ -151,8 +151,7 @@ class CmtRadio {
if(!mCmt.reset()) {
mCmtAvail = false;
DPRINTLN(DBG_WARN, F("Initializing CMT2300A failed!"));
}
else {
} else {
mCmtAvail = true;
mCmt.goRx();
}

2
src/platformio.ini

@ -20,7 +20,7 @@ monitor_speed = 115200
extra_scripts =
pre:../scripts/auto_firmware_version.py
pre:web/html/convert.py
pre:../scripts/convertHtml.py
pre:../scripts/applyPatches.py
lib_deps =

52
src/plugins/Display/Display.h

@ -5,6 +5,7 @@
#include <U8g2lib.h>
#include "../../hm/hmSystem.h"
#include "../../hm/hmRadio.h"
#include "../../utils/helper.h"
#include "Display_Mono.h"
#include "Display_Mono_128X32.h"
@ -12,21 +13,25 @@
#include "Display_Mono_84X48.h"
#include "Display_Mono_64X48.h"
#include "Display_ePaper.h"
#include "Display_data.h"
template <class HMSYSTEM>
template <class HMSYSTEM, class HMRADIO>
class Display {
public:
Display() {
mMono = NULL;
}
void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, const char *version) {
void setup(IApp *app, display_t *cfg, HMSYSTEM *sys, HMRADIO *radio, uint32_t *utcTs) {
mApp = app;
mHmRadio = radio;
mCfg = cfg;
mSys = sys;
mUtcTs = utcTs;
mNewPayload = false;
mLoopCnt = 0;
mVersion = version;
mDisplayData.version = app->getVersion(); // version never changes, so only set once
switch (mCfg->type) {
case 0: mMono = NULL; break;
@ -41,7 +46,7 @@ class Display {
mMono = NULL; // ePaper does not use this
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);
mEpaper.init(mCfg->type, mCfg->disp_cs, mCfg->disp_dc, mCfg->disp_reset, mCfg->disp_busy, mCfg->disp_clk, mCfg->disp_data, mUtcTs, mDisplayData.version);
break;
#endif
@ -49,7 +54,7 @@ class Display {
}
if(mMono) {
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);
mMono->init(mCfg->type, mCfg->rot, mCfg->disp_cs, mCfg->disp_dc, 0xff, mCfg->disp_clk, mCfg->disp_data, &mDisplayData);
}
}
@ -61,7 +66,7 @@ class Display {
if (mMono != NULL)
mMono->loop(mCfg->contrast);
if (mNewPayload || (((++mLoopCnt) % 30) == 0)) {
if (mNewPayload || (((++mLoopCnt) % 10) == 0)) {
mNewPayload = false;
mLoopCnt = 0;
DataScreen();
@ -75,14 +80,13 @@ class Display {
void DataScreen() {
if (mCfg->type == 0)
return;
if (*mUtcTs == 0)
return;
float totalPower = 0;
float totalYieldDay = 0;
float totalYieldTotal = 0;
uint8_t isprod = 0;
uint8_t nrprod = 0;
uint8_t nrsleep = 0;
Inverter<> *iv;
record_t<> *rec;
@ -93,19 +97,39 @@ class Display {
continue;
if (iv->isProducing())
isprod++;
nrprod++;
else
nrsleep++;
totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec);
totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec);
}
// prepare display data
mDisplayData.nrProducing = nrprod;
mDisplayData.nrSleeping = nrsleep;
mDisplayData.totalPower = totalPower;
mDisplayData.totalYieldDay = totalYieldDay;
mDisplayData.totalYieldTotal = totalYieldTotal;
mDisplayData.RadioSymbol = mHmRadio->isChipConnected();
mDisplayData.WifiSymbol = (WiFi.status() == WL_CONNECTED);
mDisplayData.MQTTSymbol = mApp->getMqttIsConnected();
mDisplayData.RadioRSSI = (0 < mDisplayData.nrProducing) ? 0 : SCHAR_MIN; // Workaround as NRF24 has no RSSI. Could be approximated by transmisson error heuristic in the future
mDisplayData.WifiRSSI = (WiFi.status() == WL_CONNECTED) ? WiFi.RSSI() : SCHAR_MIN;
mDisplayData.ipAddress = WiFi.localIP();
time_t utc= mApp->getTimestamp();
if (year(utc) > 2020)
mDisplayData.utcTs = utc;
else
mDisplayData.utcTs = 0;
if (mMono ) {
mMono->disp(totalPower, totalYieldDay, totalYieldTotal, isprod);
mMono->disp();
}
#if defined(ESP32)
else if (mCfg->type == 10) {
mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, isprod);
mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, nrprod);
mRefreshCycle++;
}
@ -117,12 +141,14 @@ class Display {
}
// private member variables
IApp *mApp;
DisplayData mDisplayData;
bool mNewPayload;
uint8_t mLoopCnt;
uint32_t *mUtcTs;
const char *mVersion;
display_t *mCfg;
HMSYSTEM *mSys;
HMRADIO *mHmRadio;
uint16_t mRefreshCycle;
#if defined(ESP32)

96
src/plugins/Display/Display_Mono.h

@ -9,6 +9,7 @@
#define DISP_FMT_TEXT_LEN 32
#define BOTTOM_MARGIN 5
#include "defines.h"
#ifdef ESP8266
#include <ESP8266WiFi.h>
@ -16,25 +17,29 @@
#include <WiFi.h>
#endif
#include "../../utils/helper.h"
#include "Display_data.h"
class DisplayMono {
public:
DisplayMono() {};
virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, uint32_t* utcTs, const char* version) = 0;
virtual void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) = 0;
virtual void init(uint8_t type, uint8_t rot, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) = 0;
virtual void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) = 0;
virtual void loop(uint8_t lum) = 0;
virtual void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) = 0;
virtual void disp(void) = 0;
protected:
U8G2* mDisplay;
DisplayData *mDisplayData;
uint8_t mType;
bool mEnPowerSafe, mEnScreenSaver;
uint16_t mDispWidth;
uint16_t mDispHeight;
bool mEnPowerSave, mEnScreenSaver;
uint8_t mLuminance;
uint8_t mLoopCnt;
uint32_t* mUtcTs;
uint8_t mLineXOffsets[5] = {};
uint8_t mLineYOffsets[5] = {};
@ -42,4 +47,83 @@ class DisplayMono {
uint8_t mExtra;
uint16_t mTimeout;
char mFmtText[DISP_FMT_TEXT_LEN];};
char mFmtText[DISP_FMT_TEXT_LEN];
// Common initialization function to be called by subclasses
void monoInit(U8G2* display, uint8_t type, DisplayData *displayData) {
mDisplay = display;
mType = type;
mDisplayData = displayData;
mDisplay->begin();
mDisplay->setContrast(mLuminance);
mDisplay->clearBuffer();
mDispWidth = mDisplay->getDisplayWidth();
mDispHeight = mDisplay->getDisplayHeight();
}
};
/* adapted 5x8 Font for low-res displays with symbols
Symbols:
\x80 ... antenna
\x81 ... WiFi
\x82 ... suncurve
\x83 ... sum/sigma
\x84 ... antenna crossed
\x85 ... WiFi crossed
\x86 ... sun
\x87 ... moon
\x88 ... calendar/day
\x89 ... MQTT */
const uint8_t u8g2_font_5x8_symbols_ahoy[1052] U8G2_FONT_SECTION("u8g2_font_5x8_symbols_ahoy") =
"j\0\3\2\4\4\3\4\5\10\10\0\377\6\377\6\0\1\61\2b\4\3 \5\0\304\11!\7a\306"
"\212!\11\42\7\63\335\212\304\22#\16u\304\232R\222\14JePJI\2$\14u\304\252l\251m"
"I\262E\0%\10S\315\212(\351\24&\13t\304\232(i\252\64%\1'\6\61\336\212\1(\7b"
"\305\32\245))\11b\305\212(\251(\0*\13T\304\212(Q\206D\211\2+\12U\304\252\60\32\244"
"\60\2,\7\63\275\32\245\4-\6\24\324\212!.\6\42\305\212!/\10d\304\272R[\6\60\14d"
"\304\32%R\206DJ\24\0\61\10c\305\232Dj\31\62\13d\304\32%\312\22%\33\2\63\13d\304"
"\212!\212D)Q\0\64\13d\304\252H\251\14Q\226\0\65\12d\304\212A\33\245D\1\66\13d\304"
"\32%[\42)Q\0\67\13d\304\212!\213\262(\213\0\70\14d\304\32%J\224HJ\24\0\71\13"
"d\304\32%\222\222-Q\0:\10R\305\212!\32\2;\10c\275\32\243R\2<\10c\305\252\244\224"
"\25=\10\64\314\212!\34\2>\11c\305\212\254\224\224\0?\11c\305\232\246$M\0@\15\205\274*"
")\222\226DI\244\252\2A\12d\304\32%\222\206I\12B\14d\304\212%\32\222H\32\22\0C\12"
"d\304\32%\322J\211\2D\12d\304\212%r\32\22\0E\12d\304\212A[\262l\10F\12d\304"
"\212A[\262\32\0G\13d\304\32%\322\222)Q\0H\12d\304\212H\32&S\0I\10c\305\212"
"%j\31J\12d\304\232)\253\224\42\0K\13d\304\212HI\244\244S\0L\10d\304\212\254\333\20"
"M\12d\304\212h\70D\246\0N\12d\304\212h\31\226I\12O\12d\304\32%rJ\24\0P\13"
"d\304\212%\222\206$\313\0Q\12t\274\32%\222\26\307\0R\13d\304\212%\222\206$\222\2S\14"
"d\304\32%J\302$J\24\0T\11e\304\212A\12;\1U\11d\304\212\310S\242\0V\12d\304"
"\212\310)\221\24\0W\12d\304\212\310\64\34\242\0X\13d\304\212HJ$%\222\2Y\12e\304\212"
"LKja\11Z\12d\304\212!\213\332\206\0[\10c\305\212!j\32\134\10d\304\212,l\13]"
"\10c\305\212\251i\10^\6#\345\232\6_\6\24\274\212!`\6\42\345\212(a\11D\304\232!\222"
"\222\1b\13d\304\212,[\42iH\0c\7C\305\232)\23d\12d\304\272\312\20I\311\0e\11"
"D\304\32%\31\262\1f\12d\304\252Ji\312\42\0g\12T\274\32%J\266D\1h\12d\304\212"
",[\42S\0i\10c\305\232P\252\14j\12s\275\252\64\212\224\12\0k\12d\304\212\254\64$\221"
"\24l\10c\305\12\251\313\0m\12E\304\12\245EI\224\2n\10D\304\212%\62\5o\11D\304\32"
"%\222\22\5p\12T\274\212%\32\222,\3q\11T\274\232!J\266\2r\11D\304\212$\261e\0"
"s\10C\305\232![\0t\13d\304\232,\232\262$J\0u\10D\304\212\310\224\14v\10C\305\212"
"\304R\1w\12E\304\212LI\224.\0x\11D\304\212(\221\224(y\13T\274\212HJ\206(Q"
"\0z\11D\304\212!*\15\1{\12t\304*%L\304(\24|\6a\306\212\3}\13t\304\12\61"
"\12\225\60\221\0~\10$\344\232DI\0\5\0\304\12\200\13u\274\212K\242T\266\260\4\201\14f"
"D\233!\11#-\312!\11\202\15hD<\65\12\243,\214\302$\16\203\15w<\214C\22F\71\220"
"\26\207A\204\16\205\274\212,)%Y\230%QR\13\205\17\206<\213\60\31\22\311\66D\245!\11\3"
"\206\20\210<\254\342\20]\302(L\246C\30E\0\207\15wD\334X\25\267\341\20\15\21\0\210\16w"
"<\214\203RQ\25I\212\324a\20\211\15f\304\213)\213\244,\222\222\245\0\0\0\0";
const uint8_t u8g2_font_ncenB08_symbols8_ahoy[173] U8G2_FONT_SECTION("u8g2_font_ncenB08_symbols8_ahoy") =
"\13\0\3\2\4\4\1\2\5\10\11\0\0\10\0\10\0\0\0\0\0\0\224A\14\207\305\70H\321\222H"
"k\334\6B\20\230\305\32\262\60\211\244\266\60T\243\34\326\0C\20\210\305S\243\60\312\302(\214\302("
"L\342\0D\16\210\315\70(i\224#\71\20W\207\3E\15\207\305xI\206\323\232nIS\1F\25"
"\230\305H\206\244\230$C\22\15Y\242\204j\224\205I$\5G\17\210\305*\16\321%\214\302d:\204"
"Q\4H\14w\307\215Uq\33\16\321\20\1I\21\227\305\311\222aP\245H\221\244H\212\324a\20J"
"\5\0\275\0K\5\0\315\0\0\0\0";
const uint8_t u8g2_font_ncenB10_symbols10_ahoy[207] U8G2_FONT_SECTION("u8g2_font_ncenB10_symbols10_ahoy") =
"\13\0\3\2\4\4\2\2\5\13\13\0\0\13\0\13\0\0\0\0\0\0\266A\15\267\212q\220\42\251\322"
"\266\306\275\1B\20\230\236\65da\22Ima\250F\71\254\1C\23\272\272\251\3Q\32\366Q\212\243"
"\70\212\243\70\311\221\0D\20\271\252\361\242F:\242#: {\36\16\1E\22\267\212\361\222\14I\242"
"\14\332\232\216[RJ\232\12F\25\250\233\221\14I\61I\206$\252%J\250Fa\224%J\71G\30"
"\273\312W\316r`T\262DJ\303\64L#%K\304\35\310\342,\3H\27\272\272\217\344P\16\351\210"
"\16\354\300<\244C\70,\303 \16!\0I\24\271\252\241\34\336\1-\223\64-\323\62-\323\62\35x"
"\10J\5\0\232\1K\5\0\232\1\0\0\0";

63
src/plugins/Display/Display_Mono_128X32.h

@ -9,45 +9,32 @@
class DisplayMono128X32 : public DisplayMono {
public:
DisplayMono128X32() : DisplayMono() {
mEnPowerSafe = true;
mEnPowerSave = 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 config(bool enPowerSave, bool enScreenSaver, uint8_t lum) {
mEnPowerSave = enPowerSave;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
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) {
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) {
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();
monoInit(new U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C(rot, reset, clock, data), type, displayData);
calcLinePositions();
mDisplay->clearBuffer();
mDisplay->setContrast(mLuminance);
printText("AHOY!", 0);
printText("Ahoy!", 0);
printText("ahoydtu.de", 2);
printText(version, 3);
printText(mDisplayData->version, 3);
mDisplay->sendBuffer();
}
void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) {
mEnPowerSafe = enPowerSafe;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
void loop(uint8_t lum) {
if (mEnPowerSafe) {
if (mEnPowerSave) {
if (mTimeout != 0)
mTimeout--;
}
@ -58,43 +45,43 @@ class DisplayMono128X32 : public DisplayMono {
}
}
void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
void disp(void) {
mDisplay->clearBuffer();
// set Contrast of the Display to raise the lifetime
if (3 != mType)
mDisplay->setContrast(mLuminance);
if ((totalPower > 0) && (isprod > 0)) {
if ((mDisplayData->totalPower > 0) && (mDisplayData->nrProducing > 0)) {
mTimeout = DISP_DEFAULT_TIMEOUT;
mDisplay->setPowerSave(false);
if (totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000));
if (mDisplayData->totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (mDisplayData->totalPower / 1000));
else
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", mDisplayData->totalPower);
printText(mFmtText, 0);
} else {
printText("offline", 0);
// check if it's time to enter power saving mode
if (mTimeout == 0)
mDisplay->setPowerSave(mEnPowerSafe);
mDisplay->setPowerSave(mEnPowerSave);
}
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay);
printText(mFmtText, 1);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", mDisplayData->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);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", mDisplayData->nrProducing);
printText(mFmtText, 3);
} else if (NULL != mUtcTs)
printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
} else if (0 != mDisplayData->utcTs)
printText(ah::getTimeStr(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), 3);
mDisplay->sendBuffer();
@ -119,13 +106,13 @@ class DisplayMono128X32 : public DisplayMono {
inline void setFont(uint8_t line) {
switch (line) {
case 0:
mDisplay->setFont(u8g2_font_9x15_tf);
mDisplay->setFont(u8g2_font_9x15_tr);
break;
case 3:
mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf);
mDisplay->setFont(u8g2_font_tom_thumb_4x6_tr);
break;
default:
mDisplay->setFont(u8g2_font_tom_thumb_4x6_tf);
mDisplay->setFont(u8g2_font_tom_thumb_4x6_tr);
break;
}
}

63
src/plugins/Display/Display_Mono_128X64.h

@ -9,53 +9,42 @@
class DisplayMono128X64 : public DisplayMono {
public:
DisplayMono128X64() : DisplayMono() {
mEnPowerSafe = true;
mEnPowerSave = 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) {
void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) {
mEnPowerSave = enPowerSave;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) {
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);
monoInit(new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, reset, clock, data), type, displayData);
break;
case 2:
mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data);
monoInit(new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, reset, clock, data), type, displayData);
break;
case 6:
mDisplay = new U8G2_SSD1309_128X64_NONAME0_F_HW_I2C(rot, reset, clock, data);
default:
monoInit(new U8G2_SSD1309_128X64_NONAME0_F_HW_I2C(rot, reset, clock, data), type, displayData);
break;
}
mUtcTs = utcTs;
mDisplay->begin();
calcLinePositions();
mDisplay->clearBuffer();
mDisplay->setContrast(mLuminance);
printText("AHOY!", 0, 35);
printText("Ahoy!", 0, 35);
printText("ahoydtu.de", 2, 20);
printText(version, 3, 46);
printText(mDisplayData->version, 3, 46);
mDisplay->sendBuffer();
}
void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) {
mEnPowerSafe = enPowerSafe;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
void loop(uint8_t lum) {
if (mEnPowerSafe) {
if (mEnPowerSave) {
if (mTimeout != 0)
mTimeout--;
}
@ -66,43 +55,43 @@ class DisplayMono128X64 : public DisplayMono {
}
}
void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
void disp(void) {
mDisplay->clearBuffer();
// set Contrast of the Display to raise the lifetime
mDisplay->setContrast(mLuminance);
if ((totalPower > 0) && (isprod > 0)) {
if ((mDisplayData->totalPower > 0) && (mDisplayData->nrProducing > 0)) {
mTimeout = DISP_DEFAULT_TIMEOUT;
mDisplay->setPowerSave(false);
if (totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000));
if (mDisplayData->totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (mDisplayData->totalPower / 1000));
else
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", mDisplayData->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);
mDisplay->setPowerSave(mEnPowerSave);
}
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", totalYieldDay);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay);
printText(mFmtText, 1);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", totalYieldTotal);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", mDisplayData->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);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", mDisplayData->nrProducing);
printText(mFmtText, 3);
} else if (NULL != mUtcTs)
printText(ah::getDateTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
} else if (0 != mDisplayData->utcTs)
printText(ah::getDateTimeStr(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), 3);
mDisplay->sendBuffer();
@ -126,7 +115,7 @@ class DisplayMono128X64 : public DisplayMono {
mDisplay->setFont(u8g2_font_ncenB14_tr);
break;
case 3:
mDisplay->setFont(u8g2_font_5x8_tr);
mDisplay->setFont(u8g2_font_5x8_symbols_ahoy);
break;
default:
mDisplay->setFont(u8g2_font_ncenB10_tr);

61
src/plugins/Display/Display_Mono_64X48.h

@ -9,46 +9,33 @@
class DisplayMono64X48 : public DisplayMono {
public:
DisplayMono64X48() : DisplayMono() {
mEnPowerSafe = true;
mEnPowerSave = true;
mEnScreenSaver = false;
mLuminance = 20;
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) {
void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) {
mEnPowerSave = enPowerSave;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) {
u8g2_cb_t *rot = (u8g2_cb_t *)((rotation != 0x00) ? U8G2_R2 : U8G2_R0);
mType = type;
// Wemos OLed Shield is not defined in u8 lib -> use nearest compatible
mDisplay = new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, reset, clock, data);
mUtcTs = utcTs;
mDisplay->begin();
monoInit(new U8G2_SSD1306_64X48_ER_F_HW_I2C(rot, reset, clock, data), type, displayData);
calcLinePositions();
mDisplay->clearBuffer();
mDisplay->setContrast(mLuminance);
printText("AHOY!", 0);
printText("Ahoy!", 0);
printText("ahoydtu.de", 1);
printText(version, 2);
printText(mDisplayData->version, 2);
mDisplay->sendBuffer();
}
void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) {
mEnPowerSafe = enPowerSafe;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
void loop(uint8_t lum) {
if (mEnPowerSafe) {
if (mEnPowerSave) {
if (mTimeout != 0)
mTimeout--;
}
@ -59,43 +46,43 @@ class DisplayMono64X48 : public DisplayMono {
}
}
void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
void disp(void) {
mDisplay->clearBuffer();
// set Contrast of the Display to raise the lifetime
mDisplay->setContrast(mLuminance);
if ((totalPower > 0) && (isprod > 0)) {
if ((mDisplayData->totalPower > 0) && (mDisplayData->nrProducing > 0)) {
mTimeout = DISP_DEFAULT_TIMEOUT;
mDisplay->setPowerSave(false);
if (totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (totalPower / 1000));
if (mDisplayData->totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2.2f kW", (mDisplayData->totalPower / 1000));
else
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", totalPower);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%3.0f W", mDisplayData->totalPower);
printText(mFmtText, 0);
} else {
printText("offline", 0);
// check if it's time to enter power saving mode
if (mTimeout == 0)
mDisplay->setPowerSave(mEnPowerSafe);
mDisplay->setPowerSave(mEnPowerSave);
}
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "D: %4.0f Wh", totalYieldDay);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "D: %4.0f Wh", mDisplayData->totalYieldDay);
printText(mFmtText, 1);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "T: %4.0f kWh", totalYieldTotal);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "T: %4.0f kWh", mDisplayData->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, "active Inv: %d", isprod);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "active Inv: %d", mDisplayData->nrProducing);
printText(mFmtText, 3);
} else if (NULL != mUtcTs)
printText(ah::getTimeStr(gTimezone.toLocal(*mUtcTs)).c_str(), 3);
} else if (0 != mDisplayData->utcTs)
printText(ah::getTimeStr(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), 3);
mDisplay->sendBuffer();
@ -115,11 +102,11 @@ class DisplayMono64X48 : public DisplayMono {
inline void setFont(uint8_t line) {
switch (line) {
case 0:
mDisplay->setFont(u8g2_font_fur11_tf);
mDisplay->setFont(u8g2_font_fur11_tr);
break;
case 1:
case 2:
mDisplay->setFont(u8g2_font_6x10_tf);
mDisplay->setFont(u8g2_font_6x10_tr);
break;
case 3:
mDisplay->setFont(u8g2_font_4x6_tr);

151
src/plugins/Display/Display_Mono_84X48.h

@ -5,50 +5,37 @@
#pragma once
#include "Display_Mono.h"
#include "../../utils/dbg.h"
class DisplayMono84X48 : public DisplayMono {
public:
DisplayMono84X48() : DisplayMono() {
mEnPowerSafe = true;
mEnPowerSave = true;
mEnScreenSaver = true;
mLuminance = 60;
mLuminance = 140;
mExtra = 0;
mDispY = 0;
mTimeout = DISP_DEFAULT_TIMEOUT; // interval at which to power save (milliseconds)
mUtcTs = NULL;
mType = 0;
mDispWidth = 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) {
void config(bool enPowerSave, bool enScreenSaver, uint8_t lum) {
mEnPowerSave = enPowerSave;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
void init(uint8_t type, uint8_t rotation, uint8_t cs, uint8_t dc, uint8_t reset, uint8_t clock, uint8_t data, DisplayData *displayData) {
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();
mDispWidth = mDisplay->getDisplayWidth();
monoInit(new U8G2_PCD8544_84X48_F_4W_SW_SPI(rot, clock, data, cs, dc, reset), type, displayData);
calcLinePositions();
mDisplay->clearBuffer();
mDisplay->setContrast(mLuminance);
printText("AHOY!", l_Ahoy);
printText("ahoydtu.de", l_Website);
printText(version, l_Version);
printText("Ahoy!", l_Ahoy, 0xff);
printText("ahoydtu.de", l_Website, 0xff);
printText(mDisplayData->version, l_Version, 0xff);
mDisplay->sendBuffer();
}
void config(bool enPowerSafe, bool enScreenSaver, uint8_t lum) {
mEnPowerSafe = enPowerSafe;
mEnScreenSaver = enScreenSaver;
mLuminance = lum;
}
void loop(uint8_t lum) {
if (mEnPowerSafe) {
if (mEnPowerSave) {
if (mTimeout != 0)
mTimeout--;
}
@ -59,44 +46,88 @@ class DisplayMono84X48 : public DisplayMono {
}
}
void disp(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) {
mDisplay->clearBuffer();
void disp(void) {
// Test
/*
mDisplayData->nrSleeping = 10;
mDisplayData->nrProducing = 10;
mDisplayData->totalPower = 12345.67;
mDisplayData->totalYieldDay = 12345.67;
mDisplayData->totalYieldTotal = 1234;
mDisplayData->utcTs += 1000000;
*/
// set Contrast of the Display to raise the lifetime
mDisplay->setContrast(mLuminance);
mDisplay->clearBuffer();
if ((totalPower > 0) && (isprod > 0)) {
// print total power
if (mDisplayData->nrProducing > 0) {
mTimeout = DISP_DEFAULT_TIMEOUT;
mDisplay->setPowerSave(false);
if (totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (totalPower / 1000));
if (mDisplayData->totalPower > 9999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2fkW", (mDisplayData->totalPower / 1000)); // forgo spacing between value and SI unit in favor of second position after decimal point
else if (mDisplayData->totalPower > 999)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000));
else
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", totalPower);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.1f W", mDisplayData->totalPower);
printText(mFmtText, l_TotalPower);
printText(mFmtText, l_TotalPower, 0xff);
} else {
printText("offline", l_TotalPower);
printText("offline", l_TotalPower, 0xff);
// check if it's time to enter power saving mode
if (mTimeout == 0)
mDisplay->setPowerSave(mEnPowerSafe);
mDisplay->setPowerSave(mEnPowerSave);
}
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "Today: %4.0f Wh", totalYieldDay);
printText(mFmtText, l_YieldDay);
// print Date and time
if (0 != mDisplayData->utcTs)
printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "Total: %.1f kWh", totalYieldTotal);
printText(mFmtText, l_YieldTotal);
// alternatively:
// print ip address
if (!(mExtra % 5) && (mDisplayData->ipAddress)) {
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str());
printText(mFmtText, l_Status, 0xff);
}
// print status of inverters
else {
if (0 == mDisplayData->nrSleeping)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x86");
else if (0 == mDisplayData->nrProducing)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x87");
else
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d\x86 %d\x87", mDisplayData->nrProducing, mDisplayData->nrSleeping);
setLineFont(l_Status);
printText(mFmtText, l_Status, (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2);
}
if (NULL != mUtcTs)
printText(ah::getDateTimeStrShort(gTimezone.toLocal(*mUtcTs)).c_str(), l_Time);
// print yields
printText("\x88", l_YieldDay, 11); // day symbol
printText("\x83", l_YieldTotal, 11); // total symbol
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%7.0f Wh", mDisplayData->totalYieldDay);
printText(mFmtText, l_YieldDay, 18);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%7.1f kWh", mDisplayData->totalYieldTotal);
printText(mFmtText, l_YieldTotal, 18);
// draw dynamic Nokia RSSI bars
int rssi_bar_height = 7;
for (int i=0; i<4;i++) {
int radio_rssi_threshold = -60 - i*10; // radio rssi not yet tested in reality!
int wifi_rssi_threshold = -60 - i*10;
if (mDisplayData->RadioRSSI > radio_rssi_threshold)
mDisplay->drawBox(0, 8+(rssi_bar_height+1)*i, 4-i,rssi_bar_height);
if (mDisplayData->WifiRSSI > wifi_rssi_threshold)
mDisplay->drawBox(mDispWidth-4+i, 8+(rssi_bar_height+1)*i, 4-i,rssi_bar_height);
}
IPAddress ip = WiFi.localIP();
if (!(mExtra % 5) && (ip))
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", ip.toString().c_str());
// draw dynamic antenna and WiFi symbols
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%c", mDisplayData->RadioSymbol?'\x80':'\x84'); // NRF
printText(mFmtText, l_RSSI);
if (mDisplayData->MQTTSymbol)
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x89"); // MQTT
else
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "Inv.On: %d", isprod);
printText(mFmtText, l_Status);
snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%c", mDisplayData->WifiSymbol?'\x81':'\x85'); // Wifi connected
printText(mFmtText, l_RSSI, mDispWidth - 6);
mDisplay->sendBuffer();
@ -104,7 +135,6 @@ class DisplayMono84X48 : public DisplayMono {
}
private:
uint16_t mDispWidth;
enum _dispLine {
// start page
l_Website = 0,
@ -116,6 +146,8 @@ class DisplayMono84X48 : public DisplayMono {
l_TotalPower = 2,
l_YieldDay = 3,
l_YieldTotal = 4,
// run page - rssi bar symbols
l_RSSI = 4,
// End
l_MAX_LINES = 5,
};
@ -126,7 +158,7 @@ class DisplayMono84X48 : public DisplayMono {
uint8_t asc, dsc;
do {
setFont(i);
setLineFont(i);
asc = mDisplay->getAscent();
yOff += asc;
mLineYOffsets[i] = yOff;
@ -138,18 +170,23 @@ class DisplayMono84X48 : public DisplayMono {
} while(l_MAX_LINES>i);
}
inline void setFont(uint8_t line) {
inline void setLineFont(uint8_t line) {
if ((line == l_TotalPower) || (line == l_Ahoy))
mDisplay->setFont(u8g2_font_logisoso16_tr);
else
mDisplay->setFont(u8g2_font_5x8_tr);
mDisplay->setFont(u8g2_font_5x8_symbols_ahoy);
}
void printText(const char *text, uint8_t line) {
void printText(const char *text, uint8_t line, uint8_t col=0) {
uint8_t dispX;
setFont(line);
dispX = (mDispWidth - mDisplay->getStrWidth(text)) / 2; // center text
dispX += (mEnScreenSaver) ? (mExtra % 7) : 0;
setLineFont(line);
if (0xff == col)
dispX = (mDispWidth - mDisplay->getStrWidth(text)) / 2; // center text
else
dispX = col;
mDisplay->drawStr(dispX, mLineYOffsets[line], text);
}
};

22
src/plugins/Display/Display_data.h

@ -0,0 +1,22 @@
#include "../../utils/helper.h"
#ifndef __DISPLAY_DATA__
#define __DISPLAY_DATA__
struct DisplayData {
const char *version=nullptr;
float totalPower=0.0f; // indicate current power (W)
float totalYieldDay=0.0f; // indicate day yield (W)
float totalYieldTotal=0.0f; // indicate total yield (W)
uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized
uint8_t nrProducing=0; // indicate number of producing inverters
uint8_t nrSleeping=0; // indicate number of sleeping inverters
bool WifiSymbol = false; // indicate if WiFi is connected
bool RadioSymbol = false; // indicate if radio module is connecting and working
bool MQTTSymbol = false; // indicate if MQTT is connected
int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi
int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio
IPAddress ipAddress; // indicate ip adress of ahoy
};
#endif /*__DISPLAY_DATA__*/

8
src/plugins/Display/Display_ePaper.cpp

@ -46,9 +46,9 @@ void DisplayEPaper::init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, u
}
}
void DisplayEPaper::config(uint8_t rotation, bool enPowerSafe) {
void DisplayEPaper::config(uint8_t rotation, bool enPowerSave) {
mDisplayRotation = rotation;
mEnPowerSafe = enPowerSafe;
mEnPowerSave = enPowerSave;
}
//***************************************************************************
@ -209,7 +209,7 @@ void DisplayEPaper::actualPowerPaged(float totalPower, float totalYieldDay, floa
} else
snprintf(_fmtText, sizeof(_fmtText), "offline");
if ((totalPower == 0) && (mEnPowerSafe)) {
if ((totalPower == 0) && (mEnPowerSave)) {
_display->fillRect(0, mHeadFootPadding, 200, 200, GxEPD_BLACK);
_display->drawBitmap(0, 0, logo, 200, 200, GxEPD_WHITE);
} else {
@ -291,7 +291,7 @@ void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYield
if ((isprod > 0) && (_changed)) {
_changed = false;
lastUpdatePaged();
} else if ((0 == totalPower) && (mEnPowerSafe))
} else if ((0 == totalPower) && (mEnPowerSave))
offlineFooter();
_display->powerOff();

4
src/plugins/Display/Display_ePaper.h

@ -28,7 +28,7 @@ class DisplayEPaper {
DisplayEPaper();
void fullRefresh();
void init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t* utcTs, const char* version);
void config(uint8_t rotation, bool enPowerSafe);
void config(uint8_t rotation, bool enPowerSave);
void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod);
void refreshLoop();
void tickerSecond();
@ -56,7 +56,7 @@ class DisplayEPaper {
uint8_t mHeadFootPadding;
GxEPD2_GFX* _display;
uint32_t* mUtcTs;
bool mEnPowerSafe;
bool mEnPowerSave;
const char* _version;
RefreshStatus mRefreshState, mNextRefreshState;
uint8_t mSecondCnt;

84
src/web/RestApi.h

@ -31,7 +31,7 @@ const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_Y
const uint8_t acListHmt[] = {FLD_UAC_1N, FLD_IAC_1, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP};
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP};
template<class HMSYSTEM, class HMRADIO>
template<class HMSYSTEM>
class RestApi {
public:
RestApi() {
@ -42,12 +42,15 @@ class RestApi {
nr = 0;
}
void setup(IApp *app, HMSYSTEM *sys, HMRADIO *radio, AsyncWebServer *srv, settings_t *config) {
mApp = app;
mSrv = srv;
mSys = sys;
mRadio = radio;
mConfig = config;
void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) {
mApp = app;
mSrv = srv;
mSys = sys;
mRadioNrf = (HmRadio<>*)mApp->getRadioObj(true);
#if defined(ESP32)
mRadioCmt = (CmtRadio<esp32_3wSpi>*)mApp->getRadioObj(false);
#endif
mConfig = config;
mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1));
mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody(
std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
@ -238,7 +241,11 @@ class RestApi {
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
getGeneric(request, obj);
getRadioNrf(obj.createNestedObject(F("radio")));
getRadioNrf(obj.createNestedObject(F("radioNrf")));
#if defined(ESP32)
getRadioCmtInfo(obj.createNestedObject(F("radioCmt")));
#endif
getMqttInfo(obj.createNestedObject(F("mqtt")));
getStatistics(obj.createNestedObject(F("statistics")));
#if defined(ESP32)
@ -310,8 +317,8 @@ class RestApi {
obj[F("rx_fail")] = stat->rxFail;
obj[F("rx_fail_answer")] = stat->rxFailNoAnser;
obj[F("frame_cnt")] = stat->frmCnt;
obj[F("tx_cnt")] = mRadio->mSendCnt;
obj[F("retransmits")] = mRadio->mRetransmits;
obj[F("tx_cnt")] = mRadioNrf->mSendCnt;
obj[F("retransmits")] = mRadioNrf->mRetransmits;
}
void getInverterList(JsonObject obj) {
@ -511,19 +518,28 @@ class RestApi {
obj[F("led_high_active")] = mConfig->led.led_high_active;
}
#if defined(ESP32)
void getRadioCmt(JsonObject obj) {
obj[F("csb")] = mConfig->cmt.pinCsb;
obj[F("fcsb")] = mConfig->cmt.pinFcsb;
obj[F("gpio3")] = mConfig->cmt.pinIrq;
obj[F("en")] = (bool) mConfig->cmt.enabled;
obj[F("sclk")] = mConfig->cmt.pinSclk;
obj[F("sdio")] = mConfig->cmt.pinSdio;
obj[F("csb")] = mConfig->cmt.pinCsb;
obj[F("fcsb")] = mConfig->cmt.pinFcsb;
obj[F("gpio3")] = mConfig->cmt.pinIrq;
obj[F("en")] = (bool) mConfig->cmt.enabled;
}
void getRadioCmtInfo(JsonObject obj) {
obj[F("en")] = (bool) mConfig->cmt.enabled;
obj[F("isconnected")] = mRadioCmt->isConnected();
}
#endif
void getRadioNrf(JsonObject obj) {
obj[F("en")] = (bool) mConfig->nrf.enabled;
obj[F("isconnected")] = mRadioNrf->isChipConnected();
obj[F("power_level")] = mConfig->nrf.amplifierPower;
obj[F("isconnected")] = mRadio->isChipConnected();
obj[F("DataRate")] = mRadio->getDataRate();
obj[F("isPVariant")] = mRadio->isPVariant();
obj[F("en")] = (bool) mConfig->nrf.enabled;
obj[F("dataRate")] = mRadioNrf->getDataRate();
//obj[F("isPVariant")] = mRadioNrf->isPVariant();
}
void getSerial(JsonObject obj) {
@ -555,6 +571,14 @@ class RestApi {
obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy;
}
void getMqttInfo(JsonObject obj) {
obj[F("enabled")] = (mConfig->mqtt.broker[0] != '\0');
obj[F("connected")] = mApp->getMqttIsConnected();
obj[F("tx_cnt")] = mApp->getMqttTxCnt();
obj[F("rx_cnt")] = mApp->getMqttRxCnt();
obj[F("interval")] = mConfig->mqtt.interval;
}
void getIndex(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("ts_now")] = mApp->getTimestamp();
@ -581,9 +605,9 @@ class RestApi {
}
JsonArray warn = obj.createNestedArray(F("warnings"));
if(!mRadio->isChipConnected() && mConfig->nrf.enabled)
if(!mRadioNrf->isChipConnected() && mConfig->nrf.enabled)
warn.add(F("your NRF24 module can't be reached, check the wiring, pinout and enable"));
else if(!mRadio->isPVariant() && mConfig->nrf.enabled)
else if(!mRadioNrf->isPVariant() && mConfig->nrf.enabled)
warn.add(F("your NRF24 module isn't a plus version(+), maybe incompatible"));
if(!mApp->getSettingsValid())
warn.add(F("your settings are invalid"));
@ -591,19 +615,6 @@ class RestApi {
warn.add(F("reboot your ESP to apply all your configuration changes"));
if(0 == mApp->getTimestamp())
warn.add(F("time not set. No communication to inverter possible"));
/*if(0 == mSys->getNumInverters())
warn.add(F("no inverter configured"));*/
if((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0))
warn.add(F("MQTT is not connected"));
JsonArray info = obj.createNestedArray(F("infos"));
if(mApp->getMqttIsConnected())
info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received"));
if(mConfig->mqtt.interval > 0)
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds"));
}
void getSetup(AsyncWebServerRequest *request, JsonObject obj) {
@ -614,7 +625,9 @@ class RestApi {
getNtp(obj.createNestedObject(F("ntp")));
getSun(obj.createNestedObject(F("sun")));
getPinout(obj.createNestedObject(F("pinout")));
#if defined(ESP32)
getRadioCmt(obj.createNestedObject(F("radioCmt")));
#endif
getRadioNrf(obj.createNestedObject(F("radioNrf")));
getSerial(obj.createNestedObject(F("serial")));
getStaticIp(obj.createNestedObject(F("static_ip")));
@ -743,7 +756,10 @@ class RestApi {
IApp *mApp;
HMSYSTEM *mSys;
HMRADIO *mRadio;
HmRadio<> *mRadioNrf;
#if defined(ESP32)
CmtRadio<esp32_3wSpi> *mRadioCmt;
#endif
AsyncWebServer *mSrv;
settings_t *mConfig;

8
src/web/html/index.html

@ -19,7 +19,6 @@
<div id="warn_info"></div>
</p>
<div class="hr"></div>
<div id="note">
<h3>Support this project:</h3>
<ul>
@ -157,14 +156,11 @@
document.getElementById("iv").replaceChildren(p);
}
function parseWarnInfo(warn, success) {
function parseWarn(warn) {
var p = div(["none"]);
for(var w of warn) {
p.append(svg(iconWarn, 30, 30, "icon icon-warn"), span(w), br());
}
for(var i of success) {
p.append(svg(iconSuccess, 30, 30, "icon icon-success"), span(i), br());
}
if(commInfo.length > 0)
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span(commInfo), br());
@ -197,7 +193,7 @@
parseGeneric(obj["generic"]);
parseSys(obj);
parseIv(obj["inverter"]);
parseWarnInfo(obj["warnings"], obj["infos"]);
parseWarn(obj["warnings"]);
if(exeOnce) {
window.setInterval("tick()", 1000);
exeOnce = false;

54
src/web/html/setup.html

@ -36,10 +36,10 @@
<p class="des">Radio (NRF24L01+)</p>
<div id="rf24"></div>
<!--IF_ESP32-->
<p class="des">Radio (CMT2300A)</p>
<div id="cmt"><div class="col-12">(ESP32 only)</div></div>
<!--ENDIF_ESP32-->
<p class="des">Serial Console</p>
<div class="row mb-3">
<div class="col-8 col-sm-3">print inverter data</div>
@ -398,6 +398,8 @@
[15, "D8 (GPIO15)"],
[16, "D0 (GPIO16 - no IRQ!)"]
];
/*IF_ESP32*/
var esp32pins = [
[255, "off / default"],
[0, "GPIO0"],
@ -406,26 +408,26 @@
[3, "RX (GPIO3)"],
[4, "GPIO4"],
[5, "GPIO5"],
[12, "GPIO12"],
[13, "GPIO13"],
[14, "GPIO14"],
[12, "GPIO12 (HSPI MISO)"],
[13, "GPIO13 (HSPI MOSI)"],
[14, "GPIO14 (HSPI SCLK)"],
[15, "GPIO15"],
[16, "GPIO16"],
[17, "GPIO17"],
[18, "GPIO18"],
[19, "GPIO19"],
[18, "GPIO18 (VSPI SCLK)"],
[19, "GPIO19 (VSPI MISO)"],
[21, "GPIO21 (SDA)"],
[22, "GPIO22 (SCL)"],
[23, "GPIO23"],
[23, "GPIO23 (VSPI MOSI)"],
[25, "GPIO25"],
[26, "GPIO26"],
[27, "GPIO27"],
[32, "GPIO32"],
[33, "GPIO33"],
[34, "GPIO34"],
[35, "GPIO35"],
[36, "VP (GPIO36)"],
[39, "VN (GPIO39)"]
[34, "GPIO34 (in only)"],
[35, "GPIO35 (in only)"],
[36, "VP (GPIO36, in only)"],
[39, "VN (GPIO39, in only)"]
];
var esp32s3pins = [
[255, "off / default"],
@ -475,6 +477,7 @@
[47, "GPIO47"],
[48, "GPIO48"],
];
/*ENDIF_ESP32*/
var led_high_active = [
[0, "low active"],
[1, "high active"],
@ -828,6 +831,7 @@
);
}
/*IF_ESP32*/
function parseCmtRadio(obj, type, system) {
var e = document.getElementById("cmt");
var en = inp("cmtEnable", null, null, ["cb"], "cmtEnable", "checkbox");
@ -839,18 +843,19 @@
ml("div", {class: "col-4 col-sm-9"}, en)
])
);
pins = [['csb', 'pinCsb'], ['fcsb', 'pinFcsb'], ['gpio3', 'pinGpio3']];
pins = [['sclk', 'pinCmtSclk'], ['sdio', 'pinSdio'], ['csb', 'pinCsb'], ['fcsb', 'pinFcsb'], ['gpio3', 'pinGpio3']];
for(p of pins) {
e.append(
ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, p[0].toUpperCase()),
ml("div", {class: "col-12 col-sm-9"},
sel(p[1], ("ESP8266" == type) ? esp8266pins : ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, obj[p[0]])
sel(p[1], ("ESP32-S3" == system["chip_model"]) ? esp32s3pins : esp32pins, obj[p[0]])
)
])
);
}
}
/*ENDIF_ESP32*/
function parseSerial(obj) {
for(var i of [["serEn", "show_live_data"], ["serDbg", "debug"]])
@ -865,8 +870,9 @@
var e = document.getElementById("dispPins");
//KEEP this order !!!
var pins = [['clock', 'disp_clk'], ['data', 'disp_data'], ['cs', 'disp_cs'], ['dc', 'disp_dc'], ['reset', 'disp_rst']];
if("ESP32" == type)
pins.push(['busy', 'disp_bsy']);
/*IF_ESP32*/
pins.push(['busy', 'disp_bsy']);
/*ENDIF_ESP32*/
for(p of pins) {
e.append(
ml("div", {class: "row mb-3", id: "row_" + p[1]}, [
@ -880,8 +886,9 @@
// keep display types grouped
var opts = [[0, "None"], [2, "SH1106 1.3\" 128X64"], [5, "SSD1306 0.66\" 64X48 (Wemos OLED Shield)"], [4, "SSD1306 0.91\" 128X32"], [1, "SSD1306 0.96\" 128X64"], [6, "SSD1309 2.42\" 128X64"], [3, "Nokia5110"]];
if("ESP32" == type)
opts.push([10, "ePaper"]);
/*IF_ESP32*/
opts.push([10, "ePaper"]);
/*ENDIF_ESP32*/
var dispType = sel("disp_typ", opts, obj["disp_typ"]);
document.getElementById("dispType").append(
ml("div", {class: "row mb-3"}, [
@ -894,10 +901,10 @@
});
opts = [[0, "0&deg;"], [2, "180&deg;"]];
if("ESP32" == type) {
/*IF_ESP32*/
opts.push([1, "90&deg;"]);
opts.push([3, "270&deg;"]);
}
/*ENDIF_ESP32*/
document.getElementById("dispRot").append(
ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, "Rotation"),
@ -991,9 +998,14 @@
parseSun(root["sun"]);
parsePinout(root["pinout"], root["system"]["esp_type"], root["system"]);
parseNrfRadio(root["radioNrf"], root["pinout"], root["system"]["esp_type"], root["system"]);
/*IF_ESP32*/
parseCmtRadio(root["radioCmt"], root["system"]["esp_type"], root["system"]);
/*ENDIF_ESP32*/
if(root["generic"]["esp_type"] == "ESP32")
parseCmtRadio(root["radioCmt"], root["system"]["esp_type"], root["system"]);
parsezeroExport(root["zeroExport"], root["system"]["esp_type"]);
parseSerial(root["serial"]);
parseDisplay(root["display"], root["system"]["esp_type"], root["system"]);

6
src/web/html/style.css

@ -20,6 +20,10 @@ fieldset, input[type=submit], .btn {
border-radius: 4px;
}
input[type=file] {
width: 100%;
}
#live span {
color: var(--fg2);
}
@ -760,7 +764,7 @@ h5 {
.badge {
display: inline-block;
padding: .25em .4em;
font-size: 75%;
font-size: 85%;
font-weight: 700;
line-height: 1;
text-align: center;

92
src/web/html/system.html

@ -8,10 +8,7 @@
{#HTML_NAV}
<div id="wrapper">
<div id="content">
<pre id="stat"></pre>
<div id="info" class="col-sm-12 col-md-6 mt-3"></div>
<div id="radio" class="col-sm-12 col-md-6 mt-3"></div>
<div id="sun" class="col-sm-12 col-md-6 mt-3"></div>
<div id="html" class="mt-3 mb-3"></div>
</div>
</div>
@ -49,8 +46,8 @@
}
}
function badge(success, text) {
return ml("span", {class: "badge badge-" + ((success) ? "success" : "error")}, text);
function badge(success, text, second="error") {
return ml("span", {class: "badge badge-" + ((success) ? "success" : second)}, text);
}
function headline(text) {
@ -66,21 +63,35 @@
]);
}
function parseRadio(obj, stat) {
function parseRadio(obj) {
const pa = ["MIN (recommended)", "LOW", "HIGH", "MAX"];
const datarate = ["1 MBps", "2 MBps", "250 kbps"];
const dr = ["1 M", "2 M", "250 k"]
document.getElementById("radio").append(
headline("NRF Radio"),
ml("table", {class: "table"}, [
ml("tbody", {}, [
tr("NRF24L01", badge(obj.isconnected, ((obj.isconnected) ? "" : "not ") + "connected")),
tr("Power Level", pa[obj.power_level])
])
]),
if(obj.radioNrf.en)
lines = [
tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "not ") + "connected")),
tr("NRF24 Power Level", pa[obj.radioNrf.power_level]),
tr("NRF24 Data Rate", dr[obj.radioNrf.dataRate] + "bps")
];
else
lines = [tr("NRF24L01", badge(false, "not enabled"))];
/*IF_ESP32*/
if(obj.radioCmt.en)
lines.push(tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "not ") + "connected")));
else
lines.push(tr("CMT2300A", badge(false, "not enabled")));
/*ENDIF_ESP32*/
var stat = obj.statistics;
document.getElementById("info").append(
headline("Radio"),
ml("table", {class: "table"},
ml("tbody", {}, lines)
),
headline("Statistics"),
ml("table", {class: "table"}, [
ml("table", {class: "table"},
ml("tbody", {}, [
tr("TX count", stat.tx_cnt),
tr("RX success", stat.rx_success),
@ -89,24 +100,42 @@
tr("RX fragments", stat.frame_cnt),
tr("TX retransmits", stat.retransmits)
])
])
)
);
}
function parseMqtt(obj) {
if(obj.enabled) {
lines = [
tr("connected", badge(obj.connected, ((obj.connected) ? "true" : "false"))),
tr("#TX", obj.tx_cnt),
tr("#RX", obj.rx_cnt)
];
} else
lines = tr("enabled", badge(false, "false"));
document.getElementById("info").append(
headline("MqTT"),
ml("table", {class: "table"},
ml("tbody", {}, lines)
)
);
}
function parseIndex(obj) {
if(obj["ts_sunrise"] > 0) {
var h = div(["head", "p-2"]);
var r = div(["row"]);
r.appendChild(div(["col", "a-c"], "Sun"));
h.appendChild(r);
document.getElementById("sun").append (
h,
genTabRow("Sunrise", new Date(obj["ts_sunrise"] * 1000).toLocaleString('de-DE')),
genTabRow("Sunset", new Date(obj["ts_sunset"] * 1000).toLocaleString('de-DE')),
genTabRow("Communication start", new Date((obj["ts_sunrise"] - obj["ts_offset"]) * 1000).toLocaleString('de-DE')),
genTabRow("Communication stop", new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')),
genTabRow("Night Communication", ((obj["disNightComm"]) ? "disabled" : "enabled"))
if(obj.ts_sunrise > 0) {
document.getElementById("info").append(
headline("Sun"),
ml("table", {class: "table"},
ml("tbody", {}, [
tr("Sunrise", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')),
tr("Sunset", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')),
tr("Communication start", new Date((obj.ts_sunrise - obj.ts_offset) * 1000).toLocaleString('de-DE')),
tr("Communication stop", new Date((obj.ts_sunset + obj.ts_offset) * 1000).toLocaleString('de-DE')),
tr("Night behaviour", badge(obj.disNightComm, ((obj.disNightComm) ? "not" : "") + " communicating", "warning"))
])
)
);
}
}
@ -122,8 +151,9 @@
document.getElementsByTagName('head')[0].appendChild(meta);
}
else {
parseRadio(obj.system);
parseMqtt(obj.system.mqtt);
parseSysInfo(obj["system"]);
parseRadio(obj["system"]["radio"], obj["system"]["statistics"]);
getAjax('/api/index', parseIndex);
}
document.getElementById("html").innerHTML = obj["html"];

2
src/web/html/visualization.html

@ -106,7 +106,7 @@
ml("div", {class: "col mx-2 mx-md-1"}, ml("span", { class: "pointer", onclick: function() {
getAjax("/api/inverter/version/" + obj.id, parseIvVersion);
}}, obj.name)),
ml("div", {class: "col a-c"}, "Power limit " + ((obj.power_limit_read == 65535) ? "n/a" : (obj.power_limit_read + "&nbsp;%"))),
ml("div", {class: "col a-c"}, "Active Power Control: " + ((obj.power_limit_read == 65535) ? "n/a" : (obj.power_limit_read + "&nbsp;%"))),
ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() {
getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm);
}}, ("Alarms: " + obj.alarm_cnt))),

31
src/web/web.h

@ -38,7 +38,7 @@
#define WEB_SERIAL_BUF_SIZE 2048
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinCsb", "pinFcsb", "pinGpio3"};
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinCmtSclk", "pinSdio", "pinCsb", "pinFcsb", "pinGpio3"};
template <class HMSYSTEM>
class Web {
@ -555,9 +555,11 @@ class Web {
case 6: mConfig->led.led0 = pin; break;
case 7: mConfig->led.led1 = pin; break;
case 8: mConfig->led.led_high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense
case 9: mConfig->cmt.pinCsb = pin; break;
case 10: mConfig->cmt.pinFcsb = pin; break;
case 11: mConfig->cmt.pinIrq = pin; break;
case 9: mConfig->cmt.pinSclk = pin; break;
case 10: mConfig->cmt.pinSdio = pin; break;
case 11: mConfig->cmt.pinCsb = pin; break;
case 12: mConfig->cmt.pinFcsb = pin; break;
case 13: mConfig->cmt.pinIrq = pin; break;
}
}
@ -811,16 +813,31 @@ class Web {
// This is the correct field to report
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec));
// Declare metric only once
if (!metricDeclared) {
if (channel != 0 && !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);
char total[7];
total[0] = 0;
if (metricDeclared) {
// A declaration and value for channels has been delivered. So declare and deliver a _total metric
strncpy(total,"_total",sizeof(total));
}
snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s%s %s\n", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str());
metrics += type;
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,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]);
// Use a fallback channel name (ch0, ch1, ...)if non is given by user
char chName[MAX_NAME_LENGTH];
if (iv->config->chName[channel-1][0] != 0) {
strncpy(chName, iv->config->chName[channel-1], sizeof(chName));
} else {
snprintf(chName,sizeof(chName),"ch%1d",channel);
}
snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName);
}
snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec));
metrics += topic;

6
tools/fonts/fontconv.bat

@ -0,0 +1,6 @@
.\bdfconv\bdfconv_2_22.exe -v -f 1 -m "32-137" u8g2_font_5x8_symbols_ahoy.bdf -o u8g2_font_5x8_symbols_ahoy.c_ -n u8g2_font_5x8_symbols_ahoy
.\bdfconv\bdfconv_2_22.exe -v -f 1 -m "65-75" u8g2_font_ncenB10_symbols10_ahoy.bdf -o u8g2_font_ncenB10_symbols10_ahoy.c_ -n u8g2_font_ncenB10_symbols10_ahoy
.\bdfconv\bdfconv_2_22.exe -v -f 1 -m "65-75" u8g2_font_ncenB08_symbols8_ahoy.bdf -o u8g2_font_ncenB08_symbols8_ahoy.c_ -n u8g2_font_ncenB08_symbols8_ahoy
pause

11
tools/fonts/u8g2 font-sources.txt

@ -0,0 +1,11 @@
Useful sources to edit u8g2 fonts:
bdf font files for u8g2 font library:
https://github.com/olikraus/u8g2/tree/master/tools/font/bdf
Tool to edit bdf files:
https://github.com/olikraus/u8g2/tree/master/tools/font/fony
Tool to convert bdf font files to u8g2 source code:
https://github.com/olikraus/u8g2/tree/master/tools/font/bdfconv

1328
tools/fonts/u8g2_font_5x8_symbols_ahoy.bdf

File diff suppressed because it is too large

40
tools/fonts/u8g2_font_5x8_symbols_ahoy.c_

@ -0,0 +1,40 @@
/*
Fontname: u8g2_font_5x8_symbols_ahoy
Copyright: Public domain font. Share and enjoy.
Glyphs: 106/106
BBX Build Mode: 0
*/
const uint8_t u8g2_font_5x8_symbols_ahoy[1052] U8G2_FONT_SECTION("u8g2_font_5x8_symbols_ahoy") =
"j\0\3\2\4\4\3\4\5\10\10\0\377\6\377\6\0\1\61\2b\4\3 \5\0\304\11!\7a\306"
"\212!\11\42\7\63\335\212\304\22#\16u\304\232R\222\14JePJI\2$\14u\304\252l\251m"
"I\262E\0%\10S\315\212(\351\24&\13t\304\232(i\252\64%\1'\6\61\336\212\1(\7b"
"\305\32\245))\11b\305\212(\251(\0*\13T\304\212(Q\206D\211\2+\12U\304\252\60\32\244"
"\60\2,\7\63\275\32\245\4-\6\24\324\212!.\6\42\305\212!/\10d\304\272R[\6\60\14d"
"\304\32%R\206DJ\24\0\61\10c\305\232Dj\31\62\13d\304\32%\312\22%\33\2\63\13d\304"
"\212!\212D)Q\0\64\13d\304\252H\251\14Q\226\0\65\12d\304\212A\33\245D\1\66\13d\304"
"\32%[\42)Q\0\67\13d\304\212!\213\262(\213\0\70\14d\304\32%J\224HJ\24\0\71\13"
"d\304\32%\222\222-Q\0:\10R\305\212!\32\2;\10c\275\32\243R\2<\10c\305\252\244\224"
"\25=\10\64\314\212!\34\2>\11c\305\212\254\224\224\0?\11c\305\232\246$M\0@\15\205\274*"
")\222\226DI\244\252\2A\12d\304\32%\222\206I\12B\14d\304\212%\32\222H\32\22\0C\12"
"d\304\32%\322J\211\2D\12d\304\212%r\32\22\0E\12d\304\212A[\262l\10F\12d\304"
"\212A[\262\32\0G\13d\304\32%\322\222)Q\0H\12d\304\212H\32&S\0I\10c\305\212"
"%j\31J\12d\304\232)\253\224\42\0K\13d\304\212HI\244\244S\0L\10d\304\212\254\333\20"
"M\12d\304\212h\70D\246\0N\12d\304\212h\31\226I\12O\12d\304\32%rJ\24\0P\13"
"d\304\212%\222\206$\313\0Q\12t\274\32%\222\26\307\0R\13d\304\212%\222\206$\222\2S\14"
"d\304\32%J\302$J\24\0T\11e\304\212A\12;\1U\11d\304\212\310S\242\0V\12d\304"
"\212\310)\221\24\0W\12d\304\212\310\64\34\242\0X\13d\304\212HJ$%\222\2Y\12e\304\212"
"LKja\11Z\12d\304\212!\213\332\206\0[\10c\305\212!j\32\134\10d\304\212,l\13]"
"\10c\305\212\251i\10^\6#\345\232\6_\6\24\274\212!`\6\42\345\212(a\11D\304\232!\222"
"\222\1b\13d\304\212,[\42iH\0c\7C\305\232)\23d\12d\304\272\312\20I\311\0e\11"
"D\304\32%\31\262\1f\12d\304\252Ji\312\42\0g\12T\274\32%J\266D\1h\12d\304\212"
",[\42S\0i\10c\305\232P\252\14j\12s\275\252\64\212\224\12\0k\12d\304\212\254\64$\221"
"\24l\10c\305\12\251\313\0m\12E\304\12\245EI\224\2n\10D\304\212%\62\5o\11D\304\32"
"%\222\22\5p\12T\274\212%\32\222,\3q\11T\274\232!J\266\2r\11D\304\212$\261e\0"
"s\10C\305\232![\0t\13d\304\232,\232\262$J\0u\10D\304\212\310\224\14v\10C\305\212"
"\304R\1w\12E\304\212LI\224.\0x\11D\304\212(\221\224(y\13T\274\212HJ\206(Q"
"\0z\11D\304\212!*\15\1{\12t\304*%L\304(\24|\6a\306\212\3}\13t\304\12\61"
"\12\225\60\221\0~\10$\344\232DI\0\5\0\304\12\200\13u\274\212K\242T\266\260\4\201\14f"
"D\233!\11#-\312!\11\202\15hD<\65\12\243,\214\302$\16\203\15w<\214C\22F\71\220"
"\26\207A\204\16\205\274\212,)%Y\230%QR\13\205\17\206<\213\60\31\22\311\66D\245!\11\3"
"\206\20\210<\254\342\20]\302(L\246C\30E\0\207\15wD\334X\25\267\341\20\15\21\0\210\16w"
"<\214\203RQ\25I\212\324a\20\211\15f\304\213)\213\244,\222\222\245\0\0\0\0";

BIN
tools/fonts/u8g2_font_5x8_symbols_ahoy.fon

Binary file not shown.

166
tools/fonts/u8g2_font_ncenB08_symbols8_ahoy.bdf

@ -0,0 +1,166 @@
STARTFONT 2.1
COMMENT Exported by Fony v1.4.7
FONT u8g2_font_ncenB08_symbols8_ahoy
SIZE 12 100 100
FONTBOUNDINGBOX 9 11 0 -2
STARTPROPERTIES 6
COPYRIGHT ""
RESOLUTION_X 100
RESOLUTION_Y 100
FONT_ASCENT 10
FONT_DESCENT 2
DEFAULT_CHAR 0
ENDPROPERTIES
CHARS 11
STARTCHAR 065
ENCODING 65
SWIDTH 576 0
DWIDTH 8 0
BBX 7 8 0 0
BITMAP
FE
92
D6
38
10
10
10
10
ENDCHAR
STARTCHAR 066
ENCODING 66
SWIDTH 648 0
DWIDTH 9 0
BBX 8 9 1 0
BITMAP
3C
42
99
24
42
18
24
00
18
ENDCHAR
STARTCHAR 067
ENCODING 67
SWIDTH 576 0
DWIDTH 8 0
BBX 8 8 0 0
BITMAP
18
24
24
42
42
42
42
81
ENDCHAR
STARTCHAR 068
ENCODING 68
SWIDTH 648 0
DWIDTH 9 0
BBX 8 8 0 0
BITMAP
FF
41
20
10
10
20
41
FF
ENDCHAR
STARTCHAR 069
ENCODING 69
SWIDTH 576 0
DWIDTH 8 0
BBX 7 8 0 0
BITMAP
FE
D6
FE
38
10
38
54
92
ENDCHAR
STARTCHAR 070
ENCODING 70
SWIDTH 648 0
DWIDTH 9 0
BBX 8 9 1 0
BITMAP
BD
42
BD
3C
5A
18
24
42
99
ENDCHAR
STARTCHAR 071
ENCODING 71
SWIDTH 648 0
DWIDTH 9 0
BBX 8 8 0 0
BITMAP
24
3C
E7
42
42
E7
3C
24
ENDCHAR
STARTCHAR 072
ENCODING 72
SWIDTH 576 0
DWIDTH 8 0
BBX 7 7 0 1
BITMAP
04
06
06
0E
1E
FC
78
ENDCHAR
STARTCHAR 073
ENCODING 73
SWIDTH 576 0
DWIDTH 8 0
BBX 7 9 0 0
BITMAP
44
FE
82
92
B2
92
92
82
FE
ENDCHAR
STARTCHAR 074
ENCODING 74
SWIDTH 504 0
DWIDTH 7 0
BBX 0 0 0 0
BITMAP
ENDCHAR
STARTCHAR 075
ENCODING 75
SWIDTH 648 0
DWIDTH 9 0
BBX 0 0 0 0
BITMAP
ENDCHAR
ENDFONT

13
tools/fonts/u8g2_font_ncenB08_symbols8_ahoy.c_

@ -0,0 +1,13 @@
/*
Fontname: u8g2_font_ncenB08_symbols8_ahoy
Copyright:
Glyphs: 11/11
BBX Build Mode: 0
*/
const uint8_t u8g2_font_ncenB08_symbols8_ahoy[173] U8G2_FONT_SECTION("u8g2_font_ncenB08_symbols8_ahoy") =
"\13\0\3\2\4\4\2\2\5\11\11\0\0\10\0\10\0\0\0\0\0\0\224A\14\207\212q\220\242%\221"
"\326\270\15B\20\230\233\65da\22Ima\250F\71\254\1C\20\210\212\247Fa\224\205Q\30\205Q"
"\230\304\1D\16\210\232qP\322(Gr \256\16\7E\15\207\212\361\222\14\247\65\335\222\246\2F\25"
"\230\233\221\14I\61I\206$\32\262D\11\325(\13\223H\12G\17\210\232U\34\242K\30\205\311t\10"
"\243\10H\14w\216\33\253\342\66\34\242!\2I\21\227\212\223%\303\240J\221\42I\221\24\251\303 J"
"\5\0z\1K\5\0\232\1\0\0\0";

BIN
tools/fonts/u8g2_font_ncenB08_symbols8_ahoy.fon

Binary file not shown.

188
tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.bdf

@ -0,0 +1,188 @@
STARTFONT 2.1
COMMENT Exported by Fony v1.4.7
FONT u8g2_font_symbols10_ahoy
SIZE 16 100 100
FONTBOUNDINGBOX 12 15 0 -3
STARTPROPERTIES 6
COPYRIGHT ""
RESOLUTION_X 100
RESOLUTION_Y 100
FONT_ASCENT 13
FONT_DESCENT 3
DEFAULT_CHAR 0
ENDPROPERTIES
CHARS 11
STARTCHAR 065
ENCODING 65
SWIDTH 576 0
DWIDTH 8 0
BBX 7 11 0 0
BITMAP
FE
92
92
54
38
10
10
10
10
10
10
ENDCHAR
STARTCHAR 066
ENCODING 66
SWIDTH 648 0
DWIDTH 9 0
BBX 8 9 0 1
BITMAP
3C
42
99
24
42
18
24
00
18
ENDCHAR
STARTCHAR 067
ENCODING 67
SWIDTH 792 0
DWIDTH 11 0
BBX 10 11 0 0
BITMAP
0C00
1200
2100
2100
2100
2180
4080
4080
4080
4080
8040
ENDCHAR
STARTCHAR 068
ENCODING 68
SWIDTH 720 0
DWIDTH 10 0
BBX 9 11 0 0
BITMAP
FF80
6080
3000
1800
0C00
0C00
1800
3000
6000
C080
FF80
ENDCHAR
STARTCHAR 069
ENCODING 69
SWIDTH 576 0
DWIDTH 8 0
BBX 7 11 0 0
BITMAP
FE
D6
D6
7C
38
10
38
38
54
54
92
ENDCHAR
STARTCHAR 070
ENCODING 70
SWIDTH 648 0
DWIDTH 9 0
BBX 8 10 1 0
BITMAP
BD
42
BD
24
5A
18
24
24
5A
81
ENDCHAR
STARTCHAR 071
ENCODING 71
SWIDTH 864 0
DWIDTH 12 0
BBX 11 11 0 0
BITMAP
1100
1100
0E00
D160
2080
2080
2080
D160
0E00
1100
1100
ENDCHAR
STARTCHAR 072
ENCODING 72
SWIDTH 792 0
DWIDTH 11 0
BBX 10 11 0 0
BITMAP
0080
0080
00C0
00C0
01C0
01C0
03C0
0780
1F80
FF00
3C00
ENDCHAR
STARTCHAR 073
ENCODING 73
SWIDTH 720 0
DWIDTH 10 0
BBX 9 11 0 0
BITMAP
DD80
FF80
8080
8880
9880
8880
8880
8880
8880
8080
FF80
ENDCHAR
STARTCHAR 074
ENCODING 74
SWIDTH 648 0
DWIDTH 9 0
BBX 0 0 0 0
BITMAP
ENDCHAR
STARTCHAR 075
ENCODING 75
SWIDTH 648 0
DWIDTH 9 0
BBX 0 0 0 0
BITMAP
ENDCHAR
ENDFONT

14
tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.c_

@ -0,0 +1,14 @@
/*
Fontname: u8g2_font_symbols10_ahoy
Copyright:
Glyphs: 11/11
BBX Build Mode: 0
*/
const uint8_t u8g2_font_ncenB10_symbols10_ahoy[207] U8G2_FONT_SECTION("u8g2_font_ncenB10_symbols10_ahoy") =
"\13\0\3\2\4\4\2\2\5\13\13\0\0\13\0\13\0\0\0\0\0\0\266A\15\267\212q\220\42\251\322"
"\266\306\275\1B\20\230\236\65da\22Ima\250F\71\254\1C\23\272\272\251\3Q\32\366Q\212\243"
"\70\212\243\70\311\221\0D\20\271\252\361\242F:\242#: {\36\16\1E\22\267\212\361\222\14I\242"
"\14\332\232\216[RJ\232\12F\25\250\233\221\14I\61I\206$\252%J\250Fa\224%J\71G\30"
"\273\312W\316r`T\262DJ\303\64L#%K\304\35\310\342,\3H\27\272\272\217\344P\16\351\210"
"\16\354\300<\244C\70,\303 \16!\0I\24\271\252\241\34\336\1-\223\64-\323\62-\323\62\35x"
"\10J\5\0\232\1K\5\0\232\1\0\0\0";

BIN
tools/fonts/u8g2_font_ncenB10_symbols10_ahoy.fon

Binary file not shown.

16
tools/fonts/used_fonts.txt

@ -0,0 +1,16 @@
Display_Mono_64x48:
u8g2_font_fur11_tr
u8g2_font_6x10_tf
u8g2_font_4x6_tr
Display_Mono_128x32:
u8g2_font_9x15_tr
u8g2_font_tom_thumb_4x6_tr
Display_Mono_84x48:
u8g2_font_5x8_symbols_ahoy
u8g2_font_logisoso16_tr
Display_Mono_128x64:
u8g2_font_ncenB08_symbols8_ahoy
u8g2_font_ncenB10_symbols10_ahoy
Loading…
Cancel
Save