Browse Source

Merge branch 'development03' into dev03

pull/361/head
DanielR92 2 years ago
committed by GitHub
parent
commit
97d23e7ae0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      .github/workflows/compile_development.yml
  2. 17
      tools/esp8266/User_Manual.md
  3. 44
      tools/esp8266/ahoywifi.cpp
  4. 3
      tools/esp8266/ahoywifi.h
  5. 136
      tools/esp8266/app.cpp
  6. 51
      tools/esp8266/app.h
  7. 9
      tools/esp8266/config.h
  8. 4
      tools/esp8266/crc.cpp
  9. 4
      tools/esp8266/crc.h
  10. 12
      tools/esp8266/defines.h
  11. 14
      tools/esp8266/hmDefines.h
  12. 58
      tools/esp8266/hmInverter.h
  13. 62
      tools/esp8266/hmRadio.h
  14. 22
      tools/esp8266/hmSystem.h
  15. 14
      tools/esp8266/html/api.js
  16. 48
      tools/esp8266/html/index.html
  17. 12
      tools/esp8266/html/serial.html
  18. 127
      tools/esp8266/html/setup.html
  19. 12
      tools/esp8266/html/visualization.html
  20. 14
      tools/esp8266/web.cpp
  21. 47
      tools/esp8266/webApi.cpp
  22. 9
      tools/esp8266/webApi.h

6
.github/workflows/compile_development.yml

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
ref: development03 ref: development03
- uses: benjlevesque/short-sha@v2 - uses: benjlevesque/short-sha@v2.0
id: short-sha id: short-sha
with: with:
length: 7 length: 7
@ -30,7 +30,9 @@ jobs:
path: ~/.platformio path: ~/.platformio
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
- name: setup-python - name: setup-python
uses: actions/setup-python@v4 uses: actions/setup-python@v4.3.0
with:
python-version: '3.10'
- name: install-platformio - name: install-platformio
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip

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

44
tools/esp8266/ahoywifi.cpp

