Browse Source

Merge branch 'main' of github.com:modem-man-gmx/ahoy

pull/591/head
Mo Demman 2 years ago
parent
commit
1d8100fc07
  1. 2
      .github/workflows/compile_development.yml
  2. 2
      .github/workflows/compile_release.yml
  3. 2
      User_Manual.md
  4. 24
      scripts/getVersion.py
  5. 57
      src/CHANGES.md
  6. 71
      src/app.cpp
  7. 14
      src/app.h
  8. 4
      src/appInterface.h
  9. 6
      src/config/settings.h
  10. 2
      src/defines.h
  11. 127
      src/hm/payload.h
  12. 36
      src/platformio.ini
  13. 20
      src/plugins/MonochromeDisplay/MonochromeDisplay.h
  14. 53
      src/publisher/pubMqtt.h
  15. 25
      src/web/RestApi.h
  16. 2
      src/web/html/index.html
  17. 11
      src/web/html/setup.html
  18. 1
      src/web/web.h

2
.github/workflows/compile_development.yml

@ -47,7 +47,7 @@ jobs:
run: python convert.py
- name: Run PlatformIO
run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306
run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp8266-sh1106 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 --environment esp32-wroom32-sh1106
- name: Rename Binary files
id: rename-binary-files

2
.github/workflows/compile_release.yml

@ -51,7 +51,7 @@ jobs:
run: python convert.py
- name: Run PlatformIO
run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306
run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp8266-sh1106 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 --environment esp32-wroom32-sh1106
- name: Rename Binary files
id: rename-binary-files

2
User_Manual.md

@ -29,6 +29,7 @@ The AhoyDTU will publish on the following topics
| `uptime` | 73630 | uptime in seconds | false |
| `version` | 0.5.61 | current installed verison of AhoyDTU | true |
| `wifi_rssi` | -75 | WiFi signal strength | false |
| `ip_addr` | 192.168.178.25 | WiFi Station IP Address | true |
| status code | Remarks |
|---|---|
@ -43,6 +44,7 @@ The AhoyDTU will publish on the following topics
|---|---|---|---|
| `available` | 2 | see table below | true |
| `last_success` | 1672155690 | UTC Timestamp | true |
| `ack_pwr_limit` | true | fast information if inverter has accepted power limit | false |
| status code | Remarks |
|---|---|

24
scripts/getVersion.py

@ -52,42 +52,52 @@ def readVersion(path, infile):
os.mkdir(path + "firmware/")
sha = os.getenv("SHA",default="sha")
versionout = version[:-1] + "_esp8266_" + sha + ".bin"
versionout = version[:-1] + "_" + sha + "_esp8266.bin"
src = path + ".pio/build/esp8266-release/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_esp8266_nokia5110_" + sha + ".bin"
versionout = version[:-1] + "_" + sha + "_esp8266_nokia5110.bin"
src = path + ".pio/build/esp8266-nokia5110/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_esp8266_ssd1306_" + sha + ".bin"
versionout = version[:-1] + "_" + sha + "_esp8266_ssd1306.bin"
src = path + ".pio/build/esp8266-ssd1306/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp8266_sh1106.bin"
src = path + ".pio/build/esp8266-sh1106/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_esp8285_" + sha + ".bin"
versionout = version[:-1] + "_" + sha + "_esp8285.bin"
src = path + ".pio/build/esp8285-release/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
gzip_bin(dst, dst + ".gz")
versionout = version[:-1] + "_esp32_" + sha + ".bin"
versionout = version[:-1] + "_" + sha + "_esp32.bin"
src = path + ".pio/build/esp32-wroom32-release/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_esp32_nokia5110_" + sha + ".bin"
versionout = version[:-1] + "_" + sha + "_esp32_nokia5110.bin"
src = path + ".pio/build/esp32-wroom32-nokia5110/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_esp32_ssd1306_" + sha + ".bin"
versionout = version[:-1] + "_" + sha + "_esp32_ssd1306.bin"
src = path + ".pio/build/esp32-wroom32-ssd1306/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32_sh1106.bin"
src = path + ".pio/build/esp32-wroom32-sh1106/firmware.bin"
dst = path + "firmware/" + versionout
os.rename(src, dst)
# other ESP32 bin files
src = path + ".pio/build/esp32-wroom32-release/"
dst = path + "firmware/"

