From 9da0fc4058b1bb21d9dcd9829753d9f22c1f22c7 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 5 Dec 2022 23:16:54 +0100 Subject: [PATCH] fix #468 MQTT status at sunset --- src/app.cpp | 175 +++++++++--------- src/app.h | 38 +--- src/defines.h | 2 +- .../MonochromeDisplay/MonochromeDisplay.h | 2 +- src/publisher/pubMqtt.h | 29 ++- src/utils/scheduler.h | 89 ++++++++- src/web/html/index.html | 2 +- src/web/webApi.cpp | 1 - 8 files changed, 211 insertions(+), 127 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 303ab562..81a75980 100644 --- a/src/app.cpp +++ b/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(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(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 diff --git a/src/app.h b/src/app.h index 82b0e646..08c2e3fe 100644 --- a/src/app.h +++ b/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) diff --git a/src/defines.h b/src/defines.h index 229d583b..d2f9b98f 100644 --- a/src/defines.h +++ b/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 { diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index 09ec45d4..29f44be2 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/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 #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 MonochromeDisplay { public: diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 0e3e8417..e1dc1acd 100644 --- a/src/publisher/pubMqtt.h +++ b/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; } diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index ff732b58..25702ce3 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -14,6 +14,13 @@ enum {EVERY_SEC = 1, EVERY_MIN, EVERY_HR, EVERY_12H, EVERY_DAY}; typedef std::function 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 *lType) { for(std::list::iterator it = lType->begin(); it != lType->end(); ++it) { (*it)(); } } - protected: + uint32_t mTimestamp; + + private: + void onceFuncTick(void) { + if(mOnce.empty()) + return; + for(std::list::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::iterator it = mOnceAt.begin(); it != mOnceAt.end();) { + if(((*it).n) < mTimestamp) { + ((*it).f)(); + it = mOnceAt.erase(it); + } + else + ++it; + } + } + std::list mListSecond; std::list mListMinute; std::list mListHour; std::list mList12h; std::list mListDay; - private: - uint32_t mPrevMillis; + std::list mOnce; + std::list mOnceAt; + + uint32_t mPrevMillis, mUptime; uint8_t mSeconds, mMinutes, mHours; }; } diff --git a/src/web/html/index.html b/src/web/html/index.html index e0474c08..070aee31 100644 --- a/src/web/html/index.html +++ b/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); diff --git a/src/web/webApi.cpp b/src/web/webApi.cpp index b11637fa..bed67fb7 100644 --- a/src/web/webApi.cpp +++ b/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();