mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-23 11:08:25 +00:00
More v2.2
This commit is contained in:
parent
ab7128c53a
commit
8e54f23367
@ -34,9 +34,19 @@
|
||||
<StatusPage sysinfo={sysinfo} data={data}/>
|
||||
</Route>
|
||||
</Router>
|
||||
{#if sysinfo.vndcfg === false}
|
||||
{#if sysinfo.upgrading}
|
||||
<Mask active=true message="Device is upgrading, please wait"/>
|
||||
{:else if sysinfo.vndcfg === false}
|
||||
{#if sysinfo.booting}
|
||||
<Mask active=true message="Device is booting, please wait"/>
|
||||
{:else}
|
||||
<VendorModal sysinfo={sysinfo}/>
|
||||
{/if}
|
||||
{:else if sysinfo.usrcfg === false}
|
||||
{#if sysinfo.booting}
|
||||
<Mask active=true message="Device is booting, please wait"/>
|
||||
{:else}
|
||||
<SetupModal sysinfo={sysinfo}/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
export let chip;
|
||||
</script>
|
||||
|
||||
<option value=""></option>
|
||||
<option value={-1}></option>
|
||||
{#if chip == 'esp8266'}
|
||||
<optgroup label="amsleser.no">
|
||||
<option value={7}>{boardtype(chip, 7)}</option>
|
||||
|
||||
@ -518,6 +518,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if configuration.i.v.p > 0 || sysinfo.chip == 'esp8266'}
|
||||
<input type="hidden" name="iv" value="true"/>
|
||||
<div class="my-1 flex flex-wrap">
|
||||
<div>
|
||||
Vcc offset<br/>
|
||||
|
||||
@ -7,7 +7,8 @@ let sysinfo = {
|
||||
vndcfg: null,
|
||||
usrcfg: null,
|
||||
fwconsent: null,
|
||||
booting: false
|
||||
booting: true,
|
||||
upgrading: false
|
||||
};
|
||||
export const sysinfoStore = writable(sysinfo);
|
||||
|
||||
@ -17,20 +18,38 @@ export async function getSysinfo() {
|
||||
sysinfoStore.set(sysinfo);
|
||||
};
|
||||
|
||||
let lastTemp = -127;
|
||||
let data = {};
|
||||
export const dataStore = readable(data, (set) => {
|
||||
async function getData(){
|
||||
const response = await fetch("/data.json");
|
||||
data = (await response.json())
|
||||
set(data);
|
||||
if(sysinfo.booting) {
|
||||
getSysinfo();
|
||||
}
|
||||
let timeout;
|
||||
async function getData() {
|
||||
fetch("/data.json")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
set(data);
|
||||
if(lastTemp != data.t) {
|
||||
lastTemp = data.t;
|
||||
getTemperatures();
|
||||
}
|
||||
if(sysinfo.upgrading) {
|
||||
window.location.reload();
|
||||
} else if(sysinfo.booting) {
|
||||
getSysinfo();
|
||||
}
|
||||
timeout = setTimeout(getData, 5000);
|
||||
})
|
||||
.catch((err) => {
|
||||
data.em = 3;
|
||||
data.hm = 0;
|
||||
data.wm = 0;
|
||||
data.mm = 0;
|
||||
set(data);
|
||||
timeout = setTimeout(getData, 15000);
|
||||
});
|
||||
}
|
||||
const interval = setInterval(getData, 5000);
|
||||
getData();
|
||||
return function stop() {
|
||||
clearInterval(interval);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
|
||||
@ -40,13 +59,12 @@ export const pricesStore = readable(prices, (set) => {
|
||||
const response = await fetch("/energyprice.json");
|
||||
prices = (await response.json())
|
||||
set(prices);
|
||||
|
||||
let date = new Date();
|
||||
setTimeout(getPrices, (61-date.getMinutes())*60000)
|
||||
}
|
||||
const date = new Date();
|
||||
const timeout = setTimeout(getPrices, (61-date.getMinutes())*60000)
|
||||
getPrices();
|
||||
return function stop() {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let dayPlot = {};
|
||||
@ -55,13 +73,12 @@ export const dayPlotStore = readable(dayPlot, (set) => {
|
||||
const response = await fetch("/dayplot.json");
|
||||
dayPlot = (await response.json())
|
||||
set(dayPlot);
|
||||
|
||||
let date = new Date();
|
||||
setTimeout(getDayPlot, (61-date.getMinutes())*60000)
|
||||
}
|
||||
const date = new Date();
|
||||
const timeout = setTimeout(getDayPlot, (61-date.getMinutes())*60000)
|
||||
getDayPlot();
|
||||
return function stop() {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let monthPlot = {};
|
||||
@ -70,25 +87,31 @@ export const monthPlotStore = readable(monthPlot, (set) => {
|
||||
const response = await fetch("/monthplot.json");
|
||||
monthPlot = (await response.json())
|
||||
set(monthPlot);
|
||||
|
||||
let date = new Date();
|
||||
setTimeout(getmonthPlot, (24-date.getHours())*3600000)
|
||||
}
|
||||
const date = new Date();
|
||||
const timeout = setTimeout(getmonthPlot, (24-date.getHours())*3600000)
|
||||
getmonthPlot();
|
||||
return function stop() {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let temperatures = {};
|
||||
export const temperaturesStore = readable(temperatures, (set) => {
|
||||
async function getTemperatures(){
|
||||
const response = await fetch("/temperature.json");
|
||||
temperatures = (await response.json())
|
||||
set(temperatures);
|
||||
}
|
||||
const interval = setInterval(getTemperatures, 60000);
|
||||
export async function getTemperatures() {
|
||||
const response = await fetch("/temperature.json");
|
||||
temperatures = (await response.json())
|
||||
temperaturesStore.set(temperatures);
|
||||
}
|
||||
|
||||
export const temperaturesStore = writable(temperatures, (set) => {
|
||||
getTemperatures();
|
||||
return function stop() {
|
||||
clearTimeout(interval);
|
||||
}
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let releases = [];
|
||||
export const gitHubReleaseStore = writable(releases);
|
||||
|
||||
export async function getGitHubReleases() {
|
||||
const response = await fetch("https://api.github.com/repos/gskjold/AmsToMqttBridge/releases");
|
||||
releases = (await response.json())
|
||||
gitHubReleaseStore.set(releases);
|
||||
};
|
||||
|
||||
6
lib/SvelteUi/app/src/lib/DownloadIcon.svelte
Normal file
6
lib/SvelteUi/app/src/lib/DownloadIcon.svelte
Normal 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 8.25H7.5a2.25 2.25 0 00-2.25 2.25v9a2.25 2.25 0 002.25 2.25h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25H15M9 12l3 3m0 0l3-3m-3 3V2.25" />
|
||||
</svg>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
<script>
|
||||
import { Link } from "svelte-navigator";
|
||||
import { sysinfoStore } from './DataStores.js';
|
||||
import { sysinfoStore, getGitHubReleases, gitHubReleaseStore } from './DataStores.js';
|
||||
import { upgrade, getNextVersion } from './UpgradeHelper';
|
||||
import { boardtype } from './Helpers.js';
|
||||
import GitHubLogo from './../assets/github.svg';
|
||||
import Uptime from "./Uptime.svelte";
|
||||
import Badge from './Badge.svelte';
|
||||
@ -9,10 +11,13 @@
|
||||
import InfoIcon from "./InfoIcon.svelte";
|
||||
import HelpIcon from "./HelpIcon.svelte";
|
||||
import ReloadIcon from "./ReloadIcon.svelte";
|
||||
import DownloadIcon from "./DownloadIcon.svelte";
|
||||
|
||||
export let data = {}
|
||||
export let sysinfo = {}
|
||||
|
||||
let nextVersion = {};
|
||||
|
||||
async function reboot() {
|
||||
const response = await fetch('/reboot', {
|
||||
method: 'POST'
|
||||
@ -29,6 +34,23 @@
|
||||
reboot();
|
||||
}
|
||||
}
|
||||
|
||||
function askUpgrade() {
|
||||
if(confirm('Do you want to upgrade this device to ' + nextVersion.tag_name + '?')) {
|
||||
if((sysinfo.board != 2 && sysinfo.board != 4 && sysinfo.board != 7) || confirm('WARNING: ' + boardtype(sysinfo.chip, sysinfo.board) + ' must be connected to an external power supply during firmware upgrade. Failure to do so may cause power-down during upload resulting in non-functioning unit.')) {
|
||||
sysinfoStore.update(s => {
|
||||
s.upgrading = true;
|
||||
return s;
|
||||
});
|
||||
upgrade(nextVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gitHubReleaseStore.subscribe(releases => {
|
||||
nextVersion = getNextVersion(sysinfo.version, releases);
|
||||
});
|
||||
getGitHubReleases();
|
||||
</script>
|
||||
|
||||
<nav class="bg-violet-600 p-1 rounded-md mx-2">
|
||||
@ -38,14 +60,16 @@
|
||||
</div>
|
||||
<div class="flex-none my-auto p-2 flex space-x-4">
|
||||
<div class="flex-none my-auto"><Uptime epoch={data.u}/></div>
|
||||
<div class="flex-none my-auto">{ data.t ? data.t.toFixed(1) : '-' }°C</div>
|
||||
{#if data.t > -50}
|
||||
<div class="flex-none my-auto">{ data.t > -50 ? data.t.toFixed(1) : '-' }°C</div>
|
||||
{/if}
|
||||
<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={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'}/>
|
||||
<Badge title="ESP" text={sysinfo.booting ? 'Booting' : data.v > 1.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.hm === 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.wm === 3 ? 'red' : 'gray'}/>
|
||||
<Badge title="MQTT" text="MQTT" color={sysinfo.booting ? 'gray' : data.mm === 1 ? 'green' : data.mm === 2 ? 'yellow' : data.mm === 3 ? 'red' : 'gray'}/>
|
||||
</div>
|
||||
<div class="flex-auto p-2 flex flex-row-reverse">
|
||||
<div class="flex-none">
|
||||
@ -57,15 +81,20 @@
|
||||
<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">
|
||||
<div class="flex-none px-1 mt-1" title="Reboot">
|
||||
<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">
|
||||
<div class="flex-none px-1 mt-1" title="Documentation">
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki" target='_blank' rel="noreferrer"><HelpIcon/></a>
|
||||
</div>
|
||||
{#if sysinfo.fwconsent && nextVersion}
|
||||
<div class="flex-none px-4 mt-1 text-yellow-500" title="New version: {nextVersion.tag_name}">
|
||||
<button on:click={askUpgrade} class="flex"><DownloadIcon/> <span class="mt-1">{nextVersion.tag_name}</span></button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -31,7 +31,7 @@
|
||||
}
|
||||
.plot-value {
|
||||
font-size: 1.7rem;
|
||||
cursor: pointer;
|
||||
//cursor: pointer;
|
||||
}
|
||||
.plot-unit {
|
||||
font-size: 1.0rem;
|
||||
|
||||
@ -5,9 +5,40 @@
|
||||
export let sysinfo = {}
|
||||
|
||||
let staticIp = false;
|
||||
let ntpDhcp = true;
|
||||
|
||||
let loadingOrSaving = false;
|
||||
|
||||
let tries = 0;
|
||||
function scanForDevice() {
|
||||
var url = "";
|
||||
tries++;
|
||||
|
||||
if(sysinfo.net.ip && tries%3 == 0) {
|
||||
url = "http://" + sysinfo.net.ip;
|
||||
} else if(sysinfo.hostname && tries%3 == 1) {
|
||||
url = "http://" + sysinfo.hostname;
|
||||
} else if(sysinfo.hostname && tries%3 == 2) {
|
||||
url = "http://" + sysinfo.hostname + ".local";
|
||||
} else {
|
||||
url = "";
|
||||
}
|
||||
if(console) console.log("Trying url " + url);
|
||||
|
||||
var retry = function() {
|
||||
setTimeout(scanForDevice, 1000);
|
||||
};
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.timeout = 5000;
|
||||
xhr.addEventListener('abort', retry);
|
||||
xhr.addEventListener('error', retry);
|
||||
xhr.addEventListener('timeout', retry);
|
||||
xhr.addEventListener('load', function(e) {
|
||||
window.location.href = url ? url : "/";
|
||||
});
|
||||
xhr.open("GET", url + "/is-alive", true);
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
async function handleSubmit(e) {
|
||||
loadingOrSaving = true;
|
||||
const formData = new FormData(e.target)
|
||||
@ -27,6 +58,7 @@
|
||||
sysinfoStore.update(s => {
|
||||
s.usrcfg = res.success;
|
||||
s.booting = res.reboot;
|
||||
setTimeout(scanForDevice, 5000);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
@ -49,7 +81,7 @@
|
||||
</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}"/>
|
||||
<input name="sh" bind:value={sysinfo.hostname} 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"/>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label><input type="checkbox" name="sm" value="static" class="rounded mb-1" bind:checked={staticIp} /> Static IP</label>
|
||||
|
||||
@ -1,36 +1,58 @@
|
||||
<script>
|
||||
import { metertype, boardtype } from './Helpers.js';
|
||||
import { getSysinfo } from './DataStores.js';
|
||||
import { getSysinfo, gitHubReleaseStore, sysinfoStore } from './DataStores.js';
|
||||
import { upgrade, getNextVersion } from './UpgradeHelper';
|
||||
import DownloadIcon from './DownloadIcon.svelte';
|
||||
import UploadIcon from './UploadIcon.svelte';
|
||||
|
||||
export let sysinfo = {}
|
||||
export let data = {}
|
||||
|
||||
let nextVersion = {};
|
||||
gitHubReleaseStore.subscribe(releases => {
|
||||
nextVersion = getNextVersion(sysinfo.version, releases);
|
||||
if(!nextVersion) {
|
||||
nextVersion = releases[0];
|
||||
}
|
||||
});
|
||||
|
||||
function askUpgrade() {
|
||||
if(confirm('Do you want to upgrade this device to ' + nextVersion.tag_name + '?')) {
|
||||
if((sysinfo.board != 2 && sysinfo.board != 4 && sysinfo.board != 7) || confirm('WARNING: ' + boardtype(sysinfo.chip, sysinfo.board) + ' must be connected to an external power supply during firmware upgrade. Failure to do so may cause power-down during upload resulting in non-functioning unit.')) {
|
||||
sysinfoStore.update(s => {
|
||||
s.upgrading = true;
|
||||
return s;
|
||||
});
|
||||
upgrade(nextVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let fileinput;
|
||||
|
||||
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">
|
||||
<div class="my-2">
|
||||
Chip: {sysinfo.chip}
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<div class="my-2">
|
||||
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">
|
||||
<div class="my-2">
|
||||
Manufacturer: {metertype(sysinfo.meter.mfg)}
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<div class="my-2">
|
||||
Model: {sysinfo.meter.model}
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<div class="my-2">
|
||||
ID: {sysinfo.meter.id}
|
||||
</div>
|
||||
</div>
|
||||
@ -38,18 +60,49 @@
|
||||
{#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">
|
||||
<div class="my-2">
|
||||
IP: {sysinfo.net.ip}
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<div class="my-2">
|
||||
Mask: {sysinfo.net.mask}
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<div class="my-2">
|
||||
Gateway: {sysinfo.net.gw}
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<div class="my-2">
|
||||
DNS: {sysinfo.net.dns1} {#if sysinfo.net.dns2 != '0.0.0.0'}/ {sysinfo.net.dns2}{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="bg-white m-2 p-2 rounded-md shadow-lg pb-4 text-gray-700">
|
||||
<strong class="text-sm">Firmware</strong>
|
||||
<div class="my-2">
|
||||
Installed version: {sysinfo.version}
|
||||
</div>
|
||||
<div class="my-2 flex">
|
||||
Latest version:
|
||||
{#if sysinfo.fwconsent && nextVersion && nextVersion.tag_name}
|
||||
<a href={nextVersion.html_url} class="ml-2 text-blue-600 hover:text-blue-800" target='_blank' rel="noreferrer">{nextVersion.tag_name}</a>
|
||||
<div class="flex-none ml-2 text-green-500" title="Install this version">
|
||||
<button on:click={askUpgrade}><DownloadIcon/></button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if sysinfo.board == 2 || sysinfo.board == 4 || sysinfo.board == 7 }
|
||||
<div class="my-auto bg-red-500 text-red-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
|
||||
{boardtype(sysinfo.chip, sysinfo.board)} must be connected to an external power supply during firmware upgrade. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-2 flex">
|
||||
<form action="/firmware" enctype="multipart/form-data" method="post">
|
||||
<input style="display:none" name="file" type="file" accept=".bin" bind:this={fileinput}>
|
||||
{#if fileinput && fileinput.files.length == 0}
|
||||
<button type="button" on:click={()=>{fileinput.click();}} class="text-xs py-1 px-2 rounded bg-blue-500 text-white float-right mr-3">Choose firmware file</button>
|
||||
{:else if fileinput}
|
||||
{fileinput.files[0].name}
|
||||
<button type="submit" class="ml-2 text-xs py-1 px-2 rounded bg-blue-500 text-white float-right mr-3">Upload</button>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
65
lib/SvelteUi/app/src/lib/UpgradeHelper.js
Normal file
65
lib/SvelteUi/app/src/lib/UpgradeHelper.js
Normal file
@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
export async function upgrade(version) {
|
||||
const data = new URLSearchParams()
|
||||
data.append('version', version.tag_name);
|
||||
const response = await fetch('/upgrade', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
let res = (await response.json())
|
||||
}
|
||||
|
||||
export function getNextVersion(currentVersion, releases) {
|
||||
if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(currentVersion)) {
|
||||
var v = currentVersion.substring(1).split('.');
|
||||
var v_major = parseInt(v[0]);
|
||||
var v_minor = parseInt(v[1]);
|
||||
var v_patch = parseInt(v[2]);
|
||||
|
||||
releases.reverse();
|
||||
var next_patch;
|
||||
var next_minor;
|
||||
var next_major;
|
||||
for(var i = 0; i < releases.length; i++) {
|
||||
var release = releases[i];
|
||||
var ver2 = release.tag_name;
|
||||
var v2 = ver2.substring(1).split('.');
|
||||
var v2_major = parseInt(v2[0]);
|
||||
var v2_minor = parseInt(v2[1]);
|
||||
var v2_patch = parseInt(v2[2]);
|
||||
|
||||
if(v2_major == v_major) {
|
||||
if(v2_minor == v_minor) {
|
||||
if(v2_patch > v_patch) {
|
||||
next_patch = release;
|
||||
}
|
||||
} else if(v2_minor == v_minor+1) {
|
||||
next_minor = release;
|
||||
}
|
||||
} else if(v2_major == v_major+1) {
|
||||
if(next_major) {
|
||||
var mv = next_major.tag_name.substring(1).split('.');
|
||||
var mv_major = parseInt(mv[0]);
|
||||
var mv_minor = parseInt(mv[1]);
|
||||
var mv_patch = parseInt(mv[2]);
|
||||
if(v2_minor == mv_minor) {
|
||||
next_major = release;
|
||||
}
|
||||
} else {
|
||||
next_major = release;
|
||||
}
|
||||
}
|
||||
};
|
||||
if(next_minor) {
|
||||
return next_minor;
|
||||
} else if(next_major) {
|
||||
return next_major;
|
||||
} else if(next_patch) {
|
||||
return next_patch;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return releases[0];
|
||||
}
|
||||
}
|
||||
6
lib/SvelteUi/app/src/lib/UploadIcon.svelte
Normal file
6
lib/SvelteUi/app/src/lib/UploadIcon.svelte
Normal 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 8.25H7.5a2.25 2.25 0 00-2.25 2.25v9a2.25 2.25 0 002.25 2.25h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25H15m0-3l-3-3m0 0l-3 3m3-3V15" />
|
||||
</svg>
|
||||
|
||||
@ -39,14 +39,14 @@
|
||||
<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}>
|
||||
<select name="vb" bind:value={sysinfo.board} class="h-10 rounded-md shadow-sm border-gray-300 p-0 w-full">
|
||||
<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">
|
||||
<select name="vh" class="h-10 rounded-md shadow-sm border-gray-300">
|
||||
<UartSelectOptions chip={sysinfo.chip}/>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -17,15 +17,17 @@ export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
server: {
|
||||
proxy: {
|
||||
"/data.json": "http://192.168.233.244",
|
||||
"/data.json": "http://192.168.233.229",
|
||||
"/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",
|
||||
"/reboot": "http://192.168.233.244"
|
||||
"/sysinfo.json": "http://192.168.233.229",
|
||||
"/configuration.json": "http://192.168.233.229",
|
||||
"/save": "http://192.168.233.229",
|
||||
"/reboot": "http://192.168.233.229",
|
||||
"/firmware": "http://192.168.233.229",
|
||||
"/upgrade": "http://192.168.233.229"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -87,6 +87,12 @@ private:
|
||||
void configurationJson();
|
||||
void handleSave();
|
||||
void reboot();
|
||||
void upgrade();
|
||||
void firmwarePost();
|
||||
void firmwareUpload();
|
||||
void isAliveCheck();
|
||||
|
||||
HTTPUpload& uploadFile(const char* path);
|
||||
|
||||
void notFound();
|
||||
};
|
||||
|
||||
@ -17,6 +17,10 @@
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
|
||||
AmsWebServer::AmsWebServer(uint8_t* buf, RemoteDebug* Debug, HwTools* hw) {
|
||||
this->debugger = Debug;
|
||||
@ -51,6 +55,9 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
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.on(F("/upgrade"), HTTP_POST, std::bind(&AmsWebServer::upgrade, this));
|
||||
server.on(F("/firmware"), HTTP_POST, std::bind(&AmsWebServer::firmwarePost, this), std::bind(&AmsWebServer::firmwareUpload, this));
|
||||
server.on(F("/is-alive"), HTTP_GET, std::bind(&AmsWebServer::isAliveCheck, this));
|
||||
|
||||
server.onNotFound(std::bind(&AmsWebServer::notFound, this));
|
||||
|
||||
@ -154,7 +161,8 @@ void AmsWebServer::sysinfoJson() {
|
||||
#else
|
||||
chipId = ESP.getChipId();
|
||||
#endif
|
||||
doc["chipId"] = String(chipId, HEX);
|
||||
String chipIdStr = String(chipId, HEX);;
|
||||
doc["chipId"] = chipIdStr;
|
||||
|
||||
SystemConfig sys;
|
||||
config->getSystemConfig(sys);
|
||||
@ -164,6 +172,17 @@ void AmsWebServer::sysinfoJson() {
|
||||
doc["fwconsent"] = sys.dataCollectionConsent;
|
||||
doc["country"] = sys.country;
|
||||
|
||||
if(sys.userConfigured) {
|
||||
WiFiConfig wifiConfig;
|
||||
config->getWiFiConfig(wifiConfig);
|
||||
doc["hostname"] = wifiConfig.hostname;
|
||||
} else {
|
||||
doc["hostname"] = "ams-"+chipIdStr;
|
||||
}
|
||||
|
||||
doc["booting"] = performRestart;
|
||||
doc["upgrading"] = rebootForUpgrade;
|
||||
|
||||
doc["net"]["ip"] = WiFi.localIP().toString();
|
||||
doc["net"]["mask"] = WiFi.subnetMask().toString();
|
||||
doc["net"]["gw"] = WiFi.gatewayIP().toString();
|
||||
@ -176,6 +195,23 @@ void AmsWebServer::sysinfoJson() {
|
||||
|
||||
serializeJson(doc, buf, BufferSize);
|
||||
server.send(200, MIME_JSON, buf);
|
||||
|
||||
server.handleClient();
|
||||
delay(250);
|
||||
|
||||
if(performRestart || rebootForUpgrade) {
|
||||
if(ds != NULL) {
|
||||
ds->save();
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(PSTR("Rebooting"));
|
||||
delay(1000);
|
||||
#if defined(ESP8266)
|
||||
ESP.reset();
|
||||
#elif defined(ESP32)
|
||||
ESP.restart();
|
||||
#endif
|
||||
performRestart = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AmsWebServer::dataJson() {
|
||||
@ -802,8 +838,9 @@ void AmsWebServer::handleSave() {
|
||||
|
||||
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();
|
||||
int boardType = server.arg(F("vb")).toInt();
|
||||
int hanPin = server.arg(F("vh")).toInt();
|
||||
config->clear();
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
switch(boardType) {
|
||||
@ -854,7 +891,14 @@ void AmsWebServer::handleSave() {
|
||||
case 2: // spenceme
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->vccBootLimit = 33;
|
||||
gpioConfig->hanPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
gpioConfig->tempSensorPin = 5;
|
||||
break;
|
||||
case 0: // roarfred
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
@ -900,6 +944,7 @@ void AmsWebServer::handleSave() {
|
||||
success = false;
|
||||
}
|
||||
#endif
|
||||
config->setGpioConfig(*gpioConfig);
|
||||
|
||||
SystemConfig sys;
|
||||
config->getSystemConfig(sys);
|
||||
@ -1119,11 +1164,16 @@ void AmsWebServer::handleSave() {
|
||||
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->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("iv")) && server.arg(F("iv")) == F("true")) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Received Vcc config"));
|
||||
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);
|
||||
}
|
||||
|
||||
@ -1250,4 +1300,160 @@ void AmsWebServer::reboot() {
|
||||
ESP.restart();
|
||||
#endif
|
||||
performRestart = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AmsWebServer::upgrade() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Serving /upgrade over http...\n");
|
||||
|
||||
DynamicJsonDocument doc(128);
|
||||
doc["reboot"] = true;
|
||||
|
||||
serializeJson(doc, buf, BufferSize);
|
||||
server.send(200, MIME_JSON, buf);
|
||||
|
||||
server.handleClient();
|
||||
delay(250);
|
||||
|
||||
String customFirmwareUrl = "";
|
||||
if(server.hasArg(F("url"))) {
|
||||
customFirmwareUrl = server.arg(F("url"));
|
||||
}
|
||||
|
||||
String url = customFirmwareUrl.isEmpty() || !customFirmwareUrl.startsWith(F("http")) ? F("http://ams2mqtt.rewiredinvent.no/hub/firmware/update") : customFirmwareUrl;
|
||||
|
||||
if(server.hasArg(F("version"))) {
|
||||
url += "/" + server.arg(F("version"));
|
||||
}
|
||||
|
||||
WiFiClient client;
|
||||
#if defined(ESP8266)
|
||||
String chipType = F("esp8266");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
String chipType = F("esp32s2");
|
||||
#elif defined(ESP32)
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
String chipType = F("esp32solo");
|
||||
#else
|
||||
String chipType = F("esp32");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION);
|
||||
#elif defined(ESP32)
|
||||
HTTPUpdate httpUpdate;
|
||||
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
HTTPUpdateResult ret = httpUpdate.update(client, url, String(VERSION) + "-" + chipType);
|
||||
#endif
|
||||
|
||||
switch(ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
debugger->printf(PSTR("Update failed"));
|
||||
break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
debugger->printf(PSTR("No Update"));
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
debugger->printf(PSTR("Update OK"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AmsWebServer::firmwarePost() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(PSTR("Handling firmware post..."));
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
server.send(200);
|
||||
}
|
||||
|
||||
|
||||
void AmsWebServer::firmwareUpload() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_START) {
|
||||
String filename = upload.filename;
|
||||
if(!filename.endsWith(".bin")) {
|
||||
server.send(500, MIME_PLAIN, "500: couldn't create file");
|
||||
} else {
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_delete(NULL);
|
||||
esp_task_wdt_deinit();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtDisable();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
uploadFile(FILE_FIRMWARE);
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
rebootForUpgrade = true;
|
||||
server.sendHeader("Location","/");
|
||||
server.send(302);
|
||||
}
|
||||
}
|
||||
|
||||
HTTPUpload& AmsWebServer::uploadFile(const char* path) {
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_START){
|
||||
if(uploading) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("Upload already in progress"));
|
||||
server.send_P(500, MIME_HTML, PSTR("<html><body><h1>Upload already in progress!</h1></body></html>"));
|
||||
} else if (!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("An Error has occurred while mounting LittleFS"));
|
||||
server.send_P(500, MIME_HTML, PSTR("<html><body><h1>Unable to mount LittleFS!</h1></body></html>"));
|
||||
} else {
|
||||
uploading = true;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload file: %s\n"), path);
|
||||
}
|
||||
if(LittleFS.exists(path)) {
|
||||
LittleFS.remove(path);
|
||||
}
|
||||
file = LittleFS.open(path, "w");
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload Open file and write: %u\n"), upload.currentSize);
|
||||
}
|
||||
size_t written = file.write(upload.buf, upload.currentSize);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload Written: %u\n"), written);
|
||||
}
|
||||
}
|
||||
} else if(upload.status == UPLOAD_FILE_WRITE) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload Writing: %u\n"), upload.currentSize);
|
||||
}
|
||||
if(file) {
|
||||
size_t written = file.write(upload.buf, upload.currentSize);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload Written: %u\n"), written);
|
||||
}
|
||||
delay(1);
|
||||
if(written != upload.currentSize) {
|
||||
file.flush();
|
||||
file.close();
|
||||
LittleFS.remove(path);
|
||||
LittleFS.end();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(PSTR("An Error has occurred while writing file"));
|
||||
server.send_P(500, MIME_JSON, PSTR("{ \"success\": false, \"message\": \"Unable to upload\" }"));
|
||||
}
|
||||
}
|
||||
} else if(upload.status == UPLOAD_FILE_END) {
|
||||
if(file) {
|
||||
file.flush();
|
||||
file.close();
|
||||
// LittleFS.end();
|
||||
} else {
|
||||
server.send_P(500, MIME_JSON, PSTR("{ \"success\": false, \"message\": \"Unable to upload\" }"));
|
||||
}
|
||||
}
|
||||
return upload;
|
||||
}
|
||||
|
||||
void AmsWebServer::isAliveCheck() {
|
||||
server.sendHeader(F("Access-Control-Allow-Origin"), F("*"));
|
||||
server.send(200);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user