57
src/CHANGES.md

@ -1,36 +1,27 @@
# Changelog v0.5.66
# Changelog
**Note:** Version `0.5.42` to `0.5.65` were development versions. Last release version was `0.5.41`
Detailed change log (development changes): https://github.com/lumapu/ahoy/blob/945a671d27d10d0f7c175ebbf2fbb2806f9cd79a/src/CHANGES.md
(starting from release version `0.5.66`)
## 0.5.70
* corrected MQTT `comm_disabled` #529
* updated REST API and MQTT (both of them use the same functionality)
* improved stability
* Regular expressions for input fields which are used for MQTT to be compliant to MQTT
* WiFi optimization (AP Mode and STA in parallel, reconnect if local STA is unavailable)
* improved display of `/system`
* fix Update button protection (prevent double click #527)
* optimized scheduler #515
* fix of duplicates in API `/api/record/live` (#526)
* added update information to `index.html` (check for update with github.com)
* fix web logout (auto logout)
* switched MQTT library
* removed MQTT `available_text` (can be deducted from `available`)
* enhanced MQTT documentation in `User_Manual.md`
* changed MQTT topic `status` to nummeric value, check documentation in `User_Manual.md`
* added immediate (each minute) report of inverter status MQTT #522
* increased MQTT user, pwd and topic length to 64 characters + `\0`. (The string end `\0` reduces the available size by one) #516
* added disable night communication flag to MQTT #505
* added MQTT <TOPIC>/status to show status over all inverters
* added MQTT RX counter to index.html
* added protection mask to select which pages should be protected
* added monochrome display that show values also if nothing changed and in offline mode #498
* added icons to index.html, added WiFi-strength symbol on each page
* refactored communication offset (adjustable in minutes now)
* factory reset formats entire little fs
* renamed sunrise / sunset on index.html to start / stop communication
* fixed static IP save
* fix NTP with static IP
* all values are displayed on /live even if they are 0
* added NRF24 info to Systeminfo
* reordered enqueue commands after boot up to prevent same payload length for successive commands
## 0.5.69
* merged SH1106 1.3" Display, thx @dAjaY85
* added SH1106 to automatic build
* added IP address to MQTT (version, device and IP are retained and only transmitted once after boot) #556
* added `set_power_limit` acknowledge MQTT publish #553
* changed: version, device name are only published via MQTT once after boot
* added `Login` to menu if admin password is set #554
* added `development` to second changelog link in `index.html` #543
* added interval for MQTT (as option). With this settings MQTT live data is published in a fixed timing (only if inverter is available) #542, #523
* added MQTT `comm_disabled` #529
* changed name of binaries, moved GIT-Sha to the front #538
## 0.5.68
* repaired receive payload
* Powerlimit is transfered immediately to inverter
## 0.5.67
* changed calculation of start / stop communication to 1 min after last comm. stop #515
* moved payload send to `payload.h`, function `ivSend` #515
* payload: if last frame is missing, request all frames again

71
src/app.cpp

@ -51,7 +51,7 @@ void app::setup() {
#endif
mSys->addInverters(&mConfig->inst);
mPayload.setup(mSys);
mPayload.setup(this, mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mPayload.enableSerialDebug(mConfig->serial.debug);
if(!mSys->Radio.isChipConnected())
@ -74,7 +74,7 @@ void app::setup() {
mApi.setup(this, mSys, mWeb.getWebSrvPtr(), mConfig);
// Plugins
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106)
mMonoDisplay.setup(mSys, &mTimestamp);
everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay));
#endif
@ -90,6 +90,7 @@ void app::loop(void) {
ah::Scheduler::loop();
mSys->Radio.loop();
mPayload.loop();
yield();
@ -115,7 +116,7 @@ void app::loop(void) {
yield();
if (rxRdy)
mPayload.process(true, mConfig->nrf.maxRetransPerPyld, &mStat);
mPayload.process(true);
}
#if !defined(AP_ONLY)
@ -138,14 +139,18 @@ void app::tickNtpUpdate(void) {
//-----------------------------------------------------------------------------
void app::tickCalcSunrise(void) {
ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset);
if (mSunrise == 0) // on boot/reboot calc sun values for current time
ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset);
if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) // current time is past communication stop, calc sun values for next day
ah::calculateSunriseSunset(mTimestamp + 86400, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset);
tickIVCommunication();
uint32_t nxtTrig = mTimestamp - ((mTimestamp + mCalculatedTimezoneOffset - 10) % 86400) + 86400;; // next midnight, -10 for safety that it is certain next day, local timezone
uint32_t nxtTrig = mSunset + mConfig->sun.offsetSec + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop
onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig);
if (mConfig->mqtt.broker[0] > 0) {
if (mConfig->mqtt.broker[0] > 0)
mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec, mConfig->sun.disNightCom);
}
}
//-----------------------------------------------------------------------------
@ -157,14 +162,17 @@ void app::tickIVCommunication(void) {
nxtTrig = mSunrise - mConfig->sun.offsetSec;
} else {
if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise
return;
nxtTrig = 0;
} else { // current time lies within communication start/stop time, set next trigger to communication stop
mIVCommunicationOn = true;
nxtTrig = mSunset + mConfig->sun.offsetSec;
}
}
onceAt(std::bind(&app::tickIVCommunication, this), nxtTrig);
if (nxtTrig != 0)
onceAt(std::bind(&app::tickIVCommunication, this), nxtTrig);
}
if (mConfig->mqtt.broker[0] > 0)
mMqtt.tickerComm(!mIVCommunicationOn);
}
//-----------------------------------------------------------------------------
@ -187,49 +195,8 @@ void app::tickSend(void) {
} while ((NULL == iv) && ((maxLoop--) > 0));
if (NULL != iv) {
if(iv->config->enabled) {
if (!mPayload.isComplete(iv))
mPayload.process(false, mConfig->nrf.maxRetransPerPyld, &mStat);
if (!mPayload.isComplete(iv)) {
if (0 == mPayload.getMaxPacketId(iv))
mStat.rxFailNoAnser++;
else
mStat.rxFail++;
iv->setQueuedCmdFinished(); // command failed
if (mConfig->serial.debug)
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
if (mConfig->serial.debug) {
DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") ");
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload.getRetransmits(iv)) + ")");
}
}
mPayload.reset(iv, mTimestamp);
mPayload.request(iv);
yield();
if (mConfig->serial.debug) {
DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()));
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX));
}
if (iv->devControlRequest) {
if (mConfig->serial.debug)
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit);
mPayload.setTxCmd(iv, iv->devControlCmd);
iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
} else {
uint8_t cmd = iv->getQueuedCmd();
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket"));
mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload.getTs(iv), iv->alarmMesIndex);
mPayload.setTxCmd(iv, cmd);
mRxTicker = 0;
}
}
if(iv->config->enabled)
mPayload.ivSend(iv);
}
} else {
if (mConfig->serial.debug)

14
src/app.h

@ -46,7 +46,7 @@ typedef PubMqtt<HmSystemType> PubMqttType;
typedef PubSerial<HmSystemType> PubSerialType;
// PLUGINS
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106)
#include "plugins/MonochromeDisplay/MonochromeDisplay.h"
typedef MonochromeDisplay<HmSystemType> MonoDisplayType;
#endif
@ -122,6 +122,14 @@ class app : public IApp, public ah::Scheduler {
once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1);
}
void setMqttPowerLimitAck(Inverter<> *iv) {
mMqtt.setPowerLimitAck(iv);
}
void ivSendHighPrio(Inverter<> *iv) {
mPayload.ivSendHighPrio(iv);
}
bool getMqttIsConnected() {
return mMqtt.isConnected();
}
@ -176,7 +184,7 @@ class app : public IApp, public ah::Scheduler {
#if !defined(AP_ONLY)
mMqtt.payloadEventListener(cmd);
#endif
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106)
mMonoDisplay.payloadEventListener(cmd);
#endif
}
@ -240,7 +248,7 @@ class app : public IApp, public ah::Scheduler {
uint32_t mSunrise, mSunset;
// plugins
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106)
MonoDisplayType mMonoDisplay;
#endif
};

