Browse Source

Merge pull request #359 from DanielR92/sun_for_dev03

Add Sunrise/Sunset and option to disable night communication + change Timestamp in CMDs to UTC
pull/362/head
lumapu 2 years ago
committed by GitHub
parent
commit
8daf7a15f2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      tools/esp8266/User_Manual.md
  2. 22
      tools/esp8266/ahoywifi.cpp
  3. 4
      tools/esp8266/ahoywifi.h
  4. 77
      tools/esp8266/app.cpp
  5. 29
      tools/esp8266/app.h
  6. 3
      tools/esp8266/config.h
  7. 10
      tools/esp8266/defines.h
  8. 19
      tools/esp8266/html/index.html
  9. 26
      tools/esp8266/html/setup.html
  10. 12
      tools/esp8266/web.cpp
  11. 11
      tools/esp8266/webApi.cpp
  12. 2
      tools/esp8266/webApi.h

17
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.

22
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
@ -231,22 +228,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;
}

4
tools/esp8266/ahoywifi.h

@ -30,11 +30,9 @@ class ahoywifi {
bool setupStation(uint32_t timeout);
bool getApActive(void);
time_t getNtpTime(void);
private:
void sendNTPpacket(IPAddress& address);
time_t offsetDayLightSaving (uint32_t local_t);
config_t *mConfig;
sysConfig_t *mSysCfg;

77
tools/esp8266/app.cpp

@ -56,6 +56,8 @@ void app::loop(void) {
if(millis() - mPrevMillis >= 1000) {
mPrevMillis += 1000;
mUptimeSecs++;
if(0 != mUtcTimestamp)
mUtcTimestamp++;
if(0 != mTimestamp)
mTimestamp++;
}
@ -67,7 +69,8 @@ void app::loop(void) {
if(mUpdateNtp) {
mUpdateNtp = false;
mTimestamp = mWifi->getNtpTime();
mUtcTimestamp = mWifi->getNtpTime();
mTimestamp = mUtcTimestamp + ((TIMEZONE + offsetDayLightSaving(mUtcTimestamp)) * 3600);
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp));
}
@ -154,6 +157,12 @@ void app::loop(void) {
mMqtt.loop();
if(checkTicker(&mTicker, 1000)) {
if(mTimestamp > 946684800 && mConfig.sunLat && mConfig.sunLon && mTimestamp / 86400 > mLatestSunTimestamp / 86400) // update on reboot or new day
{
calculateSunriseSunset();
mLatestSunTimestamp = mTimestamp;
}
if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) {
mMqttTicker = 0;
mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176
@ -196,7 +205,7 @@ void app::loop(void) {
if(++mSendTicker >= mConfig.sendInterval) {
mSendTicker = 0;
if(0 != mTimestamp) {
if(mUtcTimestamp > 946684800 && (!mConfig.sunDisNightCom || !mLatestSunTimestamp || (mTimestamp >= mSunrise && mTimestamp <= 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));
@ -258,7 +267,7 @@ void app::loop(void) {
}
}
else if(mConfig.serialDebug)
DPRINTLN(DBG_WARN, F("time not set, can't request inverter!"));
DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!"));
yield();
}
}
@ -380,7 +389,7 @@ void app::processPayload(bool retransmit) {
if(NULL == rec)
DPRINTLN(DBG_ERROR, F("record is NULL!"));
else {
rec->ts = mPayload[iv->id].ts;
rec->ts = mPayload[iv->id].ts + ((TIMEZONE + offsetDayLightSaving(mUtcTimestamp)) * 3600);
for(uint8_t i = 0; i < rec->length; i++) {
iv->addValue(i, payload, rec);
yield();
@ -682,8 +691,10 @@ void app::resetSystem(void) {
mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms]
#ifdef AP_ONLY
mUtcTimestamp = 1;
mTimestamp = 1;
#else
mUtcTimestamp = 0;
mTimestamp = 0;
#endif
@ -734,6 +745,11 @@ void app::loadDefaultConfig(void) {
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;
@ -813,6 +829,9 @@ void app::saveValues(void) {
}
updateCrc();
// update sun
mLatestSunTimestamp = 0;
}
@ -865,5 +884,53 @@ 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;
}
//-----------------------------------------------------------------------------
// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC
// from: https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536
uint8_t app::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;
}
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 = mTimestamp / 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 unix timestamp (days incl. fraction to seconds + unix offset 1.1.2000 12:00)
uint32_t UTC_Timestamp_Sunrise = (Jrise - 2451545.0) * 86400 + 946728000;
uint32_t UTC_Timestamp_Sunset = (Jset - 2451545.0) * 86400 + 946728000;
mSunrise = UTC_Timestamp_Sunrise + ((TIMEZONE + offsetDayLightSaving(UTC_Timestamp_Sunrise)) * 3600); // sunrise in local time, OPTIONAL: Add an offset of +-seconds to the end of the line
mSunset = UTC_Timestamp_Sunset + ((TIMEZONE + offsetDayLightSaving(UTC_Timestamp_Sunset)) * 3600); // sunset in local time, OPTIONAL: Add an offset of +-seconds to the end of the line
}

29
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
//
@ -117,7 +123,20 @@ class app {
if(0 == newTime)
mUpdateNtp = true;
else
mTimestamp = newTime;
{
mUtcTimestamp = newTime;
mTimestamp = mUtcTimestamp + ((TIMEZONE + offsetDayLightSaving(mUtcTimestamp)) * 3600);
}
}
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) {
@ -237,6 +256,8 @@ class app {
DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag));
}
uint8_t offsetDayLightSaving(uint32_t local_t);
void calculateSunriseSunset(void);
uint32_t mUptimeSecs;
uint32_t mPrevMillis;
@ -249,6 +270,7 @@ class app {
bool mSettingsValid;
eep *mEep;
uint32_t mUtcTimestamp;
uint32_t mTimestamp;
bool mUpdateNtp;
@ -280,6 +302,11 @@ class app {
// serial
uint16_t mSerialTicker;
// sun
uint32_t mSunrise;
uint32_t mSunset;
uint32_t mLatestSunTimestamp;
};
#endif /*__APP_H__*/

3
tools/esp8266/config.h

@ -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"

10
tools/esp8266/defines.h

@ -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

19
tools/esp8266/html/index.html

@ -28,10 +28,15 @@
<a href="/live">Visualization</a><br/>
<br/>
<a href="/setup">Setup</a><br/>
<br/>
<a href="/serial">Webserial & Commands</a><br/>
</p>
<p><span class="des">Uptime: </span><span id="uptime"></span></p>
<p><span class="des">ESP-Time: </span><span id="date"></span></p>
<div id="sun">
<span class="des">Sunrise: </span><span id="sunrise"></span><br>
<span class="des">Sunset: </span><span id="sunset"></span>
</div>
<p><span class="des">WiFi RSSI: </span><span id="wifi_rssi"></span> dBm</p>
<p>
<span class="des">Statistics: </span>
@ -75,10 +80,9 @@
function setTime() {
var date = new Date();
var offset = date.getTimezoneOffset() * -60;
var obj = new Object();
obj.cmd = "set_time";
obj.ts = parseInt(offset + (date.getTime() / 1000));
obj.ts = parseInt(date.getTime() / 1000);
getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj));
}
@ -95,6 +99,8 @@
var hrs = parseInt(up / 3600) % 24;
var min = parseInt(up / 60) % 60;
var sec = up % 60;
var sunrise = new Date(obj["ts_sunrise"] * 1000);
var sunset = new Date(obj["ts_sunset"] * 1000);
document.getElementById("uptime").innerHTML = days + " Days, "
+ ("0"+hrs).substr(-2) + ":"
+ ("0"+min).substr(-2) + ":"
@ -109,6 +115,13 @@
dSpan.appendChild(span("", ["span"], "apiResult"));
e.addEventListener("click", setTime);
}
document.getElementById("sunrise").innerHTML = sunrise.toLocaleString('de-DE', {timeZone: 'UTC'});
document.getElementById("sunset").innerHTML = sunset.toLocaleString('de-DE', {timeZone: 'UTC'});
if(!obj["ts_sun_upd"]) {
var elem = document.getElementById("sun");
elem.parentNode.removeChild(elem);
}
}
function parseStat(obj) {
@ -116,7 +129,7 @@
+ "\nRX fail: " + obj["rx_fail"]
+ "\nRX no answer: " + obj["rx_fail_answer"]
+ "\nFrames received: " + obj["frame_cnt"]
+ "\nTX Cnt: " + obj["tx_cnt"];
+ "\nTX cnt: " + obj["tx_cnt"];
}
function parseIv(obj) {

26
tools/esp8266/html/setup.html

@ -72,6 +72,20 @@
</fieldset>
</div>
<button type="button" class="s_collapsible">Sunrise & Sunset</button>
<div class="s_content">
<fieldset>
<legend class="des">Sunrise & Sunset</legend>
<label for="sunLat">Latitude (decimal)</label>
<input type="text" class="text" name="sunLat"/>
<label for="sunLon">Longitude (decimal)</label>
<input type="text" class="text" name="sunLon"/>
<br>
<label for="sunDisNightCom">disable night communication</label>
<input type="checkbox" class="cb" name="sunDisNightCom"/><br/>
</fieldset>
</div>
<button type="button" class="s_collapsible">MQTT</button>
<div class="s_content">
<fieldset>
@ -156,10 +170,9 @@
function setTime() {
var date = new Date();
var offset = date.getTimezoneOffset() * -60;
var obj = new Object();
obj.cmd = "set_time";
obj.ts = parseInt(offset + (date.getTime() / 1000));
obj.ts = parseInt(date.getTime() / 1000);
getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
}
@ -219,7 +232,7 @@
}
});
for(var i of [["Name", "name", "Name*", 32]]) { // so richtig?
for(var i of [["Name", "name", "Name*", 32]]) {
iv.appendChild(lbl(id + i[0], i[2]));
iv.appendChild(inp(id + i[0], obj[i[1]], i[3]));
}
@ -271,6 +284,12 @@
document.getElementsByName(i[0])[0].value = obj[i[1]];
}
function parseSun(obj) {
document.getElementsByName("sunLat")[0].value = obj["lat"];
document.getElementsByName("sunLon")[0].value = obj["lon"];
document.getElementsByName("sunDisNightCom")[0].checked = obj["disnightcom"];
}
function parsePinout(obj) {
var e = document.getElementById("pinout");
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq']];
@ -321,6 +340,7 @@
parseIv(root["inverter"]);
parseMqtt(root["mqtt"]);
parseNtp(root["ntp"]);
parseSun(root["sun"]);
parsePinout(root["pinout"]);
parseRadio(root["radio"]);
parseSerial(root["serial"]);

12
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");

11
tools/esp8266/webApi.cpp

@ -142,6 +142,9 @@ 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;
}
@ -200,6 +203,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,6 +277,7 @@ 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")));

2
tools/esp8266/webApi.h

@ -33,11 +33,11 @@ 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 getLive(JsonObject obj);

Loading…
Cancel
Save