Fixed setup and configuration bug

This commit is contained in:
EivindH06 2025-10-03 14:39:38 +02:00
parent 39e67a982e
commit 21789cda68
9 changed files with 517 additions and 87 deletions

File diff suppressed because one or more lines are too long

View File

@ -924,9 +924,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001746",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz",
"integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==",
"version": "1.0.30001747",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001747.tgz",
"integrity": "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg==",
"funding": [
{
"type": "opencollective",
@ -1363,9 +1363,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.228",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz",
"integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==",
"version": "1.5.230",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.230.tgz",
"integrity": "sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ==",
"license": "ISC"
},
"node_modules/emoji-regex": {

View File

@ -158,7 +158,7 @@
<ConsentComponent {sysinfo} {basepath} />
</Route>
<Route path="/setup">
<SetupPanel {sysinfo} />
<SetupPanel {sysinfo} {data} />
</Route>
<Route path="/vendor">
<VendorPanel {sysinfo} {basepath} />

View File

@ -523,7 +523,7 @@
<input
type="radio"
class="mr-2"
name="ws-option"
name="ws"
value={network.s}
bind:group={configuration.w.s}/>
<span class="flex items-center justify-between w-full">

View File

@ -24,6 +24,7 @@
});
export let sysinfo = {}
export let data = {}
let staticIp = false;
let connectionMode = 1;

View File