4
src/appInterface.h

@ -7,6 +7,7 @@
#define __IAPP_H__
#include "defines.h"
#include "hm/hmSystem.h"
// abstract interface to App. Make members of App accessible from child class
// like web or API without forward declaration
@ -33,6 +34,9 @@ class IApp {
virtual bool getRebootRequestState() = 0;
virtual bool getSettingsValid() = 0;
virtual void setMqttDiscoveryFlag() = 0;
virtual void setMqttPowerLimitAck(Inverter<> *iv) = 0;
virtual void ivSendHighPrio(Inverter<> *iv) = 0;
virtual bool getMqttIsConnected() = 0;
virtual uint32_t getMqttRxCnt() = 0;

6
src/config/settings.h

@ -96,6 +96,7 @@ typedef struct {
char user[MQTT_USER_LEN];
char pwd[MQTT_PWD_LEN];
char topic[MQTT_TOPIC_LEN];
uint16_t interval;
} cfgMqtt_t;
typedef struct {
@ -297,6 +298,7 @@ class settings {
snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", DEF_MQTT_USER);
snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD);
snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC);
mCfg.mqtt.interval = 0; // off
mCfg.led.led0 = DEF_LED0_PIN;
mCfg.led.led1 = DEF_LED1_PIN;
@ -396,8 +398,10 @@ class settings {
obj[F("user")] = mCfg.mqtt.user;
obj[F("pwd")] = mCfg.mqtt.pwd;
obj[F("topic")] = mCfg.mqtt.topic;
obj[F("intvl")] = mCfg.mqtt.interval;
} else {
mCfg.mqtt.port = obj[F("port")];
mCfg.mqtt.port = obj[F("port")];
mCfg.mqtt.interval = obj[F("intvl")];
snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as<const char*>());
snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as<const char*>());
snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as<const char*>());

