Browse Source

0.8.47

* started to have german translations of all variants (environments) #925 #1199
pull/1330/head
lumapu 1 year ago
parent
commit
b765916a2a
  1. 23
      .github/workflows/compile_development.yml
  2. 39
      scripts/convertHtml.py
  3. 54
      scripts/getVersion.py
  4. 1
      src/CHANGES.md
  5. 4
      src/config/config.h
  6. 144
      src/platformio.ini
  7. 2
      src/web/html/includes/header.html
  8. 10
      src/web/html/includes/nav.html
  9. 53
      src/web/html/index.html
  10. 8
      src/web/html/save.html
  11. 288
      src/web/html/setup.html
  12. 36
      src/web/html/system.html
  13. 8
      src/web/html/update.html
  14. 136
      src/web/html/visualization.html
  15. 28
      src/web/html/wizard.html
  16. 1334
      src/web/lang.json

23
.github/workflows/compile_development.yml

@ -43,7 +43,28 @@ jobs:
pip install --upgrade platformio pip install --upgrade platformio
- name: Run PlatformIO - name: Run PlatformIO
run: pio run -d src --environment esp8266 --environment esp8266-prometheus --environment esp8285 --environment esp32-wroom32 --environment esp32-wroom32-prometheus --environment esp32-wroom32-ethernet --environment esp32-s2-mini --environment esp32-c3-mini --environment opendtufusion --environment opendtufusion-ethernet run: >
pio run -d src
--environment esp8266
--environment esp8266-prometheus
--environment esp8285
--environment esp32-wroom32
--environment esp32-wroom32-prometheus
--environment esp32-wroom32-ethernet
--environment esp32-s2-mini
--environment esp32-c3-mini
--environment opendtufusion
--environment opendtufusion-ethernet
--environment esp8266-de
--environment esp8266-prometheus-de
--environment esp8285-de
--environment esp32-wroom32-de
--environment esp32-wroom32-prometheus-de
--environment esp32-wroom32-ethernet-de
--environment esp32-s2-mini-de
--environment esp32-c3-mini-de
--environment opendtufusion-de
--environment opendtufusion-ethernet-de
- name: Copy boot_app0.bin - name: Copy boot_app0.bin
run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin run: cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin src/.pio/build/opendtufusion/ota.bin

39
scripts/convertHtml.py

@ -3,6 +3,7 @@ import os
import gzip import gzip
import glob import glob
import shutil import shutil
import json
from datetime import date from datetime import date
from pathlib import Path from pathlib import Path
import subprocess import subprocess
@ -33,7 +34,7 @@ def readVersion(path):
ver += line[p+13:].rstrip() + "." ver += line[p+13:].rstrip() + "."
return ver[:-1] return ver[:-1]
def htmlParts(file, header, nav, footer, version): def htmlParts(file, header, nav, footer, version, lang):
p = ""; p = "";
f = open(file, "r") f = open(file, "r")
lines = f.readlines() lines = f.readlines()
@ -64,6 +65,8 @@ def htmlParts(file, header, nav, footer, version):
# remove if - endif ESP32 # remove if - endif ESP32
p = checkIf(p) p = checkIf(p)
p = translate(file, p, lang)
p = translate("general", p, lang) # menu / header / footer
f = open("tmp/" + file, "w") f = open("tmp/" + file, "w")
f.write(p); f.write(p);
@ -94,7 +97,30 @@ def checkIf(data):
return data return data
def convert2Header(inFile, version): def findLang(file):
with open('../lang.json') as j:
lang = json.load(j)
for l in lang["files"]:
if l["name"] == file:
return l
return None
def translate(file, data, lang="de"):
json = findLang(file)
if None != json:
matches = re.findall(r'\{\#([A-Z0-9_]+)\}', data)
for x in matches:
for e in json["list"]:
if x == e["token"]:
#print("replace " + "{#" + x + "}" + " with " + e[lang])
data = data.replace("{#" + x + "}", e[lang])
return data
def convert2Header(inFile, version, lang):
fileType = inFile.split(".")[1] fileType = inFile.split(".")[1]
define = inFile.split(".")[0].upper() define = inFile.split(".")[0].upper()
define2 = inFile.split(".")[1].upper() define2 = inFile.split(".")[1].upper()
@ -114,7 +140,7 @@ def convert2Header(inFile, version):
f.close() f.close()
else: else:
if fileType == "html": if fileType == "html":
data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version) data = htmlParts(inFile, "includes/header.html", "includes/nav.html", "includes/footer.html", version, lang)
else: else:
f = open(inFile, "r") f = open(inFile, "r")
data = f.read() data = f.read()
@ -169,6 +195,11 @@ Path("tmp").mkdir(exist_ok=True) # created to check if webpages are valid with a
shutil.copyfile("style.css", "tmp/style.css") shutil.copyfile("style.css", "tmp/style.css")
version = readVersion("../../defines.h") version = readVersion("../../defines.h")
# get language from environment
lang = "en"
if env['PIOENV'][-3:] == "-de":
lang = "de"
# go throw the array # go throw the array
for val in files_grabbed: for val in files_grabbed:
convert2Header(val, version) convert2Header(val, version, lang)

54
scripts/getVersion.py

@ -59,6 +59,7 @@ def readVersion(path, infile):
os.mkdir(path + "firmware/ESP32-S3-ETH/") os.mkdir(path + "firmware/ESP32-S3-ETH/")
sha = os.getenv("SHA",default="sha") sha = os.getenv("SHA",default="sha")
## ENGLISH VERSIONS
versionout = version[:-1] + "_" + sha + "_esp8266.bin" versionout = version[:-1] + "_" + sha + "_esp8266.bin"
src = path + ".pio/build/esp8266/firmware.bin" src = path + ".pio/build/esp8266/firmware.bin"
dst = path + "firmware/ESP8266/" + versionout dst = path + "firmware/ESP8266/" + versionout
@ -110,6 +111,59 @@ def readVersion(path, infile):
dst = path + "firmware/ESP32-S3-ETH/" + versionout dst = path + "firmware/ESP32-S3-ETH/" + versionout
os.rename(src, dst) os.rename(src, dst)
## GERMAN VERSIONS
versionout = version[:-1] + "_" + sha + "_esp8266-de.bin"
src = path + ".pio/build/esp8266-de/firmware.bin"
dst = path + "firmware/ESP8266/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp8266_prometheus-de.bin"
src = path + ".pio/build/esp8266-prometheus-de/firmware.bin"
dst = path + "firmware/ESP8266/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp8285-de.bin"
src = path + ".pio/build/esp8285-de/firmware.bin"
dst = path + "firmware/ESP8285/" + versionout
os.rename(src, dst)
gzip_bin(dst, dst + ".gz")
versionout = version[:-1] + "_" + sha + "_esp32-de.bin"
src = path + ".pio/build/esp32-wroom32-de/firmware.bin"
dst = path + "firmware/ESP32/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32_prometheus-de.bin"
src = path + ".pio/build/esp32-wroom32-prometheus-de/firmware.bin"
dst = path + "firmware/ESP32/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32_ethernet-de.bin"
src = path + ".pio/build/esp32-wroom32-ethernet-de/firmware.bin"
dst = path + "firmware/ESP32/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32s2-mini-de.bin"
src = path + ".pio/build/esp32-s2-mini-de/firmware.bin"
dst = path + "firmware/ESP32-S2/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32c3-mini-de.bin"
src = path + ".pio/build/esp32-c3-mini-de/firmware.bin"
dst = path + "firmware/ESP32-C3/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32s3-de.bin"
src = path + ".pio/build/opendtufusion-de/firmware.bin"
dst = path + "firmware/ESP32-S3/" + versionout
os.rename(src, dst)
versionout = version[:-1] + "_" + sha + "_esp32s3_ethernet-de.bin"
src = path + ".pio/build/opendtufusion-ethernet-de/firmware.bin"
dst = path + "firmware/ESP32-S3-ETH/" + versionout
os.rename(src, dst)
## BOOTLOADER AND PARTITIONS
# other ESP32 bin files # other ESP32 bin files
src = path + ".pio/build/esp32-wroom32/" src = path + ".pio/build/esp32-wroom32/"
dst = path + "firmware/ESP32/" dst = path + "firmware/ESP32/"

1
src/CHANGES.md

@ -6,6 +6,7 @@
* updated espressif32 platform to `6.5.0` * updated espressif32 platform to `6.5.0`
* updated U8g2 to `2.35.9` * updated U8g2 to `2.35.9`
* started to convert deprecated functions of new ArduinoJson `7.0.0` * started to convert deprecated functions of new ArduinoJson `7.0.0`
* started to have german translations of all variants (environments) #925 #1199
## 0.8.46 - 2024-01-06 ## 0.8.46 - 2024-01-06
* improved communication * improved communication

4
src/config/config.h

@ -1,6 +1,6 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 // 2024 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#ifndef __CONFIG_H__ #ifndef __CONFIG_H__

144
src/platformio.ini