@ -0,0 +1,406 @@
{
"language" : {
"code" : "en",
"name" : "English"
},
"common" : {
"import" : "Import",
"export" : "Export",
"voltage" : "Voltage",
"amperage" : "Amperage",
"seconds" : "seconds",
"minute" : "minute",
"minutes" : "minutes",
"hour" : "hour",
"hours" : "hours",
"day" : "day",
"days" : "days",
"month" : "month",
"unknown" : "Unknown"
},
"btn" : {
"reboot" : "Reboot",
"save" : "Save",
"upload" : "Upload"
},
"header" : {
"mem" : "Free mem",
"price" : "Price service",
"booting" : "Booting",
"config" : "Configuration",
"status" : "Device information",
"doc" : "Documentation",
"new_version" : "New version",
"upgrade" : "Do you want to upgrade this device to {0}?",
"uptime" : "Up"
},
"dashboard" : {
"phase" : "Phase power",
"pf" : "Power factor",
"tariffpeak" : "Tariff peaks",
"realtime" : "Real-time plot",
"price" : "Future energy price",
"day" : "Energy use last 24 hours",
"month" : "Energy use last {0} days",
"temperature" : "Temperature sensors"
},
"reactive" : {
"title" : "Reactive",
"instant_in" : "Instant in",
"instant_out" : "Instant out",
"total_in" : "Total in",
"total_out" : "Total out"
},
"realtime" : {
"title" : "Real-time calculations",
"consumption" : "Consumption",
"cost" : "Cost",
"last_month" : "Last month",
"last_mo" : "Last mo."
},
"status" : {
"device" : {
"title" : "Device information",
"chip" : "Chip",
"device" : "Device",
"mac" : "MAC",
"apmac" : "AP MAC",
"last_boot" : "Last boot",
"reason" : "Reason",
"btn_consents" : "Update consents",
"reboot_confirm" : "Are you sure you want to reboot the device"
},
"meter" : {
"title" : "Meter",
"manufacturer" : "Manufacturer",
"model" : "Model",
"id" : "ID"
},
"network" : {
"title" : "Network"
},
"firmware" : {
"title" : "Firmware",
"installed" : "Installed version",
"latest" : "Latest version",
"install" : "Install this version",
"failed" : "Previous upgrade attempt from {0} to {1} failed",
"btn_select_file" : "Select firmware file for upgrade",
"no_one_click" : "You have disabled one-click firmware upgrade, link to self-upgrade is disabled"
},
"backup" : {
"title" : "Backup & restore",
"iw" : "WiFi",
"im" : "MQTT",
"ie" : "Web",
"it" : "Meter",
"ih" : "Thresholds",
"ig" : "GPIO",
"in" : "NTP",
"is" : "Price API",
"secrets" : "Include secrets",
"secrets_desc" : "(SSID, PSK, passwords and tokens)",
"btn_select_file" : "Select file...",
"btn_download" : "Download"
},
"mask" : {
"firmware" : "Uploading firmware, please wait",
"config" : "Uploading configuration, please wait"
}
},
"conf" : {
"general" : {
"title" : "General",
"hostname" : "Hostname",
"timezone" : "Time zone",
"security" : {
"title" : "Security",
"none" : "None",
"conf" : "Only configuration",
"all" : "Everything"
},
"context" : "Context",
"context_placeholder" : "[root]"
},
"price" : {
"title" : "Price configuration",
"region" : "Price region",
"currency" : "Currency",
"conf" : "Configure price",
"enabled" : "Enable price fetch from remote server",
"api_key_placeholder" : "ENTSO-E API key, optional, read docs",
"both" : "Both",
"fixed" : "Fixed",
"btn_add" : "Add",
"mask_loading" : "Loading price configuration",
"mask_saving" : "Saving price configuration"
},
"meter" : {
"title" : "Meter",
"comm" : {
"title" : "Communication",
"passive" : "Passive (Push)",
"pulse" : "Pulse"
},
"serial" : "Serial conf.",
"inverted" : "inverted",
"buffer" : "Buffer size",
"pulses" : "Pulses per kWh",
"voltage" : "Voltage",
"fuse" : "Main fuse",
"prod" : "Production",
"encrypted" : "Meter is encrypted",
"authkey" : "Authentication key",
"multipliers" : {
"title" : "Multipliers",
"watt" : "Watt",
"volt" : "Volt",
"amp" : "Amp",
"kwh" : "kWh"
}
},
"connection" : {
"title" : "Connection",
"wifi" : "Connect to WiFi",
"ap" : "Standalone access point",
"eth" : "Ethernet",
"ssid" : "SSID",
"psk" : "Password",
"ps" : {
"title" : "Power saving",
"default" : "Default",
"off" : "Off",
"min" : "Minimum",
"max" : "Maximum"
},
"pwr" : "Power",
"tick_11b" : "Allow 802.11b legacy rates"
},
"network" : {
"title" : "Network",
"ip" : "IP",
"static" : "Static",
"dhcp" : "DHCP",
"gw" : "Gateway",
"dns" : "DNS",
"tick_mdns" : "enable mDNS",
"ntp" : "NTP",
"tick_ntp_dhcp" : "obtain from DHCP"
},
"mqtt" : {
"title" : "MQTT",
"server" : "Server",
"user" : "Username",
"pass" : "Password",
"id" : "Client ID",
"payload" : "Payload",
"publish" : "Publish topic",
"btn_ca_upload" : "Upload CA",
"btn_crt_upload" : "Upload cert",
"btn_key_upload" : "Upload key",
"ca_ok" : "CA OK",
"crt_ok" : "Cert OK",
"key_ok" : "Key OK",
"title_ca" : "Click here to upload CA",
"title_crt" : "Click here to upload certificate",
"title_key" : "Click here to upload private key",
"domoticz" : {
"title" : "Domoticz",
"eidx" : "Electricity IDX",
"cidx" : "Current IDX",
"vidx" : "Voltage IDX"
},
"ha" : {
"title" : "Home-Assistant",
"discovery" : "Discovery topic prefix",
"hostname" : "Hostname for URL",
"tag" : "Name tag"
}
},
"cloud" : {
"title" : "Cloud connections",
"ams" : "AMS reader cloud",
"es" : "Energy Speedometer"
},
"thresholds" : {
"title" : "Tariff thresholds",
"avg" : "Average of"
},
"ui" : {
"title" : "User interface",
"i" : "Import gauge",
"e" : "Export gauge",
"v" : "Voltage",
"a" : "Amperage",
"h" : "Per phase",
"f" : "Power factor",
"r" : "Reactive",
"c" : "Real-time",
"t" : "Peaks",
"l" : "Real-time plot",
"p" : "Price",
"d" : "Day plot",
"m" : "Month plot",
"s" : "Temperature plot",
"k" : "Dark mode",
"lang" : "Language",
"enabled" : "Enabled",
"disabled" : "Disabled",
"auto" : "Auto"
},
"hw" : {
"title" : "Hardware",
"han" : {
"rx" : "HAN RX",
"tx" : "HAN TX",
"pullup" : "Pullup"
},
"ap_btn" : "AP button",
"led" : {
"title" : "LED",
"rgb" : "RGB",
"inverted" : "inverted",
"disable" : "LED dis. GPIO",
"behaviour" : {
"title" : "LED behaviour",
"enabled" : "Enabled",
"disabled" : "Disabled"
}
},
"temp" : "Temperature",
"temp_analog" : "Analog temp",
"vcc" : {
"title" : "Vcc",
"offset" : "Vcc offset",
"multiplier" : "Multiplier",
"divider" : "Voltage divider",
"div_vcc" : "VCC",
"div_gnd" : "GND",
"boot" : "Boot limit"
}
},
"debug" : {
"title" : "Debugging",
"enable" : "Enable debugging",
"danger" : "Debug can cause sudden reboots. Do not leave on!",
"telnet" : "Enable telnet",
"telnet_danger" : "Telnet is unsafe and should be off when not in use"
},
"btn_reset" : "Factory reset",
"mask" : {
"loading" : "Loading configuration",
"saving" : "Saving configuration",
"reset" : "Performing factory reset",
"reset_done" : "Device have been factory reset and switched to AP mode"
}
},
"consent" : {
"title" : "Various permissions we need to do stuff",
"one_click" : "Enable one-click upgrade? (implies data collection)",
"read_more" : "Read more",
"yes" : "Yes",
"no" : "No",
"mask_saving" : "Saving preferences"
},
"upload" : {
"title" : "Upload",
"desc" : "Select a suitable file and click upload",
"mask" : "Uploading file, please wait"
},
"setup" : {
"title" : "Setup",
"static" : "Static IP",
"mask" : "Saving your configuration to the device"
},
"errors" : {
"han" : {
"-1" : "Parse error",
"-2" : "Incomplete data received",
"-3" : "Payload boundary flag missing",
"-4" : "Header checksum error",
"-5" : "Footer checksum error",
"-9" : "Unknown data received, check meter config",
"-41" : "Frame length not equal",
"-51" : "Authentication failed",
"-52" : "Decryption failed",
"-53" : "Encryption key invalid",
"89" : "Unrecognized data received from meter",
"90" : "No HAN data received for at least 30s",
"91" : "Serial break",
"92" : "Serial buffer full",
"93" : "Serial FIFO overflow",
"94" : "Serial frame error",
"95" : "Serial parity error",
"96" : "RX error",
"98" : "Exception in code, debugging necessary",
"99" : "Autodetection failed"
},
"mqtt" : {
"-3" : "Connection failed",
"-4" : "Network timeout",
"-10" : "Connection denied",
"-11" : "Failed to subscribe",
"-13" : "Connection lost"
},
"price" : {
"400" : "Unrecognized data in request",
"401" : "Unauthorized, check API key",
"403" : "Unauthorized, check API key",
"404" : "Price unavailable, not found",
"425" : "Server says its too early",
"429" : "Exceeded API rate limit",
"500" : "Internal server error",
"-1" : "Connection error",
"-2" : "Incomplete data received",
"-3" : "Invalid data, tag missing",
"-51" : "Authentication failed",
"-52" : "Decryption failed",
"-53" : "Encryption key invalid"
},
"http" : {
"255" : "Unable to start upgrade",
"-1": "Connection refused",
"-2": "Failed to send headers",
"-3": "Failed to send payload",
"-4": "Not connected",
"-5": "Connection lost",
"-6": "No stream",
"-7": "Not a HTTP server",
"-8": "Not enough memory",
"-9": "Encoding error",
"-10": "Stream write",
"-11": "Read timeout"
}
},
"esp8266" : {
"reason" : {
"0": "Normal",
"1": "WDT reset",
"2": "Exception reset",
"3": "Soft WDT reset",
"4": "Software restart",
"5": "Deep sleep",
"6": "External reset"
}
},
"esp32" : {
"reason" : {
"1" : "Vbat power on reset",
"3" : "Software reset",
"4" : "WDT reset",
"5" : "Deep sleep",
"6" : "SLC reset",
"7" : "Timer Group0 WDT reset",
"8" : "Timer Group1 WDT reset",
"9" : "RTC WDT reset",
"10": "Instrusion test reset CPU",
"11": "Time Group reset CPU",
"12": "Software reset CPU",
"13": "RTC WTD reset CPU",
"14": "PRO CPU",
"15": "Brownout",
"16": "RTC reset"
}
},
"months" : ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]
}