2
src/defines.h

@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 5
#define VERSION_PATCH 66
#define VERSION_PATCH 69
//-------------------------------------
typedef struct {

127
src/hm/payload.h

@ -21,6 +21,7 @@ typedef struct {
uint8_t len[MAX_PAYLOAD_ENTRIES];
bool complete;
uint8_t maxPackId;
bool lastFound;
uint8_t retransmits;
bool requested;
} invPayload_t;
@ -34,44 +35,78 @@ class Payload : public Handler<payloadListenerType> {
public:
Payload() : Handler() {}
void setup(HMSYSTEM *sys) {
mSys = sys;
void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
mApp = app;
mSys = sys;
mStat = stat;
mMaxRetrans = maxRetransmits;
mTimestamp = timestamp;
memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t)));
mLastPacketId = 0x00;
mSerialDebug = false;
mSerialDebug = false;
mHighPrioIv = NULL;
}
void enableSerialDebug(bool enable) {
mSerialDebug = enable;
}
bool isComplete(Inverter<> *iv) {
return mPayload[iv->id].complete;
void notify(uint8_t val) {
for(typename std::list<payloadListenerType>::iterator it = mList.begin(); it != mList.end(); ++it) {
(*it)(val);
}
}
uint8_t getMaxPacketId(Inverter<> *iv) {
return mPayload[iv->id].maxPackId;
void loop() {
if(NULL != mHighPrioIv) {
ivSend(mHighPrioIv, true);
mHighPrioIv = NULL;
}
}
uint8_t getRetransmits(Inverter<> *iv) {
return mPayload[iv->id].retransmits;
void ivSendHighPrio(Inverter<> *iv) {
mHighPrioIv = iv;
}
uint32_t getTs(Inverter<> *iv) {
return mPayload[iv->id].ts;
}
void ivSend(Inverter<> *iv, bool highPrio = false) {
if(!highPrio) {
if (!mPayload[iv->id].complete)
process(false);
void request(Inverter<> *iv) {
mPayload[iv->id].requested = true;
}
if (!mPayload[iv->id].complete) {
if (0 == mPayload[iv->id].maxPackId)
mStat->rxFailNoAnser++;
else
mStat->rxFail++;
iv->setQueuedCmdFinished(); // command failed
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
if (mSerialDebug) {
DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") ");
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
}
}
}
void setTxCmd(Inverter<> *iv, uint8_t cmd) {
mPayload[iv->id].txCmd = cmd;
}
reset(iv);
mPayload[iv->id].requested = true;
void notify(uint8_t val) {
for(typename std::list<payloadListenerType>::iterator it = mList.begin(); it != mList.end(); ++it) {
(*it)(val);
yield();
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX));
if (iv->devControlRequest) {
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0]));
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit);
mPayload[iv->id].txCmd = iv->devControlCmd;
iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
} else {
uint8_t cmd = iv->getQueuedCmd();
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket"));
mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex);
mPayload[iv->id].txCmd = cmd;
}
}
@ -95,7 +130,7 @@ class Payload : public Handler<payloadListenerType> {
if ((*pid & 0x7f) > mPayload[iv->id].maxPackId) {
mPayload[iv->id].maxPackId = (*pid & 0x7f);
if (*pid > 0x81)
mLastPacketId = *pid;
mPayload[iv->id].lastFound = true;
}
}
}
@ -107,7 +142,11 @@ class Payload : public Handler<payloadListenerType> {
iv->devControlRequest = false;
if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) {
String msg = (p->packet[10] == 0x00 && p->packet[11] == 0x00) ? "" : "NOT ";
String msg = "";
if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) {
msg = "NOT ";
mApp->setMqttPowerLimitAck(iv);
}
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has ") + msg + F("accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
}
iv->devControlCmd = Init;
@ -134,7 +173,7 @@ class Payload : public Handler<payloadListenerType> {
return (crc == crcRcv) ? true : false;
}
void process(bool retransmit, uint8_t maxRetransmits, statistics_t *stat) {
void process(bool retransmit) {
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id);
if (NULL == iv)
@ -142,7 +181,6 @@ class Payload : public Handler<payloadListenerType> {
if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) {
// no processing needed if txId is not 0x95
// DPRINTLN(DBG_INFO, F("processPayload - set complete, txId: ") + String(mPayload[iv->id].txId, HEX));
mPayload[iv->id].complete = true;
}
@ -152,28 +190,24 @@ class Payload : public Handler<payloadListenerType> {
if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) {
// This is required to prevent retransmissions without answer.
DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm..."));
mPayload[iv->id].retransmits = maxRetransmits;
mPayload[iv->id].retransmits = mMaxRetrans;
} else {
if (mPayload[iv->id].retransmits < maxRetransmits) {
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
if (mPayload[iv->id].maxPackId != 0) {
if(false == mPayload[iv->id].lastFound) {
DPRINTLN(DBG_WARN, F("while retrieving data: last frame missing: Request Complete Retransmit"));
mPayload[iv->id].txCmd = iv->getQueuedCmd();
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket"));
mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex);
} else {
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) {
if (mPayload[iv->id].len[i] == 0) {
DPRINTLN(DBG_WARN, F("while retrieving data: Frame ") + String(i + 1) + F(" missing: Request Retransmit"));
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
break; // only retransmit one frame per loop
break; // only request retransmit one frame per loop
}
yield();
}
} else {
DPRINTLN(DBG_WARN, F("while retrieving data: last frame missing: Request Retransmit"));
if (0x00 != mLastPacketId)
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true);
else {
mPayload[iv->id].txCmd = iv->getQueuedCmd();
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket"));
mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex);
}
}
mSys->Radio.switchRxCh(100);
}
@ -207,7 +241,7 @@ class Payload : public Handler<payloadListenerType> {
DPRINTLN(DBG_ERROR, F("record is NULL!"));
} else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) {
if (mPayload[iv->id].txId == (TX_REQ_INFO + 0x80))
stat->rxSuccess++;
mStat->rxSuccess++;
rec->ts = mPayload[iv->id].ts;
for (uint8_t i = 0; i < rec->length; i++) {
@ -218,7 +252,7 @@ class Payload : public Handler<payloadListenerType> {
notify(mPayload[iv->id].txCmd);
} else {
DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes"));
stat->rxFail++;
mStat->rxFail++;
}
iv->setQueuedCmdFinished();
@ -230,22 +264,27 @@ class Payload : public Handler<payloadListenerType> {
}
}
void reset(Inverter<> *iv, uint32_t utcTs) {
void reset(Inverter<> *iv) {
DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id));
memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES);
mPayload[iv->id].txCmd = 0;
mPayload[iv->id].retransmits = 0;
mPayload[iv->id].maxPackId = 0;
mPayload[iv->id].lastFound = false;
mPayload[iv->id].complete = false;
mPayload[iv->id].requested = false;
mPayload[iv->id].ts = utcTs;
mPayload[iv->id].ts = *mTimestamp;
}
private:
IApp *mApp;
HMSYSTEM *mSys;
statistics_t *mStat;
uint8_t mMaxRetrans;
uint32_t *mTimestamp;
invPayload_t mPayload[MAX_NUM_INVERTERS];
uint8_t mLastPacketId;
bool mSerialDebug;
Inverter<> *mHighPrioIv;
};
#endif /*__PAYLOAD_H_*/