@ -50,6 +50,16 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp8266_exception_decoder esp8266_exception_decoder
[env:esp8266-de]
platform = espressif8266
board = esp12e
board_build.f_cpu = 80000000L
build_flags = ${env.build_flags}
-DEMC_MIN_FREE_MEMORY=4096
-DLANG_DE
;-Wl,-Map,output.map
monitor_filters =
esp8266_exception_decoder
[env:esp8266-prometheus] [env:esp8266-prometheus]
platform = espressif8266 platform = espressif8266
@ -61,6 +71,17 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp8266_exception_decoder esp8266_exception_decoder
[env:esp8266-prometheus-de]
platform = espressif8266
board = esp12e
board_build.f_cpu = 80000000L
build_flags = ${env.build_flags}
-DENABLE_PROMETHEUS_EP
-DEMC_MIN_FREE_MEMORY=4096
-DLANG_DE
monitor_filters =
esp8266_exception_decoder
[env:esp8285] [env:esp8285]
platform = espressif8266 platform = espressif8266
board = esp8285 board = esp8285
@ -71,6 +92,17 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp8266_exception_decoder esp8266_exception_decoder
[env:esp8285-de]
platform = espressif8266
board = esp8285
board_build.ldscript = eagle.flash.1m64.ld
board_build.f_cpu = 80000000L
build_flags = ${env.build_flags}
-DEMC_MIN_FREE_MEMORY=4096
-DLANG_DE
monitor_filters =
esp8266_exception_decoder
[env:esp32-wroom32] [env:esp32-wroom32]
platform = espressif32@6.5.0 platform = espressif32@6.5.0
board = lolin_d32 board = lolin_d32
@ -79,6 +111,15 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder esp32_exception_decoder
[env:esp32-wroom32-de]
platform = espressif32@6.5.0
board = lolin_d32
build_flags = ${env.build_flags}
-DUSE_HSPI_FOR_EPD
-DLANG_DE
monitor_filters =
esp32_exception_decoder
[env:esp32-wroom32-prometheus] [env:esp32-wroom32-prometheus]
platform = espressif32@6.5.0 platform = espressif32@6.5.0
board = lolin_d32 board = lolin_d32
@ -88,6 +129,16 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder esp32_exception_decoder
[env:esp32-wroom32-prometheus-de]
platform = espressif32@6.5.0
board = lolin_d32
build_flags = ${env.build_flags}
-DUSE_HSPI_FOR_EPD
-DENABLE_PROMETHEUS_EP
-DLANG_DE
monitor_filters =
esp32_exception_decoder
[env:esp32-wroom32-ethernet] [env:esp32-wroom32-ethernet]
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
@ -110,6 +161,29 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder esp32_exception_decoder
[env:esp32-wroom32-ethernet-de]
platform = espressif32
board = esp32dev
lib_deps =
khoih-prog/AsyncWebServer_ESP32_W5500
khoih-prog/AsyncUDP_ESP32_W5500
nrf24/RF24 @ ^1.4.8
paulstoffregen/Time @ ^1.6.1
https://github.com/bertmelis/espMqttClient#v1.5.0
bblanchon/ArduinoJson @ ^6.21.3
https://github.com/JChristensen/Timezone @ ^1.2.4
olikraus/U8g2 @ ^2.35.9
https://github.com/zinggjm/GxEPD2 @ ^1.5.3
build_flags = ${env.build_flags}
-D ETHERNET
-DRELEASE
-DUSE_HSPI_FOR_EPD
-DLANG_DE
-DLOG_LOCAL_LEVEL=ESP_LOG_INFO
-DDEBUG_LEVEL=DBG_INFO
monitor_filters =
esp32_exception_decoder
[env:esp32-s2-mini] [env:esp32-s2-mini]
platform = espressif32@6.5.0 platform = espressif32@6.5.0
board = lolin_s2_mini board = lolin_s2_mini
@ -124,6 +198,21 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder esp32_exception_decoder
[env:esp32-s2-mini-de]
platform = espressif32@6.5.0
board = lolin_s2_mini
build_flags = ${env.build_flags}
-DUSE_HSPI_FOR_EPD
-DDEF_NRF_CS_PIN=12
-DDEF_NRF_CE_PIN=3
-DDEF_NRF_IRQ_PIN=5
-DDEF_NRF_MISO_PIN=9
-DDEF_NRF_MOSI_PIN=11
-DDEF_NRF_SCLK_PIN=7
-DLANG_DE
monitor_filters =
esp32_exception_decoder
[env:esp32-c3-mini] [env:esp32-c3-mini]
platform = espressif32@6.5.0 platform = espressif32@6.5.0
board = lolin_c3_mini board = lolin_c3_mini
@ -138,6 +227,20 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder esp32_exception_decoder
[env:esp32-c3-mini-de]
platform = espressif32@6.5.0
board = lolin_c3_mini
build_flags = ${env.build_flags}
-DUSE_HSPI_FOR_EPD
-DDEF_NRF_CS_PIN=5
-DDEF_NRF_CE_PIN=0
-DDEF_NRF_IRQ_PIN=1
-DDEF_NRF_MISO_PIN=3
-DDEF_NRF_MOSI_PIN=4
-DDEF_NRF_SCLK_PIN=2
-DLANG_DE
monitor_filters =
esp32_exception_decoder
[env:opendtufusion] [env:opendtufusion]
platform = espressif32@6.5.0 platform = espressif32@6.5.0
@ -162,6 +265,30 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder, colorize esp32_exception_decoder, colorize
[env:opendtufusion-de]
platform = espressif32@6.5.0
board = esp32-s3-devkitc-1
upload_protocol = esp-builtin
build_flags = ${env.build_flags}
-DDEF_NRF_CS_PIN=37
-DDEF_NRF_CE_PIN=38
-DDEF_NRF_IRQ_PIN=47
-DDEF_NRF_MISO_PIN=48
-DDEF_NRF_MOSI_PIN=35
-DDEF_NRF_SCLK_PIN=36
-DDEF_CMT_CSB=4
-DDEF_CMT_FCSB=21
-DDEF_CMT_IRQ=8
-DDEF_CMT_SDIO=5
-DDEF_CMT_SCLK=6
-DDEF_LED0=18
-DDEF_LED1=17
-DLED_ACTIVE_HIGH
-DARDUINO_USB_MODE=1
-DLANG_DE
monitor_filters =
esp32_exception_decoder, colorize
[env:opendtufusion-ethernet] [env:opendtufusion-ethernet]
platform = espressif32@6.5.0 platform = espressif32@6.5.0
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
@ -204,11 +331,12 @@ build_flags = ${env.build_flags}
monitor_filters = monitor_filters =
esp32_exception_decoder, colorize esp32_exception_decoder, colorize
[env:opendtufusion-dev] [env:opendtufusion-ethernet-de]
platform = espressif32@6.5.0 platform = espressif32@6.5.0
board = esp32-s3-devkitc-1 board = esp32-s3-devkitc-1
lib_deps = lib_deps =
https://github.com/yubox-node-org/ESPAsyncWebServer khoih-prog/AsyncWebServer_ESP32_W5500
khoih-prog/AsyncUDP_ESP32_W5500
https://github.com/nrf24/RF24 @ ^1.4.8 https://github.com/nrf24/RF24 @ ^1.4.8
paulstoffregen/Time @ ^1.6.1 paulstoffregen/Time @ ^1.6.1
https://github.com/bertmelis/espMqttClient#v1.5.0 https://github.com/bertmelis/espMqttClient#v1.5.0
@ -218,6 +346,14 @@ lib_deps =
https://github.com/zinggjm/GxEPD2 @ ^1.5.3 https://github.com/zinggjm/GxEPD2 @ ^1.5.3
upload_protocol = esp-builtin upload_protocol = esp-builtin
build_flags = ${env.build_flags} build_flags = ${env.build_flags}
-DETHERNET
-DSPI_HAL
-DDEF_ETH_CS_PIN=42
-DDEF_ETH_SCK_PIN=39
-DDEF_ETH_MISO_PIN=41
-DDEF_ETH_MOSI_PIN=40
-DDEF_ETH_IRQ_PIN=44
-DDEF_ETH_RST_PIN=43
-DDEF_NRF_CS_PIN=37 -DDEF_NRF_CS_PIN=37
-DDEF_NRF_CE_PIN=38 -DDEF_NRF_CE_PIN=38
-DDEF_NRF_IRQ_PIN=47 -DDEF_NRF_IRQ_PIN=47
@ -233,7 +369,7 @@ build_flags = ${env.build_flags}
-DDEF_LED1=17 -DDEF_LED1=17
-DLED_ACTIVE_HIGH -DLED_ACTIVE_HIGH
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 #-DARDUINO_USB_CDC_ON_BOOT=1
-DSPI_HAL -DLANG_DE
monitor_filters = monitor_filters =
esp32_exception_decoder, colorize esp32_exception_decoder, colorize

2
src/web/html/includes/header.html

@ -1,6 +1,6 @@
<link rel="stylesheet" type="text/css" href="style.css?v={#VERSION}"/> <link rel="stylesheet" type="text/css" href="style.css?v={#VERSION}"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="UTF-8"> <meta charset="utf-8">
<script type="text/javascript" src="api.js?v={#VERSION}"></script> <script type="text/javascript" src="api.js?v={#VERSION}"></script>
<link rel="stylesheet" type="text/css" href="colors.css?v={#VERSION}"/> <link rel="stylesheet" type="text/css" href="colors.css?v={#VERSION}"/>
<meta name="robots" content="noindex, nofollow" /> <meta name="robots" content="noindex, nofollow" />

10
src/web/html/includes/nav.html