@ -12,7 +12,6 @@
// NTP CONFIG // NTP CONFIG
#define NTP_PACKET_SIZE 48 #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); WiFi.hostByName(mConfig->ntpAddr, timeServer);
mUdp->begin(mConfig->ntpPort); mUdp->begin(mConfig->ntpPort);
sendNTPpacket(timeServer); sendNTPpacket(timeServer);
while(retry++ < 5) { while(retry++ < 5) {
@ -200,7 +198,6 @@ time_t ahoywifi::getNtpTime(void) {
secsSince1900 |= (buf[43] ); secsSince1900 |= (buf[43] );
date = secsSince1900 - 2208988800UL; // UTC time date = secsSince1900 - 2208988800UL; // UTC time
date += (TIMEZONE + offsetDayLightSaving(date)) * 3600;
break; break;
} }
else 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) { void ahoywifi::sendNTPpacket(IPAddress& address) {
//DPRINTLN(DBG_VERBOSE, F("wifi::sendNTPpacket")); //DPRINTLN(DBG_VERBOSE, F("wifi::sendNTPpacket"));
@ -231,22 +250,3 @@ void ahoywifi::sendNTPpacket(IPAddress& address) {
mUdp->write(buf, NTP_PACKET_SIZE); mUdp->write(buf, NTP_PACKET_SIZE);
mUdp->endPacket(); 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;
}

3
tools/esp8266/ahoywifi.h

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

136
tools/esp8266/app.cpp

@ -40,7 +40,7 @@ void app::setup(uint32_t timeout) {
#ifndef AP_ONLY #ifndef AP_ONLY
setupMqtt(); setupMqtt();
#endif #endif
mSys->setup(&mConfig); mSys->setup(mConfig.amplifierPower, mConfig.pinIrq, mConfig.pinCe, mConfig.pinCs);
mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion); mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion);
mWebInst->setup(); mWebInst->setup();
@ -56,8 +56,8 @@ void app::loop(void) {
if(millis() - mPrevMillis >= 1000) { if(millis() - mPrevMillis >= 1000) {
mPrevMillis += 1000; mPrevMillis += 1000;
mUptimeSecs++; mUptimeSecs++;
if(0 != mTimestamp) if(0 != mUtcTimestamp)
mTimestamp++; mUtcTimestamp++;
} }
if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) { if(checkTicker(&mNtpRefreshTicker, mNtpRefreshInterval)) {
@ -67,8 +67,8 @@ void app::loop(void) {
if(mUpdateNtp) { if(mUpdateNtp) {
mUpdateNtp = false; mUpdateNtp = false;
mTimestamp = mWifi->getNtpTime(); mUtcTimestamp = mWifi->getNtpTime();
DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp)); DPRINTLN(DBG_INFO, F("[NTP]: ") + getDateTimeStr(mUtcTimestamp) + F(" UTC"));
} }
if(mFlagSendDiscoveryConfig) { if(mFlagSendDiscoveryConfig) {
@ -166,6 +166,14 @@ void app::loop(void) {
mMqtt.loop(); mMqtt.loop();
if(checkTicker(&mTicker, 1000)) { 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) { if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) {
mMqttTicker = 0; mMqttTicker = 0;
mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176 mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176
@ -188,7 +196,7 @@ void app::loop(void) {
Inverter<> *iv = mSys->getInverterByPos(id); Inverter<> *iv = mSys->getInverterByPos(id);
if(NULL != iv) { if(NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
if(iv->isAvailable(mTimestamp, rec)) { if(iv->isAvailable(mUtcTimestamp, rec)) {
DPRINTLN(DBG_INFO, "Inverter: " + String(id)); DPRINTLN(DBG_INFO, "Inverter: " + String(id));
for(uint8_t i = 0; i < rec->length; i++) { for(uint8_t i = 0; i < rec->length; i++) {
if(0.0f != iv->getValue(i, rec)) { if(0.0f != iv->getValue(i, rec)) {
@ -208,7 +216,7 @@ void app::loop(void) {
if(++mSendTicker >= mConfig.sendInterval) { if(++mSendTicker >= mConfig.sendInterval) {
mSendTicker = 0; 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) if(mConfig.serialDebug)
DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX)); DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX));
@ -272,9 +280,8 @@ void app::loop(void) {
} }
} }
} }
else if(mConfig.serialDebug) { 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(); yield();
} }
} }
@ -295,19 +302,15 @@ bool app::buildPayload(uint8_t id) {
if(mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES) if(mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES)
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) for(uint8_t i = 0; i < mPayload[id].maxPackId; i ++) {
{ if(mPayload[id].len[i] > 0) {
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);
if(i == (mPayload[id].maxPackId-1)) crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8)
{ | (mPayload[id].data[i][mPayload[id].len[i] - 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 else
{ crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
crc = Ahoy::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
}
} }
yield(); yield();
} }
@ -387,30 +390,31 @@ void app::processPayload(bool retransmit) {
DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId)); DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
mPayload[iv->id].complete = true; mPayload[iv->id].complete = true;
if(mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES))
mStat.rxSuccess++;
uint8_t payload[128]; uint8_t payload[128];
uint8_t offs = 0; uint8_t payloadLen = 0;
memset(payload, 0, 128); memset(payload, 0, 128);
for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) { for(uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i ++) {
memcpy(&payload[offs], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
offs += (mPayload[iv->id].len[i]); payloadLen += (mPayload[iv->id].len[i]);
yield(); yield();
} }
offs-=2; payloadLen-=2;
if(mConfig.serialDebug) { if(mConfig.serialDebug) {
DPRINT(DBG_INFO, F("Payload (") + String(offs) + "): "); DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): ");
mSys->Radio.dumpBuf(NULL, payload, offs); mSys->Radio.dumpBuf(NULL, payload, payloadLen);
} }
if(NULL == rec) { if(NULL == rec) {
DPRINTLN(DBG_ERROR, F("record is NULL!")); 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; rec->ts = mPayload[iv->id].ts;
for(uint8_t i = 0; i < rec->length; i++) { for(uint8_t i = 0; i < rec->length; i++) {
iv->addValue(i, payload, rec); iv->addValue(i, payload, rec);
@ -427,7 +431,7 @@ void app::processPayload(bool retransmit) {
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id); Inverter<> *iv = mSys->getInverterByPos(id);
if (NULL != iv) { if (NULL != iv) {
if (iv->isAvailable(mTimestamp, rec)) { if (iv->isAvailable(mUtcTimestamp, rec)) {
for (uint8_t i = 0; i < rec->length; i++) { 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(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)); snprintf(val, 10, "%.3f", iv->getValue(i, rec));
@ -443,11 +447,8 @@ void app::processPayload(bool retransmit) {
} }
} }
// Todo: make this section nice to read. if(iv->isProducing(mUtcTimestamp, rec)){
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
if(iv->isProducing(mTimestamp, rec))
{
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED); snprintf(val, 32, DEF_MQTT_IV_MESSAGE_INVERTER_AVAIL_AND_PRODUCED);
mMqtt.sendMsg(topic, val); mMqtt.sendMsg(topic, val);
@ -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(); iv->setQueuedCmdFinished();
@ -505,7 +510,7 @@ void app::processPayload(bool retransmit) {
if(mMqttActive) { if(mMqttActive) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
char topic[32 + MAX_NAME_LENGTH], val[32]; 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(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->name);
snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED); snprintf(val, 32, DEF_MQTT_IV_MESSAGE_NOT_AVAIL_AND_NOT_PRODUCED);
mMqtt.sendMsg(topic, val); mMqtt.sendMsg(topic, val);
@ -647,6 +652,12 @@ bool app::getWifiApActive(void) {
} }
//-----------------------------------------------------------------------------
void app::getAvailNetworks(JsonObject obj) {
mWifi->getAvailNetworks(obj);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::sendMqttDiscoveryConfig(void) { void app::sendMqttDiscoveryConfig(void) {
DPRINTLN(DBG_VERBOSE, F("app::sendMqttDiscoveryConfig")); DPRINTLN(DBG_VERBOSE, F("app::sendMqttDiscoveryConfig"));
@ -657,7 +668,7 @@ void app::sendMqttDiscoveryConfig(void) {
if(NULL != iv) { if(NULL != iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
// TODO: next line makes no sense if discovery config is send manually by button // 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); DynamicJsonDocument deviceDoc(128);
deviceDoc["name"] = iv->name; deviceDoc["name"] = iv->name;
deviceDoc["ids"] = String(iv->serial.u64, HEX); deviceDoc["ids"] = String(iv->serial.u64, HEX);
@ -739,9 +750,9 @@ void app::resetSystem(void) {
mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms] mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms]
#ifdef AP_ONLY #ifdef AP_ONLY
mTimestamp = 1; mUtcTimestamp = 1;
#else #else
mTimestamp = 0; mUtcTimestamp = 0;
#endif #endif
mHeapStatCnt = 0; mHeapStatCnt = 0;
@ -782,15 +793,20 @@ void app::loadDefaultConfig(void) {
// nrf24 // nrf24
mConfig.sendInterval = SEND_INTERVAL; mConfig.sendInterval = SEND_INTERVAL;
mConfig.maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD; mConfig.maxRetransPerPyld = DEF_MAX_RETRANS_PER_PYLD;
mConfig.pinCs = DEF_RF24_CS_PIN; mConfig.pinCs = DEF_CS_PIN;
mConfig.pinCe = DEF_RF24_CE_PIN; mConfig.pinCe = DEF_CE_PIN;
mConfig.pinIrq = DEF_RF24_IRQ_PIN; mConfig.pinIrq = DEF_IRQ_PIN;
mConfig.amplifierPower = DEF_AMPLIFIERPOWER & 0x03; mConfig.amplifierPower = DEF_AMPLIFIERPOWER & 0x03;
// ntp // ntp
snprintf(mConfig.ntpAddr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME); snprintf(mConfig.ntpAddr, NTP_ADDR_LEN, "%s", DEF_NTP_SERVER_NAME);
mConfig.ntpPort = DEF_NTP_PORT; mConfig.ntpPort = DEF_NTP_PORT;
// Latitude + Longitude
mConfig.sunLat = 0.0;
mConfig.sunLon = 0.0;
mConfig.sunDisNightCom = false;
// mqtt // mqtt
snprintf(mConfig.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER); snprintf(mConfig.mqtt.broker, MQTT_ADDR_LEN, "%s", DEF_MQTT_BROKER);
mConfig.mqtt.port = DEF_MQTT_PORT; mConfig.mqtt.port = DEF_MQTT_PORT;
@ -870,6 +886,9 @@ void app::saveValues(void) {
} }
updateCrc(); updateCrc();
// update sun
mLatestSunTimestamp = 0;
} }
@ -907,5 +926,32 @@ void app::resetPayload(Inverter<>* iv) {
mPayload[iv->id].maxPackId = 0; mPayload[iv->id].maxPackId = 0;
mPayload[iv->id].complete = false; mPayload[iv->id].complete = false;
mPayload[iv->id].requested = 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
} }

51
tools/esp8266/app.h

@ -24,6 +24,12 @@
#include "ahoywifi.h" #include "ahoywifi.h"
#include "web.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 // hier läst sich das Verhalten der app in Bezug auf MQTT
// durch PER-Conpiler defines anpassen // 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_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 ) // #define __MQTT_NO_DISCOVERCONFIG__ // das versenden der MQTTDiscoveryConfig abschalten ( gehört eigentlich ins Setup )
typedef CircularBuffer<packet_t, PACKET_BUFFER_SIZE> BufferType; typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
typedef HmRadio<DEF_RF24_CE_PIN, DEF_RF24_CS_PIN, BufferType> RadioType;
typedef Inverter<float> InverterType;
typedef HmSystem<RadioType, BufferType, MAX_NUM_INVERTERS, InverterType> HmSystemType;
typedef struct { typedef struct {
@ -65,6 +68,7 @@ class app {
void saveValues(void); void saveValues(void);
void resetPayload(Inverter<>* iv); void resetPayload(Inverter<>* iv);
bool getWifiApActive(void); bool getWifiApActive(void);
void getAvailNetworks(JsonObject obj);
uint8_t getIrqPin(void) { uint8_t getIrqPin(void) {
return mConfig.pinIrq; return mConfig.pinIrq;
@ -95,12 +99,12 @@ class app {
return String(str); return String(str);
} }
String getTimeStr(void) { String getTimeStr(uint32_t offset = 0) {
char str[20]; char str[10];
if(0 == mTimestamp) if(0 == mUtcTimestamp)
sprintf(str, "n/a"); sprintf(str, "n/a");
else 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); return String(str);
} }
@ -109,15 +113,27 @@ class app {
} }
inline uint32_t getTimestamp(void) { 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)); DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime));
if(0 == newTime) if(0 == newTime)
mUpdateNtp = true; mUpdateNtp = true;
else 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) { void eraseSettings(bool all = false) {
@ -156,7 +172,6 @@ class app {
inline bool getSettingsValid(void) { return mSettingsValid; } inline bool getSettingsValid(void) { return mSettingsValid; }
inline bool getRebootRequestState(void) { return mShowRebootRequest; } inline bool getRebootRequestState(void) { return mShowRebootRequest; }
HmSystemType *mSys; HmSystemType *mSys;
bool mShouldReboot; bool mShouldReboot;
bool mFlagSendDiscoveryConfig; bool mFlagSendDiscoveryConfig;
@ -171,7 +186,6 @@ class app {
bool buildPayload(uint8_t id); bool buildPayload(uint8_t id);
void processPayload(bool retransmit); void processPayload(bool retransmit);
void processPayload(bool retransmit, uint8_t cmd);
const char* getFieldDeviceClass(uint8_t fieldId); const char* getFieldDeviceClass(uint8_t fieldId);
const char* getFieldStateClass(uint8_t fieldId); const char* getFieldStateClass(uint8_t fieldId);
@ -185,7 +199,7 @@ class app {
while(length > 0) { while(length > 0) {
len = (length < 32) ? length : 32; len = (length < 32) ? length : 32;
mEep->read(start, buf, len); mEep->read(start, buf, len);
crc = Ahoy::crc16(buf, len, crc); crc = ah::crc16(buf, len, crc);
start += len; start += len;
length -= len; length -= len;
} }
@ -237,6 +251,7 @@ class app {
DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag)); DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag));
} }
void calculateSunriseSunset(void);
uint32_t mUptimeSecs; uint32_t mUptimeSecs;
uint32_t mPrevMillis; uint32_t mPrevMillis;
@ -249,7 +264,7 @@ class app {
bool mSettingsValid; bool mSettingsValid;
eep *mEep; eep *mEep;
uint32_t mTimestamp; uint32_t mUtcTimestamp;
bool mUpdateNtp; bool mUpdateNtp;
bool mShowRebootRequest; bool mShowRebootRequest;
@ -280,6 +295,12 @@ class app {
// serial // serial
uint16_t mSerialTicker; uint16_t mSerialTicker;
// sun
int32_t mCalculatedTimezoneOffset;
uint32_t mSunrise;
uint32_t mSunset;
uint32_t mLatestSunTimestamp;
}; };
#endif /*__APP_H__*/ #endif /*__APP_H__*/

9
tools/esp8266/config.h

@ -41,9 +41,9 @@
#define DEF_DEVICE_NAME "AHOY-DTU" #define DEF_DEVICE_NAME "AHOY-DTU"
// default pinout (GPIO Number) // default pinout (GPIO Number)
#define DEF_RF24_CS_PIN 15 #define DEF_CS_PIN 15
#define DEF_RF24_CE_PIN 2 #define DEF_CE_PIN 2
#define DEF_RF24_IRQ_PIN 0 #define DEF_IRQ_PIN 0
// default NRF24 power, possible values (0 - 3) // default NRF24 power, possible values (0 - 3)
#define DEF_AMPLIFIERPOWER 1 #define DEF_AMPLIFIERPOWER 1
@ -78,6 +78,9 @@
// threshold of minimum power on which the inverter is marked as inactive // threshold of minimum power on which the inverter is marked as inactive
#define INACT_PWR_THRESH 3 #define INACT_PWR_THRESH 3
// Timezone
#define TIMEZONE 1
// default NTP server uri // default NTP server uri
#define DEF_NTP_SERVER_NAME "pool.ntp.org" #define DEF_NTP_SERVER_NAME "pool.ntp.org"

4
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/ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#include "crc.h" #include "crc.h"
namespace Ahoy { namespace ah {
uint8_t crc8(uint8_t buf[], uint8_t len) { uint8_t crc8(uint8_t buf[], uint8_t len) {
uint8_t crc = CRC8_INIT; uint8_t crc = CRC8_INIT;
for(uint8_t i = 0; i < len; i++) { for(uint8_t i = 0; i < len; i++) {

4
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/ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -14,7 +14,7 @@
#define CRC16_MODBUS_POLYNOM 0xA001 #define CRC16_MODBUS_POLYNOM 0xA001
namespace Ahoy { namespace ah {
uint8_t crc8(uint8_t buf[], uint8_t len); uint8_t crc8(uint8_t buf[], uint8_t len);
uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff); uint16_t crc16(uint8_t buf[], uint8_t len, uint16_t start = 0xffff);
} }

12
tools/esp8266/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 5 #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_CH_CH_NAME_LEN MAX_NUM_INVERTERS * MAX_NAME_LENGTH * 4 // (4 channels)
#define INV_INTERVAL_LEN 2 // uint16_t #define INV_INTERVAL_LEN 2 // uint16_t
#define INV_MAX_RTRY_LEN 1 // uint8_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 #define NTP_ADDR_LEN 32 // DNS Name
@ -136,6 +137,11 @@ typedef struct {
// mqtt // mqtt
mqttConfig_t mqtt; mqttConfig_t mqtt;
// sun
float sunLat;
float sunLon;
bool sunDisNightCom; // disable night communication
// serial // serial
uint16_t serialInterval; uint16_t serialInterval;
bool serialShowIv; 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_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_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_START 0
#define ADDR_CFG_SYS ADDR_START #define ADDR_CFG_SYS ADDR_START

14
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/ // 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 } { FLD_HW_ID, UNIT_NONE, CH0, 8, 2, 1 }
}; };
#define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t)) #define HMINFO_LIST_LEN (sizeof(InfoAssignment) / sizeof(byteAssign_t))
#define HMINFO_PAYLOAD_LEN 14
const byteAssign_t SystemConfigParaAssignment[] = { const byteAssign_t SystemConfigParaAssignment[] = {
{ FLD_ACT_ACTIVE_PWR_LIMIT, UNIT_PCT, CH0, 2, 2, 10 }/*, { 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 }*/ { FLD_ACT_PF, UNIT_NONE, CH0, 6, 2, 1000 }*/
}; };
#define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) #define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t))
#define HMSYSTEM_PAYLOAD_LEN 14
const byteAssign_t AlarmDataAssignment[] = { const byteAssign_t AlarmDataAssignment[] = {
{ FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 } { FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 }
}; };
#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t)) #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_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_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 } { 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_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC },
{ FLD_EFF, UNIT_PCT, CH0, CALC_EFF_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__*/ #endif /*__HM_DEFINES_H__*/

58
tools/esp8266/hmInverter.h

@ -61,6 +61,7 @@ struct record_t {
uint8_t length; // length of the assignment list uint8_t length; // length of the assignment list
T *record; // data pointer T *record; // data pointer
uint32_t ts; // timestamp of last received payload uint32_t ts; // timestamp of last received payload
uint8_t pyldLen; // expected payload length for plausibility check
}; };
class CommandAbstract { class CommandAbstract {
@ -242,10 +243,16 @@ class Inverter {
val <<= 8; val <<= 8;
val |= buf[ptr]; val |= buf[ptr];
} while(++ptr != end); } while(++ptr != end);
if ((REC_TYP)(div) > 1) if(FLD_T == rec->assign[pos].fieldId) {
rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div); // temperature is a signed value!
else rec->record[pos] = (REC_TYP)((int16_t)val) / (REC_TYP)(div);
rec->record[pos] = (REC_TYP)(val); }
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) { switch (cmd) {
case RealTimeRunData_Debug: case RealTimeRunData_Debug:
if (INV_TYPE_1CH == type) { if (INV_TYPE_1CH == type) {
rec->length = (uint8_t)(HM1CH_LIST_LEN); rec->length = (uint8_t)(HM1CH_LIST_LEN);
rec->assign = (byteAssign_t *)hm1chAssignment; rec->assign = (byteAssign_t *)hm1chAssignment;
channels = 1; rec->pyldLen = HM1CH_PAYLOAD_LEN;
channels = 1;
} }
else if (INV_TYPE_2CH == type) { else if (INV_TYPE_2CH == type) {
rec->length = (uint8_t)(HM2CH_LIST_LEN); rec->length = (uint8_t)(HM2CH_LIST_LEN);
rec->assign = (byteAssign_t *)hm2chAssignment; rec->assign = (byteAssign_t *)hm2chAssignment;
channels = 2; rec->pyldLen = HM2CH_PAYLOAD_LEN;
channels = 2;
} }
else if (INV_TYPE_4CH == type) { else if (INV_TYPE_4CH == type) {
rec->length = (uint8_t)(HM4CH_LIST_LEN); rec->length = (uint8_t)(HM4CH_LIST_LEN);
rec->assign = (byteAssign_t *)hm4chAssignment; rec->assign = (byteAssign_t *)hm4chAssignment;
channels = 4; rec->pyldLen = HM4CH_PAYLOAD_LEN;
channels = 4;
} }
else { else {
rec->length = 0; rec->length = 0;
rec->assign = NULL; rec->assign = NULL;
channels = 0; rec->pyldLen = 0;
channels = 0;
} }
break; break;
case InverterDevInform_All: case InverterDevInform_All:
rec->length = (uint8_t)(HMINFO_LIST_LEN); rec->length = (uint8_t)(HMINFO_LIST_LEN);
rec->assign = (byteAssign_t *)InfoAssignment; rec->assign = (byteAssign_t *)InfoAssignment;
rec->pyldLen = HMINFO_PAYLOAD_LEN;
break; break;
case SystemConfigPara: case SystemConfigPara:
rec->length = (uint8_t)(HMSYSTEM_LIST_LEN); rec->length = (uint8_t)(HMSYSTEM_LIST_LEN);
rec->assign = (byteAssign_t *)SystemConfigParaAssignment; rec->assign = (byteAssign_t *)SystemConfigParaAssignment;
rec->pyldLen = HMSYSTEM_PAYLOAD_LEN;
break; break;
case AlarmData: case AlarmData:
rec->length = (uint8_t)(HMALARMDATA_LIST_LEN); rec->length = (uint8_t)(HMALARMDATA_LIST_LEN);
rec->assign = (byteAssign_t *)AlarmDataAssignment; rec->assign = (byteAssign_t *)AlarmDataAssignment;
rec->pyldLen = HMALARMDATA_PAYLOAD_LEN;
break; break;
default: default:
DPRINTLN(DBG_INFO, F("initAssignment: Parser not implemented")); DPRINTLN(DBG_INFO, F("initAssignment: Parser not implemented"));

62
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/ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -8,9 +8,20 @@
#include "dbg.h" #include "dbg.h"
#include <RF24.h> #include <RF24.h>
#include <RF24_config.h>
#include "crc.h" #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 CHANNEL_HOP // switch between channels or use static channel to send
#define DEFAULT_RECV_CHANNEL 3 #define DEFAULT_RECV_CHANNEL 3
@ -54,7 +65,7 @@ const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// HM Radio class // HM Radio class
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
template <uint8_t CE_PIN, uint8_t CS_PIN, class BUFFER> template <class BUFFER, uint8_t IRQ_PIN = DEF_IRQ_PIN, uint8_t CE_PIN = DEF_CE_PIN, uint8_t CS_PIN = DEF_CS_PIN, uint8_t AMP_PWR = RF24_PA_LOW>
class HmRadio { class HmRadio {
public: public:
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) { HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
@ -84,32 +95,31 @@ class HmRadio {
} }
~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")); DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setup"));
pinMode(config->pinIrq, INPUT_PULLUP); pinMode(irq, INPUT_PULLUP);
mBufCtrl = ctrl; 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 uint32_t chipID = 0; // will be filled with last 3 bytes of MAC
#ifdef ESP32 #ifdef ESP32
uint64_t MAC = ESP.getEfuseMac(); uint64_t MAC = ESP.getEfuseMac();
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF); chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF);
#else #else
chipID = ESP.getChipId(); chipID = ESP.getChipId();
#endif #endif
if(chipID) { 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++) { for(int i = 0; i < 7; i++) {
DTU_SN |= (chipID % 10) << (i * 4); dtuSn |= (chipID % 10) << (i * 4);
chipID /= 10; chipID /= 10;
} }
} }
// change the byte order of the DTU serial number and append the required 0x01 at the end // 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.setRetries(0, 0);
mNrf24.setChannel(DEFAULT_RECV_CHANNEL); mNrf24.setChannel(DEFAULT_RECV_CHANNEL);
@ -125,8 +135,8 @@ class HmRadio {
mNrf24.maskIRQ(true, true, false); mNrf24.maskIRQ(true, true, false);
DPRINT(DBG_INFO, F("RF24 Amp Pwr: RF24_PA_")); DPRINT(DBG_INFO, F("RF24 Amp Pwr: RF24_PA_"));
DPRINTLN(DBG_INFO, String(rf24AmpPowerNames[config->amplifierPower])); DPRINTLN(DBG_INFO, String(rf24AmpPowerNames[ampPwr]));
mNrf24.setPALevel(config->amplifierPower & 0x03); mNrf24.setPALevel(ampPwr & 0x03);
mNrf24.startListening(); mNrf24.startListening();
DPRINTLN(DBG_INFO, F("Radio Config:")); DPRINTLN(DBG_INFO, F("Radio Config:"));
@ -169,6 +179,10 @@ class HmRadio {
RESTORE_IRQ; RESTORE_IRQ;
} }
void enableDebug() {
mSerialDebug = true;
}
void handleIntr(void) { void handleIntr(void) {
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:handleIntr")); //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:handleIntr"));
mIrqRcvd = true; mIrqRcvd = true;
@ -195,12 +209,12 @@ class HmRadio {
} }
// crc control data // 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 >> 8) & 0xff;
mTxBuf[10 + cnt++] = (crc ) & 0xff; mTxBuf[10 + cnt++] = (crc ) & 0xff;
// crc over all // 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); sendPacket(invId, mTxBuf, 10 + cnt + 1, true);
} }
@ -215,10 +229,10 @@ class HmRadio {
mTxBuf[18] = (alarmMesId >> 8) & 0xff; mTxBuf[18] = (alarmMesId >> 8) & 0xff;
mTxBuf[19] = (alarmMesId ) & 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[24] = (crc >> 8) & 0xff;
mTxBuf[25] = (crc ) & 0xff; mTxBuf[25] = (crc ) & 0xff;
mTxBuf[26] = Ahoy::crc8(mTxBuf, 26); mTxBuf[26] = ah::crc8(mTxBuf, 26);
sendPacket(invId, mTxBuf, 27, true); sendPacket(invId, mTxBuf, 27, true);
} }
@ -231,7 +245,7 @@ class HmRadio {
CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8)); CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8));
mTxBuf[9] = pid; mTxBuf[9] = pid;
if(calcCrc) { if(calcCrc) {
mTxBuf[10] = Ahoy::crc8(mTxBuf, 10); mTxBuf[10] = ah::crc8(mTxBuf, 10);
sendPacket(invId, mTxBuf, 11, false); sendPacket(invId, mTxBuf, 11, false);
} }
} }
@ -245,7 +259,7 @@ class HmRadio {
buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7); 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]); bool valid = (crc == buf[*len-1]);
return valid; return valid;

