Browse Source

fix #468 MQTT status at sunset

pull/487/head
lumapu 2 years ago
parent
commit
9da0fc4058
  1. 175
      src/app.cpp
  2. 38
      src/app.h
  3. 2
      src/defines.h
  4. 2
      src/plugins/MonochromeDisplay/MonochromeDisplay.h
  5. 29
      src/publisher/pubMqtt.h
  6. 89
      src/utils/scheduler.h
  7. 2
      src/web/html/index.html
  8. 1
      src/web/webApi.cpp

175
src/app.cpp

@ -24,25 +24,30 @@ void app::setup(uint32_t timeout) {
while (!Serial)
yield();
addListener(EVERY_SEC, std::bind(&app::uptimeTick, this));
addListener(EVERY_MIN, std::bind(&app::minuteTick, this));
addListener(EVERY_12H, std::bind(&app::ntpUpdateTick, this));
resetSystem();
mSettings.setup();
mSettings.getPtr(mConfig);
DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettings.getValid()) ? F("true") : F("false")));
addListener(EVERY_SEC, std::bind(&app::tickSecond, this));
addListener(EVERY_MIN, std::bind(&app::tickMinute, this));
addListener(EVERY_12H, std::bind(&app::tickNtpUpdate, this));
once(mConfig->nrf.sendInterval, std::bind(&app::tickSend, this), "tickSend");
if((mConfig->sun.lat) && (mConfig->sun.lon)) {
once(5, std::bind(&app::tickCalcSunrise, this));
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
}
mSys = new HmSystemType();
mSys->enableDebug();
mSys->setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs);
mSys->addInverters(&mConfig->inst);
#if !defined(AP_ONLY)
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mUtcTimestamp, &mSunrise, &mSunset);
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mTimestamp, &mSunrise, &mSunset);
#endif
mWifi.setup(mConfig, &mUtcTimestamp);
mWifi.setup(mConfig, &mTimestamp);
mPayload.setup(mSys);
mPayload.enableSerialDebug(mConfig->serial.debug);
@ -51,8 +56,8 @@ void app::setup(uint32_t timeout) {
mPayload.addListener(std::bind(&PubMqttType::payloadEventListener, &mMqtt, std::placeholders::_1));
addListener(EVERY_SEC, std::bind(&PubMqttType::tickerSecond, &mMqtt));
addListener(EVERY_MIN, std::bind(&PubMqttType::tickerMinute, &mMqtt));
addListener(EVERY_HR, std::bind(&PubMqttType::tickerHour, &mMqtt));
mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1));
}
#endif
setupLed();
@ -64,7 +69,7 @@ void app::setup(uint32_t timeout) {
// Plugins
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
mMonoDisplay.setup(mSys, &mUtcTimestamp);
mMonoDisplay.setup(mSys, &mTimestamp);
mPayload.addListener(std::bind(&MonoDisplayType::payloadEventListener, &mMonoDisplay, std::placeholders::_1));
addListener(EVERY_SEC, std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay));
#endif
@ -119,87 +124,95 @@ void app::loop(void) {
}
mMqtt.loop();
}
if (ah::checkTicker(&mTicker, 1000)) {
if (mUtcTimestamp > 946684800 && mConfig->sun.lat && mConfig->sun.lon && (mUtcTimestamp + mCalculatedTimezoneOffset) / 86400 != (mLatestSunTimestamp + mCalculatedTimezoneOffset) / 86400) { // update on reboot or midnight
if (!mLatestSunTimestamp) { // first call: calculate time zone from longitude to refresh at local midnight
mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600;
}
ah::calculateSunriseSunset(mUtcTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset);
mLatestSunTimestamp = mUtcTimestamp;
//-----------------------------------------------------------------------------
void app::tickCalcSunrise(void) {
if (0 == mTimestamp) {
once(5, std::bind(&app::tickCalcSunrise, this)); // check again in 5 secs
return;
}
ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset);
uint32_t nxtTrig = mTimestamp - (mTimestamp % 86400) + 86400; // next midnight
onceAt(nxtTrig, std::bind(&app::tickCalcSunrise, this), "calc sunrise");
onceAt(mSunrise, std::bind(&app::tickSend, this), "tickSend"); // register next event
if (mConfig->mqtt.broker[0] > 0) {
once(1, std::bind(&PubMqttType::tickerSun, &mMqtt), "MQTT-tickerSun");
onceAt(mSunset, std::bind(&PubMqttType::tickSunset, &mMqtt));
}
}
//-----------------------------------------------------------------------------
void app::tickSend(void) {
if ((mTimestamp > 0) && (!mConfig->sun.disNightCom || (mTimestamp >= mSunrise && mTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set)
once(mConfig->nrf.sendInterval, std::bind(&app::tickSend, this), "tickSend"); // register next event
if (mConfig->serial.debug)
DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX));
if (!mSys->BufCtrl.empty()) {
if (mConfig->serial.debug)
DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->BufCtrl.getFill()));
}
int8_t maxLoop = MAX_NUM_INVERTERS;
Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId);
do {
mSendLastIvId = ((MAX_NUM_INVERTERS - 1) == mSendLastIvId) ? 0 : mSendLastIvId + 1;
iv = mSys->getInverterByPos(mSendLastIvId);
} while ((NULL == iv) && ((maxLoop--) > 0));
if (NULL != iv) {
if (!mPayload.isComplete(iv))
mPayload.process(false, mConfig->nrf.maxRetransPerPyld, &mStat);
if (++mSendTicker >= mConfig->nrf.sendInterval) {
mSendTicker = 0;
if (!mPayload.isComplete(iv)) {
if (0 == mPayload.getMaxPacketId(iv))
mStat.rxFailNoAnser++;
else
mStat.rxFail++;
if (mUtcTimestamp > 946684800 && (!mConfig->sun.disNightCom || !mLatestSunTimestamp || (mUtcTimestamp >= mSunrise && mUtcTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set)
iv->setQueuedCmdFinished(); // command failed
if (mConfig->serial.debug)
DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX));
if (!mSys->BufCtrl.empty()) {
if (mConfig->serial.debug)
DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->BufCtrl.getFill()));
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);
int8_t maxLoop = MAX_NUM_INVERTERS;
Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId);
do {
mSendLastIvId = ((MAX_NUM_INVERTERS - 1) == mSendLastIvId) ? 0 : mSendLastIvId + 1;
iv = mSys->getInverterByPos(mSendLastIvId);
} while ((NULL == iv) && ((maxLoop--) > 0));
if (NULL != iv) {
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, mUtcTimestamp);
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);
} 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;
}
}
} else if (mConfig->serial.debug)
DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!"));
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));
}
updateLed();
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);
} 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;
}
}
} else {
once(3600, std::bind(&app::tickSend, this), "tickSend"); // register next event (one hour)
if (mConfig->serial.debug)
DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!"));
}
yield();
updateLed();
}
//-----------------------------------------------------------------------------
@ -224,22 +237,18 @@ void app::resetSystem(void) {
snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
mShouldReboot = false;
mUptimeSecs = 0;
mUpdateNtp = false;
mFlagSendDiscoveryConfig = false;
#ifdef AP_ONLY
mUtcTimestamp = 1;
mTimestamp = 1;
#else
mUtcTimestamp = 0;
mTimestamp = 0;
#endif
mSunrise = 0;
mSunset = 0;
mSendTicker = 0xffff;
mTicker = 0;
mRxTicker = 0;
mSendLastIvId = 0;
@ -278,7 +287,7 @@ void app::updateLed(void) {
Inverter<> *iv = mSys->getInverterByPos(0);
if (NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
if(iv->isProducing(mUtcTimestamp, rec))
if(iv->isProducing(mTimestamp, rec))
digitalWrite(mConfig->led.led0, LOW); // LED on
else
digitalWrite(mConfig->led.led0, HIGH); // LED off

38
src/app.h

@ -92,27 +92,19 @@ class app : public ah::Scheduler {
String getTimeStr(uint32_t offset = 0) {
char str[10];
if(0 == mUtcTimestamp)
if(0 == mTimestamp)
sprintf(str, "n/a");
else
sprintf(str, "%02d:%02d:%02d ", hour(mUtcTimestamp + offset), minute(mUtcTimestamp + offset), second(mUtcTimestamp + offset));
sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp + offset), minute(mTimestamp + offset), second(mTimestamp + offset));
return String(str);
}
inline uint32_t getUptime(void) {
return mUptimeSecs;
}
inline uint32_t getTimestamp(void) {
return mUtcTimestamp;
}
void setTimestamp(uint32_t newTime) {
DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime));
if(0 == newTime)
mUpdateNtp = true;
else
mUtcTimestamp = newTime;
Scheduler::setTimestamp(newTime);
}
inline uint32_t getSunrise(void) {
@ -121,9 +113,6 @@ class app : public ah::Scheduler {
inline uint32_t getSunset(void) {
return mSunset;
}
inline uint32_t getLatestSunTimestamp(void) {
return mLatestSunTimestamp;
}
inline bool mqttIsConnected(void) { return mMqtt.isConnected(); }
inline bool getSettingsValid(void) { return mSettings.getValid(); }
@ -142,11 +131,7 @@ class app : public ah::Scheduler {
void setupLed(void);
void updateLed(void);
void uptimeTick(void) {
mUptimeSecs++;
if (0 != mUtcTimestamp)
mUtcTimestamp++;
void tickSecond(void) {
if (mShouldReboot) {
DPRINTLN(DBG_INFO, F("Rebooting..."));
ESP.restart();
@ -158,16 +143,19 @@ class app : public ah::Scheduler {
}
}
void minuteTick(void) {
if(0 == mUtcTimestamp) {
void tickMinute(void) {
if(0 == mTimestamp) {
mUpdateNtp = true;
}
}
void ntpUpdateTick(void) {
void tickNtpUpdate(void) {
mUpdateNtp = true;
}
void tickCalcSunrise(void);
void tickSend(void);
void stats(void) {
DPRINTLN(DBG_VERBOSE, F("main.h:stats"));
#ifdef ESP8266
@ -188,9 +176,6 @@ class app : public ah::Scheduler {
DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag));
}
uint32_t mUptimeSecs;
uint32_t mUtcTimestamp;
bool mUpdateNtp;
bool mShowRebootRequest;
@ -204,13 +189,11 @@ class app : public ah::Scheduler {
settings mSettings;
settings_t *mConfig;
uint16_t mSendTicker;
uint8_t mSendLastIvId;
statistics_t mStat;
// timer
uint32_t mTicker;
uint32_t mRxTicker;
// mqtt
@ -220,7 +203,6 @@ class app : public ah::Scheduler {
// sun
int32_t mCalculatedTimezoneOffset;
uint32_t mSunrise, mSunset;
uint32_t mLatestSunTimestamp;
// plugins
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)

2
src/defines.h

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

2
src/plugins/MonochromeDisplay/MonochromeDisplay.h

@ -1,6 +1,7 @@
#ifndef __MONOCHROME_DISPLAY__
#define __MONOCHROME_DISPLAY__
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
#ifdef ENA_NOKIA
#include <U8g2lib.h>
#define DISP_PROGMEM U8X8_PROGMEM
@ -21,7 +22,6 @@ static uint8_t bmp_arrow[] DISP_PROGMEM = {
B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111,
B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000} ;
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
template<class HMSYSTEM>
class MonochromeDisplay {
public:

29
src/publisher/pubMqtt.h

@ -89,11 +89,33 @@ class PubMqtt {
}
}
void tickerHour() {
void tickerSun() {
publish("sunrise", String(*mSunrise).c_str(), true);
publish("sunset", String(*mSunset).c_str(), true);
}
void tickSunset() {
char topic[MAX_NAME_LENGTH + 15], val[32];
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id);
if (NULL == iv)
continue; // skip to next inverter
snprintf(topic, MAX_NAME_LENGTH + 15, "%s/available_text", iv->config->name);
snprintf(val, 32, "not available and not producing");
publish(topic, val, true);
snprintf(topic, MAX_NAME_LENGTH + 15, "%s/available", iv->config->name);
snprintf(val, 32, "%d", MQTT_STATUS_NOT_AVAIL_NOT_PROD);
publish(topic, val, true);
}
}
void payloadEventListener(uint8_t cmd) {
if(mClient.connected()) // prevent overflow if MQTT broker is not reachable but set
mSendList.push(cmd);
}
void publish(const char *subTopic, const char *payload, bool retained = false, bool addTopic = true) {
char topic[MQTT_TOPIC_LEN + 2];
snprintf(topic, (MQTT_TOPIC_LEN + 2), "%s/%s", mCfgMqtt->topic, subTopic);
@ -110,11 +132,6 @@ class PubMqtt {
mClient.subscribe(topic, QOS_0);
}
void payloadEventListener(uint8_t cmd) {
if(mClient.connected()) // prevent overflow if MQTT broker is not reachable but set
mSendList.push(cmd);
}
void setSubscriptionCb(subscriptionCb cb) {
mSubscriptionCb = cb;
}

89
src/utils/scheduler.h

@ -14,6 +14,13 @@
enum {EVERY_SEC = 1, EVERY_MIN, EVERY_HR, EVERY_12H, EVERY_DAY};
typedef std::function<void()> SchedulerCb;
struct once_t {
uint32_t n;
SchedulerCb f;
once_t(uint32_t a, SchedulerCb b) : n(a), f(b) {}
once_t() : n(0), f(NULL) {}
};
namespace ah {
class Scheduler {
public:
@ -21,18 +28,25 @@ class Scheduler {
void setup() {
mPrevMillis = 0;
mSeconds = 0;
mMinutes = 0;
mHours = 0;
mSeconds = 0;
mMinutes = 0;
mHours = 0;
mUptime = 0;
mTimestamp = 0;
}
void loop() {
if (millis() - mPrevMillis >= 1000) {
mPrevMillis += 1000;
mUptime++;
if(0 != mTimestamp)
mTimestamp++;
notify(&mListSecond);
onceFuncTick();
if(++mSeconds >= 60) {
mSeconds = 0;
notify(&mListMinute);
onceAtFuncTick();
if(++mMinutes >= 60) {
mMinutes = 0;
notify(&mListHour);
@ -48,6 +62,30 @@ class Scheduler {
}
}
// checked every second
void once(uint32_t sec, SchedulerCb cb, const char *info = NULL) {
if(NULL != info) {
DPRINT(DBG_INFO, F("once in [s]: ") + String(sec));
DBGPRINTLN(F(", ") + String(info));
}
mOnce.push_back(once_t(sec, cb));
}
// checked every minute
void onceAt(uint32_t timestamp, SchedulerCb cb, const char *info = NULL) {
if(timestamp > mTimestamp) {
if(NULL != info) {
DPRINT(DBG_INFO, F("onceAt (UTC): ") + getDateTimeStr(timestamp));
DBGPRINTLN(F(", ") + String(info));
}
mOnceAt.push_back(once_t(timestamp, cb));
}
}
virtual void setTimestamp(uint32_t ts) {
mTimestamp = ts;
}
void addListener(uint8_t every, SchedulerCb cb) {
switch(every) {
case EVERY_SEC: mListSecond.push_back(cb); break;
@ -59,21 +97,60 @@ class Scheduler {
}
}
uint32_t getUptime(void) {
return mUptime;
}
uint32_t getTimestamp(void) {
return mTimestamp;
}
protected:
virtual void notify(std::list<SchedulerCb> *lType) {
for(std::list<SchedulerCb>::iterator it = lType->begin(); it != lType->end(); ++it) {
(*it)();
}
}
protected:
uint32_t mTimestamp;
private:
void onceFuncTick(void) {
if(mOnce.empty())
return;
for(std::list<once_t>::iterator it = mOnce.begin(); it != mOnce.end();) {
if(((*it).n)-- == 0) {
((*it).f)();
it = mOnce.erase(it);
}
else
++it;
}
}
void onceAtFuncTick(void) {
if(mOnceAt.empty())
return;
for(std::list<once_t>::iterator it = mOnceAt.begin(); it != mOnceAt.end();) {
if(((*it).n) < mTimestamp) {
((*it).f)();
it = mOnceAt.erase(it);
}
else
++it;
}
}
std::list<SchedulerCb> mListSecond;
std::list<SchedulerCb> mListMinute;
std::list<SchedulerCb> mListHour;
std::list<SchedulerCb> mList12h;
std::list<SchedulerCb> mListDay;
private:
uint32_t mPrevMillis;
std::list<once_t> mOnce;
std::list<once_t> mOnceAt;
uint32_t mPrevMillis, mUptime;
uint8_t mSeconds, mMinutes, mHours;
};
}

2
src/web/html/index.html

@ -134,7 +134,7 @@
e.addEventListener("click", setTime);
}
if(!obj["ts_sun_upd"]) {
if(0 == obj["ts_sunrise"]) {
var e = document.getElementById("sun");
if(null != e)
e.parentNode.removeChild(e);

1
src/web/webApi.cpp

@ -163,7 +163,6 @@ void webApi::getSysInfo(JsonObject obj) {
obj[F("ts_now")] = mApp->getTimestamp();
obj[F("ts_sunrise")] = mApp->getSunrise();
obj[F("ts_sunset")] = mApp->getSunset();
obj[F("ts_sun_upd")] = mApp->getLatestSunTimestamp();
obj[F("wifi_rssi")] = WiFi.RSSI();
obj[F("mac")] = WiFi.macAddress();
obj[F("hostname")] = WiFi.getHostname();

Loading…
Cancel
Save