@ -6,16 +6,16 @@
<span></span> <span></span>
</a> </a>
<div id="topnav" class="mobile"> <div id="topnav" class="mobile">
<a id="nav3" class="hide" href="/live?v={#VERSION}">Live</a> <a id="nav3" class="hide" href="/live?v={#VERSION}">{#NAV_LIVE}</a>
<a id="nav4" class="hide" href="/serial?v={#VERSION}">Webserial</a> <a id="nav4" class="hide" href="/serial?v={#VERSION}">{#NAV_WEBSERIAL}</a>
<a id="nav5" class="hide" href="/setup?v={#VERSION}">Settings</a> <a id="nav5" class="hide" href="/setup?v={#VERSION}">{#NAV_SETTINGS}</a>
<span class="seperator"></span> <span class="seperator"></span>
<a id="nav6" class="hide" href="/update?v={#VERSION}">Update</a> <a id="nav6" class="hide" href="/update?v={#VERSION}">Update</a>
<a id="nav7" class="hide" href="/system?v={#VERSION}">System</a> <a id="nav7" class="hide" href="/system?v={#VERSION}">System</a>
<span class="seperator"></span> <span class="seperator"></span>
<a id="nav8" href="/api" target="_blank">REST API</a> <a id="nav8" href="/api" target="_blank">REST API</a>
<a id="nav9" href="https://ahoydtu.de" target="_blank">Documentation</a> <a id="nav9" href="https://ahoydtu.de" target="_blank">{#NAV_DOCUMENTATION}</a>
<a id="nav10" href="/about?v={#VERSION}">About</a> <a id="nav10" href="/about?v={#VERSION}">{#NAV_ABOUT}</a>
<span class="seperator"></span> <span class="seperator"></span>
<a id="nav0" class="hide" href="/login">Login</a> <a id="nav0" class="hide" href="/login">Login</a>
<a id="nav1" class="hide" href="/logout">Logout</a> <a id="nav1" class="hide" href="/logout">Logout</a>

53
src/web/html/index.html

@ -20,18 +20,15 @@
</p> </p>
<div id="note"> <div id="note">
<h3>Support this project:</h3> <h3>{#SUPPORT}:</h3>
<ul> <ul>
<li><a href="https://github.com/lumapu/ahoy/blob/main/src/CHANGES.md" target="_blank">Changelog</a></li> <li><a href="https://github.com/lumapu/ahoy/blob/main/src/CHANGES.md" target="_blank">{#CHANGELOG}</a></li>
<li>Discuss with us on <a href="https://discord.gg/WzhxEY62mB">Discord</a></li> <li>{#DISCUSS} <a href="https://discord.gg/WzhxEY62mB">Discord</a></li>
<li>Report <a href="https://github.com/lumapu/ahoy/issues" target="_blank">issues</a></li> <li>{#REPORT} <a href="https://github.com/lumapu/ahoy/issues" target="_blank">{#ISSUES}</a></li>
<li>Contribute to <a href="https://github.com/lumapu/ahoy/blob/main/User_Manual.md" target="_blank">documentation</a></li> <li>{#CONTRIBUTE} <a href="https://github.com/lumapu/ahoy/blob/main/User_Manual.md" target="_blank">{#DOCUMENTATION}</a></li>
<li><a href="https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip" target="_blank">Download</a> & Test development firmware, <a href="https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md" target="_blank">Development Changelog</a></li> <li><a href="https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip" target="_blank">Download</a> & Test {#DEV_FIRMWARE}, <a href="https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md" target="_blank">{#DEV_CHANGELOG}</a></li>
<li>make a <a href="https://paypal.me/lupusch" target="_blank">donation</a></li> <li>{#DON_MAKE} <a href="https://paypal.me/lupusch" target="_blank">{#DONATION}</a></li>
</ul> </ul>
<p class="lic">
This project was started from <a href="https://www.mikrocontroller.net/topic/525778" target="_blank">this discussion. (Mikrocontroller.net)</a>
</p>
</div> </div>
</div> </div>
</div> </div>
@ -46,11 +43,11 @@
function apiCb(obj) { function apiCb(obj) {
var e = document.getElementById("apiResult"); var e = document.getElementById("apiResult");
if(obj.success) { if(obj.success) {
e.innerHTML = " command executed"; e.innerHTML = " {#COMMAND_EXE}";
getAjax("/api/index", parse); getAjax("/api/index", parse);
} }
else else
e.innerHTML = " Error: " + obj.error; e.innerHTML = " {#ERROR}: " + obj.error;
} }
function setTime() { function setTime() {
@ -92,7 +89,7 @@
else { else {
dSpan.innerHTML = ""; dSpan.innerHTML = "";
var e = inp("set", "sync from browser", 0, ["btn"], "set", "button"); var e = inp("set", "sync from browser", 0, ["btn"], "set", "button");
dSpan.appendChild(span("NTP timeserver unreachable. ")); dSpan.appendChild(span("{#NTP_UNREACH}. "));
dSpan.appendChild(e); dSpan.appendChild(e);
dSpan.appendChild(span("", ["span"], "apiResult")); dSpan.appendChild(span("", ["span"], "apiResult"));
e.addEventListener("click", setTime); e.addEventListener("click", setTime);
@ -101,15 +98,15 @@
if(obj.disNightComm) { if(obj.disNightComm) {
if(((obj.ts_sunrise + obj.ts_offsSr) < obj.ts_now) if(((obj.ts_sunrise + obj.ts_offsSr) < obj.ts_now)
&& ((obj.ts_sunset + obj.ts_offsSs) > obj.ts_now)) { && ((obj.ts_sunset + obj.ts_offsSs) > obj.ts_now)) {
commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); commInfo = "{#POLLING_STOP} " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE'));
} }
else { else {
commInfo = "Night time, inverter polling disabled, "; commInfo = "{#NIGHT_TIME}, ";
if(obj.ts_now > (obj.ts_sunrise + obj.ts_offsSr)) { if(obj.ts_now > (obj.ts_sunrise + obj.ts_offsSr)) {
commInfo += "paused at " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); commInfo += "{#PAUSED_AT} " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE'));
} }
else { else {
commInfo += "will start polling at " + (new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')); commInfo += "{#START_AT} " + (new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE'));
} }
} }
} }
@ -124,33 +121,33 @@
if(false == i["enabled"]) { if(false == i["enabled"]) {
icon = iconWarn; icon = iconWarn;
cl = "icon-warn"; cl = "icon-warn";
avail = "disabled"; avail = "{#DISABLED}";
} else if((false == i["is_avail"]) || (0 == ts)) { } else if((false == i["is_avail"]) || (0 == ts)) {
icon = iconInfo; icon = iconInfo;
cl = "icon-info"; cl = "icon-info";
avail = "not yet available"; avail = "{#NOT_YET_AVAIL}";
} else if(0 == i["ts_last_success"]) { } else if(0 == i["ts_last_success"]) {
avail = "available but no data was received until now"; avail = "{#AVAIL_NO_DATA}";
} else { } else {
avail = "available and is "; avail = "{#AVAIL} ";
if(false == i["is_producing"]) if(false == i["is_producing"])
avail += "not producing"; avail += "{#NOT_PRODUCING}";
else { else {
icon = iconSuccessFull; icon = iconSuccessFull;
avail += "producing " + i.cur_pwr + "W"; avail += "{#PRODUCING} " + i.cur_pwr + "W";
} }
} }
p.append( p.append(
svg(icon, 30, 30, "icon " + cl), svg(icon, 30, 30, "icon " + cl),
span("Inverter #" + i["id"] + ": " + i["name"] + " is " + avail), span("{#INVERTER} #" + i["id"] + ": " + i["name"] + " {#IS} " + avail),
br() br()
); );
if(false == i["is_avail"]) { if(false == i["is_avail"]) {
if(i["ts_last_success"] > 0) { if(i["ts_last_success"] > 0) {
var date = new Date(i["ts_last_success"] * 1000); var date = new Date(i["ts_last_success"] * 1000);
p.append(span("-> last successful transmission: " + toIsoDateStr(date)), br()); p.append(span("-> {#LAST_SUCCESS}: " + toIsoDateStr(date)), br());
} }
} }
} }
@ -168,11 +165,11 @@
if(null != release) { if(null != release) {
if(getVerInt("{#VERSION}") < getVerInt(release)) if(getVerInt("{#VERSION}") < getVerInt(release))
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("Update available, current released version: " + release), br()); p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#UPDATE_AVAIL}: " + release), br());
else if(getVerInt("{#VERSION}") > getVerInt(release)) else if(getVerInt("{#VERSION}") > getVerInt(release))
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using development version {#VERSION}. In case of issues you may want to try the current stable release: " + release), br()); p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#USING_DEV_VERSION} {#VERSION}. {#DEV_ISSUE_RELEASE_VERSION}: " + release), br());
else else
p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("You are using the current stable release: " + release), br()); p.append(svg(iconInfo, 30, 30, "icon icon-info"), span("{#RELEASE_INSTALLED}: " + release), br());
} }
document.getElementById("warn_info").replaceChildren(p); document.getElementById("warn_info").replaceChildren(p);

8
src/web/html/save.html

@ -8,7 +8,7 @@
{#HTML_NAV} {#HTML_NAV}
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<div id="html" class="mt-3 mb-3">Saving settings...</div> <div id="html" class="mt-3 mb-3">{#SAVE_SETTINGS}</div>
</div> </div>
</div> </div>
{#HTML_FOOTER} {#HTML_FOOTER}
@ -31,15 +31,15 @@
var meta = document.createElement('meta'); var meta = document.createElement('meta');
meta.httpEquiv = "refresh" meta.httpEquiv = "refresh"
if(!obj.reboot) { if(!obj.reboot) {
html = "Settings successfully saved. Automatic page reload in 3 seconds."; html = "{#SUCCESS_SAVED_RELOAD}";
meta.content = 3; meta.content = 3;
} else { } else {
html = "Settings successfully saved. Rebooting. Automatic redirect in " + obj.reload + " seconds."; html = "{#SUCCESS_SAVED_REBOOT} " + obj.reload + " {#SECONDS}.";
meta.content = obj.reload + "; URL=/"; meta.content = obj.reload + "; URL=/";
} }
document.getElementsByTagName('head')[0].appendChild(meta); document.getElementsByTagName('head')[0].appendChild(meta);
} else { } else {
html = "Failed saving settings."; html = "{#FAILED_SAVE}.";
} }
} }
document.getElementById("html").innerHTML = html; document.getElementById("html").innerHTML = html;

288
src/web/html/setup.html

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Setup</title> <title>{#SETTINGS}</title>
{#HTML_HEADER} {#HTML_HEADER}
</head> </head>
<body> <body>
@ -10,77 +10,74 @@
<div id="content"> <div id="content">
<form method="post" action="/save" id="settings"> <form method="post" action="/save" id="settings">
<fieldset> <fieldset>
<button type="button" class="s_collapsible mt-4">System Config</button> <button type="button" class="s_collapsible mt-4">{#SYSTEM_CONFIG}</button>
<div class="s_content"> <div class="s_content">
<fieldset class="mb-2"> <fieldset class="mb-2">
<legend class="des">Device Host Name</legend> <legend class="des">{#DEVICE_NAME}</legend>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3">Device Name</div> <div class="col-12 col-sm-3">{#DEVICE_NAME}</div>
<div class="col-12 col-sm-9"><input type="text" name="device"/></div> <div class="col-12 col-sm-9"><input type="text" name="device"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">Reboot Ahoy at midnight</div> <div class="col-8 col-sm-3">{#REBOOT_AT_MIDNIGHT}</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="schedReboot"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="schedReboot"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">Dark Mode</div> <div class="col-8 col-sm-3">{#DARK_MODE}</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="darkMode"/></div>
<div class="col-12">(empty browser cache or use CTRL + F5 after reboot to apply this setting)</div> <div class="col-12">{#DARK_MODE_NOTE}</div>
</div> </div>
</fieldset> </fieldset>
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des">System Config</legend> <legend class="des">{#PINOUT_CONFIGURATION}</legend>
<p class="des">Status LEDs</p> <p class="des">Status LEDs</p>
<div id="pinout"></div> <div id="pinout"></div>
<p class="des">Radio (NRF24L01+)</p> <p class="des">{#RADIO} (NRF24L01+)</p>
<div id="rf24"></div> <div id="rf24"></div>
<!--IF_ESP32--> <!--IF_ESP32-->
<p class="des">Radio (CMT2300A)</p> <p class="des">{#RADIO} (CMT2300A)</p>
<div id="cmt"><div class="col-12">(ESP32 only)</div></div> <div id="cmt"></div>
<!--ENDIF_ESP32--> <!--ENDIF_ESP32-->
<p class="des">Serial Console</p> <p class="des">{#SERIAL_CONSOLE}</p>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">print inverter data</div> <div class="col-8 col-sm-3">{#LOG_PRINT_INVERTER_DATA}</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="serEn"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="serEn"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">Serial Debug</div> <div class="col-8 col-sm-3">{#LOG_SERIAL_DEBUG}</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="serDbg"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="serDbg"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">Privacy Mode</div> <div class="col-8 col-sm-3">{#LOG_PRIVACY_MODE}</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="priv"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="priv"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">Print whole traces in Log</div> <div class="col-8 col-sm-3">{#LOG_PRINT_TRACES}</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="wholeTrace"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="wholeTrace"/></div>
</div> </div>
</fieldset> </fieldset>
</div> </div>
<button type="button" class="s_collapsible">Network</button> <button type="button" class="s_collapsible">{#NETWORK}</button>
<div class="s_content"> <div class="s_content">
<fieldset class="mb-2"> <fieldset class="mb-2">
<legend class="des">WiFi</legend> <legend class="des">WiFi</legend>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">AP Password (min. length: 8)</div> <div class="col-12 col-sm-3 my-2">{#AP_PWD}</div>
<div class="col-12 col-sm-9"><input type="text" name="ap_pwd" minlength="8" /></div> <div class="col-12 col-sm-9"><input type="text" name="ap_pwd" minlength="8" /></div>
</div> </div>
<p>Enter the credentials to your preferred WiFi station. After rebooting the device tries to connect with this information.</p>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Search Networks</div> <div class="col-12 col-sm-3 my-2">{#SEARCH_NETWORKS}</div>
<div class="col-12 col-sm-9"><input type="button" name="scanbtn" id="scanbtn" class="btn" value="scan" onclick="scan()"/></div> <div class="col-12 col-sm-9"><input type="button" name="scanbtn" id="scanbtn" class="btn" value="{#BTN_SCAN}" onclick="scan()"/></div>
</div> </div>
<div class="row mb-2 mb-sm-3"> <div class="row mb-2 mb-sm-3">
<div class="col-12 col-sm-3 my-2">Avail Networks</div> <div class="col-12 col-sm-3 my-2">{#AVAIL_NETWORKS}</div>
<div class="col-12 col-sm-9"> <div class="col-12 col-sm-9">
<select name="networks" id="networks" onChange="selNet()"> <select name="networks" id="networks" onChange="selNet()">
<option value="-1" selected disabled hidden>not scanned</option> <option value="-1" selected disabled hidden>{#NETWORK_NOT_SCANNED}</option>
</select> </select>
</div> </div>
</div> </div>
@ -89,26 +86,25 @@
<div class="col-12 col-sm-9"><input type="text" name="ssid"/></div> <div class="col-12 col-sm-9"><input type="text" name="ssid"/></div>
</div> </div>
<div class="row mb-2 mb-sm-3"> <div class="row mb-2 mb-sm-3">
<div class="col-12 col-sm-3">SSID is hidden</div> <div class="col-12 col-sm-3">{#SSID_HIDDEN}</div>
<div class="col-12 col-sm-9"><input type="checkbox" name="hidd"/></div> <div class="col-12 col-sm-9"><input type="checkbox" name="hidd"/></div>
</div> </div>
<div class="row mb-2 mb-sm-3"> <div class="row mb-2 mb-sm-3">
<div class="col-12 col-sm-3 my-2">Password</div> <div class="col-12 col-sm-3 my-2">{#PASSWORD}</div>
<div class="col-12 col-sm-9"><input type="password" name="pwd" value="{PWD}"/></div> <div class="col-12 col-sm-9"><input type="password" name="pwd" value="{PWD}"/></div>
</div> </div>
</fieldset> </fieldset>
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des">Static IP (optional)</legend> <legend class="des">{#STATIC_IP}</legend>
<p> <p>
Leave fields blank for DHCP<br/> {#NETWORK_HINT_BLANK}
The following fields are parsed in this format: 192.168.4.1
</p> </p>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">IP Address</div> <div class="col-12 col-sm-3 my-2">{#IP_ADDRESS}</div>
<div class="col-12 col-sm-9"><input type="text" name="ipAddr" maxlength="15" /></div> <div class="col-12 col-sm-9"><input type="text" name="ipAddr" maxlength="15" /></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Submask</div> <div class="col-12 col-sm-3 my-2">{#SUBMASK}</div>
<div class="col-12 col-sm-9"><input type="text" name="ipMask" maxlength="15" /></div> <div class="col-12 col-sm-9"><input type="text" name="ipMask" maxlength="15" /></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
@ -126,58 +122,58 @@
</fieldset> </fieldset>
</div> </div>
<button type="button" class="s_collapsible">Protection</button> <button type="button" class="s_collapsible">{#PROTECTION}</button>
<div class="s_content"> <div class="s_content">
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des mx-2">Protection</legend> <legend class="des mx-2">{#PROTECTION}</legend>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 mb-2 mt-2">Admin Password</div> <div class="col-12 col-sm-3 mb-2 mt-2">{#ADMIN_PASSWORD}</div>
<div class="col-12 col-sm-9"><input type="password" name="adminpwd" value="{PWD}"/></div> <div class="col-12 col-sm-9"><input type="password" name="adminpwd" value="{PWD}"/></div>
</div> </div>
<p>Select pages which should be protected by password</p> <p>{#PROTECTION_NOTE}</p>
<div id="prot_mask"></div> <div id="prot_mask"></div>
</fieldset> </fieldset>
</div> </div>
<button type="button" class="s_collapsible">Inverter</button> <button type="button" class="s_collapsible">{#INVERTER}</button>
<div class="s_content"> <div class="s_content">
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des">Inverter</legend> <legend class="des">{#INVERTER}</legend>
<div id="inverter"></div> <div id="inverter"></div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 my-2">Interval [s]</div> <div class="col-8 my-2">{#INTERVAL}</div>
<div class="col-4"><input type="number" name="invInterval" title="Invalid input"/></div> <div class="col-4"><input type="number" name="invInterval" title="Invalid input"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 my-2">Inverter Gap [ms]</div> <div class="col-8 my-2">{#INV_GAP}</div>
<div class="col-4"><input type="number" name="invGap" title="Invalid input"/></div> <div class="col-4"><input type="number" name="invGap" title="Invalid input"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 mb-2">Reset values and YieldDay at midnight</div> <div class="col-8 mb-2">{#INV_RESET_MIDNIGHT}</div>
<div class="col-4"><input type="checkbox" name="invRstMid"/></div> <div class="col-4"><input type="checkbox" name="invRstMid"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 mb-2">Reset values when inverter polling pauses at sunset</div> <div class="col-8 mb-2">{#INV_PAUSE_SUNSET}</div>
<div class="col-4"><input type="checkbox" name="invRstComStop"/></div> <div class="col-4"><input type="checkbox" name="invRstComStop"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8">Reset values when inverter status is 'not available'</div> <div class="col-8">{#INV_RESET_NOT_AVAIL}</div>
<div class="col-4"><input type="checkbox" name="invRstNotAvail"/></div> <div class="col-4"><input type="checkbox" name="invRstNotAvail"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8">Reset 'max' values at midnight</div> <div class="col-8">{#INV_RESET_MAX_MIDNIGHT}</div>
<div class="col-4"><input type="checkbox" name="invRstMaxMid"/></div> <div class="col-4"><input type="checkbox" name="invRstMaxMid"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8">Start without time sync (useful in AP-Only-Mode)</div> <div class="col-8">{#INV_START_WITHOUT_TIME}</div>
<div class="col-4"><input type="checkbox" name="strtWthtTm"/></div> <div class="col-4"><input type="checkbox" name="strtWthtTm"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8">Read Grid Profile</div> <div class="col-8">{#INV_READ_GRID_PROFILE}</div>
<div class="col-4"><input type="checkbox" name="rdGrid"/></div> <div class="col-4"><input type="checkbox" name="rdGrid"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8">Yield Efficiency (Standard 1.0)</div> <div class="col-8">{#INV_YIELD_EFF}</div>
<div class="col-4"><input type="number" name="yldEff" step="any"/></div> <div class="col-4"><input type="number" name="yldEff" step="any"/></div>
</div> </div>
</fieldset> </fieldset>
@ -196,42 +192,42 @@
<div class="col-12 col-sm-9"><input type="number" name="ntpPort"/></div> <div class="col-12 col-sm-9"><input type="number" name="ntpPort"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">NTP Interval (in Minutes, min. 5 Minutes)</div> <div class="col-12 col-sm-3 my-2">{#NTP_INTERVAL}</div>
<div class="col-12 col-sm-9"><input type="number" name="ntpIntvl"/></div> <div class="col-12 col-sm-9"><input type="number" name="ntpIntvl"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">set System time</div> <div class="col-12 col-sm-3 my-2">{#NTP_SET_SYS_TIME}</div>
<div class="col-12 col-sm-9"> <div class="col-12 col-sm-9">
<input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="from browser" onclick="setTime()"/> <input type="button" name="ntpBtn" id="ntpBtn" class="btn" value="{#BTN_FROM_BROWSER}" onclick="setTime()"/>
<input type="button" name="ntpSync" id="ntpSync" class="btn" value="sync NTP" onclick="syncTime()"/><br/> <input type="button" name="ntpSync" id="ntpSync" class="btn" value="{#BTN_SYNC_NTP}" onclick="syncTime()"/><br/>
<span id="apiResultNtp"></span> <span id="apiResultNtp"></span>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">System Time</div> <div class="col-12 col-sm-3 my-2">{#NTP_SYS_TIME}</div>
<div class="col-12 col-sm-9 my-2"><span id="date"></span></div> <div class="col-12 col-sm-9 my-2"><span id="date"></span></div>
</div> </div>
</fieldset> </fieldset>
</div> </div>
<button type="button" class="s_collapsible">Sunrise & Sunset</button> <button type="button" class="s_collapsible">{#SUNRISE_SUNSET}</button>
<div class="s_content"> <div class="s_content">
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des">Sunrise & Sunset</legend> <legend class="des">{#SUNRISE_SUNSET}</legend>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Latitude (decimal)</div> <div class="col-12 col-sm-3 my-2">{#LATITUDE}</div>
<div class="col-12 col-sm-9"><input type="number" name="sunLat" step="any"/></div> <div class="col-12 col-sm-9"><input type="number" name="sunLat" step="any"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Longitude (decimal)</div> <div class="col-12 col-sm-3 my-2">{#LONGITUDE}</div>
<div class="col-12 col-sm-9"><input type="number" name="sunLon" step="any"/></div> <div class="col-12 col-sm-9"><input type="number" name="sunLon" step="any"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Offset (sunrise)</div> <div class="col-12 col-sm-3 my-2">{#OFFSET_SUNRISE}</div>
<div class="col-12 col-sm-9"><select name="sunOffsSr"></select></div> <div class="col-12 col-sm-9"><select name="sunOffsSr"></select></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Offset (sunset)</div> <div class="col-12 col-sm-3 my-2">{#OFFSET_SUNSET}</div>
<div class="col-12 col-sm-9"><select name="sunOffsSs"></select></div> <div class="col-12 col-sm-9"><select name="sunOffsSs"></select></div>
</div> </div>
</fieldset> </fieldset>
@ -254,20 +250,20 @@
<div class="col-12 col-sm-9"><input type="text" name="mqttClientId"/></div> <div class="col-12 col-sm-9"><input type="text" name="mqttClientId"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Username (optional)</div> <div class="col-12 col-sm-3 my-2">{#MQTT_USER}</div>
<div class="col-12 col-sm-9"><input type="text" name="mqttUser"/></div> <div class="col-12 col-sm-9"><input type="text" name="mqttUser"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Password (optional)</div> <div class="col-12 col-sm-3 my-2">{MQTT_PASSWORD}</div>
<div class="col-12 col-sm-9"><input type="password" name="mqttPwd"/></div> <div class="col-12 col-sm-9"><input type="password" name="mqttPwd"/></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Topic</div> <div class="col-12 col-sm-3 my-2">Topic</div>
<div class="col-12 col-sm-9"><input type="text" name="mqttTopic" pattern="[\-\+A-Za-z0-9\.\/#\$%&=_]+" title="Invalid input" /></div> <div class="col-12 col-sm-9"><input type="text" name="mqttTopic" pattern="[\-\+A-Za-z0-9\.\/#\$%&=_]+" title="Invalid input" /></div>
</div> </div>
<p class="des">Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)</p> <p class="des">{#MQTT_NOTE}</p>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Interval [s]</div> <div class="col-12 col-sm-3 my-2">{#INTERVAL}</div>
<div class="col-12 col-sm-9"><input type="number" name="mqttInterval" title="Invalid input" /></div> <div class="col-12 col-sm-9"><input type="number" name="mqttInterval" title="Invalid input" /></div>
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
@ -280,29 +276,29 @@
</fieldset> </fieldset>
</div> </div>
<button type="button" class="s_collapsible">Display Config</button> <button type="button" class="s_collapsible">{#DISPLAY_CONFIG}</button>
<div class="s_content"> <div class="s_content">
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des">Display Config</legend> <legend class="des">{#DISPLAY_CONFIG}</legend>
<div id="dispType"></div> <div id="dispType"></div>
<div id="dispRot"></div> <div id="dispRot"></div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-8 col-sm-3">Turn off while inverters are offline</div> <div class="col-8 col-sm-3">{#DISP_OFF_INV}</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="disp_pwr"/></div> <div class="col-4 col-sm-9"><input type="checkbox" name="disp_pwr"/></div>
</div> </div>
<div id="screenSaver"></div> <div id="screenSaver"></div>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Luminance</div> <div class="col-12 col-sm-3 my-2">{#DISP_LUMINANCE}</div>
<div class="col-12 col-sm-9"><input type="number" name="disp_cont" min="0" max="255"></select></div> <div class="col-12 col-sm-9"><input type="number" name="disp_cont" min="0" max="255"></select></div>
</div> </div>
<p class="des">Pinout</p> <p class="des">{#DISP_PINOUT}</p>
<div id="dispPins"></div> <div id="dispPins"></div>
<div id="pirPin"></div> <div id="pirPin"></div>
</fieldset> </fieldset>
</div> </div>
<div class="row mb-4 mt-4"> <div class="row mb-4 mt-4">
<div class="col-8 col-sm-3">Reboot device after successful save</div> <div class="col-8 col-sm-3">{#BTN_REBOOT_SUCCESSFUL_SAVE}</div>
<div class="col-4 col-sm-9"> <div class="col-4 col-sm-9">
<input type="checkbox" name="reboot" checked /> <input type="checkbox" name="reboot" checked />
<input type="submit" value="save" class="btn right"/> <input type="submit" value="save" class="btn right"/>
@ -311,11 +307,11 @@
</form> </form>
<div class="hr mb-3 mt-3"></div> <div class="hr mb-3 mt-3"></div>
<div class="mb-4 mt-4"> <div class="mb-4 mt-4">
<a class="btn" href="/erase">ERASE SETTINGS (not WiFi)</a> <a class="btn" href="/erase">{#BTN_ERASE}</a>
<fieldset class="mb-4"> <fieldset class="mb-4">
<legend class="des">Import / Export JSON Settings</legend> <legend class="des">{#IM_EXPORT}</legend>
<div class="row mb-4 mt-4"> <div class="row mb-4 mt-4">
<div class="col-12 col-sm-3">Import</div> <div class="col-12 col-sm-3">{#IMPORT}</div>
<div class="col-12 col-sm-9"> <div class="col-12 col-sm-9">
<form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8"> <form id="form" method="POST" action="/upload" enctype="multipart/form-data" accept-charset="utf-8">
<div class="row"> <div class="row">
@ -326,9 +322,9 @@
</div> </div>
</div> </div>
<div class="row mb-4 mt-4"> <div class="row mb-4 mt-4">
<div class="col-12 col-sm-3 my-2">Export</div> <div class="col-12 col-sm-3 my-2">{#EXPORT}</div>
<div class="col-12 col-sm-9"> <div class="col-12 col-sm-9">
<a class="btn" href="/get_setup" target="_blank">Export settings (JSON file)</a><span> (only values, passwords will be removed!)</span> <a class="btn" href="/get_setup" target="_blank">{#BTN_EXPORT}</a><span> {#EXPORT_NOTE}</span>
</div> </div>
</div> </div>
</fieldset> </fieldset>
@ -341,7 +337,7 @@
var ts = 0; var ts = 0;
var esp8266pins = [ var esp8266pins = [
[255, "off / default"], [255, "{#PIN_OFF}"],
[0, "D3 (GPIO0)"], [0, "D3 (GPIO0)"],
[1, "TX (GPIO1)"], [1, "TX (GPIO1)"],
[2, "D4 (GPIO2)"], [2, "D4 (GPIO2)"],
@ -358,12 +354,12 @@
[13, "D7 (GPIO13)"], [13, "D7 (GPIO13)"],
[14, "D5 (GPIO14)"], [14, "D5 (GPIO14)"],
[15, "D8 (GPIO15)"], [15, "D8 (GPIO15)"],
[16, "D0 (GPIO16 - no IRQ!)"] [16, "D0 (GPIO16 - {#PIN_NO_IRQ})"]
]; ];
/*IF_ESP32*/ /*IF_ESP32*/
var esp32pins = [ var esp32pins = [
[255, "off / default"], [255, "{#PIN_OFF}"],
[0, "GPIO0"], [0, "GPIO0"],
[1, "TX (GPIO1)"], [1, "TX (GPIO1)"],
[2, "GPIO2 (LED)"], [2, "GPIO2 (LED)"],
@ -386,14 +382,14 @@
[27, "GPIO27"], [27, "GPIO27"],
[32, "GPIO32"], [32, "GPIO32"],
[33, "GPIO33"], [33, "GPIO33"],
[34, "GPIO34 (in only)"], [34, "GPIO34 ({#PIN_INPUT_ONLY})"],
[35, "GPIO35 (in only)"], [35, "GPIO35 ({#PIN_INPUT_ONLY})"],
[36, "VP (GPIO36, in only)"], [36, "VP (GPIO36, {#PIN_INPUT_ONLY})"],
[39, "VN (GPIO39, in only)"] [39, "VN (GPIO39, {#PIN_INPUT_ONLY})"]
]; ];
var esp32s3pins = [ var esp32s3pins = [
[255, "off / default"], [255, "off / default"],
[0, "GPIO0 (DONT USE - BOOT)"], [0, "GPIO0 ({#PIN_DONT_USE} - BOOT)"],
[1, "GPIO1"], [1, "GPIO1"],
[2, "GPIO2"], [2, "GPIO2"],
[3, "GPIO3"], [3, "GPIO3"],
@ -412,16 +408,16 @@
[16, "GPIO16"], [16, "GPIO16"],
[17, "GPIO17"], [17, "GPIO17"],
[18, "GPIO18"], [18, "GPIO18"],
[19, "GPIO19 (DONT USE - USB-)"], [19, "GPIO19 ({#PIN_DONT_USE} - USB-)"],
[20, "GPIO20 (DONT USE - USB+)"], [20, "GPIO20 ({#PIN_DONT_USE} - USB+)"],
[21, "GPIO21"], [21, "GPIO21"],
[26, "GPIO26 (PSRAM - not available)"], [26, "GPIO26 (PSRAM - {#PIN_NOT_AVAIL})"],
[27, "GPIO27 (FLASH - not available)"], [27, "GPIO27 (FLASH - {#PIN_NOT_AVAIL})"],
[28, "GPIO28 (FLASH - not available)"], [28, "GPIO28 (FLASH - {#PIN_NOT_AVAIL})"],
[29, "GPIO29 (FLASH - not available)"], [29, "GPIO29 (FLASH - {#PIN_NOT_AVAIL})"],
[30, "GPIO30 (FLASH - not available)"], [30, "GPIO30 (FLASH - {#PIN_NOT_AVAIL})"],
[31, "GPIO31 (FLASH - not available)"], [31, "GPIO31 (FLASH - {#PIN_NOT_AVAIL})"],
[32, "GPIO32 (FLASH - not available)"], [32, "GPIO32 (FLASH - {#PIN_NOT_AVAIL})"],
[33, "GPIO33 (not exposed on WROOM modules)"], [33, "GPIO33 (not exposed on WROOM modules)"],
[34, "GPIO34 (not exposed on WROOM modules)"], [34, "GPIO34 (not exposed on WROOM modules)"],
[35, "GPIO35"], [35, "GPIO35"],
@ -434,8 +430,8 @@
[42, "GPIO42"], [42, "GPIO42"],
[43, "GPIO43"], [43, "GPIO43"],
[44, "GPIO44"], [44, "GPIO44"],
[45, "GPIO45 (DONT USE - STRAPPING PIN)"], [45, "GPIO45 ({#PIN_DONT_USE} - STRAPPING PIN)"],
[46, "GPIO46 (DONT USE - STRAPPING PIN)"], [46, "GPIO46 ({#PIN_DONT_USE} - STRAPPING PIN)"],
[47, "GPIO47"], [47, "GPIO47"],
[48, "GPIO48"], [48, "GPIO48"],
]; ];
@ -459,17 +455,17 @@
[15, "GPIO15 (PSRAM/FLASH)"], [15, "GPIO15 (PSRAM/FLASH)"],
[16, "GPIO16 (PSRAM/FLASH)"], [16, "GPIO16 (PSRAM/FLASH)"],
[17, "GPIO17 (PSRAM/FLASH)"], [17, "GPIO17 (PSRAM/FLASH)"],
[18, "GPIO18 (DONT USE - USB-)"], [18, "GPIO18 ({#PIN_DONT_USE} - USB-)"],
[19, "GPIO19 (DONT USE - USB+)"], [19, "GPIO19 ({#PIN_DONT_USE} - USB+)"],
[20, "GPIO20 (RX)"], [20, "GPIO20 (RX)"],
[21, "GPIO21 (TX)"], [21, "GPIO21 (TX)"],
]; ];
/*ENDIF_ESP32*/ /*ENDIF_ESP32*/
var nrfPa = [ var nrfPa = [
[0, "MIN (recommended)"], [0, "MIN ({#PIN_RECOMMENDED})"],
[1, "LOW"], [1, "LOW"],
[2, "HIGH"], [2, "HIGH"],
[3, "MAX (experimental)"] [3, "MAX ({#PIN_EXPERIMENTAL})"]
]; ];
var esp32cmtPa = []; var esp32cmtPa = [];
var esp32cmtFreq = []; var esp32cmtFreq = [];
@ -487,8 +483,8 @@
} }
/*ENDIF_ESP32*/ /*ENDIF_ESP32*/
var led_high_active = [ var led_high_active = [
[0, "low active"], [0, "{#PIN_LOW_ACTIVE}"],
[1, "high active"], [1, "{#PIN_HIGH_ACTIVE}"],
]; ];
window.onload = function() { window.onload = function() {
@ -513,31 +509,31 @@
var e = document.getElementById("networks"); var e = document.getElementById("networks");
selDelAllOpt(e); selDelAllOpt(e);
if(obj["success"]) if(obj["success"])
e.appendChild(opt("-1", "scanning ...")) e.appendChild(opt("-1", "{#NETWORK_SCANNING}"))
else else
e.appendChild(opt("-1", "Error: " + obj["error"])); e.appendChild(opt("-1", "{#ERROR} " + obj["error"]));
} }
function apiCbNtp(obj) { function apiCbNtp(obj) {
var e = document.getElementById("apiResultNtp"); var e = document.getElementById("apiResultNtp");
if(obj["success"]) if(obj["success"])
e.innerHTML = "command executed, set new time ..."; e.innerHTML = "{#NTP_COMMAND_EXE}";
else else
e.innerHTML = "Error: " + obj["error"]; e.innerHTML = "{#ERROR} " + obj["error"];
} }
function apiCbNtp2(obj) { function apiCbNtp2(obj) {
var e = document.getElementById("apiResultNtp"); var e = document.getElementById("apiResultNtp");
var date = new Date(obj["ts_now"] * 1000); var date = new Date(obj["ts_now"] * 1000);
e.innerHTML = "synced at: " + toIsoDateStr(date) + ", difference: " + (ts - obj["ts_now"]) + "ms"; e.innerHTML = "{#NTP_SYNCED_AT}: " + toIsoDateStr(date) + ", {#NTP_DIFF}: " + (ts - obj["ts_now"]) + "ms";
} }
function apiCbMqtt(obj) { function apiCbMqtt(obj) {
var e = document.getElementById("apiResultMqtt"); var e = document.getElementById("apiResultMqtt");
if(obj["success"]) if(obj["success"])
e.innerHTML = "command executed"; e.innerHTML = "{#MQTT_EXE}";
else else
e.innerHTML = "Error: " + obj["error"]; e.innerHTML = "{#ERROR} " + obj["error"];
} }
function setTime() { function setTime() {
@ -572,7 +568,7 @@
function hide() { function hide() {
document.getElementById("form").submit(); document.getElementById("form").submit();
var e = document.getElementById("content"); var e = document.getElementById("content");
e.replaceChildren(span("upload started")); e.replaceChildren(span("{#IMPORT_UPLOAD_STARTED}"));
} }
function delIv() { function delIv() {
@ -626,7 +622,7 @@
if(!obj["pwd_set"]) if(!obj["pwd_set"])
e.value = ""; e.value = "";
var d = document.getElementById("prot_mask"); var d = document.getElementById("prot_mask");
var a = ["Index", "Live", "Webserial", "Settings", "Update", "System"]; var a = ["Index", "{#NAV_LIVE}", "{#NAV_WEBSERIAL}", "{#NAV_SETTINGS}", "Update", "System"];
var el = []; var el = [];
for(var i = 0; i < 6; i++) { for(var i = 0; i < 6; i++) {
var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i)); var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i));
@ -657,13 +653,13 @@
ml("th", {style: "width: 10%; text-align: center;"}, ""), ml("th", {style: "width: 10%; text-align: center;"}, ""),
ml("th", {}, "Name"), ml("th", {}, "Name"),
ml("th", {}, "Serial"), ml("th", {}, "Serial"),
ml("th", {style: "width: 10%; text-align: center;"}, "Edit"), ml("th", {style: "width: 10%; text-align: center;"}, "{#INV_EDIT}"),
ml("th", {style: "width: 10%; text-align: center;"}, "Delete") ml("th", {style: "width: 10%; text-align: center;"}, "{#INV_DELETE}")
])); ]));
for(let i = 0; i < obj.inverter.length; i++) { for(let i = 0; i < obj.inverter.length; i++) {
lines.push(ml("tr", {}, [ lines.push(ml("tr", {}, [
ml("td", {}, badge(obj.inverter[i].enabled, (obj.inverter[i].enabled) ? "enabled" : "disabled")), ml("td", {}, badge(obj.inverter[i].enabled, (obj.inverter[i].enabled) ? "{#ENABLED}" : "{#DISABLED}")),
ml("td", {}, obj.inverter[i].name), ml("td", {}, obj.inverter[i].name),
ml("td", {}, String(obj.inverter[i].serial)), ml("td", {}, String(obj.inverter[i].serial)),
ml("td", {style: "text-align: center;", onclick: function() {ivModal(obj.inverter[i]);}}, svg(iconGear, 25, 25, "icon icon-fg pointer")), ml("td", {style: "text-align: center;", onclick: function() {ivModal(obj.inverter[i]);}}, svg(iconGear, 25, 25, "icon icon-fg pointer")),
@ -686,7 +682,7 @@
e.innerHTML = ""; // remove all childs e.innerHTML = ""; // remove all childs
e.append(ml("table", {class: "table"}, ml("tbody", {}, lines))); e.append(ml("table", {class: "table"}, ml("tbody", {}, lines)));
if(obj.max_num_inverters > obj.inverter.length) if(obj.max_num_inverters > obj.inverter.length)
e.append(ml("div", {class: "row my-3"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "add Inverter", class: "btn", onclick: function() { ivModal(add); }}, null)))); e.append(ml("div", {class: "row my-3"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "{#BTN_INV_ADD}", class: "btn", onclick: function() { ivModal(add); }}, null))));
ivGlob(obj); ivGlob(obj);
} }
@ -694,10 +690,10 @@
function ivModal(obj) { function ivModal(obj) {
var lines = []; var lines = [];
lines.push(ml("tr", {}, [ lines.push(ml("tr", {}, [
ml("th", {style: "width: 10%;"}, "Input"), ml("th", {style: "width: 10%;"}, "{#INV_INPUT}"),
ml("th", {}, "Max Module Power [Wp]"), ml("th", {}, "{#INV_MAX_MODULE_POWER} [Wp]"),
ml("th", {}, "Name (optional)"), ml("th", {}, "Name (optional)"),
ml("th", {}, "Yield Correction [kWh] (optional)") ml("th", {}, "{#INV_YIELD_CORR} [kWh] (optional)")
])); ]));
for(let i = 0; i < 6; i++) { for(let i = 0; i < 6; i++) {
@ -718,14 +714,14 @@
var ser = ml("input", {name: "ser", class: "text", type: "number", max: 138999999999, value: obj.serial}, null); var ser = ml("input", {name: "ser", class: "text", type: "number", max: 138999999999, value: obj.serial}, null);
var html = ml("div", {}, [ var html = ml("div", {}, [
tabs(["General", "Inputs", "Radio", "Advanced"]), tabs(["{#TAB_GENERAL}", "{#TAB_INPUTS}", "{#TAB_RADIO}", "{#TAB_ADVANCED}"]),
ml("div", {id: "divGeneral", class: "tab-content"}, [ ml("div", {id: "div{#TAB_GENERAL}", class: "tab-content"}, [
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-2"}, "Enable"), ml("div", {class: "col-2"}, "{#INV_ENABLE}"),
ml("div", {class: "col-10"}, cbEn) ml("div", {class: "col-10"}, cbEn)
]), ]),
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-2 mt-2"}, "Serial"), ml("div", {class: "col-2 mt-2"}, "{#INV_SERIAL}"),
ml("div", {class: "col-10"}, ser) ml("div", {class: "col-10"}, ser)
]), ]),
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
@ -733,45 +729,45 @@
ml("div", {class: "col-10"}, ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null)) ml("div", {class: "col-10"}, ml("input", {name: "name", class: "text", type: "text", value: obj.name}, null))
]) ])
]), ]),
ml("div", {id: "divInputs", class: "tab-content hide"}, [ ml("div", {id: "div{#TAB_INPUTS}", class: "tab-content hide"}, [
ml("div", {class: "row mb-3"}, ml("div", {class: "row mb-3"},
ml("table", {class: "table"}, ml("table", {class: "table"},
ml("tbody", {}, lines) ml("tbody", {}, lines)
) )
) )
]), ]),
ml("div", {id: "divRadio", class: "tab-content hide"}, [ ml("div", {id: "div{#TAB_RADIO}", class: "tab-content hide"}, [
ml("input", {type: "hidden", name: "isnrf"}, null), ml("input", {type: "hidden", name: "isnrf"}, null),
ml("div", {id: "setcmt"}, [ ml("div", {id: "setcmt"}, [
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-3 mt-2"}, "Frequency"), ml("div", {class: "col-3 mt-2"}, "{#INV_FREQUENCY}"),
ml("div", {class: "col-9"}, sel("freq", esp32cmtFreq, obj.freq)) ml("div", {class: "col-9"}, sel("freq", esp32cmtFreq, obj.freq))
]), ]),
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-3 mt-2"}, "Power Level"), ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"),
ml("div", {class: "col-9"}, sel("cmtpa", esp32cmtPa, obj.pa)) ml("div", {class: "col-9"}, sel("cmtpa", esp32cmtPa, obj.pa))
]), ]),
]), ]),
ml("div", {id: "setnrf"}, ml("div", {id: "setnrf"},
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-3 mt-2"}, "Power Level"), ml("div", {class: "col-3 mt-2"}, "{#INV_POWER_LEVEL}"),
ml("div", {class: "col-9"}, sel("nrfpa", nrfPa, obj.pa)) ml("div", {class: "col-9"}, sel("nrfpa", nrfPa, obj.pa))
]), ]),
), ),
]), ]),
ml("div", {id: "divAdvanced", class: "tab-content hide"}, [ ml("div", {id: "div{#TAB_ADVANCED}", class: "tab-content hide"}, [
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-10"}, "Pause communication during night (lat. and lon. need to be set)"), ml("div", {class: "col-10"}, "{#INV_PAUSE_DURING_NIGHT}"),
ml("div", {class: "col-2"}, cbDisNightCom) ml("div", {class: "col-2"}, cbDisNightCom)
]), ]),
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-10"}, "Include inverter to sum of total (should be checked by default, MqTT only)"), ml("div", {class: "col-10"}, "{#INV_INCLUDE_MQTT_SUM}"),
ml("div", {class: "col-2"}, cbAddTotal) ml("div", {class: "col-2"}, cbAddTotal)
]) ])
]), ]),
ml("div", {class: "row mt-5"}, [ ml("div", {class: "row mt-5"}, [
ml("div", {class: "col-8", id: "res"}, ""), ml("div", {class: "col-8", id: "res"}, ""),
ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "save", class: "btn", onclick: function() { ivSave(); }}, null)) ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#BTN_SAVE}", class: "btn", onclick: function() { ivSave(); }}, null))
]) ])
]); ]);
@ -808,7 +804,7 @@
}) })
}); });
modal("Edit inverter " + obj.name, html); modal("{#INV_EDIT_MODAL}: " + obj.name, html);
ser.dispatchEvent(new Event('change')); ser.dispatchEvent(new Event('change'));
function ivSave() { function ivSave() {
@ -839,7 +835,7 @@
function cb(obj2) { function cb(obj2) {
var e = document.getElementById("res"); var e = document.getElementById("res");
if(!obj2.success) if(!obj2.success)
e.innerHTML = "error: " + obj2.error; e.innerHTML = "{#ERROR}" + obj2.error;
else { else {
modalClose(); modalClose();
getAjax("/api/inverter/list", parseIv); getAjax("/api/inverter/list", parseIv);
@ -849,10 +845,10 @@
function ivDel(obj) { function ivDel(obj) {
var html = ml("div", {class: "row"}, [ var html = ml("div", {class: "row"}, [
ml("div", {class: "col-9"}, "do you realy want to delete inverter " + obj.name + "?"), ml("div", {class: "col-9"}, "{#INV_DELETE_SURE} (" + obj.name + ")"),
ml("div", {class: "col-3 a-r"}, ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "yes", class: "btn", onclick: function() { del(); }}, null))) ml("div", {class: "col-3 a-r"}, ml("div", {class: "col-4 a-r"}, ml("input", {type: "button", value: "{#BTN_YES}", class: "btn", onclick: function() { del(); }}, null)))
]); ]);
modal("Delete inverter " + obj.name, html); modal("{#INV_DELETE_MODAL}: " + obj.name, html);
function del() { function del() {
var o = new Object(); var o = new Object();
@ -896,7 +892,7 @@
for(p of [["sunOffsSr", "offsSr"], ["sunOffsSs", "offsSs"]]) { for(p of [["sunOffsSr", "offsSr"], ["sunOffsSs", "offsSs"]]) {
const sel = document.getElementsByName(p[0])[0]; const sel = document.getElementsByName(p[0])[0];
for(var i = -60; i <= 60; i++) { for(var i = -60; i <= 60; i++) {
sel.appendChild(opt(i, i + " minutes", (i == (obj[p[1]] / 60)))); sel.appendChild(opt(i, i + " {#NTP_MINUTES}", (i == (obj[p[1]] / 60))));
} }
} }
} }
@ -909,7 +905,7 @@
if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins; if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins;
else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins;
/*ENDIF_ESP32*/ /*ENDIF_ESP32*/
pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected'], ['led2', 'pinLed2', 'Night time']]; pins = [['led0', 'pinLed0', '{#LED_AT_LEAST_ONE_PRODUCING}'], ['led1', 'pinLed1', '{#LED_MQTT_CONNECTED}'], ['led2', 'pinLed2', '{#LED_NIGHT_TIME}']];
for(p of pins) { for(p of pins) {
e.append( e.append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
@ -922,13 +918,13 @@
} }
e.append( e.append(
ml("div", { class: "row mb-3" }, [ ml("div", { class: "row mb-3" }, [
ml("div", { class: "col-12 col-sm-3 my-2" }, "LED polarity"), ml("div", { class: "col-12 col-sm-3 my-2" }, "{#LED_POLARITY}"),
ml("div", { class: "col-12 col-sm-9" }, ml("div", { class: "col-12 col-sm-9" },
sel('pinLedHighActive', led_high_active, obj.led_high_active) sel('pinLedHighActive', led_high_active, obj.led_high_active)
) )
]), ]),
ml("div", { class: "row mb-3" }, [ ml("div", { class: "row mb-3" }, [
ml("div", { class: "col-12 col-sm-3 my-2" }, "LED luminance (0-255)"), ml("div", { class: "col-12 col-sm-3 my-2" }, "{#LED_LUMINANCE} (0-255)"),
ml("div", { class: "col-12 col-sm-9" }, ml("input", {class: "text", type: "number", name: "pinLedLum", value: obj.led_lum, min: 0, max: 255}, null)) ml("div", { class: "col-12 col-sm-9" }, ml("input", {class: "text", type: "number", name: "pinLedLum", value: obj.led_lum, min: 0, max: 255}, null))
]) ])
) )
@ -948,7 +944,7 @@
e.replaceChildren ( e.replaceChildren (
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-8 col-sm-3 my-2"}, "NRF24 Enable"), ml("div", {class: "col-8 col-sm-3 my-2"}, "{#NRF24_ENABLE}"),
ml("div", {class: "col-4 col-sm-9"}, en) ml("div", {class: "col-4 col-sm-9"}, en)
]) ])
); );
@ -982,7 +978,7 @@
e.replaceChildren ( e.replaceChildren (
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-8 col-sm-3 my-2"}, "CMT2300A Enable"), ml("div", {class: "col-8 col-sm-3 my-2"}, "{#CMT_ENABLE}"),
ml("div", {class: "col-4 col-sm-9"}, en) ml("div", {class: "col-4 col-sm-9"}, en)
]) ])
); );
@ -1033,7 +1029,7 @@
); );
} }
// keep display types grouped // keep display types grouped
var opts = [[0, "None"], var opts = [[0, "{#DISP_NONE}"],
[2, "SH1106 128x64 (1.3\")"], [2, "SH1106 128x64 (1.3\")"],
[5, "SSD1306 64x48 (0.66\" Wemos OLED Shield)"], [5, "SSD1306 64x48 (0.66\" Wemos OLED Shield)"],
[4, "SSD1306 128x32 (0.91\")"], [4, "SSD1306 128x32 (0.91\")"],
@ -1046,7 +1042,7 @@
var dtype_sel = sel("disp_typ", opts, obj["disp_typ"]); var dtype_sel = sel("disp_typ", opts, obj["disp_typ"]);
document.getElementById("dispType").append( document.getElementById("dispType").append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, "Type"), ml("div", {class: "col-12 col-sm-3 my-2"}, "{#DISP_TYPE}"),
ml("div", {class: "col-12 col-sm-9"}, dtype_sel) ml("div", {class: "col-12 col-sm-9"}, dtype_sel)
]) ])
); );
@ -1057,22 +1053,22 @@
/*ENDIF_ESP32*/ /*ENDIF_ESP32*/
document.getElementById("dispRot").append( document.getElementById("dispRot").append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, "Rotation"), ml("div", {class: "col-12 col-sm-3 my-2"}, "{#DISP_ROTATION}"),
ml("div", {class: "col-12 col-sm-9"}, sel("disp_rot", opts, obj["disp_rot"])) ml("div", {class: "col-12 col-sm-9"}, sel("disp_rot", opts, obj["disp_rot"]))
]) ])
); );
var opts1 = [[0, "off"], [1, "pixel shift"], [2, "motion sensor"]]; var opts1 = [[0, "{#DISP_SCREENSAVER_OFF}"], [1, "{#DISP_PIXEL_SHIFT}"], [2, "{#DISP_MOTION_SENS}"]];
var screensaver_sel = sel("disp_screensaver", opts1, obj["disp_screensaver"]); var screensaver_sel = sel("disp_screensaver", opts1, obj["disp_screensaver"]);
screensaver_sel.id = 'disp_screensaver'; screensaver_sel.id = 'disp_screensaver';
document.getElementById("screenSaver").append( document.getElementById("screenSaver").append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-3 my-2"}, "Screensaver (OLED only)"), ml("div", {class: "col-12 col-sm-3 my-2"}, "{#DISP_SCREENSAVER}"),
ml("div", {class: "col-12 col-sm-9"}, screensaver_sel) ml("div", {class: "col-12 col-sm-9"}, screensaver_sel)
]) ])
); );
var esp8266pirpins = [[255, "off / default"], var esp8266pirpins = [[255, "{#PIN_OFF}"],
[17, "A0"]]; [17, "A0"]];
document.getElementById("pirPin").append( document.getElementById("pirPin").append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
@ -1155,12 +1151,12 @@
var s = document.getElementById("networks"); var s = document.getElementById("networks");
selDelAllOpt(s); selDelAllOpt(s);
if(root["networks"].length > 0) { if(root["networks"].length > 0) {
s.appendChild(opt("-1", "please select network")); s.appendChild(opt("-1", "{#NETWORK_PLEASE_SELECT}"));
for(i = 0; i < root["networks"].length; i++) { for(i = 0; i < root["networks"].length; i++) {
s.appendChild(opt(root["networks"][i]["ssid"], root["networks"][i]["ssid"] + " (" + root["networks"][i]["rssi"] + " dBm)")); s.appendChild(opt(root["networks"][i]["ssid"], root["networks"][i]["ssid"] + " (" + root["networks"][i]["rssi"] + " dBm)"));
} }
} else } else
s.appendChild(opt("-1", "no network found")); s.appendChild(opt("-1", "{#NO_NETWORK_FOUND}"));
} }
function selNet() { function selNet() {

36
src/web/html/system.html

@ -46,7 +46,7 @@
function irqBadge(state) { function irqBadge(state) {
switch(state) { switch(state) {
case 0: return badge(false, "unknown", "warning"); break; case 0: return badge(false, "{#UNKNOWN}", "warning"); break;
case 1: return badge(true, "true"); break; case 1: return badge(true, "true"); break;
default: return badge(false, "false"); break; default: return badge(false, "false"); break;
} }
@ -57,16 +57,16 @@
if(obj.radioNrf.en) { if(obj.radioNrf.en) {
lines = [ lines = [
tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "not ") + "connected")), tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "{#NOT} ") + "{#CONNECTED}")),
tr("Interrupt Pin working", irqBadge(obj.radioNrf.irqOk)), tr("{#IRQ_WORKING}", irqBadge(obj.radioNrf.irqOk)),
tr("NRF24 Data Rate", dr[obj.radioNrf.dataRate] + "bps"), tr("{#NRF24_DATA_RATE}", dr[obj.radioNrf.dataRate] + "bps"),
tr("DTU Radio ID", obj.radioNrf.sn) tr("DTU Radio ID", obj.radioNrf.sn)
]; ];
} else } else
lines = [tr("NRF24L01", badge(false, "not enabled"))]; lines = [tr("NRF24L01", badge(false, "{#NOT_ENABLED}"))];
document.getElementById("info").append( document.getElementById("info").append(
headline("Radio NRF"), headline("{#NRF24_RADIO}"),
ml("table", {class: "table"}, ml("table", {class: "table"},
ml("tbody", {}, lines) ml("tbody", {}, lines)
) )
@ -75,15 +75,15 @@
/*IF_ESP32*/ /*IF_ESP32*/
if(obj.radioCmt.en) { if(obj.radioCmt.en) {
cmt = [ cmt = [
tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "not ") + "connected")), tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "{#NOT} ") + "{#CONNECTED}")),
tr("Interrupt Pin working", irqBadge(obj.radioCmt.irqOk)), tr("{#IRQ_WORKING}", irqBadge(obj.radioCmt.irqOk)),
tr("DTU Radio ID", obj.radioCmt.sn) tr("DTU Radio ID", obj.radioCmt.sn)
]; ];
} else } else
cmt = [tr("CMT2300A", badge(false, "not enabled"))]; cmt = [tr("CMT2300A", badge(false, "{#NOT_ENABLED}"))];
document.getElementById("info").append( document.getElementById("info").append(
headline("Radio CMT"), headline("{#CMT_RADIO}"),
ml("table", {class: "table"}, ml("table", {class: "table"},
ml("tbody", {}, cmt) ml("tbody", {}, cmt)
) )
@ -94,13 +94,13 @@
function parseMqtt(obj) { function parseMqtt(obj) {
if(obj.enabled) { if(obj.enabled) {
lines = [ lines = [
tr("connected", badge(obj.connected, ((obj.connected) ? "true" : "false"))), tr("{#CONNECTED}", badge(obj.connected, ((obj.connected) ? "{#TRUE}" : "{#FALSE}"))),
tr("#TX", obj.tx_cnt), tr("#TX", obj.tx_cnt),
tr("#RX", obj.rx_cnt) tr("#RX", obj.rx_cnt)
]; ];
} else } else
lines = tr("enabled", badge(false, "false")); lines = tr("{#ENABLED}", badge(false, "{#FALSE}"));
document.getElementById("info").append( document.getElementById("info").append(
headline("MqTT"), headline("MqTT"),
@ -113,14 +113,14 @@
function parseIndex(obj) { function parseIndex(obj) {
if(obj.ts_sunrise > 0) { if(obj.ts_sunrise > 0) {
document.getElementById("info").append( document.getElementById("info").append(
headline("Sun"), headline("{#SUN}"),
ml("table", {class: "table"}, ml("table", {class: "table"},
ml("tbody", {}, [ ml("tbody", {}, [
tr("Sunrise", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')), tr("{#SUNRISE}", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')),
tr("Sunset", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')), tr("{#SUNSET}", new Date(obj.ts_sunset * 1000).toLocaleString('de-DE')),
tr("Communication start", new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')), tr("{#COMMUNICATION_START}", new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE')),
tr("Communication stop", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')), tr("{#COMMUNICATION_STTOP}", new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')),
tr("Night behaviour", badge(obj.disNightComm, ((obj.disNightComm) ? "not" : "") + " communicating", "warning")) tr("{#NIGHT_BEHAVE}", badge(obj.disNightComm, ((obj.disNightComm) ? "{#NOT}" : "") + " {#COMMUNICATING}", "warning"))
]) ])
) )
); );

8
src/web/html/update.html

@ -9,14 +9,14 @@
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<fieldset> <fieldset>
<legend class="des">Select firmware file (*.bin)</legend> <legend class="des">{#SELECT_FILE} (*.bin)</legend>
<form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8"> <form id="form" method="POST" action="/update" enctype="multipart/form-data" accept-charset="utf-8">
<input type="file" name="update"> <input type="file" name="update">
<input type="button" class="btn my-4" value="Update" onclick="hide()"> <input type="button" class="btn my-4" value="{#BTN_UPDATE}" onclick="hide()">
</form> </form>
</fieldset> </fieldset>
<div class="row mt-4"> <div class="row mt-4">
<a href="https://fw.ahoydtu.de" target="_blank">Download latest Release and Development versions<a/> <a href="https://fw.ahoydtu.de" target="_blank">{#DOWNLOADS}<a/>
</div> </div>
</div> </div>
</div> </div>
@ -31,7 +31,7 @@
function hide() { function hide() {
document.getElementById("form").submit(); document.getElementById("form").submit();
var e = document.getElementById("content"); var e = document.getElementById("content");
e.replaceChildren(span("update started")); e.replaceChildren(span("{#UPDATE_STARTED}"));
} }
getAjax("/api/generic", parseGeneric); getAjax("/api/generic", parseGeneric);

136
src/web/html/visualization.html

@ -10,7 +10,7 @@
<div id="wrapper"> <div id="wrapper">
<div id="content"> <div id="content">
<div id="live"></div> <div id="live"></div>
<p>Every <span id="refresh"></span> seconds the values are updated</p> <p>{#EVERY} <span id="refresh"></span> {#UPDATE_SECS}</p>
</div> </div>
</div> </div>
{#HTML_FOOTER} {#HTML_FOOTER}
@ -70,20 +70,20 @@
ml("div", {class: "col"}, [ ml("div", {class: "col"}, [
ml("div", {class: "p-2 total-h"}, ml("div", {class: "p-2 total-h"},
ml("div", {class: "row"}, ml("div", {class: "row"},
ml("div", {class: "col mx-2 mx-md-1"}, "TOTAL") ml("div", {class: "col mx-2 mx-md-1"}, "{#TOTAL}")
), ),
), ),
ml("div", {class: "p-2 total-bg"}, [ ml("div", {class: "p-2 total-bg"}, [
ml("div", {class: "row"}, [ ml("div", {class: "row"}, [
numBig(total[0], "W", "AC Power"), numBig(total[0], "W", "{#AC_POWER}"),
numBig(total[1], "Wh", "Yield Day"), numBig(total[1], "Wh", "{#YIELD_DAY}"),
numBig(total[2], "kWh", "Yield Total") numBig(total[2], "kWh", "{#YIELD_TOTAL}")
]), ]),
ml("div", {class: "hr"}), ml("div", {class: "hr"}),
ml("div", {class: "row"}, [ ml("div", {class: "row"}, [
numMid(total[3], "W", "Max Power"), numMid(total[3], "W", "{#MAX_POWER}"),
numMid(total[4], "W", "DC Power"), numMid(total[4], "W", "{#DC_POWER}"),
numMid(total[5], "var", "Reactive Power") numMid(total[5], "var", "{#REACTIVE_POWER}")
]) ])
]) ])
]) ])
@ -119,31 +119,31 @@
getAjax("/api/inverter/version/" + obj.id, parseIvVersion); getAjax("/api/inverter/version/" + obj.id, parseIvVersion);
}}, obj.name)), }}, obj.name)),
ml("div", {class: "col a-c", onclick: function() {limitModal(obj)}}, [ ml("div", {class: "col a-c", onclick: function() {limitModal(obj)}}, [
ml("span", {class: "d-none d-sm-block pointer"}, "Active Power Control: " + pwrLimit), ml("span", {class: "d-none d-sm-block pointer"}, "{#ACTIVE_POWER_CONTROL}: " + pwrLimit),
ml("span", {class: "d-block d-sm-none pointer"}, "APC: " + pwrLimit) ml("span", {class: "d-block d-sm-none pointer"}, "{#APC}: " + pwrLimit)
]), ]),
ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() { ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() {
getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm); getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm);
}}, ("Alarms: " + obj.alarm_cnt))), }}, ("{#ALARMS}: " + obj.alarm_cnt))),
ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5].toFixed(1)) + t.innerText) ml("div", {class: "col a-r mx-2 mx-md-1"}, String(obj.ch[0][5].toFixed(1)) + t.innerText)
]) ])
), ),
ml("div", {class: "p-2 " + clbg}, [ ml("div", {class: "p-2 " + clbg}, [
ml("div", {class: "row"},[ ml("div", {class: "row"},[
numBig(obj.ch[0][2], "W", "AC Power"), numBig(obj.ch[0][2], "W", "{#AC_POWER}"),
numBig(obj.ch[0][7], "Wh", "Yield Day"), numBig(obj.ch[0][7], "Wh", "{#YIELD_DAY}"),
numBig(obj.ch[0][6], "kWh", "Yield Total") numBig(obj.ch[0][6], "kWh", "{#YIELD_TOTAL}")
]), ]),
ml("div", {class: "hr"}), ml("div", {class: "hr"}),
ml("div", {class: "row mt-2"},[ ml("div", {class: "row mt-2"},[
numMid(obj.ch[0][11], "W", "Max AC Power", {class: "fs-6 tooltip", data: maxAcPwr}), numMid(obj.ch[0][11], "W", "{#MAX_AC_POWER}", {class: "fs-6 tooltip", data: maxAcPwr}),
numMid(obj.ch[0][8], "W", "DC Power"), numMid(obj.ch[0][8], "W", "{#DC_POWER}"),
numMid(obj.ch[0][0], "V", "AC Voltage"), numMid(obj.ch[0][0], "V", "{#DC_VOLTAGE}"),
numMid(obj.ch[0][1], "A", "AC Current"), numMid(obj.ch[0][1], "A", "{#AC_CURRENT}"),
numMid(obj.ch[0][3], "Hz", "Frequency"), numMid(obj.ch[0][3], "Hz", "{#FREQUENCY}"),
numMid(obj.ch[0][9], "%", "Efficiency"), numMid(obj.ch[0][9], "%", "{#EFFICIENCY}"),
numMid(obj.ch[0][10], "var", "Reactive Power"), numMid(obj.ch[0][10], "var", "{#REACTIVE_POWER}"),
numMid(obj.ch[0][4], "", "Power Factor") numMid(obj.ch[0][4], "", "{#POWER_FACTOR}")
]) ])
]) ])
]) ])
@ -172,26 +172,26 @@
ml("div", {class: "p-2 a-c " + clh}, name), ml("div", {class: "p-2 a-c " + clh}, name),
ml("div", {class: "p-2 " + clbg}, [ ml("div", {class: "p-2 " + clbg}, [
ml("div", {class: "row"}, [ ml("div", {class: "row"}, [
numCh(vals[2], units[2], "DC Power"), numCh(vals[2], units[2], "{#DC_POWER}"),
numCh(vals[6], units[2], "Max Power"), numCh(vals[6], units[2], "{#MAX_POWER}"),
numCh(vals[5], units[5], "Irradiation"), numCh(vals[5], units[5], "{#IRRADIATION}"),
numCh(vals[3], units[3], "Yield Day"), numCh(vals[3], units[3], "{#YIELD_DAY}"),
numCh(vals[4], units[4], "Yield Total"), numCh(vals[4], units[4], "{#YIELD_TOTAL}"),
numCh(vals[0], units[0], "DC Voltage"), numCh(vals[0], units[0], "{#DC_VOLTAGE}"),
numCh(vals[1], units[1], "DC Current") numCh(vals[1], units[1], "{#DC_CURRENT}")
]) ])
]) ])
]); ]);
} }
function tsInfo(obj) { function tsInfo(obj) {
var ageInfo = "Last received data requested at: "; var ageInfo = "{#LAST_RECEIVED}: ";
if(obj.ts_last_success > 0) { if(obj.ts_last_success > 0) {
var date = new Date(obj.ts_last_success * 1000); var date = new Date(obj.ts_last_success * 1000);
ageInfo += toIsoDateStr(date); ageInfo += toIsoDateStr(date);
} }
else else
ageInfo += "nothing received"; ageInfo += "{#NOTHING_RECEIVED}";
if(obj.rssi > -127) { if(obj.rssi > -127) {
if(obj.generation < 2) if(obj.generation < 2)
@ -252,10 +252,10 @@
var offs = new Date().getTimezoneOffset() * -60; var offs = new Date().getTimezoneOffset() * -60;
html.push( html.push(
ml("div", {class: "row"}, [ ml("div", {class: "row"}, [
ml("div", {class: "col"}, ml("strong", {}, "Event")), ml("div", {class: "col"}, ml("strong", {}, "{#EVENT}")),
ml("div", {class: "col"}, ml("strong", {}, "ID")), ml("div", {class: "col"}, ml("strong", {}, "ID")),
ml("div", {class: "col"}, ml("strong", {}, "Start")), ml("div", {class: "col"}, ml("strong", {}, "Start")),
ml("div", {class: "col"}, ml("strong", {}, "End")) ml("div", {class: "col"}, ml("strong", {}, "{#END}"))
]) ])
); );
@ -271,7 +271,7 @@
); );
} }
} }
modal("Alarms of inverter " + obj.iv_name, ml("div", {}, html)); modal("{#ALARMS_MODAL}: " + obj.iv_name, ml("div", {}, html));
} }
function parseIvVersion(obj) { function parseIvVersion(obj) {
@ -283,7 +283,7 @@
case 3: model = "HMT-"; break; case 3: model = "HMT-"; break;
default: model = "???-"; break; default: model = "???-"; break;
} }
model += String(obj.max_pwr) + " (Serial: " + obj.serial + ")"; model += String(obj.max_pwr) + " ({#SERIAL}: " + obj.serial + ")";
var html = ml("table", {class: "table"}, [ var html = ml("table", {class: "table"}, [
@ -291,15 +291,15 @@
tr("Model", model), tr("Model", model),
tr("Firmware Version / Build", String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")"), tr("Firmware Version / Build", String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")"),
tr("Hardware Version / Build", (obj.hw_ver/100).toFixed(2) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")"), tr("Hardware Version / Build", (obj.hw_ver/100).toFixed(2) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")"),
tr("Hardware Number", obj.part_num.toString(16)), tr("{#HW_NUMBER}", obj.part_num.toString(16)),
tr("Bootloader Version", (obj.boot_ver/100).toFixed(2)), tr("Bootloader Version", (obj.boot_ver/100).toFixed(2)),
tr("Grid Profile", ml("input", {type: "button", value: "show", class: "btn", onclick: function() { tr("Grid Profile", ml("input", {type: "button", value: "{#BTN_SHOW}", class: "btn", onclick: function() {
modalClose(); modalClose();
getAjax("/api/inverter/grid/" + obj.id, showGridProfile); getAjax("/api/inverter/grid/" + obj.id, showGridProfile);
}}, null)) }}, null))
]) ])
]) ])
modal("Info for inverter " + obj.name, ml("div", {}, html)) modal("{#INV_INFO}: " + obj.name, ml("div", {}, html))
} }
function getGridValue(g) { function getGridValue(g) {
@ -332,9 +332,9 @@
)) ))
content.push(ml("div", {class: "row my-2"}, [ content.push(ml("div", {class: "row my-2"}, [
ml("div", {class: "col-4"}, ml("b", {}, "Name")), ml("div", {class: "col-4"}, ml("b", {}, "Name")),
ml("div", {class: "col-3"}, ml("b", {}, "Value")), ml("div", {class: "col-3"}, ml("b", {}, "{#VALUE}")),
ml("div", {class: "col-3"}, ml("b", {}, "Range")), ml("div", {class: "col-3"}, ml("b", {}, "{#RANGE}")),
ml("div", {class: "col-2"}, ml("b", {}, "Default")) ml("div", {class: "col-2"}, ml("b", {}, "{#DEFAULT}"))
])) ]))
for(e of g.info.group) { for(e of g.info.group) {
if(Array.isArray(e[id])) { if(Array.isArray(e[id])) {
@ -362,10 +362,10 @@
var v = getGridValue(glob); var v = getGridValue(glob);
if(null === g) { if(null === g) {
if(0 == obj.grid.length) { if(0 == obj.grid.length) {
content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "Profile was not read until now, maybe turned off?")))) content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#PROFILE_NOT_READ}?"))))
} else { } else {
content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("h5", {}, "Unknown Profile")))) content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("h5", {}, "{#UNKNOWN_PROFILE}"))))
content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "Please open a new issue at https://github.com/lumapu/ahoy and copy the raw data into it.")))) content.push(ml("div", {class: "row"}, ml("div", {class: "col"}, ml("p", {}, "{#OPEN_ISSUE}."))))
content.push(ml("div", {class: "row"}, ml("div", {class: "col my-2"}, ml("pre", {}, obj.grid)))) content.push(ml("div", {class: "row"}, ml("div", {class: "col my-2"}, ml("pre", {}, obj.grid))))
} }
} else { } else {
@ -378,7 +378,7 @@
} }
} }
modal("Grid Profile for inverter " + obj.name, ml("div", {}, ml("div", {class: "col mb-2"}, [...content]))) modal("{#PROFILE_MODAL}: " + obj.name, ml("div", {}, ml("div", {class: "col mb-2"}, [...content])))
}) })
} }
@ -386,56 +386,56 @@
function parseIvRadioStats(obj) { function parseIvRadioStats(obj) {
var html = ml("table", {class: "table"}, [ var html = ml("table", {class: "table"}, [
ml("tbody", {}, [ ml("tbody", {}, [
tr2(["TX count", obj.tx_cnt, ""]), tr2(["{#TX_COUNT}", obj.tx_cnt, ""]),
tr2(["RX success", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]), tr2(["{#RX_SUCCESS}", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
tr2(["RX fail", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]), tr2(["{#RX_FAIL}", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
tr2(["RX no answer", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]), tr2(["{#RX_NO_ANSWER}", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
tr2(["RX fragments", obj.frame_cnt, ""]), tr2(["{#RX_FRAGMENTS}", obj.frame_cnt, ""]),
tr2(["TX retransmits", obj.retransmits, ""]), tr2(["{#TX_RETRANSMITS}", obj.retransmits, ""]),
tr2(["Inverter loss rate", "lost " + obj.ivLoss + " of " + obj.ivSent, String(Math.round(obj.ivLoss / obj.ivSent * 10000) / 100) + "&nbsp;%"]), tr2(["{#INV_LOSS_RATE}", "{#LOST_1} " + obj.ivLoss + " {#LOST_2} " + obj.ivSent + " {#LOST_3}", String(Math.round(obj.ivLoss / obj.ivSent * 10000) / 100) + "&nbsp;%"]),
tr2(["DTU loss rate", "lost " + obj.dtuLoss + " of " + obj.dtuSent, String(Math.round(obj.dtuLoss / obj.dtuSent * 10000) / 100) + "&nbsp;%"]) tr2(["{#DTU_LOSS_RATE}", "{#LOST_1} " + obj.dtuLoss + " {#LOST_2} " + obj.dtuSent + " {#LOST_3}", String(Math.round(obj.dtuLoss / obj.dtuSent * 10000) / 100) + "&nbsp;%"])
]) ])
]) ])
modal("Radio statistics for inverter " + obj.name, ml("div", {}, html)) modal("{#RADIO_STAT_MODAL}: " + obj.name, ml("div", {}, html))
} }
function limitModal(obj) { function limitModal(obj) {
var opt = [["pct", "%"], ["watt", "W"]]; var opt = [["pct", "%"], ["watt", "W"]];
var html = ml("div", {}, [ var html = ml("div", {}, [
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-5 my-2"}, "Limit Value"), ml("div", {class: "col-12 col-sm-5 my-2"}, "{#LIMIT_VALUE}"),
ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number", step: "0.1", min: 1}, "")), ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number", step: "0.1", min: 1}, "")),
ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct")) ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct"))
]), ]),
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-8 col-sm-5"}, "Keep limit over inverter restart"), ml("div", {class: "col-8 col-sm-5"}, "{#KEEP_LIMIT}"),
ml("div", {class: "col-4 col-sm-7"}, ml("input", {type: "checkbox", name: "keep"})) ml("div", {class: "col-4 col-sm-7"}, ml("input", {type: "checkbox", name: "keep"}))
]), ]),
ml("div", {class: "row my-3"}, ml("div", {class: "row my-3"},
ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "Apply", class: "btn", onclick: function() { ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "{#BTN_APPLY}", class: "btn", onclick: function() {
applyLimit(obj.id); applyLimit(obj.id);
}}, null)) }}, null))
), ),
ml("div", {class: "row my-4"}, [ ml("div", {class: "row my-4"}, [
ml("div", {class: "col-12 col-sm-5 my-2"}, "Control"), ml("div", {class: "col-12 col-sm-5 my-2"}, "{#CONTROL}"),
ml("div", {class: "col col-sm-7 a-r"}, [ ml("div", {class: "col col-sm-7 a-r"}, [
ml("input", {type: "button", value: "restart", class: "btn", onclick: function() { ml("input", {type: "button", value: "{#RESTART}", class: "btn", onclick: function() {
applyCtrl(obj.id, "restart"); applyCtrl(obj.id, "restart");
}}, null), }}, null),
ml("input", {type: "button", value: "turn off", class: "btn mx-1", onclick: function() { ml("input", {type: "button", value: "{#TURN_OFF}", class: "btn mx-1", onclick: function() {
applyCtrl(obj.id, "power", 0); applyCtrl(obj.id, "power", 0);
}}, null), }}, null),
ml("input", {type: "button", value: "turn on", class: "btn", onclick: function() { ml("input", {type: "button", value: "{#TURN_ON}", class: "btn", onclick: function() {
applyCtrl(obj.id, "power", 1); applyCtrl(obj.id, "power", 1);
}}, null) }}, null)
]) ])
]), ]),
ml("div", {class: "row mt-1"}, [ ml("div", {class: "row mt-1"}, [
ml("div", {class: "col-12 col-sm-5 my-2"}, "Result"), ml("div", {class: "col-12 col-sm-5 my-2"}, "{#RESULT}"),
ml("div", {class: "col-sm-7 my-2"}, ml("span", {name: "pwrres"}, "-")) ml("div", {class: "col-sm-7 my-2"}, ml("span", {name: "pwrres"}, "-"))
]) ])
]); ]);
modal("Active Power Control for inverter " + obj.name, html); modal("{#POWER_LIMIT_MODAL}: " + obj.name, html);
} }
function applyLimit(id) { function applyLimit(id) {
@ -470,19 +470,19 @@
function ctrlCb(obj) { function ctrlCb(obj) {
var e = document.getElementsByName("pwrres")[0]; var e = document.getElementsByName("pwrres")[0];
if(obj.success) { if(obj.success) {
e.innerHTML = "received command, waiting for inverter acknowledge ..."; e.innerHTML = "{#CMD_RECEIVED_WAIT_ACK}";
tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000); tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000);
} }
else else
e.innerHTML = "Error: " + obj["error"]; e.innerHTML = "{#ERROR}: " + obj["error"];
} }
function ctrlCb2(obj) { function ctrlCb2(obj) {
var e = document.getElementsByName("pwrres")[0]; var e = document.getElementsByName("pwrres")[0];
if(obj.success) if(obj.success)
e.innerHTML = "command received"; e.innerHTML = "{#COMMAND_RECEIVED}";
else else
e.innerHTML = "Error: " + obj["error"]; e.innerHTML = "{#ERROR}: " + obj["error"];
} }
function updatePwrAck(obj) { function updatePwrAck(obj) {
@ -492,7 +492,7 @@
clearInterval(tPwrAck); clearInterval(tPwrAck);
if(null == e) if(null == e)
return; return;
e.innerHTML = "inverter acknowledged active power control command"; e.innerHTML = "{#INV_ACK}";
} }
function parse(obj) { function parse(obj) {

28
src/web/html/wizard.html

@ -24,23 +24,23 @@
function wifi() { function wifi() {
return ml("div", {}, [ return ml("div", {}, [
ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "Welcome to AhoyDTU"))), ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "{#WELCOME}"))),
ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "Network Setup"))), ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "{#NETWORK_SETUP}"))),
sect("Choose your WiFi Network", ml("select", {id: "net", onchange: () => {if(found) clearInterval(v)}}, ml("option", {value: "-1"}, "---"))), sect("{#CHOOSE_WIFI}", ml("select", {id: "net", onchange: () => {if(found) clearInterval(v)}}, ml("option", {value: "-1"}, "---"))),
sect("... or name it manually", ml("input", {id: "man", type: "text"})), sect("{#WIFI_MANUAL}", ml("input", {id: "man", type: "text"})),
sect("WiFi Password", ml("input", {id: "pwd", type: "password"})), sect("{#WIFI_PASSWORD}", ml("input", {id: "pwd", type: "password"})),
ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn", value: "next", onclick: () => {saveWifi()}}, null))), ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn", value: "{#BTN_NEXT}", onclick: () => {saveWifi()}}, null))),
ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "stop wizard"))) ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "{#STOP_WIZARD}")))
]) ])
} }
function checkWifi() { function checkWifi() {
c.replaceChildren( c.replaceChildren(
ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "Welcome to AhoyDTU"))), ml("div", {class: "row my-5"}, ml("div", {class: "col"}, ml("span", {class: "fs-1"}, "{#WELCOME}"))),
ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "Test Connection"))), ml("div", {class: "row"}, ml("div", {class: "col"}, ml("span", {class: "fs-5"}, "{#TEST_CONNECTION}"))),
sect("AhoyDTU is trying to connect to your WiFi", ml("span", {id: "state"}, "connecting ...")), sect("{#TRY_TO_CONNECT}", ml("span", {id: "state"}, "{#CONNECTING}")),
ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn hide", id: "btn", value: "Finish", onclick: () => {redirect()}}, null))), ml("div", {class: "row my-4"}, ml("div", {class: "col a-r"}, ml("input", {type: "button", class:"btn hide", id: "btn", value: "{#BTN_FINISH}", onclick: () => {redirect()}}, null))),
ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "stop wizard"))) ml("div", {class: "row mt-5"}, ml("div", {class: "col a-c"}, ml("a", {href: "http://192.168.4.1/"}, "{#STOP_WIZARD}")))
) )
v = setInterval(() => {getAjax('/api/setup/getip', printIp)}, 2500); v = setInterval(() => {getAjax('/api/setup/getip', printIp)}, 2500);
} }
@ -53,7 +53,7 @@
if("0.0.0.0" != obj["ip"]) { if("0.0.0.0" != obj["ip"]) {
clearInterval(v) clearInterval(v)
setHide("btn", false) setHide("btn", false)
document.getElementById("state").innerHTML = "success, got following IP in your network: " + obj.ip document.getElementById("state").innerHTML = "{#NETWORK_SUCCESS}" + obj.ip
} }
} }
@ -68,7 +68,7 @@
var e = document.getElementById("net"); var e = document.getElementById("net");
if(obj.networks.length > 0) { if(obj.networks.length > 0) {
var a = [] var a = []
a.push(ml("option", {value: -1}, obj.networks.length + " Network(s) found")) a.push(ml("option", {value: -1}, obj.networks.length + " {#NUM_NETWORKS_FOUND}"))
for(n of obj.networks) { for(n of obj.networks) {
a.push(ml("option", {value: n.ssid}, n.ssid + " (" + n.rssi + "dBm)")) a.push(ml("option", {value: n.ssid}, n.ssid + " (" + n.rssi + "dBm)"))
found = true; found = true;

1334
src/web/lang.json

File diff suppressed because it is too large
Loading…
Cancel
Save