mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 04:57:28 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7d28238ab | ||
|
|
6c9a8b0692 | ||
|
|
9f3dba3aab | ||
|
|
e4e4ad4107 | ||
|
|
e8fb9570bb | ||
|
|
bc42099962 | ||
|
|
be71cbe609 | ||
|
|
0d6df03c94 | ||
|
|
d777040c0a | ||
|
|
a5636a60f8 | ||
|
|
6f817b2ed5 |
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
@@ -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() {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
16
lib/SvelteUi/app/src/lib/SubnetOptions.svelte
Normal file
16
lib/SvelteUi/app/src/lib/SubnetOptions.svelte
Normal 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>
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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\"";
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user