diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml new file mode 100644 index 00000000..838293b4 --- /dev/null +++ b/.github/workflows/compile_development.yml @@ -0,0 +1,58 @@ +name: Ahoy Dev-Build for ESP8266/ESP32 + +on: + push: + branches: development* + paths-ignore: + - '**.md' # Do no build on *.md changes +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + ref: development02 + - uses: benjlevesque/short-sha@v1.2 + id: short-sha + with: + length: 7 + - name: cache-pip + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: cache-platformio + uses: actions/cache@v3 + with: + path: ~/.platformio + key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} + - name: setup-python + uses: actions/setup-python@v3 + - name: install-platformio + run: | + python -m pip install --upgrade pip + pip install --upgrade platformio + - name: update-html + working-directory: tools/esp8266/html + run: python convert.py + - name: Run PlatformIO + run: pio run -d tools/esp8266 --environment esp8266-release --environment esp32-wroom32-release + - name: rename-binary-files + id: rename-binary-files + working-directory: tools/esp8266/scripts + run: python getVersion.py + - name: set-version + uses: cschleiden/replace-tokens@v1 + with: + files: tools/esp8266/User_Manual.md + env: + VERSION: ${{ steps.rename-binary-files.outputs.name }} + - name: create-artifact + run: zip --junk-paths ${{ steps.rename-binary-files.outputs.name }}.zip tools/esp8266/.pio/build/out/* tools/esp8266/User_Manual.md + - uses: actions/upload-artifact@v3 + with: + name: ${{ steps.rename-binary-files.outputs.name }}_dev_build + path: ./${{ steps.rename-binary-files.outputs.name }}.zip diff --git a/.github/workflows/compile_esp8266.yml b/.github/workflows/compile_esp8266.yml index 245a5aac..787465d3 100644 --- a/.github/workflows/compile_esp8266.yml +++ b/.github/workflows/compile_esp8266.yml @@ -39,7 +39,7 @@ jobs: working-directory: tools/esp8266/html run: python convert.py - name: Run PlatformIO - run: pio run -d tools/esp8266 --environment esp8266-release + run: pio run -d tools/esp8266 --environment esp8266-release --environment esp32-wroom32-release - name: rename-binary-files id: rename-binary-files working-directory: tools/esp8266/scripts @@ -72,4 +72,4 @@ jobs: upload_url: ${{ steps.create-release.outputs.upload_url }} asset_path: ./${{ steps.rename-binary-files.outputs.name }}.zip asset_name: ${{ steps.rename-binary-files.outputs.name }}.zip - asset_content_type: application/zip \ No newline at end of file + asset_content_type: application/zip diff --git a/README.md b/README.md index b28364bb..2411a7f2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ ![Logo](https://github.com/grindylow/ahoy/blob/main/doc/logo1_small.png?raw=true) # ahoy -Various tools, examples, and documentation for communicating with Hoymiles microinverters. +Ahoi is a project to bypass the original Hoymiles cloud solution. +In order to use this project, it is important what the area of ​​​​application looks like. +With each version it is necessary to have an NRF24L01+. -In particular: +Click on the link below you are interested in. +There you will find further explanations on how to proceed. (*Note: It is still under construction!*) -* `doc/hoymiles-format-description.txt` is a [detailed description of the communications format](doc/hoymiles-format-description.md) and the history of this project -* `doc/getting-started-ESP8266.md` shows the [hardware setup for an ESP8266-based system](doc/getting-started-ESP8266.md) -* The `tools` folder contains various software tools for RaspberryPi, Arduino and ESP8266/ESP32: - * A [version for ESP8266](tools/esp8266/) that includes an web interface ![](../../actions/workflows/compile_esp8266.yml/badge.svg) - * A [version for Arduino Nano](tools/nano/NRF24_SendRcv/) - * An [alternative Version of the above](tools/NRF24_SendRcv/) - * A [different implementation](tools/HoyDtuSim/) - * An [implementation for Raspberry Pi](tools/rpi/) that polls an inverter and archives results as log files/stdout as well as posting them to an MQTT broker. +##### Most updated section +- [ESP8266](tools/esp8266/) that includes an web interface ![](../../actions/workflows/compile_esp8266.yml/badge.svg) -Contributors are always welcome! +##### will be updated as needed +- [Arduino Nano](tools/nano/NRF24_SendRcv/) +- [Raspberry Pi](tools/rpi/) +- [others](tools/nano/NRF24_SendRcv/) + +If errors occur or you have suggestions for ideas, please feel free to contact us [here](https://github.com/grindylow/ahoy/issues). + +**Contributors are always welcome!** diff --git a/tools/esp8266/README.md b/tools/esp8266/README.md index 24a3de4a..0e08637a 100644 --- a/tools/esp8266/README.md +++ b/tools/esp8266/README.md @@ -1,10 +1,23 @@ ## OVERVIEW -This code is intended to run on a Wemos D1mini or similar. The code is based on 'Hubi's code, which can be found here: +This page describes how the module of a Wemos D1 mini and ESP8266 is wired to the radio module, flashed and how the further steps are to communicate with the WR HM series. -The NRF24L01+ radio module is connected to the standard SPI pins. Additional there are 3 pins, which can be set individual: CS, CE and IRQ -These pins can be changed from the /setup URL +#### Compatiblity +For now the following inverters should work out of the box: +- HM350 +- HM400 +- HM600 +- HM700 +- HM800 +- HM1200 +- HM1500 + +The NRF24L01+ radio module is connected to the standard SPI pins. +Additional there are 3 pins, which can be set individual: CS, CE and IRQ +These pins can be changed in the http:///setup URL or with a click on the Setup link. +## ESP8266 electr. associate + ## Compile @@ -13,10 +26,10 @@ This code can be compiled using Visual Studio Code and **PlatformIO** Addon. The - Board: Generic ESP8266 Module - Flash-Size: 1MB (FS: none, OTA: 502kB) - Install libraries (not included in the Arduino IDE 1.8.19): - - Time Arduino Time library (TimeLib.h) - - RF24 Optimized high speed nRF24L01+ driver class documentation - - PubSubClient A client library for MQTT messaging. By Nick O'Leary - - ArduinoJson Arduino Json library + - `Time` 1.6.1 + - `RF24` 1.4.5 + - `PubSubClient` 2.8 + - `ArduinoJson` 6.19.4 ### Optional Configuration before compilation @@ -29,45 +42,67 @@ config_override.h is excluded from version control and stays local. ## Flash ESP with firmware +#### nodemcu-pyflasher (easy way) +1. download the flash-tool [nodemcu-pyflasher](https://github.com/marcelstoer/nodemcu-pyflasher) +2. download latest release bin-file from [ahoy_](https://github.com/grindylow/ahoy/releases) +3. connect the target device with your pc. +4. Set the correct serial port and select the correct *.bin file +5. click now on "Flash NodeMCU" + 1. flash the ESP with the compiled firmware using the UART pins or any preinstalled firmware with OTA capabilities 2. repower the ESP 3. the ESP will start as access point (AP) if there is no network config stored in its eeprom 4. connect to the AP, you will be forwarded to the setup page -5. configure your WiFi settings, save, repower -6. check your router or serial console for the IP address of the module. You can try ping the configured device name as well. +X. configure your WiFi settings, save, repower +Y. check your router or serial console for the IP address of the module. You can try ping the configured device name as well. + +! ATTENTION: If you update from a very low version to the newest, please make sure to wipe all flash data! + +## pages +| page | output | +| ---- | ------ | +| /uptime | 0 Days, 01:37:34; now: 2022-08-21 11:13:53 | +| /reboot | reboot dtu device | +| /erase | | +| /factory | | +| /setup | | +| /save | open the setup site | +| /cmdstat | show stat from the home site | +| /visualization | | +| /livedata | | +| /json | json output from the livedata | +| /api | | ## Usage -Connect the ESP to power and to your serial console (optional). The webinterface has the following abilities: - +The webinterface has the following abilities: - OTA Update (over the air update) -- Configuration (Wifi, inverter(s), Pinout, MQTT) +- Configuration (Wifi, inverter(s), NTP Server, Pinout, MQTT, Amplifier Power Level, Debug) - visual display of the connected inverters / modules - some statistics about communication (debug) The serial console will print the converted values which were read out of the inverter(s) - - -## Compatiblity - -For now the following inverters should work out of the box: - -- HM350 -- HM400 -- HM600 -- HM700 -- HM800 -- HM1200 -- HM1500 - -## USED LIBRARIES - -- `ESP8266WiFi` 1.0 -- `DNSServer` 1.1.0 -- `Ticker` 1.0 -- `ESP8266HTTPUpdateServer` 1.0 -- `Time` 1.6.1 -- `RF24` 1.4.5 -- `PubSubClient` 2.8 -- `ArduinoJson` 6.19.4 \ No newline at end of file + +### MQTT command to set the DTU without webinterface + [Read here](https://github.com/grindylow/ahoy/blob/development02/tools/esp8266/User_Manual.md) + + ## Todo's [See this post](https://github.com/grindylow/ahoy/issues/142) + +- [ ] Wechsel zu AsyncWebServer und ElegantOTA für Stabilität +- [x] klarer Scheduler / Task manager, der ggf. den Receive Task priorisieren kann +- [x] Device Info Kommandos (Firmware Version, etc.) über das Dashboard anzeigen [Device Information ( `0x15` `REQ_ARW_DAT_ALL` ) SubCmd Kommandos #145](https://github.com/grindylow/ahoy/issues/145) +- [ ] AlarmData & AlarmUpdate Parsen und auf eigener Seite darstellen + +------------------ SWIM LANE --------------------------- + +- [ ] Device Control Kommandos aus dem Setup ermöglichen (TurnOn, TurnOff, Restart, ActivePower Limit, ReactivePower Limit, SetPowerFactor, etc.) +- [ ] Settings exportieren / importieren (API/UI) +- [ ] Settings in settings.ini speichern (LittleFS statt EEPROM) [Settings in settings.ini speichern (LittleFS statt EEPROM) #164](https://github.com/grindylow/ahoy/issues/164) +- [ ] Homepage aufräumen nur ein Status (aktuell drei AJAX Calls /uptime, /time, /cmdstat) +- [ ] app.cpp aufräumen und in hmRadio / hmProtokollGen3 auslagern +- [ ] MI Wechselrichter unterstützen (miSystem, miInverter, miDefines, miProtokollGen2 etc.) +- [ ] nRF24 Interrupt Handling sinnvoll oder warum macht die nRF24 Bibliothek ständig `0x07` Statusabfragen [NRF24 polling trotz aktiviertem IRQ #83](https://github.com/grindylow/ahoy/issues/83) +- [ ] Debug Level im Setup änderbar -auch Livedata Visualisierung abschalten ? +- [ ] MQTT Discovery (HomeAssistant) im Setup optional machen +- [x] MQTT Subscribe nur beim Reconnect [Das subscribe in der Reconnect Procedure sollte doch nur nach einem conect ausgeführt werden und nicht bei jedem Duchlauf #139](https://github.com/grindylow/ahoy/issues/139) diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index 75fe77df..c5989562 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -20,7 +20,7 @@ app::app() { mWifi = new ahoywifi(this, &mSysConfig, &mConfig); resetSystem(); - loadDefaultConfig(); + loadDefaultConfig(); mSys = new HmSystemType(); } @@ -64,7 +64,7 @@ void app::loop(void) { } } - + mSys->Radio.loop(); yield(); @@ -114,31 +114,31 @@ void app::loop(void) { } if(NULL != iv && p->packet[0] == (TX_REQ_DEVCONTROL + 0x80)) { // response from dev control command DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received")); - iv->devControlRequest = false; - switch (p->packet[12]){ - case ActivePowerContr: - if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet){ // ok inverter accepted the set point copy it to dtu eeprom - if ((iv->powerLimit[1] & 0xff00) >0){ // User want to have it persistent - mEep->write(ADDR_INV_PWR_LIM + iv->id * 2,iv->powerLimit[0]); - mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2,iv->powerLimit[1]); - updateCrc(); - mEep->commit(); - DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom")); - } else { - DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1])); + iv->devControlRequest = false; + switch (p->packet[12]) { + case ActivePowerContr: + if (iv->devControlCmd >= ActivePowerContr && iv->devControlCmd <= PFSet) { // ok inverter accepted the set point copy it to dtu eeprom + if ((iv->powerLimit[1] & 0xff00) > 0) { // User want to have it persistent + mEep->write(ADDR_INV_PWR_LIM + iv->id * 2, iv->powerLimit[0]); + mEep->write(ADDR_INV_PWR_LIM_CON + iv->id * 2, iv->powerLimit[1]); + updateCrc(); + mEep->commit(); + DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]) + F(", written to dtu eeprom")); + } else + DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1])); + iv->devControlCmd = Init; + } + break; + + default: + if (iv->devControlCmd == ActivePowerContr) { + //case inverter did not accept the sent limit; set back to last stored limit + mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0])); + mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1])); + DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point")); } iv->devControlCmd = Init; - } - break; - default: - if (iv->devControlCmd == ActivePowerContr){ - //case inverter did not accept the sent limit; set back to last stored limit - mEep->read(ADDR_INV_PWR_LIM + iv->id * 2, (uint16_t *)&(iv->powerLimit[0])); - mEep->read(ADDR_INV_PWR_LIM_CON + iv->id * 2, (uint16_t *)&(iv->powerLimit[1])); - DPRINTLN(DBG_INFO, F("Inverter has not accepted power limit set point")); - } - iv->devControlCmd = Init; - break; + break; } } } @@ -160,20 +160,7 @@ void app::loop(void) { if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) { mMqttTicker = 0; mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176 - char topic[30], val[10]; - for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if(NULL != iv) { - if(iv->isAvailable(mTimestamp)) { - for(uint8_t i = 0; i < iv->listLen; i++) { - snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); - snprintf(val, 10, "%.3f", iv->getValue(i)); - mMqtt.sendMsg(topic, val); - yield(); - } - } - } - } + char val[10]; snprintf(val, 10, "%ld", millis()/1000); #ifndef __MQTT_NO_DISCOVERCONFIG__ @@ -239,6 +226,10 @@ void app::loop(void) { if(!mPayload[iv->id].complete) { mRxFailed++; + iv->setQueuedCmdFinished(); // command failed + if(mConfig.serialDebug) { + DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); + } if(mConfig.serialDebug) { DPRINT(DBG_INFO, F("Inverter #") + String(iv->id) + " "); DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")"); @@ -249,12 +240,12 @@ void app::loop(void) { yield(); if(mConfig.serialDebug) - DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status()) ); - DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX)); - if(iv->devControlRequest && iv->powerLimit[0] > 0){ // prevent to "switch off" + DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status())); + DPRINTLN(DBG_INFO, F("Requesting Inverter SN ") + String(iv->serial.u64, HEX)); + if(iv->devControlRequest && (iv->powerLimit[0] > 0) && (NoPowerLimit != iv->powerLimit[1])) { // prevent to "switch off" if(mConfig.serialDebug) DPRINTLN(DBG_INFO, F("Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0])); - mSys->Radio.sendControlPacket(iv->radioId.u64,iv->devControlCmd ,iv->powerLimit); + mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd ,iv->powerLimit); iv->enqueCommand(SystemConfigPara); } else { mSys->Radio.sendTimePacket(iv->radioId.u64,iv->getQueuedCmd(), mPayload[iv->id].ts,iv->alarmMesIndex); @@ -346,8 +337,11 @@ void app::processPayload(bool retransmit) { else { mPayload[iv->id].complete = true; iv->ts = mPayload[iv->id].ts; - uint8_t payload[128] = {0}; + uint8_t payload[128]; uint8_t offs = 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]); @@ -366,11 +360,34 @@ void app::processPayload(bool retransmit) { yield(); } iv->doCalculations(); // cmd value decides which parser is used to decode payload + + iv->setQueuedCmdFinished(); + + // MQTT send out + if(mMqttActive) { + char topic[30], val[10]; + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) + { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL != iv) + { + if (iv->isAvailable(mTimestamp)) + { + for (uint8_t i = 0; i < iv->listLen; i++) + { + snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, fields[iv->assign[i].fieldId]); + snprintf(val, 10, "%.3f", iv->getValue(i)); + mMqtt.sendMsg(topic, val); + yield(); + } + } + } + } + } #ifdef __MQTT_AFTER_RX__ doMQTT = true; #endif - iv->setQueuedCmdFinished(); } } yield(); @@ -483,9 +500,10 @@ String app::getStatistics(void) { Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { iv = mSys->getInverterByPos(i); + content += F("Inverter #") + String(i) + F(": "); if(NULL != iv) { bool avail = true; - content += F("Inverter '") + String(iv->name) + F(" (FW-Version: ") + String(iv->fwVersion) +F(")") + F("' is "); + content += String(iv->name) + F(" (v") + String(iv->fwVersion) +F(")") + F(" is "); if(!iv->isAvailable(mTimestamp)) { content += F("not "); avail = false; @@ -500,9 +518,8 @@ String app::getStatistics(void) { content += F("-> last successful transmission: ") + getDateTimeStr(iv->getLastTs()) + "\n"; } } - else { - content += F("Inverter ") + String(i) + F(" not (correctly) configured\n"); - } + else + content += F("n/a\n"); } if(!mSys->Radio.isChipConnected()) @@ -523,119 +540,6 @@ String app::getStatistics(void) { } - -//----------------------------------------------------------------------------- -String app::getLiveData(void) -{ - String modHtml; - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) - { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL != iv) - { -#ifdef LIVEDATA_VISUALIZED - uint8_t modNum, pos; - switch (iv->type) - { - default: - case INV_TYPE_1CH: - modNum = 1; - break; - case INV_TYPE_2CH: - modNum = 2; - break; - case INV_TYPE_4CH: - modNum = 4; - break; - } - - modHtml += F("
" - "
") + - String(iv->name) + F(" Limit ") + String(iv->actPowerLimit); - if (true) - { // live Power Limit from inverter is always in % - modHtml += F(" %"); - } - else - { - modHtml += F(" W"); - } - uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID}; - - for (uint8_t fld = 0; fld < 12; fld++) - { - pos = (iv->getPosByChFld(CH0, list[fld])); - if (0xff != pos) - { - modHtml += F("
"); - modHtml += F("") + String(iv->getValue(pos)); - modHtml += F("") + String(iv->getUnit(pos)) + F(""); - modHtml += F("") + String(iv->getFieldName(pos)) + F(""); - modHtml += F("
"); - } - } - modHtml += "
"; - - for (uint8_t ch = 1; ch <= modNum; ch++) - { - modHtml += F("
"); - if (iv->chName[ch - 1][0] == 0) - modHtml += F("CHANNEL ") + String(ch); - else - modHtml += String(iv->chName[ch - 1]); - modHtml += F(""); - for (uint8_t j = 0; j < 6; j++) - { - switch (j) - { - default: - pos = (iv->getPosByChFld(ch, FLD_UDC)); - break; - case 1: - pos = (iv->getPosByChFld(ch, FLD_IDC)); - break; - case 2: - pos = (iv->getPosByChFld(ch, FLD_PDC)); - break; - case 3: - pos = (iv->getPosByChFld(ch, FLD_YD)); - break; - case 4: - pos = (iv->getPosByChFld(ch, FLD_YT)); - break; - case 5: - pos = (iv->getPosByChFld(ch, FLD_IRR)); - break; - } - if (0xff != pos) - { - modHtml += F("") + String(iv->getValue(pos)); - modHtml += F("") + String(iv->getUnit(pos)) + F(""); - modHtml += F("") + String(iv->getFieldName(pos)) + F(""); - } - } - modHtml += "
"; - yield(); - } - modHtml += F("
Last received data requested at: ") + getDateTimeStr(iv->ts) + F("
"); - modHtml += F("
"); -#else - // dump all data to web frontend - modHtml = F("
");
-            char topic[30], val[10];
-            for (uint8_t i = 0; i < iv->listLen; i++)
-            {
-                snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
-                snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
-                modHtml += String(topic) + ": " + String(val) + "\n";
-            }
-            modHtml += F("
"); -#endif - } - } - return modHtml; -} - //----------------------------------------------------------------------------- String app::getJson(void) { DPRINTLN(DBG_VERBOSE, F("app::showJson")); @@ -852,11 +756,16 @@ void app::loadEEpconfig(void) { // it is "doppelt-gemoppelt" because the inverter shall remember the setting if the dtu makes a power cycle / reboot if (iv->powerLimit[0] != 0xffff) { iv->devControlCmd = ActivePowerContr; // set active power limit - if (iv->powerLimit[1] & 0x0001){ - DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in %"); - } else { - DPRINTLN(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX) + ", Power Limit: " + String(iv->powerLimit[0]) + " in Watt"); + DPRINT(DBG_INFO, F("add inverter: ") + String(name) + ", SN: " + String(invSerial, HEX)); + if(iv->powerLimit[1] != NoPowerLimit) { + DBGPRINT(F(", Power Limit: ") + String(iv->powerLimit[0])); + if ((iv->powerLimit[1] & 0x0001) == 0x0001) + DBGPRINTLN(F(" in %")); + else + DBGPRINTLN(F(" in Watt")); } + else + DBGPRINTLN(F(" ")); } for(uint8_t j = 0; j < 4; j++) { mEep->read(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH); @@ -879,17 +788,15 @@ void app::saveValues(void) { mEep->write(ADDR_CFG, (uint8_t*)&mConfig, CFG_LEN); Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mSys->getInverterByPos(i); - if(NULL != iv) { - mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64); - mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]); - mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]); - mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH); - // max channel power / name - for(uint8_t j = 0; j < 4; j++) { - mEep->write(ADDR_INV_CH_PWR + (i * 2 * 4) + (j*2), iv->chMaxPwr[j]); - mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH); - } + iv = mSys->getInverterByPos(i, false); + mEep->write(ADDR_INV_ADDR + (i * 8), iv->serial.u64); + mEep->write(ADDR_INV_PWR_LIM + i * 2, iv->powerLimit[0]); + mEep->write(ADDR_INV_PWR_LIM_CON + i * 2, iv->powerLimit[1]); + mEep->write(ADDR_INV_NAME + (i * MAX_NAME_LENGTH), iv->name, MAX_NAME_LENGTH); + // max channel power / name + for(uint8_t j = 0; j < 4; j++) { + mEep->write(ADDR_INV_CH_PWR + (i * 2 * 4) + (j*2), iv->chMaxPwr[j]); + mEep->write(ADDR_INV_CH_NAME + (i * 4 * MAX_NAME_LENGTH) + j * MAX_NAME_LENGTH, iv->chName[j], MAX_NAME_LENGTH); } } @@ -946,4 +853,4 @@ void app::resetPayload(Inverter<>* iv) mPayload[iv->id].complete = false; mPayload[iv->id].requested = true; mPayload[iv->id].ts = mTimestamp; -} \ No newline at end of file +} diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index 0e09701a..0ccefe7f 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -71,7 +71,6 @@ class app { void saveValues(void); void resetPayload(Inverter<>* iv); String getStatistics(void); - String getLiveData(void); String getJson(void); bool getWifiApActive(void); @@ -80,9 +79,10 @@ class app { } uint64_t Serial2u64(const char *val) { - char tmp[3] = {0}; + char tmp[3]; uint64_t ret = 0ULL; uint64_t u64; + memset(tmp, 0, 3); for(uint8_t i = 0; i < 6; i++) { tmp[0] = val[i*2]; tmp[1] = val[i*2 + 1]; @@ -95,7 +95,7 @@ class app { } String getDateTimeStr(time_t t) { - char str[20] = {0}; + char str[20]; if(0 == t) sprintf(str, "n/a"); else @@ -113,9 +113,11 @@ class app { void eraseSettings(bool all = false) { //DPRINTLN(DBG_VERBOSE, F("main.h:eraseSettings")); - uint8_t buf[64] = {0}; + uint8_t buf[64]; uint16_t addr = (all) ? ADDR_START : ADDR_START_SETTINGS; uint16_t end; + + memset(buf, 0xff, 64); do { end = addr + 64; if(end > (ADDR_SETTINGS_CRC + 2)) diff --git a/tools/esp8266/defines.h b/tools/esp8266/defines.h index 31b65b77..33732ff5 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 15 +#define VERSION_PATCH 16 //------------------------------------- @@ -58,6 +58,7 @@ typedef enum { } DevControlCmdType; typedef enum { // ToDo: to be verified by field tests + NoPowerLimit = 0xffff, // ahoy internal value, no hoymiles value! AbsolutNonPersistent = 0UL, // 0x0000 RelativNonPersistent = 1UL, // 0x0001 AbsolutPersistent = 256UL, // 0x0100 @@ -104,6 +105,7 @@ typedef enum { // ToDo: to be verified by field tests #define MQTT_PORT_LEN 2 // uint16_t #define MQTT_DISCOVERY_PREFIX "homeassistant" #define MQTT_MAX_PACKET_SIZE 384 +#define MQTT_RECONNECT_DELAY 5000 #define SER_ENABLE_LEN 1 // uint8_t #define SER_DEBUG_LEN 1 // uint8_t diff --git a/tools/esp8266/hmDefines.h b/tools/esp8266/hmDefines.h index 36139bb2..6330db98 100644 --- a/tools/esp8266/hmDefines.h +++ b/tools/esp8266/hmDefines.h @@ -23,9 +23,13 @@ const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%","VAr", // field types enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, - FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR,FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT}; + FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PCT, FLD_EFF, + FLD_IRR, FLD_PRA,FLD_ALARM_MES_ID,FLD_FW_VERSION,FLD_FW_BUILD_YEAR, + FLD_FW_BUILD_MONTH_DAY,FLD_HW_ID,FLD_ACT_PWR_LIMIT,FLD_LAST_ALARM_CODE}; + const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", - "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr","ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit"}; + "U_AC", "I_AC", "P_AC", "Freq", "Temp", "Pct", "Efficiency", "Irradiation","P_ACr", + "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","HWPartId","PowerLimit","LastAlarmCode"}; // mqtt discovery device classes enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; @@ -97,6 +101,10 @@ const byteAssign_t SystemConfigParaAssignment[] = { }; #define HMSYSTEM_LIST_LEN (sizeof(SystemConfigParaAssignment) / sizeof(byteAssign_t)) +const byteAssign_t AlarmDataAssignment[] = { + { FLD_LAST_ALARM_CODE, UNIT_NONE, CH0, 0, 2, 1 } +}; +#define HMALARMDATA_LIST_LEN (sizeof(AlarmDataAssignment) / sizeof(byteAssign_t)) diff --git a/tools/esp8266/hmInverter.h b/tools/esp8266/hmInverter.h index 774fd809..fd2eafee 100644 --- a/tools/esp8266/hmInverter.h +++ b/tools/esp8266/hmInverter.h @@ -115,17 +115,20 @@ class Inverter { RECORDTYPE *record; // pointer for values uint16_t chMaxPwr[4]; // maximum power of the modules (Wp) char chName[4][MAX_NAME_LENGTH]; // human readable name for channel + String lastAlarmMsg; bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null) Inverter() { ts = 0; powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited - powerLimit[1] = 0x0000; // + powerLimit[1] = NoPowerLimit; // actPowerLimit = 0xffff; // init feedback from inverter to -1 devControlRequest = false; - devControlCmd = 0xff; + devControlCmd = InitDataState; initialized = false; fwVersion = 0; + lastAlarmMsg = "nothing"; + alarmMesIndex = 0; } ~Inverter() { @@ -141,7 +144,8 @@ class Inverter { void setQueuedCmdFinished(){ if (!_commandQueue.empty()){ - _commandQueue.pop(); // Will destroy CommandAbstract Class Object (?) + // Will destroy CommandAbstract Class Object (?) + _commandQueue.pop(); } } @@ -150,7 +154,14 @@ class Inverter { if (_commandQueue.empty()){ // Fill with default commands enqueCommand(RealTimeRunData_Debug); - //enqueCommand(SystemConfigPara); + if (fwVersion == 0) + { // info needed maybe after "one night" (=> DC>0 to DC=0 and to DC>0) or reboot + enqueCommand(InverterDevInform_All); + } + if (actPowerLimit == 0xffff) + { // info needed maybe after "one nigth" (=> DC>0 to DC=0 and to DC>0) or reboot + enqueCommand(SystemConfigPara); + } } return _commandQueue.front().get()->getCmd(); } @@ -164,8 +175,6 @@ class Inverter { memset(name, 0, MAX_NAME_LENGTH); memset(chName, 0, MAX_NAME_LENGTH * 4); memset(record, 0, sizeof(RECORDTYPE) * listLen); - enqueCommand(InverterDevInform_All); - enqueCommand(SystemConfigPara); initialized = true; } @@ -206,12 +215,25 @@ class Inverter { val <<= 8; val |= buf[ptr]; } while(++ptr != end); - record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); + if ((RECORDTYPE)(div) > 1){ + record[pos] = (RECORDTYPE)(val) / (RECORDTYPE)(div); + } + else { + record[pos] = (RECORDTYPE)(val); + } + } if (cmd == RealTimeRunData_Debug) { // get last alarm message index and save it in the inverter object if (getPosByChFld(0, FLD_ALARM_MES_ID) == pos){ - alarmMesIndex = record[pos]; + if (alarmMesIndex < record[pos]){ + alarmMesIndex = record[pos]; + //enqueCommand(AlarmUpdate); // What is the function of AlarmUpdate? + enqueCommand(AlarmData); + } + else { + alarmMesIndex = record[pos]; // no change + } } } if (cmd == InverterDevInform_All) { @@ -228,6 +250,11 @@ class Inverter { DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit)); } } + if (cmd == AlarmData){ + if (getPosByChFld(0, FLD_LAST_ALARM_CODE) == pos){ + lastAlarmMsg = getAlarmStr(record[pos]); + } + } } RECORDTYPE getValue(uint8_t pos) { @@ -263,52 +290,273 @@ class Inverter { return false; } - uint32_t getLastTs(void) { + uint32_t getLastTs(void) + { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getLastTs")); return ts; } - void getAssignment() { + void getAssignment() + { DPRINTLN(DBG_DEBUG, F("hmInverter.h:getAssignment")); - uint8_t cmd = getQueuedCmd(); - switch (cmd) + // Default assignment; + if (INV_TYPE_1CH == type) { - case RealTimeRunData_Debug: - if (INV_TYPE_1CH == type) - { - listLen = (uint8_t)(HM1CH_LIST_LEN); - assign = (byteAssign_t *)hm1chAssignment; - channels = 1; - } - else if (INV_TYPE_2CH == type) - { - listLen = (uint8_t)(HM2CH_LIST_LEN); - assign = (byteAssign_t *)hm2chAssignment; - channels = 2; - } - else if (INV_TYPE_4CH == type) - { - listLen = (uint8_t)(HM4CH_LIST_LEN); - assign = (byteAssign_t *)hm4chAssignment; - channels = 4; - } - else - { - listLen = 0; - channels = 0; - assign = NULL; - } + listLen = (uint8_t)(HM1CH_LIST_LEN); + assign = (byteAssign_t *)hm1chAssignment; + channels = 1; + } + else if (INV_TYPE_2CH == type) + { + listLen = (uint8_t)(HM2CH_LIST_LEN); + assign = (byteAssign_t *)hm2chAssignment; + channels = 2; + } + else if (INV_TYPE_4CH == type) + { + listLen = (uint8_t)(HM4CH_LIST_LEN); + assign = (byteAssign_t *)hm4chAssignment; + channels = 4; + } + else + { + listLen = 0; + channels = 0; + assign = NULL; + } + + switch (getQueuedCmd()) { + case RealTimeRunData_Debug: + // Do nothing will use default + break; + case InverterDevInform_All: + listLen = (uint8_t)(HMINFO_LIST_LEN); + assign = (byteAssign_t *)InfoAssignment; + break; + case SystemConfigPara: + listLen = (uint8_t)(HMSYSTEM_LIST_LEN); + assign = (byteAssign_t *)SystemConfigParaAssignment; + break; + case AlarmData: + listLen = (uint8_t)(HMALARMDATA_LIST_LEN); + assign = (byteAssign_t *)AlarmDataAssignment; + break; + default: + DPRINTLN(DBG_INFO, "Parser not implemented"); + break; + } + } + String getAlarmStr(u_int16_t alarmCode) + { + switch (alarmCode) + { + case 1: + return String(F("Inverter start")); + break; + case 2: + return String(F("DTU command failed")); + break; + case 121: + return String(F("Over temperature protection")); + break; + case 125: + return String(F("Grid configuration parameter error")); + break; + case 126: + return String(F("Software error code 126")); + break; + case 127: + return String(F("Firmware error")); + break; + case 128: + return String(F("Software error code 128")); + break; + case 129: + return String(F("Software error code 129")); + break; + case 130: + return String(F("Offline")); + break; + case 141: + return String(F("Grid overvoltage")); + break; + case 142: + return String(F("Average grid overvoltage")); + break; + case 143: + return String(F("Grid undervoltage")); + break; + case 144: + return String(F("Grid overfrequency")); + break; + case 145: + return String(F("Grid underfrequency")); + break; + case 146: + return String(F("Rapid grid frequency change")); + break; + case 147: + return String(F("Power grid outage")); + break; + case 148: + return String(F("Grid disconnection")); + break; + case 149: + return String(F("Island detected")); break; - case InverterDevInform_All: - listLen = (uint8_t)(HMINFO_LIST_LEN); - assign = (byteAssign_t *)InfoAssignment; + case 205: + return String(F("Input port 1 & 2 overvoltage")); break; - case SystemConfigPara: - listLen = (uint8_t)(HMSYSTEM_LIST_LEN); - assign = (byteAssign_t *)SystemConfigParaAssignment; + case 206: + return String(F("Input port 3 & 4 overvoltage")); + break; + case 207: + return String(F("Input port 1 & 2 undervoltage")); + break; + case 208: + return String(F("Input port 3 & 4 undervoltage")); + break; + case 209: + return String(F("Port 1 no input")); + break; + case 210: + return String(F("Port 2 no input")); + break; + case 211: + return String(F("Port 3 no input")); + break; + case 212: + return String(F("Port 4 no input")); + break; + case 213: + return String(F("PV-1 & PV-2 abnormal wiring")); + break; + case 214: + return String(F("PV-3 & PV-4 abnormal wiring")); + break; + case 215: + return String(F("PV-1 Input overvoltage")); + break; + case 216: + return String(F("PV-1 Input undervoltage")); + break; + case 217: + return String(F("PV-2 Input overvoltage")); + break; + case 218: + return String(F("PV-2 Input undervoltage")); + break; + case 219: + return String(F("PV-3 Input overvoltage")); + break; + case 220: + return String(F("PV-3 Input undervoltage")); + break; + case 221: + return String(F("PV-4 Input overvoltage")); + break; + case 222: + return String(F("PV-4 Input undervoltage")); + break; + case 301: + return String(F("Hardware error code 301")); + break; + case 302: + return String(F("Hardware error code 302")); + break; + case 303: + return String(F("Hardware error code 303")); + break; + case 304: + return String(F("Hardware error code 304")); + break; + case 305: + return String(F("Hardware error code 305")); + break; + case 306: + return String(F("Hardware error code 306")); + break; + case 307: + return String(F("Hardware error code 307")); + break; + case 308: + return String(F("Hardware error code 308")); + break; + case 309: + return String(F("Hardware error code 309")); + break; + case 310: + return String(F("Hardware error code 310")); + break; + case 311: + return String(F("Hardware error code 311")); + break; + case 312: + return String(F("Hardware error code 312")); + break; + case 313: + return String(F("Hardware error code 313")); + break; + case 314: + return String(F("Hardware error code 314")); + break; + case 5041: + return String(F("Error code-04 Port 1")); + break; + case 5042: + return String(F("Error code-04 Port 2")); + break; + case 5043: + return String(F("Error code-04 Port 3")); + break; + case 5044: + return String(F("Error code-04 Port 4")); + break; + case 5051: + return String(F("PV Input 1 Overvoltage/Undervoltage")); + break; + case 5052: + return String(F("PV Input 2 Overvoltage/Undervoltage")); + break; + case 5053: + return String(F("PV Input 3 Overvoltage/Undervoltage")); + break; + case 5054: + return String(F("PV Input 4 Overvoltage/Undervoltage")); + break; + case 5060: + return String(F("Abnormal bias")); + break; + case 5070: + return String(F("Over temperature protection")); + break; + case 5080: + return String(F("Grid Overvoltage/Undervoltage")); + break; + case 5090: + return String(F("Grid Overfrequency/Underfrequency")); + break; + case 5100: + return String(F("Island detected")); + break; + case 5120: + return String(F("EEPROM reading and writing error")); + break; + case 5150: + return String(F("10 min value grid overvoltage")); + break; + case 5200: + return String(F("Firmware error")); + break; + case 8310: + return String(F("Shut down")); + break; + case 9000: + return String(F("Microinverter is suspected of being stolen")); break; default: - DPRINTLN(DBG_INFO, "Parser not implemented"); + return String(F("Unknown")); + break; } } diff --git a/tools/esp8266/hmRadio.h b/tools/esp8266/hmRadio.h index be2a1509..c7fcf0e0 100644 --- a/tools/esp8266/hmRadio.h +++ b/tools/esp8266/hmRadio.h @@ -191,10 +191,9 @@ class HmRadio { mTxBuf[10] = cmd; // cid mTxBuf[11] = 0x00; CP_U32_LittleEndian(&mTxBuf[12], ts); - if (cmd == RealTimeRunData_Debug || cmd == AlarmData || cmd == AlarmUpdate ){ + if (cmd == RealTimeRunData_Debug || cmd == AlarmData ){ mTxBuf[18] = (alarmMesId >> 8) & 0xff; mTxBuf[19] = (alarmMesId ) & 0xff; - //mTxBuf[19] = 0x05; // ToDo: Shall be the last received Alarm Index Number } else { mTxBuf[18] = 0x00; mTxBuf[19] = 0x00; diff --git a/tools/esp8266/html/h/setup_html.h b/tools/esp8266/html/h/setup_html.h index 5ec006e5..6309ef25 100644 --- a/tools/esp8266/html/h/setup_html.h +++ b/tools/esp8266/html/h/setup_html.h @@ -1,4 +1,4 @@ #ifndef __SETUP_HTML_H__ #define __SETUP_HTML_H__ -const char setup_html[] PROGMEM = "Setup - {DEVICE}

