Further work on v2.2

This commit is contained in:
Gunnar Skjold 2022-11-19 15:44:23 +01:00
parent 6563700df4
commit ab7128c53a
24 changed files with 1463 additions and 485 deletions

View File

@ -4,7 +4,7 @@
#include "Arduino.h"
#define EEPROM_SIZE 1024*3
#define EEPROM_CHECK_SUM 96 // Used to check if config is stored. Change if structure changes
#define EEPROM_CHECK_SUM 100 // Used to check if config is stored. Change if structure changes
#define EEPROM_CONFIG_ADDRESS 0
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
@ -29,7 +29,11 @@
struct SystemConfig {
uint8_t boardType;
}; // 1
bool vendorConfigured;
bool userConfigured;
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
char country[2];
}; // 6
struct WiFiConfig91 {
char ssid[32];
@ -55,7 +59,8 @@ struct WiFiConfig {
bool mdns;
uint8_t power;
uint8_t sleep;
}; // 211
uint8_t mode;
}; // 212
struct MqttConfig86 {
char host[128];
@ -280,14 +285,13 @@ private:
uint8_t tempSensorCount = 0;
TempSensorConfig** tempSensors = NULL;
bool relocateConfig86(); // 1.5.0
bool relocateConfig87(); // 1.5.4
bool relocateConfig90(); // 2.0.0
bool relocateConfig91(); // 2.0.2
bool relocateConfig92(); // 2.0.3
bool relocateConfig93(); // 2.1.0
bool relocateConfig94(); // 2.1.4
bool relocateConfig95(); // 2.1.13
bool relocateConfig94(); // 2.1.0
bool relocateConfig95(); // 2.1.4
bool relocateConfig96(); // 2.1.14
void saveToFs();
bool loadFromFs(uint8_t version);

View File

@ -7,6 +7,11 @@ bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
EEPROM.end();
return true;
} else {
config.boardType = 0xFF;
config.vendorConfigured = false;
config.userConfigured = false;
config.dataCollectionConsent = 0;
strcpy(config.country, "");
return false;
}
}
@ -619,22 +624,6 @@ bool AmsConfiguration::hasConfig() {
}
} else {
switch(configVersion) {
case 86:
configVersion = -1; // Prevent loop
if(relocateConfig86()) {
configVersion = 87;
} else {
configVersion = 0;
return false;
}
case 87:
configVersion = -1; // Prevent loop
if(relocateConfig87()) {
configVersion = 88;
} else {
configVersion = 0;
return false;
}
case 90:
configVersion = -1; // Prevent loop
if(relocateConfig90()) {
@ -683,6 +672,14 @@ bool AmsConfiguration::hasConfig() {
configVersion = 0;
return false;
}
case 96:
configVersion = -1; // Prevent loop
if(relocateConfig96()) {
configVersion = 100;
} else {
configVersion = 0;
return false;
}
case EEPROM_CHECK_SUM:
return true;
default:
@ -735,51 +732,6 @@ void AmsConfiguration::saveTempSensors() {
}
}
bool AmsConfiguration::relocateConfig86() {
MqttConfig86 mqtt86;
MqttConfig mqtt;
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_MQTT_START_86, mqtt86);
strcpy(mqtt.host, mqtt86.host);
mqtt.port = mqtt86.port;
strcpy(mqtt.clientId, mqtt86.clientId);
strcpy(mqtt.publishTopic, mqtt86.publishTopic);
strcpy(mqtt.subscribeTopic, mqtt86.subscribeTopic);
strcpy(mqtt.username, mqtt86.username);
strcpy(mqtt.password, mqtt86.password);
mqtt.payloadFormat = mqtt86.payloadFormat;
mqtt.ssl = mqtt86.ssl;
EEPROM.put(CONFIG_MQTT_START, mqtt);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 87);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
bool AmsConfiguration::relocateConfig87() {
MeterConfig87 meter87 = {0,0,0,0,0,0,0};
MeterConfig meter;
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_METER_START_87, meter87);
if(meter87.type < 5) {
meter.baud = 2400;
meter.parity = meter87.type == 3 || meter87.type == 4 ? 3 : 11;
meter.invert = false;
} else {
meter.baud = 115200;
meter.parity = 3;
meter.invert = meter87.type == 6;
}
meter.distributionSystem = meter87.distributionSystem;
meter.mainFuse = meter87.mainFuse;
meter.productionCapacity = meter87.productionCapacity;
EEPROM.put(CONFIG_METER_START, meter);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 88);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
bool AmsConfiguration::relocateConfig90() {
EntsoeConfig entsoe;
EEPROM.begin(EEPROM_SIZE);
@ -877,6 +829,27 @@ bool AmsConfiguration::relocateConfig95() {
return ret;
}
bool AmsConfiguration::relocateConfig96() {
SystemConfig sys;
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_SYSTEM_START, sys);
sys.vendorConfigured = false;
sys.userConfigured = false;
sys.dataCollectionConsent = 0;
strcpy(sys.country, "");
EEPROM.put(CONFIG_SYSTEM_START, sys);
WiFiConfig wifi;
EEPROM.get(CONFIG_WIFI_START, wifi);
wifi.mode = 1; // WIFI_STA
EEPROM.put(CONFIG_WIFI_START, wifi);
EEPROM.put(EEPROM_CONFIG_ADDRESS, 100);
bool ret = EEPROM.commit();
EEPROM.end();
return ret;
}
bool AmsConfiguration::save() {
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);

View File