View File

@ -4,90 +4,80 @@ import shutil
import subprocess
import gzip
# Attempt to import minifiers
try:
from css_html_js_minify import html_minify, js_minify, css_minify
except:
from SCons.Script import (
ARGUMENTS,
COMMAND_LINE_TARGETS,
DefaultEnvironment,
)
env = DefaultEnvironment()
env.Execute(
env.VerboseAction(
'$PYTHONEXE -m pip install "css_html_js_minify" ',
"Installing Python dependencies",
)
)
try:
from css_html_js_minify import html_minify, js_minify, css_minify
except:
print("WARN: Unable to load minifier")
except ImportError:
html_minify = lambda x: x
js_minify = lambda x: x
css_minify = lambda x: x
print("WARN: Minifiers not installed, files will not be minified.")
# Root folder for generated headers
srcroot = "lib/SvelteUi/include/html"
version = os.environ.get("GITHUB_TAG")
if version == None:
# Determine version
version = os.environ.get('GITHUB_TAG')
if version is None:
try:
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"], capture_output=True, check=False
)
if result.returncode == 0:
version = result.stdout.decode("utf-8").strip()
else:
version = "SNAPSHOT"
result = subprocess.run(['git', 'rev-parse', '--short', 'HEAD'], capture_output=True, check=False)
version = result.stdout.decode('utf-8').strip() if result.returncode == 0 else "SNAPSHOT"
except:
version = "SNAPSHOT"
# Ensure clean include folder
if os.path.exists(srcroot):
shutil.rmtree(srcroot)
os.mkdir(srcroot)
else:
os.mkdir(srcroot)
os.makedirs(srcroot, exist_ok=True)
# Folders to scan
webroots = ["lib/SvelteUi/app/dist", "lib/SvelteUi/json"]
for webroot in webroots:
if not os.path.exists(webroot):
print(f"WARN: Folder not found: {webroot}")
continue
for webroot in ["lib/SvelteUi/app/dist", "lib/SvelteUi/json"]:
for filename in os.listdir(webroot):
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
srcfile = webroot + "/" + filename
dstfile = srcroot + "/" + basename + ".h"
srcfile = os.path.join(webroot, filename)
dstfile = os.path.join(srcroot, basename + ".h")
varname = basename.upper()
# Read file content
with open(srcfile, encoding="utf-8") as f:
content = f.read()
content = content.replace("/index.js", "index-" + version + ".js")
content = content.replace("/index.css", "index-" + version + ".css")
# Replace references to JS/CSS with versioned filenames
content = content.replace("/index.js", f"index-{version}.js")
content = content.replace("/index.css", f"index-{version}.css")
# Minify if possible
try:
if filename.endswith(".html"):
content = html_minify(content)
elif filename.endswith(".json"):
content = js_minify(content)
elif filename.endswith(".svg"):
elif filename.endswith(".css"):
content = css_minify(content)
elif filename.endswith(".js"):
# JS6+ may break normal minifier, skip or handle later
pass
except:
print("WARN: Unable to minify")
except Exception as e:
print(f"WARN: Minify failed for {filename}: {e}")
# Encode to bytes for PROGMEM
content_bytes = content.encode("utf-8")
if filename in ["index.js", "index.css"]:
compress = filename.endswith((".js", ".css")) # Compress JS/CSS only
if compress:
content_bytes = gzip.compress(content_bytes, compresslevel=9)
content_len = len(content_bytes)
else:
content_len = len(content_bytes)
content_bytes += b"\0"
content_bytes += b"\0" # Null-terminate for C strings
# Write header file
with open(dstfile, "w") as dst:
dst.write("static const char ")
dst.write(varname)
dst.write("[] PROGMEM = {")
dst.write(", ".join([str(c) for c in content_bytes]))
dst.write(f"static const char {varname}[] PROGMEM = {{")
dst.write(", ".join(str(c) for c in content_bytes))
dst.write("};\n")
dst.write("const int ")
dst.write(varname)
dst.write("_LEN PROGMEM = ")
dst.write(str(content_len))
dst.write(";")
dst.write(f"const int {varname}_LEN PROGMEM = {content_len};\n")

