diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 3d32111d..e298a619 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v3 with: ref: development03 - - uses: benjlevesque/short-sha@v2 + - uses: benjlevesque/short-sha@v2.0 id: short-sha with: length: 7 @@ -30,7 +30,9 @@ jobs: path: ~/.platformio key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} - name: setup-python - uses: actions/setup-python@v4 + uses: actions/setup-python@v4.3.0 + with: + python-version: '3.10' - name: install-platformio run: | python -m pip install --upgrade pip diff --git a/tools/esp8266/User_Manual.md b/tools/esp8266/User_Manual.md index 729d82c9..1c882f48 100644 --- a/tools/esp8266/User_Manual.md +++ b/tools/esp8266/User_Manual.md @@ -259,3 +259,20 @@ In case all commands are processed (`_commandQueue.empty() == true`) then as a d In case a Device Control command (Power Limit, Off, On) is requested via MQTT or REST API this request will be send before any other enqueued command. In case of a accepted change in power limit the command get active power limit in percent (`SystemConfigPara = 5 // 0x05`) will be enqueued. The acceptance is checked by the reponse packets on the devive control commands (tx id 0x51 --> rx id 0xD1) if in byte 12 the requested sub-command (eg. power limit) is present. + +## How To +### Sunrise & Sunset +In order to display the sunrise and sunset on the start page, the location coordinates (latitude and longitude) must be set in decimal in the setup under Sunrise & Sunset. If the coordinates are set, the current sunrise and sunset are calculated and displayed daily. +If this is set, you can also tick "disable night communication", then sending to the inverter is switched off outside of the day (i.e. at night), this avoids unnecessary communication attempts and thus also the incrementing of "RX no anwser". +Here you can get easy your GeoLocation: [https://www.mapsdirections.info/en/gps-coordinates.html](https://www.mapsdirections.info/en/gps-coordinates.html) +### Commands and informations +Turn On - turns on the inverter/feeder (LED flashes green if there is no error) +Turn Off - switches off the inverter/feeder (LED flashes fast red), can be switched on again with Turn On or by disconnecting and reconnecting the DC voltage +Restart - restarts the microcontroller in the inverter, which deletes the error memory and the YieldDay values, feed-in stops briefly and starts with the last persistent limit +Send Power Limit: +- A limitation of the AC power can be sent in relative (in %) or in absolute (Watt). +- It can be set to a different value non-persistently (temporarily) at any time (regardless of what you have set for persistent), this should be normal in order to limit the power (zero feed/battery control) and does not damage the EEPROM in the WR either. +- With persistent you send a saving limit (only use it seldom, otherwise the EEPROM in the HM can break!), This is then used as the next switch-on limit when DC comes on, i.e. when the sun comes up early or the WR on batteries is switched on the limit is applied immediately when sending, like any other, but it is stored in the EEPROM of the WR. +- A persistent limit is only needed if you want to throttle your inverter permanently or you can use it to set a start value on the battery, which is then always the switch-on limit when switching on, otherwise it would ramp up to 100% without regulation, which is continuous load is not healthy. +- You can set a new limit in the turn-off state, which is then used for on (switching on again), otherwise the last limit from before the turn-off is used, but of course this only applies if DC voltage is applied the whole time. +- If the DC voltage is missing for a few seconds, the microcontroller in the inverter goes off and forgets everything that was temporary/non-persistent in the RAM: YieldDay, error memory, non-persistent limit. diff --git a/tools/esp8266/ahoywifi.cpp b/tools/esp8266/ahoywifi.cpp index b3feb43c..9823ea23 100644 --- a/tools/esp8266/ahoywifi.cpp +++ b/tools/esp8266/ahoywifi.cpp @@ -12,7 +12,6 @@ // NTP CONFIG #define NTP_PACKET_SIZE 48 -#define TIMEZONE 1 // Central European time +1 //----------------------------------------------------------------------------- @@ -185,7 +184,6 @@ time_t ahoywifi::getNtpTime(void) { WiFi.hostByName(mConfig->ntpAddr, timeServer); mUdp->begin(mConfig->ntpPort); - sendNTPpacket(timeServer); while(retry++ < 5) { @@ -200,7 +198,6 @@ time_t ahoywifi::getNtpTime(void) { secsSince1900 |= (buf[43] ); date = secsSince1900 - 2208988800UL; // UTC time - date += (TIMEZONE + offsetDayLightSaving(date)) * 3600; break; } else @@ -212,6 +209,28 @@ time_t ahoywifi::getNtpTime(void) { } +//----------------------------------------------------------------------------- +void ahoywifi::getAvailNetworks(JsonObject obj) { + JsonArray nets = obj.createNestedArray("networks"); + + int n = WiFi.scanComplete(); + if(n == -2) { + WiFi.scanNetworks(true); + } else if(n) { + for (int i = 0; i < n; ++i) { + nets[i]["ssid"] = WiFi.SSID(i); + nets[i]["rssi"] = WiFi.RSSI(i); + // TODO: does github workflow use another version of this library? + // ahoywifi.cpp:223:38: error: 'class WiFiClass' has no member named 'isHidden' + //nets[i]["hidden"] = WiFi.isHidden(i) ? true : false; + } + WiFi.scanDelete(); + if(WiFi.scanComplete() == -2) + WiFi.scanNetworks(true); + } +} + + //----------------------------------------------------------------------------- void ahoywifi::sendNTPpacket(IPAddress& address) { //DPRINTLN(DBG_VERBOSE, F("wifi::sendNTPpacket")); @@ -231,22 +250,3 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { mUdp->write(buf, NTP_PACKET_SIZE); mUdp->endPacket(); } - - -//----------------------------------------------------------------------------- -// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC -// from: https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536 -time_t ahoywifi::offsetDayLightSaving (uint32_t local_t) { - //DPRINTLN(DBG_VERBOSE, F("wifi::offsetDayLightSaving")); - int m = month (local_t); - if(m < 3 || m > 10) return 0; // no DSL in Jan, Feb, Nov, Dez - if(m > 3 && m < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep - int y = year (local_t); - int h = hour (local_t); - int hToday = (h + 24 * day(local_t)); - if((m == 3 && hToday >= (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 4) % 7))) - || (m == 10 && hToday < (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 1) % 7))) ) - return 1; - else - return 0; -} diff --git a/tools/esp8266/ahoywifi.h b/tools/esp8266/ahoywifi.h index fdd3bff0..9a3239fd 100644 --- a/tools/esp8266/ahoywifi.h +++ b/tools/esp8266/ahoywifi.h @@ -30,11 +30,10 @@ class ahoywifi { bool setupStation(uint32_t timeout); bool getApActive(void); time_t getNtpTime(void); - + void getAvailNetworks(JsonObject obj); + private: void sendNTPpacket(IPAddress& address); - time_t offsetDayLightSaving (uint32_t local_t); - config_t *mConfig; sysConfig_t *mSysCfg; diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index 8cd3aac1..1ddc0e38 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -40,7 +40,7 @@ void app::setup(uint32_t timeout) { #ifndef AP_ONLY setupMqtt(); #endif - mSys->setup(&mConfig); + mSys->setup(mConfig.amplifierPower, mConfig.pinIrq, mConfig.pinCe, mConfig.pinCs); mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion); mWebInst->setup(); @@ -56,8 +56,8 @@ void app::loop(void) { if(millis() - mPrevMillis >= 1000) { mPrevMillis += 1000; mUptimeSecs++; - if(0 != mTimestamp) - mTimestamp++; + if(0 != mUtcTimestamp) + mUtcTimestamp++; } if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) { @@ -67,8 +67,8 @@ void app::loop(void) { if(mUpdateNtp) { mUpdateNtp = false; - mTimestamp = mWifi->getNtpTime(); - DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp)); + mUtcTimestamp = mWifi->getNtpTime(); + DPRINTLN(DBG_INFO, F("[NTP]: ") + getDateTimeStr(mUtcTimestamp) + F(" UTC")); } if(mFlagSendDiscoveryConfig) { @@ -166,6 +166,14 @@ void app::loop(void) { mMqtt.loop(); if(checkTicker(&mTicker, 1000)) { + if(mUtcTimestamp > 946684800 && mConfig.sunLat && mConfig.sunLon && (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.sunLon >= 0 ? mConfig.sunLon + 7.5 : mConfig.sunLon - 7.5) / 15) * 3600; + } + calculateSunriseSunset(); + mLatestSunTimestamp = mUtcTimestamp; + } + if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) { mMqttTicker = 0; mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176 @@ -188,7 +196,7 @@ void app::loop(void) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL != iv) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - if(iv->isAvailable(mTimestamp, rec)) { + if(iv->isAvailable(mUtcTimestamp, rec)) { DPRINTLN(DBG_INFO, "Inverter: " + String(id)); for(uint8_t i = 0; i < rec->length; i++) { if(0.0f != iv->getValue(i, rec)) { @@ -208,7 +216,7 @@ void app::loop(void) { if(++mSendTicker >= mConfig.sendInterval) { mSendTicker = 0; - if(0 != mTimestamp) { + if(mUtcTimestamp > 946684800 && (!mConfig.sunDisNightCom || !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) if(mConfig.serialDebug) DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX)); @@ -272,9 +280,8 @@ void app::loop(void) { } } } - else if(mConfig.serialDebug) { - DPRINTLN(DBG_WARN, F("time not set, can't request inverter!")); - } + else if(mConfig.serialDebug) + DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!")); yield(); } } @@ -295,19 +302,15 @@ bool app::buildPayload(uint8_t id) { if(mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES) mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; - for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) - { - if(mPayload[id].len[i] > 0) - { - if(i == (mPayload[id].maxPackId-1)) - { - crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc); - crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]); - } - else - { - crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); + for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) { + if(mPayload[id].len[i] > 0) { + if(i == (mPayload[id].maxPackId-1)) { + crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc); + crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) + | (mPayload[id].data[i][mPayload[id].len[i] - 1]); } + else + crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); } yield(); } @@ -387,30 +390,31 @@ void app::processPayload(bool retransmit) { DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId)); record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser mPayload[iv->id].complete = true; - if(mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES)) - mStat.rxSuccess++; uint8_t payload[128]; - uint8_t offs = 0; + uint8_t payloadLen = 0; memset(payload, 0, 128); for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) { - memcpy(&payload[offs], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); - offs += (mPayload[iv->id].len[i]); + memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); + payloadLen += (mPayload[iv->id].len[i]); yield(); } - offs-=2; + payloadLen-=2; + if(mConfig.serialDebug) { - DPRINT(DBG_INFO, F("Payload (") + String(offs) + "): "); - mSys->Radio.dumpBuf(NULL, payload, offs); + DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): "); + mSys->Radio.dumpBuf(NULL, payload, payloadLen); } if(NULL == rec) { DPRINTLN(DBG_ERROR, F("record is NULL!")); } - else - { + else if((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { + if(mPayload[iv->id].txId == (TX_REQ_INFO + 0x80)) + mStat.rxSuccess++; + rec->ts = mPayload[iv->id].ts; for(uint8_t i = 0; i < rec->length; i++) { iv->addValue(i, payload, rec); @@ -427,7 +431,7 @@ void app::processPayload(bool retransmit) { for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); if (NULL != iv) { - if (iv->isAvailable(mTimestamp, rec)) { + if (iv->isAvailable(mUtcTimestamp, rec)) { for (uint8_t i = 0; i < rec->length; i++) { snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); snprintf(val, 10, "%.3f", iv->getValue(i, rec)); @@ -442,12 +446,9 @@ void app::processPayload(bool retransmit) { } } } - - // Todo: make this section nice to read. - snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name); - if(iv->isProducing(mTimestamp, rec)) - { + if(iv->isProducing(mUtcTimestamp, rec)){ + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name); snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED); mMqtt.sendMsg(topic, val); @@ -467,11 +468,11 @@ void app::processPayload(bool retransmit) { snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->name); snprintf(val, 48, "%i", iv->getLastTs(rec) * 1000); mMqtt.sendMsg(topic, val); - + yield(); } - } - } + } + } } // total values (sum of all inverters) @@ -493,6 +494,10 @@ void app::processPayload(bool retransmit) { } } } + else { + DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes")); + mStat.rxFail++; + } iv->setQueuedCmdFinished(); @@ -505,7 +510,7 @@ void app::processPayload(bool retransmit) { if(mMqttActive) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); char topic[32 + MAX_NAME_LENGTH], val[32]; - if (!iv->isAvailable(mTimestamp, rec) && !iv->isProducing(mTimestamp, rec)){ + if (!iv->isAvailable(mUtcTimestamp, rec) && !iv->isProducing(mUtcTimestamp, rec)){ snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name); snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED); mMqtt.sendMsg(topic, val); @@ -647,6 +652,12 @@ bool app::getWifiApActive(void) { } +//----------------------------------------------------------------------------- +void app::getAvailNetworks(JsonObject obj) { + mWifi->getAvailNetworks(obj); +} + + //----------------------------------------------------------------------------- void app::sendMqttDiscoveryConfig(void) { DPRINTLN(DBG_VERBOSE, F("app::sendMqttDiscoveryConfig")); @@ -657,7 +668,7 @@ void app::sendMqttDiscoveryConfig(void) { if(NULL != iv) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // TODO: next line makes no sense if discovery config is send manually by button - //if(iv->isAvailable(mTimestamp, rec) && mMqttConfigSendState[id] != true) { + //if(iv->isAvailable(mUtcTimestamp, rec) && mMqttConfigSendState[id] != true) { DynamicJsonDocument deviceDoc(128); deviceDoc["name"] = iv->name; deviceDoc["ids"] = String(iv->serial.u64, HEX); @@ -739,9 +750,9 @@ void app::resetSystem(void) { mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms] #ifdef AP_ONLY - mTimestamp = 1; + mUtcTimestamp = 1; #else - mTimestamp = 0; + mUtcTimestamp = 0; #endif mHeapStatCnt = 0; @@ -782,15 +793,20 @@ void app::loadDefaultConfig(void) { // nrf24 mConfig.sendInterval = SEND_INTERVAL; mConfig.maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD; - mConfig.pinCs = DEF_RF24_CS_PIN; - mConfig.pinCe = DEF_RF24_CE_PIN; - mConfig.pinIrq = DEF_RF24_IRQ_PIN; + mConfig.pinCs = DEF_CS_PIN; + mConfig.pinCe = DEF_CE_PIN; + mConfig.pinIrq = DEF_IRQ_PIN; mConfig.amplifierPower = DEF_AMPLIFIERPOWER & 0x03; // ntp snprintf(mConfig.ntpAddr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME); mConfig.ntpPort = DEF_NTP_PORT; + // Latitude + Longitude + mConfig.sunLat = 0.0; + mConfig.sunLon = 0.0; + mConfig.sunDisNightCom = false; + // mqtt snprintf(mConfig.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER); mConfig.mqtt.port = DEF_MQTT_PORT; @@ -870,6 +886,9 @@ void app::saveValues(void) { } updateCrc(); + + // update sun + mLatestSunTimestamp = 0; } @@ -907,5 +926,32 @@ void app::resetPayload(Inverter<>* iv) { mPayload[iv->id].maxPackId = 0; mPayload[iv->id].complete = false; mPayload[iv->id].requested = false; - mPayload[iv->id].ts = mTimestamp; + mPayload[iv->id].ts = mUtcTimestamp; +} + +void app::calculateSunriseSunset() { + // Source: https://en.wikipedia.org/wiki/Sunrise_equation#Complete_calculation_on_Earth + + // Julian day since 1.1.2000 12:00 + correction 69.12s + double n_JulianDay = (mUtcTimestamp + mCalculatedTimezoneOffset) / 86400 - 10957.0 + 0.0008; + // Mean solar time + double J = n_JulianDay - mConfig.sunLon / 360; + // Solar mean anomaly + double M = fmod((357.5291 + 0.98560028 * J), 360); + // Equation of the center + double C = 1.9148 * SIN(M) + 0.02 * SIN(2 * M) + 0.0003 * SIN(3 * M); + // Ecliptic longitude + double lambda = fmod((M + C + 180 + 102.9372), 360); + // Solar transit + double Jtransit = 2451545.0 + J + 0.0053 * SIN(M) - 0.0069 * SIN(2 * lambda); + // Declination of the sun + double delta = ASIN(SIN(lambda) * SIN(23.44)); + // Hour angle + double omega = ACOS(SIN(-0.83) - SIN(mConfig.sunLat) * SIN(delta) / COS(mConfig.sunLat) * COS(delta)); + // Calculate sunrise and sunset + double Jrise = Jtransit - omega / 360; + double Jset = Jtransit + omega / 360; + // Julian sunrise/sunset to UTC unix timestamp (days incl. fraction to seconds + unix offset 1.1.2000 12:00) + mSunrise = (Jrise - 2451545.0) * 86400 + 946728000; // OPTIONAL: Add an offset of +-seconds to the end of the line + mSunset = (Jset - 2451545.0) * 86400 + 946728000; // OPTIONAL: Add an offset of +-seconds to the end of the line } diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index f02d85a1..18b00263 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -24,6 +24,12 @@ #include "ahoywifi.h" #include "web.h" +// convert degrees and radians for sun calculation +#define SIN(x) (sin(radians(x))) +#define COS(x) (cos(radians(x))) +#define ASIN(x) (degrees(asin(x))) +#define ACOS(x) (degrees(acos(x))) + // hier läst sich das Verhalten der app in Bezug auf MQTT // durch PER-Conpiler defines anpassen // @@ -31,10 +37,7 @@ #define __MQTT_AFTER_RX__ // versendet die MQTT Daten sobald die WR daten Aufbereitet wurden ( gehört eigentlich ins Setup ) // #define __MQTT_NO_DISCOVERCONFIG__ // das versenden der MQTTDiscoveryConfig abschalten ( gehört eigentlich ins Setup ) -typedef CircularBuffer BufferType; -typedef HmRadio RadioType; -typedef Inverter InverterType; -typedef HmSystem HmSystemType; +typedef HmSystem HmSystemType; typedef struct { @@ -65,6 +68,7 @@ class app { void saveValues(void); void resetPayload(Inverter<>* iv); bool getWifiApActive(void); + void getAvailNetworks(JsonObject obj); uint8_t getIrqPin(void) { return mConfig.pinIrq; @@ -95,12 +99,12 @@ class app { return String(str); } - String getTimeStr(void) { - char str[20]; - if(0 == mTimestamp) + String getTimeStr(uint32_t offset = 0) { + char str[10]; + if(0 == mUtcTimestamp) sprintf(str, "n/a"); else - sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp), minute(mTimestamp), second(mTimestamp)); + sprintf(str, "%02d:%02d:%02d ", hour(mUtcTimestamp + offset), minute(mUtcTimestamp + offset), second(mUtcTimestamp + offset)); return String(str); } @@ -109,15 +113,27 @@ class app { } inline uint32_t getTimestamp(void) { - return mTimestamp; + return mUtcTimestamp; } - inline void setTimestamp(uint32_t newTime) { + void setTimestamp(uint32_t newTime) { DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime)); if(0 == newTime) mUpdateNtp = true; else - mTimestamp = newTime; + { + mUtcTimestamp = newTime; + } + } + + inline uint32_t getSunrise(void) { + return mSunrise; + } + inline uint32_t getSunset(void) { + return mSunset; + } + inline uint32_t getLatestSunTimestamp(void) { + return mLatestSunTimestamp; } void eraseSettings(bool all = false) { @@ -156,7 +172,6 @@ class app { inline bool getSettingsValid(void) { return mSettingsValid; } inline bool getRebootRequestState(void) { return mShowRebootRequest; } - HmSystemType *mSys; bool mShouldReboot; bool mFlagSendDiscoveryConfig; @@ -171,7 +186,6 @@ class app { bool buildPayload(uint8_t id); void processPayload(bool retransmit); - void processPayload(bool retransmit, uint8_t cmd); const char* getFieldDeviceClass(uint8_t fieldId); const char* getFieldStateClass(uint8_t fieldId); @@ -185,7 +199,7 @@ class app { while(length > 0) { len = (length < 32) ? length : 32; mEep->read(start, buf, len); - crc = Ahoy::crc16(buf, len, crc); + crc = ah::crc16(buf, len, crc); start += len; length -= len; } @@ -237,6 +251,7 @@ class app { DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag)); } + void calculateSunriseSunset(void); uint32_t mUptimeSecs; uint32_t mPrevMillis; @@ -249,7 +264,7 @@ class app { bool mSettingsValid; eep *mEep; - uint32_t mTimestamp; + uint32_t mUtcTimestamp; bool mUpdateNtp; bool mShowRebootRequest; @@ -280,6 +295,12 @@ class app { // serial uint16_t mSerialTicker; + + // sun + int32_t mCalculatedTimezoneOffset; + uint32_t mSunrise; + uint32_t mSunset; + uint32_t mLatestSunTimestamp; }; #endif /*__APP_H__*/ diff --git a/tools/esp8266/config.h b/tools/esp8266/config.h index e6a019f5..7ec77084 100644 --- a/tools/esp8266/config.h +++ b/tools/esp8266/config.h @@ -41,9 +41,9 @@ #define DEF_DEVICE_NAME "AHOY-DTU" // default pinout (GPIO Number) -#define DEF_RF24_CS_PIN 15 -#define DEF_RF24_CE_PIN 2 -#define DEF_RF24_IRQ_PIN 0 +#define DEF_CS_PIN 15 +#define DEF_CE_PIN 2 +#define DEF_IRQ_PIN 0 // default NRF24 power, possible values (0 - 3) #define DEF_AMPLIFIERPOWER 1 @@ -78,6 +78,9 @@ // threshold of minimum power on which the inverter is marked as inactive #define INACT_PWR_THRESH 3 +// Timezone +#define TIMEZONE 1 + // default NTP server uri #define DEF_NTP_SERVER_NAME "pool.ntp.org" diff --git a/tools/esp8266/crc.cpp b/tools/esp8266/crc.cpp index 879af286..6e341924 100644 --- a/tools/esp8266/crc.cpp +++ b/tools/esp8266/crc.cpp @@ -1,11 +1,11 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2022 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- #include "crc.h" -namespace Ahoy { +namespace ah { uint8_t crc8(uint8_t buf[], uint8_t len) { uint8_t crc = CRC8_INIT; for(uint8_t i = 0; i < len; i++) { diff --git a/tools/esp8266/crc.h b/tools/esp8266/crc.h index ef7be1f8..03f93444 100644 --- a/tools/esp8266/crc.h +++ b/tools/esp8266/crc.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2022 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- @@ -14,7 +14,7 @@ #define CRC16_MODBUS_POLYNOM 0xA001 -namespace Ahoy { +namespace ah { uint8_t crc8(uint8_t buf[], uint8_t len); uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff); } diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 36c2cede..fa6ee3f4 100644 --- a/tools/esp8266/defines.h +++ b/tools/esp8266/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 21 +#define VERSION_PATCH 22 //------------------------------------- @@ -83,7 +83,8 @@ typedef enum { #define INV_CH_CH_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH * 4 // (4 channels) #define INV_INTERVAL_LEN 2 // uint16_t #define INV_MAX_RTRY_LEN 1 // uint8_t -#define INV_PWR_LIM_LEN MAX_NUM_INVERTERS * 2 // uint16_t + +#define CFG_SUN_LEN 9 // 2x float(4+4) + bool(1) #define NTP_ADDR_LEN 32 // DNS Name @@ -136,6 +137,11 @@ typedef struct { // mqtt mqttConfig_t mqtt; + // sun + float sunLat; + float sunLon; + bool sunDisNightCom; // disable night communication + // serial uint16_t serialInterval; bool serialShowIv; @@ -153,7 +159,7 @@ typedef struct { #define CFG_MQTT_LEN MQTT_ADDR_LEN + 2 + MQTT_USER_LEN + MQTT_PWD_LEN +MQTT_TOPIC_LEN #define CFG_SYS_LEN DEVNAME_LEN + SSID_LEN + PWD_LEN + 1 -#define CFG_LEN 7 + NTP_ADDR_LEN + 2 + CFG_MQTT_LEN + 4 + DISCLAIMER +#define CFG_LEN 7 + NTP_ADDR_LEN + 2 + CFG_MQTT_LEN + CFG_SUN_LEN + 4 + DISCLAIMER #define ADDR_START 0 #define ADDR_CFG_SYS ADDR_START diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h index 4fd8bd5f..b89dd9a4 100644 --- a/tools/esp8266/hmDefines.h +++ b/tools/esp8266/hmDefines.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2022 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- @@ -98,6 +98,7 @@ const byteAssign_t InfoAssignment[] = { { FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 } }; #define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t)) +#define HMINFO_PAYLOAD_LEN 14 const byteAssign_t SystemConfigParaAssignment[] = { { FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*, @@ -105,11 +106,13 @@ const byteAssign_t SystemConfigParaAssignment[] = { { FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/ }; #define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) +#define HMSYSTEM_PAYLOAD_LEN 14 const byteAssign_t AlarmDataAssignment[] = { { FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 } }; #define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t)) +#define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off @@ -137,7 +140,8 @@ const byteAssign_t hm1chAssignment[] = { { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } }; -#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t)) +#define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t)) +#define HM1CH_PAYLOAD_LEN 30 //------------------------------------- @@ -172,7 +176,8 @@ const byteAssign_t hm2chAssignment[] = { { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } }; -#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t)) +#define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t)) +#define HM2CH_PAYLOAD_LEN 42 //------------------------------------- @@ -220,7 +225,8 @@ const byteAssign_t hm4chAssignment[] = { { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } }; -#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t)) +#define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t)) +#define HM4CH_PAYLOAD_LEN 62 #endif /*__HM_DEFINES_H__*/ diff --git a/tools/esp8266/hmInverter.h b/tools/esp8266/hmInverter.h index aeeacde1..d1806e98 100644 --- a/tools/esp8266/hmInverter.h +++ b/tools/esp8266/hmInverter.h @@ -61,6 +61,7 @@ struct record_t { uint8_t length; // length of the assignment list T *record; // data pointer uint32_t ts; // timestamp of last received payload + uint8_t pyldLen; // expected payload length for plausibility check }; class CommandAbstract { @@ -242,10 +243,16 @@ class Inverter { val <<= 8; val |= buf[ptr]; } while(++ptr != end); - if ((REC_TYP)(div) > 1) - rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div); - else - rec->record[pos] = (REC_TYP)(val); + if(FLD_T == rec->assign[pos].fieldId) { + // temperature is a signed value! + rec->record[pos] = (REC_TYP)((int16_t)val) / (REC_TYP)(div); + } + else { + if ((REC_TYP)(div) > 1) + rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div); + else + rec->record[pos] = (REC_TYP)(val); + } } } @@ -348,37 +355,44 @@ class Inverter { switch (cmd) { case RealTimeRunData_Debug: if (INV_TYPE_1CH == type) { - rec->length = (uint8_t)(HM1CH_LIST_LEN); - rec->assign = (byteAssign_t *)hm1chAssignment; - channels = 1; + rec->length = (uint8_t)(HM1CH_LIST_LEN); + rec->assign = (byteAssign_t *)hm1chAssignment; + rec->pyldLen = HM1CH_PAYLOAD_LEN; + channels = 1; } else if (INV_TYPE_2CH == type) { - rec->length = (uint8_t)(HM2CH_LIST_LEN); - rec->assign = (byteAssign_t *)hm2chAssignment; - channels = 2; + rec->length = (uint8_t)(HM2CH_LIST_LEN); + rec->assign = (byteAssign_t *)hm2chAssignment; + rec->pyldLen = HM2CH_PAYLOAD_LEN; + channels = 2; } else if (INV_TYPE_4CH == type) { - rec->length = (uint8_t)(HM4CH_LIST_LEN); - rec->assign = (byteAssign_t *)hm4chAssignment; - channels = 4; + rec->length = (uint8_t)(HM4CH_LIST_LEN); + rec->assign = (byteAssign_t *)hm4chAssignment; + rec->pyldLen = HM4CH_PAYLOAD_LEN; + channels = 4; } else { - rec->length = 0; - rec->assign = NULL; - channels = 0; + rec->length = 0; + rec->assign = NULL; + rec->pyldLen = 0; + channels = 0; } break; case InverterDevInform_All: - rec->length = (uint8_t)(HMINFO_LIST_LEN); - rec->assign = (byteAssign_t *)InfoAssignment; + rec->length = (uint8_t)(HMINFO_LIST_LEN); + rec->assign = (byteAssign_t *)InfoAssignment; + rec->pyldLen = HMINFO_PAYLOAD_LEN; break; case SystemConfigPara: - rec->length = (uint8_t)(HMSYSTEM_LIST_LEN); - rec->assign = (byteAssign_t *)SystemConfigParaAssignment; + rec->length = (uint8_t)(HMSYSTEM_LIST_LEN); + rec->assign = (byteAssign_t *)SystemConfigParaAssignment; + rec->pyldLen = HMSYSTEM_PAYLOAD_LEN; break; case AlarmData: - rec->length = (uint8_t)(HMALARMDATA_LIST_LEN); - rec->assign = (byteAssign_t *)AlarmDataAssignment; + rec->length = (uint8_t)(HMALARMDATA_LIST_LEN); + rec->assign = (byteAssign_t *)AlarmDataAssignment; + rec->pyldLen = HMALARMDATA_PAYLOAD_LEN; break; default: DPRINTLN(DBG_INFO, F("initAssignment: Parser not implemented")); diff --git a/tools/esp8266/hmRadio.h b/tools/esp8266/hmRadio.h index 737aa15a..048c5255 100644 --- a/tools/esp8266/hmRadio.h +++ b/tools/esp8266/hmRadio.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2022 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- @@ -8,9 +8,20 @@ #include "dbg.h" #include -#include #include "crc.h" - +#ifndef DISABLE_IRQ + #if defined(ESP8266) || defined(ESP32) + #define DISABLE_IRQ noInterrupts() + #define RESTORE_IRQ interrupts() + #else + #define DISABLE_IRQ \ + uint8_t sreg = SREG; \ + cli(); + + #define RESTORE_IRQ \ + SREG = sreg; + #endif +#endif //#define CHANNEL_HOP // switch between channels or use static channel to send #define DEFAULT_RECV_CHANNEL 3 @@ -54,7 +65,7 @@ const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"}; //----------------------------------------------------------------------------- // HM Radio class //----------------------------------------------------------------------------- -template +template class HmRadio { public: HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) { @@ -84,32 +95,31 @@ class HmRadio { } ~HmRadio() {} - void setup(config_t *config, BUFFER *ctrl) { + void setup(BUFFER *ctrl, uint8_t ampPwr = RF24_PA_LOW, uint8_t irq = IRQ_PIN, uint8_t ce = CE_PIN, uint8_t cs = CS_PIN) { DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setup")); - pinMode(config->pinIrq, INPUT_PULLUP); - + pinMode(irq, INPUT_PULLUP); mBufCtrl = ctrl; - mSerialDebug = config->serialDebug; - uint32_t DTU_SN = 0x87654321; + + uint32_t dtuSn = 0x87654321; uint32_t chipID = 0; // will be filled with last 3 bytes of MAC -#ifdef ESP32 + #ifdef ESP32 uint64_t MAC = ESP.getEfuseMac(); chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF); -#else + #else chipID = ESP.getChipId(); -#endif + #endif if(chipID) { - DTU_SN = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal + dtuSn = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal for(int i = 0; i < 7; i++) { - DTU_SN |= (chipID % 10) << (i * 4); + dtuSn |= (chipID % 10) << (i * 4); chipID /= 10; } } // change the byte order of the DTU serial number and append the required 0x01 at the end - DTU_RADIO_ID = ((uint64_t)(((DTU_SN >> 24) & 0xFF) | ((DTU_SN >> 8) & 0xFF00) | ((DTU_SN << 8) & 0xFF0000) | ((DTU_SN << 24) & 0xFF000000)) << 8) | 0x01; + DTU_RADIO_ID = ((uint64_t)(((dtuSn >> 24) & 0xFF) | ((dtuSn >> 8) & 0xFF00) | ((dtuSn << 8) & 0xFF0000) | ((dtuSn << 24) & 0xFF000000)) << 8) | 0x01; - mNrf24.begin(config->pinCe, config->pinCs); + mNrf24.begin(ce, cs); mNrf24.setRetries(0, 0); mNrf24.setChannel(DEFAULT_RECV_CHANNEL); @@ -125,8 +135,8 @@ class HmRadio { mNrf24.maskIRQ(true, true, false); DPRINT(DBG_INFO, F("RF24 Amp Pwr: RF24_PA_")); - DPRINTLN(DBG_INFO, String(rf24AmpPowerNames[config->amplifierPower])); - mNrf24.setPALevel(config->amplifierPower & 0x03); + DPRINTLN(DBG_INFO, String(rf24AmpPowerNames[ampPwr])); + mNrf24.setPALevel(ampPwr & 0x03); mNrf24.startListening(); DPRINTLN(DBG_INFO, F("Radio Config:")); @@ -169,6 +179,10 @@ class HmRadio { RESTORE_IRQ; } + void enableDebug() { + mSerialDebug = true; + } + void handleIntr(void) { //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:handleIntr")); mIrqRcvd = true; @@ -195,12 +209,12 @@ class HmRadio { } // crc control data - uint16_t crc = Ahoy::crc16(&mTxBuf[10], cnt); + uint16_t crc = ah::crc16(&mTxBuf[10], cnt); mTxBuf[10 + cnt++] = (crc >> 8) & 0xff; mTxBuf[10 + cnt++] = (crc ) & 0xff; // crc over all - mTxBuf[10 + cnt] = Ahoy::crc8(mTxBuf, 10 + cnt); + mTxBuf[10 + cnt] = ah::crc8(mTxBuf, 10 + cnt); sendPacket(invId, mTxBuf, 10 + cnt + 1, true); } @@ -215,10 +229,10 @@ class HmRadio { mTxBuf[18] = (alarmMesId >> 8) & 0xff; mTxBuf[19] = (alarmMesId ) & 0xff; } - uint16_t crc = Ahoy::crc16(&mTxBuf[10], 14); + uint16_t crc = ah::crc16(&mTxBuf[10], 14); mTxBuf[24] = (crc >> 8) & 0xff; mTxBuf[25] = (crc ) & 0xff; - mTxBuf[26] = Ahoy::crc8(mTxBuf, 26); + mTxBuf[26] = ah::crc8(mTxBuf, 26); sendPacket(invId, mTxBuf, 27, true); } @@ -231,7 +245,7 @@ class HmRadio { CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8)); mTxBuf[9] = pid; if(calcCrc) { - mTxBuf[10] = Ahoy::crc8(mTxBuf, 10); + mTxBuf[10] = ah::crc8(mTxBuf, 10); sendPacket(invId, mTxBuf, 11, false); } } @@ -245,7 +259,7 @@ class HmRadio { buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7); } - uint8_t crc = Ahoy::crc8(buf, *len-1); + uint8_t crc = ah::crc8(buf, *len-1); bool valid = (crc == buf[*len-1]); return valid; diff --git a/tools/esp8266/hmSystem.h b/tools/esp8266/hmSystem.h index eb246ccb..ee7c180c 100644 --- a/tools/esp8266/hmSystem.h +++ b/tools/esp8266/hmSystem.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2022 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- @@ -7,12 +7,13 @@ #define __HM_SYSTEM_H__ #include "hmInverter.h" -#ifndef NO_RADIO #include "hmRadio.h" -#endif +#include "CircularBuffer.h" +typedef CircularBuffer BufferType; +typedef HmRadio RadioType; -template > +template > class HmSystem { public: typedef RADIO RadioType; @@ -28,9 +29,12 @@ class HmSystem { // TODO: cleanup } - void setup(config_t *config) { - DPRINTLN(DBG_VERBOSE, F("hmSystem.h:setup")); - Radio.setup(config, &BufCtrl); + void setup() { + Radio.setup(&BufCtrl); + } + + void setup(uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin) { + Radio.setup(&BufCtrl, ampPwr, irqPin, cePin, csPin); } INVERTERTYPE *addInverter(const char *name, uint64_t serial, uint16_t chMaxPwr[]) { @@ -96,6 +100,10 @@ class HmSystem { return mNumInv; } + void enableDebug() { + Radio.enableDebug(); + } + private: INVERTERTYPE mInverter[MAX_INVERTER]; uint8_t mNumInv; diff --git a/tools/esp8266/html/api.js b/tools/esp8266/html/api.js index f793551a..d3288c8b 100644 --- a/tools/esp8266/html/api.js +++ b/tools/esp8266/html/api.js @@ -19,8 +19,10 @@ function getAjax(url, ptr, method="GET", json=null) { } function p() { if(xhr.readyState == 4) { - if(null != xhr.responseText) - ptr(JSON.parse(xhr.responseText)); + if(null != xhr.responseText) { + if(null != ptr) + ptr(JSON.parse(xhr.responseText)); + } } } } @@ -66,6 +68,14 @@ function sel(name, opt, selId) { return e; } +function opt(val, html) { + o = document.createElement('option'); + o.value = val; + o.innerHTML = html; + e.appendChild(o); + return o; +} + function div(cl) { e = document.createElement('div'); e.classList.add(...cl); diff --git a/tools/esp8266/html/index.html b/tools/esp8266/html/index.html index 9959529f..cc7f5f08 100644 --- a/tools/esp8266/html/index.html +++ b/tools/esp8266/html/index.html @@ -12,10 +12,9 @@ diff --git a/tools/esp8266/html/visualization.html b/tools/esp8266/html/visualization.html index 8ee0926b..b1c7e1fb 100644 --- a/tools/esp8266/html/visualization.html +++ b/tools/esp8266/html/visualization.html @@ -76,8 +76,16 @@ } var ts = div(["ts"]); - var date = new Date(iv["ts_last_success"] * 1000); - ts.innerHTML = "Last received data requested at: " + date.toLocaleString('de-DE', {timeZone: 'UTC'}); + var ageInfo = "Last received data requested at: "; + if(iv["ts_last_success"] > 0) { + var date = new Date(iv["ts_last_success"] * 1000); + ageInfo += date.toLocaleString('de-DE'); + } + else + ageInfo += "nothing received"; + + ts.innerHTML = ageInfo; + main.appendChild(ts); ivHtml.push(main); } diff --git a/tools/esp8266/web.cpp b/tools/esp8266/web.cpp index 40e6b4b5..5d0c8be5 100644 --- a/tools/esp8266/web.cpp +++ b/tools/esp8266/web.cpp @@ -283,6 +283,18 @@ void web::showSave(AsyncWebServerRequest *request) { mConfig->ntpPort = request->arg("ntpPort").toInt() & 0xffff; } + // sun + if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { + mConfig->sunLat = 0.0; + mConfig->sunLon = 0.0; + mConfig->sunDisNightCom = false; + } else { + mConfig->sunLat = request->arg("sunLat").toFloat(); + mConfig->sunLon = request->arg("sunLon").toFloat(); + mConfig->sunDisNightCom = (request->arg("sunDisNightCom") == "on"); + } + + // mqtt if(request->arg("mqttAddr") != "") { String addr = request->arg("mqttAddr"); @@ -462,7 +474,7 @@ void web::serialCb(String msg) { msg.replace("\r\n", ""); if(mSerialAddTime) { if((9 + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { - strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr().c_str(), 9); + strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr(mApi->getTimezoneOffset()).c_str(), 9); mSerialBufFill += 9; } else { diff --git a/tools/esp8266/webApi.cpp b/tools/esp8266/webApi.cpp index d2a8390a..71d73dc3 100644 --- a/tools/esp8266/webApi.cpp +++ b/tools/esp8266/webApi.cpp @@ -18,6 +18,8 @@ webApi::webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *con mConfig = config; mStat = stat; mVersion = version; + + mTimezoneOffset = 0; } @@ -43,16 +45,17 @@ void webApi::onApi(AsyncWebServerRequest *request) { Inverter<> *iv = mApp->mSys->getInverterByPos(0, false); String path = request->url().substring(5); - if(path == "system") getSystem(root); - else if(path == "statistics") getStatistics(root); - else if(path == "inverter/list") getInverterList(root); - else if(path == "index") getIndex(root); - else if(path == "setup") getSetup(root); - else if(path == "live") getLive(root); - else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All)); - else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData)); - else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara)); - else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug)); + if(path == "system") getSystem(root); + else if(path == "statistics") getStatistics(root); + else if(path == "inverter/list") getInverterList(root); + else if(path == "index") getIndex(root); + else if(path == "setup") getSetup(root); + else if(path == "setup/networks") getNetworks(root); + else if(path == "live") getLive(root); + else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All)); + else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData)); + else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara)); + else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug)); else getNotFound(root, F("http://") + request->host() + F("/api/")); @@ -142,8 +145,16 @@ void webApi::getSystem(JsonObject obj) { obj[F("build")] = String(AUTO_GIT_HASH); obj[F("ts_uptime")] = mApp->getUptime(); 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("disclaimer")] = mConfig->disclaimer; +#if defined(ESP32) + obj[F("esp_type")] = F("ESP32"); +#else + obj[F("esp_type")] = F("ESP8266"); +#endif } @@ -200,6 +211,13 @@ void webApi::getNtp(JsonObject obj) { obj[F("port")] = String(mConfig->ntpPort); } +//----------------------------------------------------------------------------- +void webApi::getSun(JsonObject obj) { + obj[F("lat")] = mConfig->sunLat ? String(mConfig->sunLat, 5) : ""; + obj[F("lon")] = mConfig->sunLat ? String(mConfig->sunLon, 5) : ""; + obj[F("disnightcom")] = mConfig->sunDisNightCom; +} + //----------------------------------------------------------------------------- void webApi::getPinout(JsonObject obj) { @@ -267,12 +285,19 @@ void webApi::getSetup(JsonObject obj) { getInverterList(obj.createNestedObject(F("inverter"))); getMqtt(obj.createNestedObject(F("mqtt"))); getNtp(obj.createNestedObject(F("ntp"))); + getSun(obj.createNestedObject(F("sun"))); getPinout(obj.createNestedObject(F("pinout"))); getRadio(obj.createNestedObject(F("radio"))); getSerial(obj.createNestedObject(F("serial"))); } +//----------------------------------------------------------------------------- +void webApi::getNetworks(JsonObject obj) { + mApp->getAvailNetworks(obj); +} + + //----------------------------------------------------------------------------- void webApi::getLive(JsonObject obj) { getSystem(obj.createNestedObject(F("system"))); @@ -414,6 +439,8 @@ bool webApi::setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut) { mApp->setTimestamp(jsonIn[F("ts")]); else if(F("sync_ntp") == jsonIn[F("cmd")]) mApp->setTimestamp(0); // 0: update ntp flag + else if(F("serial_utc_offset") == jsonIn[F("cmd")]) + mTimezoneOffset = jsonIn[F("ts")]; else if(F("discovery_cfg") == jsonIn[F("cmd")]) mApp->mFlagSendDiscoveryConfig = true; // for homeassistant else { diff --git a/tools/esp8266/webApi.h b/tools/esp8266/webApi.h index 1d3caa17..9be0cf4c 100644 --- a/tools/esp8266/webApi.h +++ b/tools/esp8266/webApi.h @@ -21,6 +21,10 @@ class webApi { void setup(void); void loop(void); + uint32_t getTimezoneOffset() { + return mTimezoneOffset; + } + private: void onApi(AsyncWebServerRequest *request); void onApiPost(AsyncWebServerRequest *request); @@ -33,13 +37,14 @@ class webApi { void getInverterList(JsonObject obj); void getMqtt(JsonObject obj); void getNtp(JsonObject obj); + void getSun(JsonObject obj); void getPinout(JsonObject obj); void getRadio(JsonObject obj); void getSerial(JsonObject obj); - void getIndex(JsonObject obj); void getSetup(JsonObject obj); + void getNetworks(JsonObject obj); void getLive(JsonObject obj); void getRecord(JsonObject obj, record_t<> *rec); @@ -59,6 +64,8 @@ class webApi { sysConfig_t *mSysCfg; statistics_t *mStat; char *mVersion; + + uint32_t mTimezoneOffset; }; #endif /*__WEB_API_H__*/