diff --git a/.github/workflows/dev_build.yml b/.github/workflows/dev_build.yml index 43c82e6..db28cbf 100644 --- a/.github/workflows/dev_build.yml +++ b/.github/workflows/dev_build.yml @@ -9,24 +9,29 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: | ~/.cache/pip ~/.platformio/.cache key: ${{ runner.os }}-pio - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' - name: Install PlatformIO Core run: pip install --upgrade platformio + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + - name: write temp file with build number and set to env variable run: | touch ${{ github.workspace }}/include/buildnumber.txt printf -v BUILDNUMBER "%04d" ${{github.run_number}} - echo $BUILDNUMBER >> ${{ github.workspace }}/include/buildnumber.txt + echo "${BUILDNUMBER}_${{ steps.extract_branch.outputs.branch }}" >> ${{ github.workspace }}/include/buildnumber.txt echo "got current buildnumber and saved: " cat ${{ github.workspace }}/include/buildnumber.txt echo "CURRENT_BUILDNUMBER=$BUILDNUMBER" >> $GITHUB_ENV @@ -42,7 +47,7 @@ jobs: mv .pio/build/esp12e/firmware.bin .pio/build/esp12e/dtuGateway_snapshot_$VERSIONNUMBER.bin - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: .pio/build/esp12e/dtuGateway_snapshot_${{ env.CURRENT_VERSIONNUMBER }}.bin diff --git a/.github/workflows/feature_build.yml b/.github/workflows/feature_build.yml new file mode 100644 index 0000000..47bbb14 --- /dev/null +++ b/.github/workflows/feature_build.yml @@ -0,0 +1,54 @@ +name: PlatformIO CI feature branch build + +on: + push: + branches: + - '*' + - '!develop' + - '!main' +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + with: + path: | + ~/.cache/pip + ~/.platformio/.cache + key: ${{ runner.os }}-pio + - uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install PlatformIO Core + run: pip install --upgrade platformio + + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + + - name: write temp file with build number and set to env variable + run: | + touch ${{ github.workspace }}/include/buildnumber.txt + printf -v BUILDNUMBER "%04d" ${{github.run_number}} + echo "${BUILDNUMBER}_${{ steps.extract_branch.outputs.branch }}" >> ${{ github.workspace }}/include/buildnumber.txt + echo "got current buildnumber and saved: " + cat ${{ github.workspace }}/include/buildnumber.txt + echo "CURRENT_BUILDNUMBER=$BUILDNUMBER" >> $GITHUB_ENV + + - name: Build PlatformIO Project + run: pio run + + - name: got current version and changing firmware name after building + run: | + VERSIONNUMBER=$(python ${{ github.workspace }}/version_inc.py getversion 2>&1) + echo "got versionnumber: " $VERSIONNUMBER + echo "CURRENT_VERSIONNUMBER=$VERSIONNUMBER" >> $GITHUB_ENV + mv .pio/build/esp12e/firmware.bin .pio/build/esp12e/dtuGateway_snapshot_$VERSIONNUMBER.bin + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + path: .pio/build/esp12e/dtuGateway_snapshot_${{ env.CURRENT_VERSIONNUMBER }}.bin diff --git a/.github/workflows/main_build.yml b/.github/workflows/main_build.yml index c12812e..542243f 100644 --- a/.github/workflows/main_build.yml +++ b/.github/workflows/main_build.yml @@ -9,14 +9,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: | ~/.cache/pip ~/.platformio/.cache key: ${{ runner.os }}-pio - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.9' - name: Install PlatformIO Core @@ -42,7 +42,7 @@ jobs: mv .pio/build/esp12e/firmware.bin .pio/build/esp12e/dtuGateway_release_$VERSIONNUMBER.bin - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: path: .pio/build/esp12e/dtuGateway_release_${{ env.CURRENT_VERSIONNUMBER }}.bin diff --git a/include/Config.h b/include/Config.h index 6aa0614..43e7dc5 100644 --- a/include/Config.h +++ b/include/Config.h @@ -9,13 +9,20 @@ struct UserConfig { - char dtuSsid[32]; - char dtuPassword[32]; - char wifiSsid[32]; - char wifiPassword[32]; + char dtuSsid[64]; + char dtuPassword[64]; + char wifiSsid[64]; + char wifiPassword[64]; char dtuHostIp[16]; char openhabHostIp[16]; char openItemPrefix[32]; + boolean openhabActive; + char mqttBrokerIp[16]; + int mqttBrokerPort; + char mqttBrokerUser[64]; + char mqttBrokerPassword[64]; + char mqttBrokerMainTopic[32]; + boolean mqttActive; int dtuCloudPauseTime; boolean dtuCloudPauseActive; int dtuUpdateTime; diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..4a699f5 --- /dev/null +++ b/include/display.h @@ -0,0 +1,46 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include + +// OLED display + +#define BRIGHTNESS_MIN 50 +#define BRIGHTNESS_MAX 250 + +struct DisplayData { + int16_t totalPower=0; // indicate current power (W) + float totalYieldDay=0.0f; // indicate day yield (Wh) + float totalYieldTotal=0.0f; // indicate total yield (kWh) + const char *formattedTime=nullptr; + const char *version=nullptr; + uint8_t powerLimit=0; + uint8_t rssiGW=0; + uint8_t rssiDTU=0; +}; + +class Display { + public: + Display(); + void setup(); + void renderScreen(String time, String version); + private: + void drawScreen(); + void drawHeader(); + void drawFooter(); + + void drawMainDTUOnline(bool pause=false); + void drawMainDTUOffline(); + + void screenSaver(); + void checkChangedValues(); + // private member variables + DisplayData lastDisplayData; + uint8_t brightness=BRIGHTNESS_MIN; + u8g2_uint_t offset_x = 0; // shifting for anti burn in effect + u8g2_uint_t offset_y = 0; // shifting for anti burn in effect + bool valueChanged = false; + uint16_t displayTicks = 0; // local timer state machine +}; + +#endif // DISPLAY_H \ No newline at end of file diff --git a/include/index_html.h b/include/index_html.h index a3a9f1c..1b17f48 100644 --- a/include/index_html.h +++ b/include/index_html.h @@ -23,7 +23,7 @@ const char INDEX_HTML[] PROGMEM = R"=====(
-
openhab
+
bindings
dtu
wifi
@@ -39,37 +39,71 @@ const char INDEX_HTML[] PROGMEM = R"=====( connect to wifi:
- +
wifi password (show):
- +
save close
-
-
-

define your openhab instance

-
-
- IP to openhab: -
-
- -
-
- openHab item prefix for U,I,P,dE,TE per channel: +
+
+

openhab

+
+

define your openhab instance

+
+
+ IP to openhab: +
+
+ +
+
+ openHab item prefix for U,I,P,dE,TE per channel: +
+
+ +
-
- +
+
+

mqtt publishing

+
+

publish all data to a specific mqtt broker and subscribing to the requested powersetting

+
+
+ IP/port to mqtt broker (e.g. 192.168.178.100:1883): +
+
+ +
+
+ specific user on your mqtt broker instance: +
+
+ +
+
+ password for the given mqtt user (show): +
+
+ +
+
+ mqtt main topic for this dtu (e.g. dtu1 will appear as 'dtu1/grid/U' in the broker): +
+
+ +
- save + save close
@@ -89,8 +123,6 @@ const char INDEX_HTML[] PROGMEM = R"=====(
dtu cloud update pause (no cycle update every full 15 min): -
-

@@ -98,13 +130,13 @@ const char INDEX_HTML[] PROGMEM = R"=====( dtu local wireless access point:
- +
dtu wifi password (show):
- +
@@ -386,14 +418,14 @@ const char INDEX_HTML[] PROGMEM = R"=====( // check every minute (62,5s) for an available update window.setInterval(function () { requestVersionData(); - }, 62500); + }, 300000); timerRemainingProgess = window.setInterval(function () { remainingResponse(); }, 100); }); - // switchung in popups between tabs + // switching in popups between tabs $(document).on("click", ".popupHeaderTabs>div", function (event) { $('.popupHeaderTabs>div').each(function () { $(this).removeClass("selected"); @@ -406,13 +438,24 @@ const char INDEX_HTML[] PROGMEM = R"=====( }); }) + // grey'ing the bindings sections according to activation + $("input[type='checkbox']").change(function () { + if ($(this).closest('div').get(0).id != '') { + if (this.checked) { + $(this).closest('div').css('color', ''); + } else { + $(this).closest('div').css('color', 'grey'); + } + } + }); + var show = function (id) { console.log("show " + id) $(id).show(200); if (id == '#changeSettings') { getWIFIdata(); getDTUdata(); - getOHdata(); + getBindingsData(); } } @@ -423,10 +466,11 @@ const char INDEX_HTML[] PROGMEM = R"=====( function checkInitToSettings(data) { // if not configured then start directly with settings dialogue - if (data.initMode == 1) { - show("#changeSettings"); + var startUptext = "settings --- startup config mode"; + if (data.initMode == 1 && $('.popupHeaderTitle').html() != startUptext) { + show('#changeSettings'); remainingTime = 0.1; // no countdown on top of the site - $('#settingsTitle').html("settings - in startup config mode"); + $('.popupHeaderTitle').html(startUptext); // disable close button $('#btnSettingsClose').css('opacity', '0.3'); $('#btnSettingsClose').attr('onclick', "") @@ -546,7 +590,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( // setting timer value according to user setting waitTime = data.dtuConnection.dtuDataCycle * 1000; - checkValueUpdate('#dtu_reboots_no', data.dtuConnection.dtuResetRequested ); + checkValueUpdate('#dtu_reboots_no', data.dtuConnection.dtuResetRequested); return true; } @@ -597,16 +641,36 @@ const char INDEX_HTML[] PROGMEM = R"=====( } - function getOHdata() { + function getBindingsData() { // $('#btnSaveDtuSettings').css('opacity', '1.0'); $('#btnSaveDtuSettings').attr('onclick', "changeDtuData();") ohData = cacheInfoData.openHabConnection; + mqttData = cacheInfoData.mqttConnection; // get networkdata + if (ohData.ohActive) { + $('#openhabActive').prop("checked", true); + $('#openhabSection').css('color', ''); + } else { + $('#openhabActive').prop("checked", false); + $('#openhabSection').css('color', 'grey'); + } $('#openhabIP').val(ohData.ohHostIp); $('#ohItemPrefix').val(ohData.ohItemPrefix); + + if (mqttData.mqttActive) { + $('#mqttActive').prop("checked", true); + $('#mqttSection').css('color', ''); + } else { + $('#mqttActive').prop("checked", false); + $('#mqttSection').css('color', 'grey'); + } + $('#mqttIP').val(mqttData.mqttIp+":"+mqttData.mqttPort); + $('#mqttUser').val(mqttData.mqttUser); + $('#mqttPassword').val(mqttData.mqttPass); + $('#mqttMainTopic').val(mqttData.mqttMainTopic); } $('.passcheck').click(function () { @@ -614,11 +678,13 @@ const char INDEX_HTML[] PROGMEM = R"=====( if ($(this).attr("value") == 'invisible') { $('#wifiPASSsend').attr('type', 'text'); $('#dtuPassword').attr('type', 'text'); + $('#mqttPassword').attr('type', 'text'); $('.passcheck').attr('value', 'visibile'); $('.passcheck').html("hide"); } else { $('#wifiPASSsend').attr('type', 'password'); $('#dtuPassword').attr('type', 'password'); + $('#mqttPassword').attr('type', 'password'); $('.passcheck').attr('value', 'invisible'); $('.passcheck').html("show"); } @@ -729,10 +795,43 @@ const char INDEX_HTML[] PROGMEM = R"=====( return; } - function changeOpenhabData() { + function changeBindingsData() { var openhabHostIpSend = $('#openhabIP').val(); + var openhabPrefixSend = $('#ohItemPrefix').val(); + if ($("#openhabActive").is(':checked')) { + openhabActiveSend = 1; + } else { + openhabActiveSend = 0; + } + + var mqttIpPortString = $('#mqttIP').val().split(":"); + + + var mqttIpSend = mqttIpPortString[0]; + var mqttPortSend = "1883"; + if(mqttIpPortString[1] != undefined && !isNaN(mqttIpPortString[1])) { + mqttPortSend = mqttIpPortString[1]; + } + var mqttUserSend = $('#mqttUser').val(); + var mqttPassSend = $('#mqttPassword').val(); + var mqttMainTopicSend = $('#mqttMainTopic').val(); + if ($("#mqttActive").is(':checked')) { + mqttActiveSend = 1; + } else { + mqttActiveSend = 0; + } + var data = {}; data["openhabHostIpSend"] = openhabHostIpSend; + data["openhabPrefixSend"] = openhabPrefixSend; + data["openhabActiveSend"] = openhabActiveSend; + + data["mqttIpSend"] = mqttIpSend; + data["mqttPortSend"] = mqttPortSend; + data["mqttUserSend"] = mqttUserSend; + data["mqttPassSend"] = mqttPassSend; + data["mqttMainTopicSend"] = mqttMainTopicSend; + data["mqttActiveSend"] = mqttActiveSend; console.log("send to server: openhabHostIpSend: " + openhabHostIpSend); @@ -743,7 +842,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( urlEncodedDataPairs.push( `${encodeURIComponent(name)}=${encodeURIComponent(value)}`, ); - console.log("push: " + name); + console.log("push: " + name + " - value: " + value); } // Combine the pairs into a single string and replace all %-encoded spaces to @@ -752,7 +851,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( var xmlHttp = new XMLHttpRequest(); - xmlHttp.open("POST", "/updateOHSettings", false); // false for synchronous request + xmlHttp.open("POST", "/updateBindingsSettings", false); // false for synchronous request xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // Finally, send our data. @@ -760,13 +859,13 @@ const char INDEX_HTML[] PROGMEM = R"=====( strResult = JSON.parse(xmlHttp.responseText); console.log("got from server: " + strResult); - console.log("got from server - strResult.dtuHostIp: " + strResult.openhabHostIp + " - cmp with: " + openhabHostIp); + console.log("got from server - strResult.dtuHostIp: " + strResult.openhabHostIp + " - cmp with: " + openhabHostIpSend); - if (strResult.dtuHostIp == dtuHostIpSend && strResult.dtuSsid == dtuSsidSend && strResult.dtuPassword == dtuPasswordSend) { + if (strResult.openhabHostIp == openhabHostIpSend && strResult.mqttBrokerIp == mqttIpSend && strResult.mqttBrokerUser == mqttUserSend) { console.log("check saved data - OK"); - alert("openhab Settings change\n__________________________________\n\nYour settings were successfully changed.\n\nClient connection will be reconnected to the new IP."); + alert("bindings Settings change\n__________________________________\n\nYour settings were successfully changed.\n\nChanges will be applied."); } else { - alert("openhab Settings change\n__________________________________\n\nSome error occured! Checking data from gateway are not as excpeted after sending to save.\n\nPlease try again!"); + alert("bindings Settings change\n__________________________________\n\nSome error occured! Checking data from gateway are not as excpeted after sending to save.\n\nPlease try again!"); } hide('#changeSettings'); @@ -997,7 +1096,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( type: 'GET', contentType: false, processData: false, - timeout: 1000, + timeout: 2000, success: function (data) { refreshData(data); }, @@ -1015,7 +1114,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( type: 'GET', contentType: false, processData: false, - timeout: 1000, + timeout: 2000, success: function (info) { cacheInfoData = info; checkInitToSettings(info); diff --git a/include/style_css.h b/include/style_css.h index 9f26b12..805d7a6 100644 --- a/include/style_css.h +++ b/include/style_css.h @@ -359,6 +359,14 @@ input { padding: 0 15px } +input[type=checkbox] { + width:1.5em; + height:1.5em; + display:inline; + position: relative; + top: 0.3em; +} + #frame { background: #000000; /* max-width: 100%; */ diff --git a/include/version.h b/include/version.h index c505119..10fba7a 100644 --- a/include/version.h +++ b/include/version.h @@ -1,3 +1,3 @@ -#define VERSION "1.4.0" -#define BUILDTIME "24.03.2024 - 15:28:25" -#define BUILDTIMESTAMP "1711290505" \ No newline at end of file +#define VERSION "1.5.0_localDev" +#define BUILDTIME "01.06.2024 - 17:49:18" +#define BUILDTIMESTAMP "1717256958" \ No newline at end of file diff --git a/include/version.json b/include/version.json index a57d02e..3309615 100644 --- a/include/version.json +++ b/include/version.json @@ -1,6 +1,6 @@ { - "version": "1.4.0", - "versiondate": "24.03.2024 - 15:28:25", - "linksnapshot": "https://github.com/ohAnd/dtuGateway/releases/download/snapshot/dtuGateway_snapshot_1.4.0.bin", - "link": "https://github.com/ohAnd/dtuGateway//releases/latest/download/dtuGateway_release_1.4.0.bin" + "version": "1.5.0_localDev", + "versiondate": "01.06.2024 - 17:49:18", + "linksnapshot": "https://github.com/ohAnd/dtuGateway/releases/download/snapshot/dtuGateway_snapshot_1.5.0_localDev.bin", + "link": "https://github.com/ohAnd/dtuGateway//releases/latest/download/dtuGateway_release_1.5.0_localDev.bin" } \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index e564bde..ae35648 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,7 +12,7 @@ platform = espressif8266 board = esp07s framework = arduino -monitor_port = COM3 +monitor_port = COM4 monitor_speed = 115200 lib_deps = arduino-libraries/NTPClient @ ^3.2.1 @@ -21,10 +21,17 @@ lib_deps = gyverlibs/UnixTime @ ^1.1 bblanchon/ArduinoJson @ ^7.0.0 khoih-prog/ESP8266TimerInterrupt @ ^1.6.0 + knolleary/PubSubClient @ ^2.8 + me-no-dev/ESPAsyncTCP @ ^1.2.2 + me-no-dev/ESP Async WebServer @ ^1.2.3 + olikraus/U8g2 @ ^2.35.19 custom_nanopb_protos = + + + + extra_scripts = pre:version_inc.py -monitor_filters = time +monitor_filters = + esp8266_exception_decoder + default + time diff --git a/readme.md b/readme.md index 20d1a4f..9c51d98 100644 --- a/readme.md +++ b/readme.md @@ -12,10 +12,14 @@ - [data - http://\/api/data](#data---httpip_to_your_deviceapidata) - [info - http://\/api/info](#info---httpip_to_your_deviceapiinfo) - [openhab integration/ configuration](#openhab-integration-configuration) + - [mqqt integration/ configuration](#mqqt-integration-configuration) - [known bugs](#known-bugs) - [releases](#releases) - [installation / update](#installation--update) - - [first setup with access point](#first-setup-with-access-point) + - [hardware](#hardware) + - [first installation to the ESP device](#first-installation-to-the-esp-device) + - [first setup with access point](#first-setup-with-access-point) + - [return to factory mode](#return-to-factory-mode) - [main](#main) - [snapshot](#snapshot) - [experiences with the hoymiles HMS-800W-2T](#experiences-with-the-hoymiles-hms-800w-2t) @@ -37,6 +41,7 @@ Data from dtu can be read in a very short time, but it has to be tested how ofte On a manual way you can be back on track, if you are logging in to the local access point of the dtu and resend your local wifi login data to (it seems) initiate a reboot. With this way you can be back online in ~ 1:30 minutes. +So I decided to put this abstraction in an **ESP8266** to have a stable abstraction to an existing smart home environment. > *hint: the whole project could be also implemented on a small server and translated to e.g. python [see here for an example](https://github.com/henkwiedig/Hoymiles-DTU-Proto) and also the sources below* @@ -44,7 +49,7 @@ On a manual way you can be back on track, if you are logging in to the local acc 1. Abstract the interface to the dtu (inverter connection endpoint) with different possibilities to connect to other systems. (push/ pull) 2. Very stable interface with no dependencies to an environment/ a system with a stand alone application based on an arduino board (ESP8266). 3. TODO: Ability to change running wifi to connect to dtu over local network or direct access point. -4. Use this need to create a full enivronment for an ESP based project. (see features below) +4. Use this need to create a full enivronment for an ESP8266 based project. (see features below) ## features @@ -56,11 +61,28 @@ On a manual way you can be back on track, if you are logging in to the local acc - temperature and wifi rssi of the dtu - setting the target inverter power limit dynamically - serving the readed data per /api/data -- updating openHab instance with readed data and pulling set data from the instance +- binding configuration with seperate activation and login data setting +- binding: updating openHab instance with readed data and pulling set data from the instance +- binding: updating to a MQTT broker with readed data [OPEN: pulling set power data from the mqtt instance] - for testing purposes the time between each request is adjustable (default 31 seconds) - syncing time of gateway with the local time of the dtu to prevent wrong restart counters - configurable 'cloud pause' - see [experiences](#-experiences-with-the-hoymiles-HMS-800W-2T) - to prevent missing updates by the dtu to the hoymiles cloud - automatic reboot of DTU, if there is an error detected (e.g. inplausible not changed values) +- display SSH1106 implemented + - segmented in 3 parts + - header: + - left: wifi quality dtuGateway to local wifi + - mid: current time of dtuGateway + - right: wifi quality of dtu connection to local wifi + - main: + - small left: current power limit of inverter + - big mid/ right: current power of inverter + - footer: + - left: current daily yield + - right: current total yield + - additonal features + - small screensaver to prevent burn-in effect with steady components on the screen (shifting the whole screen every minute with 1 pixel in a 4 step rotation) + - smooth brightness control for changed main value - increase to max after change and then dimmming smooth back to the default level ### regarding environment @@ -199,11 +221,57 @@ On a manual way you can be back on track, if you are logging in to the local acc - "_PowerLimit" //current read power limit from dtu - "_WifiRSSI" +## mqqt integration/ configuration + +- set the IP to your MQTT broker +- set the MQTT user and MQTT password +- set the main topic e.g. 'dtu1' for the pubished data +- data will be published as following ('dtu1' is configurable in the settings): + + ``` + dtu1/timestamp + + dtu1/grid/U + dtu1/grid/I + dtu1/grid/P + dtu1/grid/dailyEnergy + dtu1/grid/totalEnergy + + dtu1/pv0/U + dtu1/pv0/I + dtu1/pv0/P + dtu1/pv0/dailyEnergy + dtu1/pv0/totalEnergy + + dtu1/pv1/U + dtu1/pv1/I + dtu1/pv1/P + dtu1/pv1/dailyEnergy + dtu1/pv1/totalEnergy + + dtu1/inverter/Temp + dtu1/inverter/PowerLimit + dtu1/inverter/WifiRSSI + ``` + ## known bugs - sometimes out-of-memory resets with instant reboots (rare after some hours or more often after some days) ## releases ### installation / update +#### hardware +- ESP8266 based board +- optional display: + - connect SSH1106 driven OLED display (128x64 e.g. 1.27") with your ESP8266 board (VCC, GND, SCK, SCL) + - pinning for different boards (display connector to ESPxx board pins) + + | dev board | ESP family | VCC | GND | SCK | SDA | tested | + |--------------------------------------------------|------------|:----:|:---:|:----------------:|:----------------:|:------:| + | AZDelivery D1 Board NodeMCU ESP8266MOD-12F | ESP8266 | 3.3V | GND | D15/GPIO5/SCL/D3 | D14/GPIO4/SDA/D4 | OK | + | AZDelivery NodeMCU V2 WiFi Amica ESP8266 ESP-12F | ESP8266 | 3.3V | GND | D1/GPIO5/SCL | D2/GPIO4/SDA | OK | + | AZDelivery D1 Mini NodeMcu mit ESP8266-12F | ESP8266 | 3V3 | G | D1/GPIO5/SCL | D2/GPIO4/SDA | OK | + +#### first installation to the ESP device 1. download the preferred release as binary (see below) 2. **HAS TO BE VERIFIED** [only once] flash the esp8266 board with the (esp download tool)[https://www.espressif.com/en/support/download/other-tools] 1. choose bin file at address 0x0 @@ -215,16 +283,30 @@ On a manual way you can be back on track, if you are logging in to the local acc 7. press start ;-) 3. all further updates are done by OTA (see chapters above) -### first setup with access point +#### first setup with access point 1. connect with the AP hoymilesGW_ (on smartphone sometimes you have to accept the connection explicitly with the knowledge there is no internet connectivity) 2. open the website http://192.168.4.1 (or http://hoymilesGW.local) for the first configuration 3. choose your wifi 4. type in the wifi password - save +5. in webfrontend setting your DTU IP adress within your local network (currently the user and password for dtu are not needed, for later integration relevant for a direct connection to the dtu over their access point) +6. then you can configure your needed binding + 1. openhab -> set the IP of your openhab instance and the prefix for the dtu items according to your configured item file in openhab + 2. mqtt -> set the IP and port (e.g. 192.178.0.42:1883) of your mqtt broker and the user and passwort that your hacve for this instance +7. after this one time configuration the connection to the dtu should be established and the data displayed in the webfrontend and according to your setup transmitted to the target instance + +#### return to factory mode +1. connect your ESP with serial (115200 baud) in a COM terminal +2. check if receive some debug data from the device +3. type in `resetToFactory 1` +4. response of the device will be `reinitialize EEPROM data and reboot ...` +5. after reboot the device starting again in AP mode for first setup ### main latest release - changes will documented by commit messages https://github.com/ohAnd/dtuGateway/releases/latest +(to be fair, the amount of downloads is the count of requests from the client to check for new firmware for the OTA update) + ![GitHub Downloads (all assets, latest release)](https://img.shields.io/github/downloads/ohand/dtuGateway/latest/total) ![GitHub (Pre-)Release Date](https://img.shields.io/github/release-date/ohand/dtuGateway) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ohand/dtuGateway/main_build.yml) @@ -271,11 +353,13 @@ fully covered with github actions building on push to develop and serving as a snapshot release with direct connection to the device - available updates will be locally checked and offered to the user for installation +hint: referring to [Error Build in platform.io - buildnumber file not found #6](https://github.com/ohAnd/dtuGateway/issues/6) for local building: +> For automatic versioning there is a file called ../include/buildnumber.txt expected. With the content "localDev" or versionnumber e.g. "1.0.0" in first line. (File is blocked by .gitignore for GitHub actions to run.) + + + ### platformio - https://docs.platformio.org/en/latest/core/installation/methods/installer-script.html#local-download-macos-linux-windows ### hints for workflow - creating dev release (https://blog.derlin.ch/how-to-create-nightly-releases-with-github-actions) - - - diff --git a/src/Config.cpp b/src/Config.cpp index 5f653af..e365629 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -18,7 +18,7 @@ void loadConfigFromEEPROM() void initializeEEPROM() { // EEPROM initialize - EEPROM.begin(256); // emulate 512 Byte pf EEPROM + EEPROM.begin(1024); // emulate 512 Byte pf EEPROM // Check if EEPROM has been initialized before EEPROM.get(0, userConfig); @@ -32,11 +32,22 @@ void initializeEEPROM() // EEPROM not initialized, set default values strcpy(userConfig.dtuSsid, "DTUBI-12345678"); strcpy(userConfig.dtuPassword, "dtubiPassword"); + strcpy(userConfig.wifiSsid, "mySSID"); strcpy(userConfig.wifiPassword, "myPassword"); strcpy(userConfig.dtuHostIp, "192.168.0.254"); + strcpy(userConfig.openhabHostIp, "192.168.1.100"); strcpy(userConfig.openItemPrefix, "inverter"); + userConfig.openhabActive = 0; + + strcpy(userConfig.mqttBrokerIp, "192.168.1.100"); + userConfig.mqttBrokerPort = 1883; + strcpy(userConfig.mqttBrokerUser, "dtuuser"); + strcpy(userConfig.mqttBrokerPassword, "dtupass"); + strcpy(userConfig.mqttBrokerMainTopic, "dtu1"); + userConfig.mqttActive = 0; + userConfig.selectedUpdateChannel = 0; // default - release channel userConfig.dtuCloudPauseActive = 1; userConfig.dtuCloudPauseTime = 40; @@ -76,9 +87,23 @@ void printEEPROMdata() Serial.print(F("openhab host: \t\t")); Serial.println(userConfig.openhabHostIp); - Serial.print(F("openhab item prefix: \t")); Serial.println(userConfig.openItemPrefix); + Serial.print(F("openhab binding active: \t")); + Serial.println(userConfig.openhabActive); + + Serial.print(F("mqtt host: \t\t")); + Serial.println(userConfig.mqttBrokerIp); + Serial.print(F("mqtt port: \t\t")); + Serial.println(userConfig.mqttBrokerPort); + Serial.print(F("mqtt user: \t\t")); + Serial.println(userConfig.mqttBrokerUser); + Serial.print(F("mqtt pass: \t\t")); + Serial.println(userConfig.mqttBrokerPassword); + Serial.print(F("mqtt topic: \t\t")); + Serial.println(userConfig.mqttBrokerMainTopic); + Serial.print(F("mqtt binding active: \t")); + Serial.println(userConfig.mqttActive); Serial.print(F("dtu update time: \t")); Serial.println(userConfig.dtuUpdateTime); diff --git a/src/display.cpp b/src/display.cpp new file mode 100644 index 0000000..d688b6b --- /dev/null +++ b/src/display.cpp @@ -0,0 +1,213 @@ +#include +#include + +U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE); + +Display::Display() {} + +void Display::setup() +{ + u8g2.begin(); +} + +void Display::renderScreen(String time, String version) +{ + displayTicks++; + if (displayTicks > 600) + displayTicks = 0; // after 1 minute restart + + lastDisplayData.version = version.c_str(); + lastDisplayData.formattedTime = time.c_str(); + + checkChangedValues(); + + if (valueChanged) + { + brightness = BRIGHTNESS_MAX; + drawScreen(); // draw once to update values on screen + } + else if (brightness > BRIGHTNESS_MIN) + { + brightness = brightness - 5; + u8g2.setContrast(brightness); + } + + if (displayTicks % 10 == 0) + drawScreen(); // draw every 1 second + + if (displayTicks == 0) + screenSaver(); // every minute shift screen to avoid burn in +} + +void Display::drawScreen() +{ + // store last shown value + lastDisplayData.totalYieldDay = globalData.grid.dailyEnergy; + lastDisplayData.totalYieldTotal = round(globalData.grid.totalEnergy); + lastDisplayData.rssiGW = globalData.wifi_rssi_gateway; + lastDisplayData.rssiDTU = globalData.dtuRssi; + + u8g2.clearBuffer(); + u8g2.setDrawColor(1); + u8g2.setFontPosTop(); + u8g2.setFontDirection(0); + u8g2.setFontRefHeightExtendedText(); + + drawHeader(); + + // main screen + if (dtuConnection.dtuConnectState == DTU_STATE_CONNECTED) + { + drawMainDTUOnline(); + } + else if (dtuConnection.dtuConnectState == DTU_STATE_CLOUD_PAUSE) + { + drawMainDTUOnline(true); + } + else + { + drawMainDTUOffline(); + } + + drawFooter(); + + // set current choosen contrast + u8g2.setContrast(brightness); + + u8g2.sendBuffer(); +} + +void Display::drawMainDTUOnline(bool pause) +{ + lastDisplayData.totalPower = round(globalData.grid.power); + lastDisplayData.powerLimit = globalData.powerLimit; + + // main screen + + u8g2.setFont(u8g2_font_logisoso28_tf); + // String wattage = String(lastDisplayData.totalPower) + " W"; + String wattage = String(lastDisplayData.totalPower); + u8g2_uint_t width = u8g2.getUTF8Width(wattage.c_str()); + int wattage_xpos = (128 - width) / 2; + u8g2.drawStr(wattage_xpos + offset_x, 19 + offset_y, wattage.c_str()); + if (!pause) + { + u8g2.setFont(u8g2_font_logisoso28_tf); + u8g2.drawStr(107 + offset_x, 19 + offset_y, "W"); + } + else + { + u8g2.setFont(u8g2_font_logisoso16_tf); + u8g2.drawStr(107 + offset_x, 19 + offset_y, "W"); + u8g2.setFont(u8g2_font_unifont_t_emoticons); + u8g2.drawGlyph(104 + offset_x, 50 + offset_y, 0x0054); + } + + // main screen small left + u8g2.drawRFrame(0 + offset_x, 36 + offset_y, 29, 16, 4); + u8g2.setFont(u8g2_font_6x10_tf); + u8g2.drawStr(3 + offset_x, 40 + offset_y, (String(lastDisplayData.powerLimit)).c_str()); + u8g2.drawStr(19 + offset_x, 40 + offset_y, "%"); +} + +void Display::drawMainDTUOffline() +{ + // main screen + u8g2.setFont(u8g2_font_logisoso16_tf); + u8g2.drawStr(15 + offset_x, 25 + offset_y, "DTU offline"); +} + +void Display::drawHeader() +{ + // header + // header - content center + u8g2.setFont(u8g2_font_6x10_tf); + // u8g2.drawStr(5 * 7 + offset_x, -1 + offset_y, "dtuGateway"); + u8g2.drawStr(7 * 6 + offset_x, -1 + offset_y, lastDisplayData.formattedTime); + + // header - content left + u8g2.setFont(u8g2_font_siji_t_6x10); + // 0xE217 - wifi off + // 0xE218 - wifi 1 + // 0xE219 - wifi 2 + // 0xE21A - wifi 3 + uint16_t wifi_symbol = 0xE217; + if (lastDisplayData.rssiGW > 80) + wifi_symbol = 0xE21A; + else if (lastDisplayData.rssiGW > 50) + wifi_symbol = 0xE219; + else if (lastDisplayData.rssiGW > 10) + wifi_symbol = 0xE218; + u8g2.drawGlyph(3 + offset_x, 0 + offset_y, wifi_symbol); + + // header - content right + // uint16_t inverterState = 0xE233; // moon + // if (lastDisplayData.totalPower > 0) + // inverterState = 0xE234; // sun + // u8g2.drawGlyph(113 + offset_x, 0 + offset_y, inverterState); + u8g2.setFont(u8g2_font_siji_t_6x10); + // 0xE21F - wifi off + // 0xE220 - wifi 1 + // 0xE221 - wifi 2 + // 0xE222 - wifi 3 + uint16_t wifi_dtu_symbol = 0xE21F; + if (lastDisplayData.rssiDTU > 80) + wifi_dtu_symbol = 0xE222; + else if (lastDisplayData.rssiDTU > 50) + wifi_dtu_symbol = 0xE221; + else if (lastDisplayData.rssiDTU > 10) + wifi_dtu_symbol = 0xE220; + u8g2.drawGlyph(113 + offset_x, 0 + offset_y, wifi_dtu_symbol); + + // header - bootom line + u8g2.drawRFrame(0 + offset_x, -11 + offset_y, 127, 22, 4); +} + +void Display::drawFooter() +{ + // footer + // footer - upper line + u8g2.drawRFrame(0 + offset_x, 54 + offset_y, 127, 14, 4); + // footer - content + u8g2.setFont(u8g2_font_5x7_tf); + // u8g2.drawStr(3 + offset_x, 57 + offset_y, lastDisplayData.formattedTime); + // u8g2.drawStr(3 + 11 * 4 + offset_x, 57 + offset_y, "FW:"); + // u8g2.drawStr(3 + 11 * 4 + 4 * 4 + offset_x, 57 + offset_y, lastDisplayData.version); + u8g2.drawStr(3 + offset_x, 56 + offset_y, ("d: " + String(lastDisplayData.totalYieldDay, 3) + " kWh").c_str()); + u8g2.drawStr(3 + 18 * 4 + offset_x, 56 + offset_y, ("t: " + String(lastDisplayData.totalYieldTotal, 0) + " kWh").c_str()); +} + +void Display::screenSaver() +{ + if (offset_x == 0 && offset_y == 0) + { + offset_x = 1; + offset_y = 0; + } + else if (offset_x == 1 && offset_y == 0) + { + offset_x = 1; + offset_y = 1; + } + else if (offset_x == 1 && offset_y == 1) + { + offset_x = 0; + offset_y = 1; + } + else if (offset_x == 0 && offset_y == 1) + { + offset_x = 0; + offset_y = 0; + } + // contrast_value = contrast_value + 5; + // if(contrast_value > 255) contrast_value = 100; + // if(brightness == 255) brightness = 4; + // else if(brightness != 255) brightness = 255; +} + +void Display::checkChangedValues() +{ + valueChanged = false; + if (lastDisplayData.totalPower != round(globalData.grid.power)) + valueChanged = true; +} diff --git a/src/dtuGateway.ino b/src/dtuGateway.ino index 1241769..cc89328 100644 --- a/src/dtuGateway.ino +++ b/src/dtuGateway.ino @@ -13,10 +13,14 @@ #include #include +#include + #include #include +#include + #include "dtuInterface.h" #include "index_html.h" @@ -38,11 +42,13 @@ char updateInfoWebPathRelease[128] = "https://github.com/ohAnd/dtuGateway/releas char versionServer[32] = "checking"; char versiondateServer[32] = "..."; -char updateURL[128] = ""; // will be read by getting -> updateInfoWebPath +char updateURL[196] = ""; // will be read by getting -> updateInfoWebPath char versionServerRelease[32] = "checking"; char versiondateServerRelease[32] = "..."; -char updateURLRelease[128] = ""; // will be read by getting -> updateInfoWebPath +char updateURLRelease[196] = ""; // will be read by getting -> updateInfoWebPath boolean updateAvailable = false; +boolean updateRunning = false; +boolean updateInfoRequested = false; float updateProgress = 0; char updateState[16] = "waiting"; @@ -60,12 +66,17 @@ int reconnectsCnt = -1; // first needed run inkrement to 0 #define BLINK_PAUSE_CLOUD_UPDATE 4 // 0,5 Hz blip - DTO - Cloud update int8_t blinkCode = BLINK_WIFI_OFF; +Display oledDisplay; + String host; WiFiUDP ntpUDP; WiFiClient dtuClient; NTPClient timeClient(ntpUDP); // By default 'pool.ntp.org' is used with 60 seconds update interval #define CLIENT_TIME_OFFSET 3600 +WiFiClient puSubClient; +PubSubClient mqttClient(puSubClient); + ESP8266WebServer server(80); uint32_t chipID = ESP.getChipId(); @@ -180,8 +191,10 @@ boolean checkWifiTask() // scan network for first settings or change int networkCount = 0; String foundNetworks = "[{\"name\":\"empty\",\"wifi\":0,\"chan\":0}]"; -boolean scanNetworksResult(int networksFound) + +boolean scanNetworksResult() { + int networksFound = WiFi.scanComplete(); // print out Wi-Fi network scan result upon completion if (networksFound > 0) { @@ -204,13 +217,14 @@ boolean scanNetworksResult(int networksFound) } } foundNetworks = foundNetworks + "]"; - // WiFi.scanDelete(); + WiFi.scanDelete(); + return true; } else { - Serial.println(F("no networks found after scanning!")); + // Serial.println(F("no networks found after scanning!")); + return false; } - return true; } // web page @@ -287,10 +301,20 @@ void handleInfojson() JSON = JSON + "},"; JSON = JSON + "\"openHabConnection\": {"; + JSON = JSON + "\"ohActive\": " + userConfig.openhabActive + ","; JSON = JSON + "\"ohHostIp\": \"" + String(userConfig.openhabHostIp) + "\","; JSON = JSON + "\"ohItemPrefix\": \"" + String(userConfig.openItemPrefix) + "\""; JSON = JSON + "},"; + JSON = JSON + "\"mqttConnection\": {"; + JSON = JSON + "\"mqttActive\": " + userConfig.mqttActive + ","; + JSON = JSON + "\"mqttIp\": \"" + String(userConfig.mqttBrokerIp) + "\","; + JSON = JSON + "\"mqttPort\": " + String(userConfig.mqttBrokerPort) + ","; + JSON = JSON + "\"mqttUser\": \"" + String(userConfig.mqttBrokerUser) + "\","; + JSON = JSON + "\"mqttPass\": \"" + String(userConfig.mqttBrokerPassword) + "\","; + JSON = JSON + "\"mqttMainTopic\": \"" + String(userConfig.mqttBrokerMainTopic) + "\""; + JSON = JSON + "},"; + JSON = JSON + "\"dtuConnection\": {"; JSON = JSON + "\"dtuHostIp\": \"" + String(userConfig.dtuHostIp) + "\","; JSON = JSON + "\"dtuSsid\": \"" + String(userConfig.dtuSsid) + "\","; @@ -337,8 +361,6 @@ void handleUpdateWifiSettings() server.send(200, "application/json", JSON); - delay(2000); // give time for the json response - // reconnect with new values WiFi.disconnect(); WiFi.mode(WIFI_STA); @@ -381,31 +403,62 @@ void handleUpdateDtuSettings() server.send(200, "application/json", JSON); - delay(2000); // give time for the json response - // stopping connection to DTU - to force reconnect with new data dtuConnectionStop(&dtuClient, DTU_STATE_TRY_RECONNECT); } -void handleUpdateOpenhabSettings() +void handleUpdateBindingsSettings() { String openhabHostIpUser = server.arg("openhabHostIpSend"); // retrieve message from webserver - Serial.println("\nhandleUpdateDtuSettings - got openhab ip: " + openhabHostIpUser); + String openhabPrefix = server.arg("openhabPrefixSend"); + String openhabActive = server.arg("openhabActiveSend"); + + String mqttIP = server.arg("mqttIpSend"); + String mqttPort = server.arg("mqttPortSend"); + String mqttUser = server.arg("mqttUserSend"); + String mqttPass = server.arg("mqttPassSend"); + String mqttMainTopic = server.arg("mqttMainTopicSend"); + String mqttActive = server.arg("mqttActiveSend"); openhabHostIpUser.toCharArray(userConfig.openhabHostIp, sizeof(userConfig.openhabHostIp)); + openhabPrefix.toCharArray(userConfig.openItemPrefix, sizeof(userConfig.openItemPrefix)); + + if (openhabActive == "1") + userConfig.openhabActive = true; + else + userConfig.openhabActive = false; + + mqttIP.toCharArray(userConfig.mqttBrokerIp, sizeof(userConfig.mqttBrokerIp)); + userConfig.mqttBrokerPort = mqttPort.toInt(); + mqttUser.toCharArray(userConfig.mqttBrokerUser, sizeof(userConfig.mqttBrokerUser)); + mqttPass.toCharArray(userConfig.mqttBrokerPassword, sizeof(userConfig.mqttBrokerPassword)); + mqttMainTopic.toCharArray(userConfig.mqttBrokerMainTopic, sizeof(userConfig.mqttBrokerMainTopic)); + + if (mqttActive == "1") + userConfig.mqttActive = true; + else + userConfig.mqttActive = false; saveConfigToEEPROM(); delay(500); + // reintialize mqtt with new settings + initMqttClient(); + String JSON = "{"; + JSON = JSON + "\"openhabActive\": " + userConfig.openhabActive + ","; JSON = JSON + "\"openhabHostIp\": \"" + userConfig.openhabHostIp + "\","; + JSON = JSON + "\"openItemPrefix\": \"" + userConfig.openItemPrefix + "\","; + JSON = JSON + "\"mqttActive\": " + userConfig.mqttActive + ","; + JSON = JSON + "\"mqttBrokerIp\": \"" + userConfig.mqttBrokerIp + "\","; + JSON = JSON + "\"mqttBrokerPort\": " + String(userConfig.mqttBrokerPort) + ","; + JSON = JSON + "\"mqttBrokerUser\": \"" + userConfig.mqttBrokerUser + "\","; + JSON = JSON + "\"mqttBrokerPassword\": \"" + userConfig.mqttBrokerPassword + "\","; + JSON = JSON + "\"mqttBrokerMainTopic\": \"" + userConfig.mqttBrokerMainTopic + "\""; JSON = JSON + "}"; server.send(200, "application/json", JSON); - - delay(2000); // give time for the json response - - Serial.println("handleUpdateOpenhabSettings - send JSON: " + String(JSON)); + Serial.println("handleUpdateBindingsSettings - send JSON: " + String(JSON)); } void handleUpdateOTASettings() @@ -423,16 +476,18 @@ void handleUpdateOTASettings() JSON = JSON + "}"; server.send(200, "application/json", JSON); - - // delay(2000); // give time for the json response + Serial.println("handleUpdateDtuSettings - send JSON: " + String(JSON)); // trigger new update info with changed release channel - getUpdateInfo(); - - Serial.println("handleUpdateDtuSettings - send JSON: " + String(JSON)); + // getUpdateInfo(AsyncWebServerRequest *request); + updateInfoRequested = true; } // webserver port 80 +// void initializeWebServer() +// { +// server.on("/", HTTP_GET, handleRoot); + void initializeWebServer() { server.on("/", HTTP_GET, handleRoot); @@ -450,14 +505,14 @@ void initializeWebServer() server.on("/updateWifiSettings", handleUpdateWifiSettings); server.on("/updateDtuSettings", handleUpdateDtuSettings); server.on("/updateOTASettings", handleUpdateOTASettings); - server.on("/updateOHSettings", handleUpdateOpenhabSettings); + server.on("/updateBindingsSettings", handleUpdateBindingsSettings); // api GETs server.on("/api/data", handleDataJson); server.on("/api/info", handleInfojson); // OTA update - server.on("/updateGetInfo", getUpdateInfo); + server.on("/updateGetInfo", requestUpdateInfo); server.on("/updateRequest", handleUpdateRequest); server.begin(); @@ -497,15 +552,33 @@ void handleUpdateRequest() ESPhttpUpdate.onError(update_error); ESPhttpUpdate.closeConnectionsOnUpdate(false); - // // ESPhttpUpdate.rebootOnUpdate(false); // remove automatic update + // ESPhttpUpdate.rebootOnUpdate(false); // remove automatic update + + Serial.println(F("[update] starting update")); ESPhttpUpdate.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + updateRunning = true; + + // stopping all services to prevent OOM/ stackoverflow + timeClient.end(); + ntpUDP.stopAll(); + mqttClient.disconnect(); + puSubClient.stopAll(); + dtuClient.stopAll(); + MDNS.close(); + server.stop(); + server.close(); + t_httpUpdate_return ret = ESPhttpUpdate.update(updateclient, urlToBin); switch (ret) { case HTTP_UPDATE_FAILED: - Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); + Serial.printf("HTTP_UPDATE_FAILED Error (%d): %s\n", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str()); Serial.println(F("[update] Update failed.")); + // restart all services if failed + initializeWebServer(); // starting server again + startServices(); + updateRunning = false; break; case HTTP_UPDATE_NO_UPDATES: Serial.println(F("[update] Update no Update.")); @@ -516,6 +589,14 @@ void handleUpdateRequest() } Serial.println("[update] Update routine done - ReturnCode: " + String(ret)); } + +// +void requestUpdateInfo() +{ + updateInfoRequested = true; + server.send(200, "application/json", "{\"updateInfoRequested\": \"done\"}"); +} + // get the info about update from remote boolean getUpdateInfo() { @@ -532,25 +613,27 @@ boolean getUpdateInfo() versionUrl = updateInfoWebPath; } + Serial.print("\n---> getUpdateInfo - check for: " + versionUrl + "\n"); + // create an HTTPClient instance HTTPClient https; // Initializing an HTTPS communication using the secure client if (https.begin(*secClient, versionUrl)) - { // HTTPS + { // HTTPS + Serial.print(F("\n---> getUpdateInfo - https connected\n")); https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); // Enable automatic following of redirects int httpCode = https.GET(); + Serial.println("\n---> getUpdateInfo - got http ret code:" + String(httpCode)); // httpCode will be negative on error if (httpCode > 0) { // HTTP header has been send and Server response header has been handled - // Serial.printf("\n[HTTPS] GET... code: %d\n", httpCode); // file found at server if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { String payload = https.getString(); - // Serial.println(payload); // Parse JSON using ArduinoJson library DynamicJsonDocument doc(1024); @@ -567,25 +650,32 @@ boolean getUpdateInfo() } else { + // for special versions: develop, feature, localDev the version has to be truncated + String localVersion = String(VERSION); + if (localVersion.indexOf("_")) + { + localVersion = localVersion.substring(0, localVersion.indexOf("_")); + } + if (userConfig.selectedUpdateChannel == 0) { - // versionServerRelease = String(doc["version"]); strcpy(versionServerRelease, (const char *)(doc["version"])); - // versiondateServerRelease = String(doc["versiondate"]); strcpy(versiondateServerRelease, (const char *)(doc["versiondate"])); - // updateURLRelease = String(doc["link"]); strcpy(updateURLRelease, (const char *)(doc["link"])); - updateAvailable = checkVersion(String(VERSION), versionServerRelease); + updateAvailable = checkVersion(localVersion, versionServerRelease); } else { - // versionServer = String(doc["version"]); strcpy(versionServer, (const char *)(doc["version"])); - // versiondateServer = String(doc["versiondate"]); + String versionSnapshot = versionServer; + if (versionSnapshot.indexOf("_")) + { + versionSnapshot = versionSnapshot.substring(0, versionSnapshot.indexOf("_")); + } + strcpy(versiondateServer, (const char *)(doc["versiondate"])); - // updateURL = String(doc["linksnapshot"]); strcpy(updateURL, (const char *)(doc["linksnapshot"])); - updateAvailable = checkVersion(String(VERSION), versionServer); + updateAvailable = checkVersion(localVersion, versionSnapshot); } server.sendHeader("Connection", "close"); @@ -597,21 +687,22 @@ boolean getUpdateInfo() { Serial.printf("[HTTPS] GET... failed, error: %s\n", https.errorToString(httpCode).c_str()); } - + secClient->stop(); https.end(); } else { Serial.println(F("\ngetUpdateInfo - [HTTPS] Unable to connect to server")); } - + // secClient->stopAll(); + updateInfoRequested = false; return true; } // check version local with remote boolean checkVersion(String v1, String v2) { - Serial.println("\nstart compare: " + String(v1) + " - " + String(v2)); + Serial.println("\ncompare versions: " + String(v1) + " - " + String(v2)); // Method to compare two versions. // Returns 1 if v2 is smaller, -1 // if v1 is smaller, 0 if equal @@ -675,9 +766,9 @@ void update_finished() } void update_progress(int cur, int total) { - updateProgress = (cur / total) * 100; + updateProgress = ((float)cur / (float)total) * 100; strcpy(updateState, "running"); - Serial.print("CALLBACK: HTTP update process at " + String(cur) + " of " + String(total) + " bytes - " + String(updateProgress, 1) + " %...\n"); + Serial.print("CALLBACK: HTTP update process at " + String(cur) + " of " + String(total) + " bytes - " + String(updateProgress, 1) + " %\n"); } void update_error(int err) { @@ -819,6 +910,98 @@ boolean updateValueToOpenhab() return true; } +// mqtt client + +void initMqttClient() +{ + mqttClient.setServer(userConfig.mqttBrokerIp, userConfig.mqttBrokerPort); + Serial.print("\ninitialized MQTT client ... to broker: " + String(userConfig.mqttBrokerIp) + ":" + String(userConfig.mqttBrokerPort) + "\n"); +} + +void connectCheckMqttClient() +{ + if (!mqttClient.connected()) + { + Serial.print("\nMQTT not connected, try to connect ... "); + // Attempt to connect + if (mqttClient.connect("dtuGateway", userConfig.mqttBrokerUser, userConfig.mqttBrokerPassword)) + { + Serial.println("connected"); + } + else + { + Serial.print("failed, rc="); + Serial.print(mqttClient.state()); + Serial.println(" try again in 5 seconds"); + } + } +} + +boolean postMessageToMQTTbroker(String topic, String value) +{ + const char *charTopic = topic.c_str(); + const char *charValue = value.c_str(); + mqttClient.publish(charTopic, charValue); + + // Serial.println("\npostMessageToMQTTbroker - send '" + value + "' to topic: " + topic); + return true; +} + +boolean updateValuesToMqtt() +{ + connectCheckMqttClient(); + if (mqttClient.connected()) + { + boolean sendOk = postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/timestamp", (String)timeStampInSecondsDtuSynced); + if (sendOk) + { + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/grid/U", (String)globalData.grid.voltage); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/grid/I", (String)globalData.grid.current); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/grid/P", (String)globalData.grid.power); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/grid/dailyEnergy", String(globalData.grid.dailyEnergy, 3)); + if (globalData.grid.totalEnergy != 0) + { + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/grid/totalEnergy", String(globalData.grid.totalEnergy, 3)); + } + + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv0/U", (String)globalData.pv0.voltage); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv0/I", (String)globalData.pv0.current); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv0/P", (String)globalData.pv0.power); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv0/dailyEnergy", String(globalData.pv0.dailyEnergy, 3)); + if (globalData.pv0.totalEnergy != 0) + { + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv0/totalEnergy", String(globalData.pv0.totalEnergy, 3)); + } + + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv1/U", (String)globalData.pv1.voltage); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv1/I", (String)globalData.pv1.current); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv1/P", (String)globalData.pv1.power); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv1/dailyEnergy", String(globalData.pv1.dailyEnergy, 3)); + if (globalData.pv1.totalEnergy != 0) + { + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/pv1/totalEnergy", String(globalData.pv1.totalEnergy, 3)); + } + + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/inverter/Temp", (String)globalData.inverterTemp); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/inverter/PowerLimit", (String)globalData.powerLimit); + postMessageToMQTTbroker(String(userConfig.mqttBrokerMainTopic) + "/inverter/WifiRSSI", (String)globalData.dtuRssi); + Serial.println("\nsent values to mqtt broker"); + } + else + { + Serial.println("\nerror during sent values to mqtt broker"); + } + } + else + { + Serial.println("\ncould not send to mqtt broker - mqtt not connected"); + return false; + } + return true; +} + +// **** + void setup() { // switch off SCK LED @@ -829,6 +1012,10 @@ void setup() pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW + // init display + oledDisplay.setup(); + + Serial.begin(115200); Serial.print(F("\nBooting - with firmware version ")); Serial.println(VERSION); @@ -844,7 +1031,10 @@ void setup() { Serial.println(F("\n+++ device in 'first start' mode - have to be initialized over own served wifi +++\n")); // first scan of networks - synchronous - scanNetworksResult(WiFi.scanNetworks()); + // scanNetworksResult(WiFi.scanNetworks()); + + WiFi.scanNetworks(); + scanNetworksResult(); // Connect to Wi-Fi as AP WiFi.mode(WIFI_AP); @@ -899,10 +1089,10 @@ void startServices() Serial.print(F("IP address of gateway: ")); Serial.println(WiFi.gatewayIP()); host = "hoymilesGW_" + String(chipID); - MDNS.begin(host); httpUpdater.setup(&server); + MDNS.begin(host); MDNS.addService("http", "tcp", 80); Serial.println("Ready! Open http://" + String(host) + ".local in your browser"); @@ -915,7 +1105,11 @@ void startServices() Serial.print(F("got time from time server: ")); Serial.println(String(starttime)); + // start first search for available wifi networks + WiFi.scanNetworks(true); + initializeWebServer(); + initMqttClient(); } else { @@ -1155,8 +1349,20 @@ void IRAM_ATTR timer1000MilliSeconds() void loop() { + // skip all tasks if update is running + if (updateRunning) + return; + // web server runner server.handleClient(); + // serving domain name + MDNS.update(); + + // runner for mqttClient to hold a already etablished connection + if (userConfig.mqttActive && mqttClient.connected()) + { + mqttClient.loop(); + } unsigned long currentMillis = millis(); // 100ms task @@ -1166,6 +1372,7 @@ void loop() // --------> blinkCodeTask(); serialInputTask(); + oledDisplay.renderScreen(timeClient.getFormattedTime(), String(VERSION)); } // CHANGE to precise 1 second timer increment @@ -1178,8 +1385,27 @@ void loop() // Serial.print("local: " + getTimeStringByTimestamp(timeStampInSecondsDtuSynced)); // Serial.print(" --- NTP: " + timeClient.getFormattedTime() + " --- currentMillis " + String(currentMillis) + " --- "); previousMillisShort = currentMillis; + // Serial.print(F("free mem: ")); + // Serial.print(ESP.getFreeHeap()); + // Serial.print(F(" - heap fragm: ")); + // Serial.print(ESP.getHeapFragmentation()); + // Serial.print(F(" - max free block size: ")); + // Serial.print(ESP.getMaxFreeBlockSize()); + // Serial.print(F(" - free cont stack: ")); + // Serial.print(ESP.getFreeContStack()); + // Serial.print(F(" \n")); + // --------> + if (globalControls.wifiSwitch && !userConfig.wifiAPstart) + checkWifiTask(); + else + { + // stopping connection to DTU before go wifi offline + dtuConnectionStop(&dtuClient, DTU_STATE_OFFLINE); + WiFi.disconnect(); + } + if (dtuConnection.preventCloudErrors) { // task to check and change for cloud update pause @@ -1191,31 +1417,28 @@ void loop() } } - if (globalControls.wifiSwitch && !userConfig.wifiAPstart) - checkWifiTask(); - else - { - // stopping connection to DTU before go wifi offline - dtuConnectionStop(&dtuClient, DTU_STATE_OFFLINE); - WiFi.disconnect(); - } - // direct request of new powerLimit if (globalData.powerLimitSet != globalData.powerLimit && globalData.powerLimitSet != 101 && globalData.uptodate) { Serial.print("\n----- ----- set new power limit from " + String(globalData.powerLimit) + " to " + String(globalData.powerLimitSet)); if (writeReqCommand(&dtuClient, globalData.powerLimitSet, timeStampInSecondsDtuSynced)) { - Serial.print(" --- done"); + Serial.println(F(" --- done")); // set next normal request in 5 seconds from now on, only if last data updated within last 2 times of user setted update rate if (currentMillis - globalData.lastRespTimestamp < (intervalMid * 2)) previousMillisMid = currentMillis - (intervalMid - 5); } else { - Serial.print(" --- error"); + Serial.println(F(" --- error")); } } + + // + if (updateInfoRequested) + { + getUpdateInfo(); + } } // 5s task @@ -1224,20 +1447,22 @@ void loop() Serial.printf("\n>>>>> %02is task - state --> ", int(interval5000ms)); Serial.print("local: " + getTimeStringByTimestamp(timeStampInSecondsDtuSynced)); Serial.print(" --- NTP: " + timeClient.getFormattedTime()); + // Serial.print(" --- currentMillis " + String(currentMillis) + " --- "); previousMillis5000ms = currentMillis; // --------> + // ----------------------------------------- if (WiFi.status() == WL_CONNECTED) { - timeClient.update(); // get current RSSI to AP int wifiPercent = 2 * (WiFi.RSSI() + 100); if (wifiPercent > 100) wifiPercent = 100; globalData.wifi_rssi_gateway = wifiPercent; - Serial.print(" --- RSSI to AP: '" + String(WiFi.SSID()) + "': " + String(globalData.wifi_rssi_gateway) + " %"); + // Serial.print(" --- RSSI to AP: '" + String(WiFi.SSID()) + "': " + String(globalData.wifi_rssi_gateway) + " %"); - getPowerSetDataFromOpenHab(); + if (userConfig.openhabActive) + getPowerSetDataFromOpenHab(); } } @@ -1247,8 +1472,10 @@ void loop() Serial.printf("\n>>>>> %02is task - state --> ", int(intervalMid)); Serial.print("local: " + getTimeStringByTimestamp(timeStampInSecondsDtuSynced)); Serial.print(" --- NTP: " + timeClient.getFormattedTime() + "\n"); + previousMillisMid = currentMillis; // --------> + if (WiFi.status() == WL_CONNECTED) { dtuConnectionEstablish(&dtuClient, userConfig.dtuHostIp); @@ -1258,7 +1485,11 @@ void loop() { if ((globalControls.getDataAuto || globalControls.getDataOnce) && globalData.uptodate) { - updateValueToOpenhab(); + if (userConfig.openhabActive) + updateValueToOpenhab(); + if (userConfig.mqttActive) + updateValuesToMqtt(); + if (globalControls.dataFormatJSON) { printDataAsJsonToSerial(); @@ -1286,7 +1517,10 @@ void loop() globalData.pv1.current = 0; globalData.pv1.voltage = 0; - updateValueToOpenhab(); + if (userConfig.openhabActive) + updateValueToOpenhab(); + if (userConfig.mqttActive) + updateValuesToMqtt(); dtuConnection.dtuErrorState = DTU_ERROR_LAST_SEND; Serial.print(F("\n>>>>> TIMEOUT 5 min for DTU -> NIGHT - send zero values\n")); } @@ -1303,8 +1537,13 @@ void loop() previousMillisLong = currentMillis; // --------> + if (WiFi.status() == WL_CONNECTED) + { + timeClient.update(); + } // start async scan for wifi'S - // WiFi.scanNetworksAsync(scanNetworksResult); - scanNetworksResult(WiFi.scanNetworks()); + Serial.print(F("\nstart scan for wifi's\n")); + WiFi.scanNetworks(true); } + scanNetworksResult(); } \ No newline at end of file diff --git a/src/dtuInterface.cpp b/src/dtuInterface.cpp index 31316b0..b87c07b 100644 --- a/src/dtuInterface.cpp +++ b/src/dtuInterface.cpp @@ -13,12 +13,13 @@ void dtuConnectionEstablish(WiFiClient *localDtuClient, char localDtuHostIp[16], { if (!localDtuClient->connected() && !dtuConnection.dtuActiveOffToCloudUpdate) { - localDtuClient->setTimeout(5000); + localDtuClient->setTimeout(1500); Serial.print("\n>>> Client not connected with DTU! - trying to connect to " + String(localDtuHostIp) + " ... "); if (!localDtuClient->connect(localDtuHostIp, localDtuPort)) { Serial.print(F("Connection to DTU failed. Setting try to reconnect.\n")); dtuConnection.dtuConnectState = DTU_STATE_TRY_RECONNECT; + globalData.dtuRssi = 0; } else { @@ -35,6 +36,7 @@ void dtuConnectionStop(WiFiClient *localDtuClient, uint8_t tgtState) { localDtuClient->stop(); dtuConnection.dtuConnectState = tgtState; + globalData.dtuRssi = 0; Serial.print(F("+++ DTU Connection --- stopped\n")); } } diff --git a/version_inc.py b/version_inc.py index c6d3381..078d811 100644 --- a/version_inc.py +++ b/version_inc.py @@ -34,7 +34,8 @@ print(f"got buildnumber to use: ->{build_string}<-") except FileNotFoundError: print('ERROR: buildnumber file not found. For automatic versioning there is a file called ../include/buildnumber.txt expected. With the content "localDev" or versionnumber e.g. "1.0.0" in first line. (File is blocked by .gitignore for GitHub actions to run.)') - + if "_" in version_string: + version_string = version_string.split('_')[0] major, minor, patch = map(int, version_string.split('.')) if build_string != "localDev": @@ -43,7 +44,7 @@ else: # Increment to the new version string # patch += 1 - version_string_new = f"{major}.{minor}.{patch}" + version_string_new = f"{major}.{minor}.{patch}_{build_string}" print(f"set new version: {version_string_new}")