36
src/platformio.ini

@ -125,6 +125,24 @@ lib_deps =
https://github.com/ThingPulse/esp8266-oled-ssd1306.git
https://github.com/JChristensen/Timezone
[env:esp8266-sh1106]
platform = espressif8266
board = esp12e
board_build.f_cpu = 80000000L
build_flags = -D RELEASE -DENA_SH1106
monitor_filters =
;default ; Remove typical terminal control codes from input
time ; Add timestamp with milliseconds for each new line
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer
nrf24/RF24
paulstoffregen/Time
https://github.com/bertmelis/espMqttClient#v1.3.3
bblanchon/ArduinoJson
https://github.com/ThingPulse/esp8266-oled-ssd1306.git
https://github.com/JChristensen/Timezone
[env:esp32-wroom32-release]
platform = espressif32
board = lolin_d32
@ -181,3 +199,21 @@ lib_deps =
bblanchon/ArduinoJson
https://github.com/ThingPulse/esp8266-oled-ssd1306.git
https://github.com/JChristensen/Timezone
[env:esp32-wroom32-sh1106]
platform = espressif32
board = lolin_d32
build_flags = -D RELEASE -std=gnu++14 -DENA_SH1106
build_unflags = -std=gnu++11
monitor_filters =
;default ; Remove typical terminal control codes from input
time ; Add timestamp with milliseconds for each new line
;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory
lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer
nrf24/RF24
paulstoffregen/Time
https://github.com/bertmelis/espMqttClient#v1.3.3
bblanchon/ArduinoJson
https://github.com/ThingPulse/esp8266-oled-ssd1306.git
https://github.com/JChristensen/Timezone