Setup

ERASE SETTINGS (not WiFi)
Device Host Name
WiFi

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

Inverter{INVERTERS}

General

NTP Server
MQTT
System Config

Pinout (Wemos)

{PINOUT}

Radio (NRF24L01+)

Serial Console



"; +const char setup_html[] PROGMEM = "Setup - {DEVICE}

Setup

ERASE SETTINGS (not WiFi)
Device Host Name
WiFi

Enter the credentials to your prefered WiFi station. After rebooting the device tries to connect with this information.

Inverter{INVERTERS}

General

NTP Server
MQTT
System Config

Pinout (Wemos)

{PINOUT}

Radio (NRF24L01+)

Serial Console



"; #endif /*__SETUP_HTML_H__*/ diff --git a/tools/esp8266/html/setup.html b/tools/esp8266/html/setup.html index 98a30680..d3f2ee39 100644 --- a/tools/esp8266/html/setup.html +++ b/tools/esp8266/html/setup.html @@ -128,7 +128,7 @@

Serial Console


- +
diff --git a/tools/esp8266/mqtt.h b/tools/esp8266/mqtt.h index 74193afa..28ca2c1c 100644 --- a/tools/esp8266/mqtt.h +++ b/tools/esp8266/mqtt.h @@ -85,7 +85,8 @@ class mqtt { #endif boolean resub = false; - if(!mClient->connected()) { + if(!mClient->connected() && (millis() - lastReconnect) > MQTT_RECONNECT_DELAY ) { + lastReconnect = millis(); if(strlen(mDevName) > 0) { // der Server und der Port müssen neu gesetzt werden, // da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat. @@ -95,14 +96,14 @@ class mqtt { resub = mClient->connect(mDevName, mCfg->user, mCfg->pwd); else resub = mClient->connect(mDevName); - } - // ein Subscribe ist nur nach einem connect notwendig - if(resub) { - char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte - // ToDo: "/devcontrol/#" is hardcoded - snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic); - DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic)); - mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#" + // ein Subscribe ist nur nach einem connect notwendig + if(resub) { + char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte + // ToDo: "/devcontrol/#" is hardcoded + snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg->topic); + DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic)); + mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#" + } } } } @@ -113,6 +114,7 @@ class mqtt { bool mAddressSet; mqttConfig_t *mCfg; char mDevName[DEVNAME_LEN]; + unsigned long lastReconnect = 0; }; #endif /*__MQTT_H_*/ diff --git a/tools/esp8266/scripts/getVersion.py b/tools/esp8266/scripts/getVersion.py index 9907eb55..6ebe71b0 100644 --- a/tools/esp8266/scripts/getVersion.py +++ b/tools/esp8266/scripts/getVersion.py @@ -24,6 +24,12 @@ def readVersion(path, infile): src = path + ".pio/build/esp8266-release/firmware.bin" dst = path + ".pio/build/out/" + versionout os.rename(src, dst) + + versionout = version[:-1] + "_esp32_" + sha + ".bin" + src = path + ".pio/build/esp32-wroom32-release/firmware.bin" + dst = path + ".pio/build/out/" + versionout + os.rename(src, dst) + print("::set-output name=name::" + versionnumber[:-1] ) diff --git a/tools/esp8266/web.cpp b/tools/esp8266/web.cpp index 9e4e9802..0d933142 100644 --- a/tools/esp8266/web.cpp +++ b/tools/esp8266/web.cpp @@ -16,6 +16,23 @@ #include "html/h/setup_html.h" #include "html/h/visualization_html.h" + +const uint16_t pwrLimitOptionValues[] { + NoPowerLimit, + AbsolutNonPersistent, + AbsolutPersistent, + RelativNonPersistent, + RelativPersistent +}; + +const char* const pwrLimitOptions[] { + "no power limit", + "absolute in Watt non persistent", + "absolute in Watt persistent", + "relativ in percent non persistent", + "relativ in percent persistent" +}; + //----------------------------------------------------------------------------- web::web(app *main, sysConfig_t *sysCfg, config_t *config, char version[]) { mMain = main; @@ -185,7 +202,7 @@ void web::showSetup(void) { inv += F("serial.u64, HEX); - inv += F("\"/ maxlength=\"12\" onkeyup=\"checkSerial()\">"); + inv += F("\"/ maxlength=\"12\">"); inv += F(""); inv += F(""; inv += F(""); - inv += F(""); - if(iv->powerLimit[1] == RelativNonPersistent) - inv += F("PowerLimitControl\">"); - if(iv->powerLimit[1] == AbsolutPersistent) - inv += F("PowerLimitControl\">"); - if(iv->powerLimit[1] == RelativPersistent) - inv += F("PowerLimitControl\">"); - } else - inv += F("PowerLimitControl\">"); - // UGLY! But I do not know it a better way --// + inv += F(""); inv += F("
"); @@ -320,13 +332,14 @@ void web::showSave(void) { iv->powerLimit[1] = actPwrLimitControl; iv->devControlCmd = ActivePowerContr; iv->devControlRequest = true; - if (iv->powerLimit[1] & 0x0001) + if ((iv->powerLimit[1] & 0x0001) == 0x0001) DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%") ); - else + else { DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W") ); DPRINTLN(DBG_INFO, F("Power Limit Control Setting ") + String(iv->powerLimit[1])); + } } - if (actPwrLimit == 0xffff){ // set to 100% + if (actPwrLimit == 0xffff) { // set to 100% iv->powerLimit[0] = 100; iv->powerLimit[1] = RelativPersistent; iv->devControlCmd = ActivePowerContr; @@ -421,7 +434,84 @@ void web::showVisualization(void) { //----------------------------------------------------------------------------- void web::showLiveData(void) { DPRINTLN(DBG_VERBOSE, F("web::showLiveData")); - mWeb->send(200, F("text/html"), mMain->getLiveData()); + + String modHtml; + for (uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) { + Inverter<> *iv = mMain->mSys->getInverterByPos(id); + if (NULL != iv) { +#ifdef LIVEDATA_VISUALIZED + uint8_t modNum, pos; + switch (iv->type) { + default: + case INV_TYPE_1CH: modNum = 1; break; + case INV_TYPE_2CH: modNum = 2; break; + case INV_TYPE_4CH: modNum = 4; break; + } + + modHtml += F("
" + "
") + + String(iv->name) + F(" Limit ") + + String(iv->actPowerLimit) + F("%"); + if(NoPowerLimit == iv->powerLimit[1]) + modHtml += F(" (not controlled)"); + modHtml += F(" | last Alarm: ") + iv->lastAlarmMsg + F(""); + + uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PCT, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_PRA, FLD_ALARM_MES_ID}; + + for (uint8_t fld = 0; fld < 11; fld++) { + pos = (iv->getPosByChFld(CH0, list[fld])); + if (0xff != pos) { + modHtml += F("
"); + modHtml += F("") + String(iv->getValue(pos)); + modHtml += F("") + String(iv->getUnit(pos)) + F(""); + modHtml += F("") + String(iv->getFieldName(pos)) + F(""); + modHtml += F("
"); + } + } + modHtml += "
"; + + for (uint8_t ch = 1; ch <= modNum; ch++) { + modHtml += F("
"); + if (iv->chName[ch - 1][0] == 0) + modHtml += F("CHANNEL ") + String(ch); + else + modHtml += String(iv->chName[ch - 1]); + modHtml += F(""); + for (uint8_t j = 0; j < 6; j++) { + switch (j) { + default: pos = (iv->getPosByChFld(ch, FLD_UDC)); break; + case 1: pos = (iv->getPosByChFld(ch, FLD_IDC)); break; + case 2: pos = (iv->getPosByChFld(ch, FLD_PDC)); break; + case 3: pos = (iv->getPosByChFld(ch, FLD_YD)); break; + case 4: pos = (iv->getPosByChFld(ch, FLD_YT)); break; + case 5: pos = (iv->getPosByChFld(ch, FLD_IRR)); break; + } + if (0xff != pos) { + modHtml += F("") + String(iv->getValue(pos)); + modHtml += F("") + String(iv->getUnit(pos)) + F(""); + modHtml += F("") + String(iv->getFieldName(pos)) + F(""); + } + } + modHtml += "
"; + yield(); + } + modHtml += F("
Last received data requested at: ") + mMain->getDateTimeStr(iv->ts) + F("
"); + modHtml += F("
"); +#else + // dump all data to web frontend + modHtml = F("
");
+            char topic[30], val[10];
+            for (uint8_t i = 0; i < iv->listLen; i++) {
+                snprintf(topic, 30, "%s/ch%d/%s", iv->name, iv->assign[i].ch, iv->getFieldName(i));
+                snprintf(val, 10, "%.3f %s", iv->getValue(i), iv->getUnit(i));
+                modHtml += String(topic) + ": " + String(val) + "\n";
+            }
+            modHtml += F("
"); +#endif + } + } + + mWeb->send(200, F("text/html"), modHtml); } @@ -450,10 +540,11 @@ void web::showWebApi(void) if (response["tx_request"] == (uint8_t)TX_REQ_INFO) { // if the AlarmData is requested set the Alarm Index to the requested one - if (cmd == AlarmData){ - iv->alarmMesIndex = response["payload"]; + if (cmd == AlarmData || cmd == AlarmUpdate){ + // set the AlarmMesIndex for the request from user input + iv->alarmMesIndex = response["payload"]; } - DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String(response["payload"])); + DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"])); // process payload from web request corresponding to the cmd iv->enqueCommand(cmd); } @@ -488,6 +579,14 @@ void web::showWebApi(void) iv->devControlRequest = true; // queue it in the request loop } } + if (response["cmd"] == (uint8_t)TurnOff){ + iv->devControlCmd = TurnOff; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)TurnOn){ + iv->devControlCmd = TurnOn; + iv->devControlRequest = true; // queue it in the request loop + } } } mWeb->send(200, "text/json", "{success:true}");