From 93057fc3dc3e8c6ff2831e1dc40cff71f36863a4 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Wed, 21 Jun 2023 22:44:18 +0200 Subject: [PATCH 1/7] Feature: Added touch icon for iOS and Android Implements #1060 --- platformio.ini | 2 +- src/WebApi_webapp.cpp | 7 +++++++ webapp/index.html | 2 ++ webapp/public/favicon.png | Bin 0 -> 682 bytes webapp_dist/favicon.png | Bin 0 -> 682 bytes webapp_dist/index.html.gz | Bin 329 -> 363 bytes 6 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 webapp/public/favicon.png create mode 100644 webapp_dist/favicon.png diff --git a/platformio.ini b/platformio.ini index bdc0e9370..76b9188f2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ framework = arduino platform = espressif32@6.3.1 build_flags = - -DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/js/app.js.gz + -DCOMPONENT_EMBED_FILES=webapp_dist/index.html.gz:webapp_dist/zones.json.gz:webapp_dist/favicon.ico:webapp_dist/favicon.png:webapp_dist/js/app.js.gz -DPIOENV=\"$PIOENV\" -Wall -Wextra -Werror -std=c++17 diff --git a/src/WebApi_webapp.cpp b/src/WebApi_webapp.cpp index 4976f78e2..fd42da5c1 100644 --- a/src/WebApi_webapp.cpp +++ b/src/WebApi_webapp.cpp @@ -6,11 +6,13 @@ extern const uint8_t file_index_html_start[] asm("_binary_webapp_dist_index_html_gz_start"); extern const uint8_t file_favicon_ico_start[] asm("_binary_webapp_dist_favicon_ico_start"); +extern const uint8_t file_favicon_png_start[] asm("_binary_webapp_dist_favicon_png_start"); extern const uint8_t file_zones_json_start[] asm("_binary_webapp_dist_zones_json_gz_start"); extern const uint8_t file_app_js_start[] asm("_binary_webapp_dist_js_app_js_gz_start"); extern const uint8_t file_index_html_end[] asm("_binary_webapp_dist_index_html_gz_end"); extern const uint8_t file_favicon_ico_end[] asm("_binary_webapp_dist_favicon_ico_end"); +extern const uint8_t file_favicon_png_end[] asm("_binary_webapp_dist_favicon_png_end"); extern const uint8_t file_zones_json_end[] asm("_binary_webapp_dist_zones_json_gz_end"); extern const uint8_t file_app_js_end[] asm("_binary_webapp_dist_js_app_js_gz_end"); @@ -41,6 +43,11 @@ void WebApiWebappClass::init(AsyncWebServer* server) request->send(response); }); + _server->on("/favicon.png", HTTP_GET, [](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse_P(200, "image/png", file_favicon_png_start, file_favicon_png_end - file_favicon_png_start); + request->send(response); + }); + _server->on("/zones.json", HTTP_GET, [](AsyncWebServerRequest* request) { AsyncWebServerResponse* response = request->beginResponse_P(200, "application/json", file_zones_json_start, file_zones_json_end - file_zones_json_start); response->addHeader("Content-Encoding", "gzip"); diff --git a/webapp/index.html b/webapp/index.html index 36236f188..39a94a039 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -3,6 +3,8 @@ + + OpenDTU diff --git a/webapp/public/favicon.png b/webapp/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3378b661323215355b212542a9fe59b610adeae1 GIT binary patch literal 682 zcmV;b0#*HqP)Yc1Y}7*P?l{03AtJT4RzxI-h<_!=egl{XkU>KF z^h5+}t&1^w5rN)AMB!rv0PxKGeY@RoyG zB!GHK6>3jNMf-P0;F;Mm^JDI7qN*y__q~fT&XRFW0io-jEVgNPu;m^l(wtDw;O zQztS30W=ed*X$XPl;&jiTcKrYboHmx>DR#lAmR8Y4jc}LvP!6yLbZ16{@hxlom1oN z3$uA*y0vzYDaIH8fSFzXP2S&i-C!+8BJrqenlbVv={RO~#u(^J7n>86N%Qx7rGl~~ zA0rnLpELUN`K&#@0nq5A??lSeq`Uxtgy$b4*zI-$nE*iF_miZ&-|s(dG-l8Ny;kPT z{K$CwF(b+2C|Wj%%jL3l&8IPEg4f7f*b#+JopLRD2Yrf(z?q8wkc>e!?@P~)W-U35ZvO;%Mm>D);IdSt?j#+LNBR2t(lcCg zx==iHwypz!%N!zWt$a%FFLC29!5>!qrv0I!+Z<&kMWS9bd_m}EwaZ%1oM=&P!7MD%nnDD5^s8I#}p|6T?E0Pw3|@?Jmc Qz5oCK07*qoM6N<$f~lH1Y5)KL literal 0 HcmV?d00001 diff --git a/webapp_dist/favicon.png b/webapp_dist/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3378b661323215355b212542a9fe59b610adeae1 GIT binary patch literal 682 zcmV;b0#*HqP)Yc1Y}7*P?l{03AtJT4RzxI-h<_!=egl{XkU>KF z^h5+}t&1^w5rN)AMB!rv0PxKGeY@RoyG zB!GHK6>3jNMf-P0;F;Mm^JDI7qN*y__q~fT&XRFW0io-jEVgNPu;m^l(wtDw;O zQztS30W=ed*X$XPl;&jiTcKrYboHmx>DR#lAmR8Y4jc}LvP!6yLbZ16{@hxlom1oN z3$uA*y0vzYDaIH8fSFzXP2S&i-C!+8BJrqenlbVv={RO~#u(^J7n>86N%Qx7rGl~~ zA0rnLpELUN`K&#@0nq5A??lSeq`Uxtgy$b4*zI-$nE*iF_miZ&-|s(dG-l8Ny;kPT z{K$CwF(b+2C|Wj%%jL3l&8IPEg4f7f*b#+JopLRD2Yrf(z?q8wkc>e!?@P~)W-U35ZvO;%Mm>D);IdSt?j#+LNBR2t(lcCg zx==iHwypz!%N!zWt$a%FFLC29!5>!qrv0I!+Z<&kMWS9bd_m}EwaZ%1oM=&P!7MD%nnDD5^s8I#}p|6T?E0Pw3|@?Jmc Qz5oCK07*qoM6N<$f~lH1Y5)KL literal 0 HcmV?d00001 diff --git a/webapp_dist/index.html.gz b/webapp_dist/index.html.gz index d0a8c5248d5b683e445218e217e429462811da31..cc2cb3815560274c68bf49a7d9a0278102d8ad22 100644 GIT binary patch literal 363 zcmV-x0hIn9iwFP!000023T;w7Z-X!p-SaE1Ua}Nuw+_J4v_qFfYND#6gD>F3*p_WT z{QDl5s;QC%``LHz<4pdr*}m?-cW=;yR+X!KT0n7K7YsEkR{(i~Tud9zA;7}DzRz@>u9G(i6op?Xtls@-rePdjxx&W~$j24NYI|qRpr%NJ6rm{uQX3{Scm%0skX$9c;tGrB z^yx1~kU?R&wWv4yL!Lz#uYBdC4NIQZh)yBtIODx>vX&aWtH|NmXXHLTdsgOI?1==^ zL*+r zBp6&cgAdMQFe40 Date: Thu, 22 Jun 2023 10:11:40 +0200 Subject: [PATCH 2/7] =?UTF-8?q?Ajout=20de=20traductions=20en=20Fran=C3=A7a?= =?UTF-8?q?is?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- webapp/src/locales/fr.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 86b517f50..9e3807e99 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -27,8 +27,8 @@ "Reload": "Reload" }, "localeswitcher": { - "Dark": "Dark", - "Light": "Light", + "Dark": "Sombre", + "Light": "Clair", "Auto": "Auto" }, "apiresponse": { @@ -190,13 +190,13 @@ }, "radioinfo": { "RadioInformation": "Informations sur la radio", - "Status": "{module} Status", - "ChipStatus": "{module} sÉtat de la puce", + "Status": "{module} Statut", + "ChipStatus": "{module} État de la puce", "ChipType": "{module} Type de puce", "Connected": "connectée", "NotConnected": "non connectée", - "Configured": "configured", - "NotConfigured": "not configured", + "Configured": "configurée", + "NotConfigured": "non configurée", "Unknown": "Inconnue" }, "networkinfo": { @@ -245,12 +245,12 @@ "Synced": "synchronisée", "NotSynced": "pas synchronisée", "LocalTime": "Heure locale", - "Sunrise": "Sunrise", - "Sunset": "Sunset", + "Sunrise": "Lever du soleil", + "Sunset": "Coucher du soleil", "NotAvailable": "Not Available", "Mode": "Mode", - "Day": "Day", - "Night": "Night" + "Day": "Jour", + "Night": "Nuit" }, "mqttinfo": { "MqttInformation": "MQTT Information", @@ -448,7 +448,7 @@ "InverterSerial": "Numéro de série de l'onduleur", "InverterName": "Nom de l'onduleur :", "InverterNameHint": "Ici, vous pouvez spécifier un nom personnalisé pour votre onduleur.", - "InverterStatus": "Receive / Send", + "InverterStatus": "Recevoir / Envoyer", "PollEnable": "Interroger les données de l'onduleur", "PollEnableNight": "Interroger les données de l'onduleur la nuit", "CommandEnable": "Envoyer des commandes", @@ -538,10 +538,10 @@ "ScreensaverHint": "Déplacez un peu l'écran à chaque mise à jour pour éviter le phénomène de brûlure. (Utile surtout pour les écrans OLED)", "Contrast": "Contraste ({contrast}):", "Rotation": "Rotation:", - "rot0": "No rotation", - "rot90": "90 degree rotation", - "rot180": "180 degree rotation", - "rot270": "270 degree rotation", + "rot0": "Pas de rotation", + "rot90": "Rotation de 90 degrés", + "rot180": "Rotation de 180 degrés", + "rot270": "Rotation de 270 degrés", "DisplayLanguage": "Langue d'affichage", "en": "Anglais", "de": "Allemand", From d8a3a75c0bbca37976fed69b1a14bfdad3a62f3c Mon Sep 17 00:00:00 2001 From: Gregor Wolf Date: Mon, 26 Jun 2023 07:39:18 +0200 Subject: [PATCH 3/7] Device Profile for Wemos Lolin32 OLED --- docs/DeviceProfiles/wemos-lolin32-oled.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/DeviceProfiles/wemos-lolin32-oled.json diff --git a/docs/DeviceProfiles/wemos-lolin32-oled.json b/docs/DeviceProfiles/wemos-lolin32-oled.json new file mode 100644 index 000000000..cbfa6eaa1 --- /dev/null +++ b/docs/DeviceProfiles/wemos-lolin32-oled.json @@ -0,0 +1,21 @@ +[ + { + "name": "Wemos Lolin32 OLED", + "nrf24": { + "miso": 2, + "mosi": 14, + "clk": 12, + "irq": 0, + "en": 15, + "cs": 13 + }, + "eth": { + "enabled": false + }, + "display": { + "type": 2, + "data": 5, + "clk": 4 + } + } +] From 6955233371d637d9b299fef8c1660c0bb4f63c8c Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 26 Jun 2023 20:42:43 +0200 Subject: [PATCH 4/7] webapp: Update dependencies --- webapp/package.json | 6 ++-- webapp/yarn.lock | 80 ++++++++++++++++++++++----------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 590181fbc..15f262050 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -33,14 +33,14 @@ "@vue/eslint-config-typescript": "^11.0.3", "@vue/tsconfig": "^0.4.0", "eslint": "^8.43.0", - "eslint-plugin-vue": "^9.15.0", + "eslint-plugin-vue": "^9.15.1", "npm-run-all": "^4.1.5", - "sass": "^1.63.5", + "sass": "^1.63.6", "terser": "^5.18.1", "typescript": "^5.1.3", "vite": "^4.3.9", "vite-plugin-compression": "^0.5.1", "vite-plugin-css-injected-by-js": "^3.1.1", - "vue-tsc": "^1.8.1" + "vue-tsc": "^1.8.2" } } diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 8f82522d5..9f54d646f 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -481,26 +481,26 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz#ee0b6dfcc62fe65364e6395bf38fa2ba10bb44b6" integrity sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw== -"@volar/language-core@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.7.8.tgz#88e33fda749ecb1ecb28c031027d45e778b8cd5f" - integrity sha512-TPklg4c2e/f1xB/MGZEiQc3AWG+dH64ZfBlYjFB8nNaWJt4Z4k+IHBhmaP52APG+5PHFerwiWI9oF002RrRTPA== +"@volar/language-core@1.7.9": + version "1.7.9" + resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-1.7.9.tgz#e9ca92fcbffa88136394c692454036548d97ea5a" + integrity sha512-U6GMPDNqfGFqVRv4npUN2hEDW4/6EwL4YHd6qggapcvTzRrYAodTTbOTZs4PDzmw7NSZ2Cdrmd54SjzCCMXbZw== dependencies: - "@volar/source-map" "1.7.8" + "@volar/source-map" "1.7.9" -"@volar/source-map@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.7.8.tgz#46710f1d1e948e8009993595b70670e3ee4bfc1d" - integrity sha512-g2dtC2kOghvfzMDWeODIo4HO1Ml4hxzPTZyAFDz+YhRF9HjZYJSCaWaVuPZ+z0kY+T2daOHYA10GdrWQ5q0teA== +"@volar/source-map@1.7.9": + version "1.7.9" + resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-1.7.9.tgz#a9588fce0a989f320bcd1ab37e31948a6a57f5cc" + integrity sha512-bLizh8HIAzbq7OdxfyoG18dXJJF9FNXBcaiRj7eqg2Bq+DkgkYHabaY+xobgaXeKFOp93Tg1KfMM7qyR2KXHmQ== dependencies: muggle-string "^0.3.1" -"@volar/typescript@1.7.8": - version "1.7.8" - resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.7.8.tgz#6997b3c7637292a6dc6e4a3737e45f3c4e49ef12" - integrity sha512-NDcI5ZQcdr8kgxzMQrhSSWIM8Tl0MbMFrkvJPTjfm2rdAQZPFT8zv3LrEW9Fqh0e9z2YbCry7jr4a/GShBqeDA== +"@volar/typescript@1.7.9": + version "1.7.9" + resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-1.7.9.tgz#28f3597a391a90036c9e90c770654cd45201f572" + integrity sha512-cXGg7lgvdjpRjYfz52cXKo6ExBi8k3pWeBe6Gckf4+9zmTfEwEFfKWMj0H/IyUYO+S2rjyE9jytdsu1Imk+Azw== dependencies: - "@volar/language-core" "1.7.8" + "@volar/language-core" "1.7.9" "@vue/compiler-core@3.2.47": version "3.2.47" @@ -623,13 +623,13 @@ "@typescript-eslint/parser" "^5.59.1" vue-eslint-parser "^9.1.1" -"@vue/language-core@1.8.1": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.1.tgz#86b709f803bcc0cab84fd14677c0fb909154c562" - integrity sha512-pumv3k4J7P58hVh4YGRM9Qz3HaAr4TlFWM9bnVOkZ/2K9o2CK1lAP2y9Jw+Z0+mNL4F2uWQqnAPzj3seLyfpDA== +"@vue/language-core@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-1.8.2.tgz#560c1df639394a5df0043f2d7989dc2fba5bf4ca" + integrity sha512-QJujhmp89TRoWwzjn2sPMezG97+mNyaCTfznGHWNCE3LBsillZCBqAO7M7cxO8ee1V3r+qHjWytkoh3M4YkRJw== dependencies: - "@volar/language-core" "1.7.8" - "@volar/source-map" "1.7.8" + "@volar/language-core" "1.7.9" + "@volar/source-map" "1.7.9" "@vue/compiler-dom" "^3.3.0" "@vue/reactivity" "^3.3.0" "@vue/shared" "^3.3.0" @@ -718,13 +718,13 @@ resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.4.0.tgz#f01e2f6089b5098136fb084a0dd0cdd4533b72b0" integrity sha512-CPuIReonid9+zOG/CGTT05FXrPYATEqoDGNrEaqS4hwcw5BUNM2FguC0mOwJD4Jr16UpRVl9N0pY3P+srIbqmg== -"@vue/typescript@1.8.1": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@vue/typescript/-/typescript-1.8.1.tgz#2302f3cc8330e8dd91982eecd64d71ee11198cb0" - integrity sha512-nQpo55j/roie8heCfqyXHnyayqD5+p4/0fzfxH4ZuHf7NSBQS791PNv7ztp2CCOjnGAiaiCMdtC9rc6oriyPUg== +"@vue/typescript@1.8.2": + version "1.8.2" + resolved "https://registry.yarnpkg.com/@vue/typescript/-/typescript-1.8.2.tgz#d81e2c16197e8fcf032fe766d79952cb9a629af7" + integrity sha512-5q2gpCBIfGlygfJupyjAQbc82r5J6qQuhupPeX3NlHvDK+mR6m5n4jVGknSacsp+7gtgs4RKYDq+tysoto+npA== dependencies: - "@volar/typescript" "1.7.8" - "@vue/language-core" "1.8.1" + "@volar/typescript" "1.7.9" + "@vue/language-core" "1.8.2" acorn-jsx@^5.2.0, acorn-jsx@^5.3.2: version "5.3.2" @@ -1089,10 +1089,10 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-plugin-vue@^9.15.0: - version "9.15.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.15.0.tgz#2bffe2b8a628ee438f983672a73cd89df455c461" - integrity sha512-XYzpK6e2REli100+6iCeBA69v6Sm0D/yK2FZP+fCeNt0yH/m82qZQq+ztseyV0JsKdhFysuSEzeE1yCmSC92BA== +eslint-plugin-vue@^9.15.1: + version "9.15.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz#3c09e0edab444b5d4d9239a12a645a0e2e2ea5be" + integrity sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A== dependencies: "@eslint-community/eslint-utils" "^4.3.0" natural-compare "^1.4.0" @@ -2184,10 +2184,10 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -sass@^1.63.5: - version "1.63.5" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.5.tgz#6e1900b12576e3e74a8ab0a9d8607cacbe584ef1" - integrity sha512-Q6c5gs482oezdAp+0fWF9cRisvpy7yfYb64knID0OE8AnMgtkluRPfpGMFjeD4/+M4+6QpJZCU6JRSxbjiktkg== +sass@^1.63.6: + version "1.63.6" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.6.tgz#481610e612902e0c31c46b46cf2dad66943283ea" + integrity sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -2566,13 +2566,13 @@ vue-template-compiler@^2.7.14: de-indent "^1.0.2" he "^1.2.0" -vue-tsc@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.1.tgz#cdbc83ad7d1c80eac82e01917a58bc57e2debed2" - integrity sha512-GxBQrcb0Qvyrj1uZqnTXQyWbXdNDRY2MTa+r7ESgjhf+WzBSdxZfkS3KD/C3WhKYG+aN8hf44Hp5Gqzb6PehAA== +vue-tsc@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-1.8.2.tgz#dd9b0edfed10b590a82f964969e12d7733a282c4" + integrity sha512-iLS+z7jzxEAZRGLo4bYWfzZeBNVA71uXKmT2+5bQSsOJBtGLniR45sVpR+X7sa0m3J8iMEIU5+phLB7FnMLS0A== dependencies: - "@vue/language-core" "1.8.1" - "@vue/typescript" "1.8.1" + "@vue/language-core" "1.8.2" + "@vue/typescript" "1.8.2" semver "^7.3.8" vue@^3.3.4: From 12a18fb34b6aea58c12c802f16e91aab7023f52e Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Mon, 26 Jun 2023 20:44:30 +0200 Subject: [PATCH 5/7] webapp: add app.js.gz --- webapp_dist/js/app.js.gz | Bin 168460 -> 168482 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz index 05777db247e9781591cbe0d113cfd61777e7e272..d5965f1ed7cd9f4c115f76ad0e57c5fd21f9120c 100644 GIT binary patch delta 8280 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 W`#o%vYny-kr~d_gdb3R;M+*S2Ef!b+ delta 8267 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 JTKf1#3jm(p1uFmm From 69aa247470acf0a46db97088bd3745086a39c222 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Tue, 27 Jun 2023 18:59:54 +0200 Subject: [PATCH 6/7] Added check in statistics data that at least all required bytes are received It can occour for some inverters that incomplete fragments with valid crc but less bytes are received This was mentioned in #1084 --- .../src/commands/MultiDataCommand.cpp | 11 ++++++++++- lib/Hoymiles/src/commands/MultiDataCommand.h | 1 + .../src/commands/RealTimeRunDataCommand.cpp | 13 +++++++++++++ lib/Hoymiles/src/parser/StatisticsParser.cpp | 19 +++++++++++++++++++ lib/Hoymiles/src/parser/StatisticsParser.h | 3 +++ 5 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.cpp b/lib/Hoymiles/src/commands/MultiDataCommand.cpp index b25c9027d..39a0d4c64 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.cpp +++ b/lib/Hoymiles/src/commands/MultiDataCommand.cpp @@ -91,4 +91,13 @@ void MultiDataCommand::udpateCRC() uint16_t crc = crc16(&_payload[10], 14); // From data_type till password _payload[24] = (uint8_t)(crc >> 8); _payload[25] = (uint8_t)(crc); -} \ No newline at end of file +} + +uint8_t MultiDataCommand::getTotalFragmentSize(fragment_t fragment[], uint8_t max_fragment_id) +{ + uint8_t fragmentSize = 0; + for (uint8_t i = 0; i < max_fragment_id; i++) { + fragmentSize += fragment[i].len; + } + return fragmentSize; +} diff --git a/lib/Hoymiles/src/commands/MultiDataCommand.h b/lib/Hoymiles/src/commands/MultiDataCommand.h index ff835d7b9..4d2adfde4 100644 --- a/lib/Hoymiles/src/commands/MultiDataCommand.h +++ b/lib/Hoymiles/src/commands/MultiDataCommand.h @@ -20,6 +20,7 @@ class MultiDataCommand : public CommandAbstract { void setDataType(uint8_t data_type); uint8_t getDataType(); void udpateCRC(); + static uint8_t getTotalFragmentSize(fragment_t fragment[], uint8_t max_fragment_id); RequestFrameCommand _cmdRequestFrame; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index 6a7db92a9..646e363fe 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "RealTimeRunDataCommand.h" +#include "Hoymiles.h" #include "inverters/InverterAbstract.h" RealTimeRunDataCommand::RealTimeRunDataCommand(uint64_t target_address, uint64_t router_address, time_t time) @@ -25,6 +26,18 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment return false; } + // Check if at least all required bytes are received + // In case of low power in the inverter it occours that some incomplete fragments + // with a valid CRC are received. + if (getTotalFragmentSize(fragment, max_fragment_id) < inverter->Statistics()->getMaxByteCount()) { + Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d min. expected size: %d\r\n", + getCommandName().c_str(), + getTotalFragmentSize(fragment, max_fragment_id), + inverter->Statistics()->getMaxByteCount()); + + return false; + } + // Move all fragments into target buffer uint8_t offs = 0; inverter->Statistics()->clearBuffer(); diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index 0575a5fbb..c0e0b8ba5 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -34,6 +34,25 @@ void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, uin _byteAssignmentSize = size; } +uint8_t StatisticsParser::getMaxByteCount() +{ + static uint8_t maxByteCount = 0; + + // Use already calculated value + if (maxByteCount > 0) { + return maxByteCount; + } + + for (uint8_t i = 0; i < _byteAssignmentSize; i++) { + if (_byteAssignment[i].div == CMD_CALC) { + continue; + } + maxByteCount = max(maxByteCount, _byteAssignment[i].start + _byteAssignment[i].num); + } + + return maxByteCount; +} + void StatisticsParser::clearBuffer() { memset(_payloadStatistic, 0, STATISTIC_PACKET_SIZE); diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index f4bf7ae02..9f71045a3 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -109,6 +109,9 @@ class StatisticsParser : public Parser { void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); + // Returns 1 based amount of expected bytes of statistic data + uint8_t getMaxByteCount(); + const byteAssign_t* getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); fieldSettings_t* getSettingByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); From a510afe53e5b91b7eb88835106b4a125649f1326 Mon Sep 17 00:00:00 2001 From: Thomas Basler Date: Tue, 27 Jun 2023 19:01:43 +0200 Subject: [PATCH 7/7] webapp: Update dependencies --- webapp/package.json | 4 ++-- webapp/yarn.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/webapp/package.json b/webapp/package.json index 15f262050..23660a816 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -26,7 +26,7 @@ "@rushstack/eslint-patch": "^1.3.2", "@tsconfig/node18": "^2.0.1", "@types/bootstrap": "^5.2.6", - "@types/node": "^20.3.1", + "@types/node": "^20.3.2", "@types/sortablejs": "^1.15.1", "@types/spark-md5": "^3.0.2", "@vitejs/plugin-vue": "^4.2.3", @@ -36,7 +36,7 @@ "eslint-plugin-vue": "^9.15.1", "npm-run-all": "^4.1.5", "sass": "^1.63.6", - "terser": "^5.18.1", + "terser": "^5.18.2", "typescript": "^5.1.3", "vite": "^4.3.9", "vite-plugin-compression": "^0.5.1", diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 9f54d646f..330d42667 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -372,10 +372,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/node@^20.3.1": - version "20.3.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.1.tgz#e8a83f1aa8b649377bb1fb5d7bac5cb90e784dfe" - integrity sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg== +"@types/node@^20.3.2": + version "20.3.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.2.tgz#fa6a90f2600e052a03c18b8cb3fd83dd4e599898" + integrity sha512-vOBLVQeCQfIcF/2Y7eKFTqrMnizK5lRNQ7ykML/5RuwVXVWxYkgwS7xbt4B6fKCUPgbSL5FSsjHQpaGQP/dQmw== "@types/semver@^7.3.12": version "7.3.13" @@ -2382,10 +2382,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -terser@^5.18.1: - version "5.18.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.1.tgz#6d8642508ae9fb7b48768e48f16d675c89a78460" - integrity sha512-j1n0Ao919h/Ai5r43VAnfV/7azUYW43GPxK7qSATzrsERfW7+y2QW9Cp9ufnRF5CQUWbnLSo7UJokSWCqg4tsQ== +terser@^5.18.2: + version "5.18.2" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.2.tgz#ff3072a0faf21ffd38f99acc9a0ddf7b5f07b948" + integrity sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2"