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/src/CHANGES.md b/src/CHANGES.md index 4a0a2f94..27fd1205 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,17 @@ (starting from release version `0.5.66`) +## 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 + ## 0.5.68 * repaired receive payload * Powerlimit is transfered immediately to inverter diff --git a/src/app.cpp b/src/app.cpp index 83be5a36..3e8da14f 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -51,7 +51,7 @@ void app::setup() { #endif mSys->addInverters(&mConfig->inst); - mPayload.setup(mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); + mPayload.setup(this, mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); mPayload.enableSerialDebug(mConfig->serial.debug); if(!mSys->Radio.isChipConnected()) @@ -162,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); } //----------------------------------------------------------------------------- diff --git a/src/app.h b/src/app.h index 5a4bb494..b08b7d05 100644 --- a/src/app.h +++ b/src/app.h @@ -122,6 +122,10 @@ 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); } diff --git a/src/appInterface.h b/src/appInterface.h index 64acab6b..4c25c3a2 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -34,6 +34,7 @@ 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; 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 616caa0d..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 68 +#define VERSION_PATCH 69 //------------------------------------- typedef struct { diff --git a/src/hm/payload.h b/src/hm/payload.h index b0ea7464..1f112844 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -35,7 +35,8 @@ class Payload : public Handler { public: Payload() : Handler() {} - void setup(HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { + void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { + mApp = app; mSys = sys; mStat = stat; mMaxRetrans = maxRetransmits; @@ -141,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; @@ -272,6 +277,7 @@ class Payload : public Handler { } private: + IApp *mApp; HMSYSTEM *mSys; statistics_t *mStat; uint8_t mMaxRetrans; 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 4246a866..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) { 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..8377cede 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -94,7 +94,7 @@

    General

    - + @@ -148,6 +148,9 @@ +

    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") != "") {