View File

@ -32,6 +32,7 @@
#include "html/conf_ha_json.h"
#include "html/conf_ui_json.h"
#include "html/conf_cloud_json.h"
#include "html/translations_json.h"
#include "html/firmware_html.h"
#include "html/neas_logotype_white_svg.h"
#include "html/wifi_high_light_svg.h"
@ -130,6 +131,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, AmsDa
server.on(context + F("/wifi-high-light.svg"), HTTP_GET, std::bind(&AmsWebServer::wifiHighLightSvg, this));
server.on(context + F("/wifi-medium-light.svg"), HTTP_GET, std::bind(&AmsWebServer::wifiMediumLightSvg, this));
server.on(context + F("/wifi-low-light.svg"), HTTP_GET, std::bind(&AmsWebServer::wifiLowLightSvg, this));
server.on(context + F("/wifi-off-light.svg"), HTTP_GET, std::bind(&AmsWebServer::wifiOffSvg, this));
server.on(context + F("/wifi-off.svg"), HTTP_GET, std::bind(&AmsWebServer::wifiOffSvg, this));
server.on(context + F("/sysinfo.json"), HTTP_GET, std::bind(&AmsWebServer::sysinfoJson, this));
@ -1162,11 +1164,6 @@ void AmsWebServer::priceConfigJson() {
}
void AmsWebServer::translationsJson() {
if(!LittleFS.begin()) {
server.send_P(500, MIME_PLAIN, PSTR("500: Filesystem unavailable"));
return;
}
String lang = server.arg("lang");
if(lang.isEmpty()) {
UiConfig ui;
@ -1174,28 +1171,44 @@ void AmsWebServer::translationsJson() {
lang = String(ui.language);
}
}
if(lang.isEmpty()) {
lang = F("en");
}
snprintf_P(buf, BufferSize, PSTR("/translations-%s.json"), lang.c_str());
if(!LittleFS.exists(buf)) {
notFound();
bool served = false;
if(LittleFS.begin()) {
snprintf_P(buf, BufferSize, PSTR("/translations-%s.json"), lang.c_str());
if(LittleFS.exists(buf)) {
File file = LittleFS.open(buf, "r");
if(file) {
addConditionalCloudHeaders();
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
server.setContentLength(file.size());
server.send(200, MIME_JSON);
while(file.available() > 0) {
int len = file.readBytes(buf, BufferSize);
server.sendContent(buf, len);
}
file.close();
served = true;
}
}
}
if(served) {
return;
}
addConditionalCloudHeaders();
// server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1DA);
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
File file = LittleFS.open(buf, "r");
server.setContentLength(file.size());
server.send(200, MIME_JSON);
while(file.available() > 0) {
int len = file.readBytes(buf, BufferSize);
server.sendContent(buf, len);
}
file.close();
uint16_t len = pgm_read_word(&TRANSLATIONS_JSON_LEN);
server.send_P(200, MIME_JSON, TRANSLATIONS_JSON, len);
}
void AmsWebServer::cloudkeyJson() {
@ -1365,7 +1378,12 @@ void AmsWebServer::handleSave() {
NetworkConfig network;
config->getNetworkConfig(network);
network.mode = mode;
strcpy(network.ssid, server.arg(F("ws")).c_str());
if(server.hasArg(F("ws"))) {
String ssid = server.arg(F("ws"));
if(ssid.length() > 0) {
strcpy(network.ssid, ssid.c_str());
}
}
String psk = server.arg(F("wp"));
if(!psk.equals("***")) {
strcpy(network.psk, psk.c_str());

View File

@ -45,6 +45,8 @@ build_flags =
-D AMS_CLOUD=1
-D AMS_KMP=1
-D ZMART_CHARGE=1
-L precompiled/esp32s2
-lKmpTalker
-L precompiled/esp32
-lKmpTalker
lib_ldf_mode = off
@ -149,4 +151,17 @@ monitor_port = /dev/cu.usbmodem01
upload_speed = 921600
monitor_speed = 115200
board_build.flash_mode = dio
board_build.flash_size = 4MB
board_build.flash_size = 4MB
build_flags =
${common.build_flags}
-D AMS_REMOTE_DEBUG=1
-D AMS_CLOUD=1
-D AMS_KMP=1
-D ZMART_CHARGE=1
-L precompiled/esp32s2
-lKmpTalker
lib_ldf_mode = off
lib_compat_mode = off
lib_deps = ${esp32.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_scripts = ${common.extra_scripts}