22
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/ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -7,12 +7,13 @@
#define __HM_SYSTEM_H__ #define __HM_SYSTEM_H__
#include "hmInverter.h" #include "hmInverter.h"
#ifndef NO_RADIO
#include "hmRadio.h" #include "hmRadio.h"
#endif #include "CircularBuffer.h"
typedef CircularBuffer<packet_t, PACKET_BUFFER_SIZE> BufferType;
typedef HmRadio<BufferType> RadioType;
template <class RADIO, class BUFFER, uint8_t MAX_INVERTER=3, class INVERTERTYPE=Inverter<float>> template <uint8_t MAX_INVERTER=3, class RADIO = RadioType, class BUFFER = BufferType, class INVERTERTYPE=Inverter<float>>
class HmSystem { class HmSystem {
public: public:
typedef RADIO RadioType; typedef RADIO RadioType;
@ -28,9 +29,12 @@ class HmSystem {
// TODO: cleanup // TODO: cleanup
} }
void setup(config_t *config) { void setup() {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:setup")); Radio.setup(&BufCtrl);
Radio.setup(config, &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[]) { INVERTERTYPE *addInverter(const char *name, uint64_t serial, uint16_t chMaxPwr[]) {
@ -96,6 +100,10 @@ class HmSystem {
return mNumInv; return mNumInv;
} }
void enableDebug() {
Radio.enableDebug();
}
private: private:
INVERTERTYPE mInverter[MAX_INVERTER]; INVERTERTYPE mInverter[MAX_INVERTER];
uint8_t mNumInv; uint8_t mNumInv;

14
tools/esp8266/html/api.js

@ -19,8 +19,10 @@ function getAjax(url, ptr, method="GET", json=null) {
} }
function p() { function p() {
if(xhr.readyState == 4) { if(xhr.readyState == 4) {
if(null != xhr.responseText) if(null != xhr.responseText) {
ptr(JSON.parse(xhr.responseText)); if(null != ptr)
ptr(JSON.parse(xhr.responseText));
}
} }
} }
} }
@ -66,6 +68,14 @@ function sel(name, opt, selId) {
return e; return e;
} }
function opt(val, html) {
o = document.createElement('option');
o.value = val;
o.innerHTML = html;
e.appendChild(o);
return o;
}
function div(cl) { function div(cl) {
e = document.createElement('div'); e = document.createElement('div');
e.classList.add(...cl); e.classList.add(...cl);

48
tools/esp8266/html/index.html

@ -12,10 +12,9 @@
<SCRIPT> <SCRIPT>
function promptFunction() { function promptFunction() {
var Text = prompt("This project was started from https://www.mikrocontroller.net/topic/525778 this discussion.\n\n" + var Text = prompt("This project was started from https://www.mikrocontroller.net/topic/525778 this discussion.\n\n" +
"The Hoymiles protocol was decrypted through the voluntary efforts of many participants. ahoy, among others, was developed based on this work. The project is licensed under an Open Source " + "The Hoymiles protocol was decrypted through the voluntary efforts of many participants. ahoy, among others, was developed based on this work.\n" +
"License (https://github.com/lumapu/ahoy/blob/main/LICENSE) GNU General Public License v3.0.\n\n" +
"The software was developed to the best of our knowledge and belief. Nevertheless, no liability can be accepted for a malfunction or guarantee loss of the inverter.\n\n" + "The software was developed to the best of our knowledge and belief. Nevertheless, no liability can be accepted for a malfunction or guarantee loss of the inverter.\n\n" +
"Ahoy is freely available. If you paid money for the software, you probably got ripped off.\n\nPlease type in 'YeS', you are accept our Disclaim.", ""); "Ahoy is freely available. If you paid money for the software, you probably got ripped off.\n\nPlease type in 'YeS', you are accept our Disclaim. You should then save your config.", "");
if (Text != "YeS") if (Text != "YeS")
promptFunction(); promptFunction();
else else
@ -28,10 +27,15 @@
<a href="/live">Visualization</a><br/> <a href="/live">Visualization</a><br/>
<br/> <br/>
<a href="/setup">Setup</a><br/> <a href="/setup">Setup</a><br/>
<br/>
<a href="/serial">Webserial & Commands</a><br/> <a href="/serial">Webserial & Commands</a><br/>
</p> </p>
<p><span class="des">Uptime: </span><span id="uptime"></span></p> <p><span class="des">Uptime: </span><span id="uptime"></span></p>
<p><span class="des">ESP-Time: </span><span id="date"></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">WiFi RSSI: </span><span id="wifi_rssi"></span> dBm</p>
<p> <p>
<span class="des">Statistics: </span> <span class="des">Statistics: </span>
@ -42,15 +46,15 @@
<p>Every <span id="refresh"></span> seconds the values are updated</p> <p>Every <span id="refresh"></span> seconds the values are updated</p>
<div id="note"> <div id="note">
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a><br/>
New updates can be found on Github: <a href="https://github.com/lumapu/ahoy" target="_blank">https://github.com/lumapu/ahoy</a><br/> New updates can be found on Github: <a href="https://github.com/lumapu/ahoy" target="_blank">https://github.com/lumapu/ahoy</a><br/>
<br/> <br/>
Please report issues using the feature provided by <a href="https://github.com/lumapu/ahoy/issues">Github</a><br/> Please report issues in <a href="https://github.com/lumapu/ahoy/issues">Github</a><br/>
<br/> Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a><br/>
Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a> Support this project: <a href="https://paypal.me/lupusch">Donate</a><br/>
<br/>
<p class="lic"><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de">Creative Commons - https://creativecommons.org/licenses/by-nc-sa/3.0/de/</a><br/> <p class="lic"><a href="https://creativecommons.org/licenses/by-nc-sa/3.0/de">Creative Commons - https://creativecommons.org/licenses/by-nc-sa/3.0/de/</a><br/>
Check the licenses which are published on <a href="https://github.com/lumapu/ahoy">https://github.com/lumapu/ahoy</a> as well</p> Check the licenses which are published on <a href="https://github.com/lumapu/ahoy">https://github.com/lumapu/ahoy</a> as well</p><br/>
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a>
</div> </div>
</div> </div>
<div id="footer"> <div id="footer">
@ -75,16 +79,15 @@
function setTime() { function setTime() {
var date = new Date(); var date = new Date();
var offset = date.getTimezoneOffset() * -60;
var obj = new Object(); var obj = new Object();
obj.cmd = "set_time"; 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)); getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj));
} }
function parseSys(obj) { function parseSys(obj) {
// Disclaimer // Disclaimer
if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction()); //if(obj["disclaimer"] == false) sessionStorage.setItem("gDisclaimer", promptFunction());
document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"]; document.getElementById("version").innerHTML = "Git SHA: " + obj["build"] + " :: " + obj["version"];
document.getElementById("wifi_rssi").innerHTML = obj["wifi_rssi"]; document.getElementById("wifi_rssi").innerHTML = obj["wifi_rssi"];
@ -95,20 +98,33 @@
var hrs = parseInt(up / 3600) % 24; var hrs = parseInt(up / 3600) % 24;
var min = parseInt(up / 60) % 60; var min = parseInt(up / 60) % 60;
var sec = up % 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, " document.getElementById("uptime").innerHTML = days + " Days, "
+ ("0"+hrs).substr(-2) + ":" + ("0"+hrs).substr(-2) + ":"
+ ("0"+min).substr(-2) + ":" + ("0"+min).substr(-2) + ":"
+ ("0"+sec).substr(-2); + ("0"+sec).substr(-2);
var dSpan = document.getElementById("date") var dSpan = document.getElementById("date");
if(0 != obj["ts_now"]) if(0 != obj["ts_now"])
dSpan.innerHTML = date.toLocaleString('de-DE', {timeZone: 'UTC'}); dSpan.innerHTML = date.toLocaleString('de-DE');
else { else {
dSpan.innerHTML = "";
var e = inp("set", "sync from browser", 0, ["btn"], "set", "button"); var e = inp("set", "sync from browser", 0, ["btn"], "set", "button");
dSpan.appendChild(span("NTP timeserver unreachable. ")); dSpan.appendChild(span("NTP timeserver unreachable. "));
dSpan.appendChild(e); dSpan.appendChild(e);
dSpan.appendChild(span("", ["span"], "apiResult")); dSpan.appendChild(span("", ["span"], "apiResult"));
e.addEventListener("click", setTime); e.addEventListener("click", setTime);
} }
if(!obj["ts_sun_upd"]) {
var e = document.getElementById("sun");
if(null != e)
e.parentNode.removeChild(e);
}
else {
document.getElementById("sunrise").innerHTML = sunrise.toLocaleString('de-DE');
document.getElementById("sunset").innerHTML = sunset.toLocaleString('de-DE');
}
} }
function parseStat(obj) { function parseStat(obj) {
@ -116,7 +132,7 @@
+ "\nRX fail: " + obj["rx_fail"] + "\nRX fail: " + obj["rx_fail"]
+ "\nRX no answer: " + obj["rx_fail_answer"] + "\nRX no answer: " + obj["rx_fail_answer"]
+ "\nFrames received: " + obj["frame_cnt"] + "\nFrames received: " + obj["frame_cnt"]
+ "\nTX Cnt: " + obj["tx_cnt"]; + "\nTX cnt: " + obj["tx_cnt"];
} }
function parseIv(obj) { function parseIv(obj) {
@ -133,7 +149,7 @@
if(false == i["is_avail"]) { if(false == i["is_avail"]) {
if(i["ts_last_success"] > 0) { if(i["ts_last_success"] > 0) {
var date = new Date(i["ts_last_success"] * 1000); var date = new Date(i["ts_last_success"] * 1000);
html += "-> last successful transmission: " + date.toLocaleString('de-DE', {timeZone: 'UTC'}); html += "-> last successful transmission: " + date.toLocaleString('de-DE');
} }
} }

12
tools/esp8266/html/serial.html

@ -85,14 +85,19 @@
if(null == root) return; if(null == root) return;
root = root.inverter; root = root.inverter;
for(var i = 0; i < root.inverter.length; i++) for(var i = 0; i < root.inverter.length; i++) {
{
inv = root.inverter[i]; inv = root.inverter[i];
var opt = document.createElement('option'); var opt = document.createElement('option');
opt.value = inv.id; opt.value = inv.id;
opt.innerHTML = inv.name; opt.innerHTML = inv.name;
select.appendChild(opt); select.appendChild(opt);
} }
// set time offset for serial console
var obj = new Object();
obj.cmd = "serial_utc_offset";
obj.ts = new Date().getTimezoneOffset() * -60;
getAjax("/api/setup", null, "POST", JSON.stringify(obj));
} }
document.getElementById("clear").addEventListener("click", function() { document.getElementById("clear").addEventListener("click", function() {
@ -133,8 +138,7 @@
e.innerHTML = "Error: " + obj["error"]; e.innerHTML = "Error: " + obj["error"];
} }
function get_selected_iv() function get_selected_iv() {
{
var e = document.getElementById("InvID"); var e = document.getElementById("InvID");
return parseInt(e.value); return parseInt(e.value);
} }

127
tools/esp8266/html/setup.html

@ -36,6 +36,10 @@
<fieldset> <fieldset>
<legend class="des">WiFi</legend> <legend class="des">WiFi</legend>
<p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p> <p>Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.</p>
<label for="networks">Avail Networks</label>
<select name="networks" id="networks" onChange="selNet()">
<option value="-1">scanning ...</option>
</select>
<label for="ssid">SSID</label> <label for="ssid">SSID</label>
<input type="text" name="ssid" class="text"/> <input type="text" name="ssid" class="text"/>
<label for="pwd">Password</label> <label for="pwd">Password</label>
@ -72,6 +76,20 @@
</fieldset> </fieldset>
</div> </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> <button type="button" class="s_collapsible">MQTT</button>
<div class="s_content"> <div class="s_content">
<fieldset> <fieldset>
@ -156,10 +174,9 @@
function setTime() { function setTime() {
var date = new Date(); var date = new Date();
var offset = date.getTimezoneOffset() * -60;
var obj = new Object(); var obj = new Object();
obj.cmd = "set_time"; 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)); getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj));
} }
@ -219,7 +236,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(lbl(id + i[0], i[2]));
iv.appendChild(inp(id + i[0], obj[i[1]], i[3])); iv.appendChild(inp(id + i[0], obj[i[1]], i[3]));
} }
@ -271,30 +288,68 @@
document.getElementsByName(i[0])[0].value = obj[i[1]]; document.getElementsByName(i[0])[0].value = obj[i[1]];
} }
function parsePinout(obj) { 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, type) {
var e = document.getElementById("pinout"); var e = document.getElementById("pinout");
pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq']]; pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq']];
for(p of pins) { for(p of pins) {
e.appendChild(lbl(p[1], p[0].toUpperCase())); e.appendChild(lbl(p[1], p[0].toUpperCase()));
e.appendChild(sel(p[1], [ if("ESP8266" == type) {
[0, "D3 (GPIO0)"], e.appendChild(sel(p[1], [
[1, "TX (GPIO1)"], [0, "D3 (GPIO0)"],
[2, "D4 (GPIO2)"], [1, "TX (GPIO1)"],
[3, "RX (GPIO3)"], [2, "D4 (GPIO2)"],
[4, "D2 (GPIO4)"], [3, "RX (GPIO3)"],
[5, "D1 (GPIO5)"], [4, "D2 (GPIO4)"],
[6, "GPIO6"], [5, "D1 (GPIO5)"],
[7, "GPIO7"], [6, "GPIO6"],
[8, "GPIO8"], [7, "GPIO7"],
[9, "GPIO9"], [8, "GPIO8"],
[10, "GPIO10"], [9, "GPIO9"],
[11, "GPIO11"], [10, "GPIO10"],
[12, "D6 (GPIO12)"], [11, "GPIO11"],
[13, "D7 (GPIO13)"], [12, "D6 (GPIO12)"],
[14, "D5 (GPIO14)"], [13, "D7 (GPIO13)"],
[15, "D8 (GPIO15)"], [14, "D5 (GPIO14)"],
[16, "D0 (GPIO16 - no IRQ!)"] [15, "D8 (GPIO15)"],
], obj[p[0]])); [16, "D0 (GPIO16 - no IRQ!)"]
], obj[p[0]]));
}
else {
e.appendChild(sel(p[1], [
[0, "GPIO0"],
[1, "TX (GPIO1)"],
[2, "GPIO2 (LED)"],
[3, "RX (GPIO3)"],
[4, "GPIO4"],
[5, "GPIO5"],
[12, "GPIO12"],
[13, "GPIO13"],
[14, "GPIO14"],
[15, "GPIO15"],
[16, "GPIO16"],
[17, "GPIO17"],
[18, "GPIO18"],
[19, "GPIO19"],
[21, "GPIO21"],
[22, "GPIO22"],
[23, "GPIO23"],
[25, "GPIO25"],
[26, "GPIO26"],
[27, "GPIO27"],
[32, "GPIO32"],
[33, "GPIO33"],
[34, "GPIO34"],
[35, "GPIO35"],
[36, "VP (GPIO36)"],
[39, "VN (GPIO39)"]
], obj[p[0]]));
}
} }
} }
@ -321,16 +376,40 @@
parseIv(root["inverter"]); parseIv(root["inverter"]);
parseMqtt(root["mqtt"]); parseMqtt(root["mqtt"]);
parseNtp(root["ntp"]); parseNtp(root["ntp"]);
parsePinout(root["pinout"]); parseSun(root["sun"]);
parsePinout(root["pinout"], root["system"]["esp_type"]);
parseRadio(root["radio"]); parseRadio(root["radio"]);
parseSerial(root["serial"]); parseSerial(root["serial"]);
} }
getAjax('/api/setup/networks', listNetworks);
window.setInterval("getAjax('/api/setup/networks', listNetworks)", 7000);
}
function listNetworks(root) {
if(root["networks"].length > 0) {
var s = document.getElementById("networks");
var i, l = s.options.length - 1;
for(i = l; i >= 0; i--) {
s.remove(i);
}
for(i = 0; i < root["networks"].length; i++) {
s.appendChild(opt(root["networks"][i]["ssid"], root["networks"][i]["ssid"] + " (" + root["networks"][i]["rssi"] + " dBm)"));
}
}
}
function selNet() {
var s = document.getElementById("networks");
var e = document.getElementsByName("ssid")[0];
e.value = s.value;
} }
hiddenInput = document.getElementById("disclaimer") hiddenInput = document.getElementById("disclaimer")
hiddenInput.value = sessionStorage.getItem("gDisclaimer"); hiddenInput.value = sessionStorage.getItem("gDisclaimer");
getAjax("/api/setup", parse); getAjax("/api/setup", parse);
</script> </script>
</body> </body>
</html> </html>

