Compare commits

...

11 Commits

Author SHA1 Message Date
Gunnar Skjold
b7d28238ab Extended cache for js and css 2023-01-31 17:16:18 +01:00
Gunnar Skjold
6c9a8b0692 Label fix 2023-01-31 17:04:46 +01:00
Gunnar Skjold
9f3dba3aab Fixed reconnect after setup 2023-01-30 20:58:08 +01:00
Gunnar Skjold
e4e4ad4107 Fixed autodetect 2023-01-30 18:32:17 +01:00
Gunnar Skjold
e8fb9570bb Chasing a reboot bug... 2023-01-30 16:52:12 +01:00
Gunnar Skjold
bc42099962 Fixing some bug reports 2023-01-30 16:07:08 +01:00
Gunnar Skjold
be71cbe609 Some changes after bug reports 2023-01-30 12:02:23 +01:00
Gunnar Skjold
0d6df03c94 Fixed reboot loop, changed temperature sensor behaviour and changed some debugging 2023-01-29 22:45:35 +01:00
Gunnar Skjold
d777040c0a Fixed loading error from previous version energy accounting 2023-01-29 17:04:04 +01:00
Gunnar Skjold
a5636a60f8 Some changes after testing 2023-01-28 20:24:18 +01:00
Gunnar Skjold
6f817b2ed5 Some changes after testing 2023-01-28 20:19:06 +01:00
17 changed files with 235 additions and 139 deletions

View File

@@ -358,8 +358,9 @@ bool AmsConfiguration::pinUsed(uint8_t pin, GpioConfig& config) {
}
bool AmsConfiguration::getGpioConfig(GpioConfig& config) {
if(hasConfig()) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.begin(EEPROM_SIZE);
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
EEPROM.get(CONFIG_GPIO_START, config);
EEPROM.end();
return true;
@@ -505,6 +506,7 @@ bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) {
}
return true;
} else {
clearEntsoe(config);
return false;
}
}
@@ -560,6 +562,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
if(config.hours > 5) config.hours = 5;
return true;
} else {
clearEnergyAccountingConfig(config);
return false;
}
}

View File

@@ -77,7 +77,7 @@ protected:
unsigned long lastList2 = 0;
uint8_t listType = 0, meterType = AmsTypeUnknown;
time_t packageTimestamp = 0;
String listId, meterId, meterModel;
String listId = "", meterId = "", meterModel = "";
time_t meterTimestamp = 0;
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;

View File

@@ -89,8 +89,8 @@ private:
EnergyAccountingConfig *config = NULL;
Timezone *tz = NULL;
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
double use, costHour, costDay;
double produce, incomeHour, incomeDay;
double use = 0, costHour = 0, costDay = 0;
double produce = 0, incomeHour = 0, incomeDay = 0;
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
void calcDayCost();

View File