@ -1,15 +1,20 @@
<script>
import { Router, Route } from "svelte-navigator";
import { sysinfoStore, dataStore } from './lib/DataStores.js';
import { getSysinfo, sysinfoStore, dataStore } from './lib/DataStores.js';
import Header from './lib/Header.svelte';
import Dashboard from './lib/Dashboard.svelte';
import ConfigurationPanel from './lib/ConfigurationPanel.svelte';
import StatusPage from './lib/StatusPage.svelte';
import VendorModal from './lib/VendorModal.svelte';
import SetupModal from './lib/SetupModal.svelte';
import Mask from './lib/Mask.svelte';
let sysinfo = {};
sysinfoStore.subscribe(update => {
sysinfo = update;
});
getSysinfo();
let data = {};
dataStore.subscribe(update => {
data = update;
@ -25,6 +30,13 @@
<Route path="/configuration">
<ConfigurationPanel sysinfo={sysinfo}/>
</Route>
<Route path="/status">
<StatusPage sysinfo={sysinfo} data={data}/>
</Route>
</Router>
{#if sysinfo.vndcfg === false}
<VendorModal sysinfo={sysinfo}/>
{:else if sysinfo.usrcfg === false}
<SetupModal sysinfo={sysinfo}/>
{/if}
</div>

View File

@ -9,6 +9,8 @@
<span title={title} class="my-auto bg-yellow-500 text-yellow-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">{text}</span>
{:else if color === `red`}
<span title={title} class="my-auto bg-red-500 text-red-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">{text}</span>
{:else if color === `blue`}
<span title={title} class="my-auto bg-blue-500 text-blue-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">{text}</span>
{:else if color === `gray`}
<span title={title} class="my-auto bg-gray-500 text-gray-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">{text}</span>
{/if}

View File

@ -0,0 +1,48 @@
<script>
import {boardtype} from './Helpers.js'
export let chip;
</script>
<option value=""></option>
{#if chip == 'esp8266'}
<optgroup label="amsleser.no">
<option value={7}>{boardtype(chip, 7)}</option>
<option value={5}>{boardtype(chip, 5)}</option>
<option value={4}>{boardtype(chip, 4)}</option>
<option value={3}>{boardtype(chip, 3)}</option>
</optgroup>
<optgroup label="Custom hardware">
<option value={2}>{boardtype(chip, 2)}</option>
<option value={1}>{boardtype(chip, 1)}</option>
<option value={0}>{boardtype(chip, 0)}</option>
</optgroup>
<optgroup label="Generic hardware">
<option value={101}>{boardtype(chip, 101)}</option>
<option value={100}>{boardtype(chip, 100)}</option>
</optgroup>
{/if}
{#if chip == 'esp32'}
<optgroup label="Generic hardware">
<option value={201}>{boardtype(chip, 201)}</option>
<option value={202}>{boardtype(chip, 202)}</option>
<option value={203}>{boardtype(chip, 203)}</option>
<option value={200}>{boardtype(chip, 200)}</option>
</optgroup>
{/if}
{#if chip == 'esp32s2'}
<optgroup label="amsleser.no">
<option value={7}>{boardtype(chip, 7)}</option>
<option value={6}>{boardtype(chip, 6)}</option>
<option value={5}>{boardtype(chip, 5)}</option>
</optgroup>
<optgroup label="Generic hardware">
<option value={51}>{boardtype(chip, 51)}</option>
<option value={50}>{boardtype(chip, 50)}</option>
</optgroup>
{/if}
{#if chip == 'esp32solo'}
<optgroup label="Generic hardware">
<option value={200}>{boardtype(chip, 200)}</option>
</optgroup>
{/if}

View File

@ -1,138 +1,52 @@
<script>
import { getConfiguration, configurationStore } from './ConfigurationStore'
import Modal from './Modal.svelte'
import { sysinfoStore } from './DataStores.js';
import BoardTypeSelectOptions from './BoardTypeSelectOptions.svelte';
import UartSelectOptions from './UartSelectOptions.svelte';
import Mask from './Mask.svelte'
import { metertype } from './Helpers';
import Badge from './Badge.svelte';
export let sysinfo = {}
let loadingOrSaving = true;
let configuration = {
general: {
host: '',
sec: 0,
user: '',
pass: ''
g: {
t: '', h: '', s: 0, u: '', p: ''
},
meter: {
inv: false,
dist: 0,
fuse: 0,
prod: 0,
enc: '',
auth: ''
m: {
b: 2400, p: 11, i: false, d: 0, f: 0, r: 0,
e: { e: false, k: '', a: '' },
m: { e: false, w: false, v: false, a: false, c: false }
},
wifi: {
ssid: '',
psk: '',
pwr: 0.0
w: { s: '', p: '', w: 0.0, z: 255 },
n: {
m: '', i: '', s: '', g: '', d1: '', d2: '', d: false, n1: '', n2: '', h: false
},
net: {
mode: '',
ip: '',
gw: '',
dns1: '',
dns2: '',
ntp1: '',
ntp2: ''
q: {
h: '', p: 1883, u: '', a: '', b: '',
s: { e: false, c: false, r: true, k: false }
},
mqtt: {
host: '',
post: 1883,
user: '',
pass: '',
pub: ''
t: {
t: [0,0,0,0,0,0,0,0,0,0], h: 1
},
p: {
e: false, t: '', r: '', c: '', m: 1.0
},
d: {
s: false, t: false, l: 5
},
i: {
h: null, a: null,
l: { p: null, i: false },
r: { r: null, g: null, b: null, i: false },
t: { d: null, a: null },
v: { p: null, d: { v: null, g: null }, o: null, m: null, b: null }
}
};
let metersources = {};
configurationStore.subscribe(update => {
if(update.version) {
switch(sysinfo.chip) {
case "esp8266":
metersources = {
'UART0' : 3,
'UART2' : 113,
'GPIO4' : 4,
'GPIO5' : 5,
'GPIO9' : 9,
'GPIO10' : 10,
'GPIO12' : 12,
'GPIO13' : 13,
'GPIO14' : 14,
'GPIO15' : 15,
'GPIO16' : 16,
}
break;
case "esp32":
metersources = {
'UART0' : 3,
'UART1' : 9,
'UART2' : 16,
'GPIO4' : 4,
'GPIO5' : 5,
'GPIO6' : 6,
'GPIO7' : 7,
'GPIO8' : 8,
'GPIO10' : 10,
'GPIO11' : 11,
'GPIO12' : 12,
'GPIO13' : 13,
'GPIO14' : 14,
'GPIO15' : 15,
'GPIO17' : 17,
'GPIO18' : 18,
'GPIO19' : 19,
'GPIO21' : 21,
'GPIO22' : 22,
'GPIO23' : 23,
'GPIO25' : 25,
'GPIO32' : 32,
'GPIO33' : 33,
'GPIO34' : 34,
'GPIO35' : 35,
'GPIO36' : 36,
'GPIO39' : 39,
}
break;
case "esp32s2":
metersources = {
'UART0' : 3,
'UART1' : 18,
'GPIO4' : 4,
'GPIO5' : 5,
'GPIO6' : 6,
'GPIO7' : 7,
'GPIO8' : 8,
'GPIO9' : 9,
'GPIO10' : 10,
'GPIO11' : 11,
'GPIO12' : 12,
'GPIO13' : 13,
'GPIO14' : 14,
'GPIO15' : 15,
'GPIO16' : 16,
'GPIO17' : 17,
'GPIO19' : 19,
'GPIO21' : 21,
'GPIO22' : 22,
'GPIO23' : 23,
'GPIO25' : 25,
'GPIO32' : 32,
'GPIO33' : 33,
'GPIO34' : 34,
'GPIO35' : 35,
'GPIO36' : 36,
'GPIO39' : 39,
'GPIO40' : 40,
'GPIO41' : 41,
'GPIO42' : 42,
'GPIO43' : 43,
'GPIO44' : 44,
}
break;
default:
metersources = {};
}
configuration = update;
loadingOrSaving = false;
}
@ -153,6 +67,12 @@
body: data
});
let res = (await response.json())
sysinfoStore.update(s => {
s.booting = res.reboot;
return s;
});
loadingOrSaving = false;
getConfiguration();
}
@ -162,41 +82,109 @@
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">General</strong>
<input type="hidden" name="general" value="true"/>
<div class="my-3">
Timezone<br/>
<select class="h-10 rounded-md shadow-sm border-gray-300">
<option value="Europe/Oslo">Europe/Oslo</option>
</select>
<input type="hidden" name="g" value="true"/>
<div class="my-1">
<div class="flex">
<div>
Hostname<br/>
<input name="gh" bind:value={configuration.g.h} type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
</div>
<div>
Timezone<br/>
<select name="gt" bind:value={configuration.g.t} class="h-10 rounded-r-md border-l-0 shadow-sm border-gray-300">
<option value="UTC">UTC</option>
<option value="CET/CEST">CET/CEST</option>
</select>
</div>
</div>
</div>
<div class="my-3">
Hostname<br/>
<input name="general_host" bind:value={configuration.general.host} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input type="hidden" name="p" value="true"/>
<div class="my-1">
<div class="flex">
<div>
Price region<br/>
<select name="pr" bind:value={configuration.p.r} class="h-10 rounded-l-md shadow-sm border-gray-300">
<optgroup label="Norway">
<option value="10YNO-1--------2">NO1</option>
<option value="10YNO-2--------T">NO2</option>
<option value="10YNO-3--------J">NO3</option>
<option value="10YNO-4--------9">NO4</option>
<option value="10Y1001A1001A48H">NO5</option>
</optgroup>
<optgroup label="Sweden">
<option value="10Y1001A1001A44P">SE1</option>
<option value="10Y1001A1001A45N">SE2</option>
<option value="10Y1001A1001A46L">SE3</option>
<option value="10Y1001A1001A47J">SE4</option>
</optgroup>
<optgroup label="Denmark">
<option value="10YDK-1--------W">DK1</option>
<option value="10YDK-2--------M">DK2</option>
</optgroup>
<option value="10YAT-APG------L">Austria</option>
<option value="10YBE----------2">Belgium</option>
<option value="10YCZ-CEPS-----N">Czech Republic</option>
<option value="10Y1001A1001A39I">Estonia</option>
<option value="10YFI-1--------U">Finland</option>
<option value="10YFR-RTE------C">France</option>
<option value="10Y1001A1001A83F">Germany</option>
<option value="10YGB----------A">Great Britain</option>
<option value="10YLV-1001A00074">Latvia</option>
<option value="10YLT-1001A0008Q">Lithuania</option>
<option value="10YNL----------L">Netherland</option>
<option value="10YPL-AREA-----S">Poland</option>
<option value="10YCH-SWISSGRIDZ">Switzerland</option>
</select>
</div>
<div>
Currency<br/>
<select name="pc" bind:value={configuration.p.c} class="h-10 border-l-0 shadow-sm border-gray-300">
<option value="NOK">NOK</option>
<option value="SEK">SEK</option>
<option value="DKK">DKK</option>
<option value="EUR">EUR</option>
</select>
</div>
<div>
Multiplier<br/>
<input name="pm" bind:value={configuration.p.m} type="number" min="0.001" max="1000" step="0.001" class="h-10 rounded-r-md border-l-0 shadow-sm border-gray-300 w-24 text-right"/>
</div>
</div>
</div>
<div class="my-3">
{#if sysinfo.chip != 'esp8266'}
<div class="my-1">
<label><input type="checkbox" name="pe" bind:checked={configuration.p.e} class="rounded mb-1"/> ENTSO-E token</label>
{#if configuration.p.e}
<br/><input name="pt" bind:value={configuration.p.t} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
{/if}
</div>
{/if}
<div class="my-1">
Security<br/>
<select name="general_sec" bind:value={configuration.general.sec} class="h-10 rounded-md shadow-sm border-gray-300">
<select name="gs" bind:value={configuration.g.s} class="h-10 rounded-md shadow-sm border-gray-300">
<option value={0}>None</option>
<option value={1}>Only configuration</option>
<option value={2}>Everything</option>
</select>
</div>
<div class="my-3">
{#if configuration.g.s > 0}
<div class="my-1">
Username<br/>
<input name="general_user" bind:value={configuration.general.user} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="gu" bind:value={configuration.g.u} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div class="my-3">
<div class="my-1">
Password<br/>
<input name="general_pass" bind:value={configuration.general.pass} type="password" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="gp" bind:value={configuration.g.p} type="password" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
{/if}
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Meter</strong>
<input type="hidden" name="meter" value="true"/>
<div class="my-3">
<input type="hidden" name="m" value="true"/>
<div class="my-1">
<span>Serial configuration</span>
<div class="flex">
<select name="meter_baud" bind:value={configuration.meter.baud} class="h-10 rounded-l-md shadow-sm border-gray-300">
<select name="mb" bind:value={configuration.m.b} class="h-10 rounded-l-md shadow-sm border-gray-300">
<option value={2400}>2400</option>
<option value={4800}>4800</option>
<option value={9600}>9600</option>
@ -205,179 +193,369 @@
<option value={57600}>57600</option>
<option value={115200}>115200</option>
</select>
<select name="meter_par" bind:value={configuration.meter.par} class="h-10 rounded-r-md shadow-sm border-l-0 border-gray-300">
<select name="mp" bind:value={configuration.m.p} class="h-10 rounded-r-md border-l-0 shadow-sm border-gray-300">
<option value={2}>7N1</option>
<option value={3}>8N1</option>
<option value={10}>7E1</option>
<option value={11}>8E1</option>
</select>
<label class="mt-2 ml-3"><input name="mi" value="true" bind:checked={configuration.m.i} type="checkbox" class="rounded mb-1"/> inverted</label>
</div>
<label><input name="meter_inv" value="true" bind:checked={configuration.meter.inv} type="checkbox" class="rounded"/> inverted</label>
</div>
<div class="grid grid-cols-2">
<div class="mb-1.5 col-span-2">
<div class="my-1 flex">
<div class="w-32">
Distribution<br/>
<select name="meter_dist" bind:value={configuration.meter.dist} class="h-10 rounded-md shadow-sm border-gray-300 w-full">
<select name="md" bind:value={configuration.m.d} class="h-10 rounded-l-md shadow-sm border-gray-300 w-full">
<option value={0}></option>
<option value={1}>IT or TT (230V)</option>
<option value={2}>TN (400V)</option>
<option value={1}>IT/TT</option>
<option value={2}>TN</option>
</select>
</div>
<div class="my-1.5 mr-2">
<div>
Main fuse<br/>
<label class="flex">
<input name="meter_fuse" bind:value={configuration.meter.fuse} type="number" min="5" max="255" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
<div class="flex -mr-px">
<span class="flex items-center bg-gray-100 rounded-r-md border border-l-0 border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">A</span>
</div>
<input name="mf" bind:value={configuration.m.f} type="number" min="5" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">A</span>
</label>
</div>
<div class="my-1.5">
<div>
Production<br/>
<label class="flex">
<input name="meter_prod" bind:value={configuration.meter.prod} type="number" min="0" max="255" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
<div class="flex">
<span class="flex items-center bg-gray-100 rounded-r-md border border-l-0 border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWp</span>
</div>
<input name="mr" bind:value={configuration.m.r} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWp</span>
</label>
</div>
</div>
<div class="my-3">
Encryption key<br/>
<input name="meter_enc" bind:value={configuration.meter.enc} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<div class="my-1">
</div>
<div class="my-3">
<div class="my-1">
<label><input type="checkbox" name="me" value="true" bind:checked={configuration.m.e.e} class="rounded mb-1"/> Meter uses encryption</label>
{#if configuration.m.e.e}
<br/><input name="mek" bind:value={configuration.m.e.k} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
{/if}
</div>
{#if configuration.m.e.e}
<div class="my-1">
Authentication key<br/>
<input name="meter_auth" bind:value={configuration.meter.auth} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="mea" bind:value={configuration.m.e.a} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
{/if}
<label><input type="checkbox" name="mm" value="true" bind:checked={configuration.m.m.e} class="rounded mb-1"/> Multipliers</label>
{#if configuration.m.m.e}
<div class="flex my-1">
<div class="w-1/4">
Instant<br/>
<input name="mmw" bind:value={configuration.m.m.w} type="number" min="0.00" max="655.35" step="0.01" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full text-right"/>
</div>
<div class="w-1/4">
Volt<br/>
<input name="mmv" bind:value={configuration.m.m.v} type="number" min="0.00" max="655.35" step="0.01" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
</div>
<div class="w-1/4">
Amp<br/>
<input name="mma" bind:value={configuration.m.m.a} type="number" min="0.00" max="655.35" step="0.01" class="h-10 border-r-0 shadow-sm border-gray-300 w-full text-right"/>
</div>
<div class="w-1/4">
Acc.<br/>
<input name="mmc" bind:value={configuration.m.m.c} type="number" min="0.00" max="655.35" step="0.01" class="h-10 rounded-r-md shadow-sm border-gray-300 w-full text-right"/>
</div>
</div>
{/if}
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">WiFi</strong>
<input type="hidden" name="wifi" value="true"/>
<div class="my-3">
<input type="hidden" name="w" value="true"/>
<div class="my-1">
SSID<br/>
<input name="wifi_ssid" bind:value={configuration.wifi.ssid} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="ws" bind:value={configuration.w.s} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div class="my-3">
<div class="my-1">
PSK<br/>
<input name="wifi_psk" bind:value={configuration.wifi.psk} type="password" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="wp" bind:value={configuration.w.p} type="password" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div class="my-3">
Power<br/>
<label class="flex">
<input name="wifi_pwr" bind:value={configuration.wifi.pwr} type="number" min="0" max="20.5" step="0.5" class="h-10 rounded-l-md shadow-sm border-gray-300"/>
<div class="flex -mr-px">
<span class="flex items-center bg-gray-100 rounded-r-md border border-l-0 border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">dBm</span>
</div>
</label>
</div>
<div class="my-3">
Power saving<br/>
<select name="wifi_sleep" bind:value={configuration.wifi.sleep} class="h-10 rounded-md shadow-sm border-gray-300">
<option value={255}>Default</option>
<option value={0}>Off</option>
<option value={1}>Minimum</option>
<option value={2}>Maximum</option>
</select>
<div class="my-1 flex">
<div>
Power saving<br/>
<select name="wz" bind:value={configuration.w.z} class="h-10 rounded-md shadow-sm border-gray-300">
<option value={255}>Default</option>
<option value={0}>Off</option>
<option value={1}>Minimum</option>
<option value={2}>Maximum</option>
</select>
</div>
<div class="ml-2">
Power<br/>
<label class="flex">
<input name="ww" bind:value={configuration.w.w} type="number" min="0" max="20.5" step="0.5" class="h-10 rounded-l-md shadow-sm border-gray-300 text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border-l-0 border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">dBm</span>
</label>
</div>
</div>
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Network</strong>
<input type="hidden" name="net" value="true"/>
<div class="my-3">
<div class="my-1">
IP<br/>
<div class="flex">
<select name="net_mode" bind:value={configuration.net.mode} class="h-10 rounded-l-md shadow-sm border-r-0 border-gray-300">
<select name="nm" bind:value={configuration.n.m} class="h-10 rounded-l-md shadow-sm border border-gray-300">
<option value="dhcp">DHCP</option>
<option value="static">Static</option>
</select>
<input name="net_ip" bind:value={configuration.net.ip} type="text" class="h-10 shadow-sm border-gray-300 w-full"/>
<select class="h-10 rounded-r-md shadow-sm border-l-0 border-gray-300">
<option>/24</option>
<input name="ni" bind:value={configuration.n.i} type="text" class="h-10 border-x-0 shadow-sm border-gray-300 w-full disabled:bg-gray-200" disabled={configuration.n.m == 'dhcp'}/>
<select name="ns" bind:value={configuration.n.s} class="h-10 rounded-r-md shadow-sm border-gray-300 disabled:bg-gray-200" disabled={configuration.n.m == 'dhcp'}>
<option value="255.255.255.0">/24</option>
<option value="255.255.0.0">/16</option>
<option value="255.0.0.0">/8</option>
</select>
</div>
</div>
<div class="my-3">
{#if configuration.n.m == 'static'}
<div class="my-1">
Gateway<br/>
<input name="net_gw" bind:value={configuration.net.gw} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="ng" bind:value={configuration.n.g} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full disabled:bg-gray-200"/>
</div>
<div class="my-3">
<div class="my-1">
DNS<br/>
<div class="flex">
<input name="net_dns1" bind:value={configuration.net.dns1} type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
<input name="net_dns2" bind:value={configuration.net.dns2} type="text" class="h-10 rounded-r-md shadow-sm border-l-0 border-gray-300 w-full"/>
<input name="nd1" bind:value={configuration.n.d1} type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full disabled:bg-gray-200"/>
<input name="nd2" bind:value={configuration.n.d2} type="text" class="h-10 border-l-0 rounded-r-md shadow-sm border-gray-300 w-full disabled:bg-gray-200"/>
</div>
<label><input name="net_mdns" value="true" bind:checked={configuration.net.mdns} type="checkbox" class="rounded"/> enable mDNS</label>
</div>
<div class="my-3">
NTP<br/>
{/if}
<div class="my-1">
<label><input name="nd" value="true" bind:checked={configuration.n.d} type="checkbox" class="rounded mb-1"/> enable mDNS</label>
</div>
<input type="hidden" name="ntp" value="true"/>
<div class="my-1">
NTP <label class="ml-4"><input name="ntpd" value="true" bind:checked={configuration.n.h} type="checkbox" class="rounded mb-1"/> obtain from DHCP</label><br/>
<div class="flex">
<input name="net_ntp1" bind:value={configuration.net.ntp1} type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
<input name="net_ntp2" bind:value={configuration.net.ntp2} type="text" class="h-10 rounded-r-md shadow-sm border-l-0 border-gray-300 w-full"/>
</div>
<label><input name="net_ntpdhcp" value="true" bind:checked={configuration.net.ntpdhcp} type="checkbox" class="rounded"/> obtain from DHCP</label>
<input name="ntph" bind:value={configuration.n.n1} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
</div>
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">MQTT</strong>
<input type="hidden" name="mqtt" value="true"/>
<div class="my-3">
Server<br/>
<input type="hidden" name="q" value="true"/>
<div class="my-1">
Server
<label class="float-right mr-3"><input type="checkbox" name="qs" bind:checked={configuration.q.s.e} class="rounded mb-1"/> SSL</label>
<br/>
<div class="flex">
<input name="mqtt_host" bind:value={configuration.mqtt.host} type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
<input name="mqtt_port" bind:value={configuration.mqtt.port} type="text" class="h-10 rounded-r-md shadow-sm border-l-0 border-gray-300 w-20"/>
<input name="qh" bind:value={configuration.q.h} type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
<input name="qp" bind:value={configuration.q.p} type="number" min="1024" max="65535" class="h-10 border-l-0 rounded-r-md shadow-sm border-gray-300 w-20 text-right"/>
</div>
</div>
<div class="my-3">
{#if configuration.q.s.e}
<div class="my-1">
<div>
{#if configuration.q.s.c}
<Badge color="green" text="CA OK" title="Click here to replace CA"/>
{:else}
<Badge color="blue" text="Upload CA" title="Click here to upload CA"/>
{/if}
{#if configuration.q.s.r}
<Badge color="green" text="Cert OK" title="Click here to replace certificate"/>
{:else}
<Badge color="blue" text="Upload cert" title="Click here to upload certificate"/>
{/if}
{#if configuration.q.s.k}
<Badge color="green" text="Key OK" title="Click here to replace key"/>
{:else}
<Badge color="blue" text="Upload key" title="Click here to upload key"/>
{/if}
</div>
</div>
{/if}
<div class="my-1">
Username<br/>
<input name="mqtt_user" bind:value={configuration.mqtt.user} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="qu" bind:value={configuration.q.u} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div class="my-3">
<div class="my-1">
Password<br/>
<input name="mqtt_pass" bind:value={configuration.mqtt.pass} type="password" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="qa" bind:value={configuration.q.a} type="password" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div class="my-3">
Client ID<br/>
<input name="mqtt_clid" bind:value={configuration.mqtt.clid} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<div class="my-1 flex">
<div>
Client ID<br/>
<input name="qc" bind:value={configuration.q.c} type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
</div>
<div>
Payload<br/>
<select name="qm" bind:value={configuration.q.m} class="h-10 border-l-0 rounded-r-md shadow-sm border-gray-300 w-36">
<option value={0}>JSON</option>
<option value={1}>Raw (minimal)</option>
<option value={2}>Raw (full)</option>
<option value={3}>Domoticz</option>
<option value={4}>HomeAssistant</option>
<option value={255}>Raw bytes</option>
</select>
</div>
</div>
<div class="my-3">
<div class="my-1">
Publish topic<br/>
<input name="mqtt_pub" bind:value={configuration.mqtt.pub} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
<input name="qb" bind:value={configuration.q.b} type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div class="my-3">
Payload<br/>
<select name="mqtt_mode" bind:value={configuration.mqtt.mode} class="h-10 rounded-md shadow-sm border-gray-300">
<option value={0}>JSON</option>
<option value={1}>Raw values (minimal)</option>
<option value={2}>Raw values (full)</option>
<option value={3}>Domoticz</option>
<option value={4}>Home-Assistant</option>
<option value={255}>Raw data (bytes)</option>
</div>
{#if configuration.p.r.startsWith("10YNO") || configuration.p.r == '10Y1001A1001A48H'}
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Tariff thresholds</strong>
<input type="hidden" name="t" value="true"/>
<div class="flex flex-wrap my-1">
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">1</span>
<input name="t0" bind:value={configuration.t.t[0]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">2</span>
<input name="t1" bind:value={configuration.t.t[1]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">3</span>
<input name="t2" bind:value={configuration.t.t[2]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">4</span>
<input name="t3" bind:value={configuration.t.t[3]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">5</span>
<input name="t4" bind:value={configuration.t.t[4]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">6</span>
<input name="t5" bind:value={configuration.t.t[5]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">7</span>
<input name="t6" bind:value={configuration.t.t[6]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">8</span>
<input name="t7" bind:value={configuration.t.t[7]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
<label class="flex w-40 m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">9</span>
<input name="t8" bind:value={configuration.t.t[8]} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-full text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">kWh</span>
</label>
</div>
<label class="flex m-1">
<span class="flex items-center bg-gray-100 rounded-l-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">Average of</span>
<input name="th" bind:value={configuration.t.h} type="number" min="0" max="255" class="h-10 border-x-0 shadow-sm border-gray-300 w-24 text-right"/>
<span class="flex items-center bg-gray-100 rounded-r-md border border-gray-300 px-3 whitespace-no-wrap text-grey-dark text-sm">hours</span>
</label>
</div>
{/if}
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Cloud</strong>
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Hardware</strong>
{#if sysinfo.board > 20}
<input type="hidden" name="i" value="true"/>
<div class="flex flex-wrap">
<div>
HAN<br/>
<select name="ih" bind:value={configuration.i.h} class="h-10 rounded-l-md shadow-sm border-gray-300">
<UartSelectOptions chip={sysinfo.chip}/>
</select>
</div>
<div>
AP button<br/>
<input name="ia" bind:value={configuration.i.a} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 border-x-0 shadow-sm border-gray-300 text-right"/>
</div>
<div>
LED<label class="ml-4"><input name="ili" value="true" bind:checked={configuration.i.l.i} type="checkbox" class="rounded mb-1"/> inv</label><br/>
<div class="flex">
<input name="ilp" bind:value={configuration.i.l.p} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 rounded-r-md shadow-sm border-gray-300 text-right"/>
</div>
</div>
<div>
RGB<label class="ml-4"><input name="iri" value="true" bind:checked={configuration.i.r.i} type="checkbox" class="rounded mb-1"/> inverted</label><br/>
<div class="flex">
<input name="irr" bind:value={configuration.i.r.r} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 rounded-l-md shadow-sm border-gray-300 text-right"/>
<input name="irg" bind:value={configuration.i.r.g} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 border-x-0 shadow-sm border-gray-300 text-right"/>
<input name="irb" bind:value={configuration.i.r.b} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 rounded-r-md shadow-sm border-gray-300 text-right"/>
</div>
</div>
<div class="my-1">
Temperature<br/>
<input name="itd" bind:value={configuration.i.t.d} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 rounded-l-md shadow-sm border-gray-300 text-right"/>
</div>
<div class="my-1">
Analog temp<br/>
<input name="ita" bind:value={configuration.i.t.a} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 border-x-0 shadow-sm border-gray-300 text-right"/>
</div>
{#if sysinfo.chip != 'esp8266'}
<div class="my-1">
Vcc<br/>
<input name="ivp" bind:value={configuration.i.v.p} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="h-10 rounded-r-md shadow-sm border-gray-300 text-right"/>
</div>
{/if}
{#if configuration.i.v.p > 0}
<div class="my-1">
Voltage divider<br/>
<div class="flex">
<input name="ivdv" bind:value={configuration.i.v.d.v} type="number" min="0" max="65535" class="h-10 rounded-l-md shadow-sm border-gray-300 text-right" placeholder="VCC"/>
<input name="ivdg" bind:value={configuration.i.v.d.g} type="number" min="0" max="65535" class="h-10 border-l-0 rounded-r-md shadow-sm border-gray-300 text-right" placeholder="GND"/>
</div>
</div>
{/if}
</div>
{/if}
{#if configuration.i.v.p > 0 || sysinfo.chip == 'esp8266'}
<div class="my-1 flex flex-wrap">
<div>
Vcc offset<br/>
<input name="ivo" bind:value={configuration.i.v.o} type="number" min="0.0" max="3.5" step="0.01" class="h-10 rounded-l-md shadow-sm border-gray-300 w-24 text-right"/>
</div>
<div>
multiplier<br/>
<input name="ivm" bind:value={configuration.i.v.m} type="number" min="0.1" max="10" step="0.01" class="h-10 border-x-0 shadow-sm border-gray-300 w-24 text-right"/>
</div>
<div>
boot limit<br/>
<input name="ivb" bind:value={configuration.i.v.b} type="number" min="2.5" max="3.5" step="0.1" class="h-10 rounded-r-md shadow-sm border-gray-300 w-24 text-right"/>
</div>
</div>
{/if}
<input type="hidden" name="d" value="true"/>
<div class="mt-3">
<label><input type="checkbox" name="ds" value="true" bind:checked={configuration.d.s} class="rounded mb-1"/> Enable debugging</label>
</div>
{#if configuration.d.s}
<div class="my-auto bg-red-500 text-red-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">Debug can cause sudden reboots. Do not leave on!</div>
<div class="my-1">
<label><input type="checkbox" name="dt" value="true" bind:checked={configuration.d.t} class="rounded mb-1"/> Enable telnet</label>
</div>
{#if configuration.d.t}
<div class="my-auto bg-red-500 text-red-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">Telnet is unsafe and should be off when not in use</div>
{/if}
<div class="my-1">
<select name="dl" bind:value={configuration.d.l} class="form-control form-control-sh-10 rounded-md shadow-sm border-gray-300m">
<option value={1}>Verbose</option>
<option value={2}>Debug</option>
<option value={3}>Info</option>
<option value={4}>Warning</option>
</select>
</div>
<div class="my-3">
SSL
</div>
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Prices</strong>
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Webhook</strong>
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Debugging</strong>
</div>
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Vendor menu</strong>
Board type<br/>
GPIO<br/>
Vcc<br/>
Favico<br/>
{/if}
</div>
</div>
<button type="submit" class="font-bold py-2 px-4 rounded bg-blue-500 text-white">Save</button>
<button type="submit" class="font-bold py-2 px-4 rounded bg-blue-500 text-white float-right mr-3">Save</button>
</form>
<Modal active={loadingOrSaving}/>
<Mask active={loadingOrSaving} message="Loading configuration"/>

View File

@ -3,7 +3,7 @@ import { writable } from 'svelte/store';
let configuration = {};
export const configurationStore = writable(configuration);
export async function getConfiguration(){
export async function getConfiguration() {
const response = await fetch("/configuration.json");
configuration = (await response.json())
configurationStore.set(configuration);

View File

@ -1,15 +1,21 @@
import { readable } from 'svelte/store';
import { readable, writable } from 'svelte/store';
let sysinfo = {};
export const sysinfoStore = readable(sysinfo, (set) => {
async function getSysinfo(){
const response = await fetch("/sysinfo.json");
sysinfo = (await response.json())
set(sysinfo);
}
getSysinfo();
return function stop() {}
});
let sysinfo = {
version: '',
chip: '',
vndcfg: null,
usrcfg: null,
fwconsent: null,
booting: false
};
export const sysinfoStore = writable(sysinfo);
export async function getSysinfo() {
const response = await fetch("/sysinfo.json");
sysinfo = (await response.json())
sysinfoStore.set(sysinfo);
};
let data = {};
export const dataStore = readable(data, (set) => {
@ -17,6 +23,9 @@ export const dataStore = readable(data, (set) => {
const response = await fetch("/data.json");
data = (await response.json())
set(data);
if(sysinfo.booting) {
getSysinfo();
}
}
const interval = setInterval(getData, 5000);
getData();

View File

@ -1,5 +1,5 @@
<script></script>
<!-- Heroicons -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />

View File

@ -1,14 +1,34 @@
<script>
import { Link } from "svelte-navigator";
import { sysinfoStore } from './DataStores.js';
import GitHubLogo from './../assets/github.svg';
import Uptime from "./Uptime.svelte";
import Badge from './Badge.svelte';
import Clock from './Clock.svelte';
import GearIcon from './GearIcon.svelte';
import InfoIcon from "./InfoIcon.svelte";
import HelpIcon from "./HelpIcon.svelte";
import ReloadIcon from "./ReloadIcon.svelte";
export let data = {}
export let sysinfo = {}
let timestamp = new Date(0);
async function reboot() {
const response = await fetch('/reboot', {
method: 'POST'
});
let res = (await response.json())
}
const askReload = function() {
if(confirm('Are you sure you want to reboot the device?')) {
sysinfoStore.update(s => {
s.booting = true;
return s;
});
reboot();
}
}
</script>
<nav class="bg-violet-600 p-1 rounded-md mx-2">
@ -22,10 +42,10 @@
<div class="flex-none my-auto">Free mem: {data.m ? (data.m/1000).toFixed(1) : '-'}kb</div>
</div>
<div class="flex-auto my-auto justify-center p-2">
<Badge title="ESP" text={data.v > 0 ? data.v.toFixed(2)+"V" : "ESP"} color={data.em === 1 ? 'green' : data.em === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
<Badge title="HAN" text="HAN" color={data.hm === 1 ? 'green' : data.hm === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
<Badge title="WiFi" text={data.r ? data.r.toFixed(0)+"dBm" : "WiFi"} color={data.wm === 1 ? 'green' : data.wm === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
<Badge title="MQTT" text="MQTT" color={data.mm === 1 ? 'green' : data.mm === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
<Badge title="ESP" text={sysinfo.booting ? 'Booting' : data.v > 0 ? data.v.toFixed(2)+"V" : "ESP"} color={sysinfo.booting ? 'yellow' : data.em === 1 ? 'green' : data.em === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
<Badge title="HAN" text="HAN" color={sysinfo.booting ? 'gray' : data.hm === 1 ? 'green' : data.hm === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
<Badge title="WiFi" text={data.r ? data.r.toFixed(0)+"dBm" : "WiFi"} color={sysinfo.booting ? 'gray' : data.wm === 1 ? 'green' : data.wm === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
<Badge title="MQTT" text="MQTT" color={sysinfo.booting ? 'gray' : data.mm === 1 ? 'green' : data.mm === 2 ? 'yellow' : data.em === 3 ? 'red' : 'gray'}/>
</div>
<div class="flex-auto p-2 flex flex-row-reverse">
<div class="flex-none">
@ -34,9 +54,18 @@
<div class="flex-none my-auto px-2">
<Clock timestamp={ data.c ? new Date(data.c * 1000) : new Date(0) } />
</div>
<div class="flex-none px-2 mt-1">
<div class="flex-none px-1 mt-1" title="Configuration">
<Link to="/configuration"><GearIcon/></Link>
</div>
<div class="flex-none px-1 mt-1" title="Configuration">
<button on:click={askReload} class={sysinfo.booting ? 'text-yellow-300' : ''}><ReloadIcon/></button>
</div>
<div class="flex-none px-1 mt-1" title="Device information">
<Link to="/status"><InfoIcon/></Link>
</div>
<div class="flex-none px-1 mt-1" title="Device information">
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki" target='_blank' rel="noreferrer"><HelpIcon/></a>
</div>
</div>
</div>
</nav>

View File

@ -0,0 +1,6 @@
<script></script>
<!-- Heroicons -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
</svg>

View File

@ -42,3 +42,50 @@ export function zeropad(num) {
while (num.length < 2) num = "0" + num;
return num;
}
export function boardtype(c, b) {
switch(b) {
case 5:
switch(c) {
case 'esp8266':
return "Pow-K (GPIO12)";
case 'esp32s2':
return "Pow-K+";
}
case 7:
switch(c) {
case 'esp8266':
return "Pow-U (GPIO12)";
case 'esp32s2':
return "Pow-U+";
}
case 6:
return "Pow-P1";
case 51:
return "Wemos S2 mini";
case 50:
return "Generic ESP32-S2";
case 201:
return "Wemos LOLIN D32";
case 202:
return "Adafruit HUZZAH32";
case 203:
return "DevKitC";
case 200:
return "Generic ESP32";
case 2:
return "HAN Reader 2.0 by Max Spencer";
case 0:
return "Custom hardware by Roar Fredriksen";
case 1:
return "Kamstrup module by Egil Opsahl"
case 3:
return "Pow-K (UART0)";
case 4:
return "Pow-U (UART0)";
case 101:
return "Wemos D1 mini";
case 100:
return "Generic ESP8266";
}
}

View File

@ -0,0 +1,6 @@
<script></script>
<!-- Heroicons -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
</svg>

View File

@ -0,0 +1,14 @@
<script>
export let active;
export let message;
</script>
{#if active}
<div class="z-50" aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center">
{#if message}
<div class="bg-white m-2 p-3 rounded-md shadow-lg pb-4 text-gray-700 w-96">{message}</div>
{/if}
</div>
</div>
{/if}

View File

@ -1,10 +0,0 @@
<script>
export let active;
</script>
{#if active}
<div class="z-10" aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-50"></div>
</div>
{/if}

View File

@ -34,6 +34,6 @@
xmlns="http://www.w3.org/2000/svg">
<path d="{ describeArc(150, 150, 115, 210, 510) }" stroke="#eee" fill="none" stroke-width="55"/>
<path d="{ describeArc(150, 150, 115, 210, 210 + (510*pct/100)) }" stroke={color} fill="none" stroke-width="55"/>
<path d="{ describeArc(150, 150, 115, 210, 210 + (300*pct/100)) }" stroke={color} fill="none" stroke-width="55"/>
</svg>
</div>

View File

@ -0,0 +1,6 @@
<script></script>
<!-- Heroicons -->
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</svg>

View File

@ -0,0 +1,92 @@
<script>
import { sysinfoStore } from './DataStores.js';
import Mask from './Mask.svelte'
export let sysinfo = {}
let staticIp = false;
let ntpDhcp = true;
let loadingOrSaving = false;
async function handleSubmit(e) {
loadingOrSaving = true;
const formData = new FormData(e.target)
const data = new URLSearchParams()
for (let field of formData) {
const [key, value] = field
data.append(key, value)
}
const response = await fetch('/save', {
method: 'POST',
body: data
});
let res = (await response.json())
loadingOrSaving = false;
sysinfoStore.update(s => {
s.usrcfg = res.success;
s.booting = res.reboot;
return s;
});
}
</script>
<div class="z-10" aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center">
<div class="bg-white m-2 p-3 rounded-md shadow-lg pb-4 text-gray-700 w-96">
<form on:submit|preventDefault={handleSubmit}>
<input type="hidden" name="s" value="true"/>
<strong class="text-sm">Setup</strong>
<div class="my-3">
SSID<br/>
<input name="ss" type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div class="my-3">
PSK<br/>
<input name="sp" type="password" class="h-10 rounded-md shadow-sm border-gray-300 w-full"/>
</div>
<div>
Hostname:
<input name="sh" type="text" class="h-10 rounded-md shadow-sm border-gray-300 w-full" maxlength="32" pattern="[a-z0-9_-]+" placeholder="Optional, ex.: ams-reader" value="ams-{sysinfo.chipId}"/>
</div>
<div class="my-3">
<label><input type="checkbox" name="sm" value="static" class="rounded mb-1" bind:checked={staticIp} /> Static IP</label>
{#if staticIp}
<br/>
<div class="flex">
<input name="si" type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full" required={staticIp}/>
<select name="su" class="h-10 rounded-r-md shadow-sm border-l-0 border-gray-300" required={staticIp}>
<option value="255.255.255.0">/24</option>
<option value="255.255.0.0">/16</option>
<option value="255.0.0.0">/8</option>
</select>
</div>
{/if}
</div>
{#if staticIp}
<div class="my-3 flex">
<div>
Gateway<br/>
<input name="sg" type="text" class="h-10 rounded-l-md shadow-sm border-gray-300 w-full"/>
</div>
<div>
DNS<br/>
<input name="sd" type="text" class="h-10 rounded-r-md border-l-0 shadow-sm border-gray-300 w-full"/>
</div>
</div>
{/if}
<div class="my-3">
<label><input type="checkbox" name="sf" value="true" class="rounded mb-1"/> Enable OTA upgrade (implies data collection)</label><br/>
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/Data-collection-on-OTA-firmware-upgrade" target="_blank" class="text-blue-600 hover:text-blue-800">Read more</a>
</div>
<div class="my-3">
<button type="submit" class="font-bold py-1 px-4 rounded bg-blue-500 text-white float-right">Save</button>
</div>
</form>
</div>
</div>
</div>
<Mask active={loadingOrSaving} message="Saving your configuration to the device"/>

View File

@ -0,0 +1,55 @@
<script>
import { metertype, boardtype } from './Helpers.js';
import { getSysinfo } from './DataStores.js';
export let sysinfo = {}
export let data = {}
getSysinfo();
</script>
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Device information</strong>
<div class="my-3">
Chip: {sysinfo.chip}
</div>
<div class="my-3">
Device: {boardtype(sysinfo.chip, sysinfo.board)}
</div>
<div class="my-3">
Firmware version: {sysinfo.version}
</div>
</div>
{#if sysinfo.meter}
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Meter</strong>
<div class="my-3">
Manufacturer: {metertype(sysinfo.meter.mfg)}
</div>
<div class="my-3">
Model: {sysinfo.meter.model}
</div>
<div class="my-3">
ID: {sysinfo.meter.id}
</div>
</div>
{/if}
{#if sysinfo.net}
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
<strong class="text-sm">Network</strong>
<div class="my-3">
IP: {sysinfo.net.ip}
</div>
<div class="my-3">
Mask: {sysinfo.net.mask}
</div>
<div class="my-3">
Gateway: {sysinfo.net.gw}
</div>
<div class="my-3">
DNS: {sysinfo.net.dns1} {#if sysinfo.net.dns2 != '0.0.0.0'}/ {sysinfo.net.dns2}{/if}
</div>
</div>
{/if}
</div>

View File

@ -0,0 +1,80 @@
<script>
export let chip;
</script>
{#if chip == 'esp8266'}
<option value={3}>UART0</option>
<option value={113}>UART2</option>
<option value={4}>GPIO4</option>
<option value={5}>GPIO5</option>
<option value={9}>GPIO9</option>
<option value={10}>GPIO10</option>
<option value={12}>GPIO12</option>
<option value={13}>GPIO13</option>
<option value={14}>GPIO14</option>
<option value={15}>GPIO15</option>
<option value={16}>GPIO16</option>
{/if}
{#if chip == 'esp32' || chip == 'esp32solo'}
<option value={3}>UART0</option>
<option value={9}>UART1</option>
<option value={16}>UART2</option>
<option value={4}>GPIO4</option>
<option value={5}>GPIO5</option>
<option value={6}>GPIO6</option>
<option value={7}>GPIO7</option>
<option value={8}>GPIO8</option>
<option value={10}>GPIO10</option>
<option value={11}>GPIO11</option>
<option value={12}>GPIO12</option>
<option value={13}>GPIO13</option>
<option value={14}>GPIO14</option>
<option value={15}>GPIO15</option>
<option value={17}>GPIO17</option>
<option value={18}>GPIO18</option>
<option value={19}>GPIO19</option>
<option value={21}>GPIO21</option>
<option value={22}>GPIO22</option>
<option value={23}>GPIO23</option>
<option value={25}>GPIO25</option>
<option value={32}>GPIO32</option>
<option value={33}>GPIO33</option>
<option value={34}>GPIO34</option>
<option value={35}>GPIO35</option>
<option value={36}>GPIO36</option>
<option value={39}>GPIO39</option>
{/if}
{#if chip == 'esp32s2'}
<option value={3}>UART0</option>
<option value={18}>UART1</option>
<option value={4}>GPIO4</option>
<option value={5}>GPIO5</option>
<option value={6}>GPIO6</option>
<option value={7}>GPIO7</option>
<option value={8}>GPIO8</option>
<option value={9}>GPIO9</option>
<option value={10}>GPIO10</option>
<option value={11}>GPIO11</option>
<option value={12}>GPIO12</option>
<option value={13}>GPIO13</option>
<option value={14}>GPIO14</option>
<option value={15}>GPIO15</option>
<option value={16}>GPIO16</option>
<option value={17}>GPIO17</option>
<option value={19}>GPIO19</option>
<option value={21}>GPIO21</option>
<option value={22}>GPIO22</option>
<option value={23}>GPIO23</option>
<option value={25}>GPIO25</option>
<option value={32}>GPIO32</option>
<option value={33}>GPIO33</option>
<option value={34}>GPIO34</option>
<option value={35}>GPIO35</option>
<option value={36}>GPIO36</option>
<option value={39}>GPIO39</option>
<option value={40}>GPIO40</option>
<option value={41}>GPIO41</option>
<option value={42}>GPIO42</option>
<option value={43}>GPIO43</option>
<option value={44}>GPIO44</option>
{/if}

View File

@ -0,0 +1,61 @@
<script>
import { sysinfoStore } from './DataStores.js';
import BoardTypeSelectOptions from './BoardTypeSelectOptions.svelte';
import UartSelectOptions from './UartSelectOptions.svelte';
import Mask from './Mask.svelte'
export let sysinfo = {}
let loadingOrSaving = false;
async function handleSubmit(e) {
loadingOrSaving = true;
const formData = new FormData(e.target)
const data = new URLSearchParams()
for (let field of formData) {
const [key, value] = field
data.append(key, value)
}
const response = await fetch('/save', {
method: 'POST',
body: data
});
let res = (await response.json())
loadingOrSaving = false;
sysinfoStore.update(s => {
s.vndcfg = res.success;
s.booting = res.reboot;
return s;
});
}
</script>
<div class="z-10" aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center">
<div class="bg-white m-2 p-3 rounded-md shadow-lg pb-4 text-gray-700 w-96">
<form on:submit|preventDefault={handleSubmit}>
<input type="hidden" name="v" value="true"/>
<strong class="text-sm">Vendor configuration</strong>
<div class="my-3">
Board type<br/>
<select name="b" class="h-10 rounded-md shadow-sm border-gray-300 p-0 w-full" bind:value={sysinfo.board}>
<BoardTypeSelectOptions chip={sysinfo.chip}/>
</select>
</div>
{#if sysinfo.board && sysinfo.board > 20}
<div class="my-3">
HAN GPIO<br/>
<select name="h" class="h-10 rounded-md shadow-sm border-gray-300">
<UartSelectOptions chip={sysinfo.chip}/>
</select>
</div>
{/if}
<div class="my-3">
<button type="submit" class="font-bold py-1 px-4 rounded bg-blue-500 text-white float-right">Save</button>
</div>
</form>
</div>
</div>
</div>
<Mask active={loadingOrSaving} message="Saving device configuration" />

View File

@ -17,14 +17,15 @@ export default defineConfig({
plugins: [svelte()],
server: {
proxy: {
"/data.json": "http://192.168.233.235",
"/data.json": "http://192.168.233.244",
"/energyprice.json": "http://192.168.233.235",
"/dayplot.json": "http://192.168.233.235",
"/monthplot.json": "http://192.168.233.235",
"/temperature.json": "http://192.168.233.235",
"/sysinfo.json": "http://192.168.233.244",
"/configuration.json": "http://192.168.233.244",
"/save": "http://192.168.233.244"
"/save": "http://192.168.233.244",
"/reboot": "http://192.168.233.244"
}
}
})

View File

@ -82,9 +82,11 @@ private:
void monthplotJson();
void energyPriceJson();
void temperatureJson();
void wifiScanJson();
void configurationJson();
void handleSave();
void reboot();
void notFound();
};

View File

@ -35,6 +35,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
// TODO
server.on(F("/"), HTTP_GET, std::bind(&AmsWebServer::indexHtml, this));
server.on(F("/configuration"), HTTP_GET, std::bind(&AmsWebServer::indexHtml, this));
server.on(F("/status"), HTTP_GET, std::bind(&AmsWebServer::indexHtml, this));
server.on(F("/index.css"), HTTP_GET, std::bind(&AmsWebServer::indexCss, this));
server.on(F("/index.js"), HTTP_GET, std::bind(&AmsWebServer::indexJs, this));
server.on(F("/github.svg"), HTTP_GET, std::bind(&AmsWebServer::githubSvg, this));
@ -45,8 +46,11 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
server.on(F("/energyprice.json"), HTTP_GET, std::bind(&AmsWebServer::energyPriceJson, this));
server.on(F("/temperature.json"), HTTP_GET, std::bind(&AmsWebServer::temperatureJson, this));
server.on(F("/wifiscan.json"), HTTP_GET, std::bind(&AmsWebServer::wifiScanJson, this));
server.on(F("/configuration.json"), HTTP_GET, std::bind(&AmsWebServer::configurationJson, this));
server.on(F("/save"), HTTP_POST, std::bind(&AmsWebServer::handleSave, this));
server.on(F("/reboot"), HTTP_POST, std::bind(&AmsWebServer::reboot, this));
server.onNotFound(std::bind(&AmsWebServer::notFound, this));
@ -132,7 +136,7 @@ void AmsWebServer::githubSvg() {
void AmsWebServer::sysinfoJson() {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /sysinfo.json over http...\n");
DynamicJsonDocument doc(256);
DynamicJsonDocument doc(512);
doc["version"] = VERSION;
#if defined(CONFIG_IDF_TARGET_ESP32S2)
doc["chip"] = "esp32s2";
@ -144,6 +148,32 @@ void AmsWebServer::sysinfoJson() {
doc["chip"] = "esp8266";
#endif
uint16_t chipId;
#if defined(ESP32)
chipId = ESP.getEfuseMac();
#else
chipId = ESP.getChipId();
#endif
doc["chipId"] = String(chipId, HEX);
SystemConfig sys;
config->getSystemConfig(sys);
doc["board"] = sys.boardType;
doc["vndcfg"] = sys.vendorConfigured;
doc["usrcfg"] = sys.userConfigured;
doc["fwconsent"] = sys.dataCollectionConsent;
doc["country"] = sys.country;
doc["net"]["ip"] = WiFi.localIP().toString();
doc["net"]["mask"] = WiFi.subnetMask().toString();
doc["net"]["gw"] = WiFi.gatewayIP().toString();
doc["net"]["dns1"] = WiFi.dnsIP(0).toString();
doc["net"]["dns2"] = WiFi.dnsIP(1).toString();
doc["meter"]["mfg"] = meterState->getMeterType();
doc["meter"]["model"] = meterState->getMeterModel();
doc["meter"]["id"] = meterState->getMeterId();
serializeJson(doc, buf, BufferSize);
server.send(200, MIME_JSON, buf);
}
@ -578,59 +608,189 @@ void AmsWebServer::configurationJson() {
if(!checkSecurity(1))
return;
DynamicJsonDocument doc(1024);
DynamicJsonDocument doc(2048);
doc["version"] = VERSION;
NtpConfig ntpConfig;
config->getNtpConfig(ntpConfig);
WiFiConfig wifiConfig;
config->getWiFiConfig(wifiConfig);
WebConfig webConfig;
config->getWebConfig(webConfig);
doc["general"]["zone"] = "Europe/Oslo";
doc["general"]["host"] = wifiConfig.hostname;
doc["general"]["sec"] = webConfig.security;
doc["general"]["user"] = webConfig.username;
doc["general"]["pass"] = webConfig.password;
if(ntpConfig.offset == 0 && ntpConfig.summerOffset == 0)
doc["g"]["t"] = "UTC";
else if(ntpConfig.offset == 360 && ntpConfig.summerOffset == 360)
doc["g"]["t"] = "CET/CEST";
doc["g"]["h"] = wifiConfig.hostname;
doc["g"]["s"] = webConfig.security;
doc["g"]["u"] = webConfig.username;
doc["g"]["p"] = strlen(webConfig.password) > 0 ? "***" : "";
bool encen = false;
for(uint8_t i = 0; i < 16; i++) {
if(meterConfig->encryptionKey[i] > 0) {
encen = true;
}
}
config->getMeterConfig(*meterConfig);
doc["meter"]["baud"] = meterConfig->baud;
doc["meter"]["par"] = meterConfig->parity;
doc["meter"]["inv"] = meterConfig->invert;
doc["meter"]["dist"] = meterConfig->distributionSystem;
doc["meter"]["fuse"] = meterConfig->mainFuse;
doc["meter"]["prod"] = meterConfig->productionCapacity;
doc["meter"]["enc"] = toHex(meterConfig->encryptionKey, 16);
doc["meter"]["auth"] = toHex(meterConfig->authenticationKey, 16);
doc["m"]["b"] = meterConfig->baud;
doc["m"]["p"] = meterConfig->parity;
doc["m"]["i"] = meterConfig->invert;
doc["m"]["d"] = meterConfig->distributionSystem;
doc["m"]["f"] = meterConfig->mainFuse;
doc["m"]["r"] = meterConfig->productionCapacity;
doc["m"]["e"]["e"] = encen;
doc["m"]["e"]["k"] = toHex(meterConfig->encryptionKey, 16);
doc["m"]["e"]["a"] = toHex(meterConfig->authenticationKey, 16);
doc["m"]["m"]["e"] = meterConfig->wattageMultiplier > 1 || meterConfig->voltageMultiplier > 1 || meterConfig->amperageMultiplier > 1 || meterConfig->accumulatedMultiplier > 1;
doc["m"]["m"]["w"] = meterConfig->wattageMultiplier / 1000.0;
doc["m"]["m"]["v"] = meterConfig->voltageMultiplier / 1000.0;
doc["m"]["m"]["a"] = meterConfig->amperageMultiplier / 1000.0;
doc["m"]["m"]["c"] = meterConfig->accumulatedMultiplier / 1000.0;
// TODO: Tariff thresholds
// TODO: Multipliers
EnergyAccountingConfig eac;
config->getEnergyAccountingConfig(eac);
doc["t"]["t"][0] = eac.thresholds[0];
doc["t"]["t"][1] = eac.thresholds[1];
doc["t"]["t"][2] = eac.thresholds[2];
doc["t"]["t"][3] = eac.thresholds[3];
doc["t"]["t"][4] = eac.thresholds[4];
doc["t"]["t"][5] = eac.thresholds[5];
doc["t"]["t"][6] = eac.thresholds[6];
doc["t"]["t"][7] = eac.thresholds[7];
doc["t"]["t"][8] = eac.thresholds[8];
doc["t"]["t"][9] = eac.thresholds[9];
doc["t"]["h"] = eac.hours;
doc["wifi"]["ssid"] = wifiConfig.ssid;
doc["wifi"]["psk"] = wifiConfig.psk;
doc["wifi"]["pwr"] = wifiConfig.power / 10.0;
doc["wifi"]["sleep"] = wifiConfig.sleep;
doc["w"]["s"] = wifiConfig.ssid;
doc["w"]["p"] = strlen(wifiConfig.psk) > 0 ? "***" : "";
doc["w"]["w"] = wifiConfig.power / 10.0;
doc["w"]["z"] = wifiConfig.sleep;
NtpConfig ntpConfig;
config->getNtpConfig(ntpConfig);
doc["net"]["mode"] = strlen(wifiConfig.ip) > 0 ? "static" : "dhcp";
doc["net"]["ip"] = wifiConfig.ip;
doc["net"]["mask"] = wifiConfig.subnet;
doc["net"]["gw"] = wifiConfig.gateway;
doc["net"]["dns1"] = wifiConfig.dns1;
doc["net"]["dns2"] = wifiConfig.dns2;
doc["net"]["mdns"] = wifiConfig.mdns;
doc["net"]["ntp1"] = ntpConfig.server;
doc["net"]["ntpdhcp"] = ntpConfig.dhcp;
doc["n"]["m"] = strlen(wifiConfig.ip) > 0 ? "static" : "dhcp";
doc["n"]["i"] = wifiConfig.ip;
doc["n"]["s"] = wifiConfig.subnet;
doc["n"]["g"] = wifiConfig.gateway;
doc["n"]["d1"] = wifiConfig.dns1;
doc["n"]["d2"] = wifiConfig.dns2;
doc["n"]["d"] = wifiConfig.mdns;
doc["n"]["n1"] = ntpConfig.server;
doc["n"]["h"] = ntpConfig.dhcp;
MqttConfig mqttConfig;
config->getMqttConfig(mqttConfig);
doc["mqtt"]["host"] = mqttConfig.host;
doc["mqtt"]["port"] = mqttConfig.port;
doc["mqtt"]["user"] = mqttConfig.username;
doc["mqtt"]["pass"] = mqttConfig.password;
doc["mqtt"]["clid"] = mqttConfig.clientId;
doc["mqtt"]["pub"] = mqttConfig.publishTopic;
doc["mqtt"]["mode"] = mqttConfig.payloadFormat;
doc["mqtt"]["ssl"] = mqttConfig.ssl;
doc["q"]["h"] = mqttConfig.host;
doc["q"]["p"] = mqttConfig.port;
doc["q"]["u"] = mqttConfig.username;
doc["q"]["a"] = strlen(mqttConfig.password) > 0 ? "***" : "";
doc["q"]["c"] = mqttConfig.clientId;
doc["q"]["b"] = mqttConfig.publishTopic;
doc["q"]["m"] = mqttConfig.payloadFormat;
doc["q"]["s"]["e"] = mqttConfig.ssl;
if(LittleFS.begin()) {
doc["q"]["s"]["c"] = LittleFS.exists(FILE_MQTT_CA);
doc["q"]["s"]["r"] = LittleFS.exists(FILE_MQTT_CERT);
doc["q"]["s"]["k"] = LittleFS.exists(FILE_MQTT_KEY);
LittleFS.end();
} else {
doc["q"]["s"]["c"] = false;
doc["q"]["s"]["r"] = false;
doc["q"]["s"]["k"] = false;
}
EntsoeConfig entsoe;
config->getEntsoeConfig(entsoe);
doc["p"]["e"] = strlen(entsoe.token) > 0;
doc["p"]["t"] = entsoe.token;
doc["p"]["r"] = entsoe.area;
doc["p"]["c"] = entsoe.currency;
doc["p"]["m"] = entsoe.multiplier / 1000.0;
DebugConfig debugConfig;
config->getDebugConfig(debugConfig);
doc["d"]["s"] = debugConfig.serial;
doc["d"]["t"] = debugConfig.telnet;
doc["d"]["l"] = debugConfig.level;
GpioConfig gpioConfig;
config->getGpioConfig(gpioConfig);
if(gpioConfig.hanPin == 0xff)
doc["i"]["h"] = nullptr;
else
doc["i"]["h"] = gpioConfig.hanPin;
if(gpioConfig.apPin == 0xff)
doc["i"]["a"] = nullptr;
else
doc["i"]["a"] = gpioConfig.apPin;
if(gpioConfig.ledPin == 0xff)
doc["i"]["l"]["p"] = nullptr;
else
doc["i"]["l"]["p"] = gpioConfig.ledPin;
doc["i"]["l"]["i"] = gpioConfig.ledInverted;
if(gpioConfig.ledPinRed == 0xff)
doc["i"]["r"]["r"] = nullptr;
else
doc["i"]["r"]["r"] = gpioConfig.ledPinRed;
if(gpioConfig.ledPinGreen == 0xff)
doc["i"]["r"]["g"] = nullptr;
else
doc["i"]["r"]["g"] = gpioConfig.ledPinGreen;
if(gpioConfig.ledPinBlue == 0xff)
doc["i"]["r"]["b"] = nullptr;
else
doc["i"]["r"]["b"] = gpioConfig.ledPinBlue;
doc["i"]["r"]["i"] = gpioConfig.ledRgbInverted;
if(gpioConfig.tempSensorPin == 0xff)
doc["i"]["t"]["d"] = nullptr;
else
doc["i"]["t"]["d"] = gpioConfig.tempSensorPin;
if(gpioConfig.tempAnalogSensorPin == 0xff)
doc["i"]["t"]["a"] = nullptr;
else
doc["i"]["t"]["a"] = gpioConfig.tempAnalogSensorPin;
if(gpioConfig.vccPin == 0xff)
doc["i"]["v"]["p"] = nullptr;
else
doc["i"]["v"]["p"] = gpioConfig.vccPin;
if(gpioConfig.vccOffset == 0)
doc["i"]["v"]["o"] = nullptr;
else
doc["i"]["v"]["o"] = gpioConfig.vccOffset / 100.0;
if(gpioConfig.vccMultiplier == 0)
doc["i"]["v"]["m"] = nullptr;
else
doc["i"]["v"]["m"] = gpioConfig.vccMultiplier / 1000.0;
if(gpioConfig.vccResistorVcc == 0)
doc["i"]["v"]["d"]["v"] = nullptr;
else
doc["i"]["v"]["d"]["v"] = gpioConfig.vccResistorVcc;
if(gpioConfig.vccResistorGnd == 0)
doc["i"]["v"]["d"]["g"] = nullptr;
else
doc["i"]["v"]["d"]["g"] = gpioConfig.vccResistorGnd;
if(gpioConfig.vccBootLimit == 0)
doc["i"]["v"]["b"] = nullptr;
else
doc["i"]["v"]["b"] = gpioConfig.vccBootLimit / 10.0;
serializeJson(doc, buf, BufferSize);
server.send(200, MIME_JSON, buf);
@ -640,86 +800,253 @@ void AmsWebServer::handleSave() {
if(!checkSecurity(1))
return;
if(server.hasArg(F("meter")) && server.arg(F("meter")) == F("true")) {
bool success = true;
if(server.hasArg(F("v")) && server.arg(F("v")) == F("true")) {
int boardType = server.arg(F("b")).toInt();
int hanPin = server.arg(F("h")).toInt();
#if defined(CONFIG_IDF_TARGET_ESP32S2)
switch(boardType) {
case 5: // Pow-K+
case 7: // Pow-U+
case 6: // Pow-P1
config->clearGpio(*gpioConfig);
gpioConfig->hanPin = 16;
gpioConfig->apPin = 0;
gpioConfig->ledPinRed = 13;
gpioConfig->ledPinGreen = 14;
gpioConfig->ledRgbInverted = true;
gpioConfig->vccPin = 10;
gpioConfig->vccResistorGnd = 22;
gpioConfig->vccResistorVcc = 33;
break;
case 51: // Wemos S2 mini
gpioConfig->ledPin = 15;
gpioConfig->ledInverted = false;
gpioConfig->apPin = 0;
case 50: // Generic ESP32-S2
gpioConfig->hanPin = hanPin > 0 ? hanPin : 18;
break;
default:
success = false;
}
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
#elif defined(ESP32)
switch(boardType) {
case 201: // D32
gpioConfig->hanPin = hanPin > 0 ? hanPin : 16;
gpioConfig->apPin = 4;
gpioConfig->ledPin = 5;
gpioConfig->ledInverted = true;
break;
case 202: // Feather
case 203: // DevKitC
case 200: // ESP32
gpioConfig->hanPin = hanPin > 0 ? hanPin : 16;
gpioConfig->ledPin = 2;
gpioConfig->ledInverted = false;
break;
default:
success = false;
}
#elif defined(ESP8266)
switch(boardType) {
case 2: // spenceme
config->clearGpio(*gpioConfig);
gpioConfig->vccBootLimit = 33;
case 0: // roarfred
gpioConfig->hanPin = 3;
gpioConfig->apPin = 0;
gpioConfig->ledPin = 2;
gpioConfig->ledInverted = true;
gpioConfig->tempSensorPin = 5;
break;
case 1: // Arnio Kamstrup
case 3: // Pow-K UART0
case 4: // Pow-U UART0
config->clearGpio(*gpioConfig);
gpioConfig->hanPin = 3;
gpioConfig->apPin = 0;
gpioConfig->ledPin = 2;
gpioConfig->ledInverted = true;
gpioConfig->ledPinRed = 13;
gpioConfig->ledPinGreen = 14;
gpioConfig->ledRgbInverted = true;
break;
case 5: // Pow-K GPIO12
case 7: // Pow-U GPIO12
config->clearGpio(*gpioConfig);
gpioConfig->hanPin = 12;
gpioConfig->apPin = 0;
gpioConfig->ledPin = 2;
gpioConfig->ledInverted = true;
gpioConfig->ledPinRed = 13;
gpioConfig->ledPinGreen = 14;
gpioConfig->ledRgbInverted = true;
break;
case 101: // D1
gpioConfig->hanPin = hanPin > 0 ? hanPin : 5;
gpioConfig->apPin = 4;
gpioConfig->ledPin = 2;
gpioConfig->ledInverted = true;
gpioConfig->vccMultiplier = 1100;
break;
case 100: // ESP8266
gpioConfig->hanPin = hanPin > 0 ? hanPin : 3;
gpioConfig->ledPin = 2;
gpioConfig->ledInverted = true;
break;
default:
success = false;
}
#endif
SystemConfig sys;
config->getSystemConfig(sys);
sys.boardType = success ? boardType : 0xFF;
sys.vendorConfigured = success;
config->setSystemConfig(sys);
}
if(server.hasArg(F("s")) && server.arg(F("s")) == F("true")) {
SystemConfig sys;
config->getSystemConfig(sys);
config->clear();
WiFiConfig wifi;
config->clearWifi(wifi);
strcpy(wifi.ssid, server.arg(F("ss")).c_str());
String psk = server.arg(F("sp"));
if(!psk.equals("***")) {
strcpy(wifi.psk, psk.c_str());
}
wifi.mode = 1; // WIFI_STA
if(server.hasArg(F("sm")) && server.arg(F("sm")) == "static") {
strcpy(wifi.ip, server.arg(F("si")).c_str());
strcpy(wifi.gateway, server.arg(F("sg")).c_str());
strcpy(wifi.subnet, server.arg(F("su")).c_str());
strcpy(wifi.dns1, server.arg(F("sd")).c_str());
}
if(server.hasArg(F("sh")) && !server.arg(F("sh")).isEmpty()) {
strcpy(wifi.hostname, server.arg(F("sh")).c_str());
wifi.mdns = true;
} else {
wifi.mdns = false;
}
switch(sys.boardType) {
case 6: // Pow-P1
meterConfig->baud = 115200;
meterConfig->parity = 3; // 8N1
break;
case 3: // Pow-K UART0
case 5: // Pow-K+
meterConfig->parity = 3; // 8N1
case 2: // spenceme
case 50: // Generic ESP32-S2
case 51: // Wemos S2 mini
meterConfig->baud = 2400;
wifi.sleep = 1; // Modem sleep
break;
case 4: // Pow-U UART0
case 7: // Pow-U+
wifi.sleep = 2; // Light sleep
break;
}
config->setWiFiConfig(wifi);
config->setMeterConfig(*meterConfig);
sys.userConfigured = success;
//TODO sys.country
sys.dataCollectionConsent = server.hasArg(F("sf")) && server.arg(F("sf")) == F("true") ? 1 : 2;
config->setSystemConfig(sys);
}
if(server.hasArg(F("m")) && server.arg(F("m")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received meter config"));
config->getMeterConfig(*meterConfig);
meterConfig->baud = server.arg(F("meter_baud")).toInt();
meterConfig->parity = server.arg(F("meter_par")).toInt();
meterConfig->invert = server.hasArg(F("meter_inv")) && server.arg(F("meter_inv")) == F("true");
meterConfig->distributionSystem = server.arg(F("meter_dist")).toInt();
meterConfig->mainFuse = server.arg(F("meter_fuse")).toInt();
meterConfig->productionCapacity = server.arg(F("meter_prod")).toInt();
meterConfig->baud = server.arg(F("mb")).toInt();
meterConfig->parity = server.arg(F("mp")).toInt();
meterConfig->invert = server.hasArg(F("mi")) && server.arg(F("mi")) == F("true");
meterConfig->distributionSystem = server.arg(F("md")).toInt();
meterConfig->mainFuse = server.arg(F("mf")).toInt();
meterConfig->productionCapacity = server.arg(F("mr")).toInt();
maxPwr = 0;
String encryptionKeyHex = server.arg(F("meter_enc"));
String encryptionKeyHex = server.arg(F("mek"));
if(!encryptionKeyHex.isEmpty()) {
encryptionKeyHex.replace(F("0x"), F(""));
fromHex(meterConfig->encryptionKey, encryptionKeyHex, 16);
}
String authenticationKeyHex = server.arg(F("meter_auth"));
String authenticationKeyHex = server.arg(F("mea"));
if(!authenticationKeyHex.isEmpty()) {
authenticationKeyHex.replace(F("0x"), F(""));
fromHex(meterConfig->authenticationKey, authenticationKeyHex, 16);
}
meterConfig->wattageMultiplier = server.arg(F("mmw")).toDouble() * 1000;
meterConfig->voltageMultiplier = server.arg(F("mmv")).toDouble() * 1000;
meterConfig->amperageMultiplier = server.arg(F("mma")).toDouble() * 1000;
meterConfig->accumulatedMultiplier = server.arg(F("mmc")).toDouble() * 1000;
config->setMeterConfig(*meterConfig);
}
if(server.hasArg(F("ma")) && server.arg(F("ma")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received meter advanced config"));
config->getMeterConfig(*meterConfig);
meterConfig->wattageMultiplier = server.arg(F("wm")).toDouble() * 1000;
meterConfig->voltageMultiplier = server.arg(F("vm")).toDouble() * 1000;
meterConfig->amperageMultiplier = server.arg(F("am")).toDouble() * 1000;
meterConfig->accumulatedMultiplier = server.arg(F("cm")).toDouble() * 1000;
config->setMeterConfig(*meterConfig);
}
if(server.hasArg(F("wifi")) && server.arg(F("wifi")) == F("true")) {
if(server.hasArg(F("w")) && server.arg(F("w")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received WiFi config"));
WiFiConfig wifi;
config->getWiFiConfig(wifi);
strcpy(wifi.ssid, server.arg(F("wifi_ssid")).c_str());
strcpy(wifi.psk, server.arg(F("wifi_psk")).c_str());
wifi.power = server.arg(F("wifi_pwr")).toFloat() * 10;
wifi.sleep = server.arg(F("wifi_sleep")).toInt();
strcpy(wifi.ssid, server.arg(F("ws")).c_str());
String psk = server.arg(F("wp"));
if(!psk.equals("***")) {
strcpy(wifi.psk, psk.c_str());
}
wifi.power = server.arg(F("ww")).toFloat() * 10;
wifi.sleep = server.arg(F("wz")).toInt();
config->setWiFiConfig(wifi);
if(server.hasArg(F("nm")) && server.arg(F("nm")) == "static") {
strcpy(wifi.ip, server.arg(F("ni")).c_str());
strcpy(wifi.gateway, server.arg(F("ng")).c_str());
strcpy(wifi.subnet, server.arg(F("ns")).c_str());
strcpy(wifi.dns1, server.arg(F("nd1")).c_str());
strcpy(wifi.dns2, server.arg(F("nd2")).c_str());
}
wifi.mdns = server.hasArg(F("nd")) && server.arg(F("nd")) == F("true");
config->setWiFiConfig(wifi);
}
if(server.hasArg(F("net")) && server.arg(F("net")) == F("true")) {
WiFiConfig wifi;
config->getWiFiConfig(wifi);
if(server.hasArg(F("net_mode")) && server.arg(F("net_mode")) == "static") {
strcpy(wifi.ip, server.arg(F("net_ip")).c_str());
strcpy(wifi.gateway, server.arg(F("net_gw")).c_str());
strcpy(wifi.subnet, server.arg(F("net_sn")).c_str());
strcpy(wifi.dns1, server.arg(F("net_dns1")).c_str());
strcpy(wifi.dns2, server.arg(F("net_dns2")).c_str());
}
wifi.mdns = server.hasArg(F("net_mdns")) && server.arg(F("net_mdns")) == F("true");
config->setWiFiConfig(wifi);
if(server.hasArg(F("ntp")) && server.arg(F("ntp")) == F("true")) {
NtpConfig ntp;
config->getNtpConfig(ntp);
ntp.dhcp = server.hasArg(F("net_ntpdhcp")) && server.arg(F("net_ntpdhcp")) == F("true");
strcpy(ntp.server, server.arg(F("net_ntp1")).c_str());
ntp.enable = true;
ntp.dhcp = server.hasArg(F("ntpd")) && server.arg(F("ntpd")) == F("true");
strcpy(ntp.server, server.arg(F("ntph")).c_str());
config->setNtpConfig(ntp);
}
if(server.hasArg(F("mqtt")) && server.arg(F("mqtt")) == F("true")) {
if(server.hasArg(F("q")) && server.arg(F("q")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received MQTT config"));
MqttConfig mqtt;
if(server.hasArg(F("mqtt_host")) && !server.arg(F("mqtt_host")).isEmpty()) {
strcpy(mqtt.host, server.arg(F("mqtt_host")).c_str());
strcpy(mqtt.clientId, server.arg(F("mqtt_clid")).c_str());
strcpy(mqtt.publishTopic, server.arg(F("mqtt_pub")).c_str());
strcpy(mqtt.subscribeTopic, server.arg(F("mqtt_sub")).c_str());
strcpy(mqtt.username, server.arg(F("mqtt_user")).c_str());
strcpy(mqtt.password, server.arg(F("mqtt_pass")).c_str());
mqtt.payloadFormat = server.arg(F("mqtt_mode")).toInt();
mqtt.ssl = server.arg(F("mqtt_ssl")) == F("true");
if(server.hasArg(F("qh")) && !server.arg(F("qh")).isEmpty()) {
strcpy(mqtt.host, server.arg(F("qh")).c_str());
strcpy(mqtt.clientId, server.arg(F("qc")).c_str());
strcpy(mqtt.publishTopic, server.arg(F("qb")).c_str());
strcpy(mqtt.subscribeTopic, server.arg(F("qr")).c_str());
strcpy(mqtt.username, server.arg(F("qu")).c_str());
String pass = server.arg(F("qp"));
if(!pass.equals("***")) {
strcpy(mqtt.password, pass.c_str());
}
mqtt.payloadFormat = server.arg(F("qm")).toInt();
mqtt.ssl = server.arg(F("qs")) == F("true");
mqtt.port = server.arg(F("mqtt_port")).toInt();
mqtt.port = server.arg(F("qp")).toInt();
if(mqtt.port == 0) {
mqtt.port = mqtt.ssl ? 8883 : 1883;
}
@ -742,12 +1069,15 @@ void AmsWebServer::handleSave() {
}
if(server.hasArg(F("general")) && server.arg(F("general")) == F("true")) {
if(server.hasArg(F("g")) && server.arg(F("g")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received web config"));
webConfig.security = server.arg(F("general_sec")).toInt();
webConfig.security = server.arg(F("gs")).toInt();
if(webConfig.security > 0) {
strcpy(webConfig.username, server.arg(F("general_user")).c_str());
strcpy(webConfig.password, server.arg(F("general_pass")).c_str());
strcpy(webConfig.username, server.arg(F("gu")).c_str());
String pass = server.arg(F("gp"));
if(!pass.equals("***")) {
strcpy(webConfig.password, pass.c_str());
}
debugger->setPassword(webConfig.password);
} else {
strcpy_P(webConfig.username, PSTR(""));
@ -758,42 +1088,54 @@ void AmsWebServer::handleSave() {
WiFiConfig wifi;
config->getWiFiConfig(wifi);
if(server.hasArg(F("general_host")) && !server.arg(F("general_host")).isEmpty()) {
strcpy(wifi.hostname, server.arg(F("general_host")).c_str());
if(server.hasArg(F("gh")) && !server.arg(F("gh")).isEmpty()) {
strcpy(wifi.hostname, server.arg(F("gh")).c_str());
}
config->setWiFiConfig(wifi);
NtpConfig ntp;
config->getNtpConfig(ntp);
String tz = server.arg(F("gt"));
if(tz.equals("UTC")) {
ntp.offset = 0;
ntp.summerOffset = 0;
} else if(tz.equals("CET/CEST")) {
ntp.offset = 360;
ntp.summerOffset = 360;
}
config->setNtpConfig(ntp);
}
if(server.hasArg(F("gc")) && server.arg(F("gc")) == F("true")) {
if(server.hasArg(F("i")) && server.arg(F("i")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received GPIO config"));
gpioConfig->hanPin = server.hasArg(F("h")) && !server.arg(F("h")).isEmpty() ? server.arg(F("h")).toInt() : 3;
gpioConfig->ledPin = server.hasArg(F("l")) && !server.arg(F("l")).isEmpty() ? server.arg(F("l")).toInt() : 0xFF;
gpioConfig->ledInverted = server.hasArg(F("i")) && server.arg(F("i")) == F("true");
gpioConfig->ledPinRed = server.hasArg(F("r")) && !server.arg(F("r")).isEmpty() ? server.arg(F("r")).toInt() : 0xFF;
gpioConfig->ledPinGreen = server.hasArg(F("e")) && !server.arg(F("e")).isEmpty() ? server.arg(F("e")).toInt() : 0xFF;
gpioConfig->ledPinBlue = server.hasArg(F("b")) && !server.arg(F("b")).isEmpty() ? server.arg(F("b")).toInt() : 0xFF;
gpioConfig->ledRgbInverted = server.hasArg(F("n")) && server.arg(F("n")) == F("true");
gpioConfig->apPin = server.hasArg(F("a")) && !server.arg(F("a")).isEmpty() ? server.arg(F("a")).toInt() : 0xFF;
gpioConfig->tempSensorPin = server.hasArg(F("t")) && !server.arg(F("t")).isEmpty() ?server.arg(F("t")).toInt() : 0xFF;
gpioConfig->tempAnalogSensorPin = server.hasArg(F("m")) && !server.arg(F("m")).isEmpty() ?server.arg(F("m")).toInt() : 0xFF;
gpioConfig->vccPin = server.hasArg(F("v")) && !server.arg(F("v")).isEmpty() ? server.arg(F("v")).toInt() : 0xFF;
gpioConfig->vccOffset = server.hasArg(F("o")) && !server.arg(F("o")).isEmpty() ? server.arg(F("o")).toFloat() * 100 : 0;
gpioConfig->vccMultiplier = server.hasArg(F("u")) && !server.arg(F("u")).isEmpty() ? server.arg(F("u")).toFloat() * 1000 : 1000;
gpioConfig->vccBootLimit = server.hasArg(F("c")) && !server.arg(F("c")).isEmpty() ? server.arg(F("c")).toFloat() * 10 : 0;
gpioConfig->vccResistorGnd = server.hasArg(F("d")) && !server.arg(F("d")).isEmpty() ? server.arg(F("d")).toInt() : 0;
gpioConfig->vccResistorVcc = server.hasArg(F("s")) && !server.arg(F("s")).isEmpty() ? server.arg(F("s")).toInt() : 0;
gpioConfig->hanPin = server.hasArg(F("ih")) && !server.arg(F("ih")).isEmpty() ? server.arg(F("ih")).toInt() : 3;
gpioConfig->ledPin = server.hasArg(F("ilp")) && !server.arg(F("ilp")).isEmpty() ? server.arg(F("ilp")).toInt() : 0xFF;
gpioConfig->ledInverted = server.hasArg(F("ili")) && server.arg(F("ili")) == F("true");
gpioConfig->ledPinRed = server.hasArg(F("irr")) && !server.arg(F("irr")).isEmpty() ? server.arg(F("irr")).toInt() : 0xFF;
gpioConfig->ledPinGreen = server.hasArg(F("irg")) && !server.arg(F("irg")).isEmpty() ? server.arg(F("irg")).toInt() : 0xFF;
gpioConfig->ledPinBlue = server.hasArg(F("irb")) && !server.arg(F("irb")).isEmpty() ? server.arg(F("irb")).toInt() : 0xFF;
gpioConfig->ledRgbInverted = server.hasArg(F("iri")) && server.arg(F("iri")) == F("true");
gpioConfig->apPin = server.hasArg(F("ia")) && !server.arg(F("ia")).isEmpty() ? server.arg(F("ia")).toInt() : 0xFF;
gpioConfig->tempSensorPin = server.hasArg(F("itd")) && !server.arg(F("itd")).isEmpty() ?server.arg(F("itd")).toInt() : 0xFF;
gpioConfig->tempAnalogSensorPin = server.hasArg(F("ita")) && !server.arg(F("ita")).isEmpty() ?server.arg(F("ita")).toInt() : 0xFF;
gpioConfig->vccPin = server.hasArg(F("ivp")) && !server.arg(F("ivp")).isEmpty() ? server.arg(F("ivp")).toInt() : 0xFF;
gpioConfig->vccOffset = server.hasArg(F("ivo")) && !server.arg(F("ivo")).isEmpty() ? server.arg(F("ivo")).toFloat() * 100 : 0;
gpioConfig->vccMultiplier = server.hasArg(F("ivm")) && !server.arg(F("ivm")).isEmpty() ? server.arg(F("ivm")).toFloat() * 1000 : 1000;
gpioConfig->vccBootLimit = server.hasArg(F("ivb")) && !server.arg(F("ivb")).isEmpty() ? server.arg(F("ivb")).toFloat() * 10 : 0;
gpioConfig->vccResistorGnd = server.hasArg(F("ivdg")) && !server.arg(F("ivdg")).isEmpty() ? server.arg(F("ivdg")).toInt() : 0;
gpioConfig->vccResistorVcc = server.hasArg(F("ivdv")) && !server.arg(F("ivdv")).isEmpty() ? server.arg(F("ivdv")).toInt() : 0;
config->setGpioConfig(*gpioConfig);
}
if(server.hasArg(F("debugConfig")) && server.arg(F("debugConfig")) == F("true")) {
if(server.hasArg(F("d")) && server.arg(F("d")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received Debug config"));
DebugConfig debug;
config->getDebugConfig(debug);
bool active = debug.serial || debug.telnet;
debug.telnet = server.hasArg(F("debugTelnet")) && server.arg(F("debugTelnet")) == F("true");
debug.serial = server.hasArg(F("debugSerial")) && server.arg(F("debugSerial")) == F("true");
debug.level = server.arg(F("debugLevel")).toInt();
debug.telnet = server.hasArg(F("dt")) && server.arg(F("dt")) == F("true");
debug.serial = server.hasArg(F("ds")) && server.arg(F("ds")) == F("true");
debug.level = server.arg(F("dl")).toInt();
if(debug.telnet || debug.serial) {
if(webConfig.security > 0) {
@ -815,29 +1157,17 @@ void AmsWebServer::handleSave() {
config->setDebugConfig(debug);
}
if(server.hasArg(F("nc")) && server.arg(F("nc")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received NTP config"));
NtpConfig ntp {
server.hasArg(F("n")) && server.arg(F("n")) == F("true"),
server.hasArg(F("nd")) && server.arg(F("nd")) == F("true"),
static_cast<int16_t>(server.arg(F("o")).toInt() / 10),
static_cast<int16_t>(server.arg(F("so")).toInt() / 10)
};
strcpy(ntp.server, server.arg(F("ns")).c_str());
config->setNtpConfig(ntp);
}
if(server.hasArg(F("ec")) && server.arg(F("ec")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received ENTSO-E config"));
if(server.hasArg(F("p")) && server.arg(F("p")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received price API config"));
EntsoeConfig entsoe;
strcpy(entsoe.token, server.arg(F("et")).c_str());
strcpy(entsoe.area, server.arg(F("ea")).c_str());
strcpy(entsoe.currency, server.arg(F("ecu")).c_str());
entsoe.multiplier = server.arg(F("em")).toFloat() * 1000;
strcpy(entsoe.token, server.arg(F("pt")).c_str());
strcpy(entsoe.area, server.arg(F("pr")).c_str());
strcpy(entsoe.currency, server.arg(F("pc")).c_str());
entsoe.multiplier = server.arg(F("pm")).toFloat() * 1000;
config->setEntsoeConfig(entsoe);
}
if(server.hasArg(F("cc")) && server.arg(F("cc")) == F("true")) {
if(server.hasArg(F("t")) && server.arg(F("t")) == F("true")) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received energy accounting config"));
EnergyAccountingConfig eac;
eac.thresholds[0] = server.arg(F("t0")).toInt();
@ -849,7 +1179,7 @@ void AmsWebServer::handleSave() {
eac.thresholds[6] = server.arg(F("t6")).toInt();
eac.thresholds[7] = server.arg(F("t7")).toInt();
eac.thresholds[8] = server.arg(F("t8")).toInt();
eac.hours = server.arg(F("h")).toInt();
eac.hours = server.arg(F("th")).toInt();
config->setEnergyAccountingConfig(eac);
}
@ -857,7 +1187,7 @@ void AmsWebServer::handleSave() {
DynamicJsonDocument doc(128);
if (config->save()) {
doc["success"] = true;
doc["success"] = success;
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(PSTR("Successfully saved."));
if(config->isWifiChanged() || performRestart) {
performRestart = true;
@ -868,11 +1198,13 @@ void AmsWebServer::handleSave() {
}
} else {
doc["success"] = false;
doc["reboot"] = false;
}
serializeJson(doc, buf, BufferSize);
server.send(200, MIME_JSON, buf);
delay(100);
server.handleClient();
delay(250);
if(performRestart || rebootForUpgrade) {
if(ds != NULL) {
@ -888,3 +1220,34 @@ void AmsWebServer::handleSave() {
performRestart = false;
}
}
void AmsWebServer::wifiScanJson() {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /wifiscan.json over http...\n");
DynamicJsonDocument doc(512);
serializeJson(doc, buf, BufferSize);
server.send(200, MIME_JSON, buf);
}
void AmsWebServer::reboot() {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /reboot over http...\n");
DynamicJsonDocument doc(128);
doc["reboot"] = true;
serializeJson(doc, buf, BufferSize);
server.send(200, MIME_JSON, buf);
server.handleClient();
delay(250);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(PSTR("Rebooting"));
delay(1000);
#if defined(ESP8266)
ESP.reset();
#elif defined(ESP32)
ESP.restart();
#endif
performRestart = false;
}