diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 8d8ab45e..203e23a7 100644 --- a/.github/workflows/compile_development.yml +++ b/.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 diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 20fcef87..91c4c8e3 100644 --- a/.github/workflows/compile_release.yml +++ b/.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 diff --git a/User_Manual.md b/User_Manual.md index 529125a0..be7519ec 100644 --- a/User_Manual.md +++ b/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 | |---|---| diff --git a/scripts/getVersion.py b/scripts/getVersion.py index f7c825ce..5f96f37f 100644 --- a/scripts/getVersion.py +++ b/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/" diff --git a/src/CHANGES.md b/src/CHANGES.md index f8a2c5ca..364b2b4d 100644 --- a/src/CHANGES.md +++ b/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 /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 diff --git a/src/app.cpp b/src/app.cpp index 29df1e64..6f53121b 100644 --- a/src/app.cpp +++ b/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(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) diff --git a/src/app.h b/src/app.h index 54dca314..b08b7d05 100644 --- a/src/app.h +++ b/src/app.h @@ -46,7 +46,7 @@ typedef PubMqtt PubMqttType; typedef PubSerial 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 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 }; diff --git a/src/appInterface.h b/src/appInterface.h index 94f75399..4c25c3a2 100644 --- a/src/appInterface.h +++ b/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; diff --git a/src/config/settings.h b/src/config/settings.h index 47c59a8c..921ebc96 100644 --- a/src/config/settings.h +++ b/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()); snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); diff --git a/src/defines.h b/src/defines.h index ddfda35e..b37cb6c7 100644 --- a/src/defines.h +++ b/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 { diff --git a/src/hm/payload.h b/src/hm/payload.h index ef69c4fe..1f112844 100644 --- a/src/hm/payload.h +++ b/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 { 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::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::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(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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { } } - 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_*/ diff --git a/src/platformio.ini b/src/platformio.ini index ba6c8be0..d64a8c93 100644 --- a/src/platformio.ini +++ b/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 \ No newline at end of file diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index 83236992..3bfe9458 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/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 #define DISP_PROGMEM U8X8_PROGMEM -#else // ENA_SSD1306 +#else // ENA_SSD1306 || ENA_SH1106 /* esp8266 : SCL = 5, SDA = 4 */ /* ewsp32 : SCL = 22, SDA = 21 */ #include - #include + #ifdef ENA_SSD1306 + #include + # else //ENA_SH1106 + #include + #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 diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index bd7a709f..382406f4 100644 --- a/src/publisher/pubMqtt.h +++ b/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]; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 8ad55e26..8986e76d 100644 --- a/src/web/RestApi.h +++ b/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")); diff --git a/src/web/html/index.html b/src/web/html/index.html index 0cd7f430..6634197e 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -51,7 +51,7 @@
  • Discuss with us on Discord
  • Report issues
  • Contribute to documentation
  • -
  • Download & Test development firmware, Changelog
  • +
  • Download & Test development firmware, Development Changelog
  • make a donation
  • diff --git a/src/web/html/setup.html b/src/web/html/setup.html index cf57f12b..c60020e1 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -94,7 +94,7 @@

    General

    - + @@ -147,7 +147,10 @@ - + +

    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)

    + + @@ -170,7 +173,7 @@
    - + @@ -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]]; } diff --git a/src/web/web.h b/src/web/web.h index d8922747..b3667b64 100644 --- a/src/web/web.h +++ b/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") != "") {