12
tools/esp8266/html/visualization.html

@ -76,8 +76,16 @@
} }
var ts = div(["ts"]); var ts = div(["ts"]);
var date = new Date(iv["ts_last_success"] * 1000); var ageInfo = "Last received data requested at: ";
ts.innerHTML = "Last received data requested at: " + date.toLocaleString('de-DE', {timeZone: 'UTC'}); 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); main.appendChild(ts);
ivHtml.push(main); ivHtml.push(main);
} }

14
tools/esp8266/web.cpp

@ -283,6 +283,18 @@ void web::showSave(AsyncWebServerRequest *request) {
mConfig->ntpPort = request->arg("ntpPort").toInt() & 0xffff; 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 // mqtt
if(request->arg("mqttAddr") != "") { if(request->arg("mqttAddr") != "") {
String addr = request->arg("mqttAddr"); String addr = request->arg("mqttAddr");
@ -462,7 +474,7 @@ void web::serialCb(String msg) {
msg.replace("\r\n", "<rn>"); msg.replace("\r\n", "<rn>");
if(mSerialAddTime) { if(mSerialAddTime) {
if((9 + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { 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; mSerialBufFill += 9;
} }
else { else {

47
tools/esp8266/webApi.cpp

@ -18,6 +18,8 @@ webApi::webApi(AsyncWebServer *srv, app *app, sysConfig_t *sysCfg, config_t *con
mConfig = config; mConfig = config;
mStat = stat; mStat = stat;
mVersion = version; mVersion = version;
mTimezoneOffset = 0;
} }
@ -43,16 +45,17 @@ void webApi::onApi(AsyncWebServerRequest *request) {
Inverter<> *iv = mApp->mSys->getInverterByPos(0, false); Inverter<> *iv = mApp->mSys->getInverterByPos(0, false);
String path = request->url().substring(5); String path = request->url().substring(5);
if(path == "system") getSystem(root); if(path == "system") getSystem(root);
else if(path == "statistics") getStatistics(root); else if(path == "statistics") getStatistics(root);
else if(path == "inverter/list") getInverterList(root); else if(path == "inverter/list") getInverterList(root);
else if(path == "index") getIndex(root); else if(path == "index") getIndex(root);
else if(path == "setup") getSetup(root); else if(path == "setup") getSetup(root);
else if(path == "live") getLive(root); else if(path == "setup/networks") getNetworks(root);
else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All)); else if(path == "live") getLive(root);
else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData)); else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All));
else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara)); else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData));
else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug)); else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara));
else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug));
else else
getNotFound(root, F("http://") + request->host() + F("/api/")); 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("build")] = String(AUTO_GIT_HASH);
obj[F("ts_uptime")] = mApp->getUptime(); obj[F("ts_uptime")] = mApp->getUptime();
obj[F("ts_now")] = mApp->getTimestamp(); 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("wifi_rssi")] = WiFi.RSSI();
obj[F("disclaimer")] = mConfig->disclaimer; 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); 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) { void webApi::getPinout(JsonObject obj) {
@ -267,12 +285,19 @@ void webApi::getSetup(JsonObject obj) {
getInverterList(obj.createNestedObject(F("inverter"))); getInverterList(obj.createNestedObject(F("inverter")));
getMqtt(obj.createNestedObject(F("mqtt"))); getMqtt(obj.createNestedObject(F("mqtt")));
getNtp(obj.createNestedObject(F("ntp"))); getNtp(obj.createNestedObject(F("ntp")));
getSun(obj.createNestedObject(F("sun")));
getPinout(obj.createNestedObject(F("pinout"))); getPinout(obj.createNestedObject(F("pinout")));
getRadio(obj.createNestedObject(F("radio"))); getRadio(obj.createNestedObject(F("radio")));
getSerial(obj.createNestedObject(F("serial"))); getSerial(obj.createNestedObject(F("serial")));
} }
//-----------------------------------------------------------------------------
void webApi::getNetworks(JsonObject obj) {
mApp->getAvailNetworks(obj);
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void webApi::getLive(JsonObject obj) { void webApi::getLive(JsonObject obj) {
getSystem(obj.createNestedObject(F("system"))); getSystem(obj.createNestedObject(F("system")));
@ -414,6 +439,8 @@ bool webApi::setSetup(DynamicJsonDocument jsonIn, JsonObject jsonOut) {
mApp->setTimestamp(jsonIn[F("ts")]); mApp->setTimestamp(jsonIn[F("ts")]);
else if(F("sync_ntp") == jsonIn[F("cmd")]) else if(F("sync_ntp") == jsonIn[F("cmd")])
mApp->setTimestamp(0); // 0: update ntp flag 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")]) else if(F("discovery_cfg") == jsonIn[F("cmd")])
mApp->mFlagSendDiscoveryConfig = true; // for homeassistant mApp->mFlagSendDiscoveryConfig = true; // for homeassistant
else { else {

9
tools/esp8266/webApi.h

@ -21,6 +21,10 @@ class webApi {
void setup(void); void setup(void);
void loop(void); void loop(void);
uint32_t getTimezoneOffset() {
return mTimezoneOffset;
}
private: private:
void onApi(AsyncWebServerRequest *request); void onApi(AsyncWebServerRequest *request);
void onApiPost(AsyncWebServerRequest *request); void onApiPost(AsyncWebServerRequest *request);
@ -33,13 +37,14 @@ class webApi {
void getInverterList(JsonObject obj); void getInverterList(JsonObject obj);
void getMqtt(JsonObject obj); void getMqtt(JsonObject obj);
void getNtp(JsonObject obj); void getNtp(JsonObject obj);
void getSun(JsonObject obj);
void getPinout(JsonObject obj); void getPinout(JsonObject obj);
void getRadio(JsonObject obj); void getRadio(JsonObject obj);
void getSerial(JsonObject obj); void getSerial(JsonObject obj);
void getIndex(JsonObject obj); void getIndex(JsonObject obj);
void getSetup(JsonObject obj); void getSetup(JsonObject obj);
void getNetworks(JsonObject obj);
void getLive(JsonObject obj); void getLive(JsonObject obj);
void getRecord(JsonObject obj, record_t<> *rec); void getRecord(JsonObject obj, record_t<> *rec);
@ -59,6 +64,8 @@ class webApi {
sysConfig_t *mSysCfg; sysConfig_t *mSysCfg;
statistics_t *mStat; statistics_t *mStat;
char *mVersion; char *mVersion;
uint32_t mTimezoneOffset;
}; };
#endif /*__WEB_API_H__*/ #endif /*__WEB_API_H__*/

Loading…
Cancel
Save