@@ -185,7 +185,8 @@ double EnergyAccounting::getUseThisHour() {
double EnergyAccounting::getUseToday() {
float ret = 0.0;
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
if(now < BUILD_EPOCH) return 0.0;
if(tz == NULL) return 0.0;
tmElements_t utc, local;
breakTime(tz->toLocal(now), local);
for(int i = 0; i < currentHour; i++) {
@@ -197,7 +198,7 @@ double EnergyAccounting::getUseToday() {
double EnergyAccounting::getUseThisMonth() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
if(now < BUILD_EPOCH) return 0.0;
float ret = 0;
for(int i = 0; i < currentDay; i++) {
ret += ds->getDayImport(i) / 1000.0;
@@ -212,7 +213,7 @@ double EnergyAccounting::getProducedThisHour() {
double EnergyAccounting::getProducedToday() {
float ret = 0.0;
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
if(now < BUILD_EPOCH) return 0.0;
tmElements_t utc;
for(int i = 0; i < currentHour; i++) {
breakTime(now - ((currentHour - i) * 3600), utc);
@@ -223,7 +224,7 @@ double EnergyAccounting::getProducedToday() {
double EnergyAccounting::getProducedThisMonth() {
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
if(now < BUILD_EPOCH) return 0.0;
float ret = 0;
for(int i = 0; i < currentDay; i++) {
ret += ds->getDayExport(i) / 1000.0;
@@ -279,6 +280,8 @@ uint8_t EnergyAccounting::getCurrentThreshold() {
}
float EnergyAccounting::getMonthMax() {
if(config == NULL)
return 0.0;
uint8_t count = 0;
uint32_t maxHour = 0.0;
bool included[5] = { false, false, false, false, false };
@@ -308,6 +311,8 @@ float EnergyAccounting::getMonthMax() {
}
EnergyAccountingPeak EnergyAccounting::getPeak(uint8_t num) {
if(config == NULL)
return EnergyAccountingPeak({0,0});
if(num < 1 || num > 5) return EnergyAccountingPeak({0,0});
uint8_t count = 0;
@@ -362,7 +367,9 @@ bool EnergyAccounting::load() {
} else if(buf[0] == 4) {
EnergyAccountingData4* data = (EnergyAccountingData4*) buf;
this->data = { 5, data->month,
(uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100),
data->costYesterday,
data->costThisMonth,
data->costLastMonth,
0,0,0, // Income from production
data->peaks[0].day, data->peaks[0].value,
data->peaks[1].day, data->peaks[1].value,

View File

@@ -64,6 +64,10 @@
{#if sysinfo.upgrading}
<Mask active=true message="Device is upgrading, please wait"/>
{:else if sysinfo.booting}
<Mask active=true message="Device is booting, please wait"/>
{#if sysinfo.trying}
<Mask active=true message="Device is booting, please wait. Trying to reach it on {sysinfo.trying}"/>
{:else}
<Mask active=true message="Device is booting, please wait"/>
{/if}
{/if}
</div>

View File

@@ -1,54 +1,79 @@
<script>
export let data;
export let currency;
let hasExport = data && (data.om || data.e > 0);
export let hasExport;
</script>
<div class="mx-2 text-sm">
<strong>Real time calculation</strong>
{#if data && data.h !== undefined}
<div class="flex">
<div>Hour</div>
<div class="flex-auto text-right">{data.h.u ? data.h.u.toFixed(2) : '-'} kWh {#if currency && (hasExport)}/ {data.h.c ? data.h.c.toFixed(2) : '-'} {currency}{/if}</div>
</div>
<div class="flex">
<div>Day</div>
<div class="flex-auto text-right">{data.d.u ? data.d.u.toFixed(1) : '-'} kWh {#if currency && (hasExport)}/ {data.d.c ? data.d.c.toFixed(2) : '-'} {currency}{/if}</div>
</div>
<div class="flex">
<div>Month</div>
<div class="flex-auto text-right">{data.m.u ? data.m.u.toFixed(0) : '-'} kWh {#if currency && (hasExport)}/ {data.m.c ? data.m.c.toFixed(2) : '-'} {currency}{/if}</div>
</div>
<div class="mt-4">
{#if hasExport}
<div class="flex">
<div>Hour</div>
<div class="flex-auto text-right">{data.h.p ? data.h.p.toFixed(2) : '-'} kWh {#if currency}/ {data.h.i ? data.h.i.toFixed(2) : '-'} {currency}{/if}</div>
</div>
<div class="flex">
<div>Day</div>
<div class="flex-auto text-right">{data.d.p ? data.d.p.toFixed(1) : '-'} kWh {#if currency}/ {data.d.i ? data.d.i.toFixed(2) : '-'} {currency}{/if}</div>
</div>
<div class="flex">
<div>Month</div>
<div class="flex-auto text-right">{data.m.p ? data.m.p.toFixed(0) : '-'} kWh {#if currency}/ {data.m.i ? data.m.i.toFixed(2) : '-'} {currency}{/if}</div>
</div>
<br/><br/>
{#if data}
{#if hasExport && currency}
<strong>Import</strong>
<div class="grid grid-cols-3 mb-3">
<div>Hour</div>
<div class="text-right">{data.h.u ? data.h.u.toFixed(2) : '-'} kWh</div>
<div class="text-right">{data.h.c ? data.h.c.toFixed(2) : '-'} {currency}</div>
<div>Day</div>
<div class="text-right">{data.d.u ? data.d.u.toFixed(1) : '-'} kWh</div>
<div class="text-right">{data.d.c ? data.d.c.toFixed(1) : '-'} {currency}</div>
<div>Month</div>
<div class="text-right">{data.m.u ? data.m.u.toFixed(0) : '-'} kWh</div>
<div class="text-right">{data.m.c ? data.m.c.toFixed(0) : '-'} {currency}</div>
</div>
<strong>Export</strong>
<div class="grid grid-cols-3">
<div>Hour</div>
<div class="text-right">{data.h.p ? data.h.p.toFixed(2) : '-'} kWh</div>
<div class="text-right">{data.h.i ? data.h.i.toFixed(2) : '-'} {currency}</div>
<div>Day</div>
<div class="text-right">{data.d.p ? data.d.p.toFixed(1) : '-'} kWh</div>
<div class="text-right">{data.d.i ? data.d.i.toFixed(1) : '-'} {currency}</div>
<div>Month</div>
<div class="text-right">{data.m.p ? data.m.p.toFixed(0) : '-'} kWh</div>
<div class="text-right">{data.m.i ? data.m.i.toFixed(0) : '-'} {currency}</div>
</div>
{:else if hasExport}
<strong>Import</strong>
<div class="grid grid-cols-2 mb-3">
<div>Hour</div>
<div class="text-right">{data.h.u ? data.h.u.toFixed(2) : '-'} kWh</div>
<div>Day</div>
<div class="text-right">{data.d.u ? data.d.u.toFixed(1) : '-'} kWh</div>
<div>Month</div>
<div class="text-right">{data.m.u ? data.m.u.toFixed(0) : '-'} kWh</div>
</div>
<strong>Export</strong>
<div class="grid grid-cols-2">
<div>Hour</div>
<div class="text-right">{data.h.p ? data.h.p.toFixed(2) : '-'} kWh</div>
<div>Day</div>
<div class="text-right">{data.d.p ? data.d.p.toFixed(1) : '-'} kWh</div>
<div>Month</div>
<div class="text-right">{data.m.p ? data.m.p.toFixed(0) : '-'} kWh</div>
</div>
{:else}
<div class="flex">
<div>Hour</div>
<div class="flex-auto text-right">{data.h.c ? data.h.c.toFixed(2) : '-'} {currency}</div>
</div>
<div class="flex">
<div>Day</div>
<div class="flex-auto text-right">{data.d.c ? data.d.c.toFixed(2) : '-'} {currency}</div>
</div>
<div class="flex">
<div>Month</div>
<div class="flex-auto text-right">{data.m.c ? data.m.c.toFixed(2) : '-'} {currency}</div>
</div>
<strong>Consumption</strong>
<div class="grid grid-cols-2 mb-3">
<div>Hour</div>
<div class="text-right">{data.h.u ? data.h.u.toFixed(2) : '-'} kWh</div>
<div>Day</div>
<div class="text-right">{data.d.u ? data.d.u.toFixed(1) : '-'} kWh</div>
<div>Month</div>
<div class="text-right">{data.m.u ? data.m.u.toFixed(0) : '-'} kWh</div>
</div>
{#if currency}
<strong>Cost</strong>
<div class="grid grid-cols-2">
<div>Hour</div>
<div class="text-right">{data.h.c ? data.h.c.toFixed(2) : '-'} {currency}</div>
<div>Day</div>
<div class="text-right">{data.d.c ? data.d.c.toFixed(1) : '-'} {currency}</div>
<div>Month</div>
<div class="text-right">{data.m.c ? data.m.c.toFixed(0) : '-'} {currency}</div>
</div>
{/if}
{/if}
</div>
{/if}
</div>

View File

@@ -7,6 +7,7 @@
import HelpIcon from './HelpIcon.svelte';
import CountrySelectOptions from './CountrySelectOptions.svelte';
import { Link, navigate } from 'svelte-navigator';
import SubnetOptions from './SubnetOptions.svelte';
export let sysinfo = {}
@@ -67,8 +68,10 @@
getConfiguration();
let isFactoryReset = false;
let isFactoryResetComplete = false;
async function factoryReset() {
if(confirm("Are you sure you want to factory reset the device?")) {
isFactoryReset = true;
const data = new URLSearchParams();
data.append("perform", "true");
const response = await fetch('/reset', {
@@ -76,7 +79,8 @@
body: data
});
let res = (await response.json());
isFactoryReset = res.success;
isFactoryReset = false;
isFactoryResetComplete = res.success;
}
}
@@ -362,11 +366,9 @@
<option value="dhcp">DHCP</option>
<option value="static">Static</option>
</select>
<input name="ni" bind:value={configuration.n.i} type="text" class="in-m w-full" disabled={configuration.n.m == 'dhcp'}/>
<select name="ns" bind:value={configuration.n.s} class="in-l" 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>
<input name="ni" bind:value={configuration.n.i} type="text" class="in-m w-full" disabled={configuration.n.m == 'dhcp'} required={configuration.n.m == 'static'}/>
<select name="ns" bind:value={configuration.n.s} class="in-l" disabled={configuration.n.m == 'dhcp'} required={configuration.n.m == 'static'}>
<SubnetOptions/>
</select>
</div>
</div>
@@ -765,4 +767,5 @@
<Mask active={loading} message="Loading configuration"/>
<Mask active={saving} message="Saving configuration"/>
<Mask active={isFactoryReset} message="Device have been factory reset and switched to AP mode"/>
<Mask active={isFactoryReset} message="Performing factory reset"/>
<Mask active={isFactoryResetComplete} message="Device have been factory reset and switched to AP mode"/>

View File

@@ -1,6 +1,6 @@
<script>
import { pricesStore, dayPlotStore, monthPlotStore, temperaturesStore } from './DataStores.js';
import { metertype, uiVisibility } from './Helpers.js';
import { ampcol, exportcol, metertype, uiVisibility } from './Helpers.js';
import PowerGauge from './PowerGauge.svelte';
import VoltPlot from './VoltPlot.svelte';
import AmpPlot from './AmpPlot.svelte';
@@ -32,23 +32,23 @@
});
</script>
<div class="grid xl:grid-cols-6 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
<div class="grid 2xl:grid-cols-6 xl:grid-cols-5 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
{#if uiVisibility(sysinfo.ui.i, data.i)}
<div class="cnt">
<div class="grid grid-cols-2">
<div class="col-span-2">
<PowerGauge val={data.i ? data.i : 0} max={data.im} unit="W" label="Import" sub={data.p} subunit={prices.currency}/>
<div class="cnt">
<div class="grid grid-cols-2">
<div class="col-span-2">
<PowerGauge val={data.i ? data.i : 0} max={data.im ? data.im : 15000} unit="W" label="Import" sub={data.p} subunit={prices.currency} colorFn={ampcol}/>
</div>
<div>{data.mt ? metertype(data.mt) : '-'}</div>
<div class="text-right">{data.ic ? data.ic.toFixed(1) : '-'} kWh</div>
</div>
<div>{data.mt ? metertype(data.mt) : '-'}</div>
<div class="text-right">{data.ic ? data.ic.toFixed(1) : '-'} kWh</div>
</div>
</div>
{/if}
{#if uiVisibility(sysinfo.ui.e, data.om || data.e > 0)}
<div class="cnt">
<div class="grid grid-cols-2">
<div class="col-span-2">
<PowerGauge val={data.e ? data.e : 0} max={data.om ? data.om : 10000} unit="W" label="Export"/>
<PowerGauge val={data.e ? data.e : 0} max={data.om ? data.om * 1000 : 10000} unit="W" label="Export" colorFn={exportcol}/>
</div>
<div></div>
<div class="text-right">{data.ec ? data.ec.toFixed(1) : '-'} kWh</div>
@@ -56,48 +56,48 @@
</div>
{/if}
{#if uiVisibility(sysinfo.ui.v, data.u1 > 100 || data.u2 > 100 || data.u3 > 100)}
<div class="cnt">
<VoltPlot u1={data.u1} u2={data.u2} u3={data.u3} ds={data.ds}/>
</div>
<div class="cnt">
<VoltPlot u1={data.u1} u2={data.u2} u3={data.u3} ds={data.ds}/>
</div>
{/if}
{#if uiVisibility(sysinfo.ui.a, data.i1 > 0.01 || data.i2 > 0.01 || data.i3 > 0.01)}
<div class="cnt">
<AmpPlot u1={data.u1} u2={data.u2} u3={data.u3} i1={data.i1} i2={data.i2} i3={data.i3} max={data.mf ? data.mf : 32}/>
</div>
<div class="cnt">
<AmpPlot u1={data.u1} u2={data.u2} u3={data.u3} i1={data.i1} i2={data.i2} i3={data.i3} max={data.mf ? data.mf : 32}/>
</div>
{/if}
{#if uiVisibility(sysinfo.ui.r, data.ri > 0 || data.re > 0 || data.ric > 0 || data.rec > 0)}
<div class="cnt">
<ReactiveData importInstant={data.ri} exportInstant={data.re} importTotal={data.ric} exportTotal={data.rec}/>
</div>
<div class="cnt">
<ReactiveData importInstant={data.ri} exportInstant={data.re} importTotal={data.ric} exportTotal={data.rec}/>
</div>
{/if}
{#if uiVisibility(sysinfo.ui.c, data.ea)}
<div class="cnt">
<AccountingData data={data.ea} currency={prices.currency}/>
</div>
<div class="cnt">
<AccountingData data={data.ea} currency={prices.currency} hasExport={data.om > 0 || data.e > 0}/>
</div>
{/if}
{#if data && data.pr && (data.pr.startsWith("10YNO") || data.pr == '10Y1001A1001A48H')}
<div class="cnt h-64">
<TariffPeakChart />
</div>
{#if uiVisibility(sysinfo.ui.t, data.pr && (data.pr.startsWith("10YNO") || data.pr == '10Y1001A1001A48H'))}
<div class="cnt h-64">
<TariffPeakChart />
</div>
{/if}
{#if uiVisibility(sysinfo.ui.p, (typeof data.p == "number") && !Number.isNaN(data.p))}
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<div class="cnt 2xl:col-span-6 xl:col-span-5 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<PricePlot json={prices}/>
</div>
{/if}
{#if uiVisibility(sysinfo.ui.d, dayPlot)}
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<DayPlot json={dayPlot} />
</div>
<div class="cnt 2xl:col-span-6 xl:col-span-5 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<DayPlot json={dayPlot} />
</div>
{/if}
{#if uiVisibility(sysinfo.ui.m, monthPlot)}
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<MonthPlot json={monthPlot} />
</div>
<div class="cnt 2xl:col-span-6 xl:col-span-5 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<MonthPlot json={monthPlot} />
</div>
{/if}
{#if uiVisibility(sysinfo.ui.s, data.t && data.t != -127 && temperatures.c > 1)}
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<TemperaturePlot json={temperatures} />
</div>
<div class="cnt 2xl:col-span-6 xl:col-span-5 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
<TemperaturePlot json={temperatures} />
</div>
{/if}
</div>

View File

@@ -25,7 +25,8 @@ let sysinfo = {
booting: false,
upgrading: false,
ui: {},
security: 0
security: 0,
trying: null
};
export const sysinfoStore = writable(sysinfo);
export async function getSysinfo() {

View File

@@ -15,6 +15,13 @@ export function ampcol(pct) {
else return '#32d900';
};
export function exportcol(pct) {
if(pct > 75) return '#32d900';
else if(pct > 50) return '#77d900';
else if(pct > 25) return '#94d900';
else return '#dcd800';
};
export function metertype(mt) {
switch(mt) {
case 1:

View File

@@ -1,6 +1,5 @@
<script>
import PowerGaugeSvg from './PowerGaugeSvg.svelte';
import { ampcol } from './Helpers.js';
export let val;
export let max;
@@ -8,10 +7,16 @@
export let label;
export let sub = "";
export let subunit = "";
export let colorFn;
let pct = 0;
$: {
pct = (Math.min(val,max)/max) * 100
}
</script>
<div class="pl-root">
<PowerGaugeSvg pct={val/max * 100} color={ampcol(val/max * 100)}/>
<PowerGaugeSvg pct={pct} color={colorFn(pct)}/>
<span class="pl-ov">
<span class="pl-lab">{label}</span>
<br/>

View File

@@ -1,6 +1,7 @@
<script>
import { sysinfoStore } from './DataStores.js';
import Mask from './Mask.svelte'
import SubnetOptions from './SubnetOptions.svelte';
export let sysinfo = {}
@@ -12,7 +13,15 @@
var url = "";
tries++;
var retry = function() {
setTimeout(scanForDevice, 1000);
};
if(sysinfo.net.ip && tries%3 == 0) {
if(sysinfo.net.ip == '0.0.0.0') {
retry();
return;
};
url = "http://" + sysinfo.net.ip;
} else if(sysinfo.hostname && tries%3 == 1) {
url = "http://" + sysinfo.hostname;
@@ -22,10 +31,10 @@
url = "";
}
if(console) console.log("Trying url " + url);
var retry = function() {
setTimeout(scanForDevice, 1000);
};
sysinfoStore.update(s => {
s.trying = url;
return s;
});
var xhr = new XMLHttpRequest();
xhr.timeout = 5000;
@@ -42,12 +51,10 @@
async function handleSubmit(e) {
loadingOrSaving = true;
const formData = new FormData(e.target);
let hostname = sysinfo.hostname;
const data = new URLSearchParams();
for (let field of formData) {
const [key, value] = field;
data.append(key, value)
if(key == 'sh') hostname = value;
}
const response = await fetch('/save', {
@@ -58,9 +65,15 @@
loadingOrSaving = false;
sysinfoStore.update(s => {
s.hostname = hostname;
s.hostname = formData.get('sh');
s.usrcfg = res.success;
s.booting = res.reboot;
if(staticIp) {
s.net.ip = formData.get('si');
s.net.mask = formData.get('su');
s.net.gw = formData.get('sg');
s.net.dns1 = formData.get('sd');
}
setTimeout(scanForDevice, 5000);
return s;
});
@@ -75,14 +88,14 @@
<strong class="text-sm">Setup</strong>
<div class="my-3">
SSID<br/>
<input name="ss" type="text" class="in-s"/>
<input name="ss" type="text" class="in-s" required/>
</div>
<div class="my-3">
PSK<br/>
<input name="sp" type="password" class="in-s" autocomplete="off"/>
</div>
<div>
Hostname:
Hostname
<input name="sh" bind:value={sysinfo.hostname} type="text" class="in-s" maxlength="32" pattern="[a-z0-9_-]+" placeholder="Optional, ex.: ams-reader" autocomplete="off"/>
</div>
<div class="my-3">
@@ -92,9 +105,7 @@
<div class="flex">
<input name="si" type="text" class="in-f w-full" required={staticIp}/>
<select name="su" class="in-l" 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>
<SubnetOptions/>
</select>
</div>
{/if}

View File

@@ -0,0 +1,16 @@
<optgroup label="Most common is /24 (255.255.255.0)">
<option value="255.255.255.0">/24</option>
</optgroup>
<optgroup label="Smaller subnets">
<option value="255.255.255.128">/25</option>
<option value="255.255.255.192">/26</option>
<option value="255.255.255.224">/27</option>
<option value="255.255.255.240">/28</option>
<option value="255.255.255.248">/29</option>
</optgroup>
<optgroup label="Larger subnets">
<option value="255.255.254.0">/23</option>
<option value="255.255.252.0">/22</option>
<option value="255.255.0.0">/16</option>
</optgroup>

View File

@@ -31,7 +31,9 @@
<option value={13}>GPIO13</option>
<option value={14}>GPIO14</option>
<option value={15}>GPIO15</option>
{#if chip == 'esp32s2'}
<option value={16}>GPIO16</option>
{/if}
{#if chip.startsWith('esp32')}
<option value={17}>GPIO17</option>
{#if chip != 'esp32s2'}

View File

@@ -6,6 +6,7 @@ static const char HEADER_LOCATION[] PROGMEM = "Location";
static const char CACHE_CONTROL_NO_CACHE[] PROGMEM = "no-cache, no-store, must-revalidate";
static const char CACHE_1HR[] PROGMEM = "public, max-age=3600";
static const char CACHE_1MO[] PROGMEM = "public, max-age=2592000";
static const char PRAGMA_NO_CACHE[] PROGMEM = "no-cache";
static const char EXPIRES_OFF[] PROGMEM = "-1";
static const char AUTHENTICATE_BASIC[] PROGMEM = "Basic realm=\"Secure Area\"";

View File

@@ -240,12 +240,22 @@ void AmsWebServer::sysinfoJson() {
UiConfig ui;
config->getUiConfig(ui);
String meterModel = meterState->getMeterModel();
if(!meterModel.isEmpty())
meterModel.replace("\\", "\\\\");
String meterId = meterState->getMeterId();
if(!meterId.isEmpty())
meterId.replace("\\", "\\\\");
int size = snprintf_P(buf, BufferSize, SYSINFO_JSON,
VERSION,
#if defined(CONFIG_IDF_TARGET_ESP32S2)
"esp32s2",
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
"esp32c3",
#elif defined(CONFIG_FREERTOS_UNICORE)
"esp32solo",
#elif defined(ESP32)
"esp32",
#elif defined(ESP8266)
@@ -272,8 +282,8 @@ void AmsWebServer::sysinfoJson() {
dns2.toString().c_str(),
#endif
meterState->getMeterType(),
meterState->getMeterModel().c_str(),
meterState->getMeterId().c_str(),
meterModel.c_str(),
meterId.c_str(),
ui.showImport,
ui.showExport,
ui.showVoltage,
@@ -348,7 +358,6 @@ void AmsWebServer::dataJson() {
}
#endif
uint8_t hanStatus;
if(meterState->getLastError() != 0) {
hanStatus = 3;
@@ -397,7 +406,7 @@ void AmsWebServer::dataJson() {
snprintf_P(buf, BufferSize, DATA_JSON,
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
meterConfig->productionCapacity,
meterConfig->mainFuse == 0 ? 32 : meterConfig->mainFuse,
meterConfig->mainFuse == 0 ? 40 : meterConfig->mainFuse,
meterState->getActiveImportPower(),
meterState->getActiveExportPower(),
meterState->getReactiveImportPower(),
@@ -444,7 +453,7 @@ void AmsWebServer::dataJson() {
ea->getCostThisMonth(),
ea->getProducedThisMonth(),
ea->getIncomeThisMonth(),
priceRegion.c_str(),
eapi == NULL ? "" : priceRegion.c_str(),
meterState->getLastError(),
eapi == NULL ? 0 : eapi->getLastError(),
(uint32_t) now,
@@ -724,7 +733,7 @@ void AmsWebServer::indexCss() {
if(!checkSecurity(2))
return;
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1HR);
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1MO);
server.setContentLength(INDEX_CSS_LEN);
server.send_P(200, MIME_CSS, INDEX_CSS);
}
@@ -735,7 +744,7 @@ void AmsWebServer::indexJs() {
if(!checkSecurity(2))
return;
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1HR);
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1MO);
server.setContentLength(INDEX_JS_LEN);
server.send_P(200, MIME_JS, INDEX_JS);
}
@@ -1037,16 +1046,18 @@ void AmsWebServer::handleSave() {
success = false;
}
#endif
config->setGpioConfig(*gpioConfig);
if(success) {
config->setGpioConfig(*gpioConfig);
SystemConfig sys;
config->getSystemConfig(sys);
sys.boardType = success ? boardType : 0xFF;
sys.vendorConfigured = success;
config->setSystemConfig(sys);
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")) {
if(server.hasArg(F("s")) && server.arg(F("s")) == F("true") && server.hasArg(F("ss")) && !server.arg(F("ss")).isEmpty()) {
SystemConfig sys;
config->getSystemConfig(sys);

View File

@@ -577,13 +577,14 @@ void loop() {
if(readHanPort() || now - meterState.getLastUpdateMillis() > 30000) {
if(now - lastTemperatureRead > 15000) {
unsigned long start = millis();
hw.updateTemperatures();
lastTemperatureRead = now;
if(hw.updateTemperatures()) {
lastTemperatureRead = now;
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
mqttHandler->publishTemperatures(&config, &hw);
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
mqttHandler->publishTemperatures(&config, &hw);
}
debugD("Used %ld ms to update temperature", millis()-start);
}
debugD("Used %ld ms to update temperature", millis()-start);
}
if(now - lastSysupdate > 60000) {
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
@@ -597,8 +598,8 @@ void loop() {
meterState.setLastError(98);
}
try {
if(meterState.getLastError() > 0) {
if(now - meterAutodetectLastChange > 15000 && (meterConfig.baud == 0 || meterConfig.parity == 0)) {
if(meterState.getListType() == 0) {
if(now - meterAutodetectLastChange > 20000 && (meterConfig.baud == 0 || meterConfig.parity == 0)) {
meterAutodetect = true;
meterAutoIndex++; // Default is to try the first one in setup()
debugI("Meter serial autodetect, swapping to: %d, %d, %s", bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
@@ -607,7 +608,6 @@ void loop() {
meterAutodetectLastChange = now;
}
} else if(meterAutodetect) {
meterAutoIndex--; // Last one worked, so lets use that
debugI("Meter serial autodetected, saving: %d, %d, %s", bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
meterAutodetect = false;
meterConfig.baud = bauds[meterAutoIndex];
@@ -862,9 +862,9 @@ bool readHanPort() {
pos = unwrapData((uint8_t *) hanBuffer, ctx);
if(ctx.type > 0 && pos >= 0) {
if(ctx.type == DATA_TAG_DLMS) {
debugV("Received valid DLMS at %d", pos);
debugD("Received valid DLMS at %d", pos);
} else if(ctx.type == DATA_TAG_DSMR) {
debugV("Received valid DSMR at %d", pos);
debugD("Received valid DSMR at %d", pos);
} else {
// TODO: Move this so that payload is sent to MQTT
debugE("Unknown tag %02X at pos %d", ctx.type, pos);
@@ -879,7 +879,7 @@ bool readHanPort() {
meterState.setLastError(pos);
debugV("Unknown data payload:");
len = len + hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len);
debugPrint(hanBuffer, 0, len);
if(Debug.isActive(RemoteDebug::VERBOSE)) debugPrint(hanBuffer, 0, len);
len = 0;
return false;
}
@@ -962,7 +962,7 @@ bool readHanPort() {
bool saveData = false;
if(!ds.isHappy() && now > BUILD_EPOCH) {
debugV("Its time to update data storage");
debugD("Its time to update data storage");
tmElements_t tm;
breakTime(now, tm);
if(tm.Minute == 0) {