20
src/plugins/MonochromeDisplay/MonochromeDisplay.h

@ -1,15 +1,19 @@
#ifndef __MONOCHROME_DISPLAY__
#define __MONOCHROME_DISPLAY__
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106)
#ifdef ENA_NOKIA
#include <U8g2lib.h>
#define DISP_PROGMEM U8X8_PROGMEM
#else // ENA_SSD1306
#else // ENA_SSD1306 || ENA_SH1106
/* esp8266 : SCL = 5, SDA = 4 */
/* ewsp32 : SCL = 22, SDA = 21 */
#include <Wire.h>
#include <SSD1306Wire.h>
#ifdef ENA_SSD1306
#include <SSD1306Wire.h>
# else //ENA_SH1106
#include <SH1106Wire.h>
#endif
#define DISP_PROGMEM PROGMEM
#endif
@ -34,7 +38,7 @@ class MonochromeDisplay {
mNewPayload = false;
mExtra = 0;
}
#else // ENA_SSD1306
#else // ENA_SSD1306 || ENA_SH1106
MonochromeDisplay() : mDisplay(0x3c, SDA, SCL), mCE(CEST, CET) {
mNewPayload = false;
mExtra = 0;
@ -215,7 +219,7 @@ class MonochromeDisplay {
mDisplay.sendBuffer();
} while( mDisplay.nextPage() );
mExtra++;
#else // ENA_SSD1306
#else // ENA_SSD1306 || ENA_SH1106
if(mUp) {
mRx += 2;
if(mRx >= 20)
@ -289,7 +293,11 @@ class MonochromeDisplay {
#if defined(ENA_NOKIA)
U8G2_PCD8544_84X48_1_4W_HW_SPI mDisplay;
#else // ENA_SSD1306
SSD1306Wire mDisplay;
#ifdef ENA_SSD1306
SSD1306Wire mDisplay;
# else //ENA_SH1106
SH1106Wire mDisplay;
#endif
int mRx;
char mUp;
#endif

53
src/publisher/pubMqtt.h

@ -41,11 +41,13 @@ class PubMqtt {
~PubMqtt() { }
void setup(cfgMqtt_t *cfg_mqtt, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs) {
mCfgMqtt = cfg_mqtt;
mDevName = devName;
mVersion = version;
mSys = sys;
mUtcTimestamp = utcTs;
mCfgMqtt = cfg_mqtt;
mDevName = devName;
mVersion = version;
mSys = sys;
mUtcTimestamp = utcTs;
mExeOnce = true;
mIntervalTimeout = 1;
snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic);
@ -73,7 +75,16 @@ class PubMqtt {
}
void tickerSecond() {
sendIvData();
if(0 == mCfgMqtt->interval) // no fixed interval, publish once new data were received (from inverter)
sendIvData();
else { // send mqtt data in a fixed interval
if(--mIntervalTimeout == 0) {
mIntervalTimeout = mCfgMqtt->interval;
mSendList.push(RealTimeRunData_Debug);
sendIvData();
}
}
}
void tickerMinute() {
@ -98,9 +109,16 @@ class PubMqtt {
publish("dis_night_comm", ((disNightCom) ? "true" : "false"), true);
}
void tickerComm(bool disabled) {
publish("comm_disabled", ((disabled) ? "true" : "false"), true);
publish("comm_dis_ts", String(*mUtcTimestamp).c_str(), true);
}
void payloadEventListener(uint8_t cmd) {
if(mClient.connected()) // prevent overflow if MQTT broker is not reachable but set
mSendList.push(cmd);
if(mClient.connected()) { // prevent overflow if MQTT broker is not reachable but set
if((0 == mCfgMqtt->interval) || (RealTimeRunData_Debug != cmd)) // no interval or no live data
mSendList.push(cmd);
}
}
void publish(const char *subTopic, const char *payload, bool retained = false, bool addTopic = true) {
@ -188,6 +206,15 @@ class PubMqtt {
}
}
void setPowerLimitAck(Inverter<> *iv) {
if (NULL != iv) {
char topic[7 + MQTT_TOPIC_LEN];
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ack_pwr_limit", iv->config->name);
publish(topic, "true", true);
}
}
private:
#if defined(ESP8266)
void onWifiConnect(const WiFiEventStationModeGotIP& event) {
@ -223,8 +250,12 @@ class PubMqtt {
DPRINTLN(DBG_INFO, F("MQTT connected"));
mEnReconnect = true;
publish("version", mVersion, true);
publish("device", mDevName, true);
if(mExeOnce) {
publish("version", mVersion, true);
publish("device", mDevName, true);
publish("ip_addr", WiFi.localIP().toString().c_str(), true);
mExeOnce = false;
}
tickerMinute();
publish(mLwtTopic, mLwtOnline, true, false);
@ -494,6 +525,8 @@ class PubMqtt {
subscriptionCb mSubscriptionCb;
bool mIvAvail; // shows if at least one inverter is available
uint8_t mLastIvState[MAX_NUM_INVERTERS];
bool mExeOnce;
uint16_t mIntervalTimeout;
// last will topic and payload must be available trough lifetime of 'espMqttClient'
char mLwtTopic[MQTT_TOPIC_LEN+5];

25
src/web/RestApi.h

@ -276,11 +276,12 @@ class RestApi {
}
void getMqtt(JsonObject obj) {
obj[F("broker")] = String(mConfig->mqtt.broker);
obj[F("port")] = String(mConfig->mqtt.port);
obj[F("user")] = String(mConfig->mqtt.user);
obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String("");
obj[F("topic")] = String(mConfig->mqtt.topic);
obj[F("broker")] = String(mConfig->mqtt.broker);
obj[F("port")] = String(mConfig->mqtt.port);
obj[F("user")] = String(mConfig->mqtt.user);
obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String("");
obj[F("topic")] = String(mConfig->mqtt.topic);
obj[F("interval")] = String(mConfig->mqtt.interval);
}
void getNtp(JsonObject obj) {
@ -357,10 +358,15 @@ class RestApi {
obj[F("name")][i] = "Documentation";
obj[F("link")][i] = "https://ahoydtu.de";
obj[F("trgt")][i++] = "_blank";
if((strlen(mConfig->sys.adminPwd) > 0) && !mApp->getProtection()) {
if(strlen(mConfig->sys.adminPwd) > 0) {
obj[F("name")][i++] = "-";
obj[F("name")][i] = "Logout";
obj[F("link")][i++] = "/logout";
if(mApp->getProtection()) {
obj[F("name")][i] = "Login";
obj[F("link")][i++] = "/login";
} else {
obj[F("name")][i] = "Logout";
obj[F("link")][i++] = "/logout";
}
}
}
@ -411,6 +417,8 @@ class RestApi {
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(JsonObject obj) {
@ -534,6 +542,7 @@ class RestApi {
iv->powerLimit[1] = AbsolutNonPersistent;
iv->devControlCmd = ActivePowerContr;
iv->devControlRequest = true;
mApp->ivSendHighPrio(iv);
}
else if(F("dev") == jsonIn[F("cmd")]) {
DPRINTLN(DBG_INFO, F("dev cmd"));

2
src/web/html/index.html

@ -51,7 +51,7 @@
<li>Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a></li>
<li>Report <a href="https://github.com/lumapu/ahoy/issues" target="_blank">issues</a></li>
<li>Contribute to <a href="https://github.com/lumapu/ahoy/blob/main/User_Manual.md" target="_blank">documentation</a></li>
<li><a href="https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip" target="_blank">Download</a> & Test development firmware, <a href="https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md" target="_blank">Changelog</a></li>
<li><a href="https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip" target="_blank">Download</a> & Test development firmware, <a href="https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md" target="_blank">Development Changelog</a></li>
<li>make a <a href="https://paypal.me/lupusch" target="_blank">donation</a></li>
</ul>
<p class="lic">

11
src/web/html/setup.html

@ -94,7 +94,7 @@
<input type="button" id="btnAdd" class="btn" value="Add Inverter"/>
<p class="subdes">General</p>
<label for="invInterval">Interval [s]</label>
<input type="text" class="text" name="invInterval"/>
<input type="text" class="text" name="invInterval" pattern="[0-9]+" title="Invalid input"/>
<label for="invRetry">Max retries per Payload</label>
<input type="text" class="text" name="invRetry"/>
</fieldset>
@ -147,7 +147,10 @@
<label for="mqttPwd">Password (optional)</label>
<input type="password" class="text" name="mqttPwd"/>
<label for="mqttTopic">Topic</label>
<input type="text" class="text" name="mqttTopic" pattern="[A-Za-z0-9.\-_\+\/]+" title="Invalid input" />
<input type="text" class="text" name="mqttTopic" pattern="[A-Za-z0-9./#$%&=+_-]+" title="Invalid input" />
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p>
<label for="mqttIntvl">Interval [s]</label>
<input type="text" class="text" name="mqttInterval" pattern="[0-9]+" title="Invalid input" />
<label for="mqttBtn">Discovery Config (homeassistant)</label>
<input type="button" name="mqttDiscovery" id="mqttDiscovery" class="btn" value="send" onclick="sendDiscoveryConfig()"/>
<span id="apiResultMqtt"></span>
@ -170,7 +173,7 @@
<label for="serDbg">Serial Debug</label>
<input type="checkbox" class="cb" name="serDbg"/><br/>
<label for="serIntvl">Interval [s]</label>
<input type="text" class="text" name="serIntvl"/>
<input type="text" class="text" name="serIntvl" pattern="[0-9]+" title="Invalid input"/>
</fieldset>
</div>
@ -389,7 +392,7 @@
}
function parseMqtt(obj) {
for(var i of [["Addr", "broker"], ["Port", "port"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"]])
for(var i of [["Addr", "broker"], ["Port", "port"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"], ["Interval", "interval"]])
document.getElementsByName("mqtt"+i[0])[0].value = obj[i[1]];
}

1
src/web/web.h

@ -494,6 +494,7 @@ class Web {
request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN);
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
mConfig->mqtt.port = request->arg("mqttPort").toInt();
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
// serial console
if(request->arg("serIntvl") != "") {

Loading…
Cancel
Save