mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-07 03:46:06 +00:00
GUI improvements after testign
This commit is contained in:
2
lib/SvelteUi/app/dist/index.css
vendored
2
lib/SvelteUi/app/dist/index.css
vendored
File diff suppressed because one or more lines are too long
12
lib/SvelteUi/app/dist/index.js
vendored
12
lib/SvelteUi/app/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -34,17 +34,13 @@
|
||||
}
|
||||
|
||||
if(sysinfo.ui.k === 1) {
|
||||
console.log("dark");
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if (sysinfo.ui.k === 0) {
|
||||
console.log("light");
|
||||
document.documentElement.classList.remove('dark')
|
||||
} else {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
console.log("dark auto");
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
console.log("light auto");
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,11 @@
|
||||
translations = update;
|
||||
});
|
||||
|
||||
let it,et;
|
||||
let it,et,threePhase;
|
||||
$: {
|
||||
it = formatUnit(data?.ic * 1000, "Wh");
|
||||
et = formatUnit(data?.ec * 1000, "Wh");
|
||||
threePhase = data?.l1?.u > 100 && data?.l2?.u > 100 && data?.l3?.u > 100;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -80,7 +81,7 @@
|
||||
{#if data.l1}
|
||||
<PerPhasePlot title={translations.common?.amperage ?? "Amp"} unit="A" importColorFn={ampcol} exportColorFn={exportcol}
|
||||
maxImport={data.mf}
|
||||
maxExport={data.om ? data.om / 230 : 0}
|
||||
maxExport={data.om ? threePhase ? data.om / 0.4 / Math.sqrt(3) : data.om / 0.23 : 0}
|
||||
l1={data.l1 && data.l1.u > 100}
|
||||
l2={data.l2 && data.l2.u > 100}
|
||||
l3={data.l3 && data.l3.u > 100}
|
||||
@@ -88,9 +89,9 @@
|
||||
l1i={Math.max(data.l1.i,0)}
|
||||
l2i={Math.max(data.l2.i,0)}
|
||||
l3i={Math.max(data.l3.i,0)}
|
||||
l1e={Math.min(data.l1.i*-1,0)}
|
||||
l2e={Math.min(data.l2.i*-1,0)}
|
||||
l3e={Math.min(data.l3.i*-1,0)}
|
||||
l1e={Math.max(data.l1.e,0)}
|
||||
l2e={Math.max(data.l2.e,0)}
|
||||
l3e={Math.max(data.l3.e,0)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -100,15 +101,15 @@
|
||||
{#if data.l1}
|
||||
<PerPhasePlot title={translations.dashboard?.phase ?? "Phase"} unit="W" importColorFn={ampcol} exportColorFn={exportcol}
|
||||
maxImport={(data.mf ? data.mf : 32) * 230}
|
||||
maxExport={data.om}
|
||||
maxExport={data.om ? threePhase ? (data.om * 1000) / Math.sqrt(3) : data.om * 1000 : 0}
|
||||
l1={data.l1 && data.l1.u > 100}
|
||||
l2={data.l2 && data.l2.u > 100}
|
||||
l3={data.l3 && data.l3.u > 100}
|
||||
l1i={data.l1.p}
|
||||
l1e={data.l1.q}
|
||||
l2i={data.l2.p}
|
||||
l2e={data.l2.q}
|
||||
l3i={data.l3.p}
|
||||
l1e={data.l1.q}
|
||||
l2e={data.l2.q}
|
||||
l3e={data.l3.q}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { readable, writable } from 'svelte/store';
|
||||
import { isBusPowered, zeropad, scanForDevice } from './Helpers';
|
||||
|
||||
export async function fetchWithTimeout(resource, options = {}) {
|
||||
let queueMap = {}
|
||||
let queue = [];
|
||||
|
||||
async function fetchWithTimeoutReal(resource, options = {}) {
|
||||
const { timeout = 8000 } = options;
|
||||
|
||||
const controller = new AbortController();
|
||||
@@ -14,6 +17,38 @@ export async function fetchWithTimeout(resource, options = {}) {
|
||||
return response;
|
||||
}
|
||||
|
||||
let eatTimeout;
|
||||
async function eatQueue() {
|
||||
if(queue.length) {
|
||||
let item = queue.shift();
|
||||
delete queueMap[item.resource];
|
||||
let response = await fetchWithTimeoutReal(item.resource, item.options);
|
||||
for(let i in item.callbacks) {
|
||||
item.callbacks[i](response);
|
||||
}
|
||||
}
|
||||
if(eatTimeout) clearTimeout(eatTimeout);
|
||||
eatTimeout = setTimeout(eatQueue, 100);
|
||||
};
|
||||
eatQueue();
|
||||
|
||||
export async function fetchWithTimeout(resource, options = {}) {
|
||||
let item;
|
||||
if(queueMap[resource]) {
|
||||
item = queueMap[resource];
|
||||
} else {
|
||||
item = {
|
||||
resource: resource,
|
||||
options: options,
|
||||
callbacks: []
|
||||
};
|
||||
queueMap[resource] = item;
|
||||
queue.push(item);
|
||||
}
|
||||
let promise = new Promise((cb) => item.callbacks.push(cb));
|
||||
return promise;
|
||||
}
|
||||
|
||||
let sysinfo = {
|
||||
version: '',
|
||||
chip: '',
|
||||
@@ -231,14 +266,26 @@ export async function getGitHubReleases() {
|
||||
gitHubReleaseStore.set(releases);
|
||||
};
|
||||
|
||||
let realtime = {};
|
||||
let realtimeOffset = 0;
|
||||
let realtime = { data: [] };
|
||||
export async function getRealtime() {
|
||||
const response = await fetchWithTimeout("realtime.json");
|
||||
realtime = (await response.json())
|
||||
realtimeStore.set(realtime);
|
||||
const response = await fetchWithTimeout(realtimeOffset < 0 ? "realtime.json" : "realtime.json?offset="+realtimeOffset);
|
||||
let res = (await response.json())
|
||||
realtimeStore.update(current => {
|
||||
for(let i = 0; i < res.size; i++) {
|
||||
current.data[res.offset+i] = res.data[i];
|
||||
}
|
||||
current.size = current.data.length;
|
||||
return current;
|
||||
});
|
||||
if(realtimeOffset >= 0) {
|
||||
realtimeOffset += res.size;
|
||||
if(realtimeOffset < res.total) {
|
||||
setTimeout(getRealtime, 2000);
|
||||
} else {
|
||||
realtimeOffset = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const realtimeStore = writable(realtime, (set) => {
|
||||
getRealtime();
|
||||
return function stop() {}
|
||||
});
|
||||
export const realtimeStore = writable(realtime);
|
||||
|
||||
7
lib/SvelteUi/app/src/lib/GearIcon.svelte
Normal file
7
lib/SvelteUi/app/src/lib/GearIcon.svelte
Normal file
@@ -0,0 +1,7 @@
|
||||
<script></script>
|
||||
<!-- Heroicons -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
import Uptime from "./Uptime.svelte";
|
||||
import Badge from './Badge.svelte';
|
||||
import Clock from './Clock.svelte';
|
||||
import GearIcon from './GearIcon.svelte';
|
||||
import InfoIcon from "./InfoIcon.svelte";
|
||||
import HelpIcon from "./HelpIcon.svelte";
|
||||
|
||||
export let basepath = "/";
|
||||
export let data = {};
|
||||
@@ -79,14 +82,14 @@
|
||||
</div>
|
||||
{#if sysinfo.vndcfg && sysinfo.usrcfg}
|
||||
<div class="flex-none px-1 mt-1" style="font-size: 18px;font-weight:bold;" title={translations.header?.config ?? ""}>
|
||||
<Link to="/configuration">⛭</Link>
|
||||
<Link to="/configuration"><GearIcon/></Link>
|
||||
</div>
|
||||
<div class="flex-none px-1 mt-1" style="font-size: 18px;font-weight:bold;" title={translations.header?.status ?? ""}>
|
||||
<Link to="/status">ⓘ</Link>
|
||||
<Link to="/status"><InfoIcon/></Link>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-none px-1 mt-1" style="font-size: 18px;font-weight:bold;" title={translations.header?.doc ?? ""}>
|
||||
<a href={wiki('')} target='_blank' rel="noreferrer">🗐</a>
|
||||
<a href={wiki('')} target='_blank' rel="noreferrer"><HelpIcon/></a>
|
||||
</div>
|
||||
{#if sysinfo.fwconsent === 1 && nextVersion}
|
||||
<div class="flex-none mr-3 text-yellow-500" title={(translations.header?.new_version ?? "New version") + ': ' + nextVersion.tag_name}>
|
||||
|
||||
6
lib/SvelteUi/app/src/lib/HelpIcon.svelte
Normal file
6
lib/SvelteUi/app/src/lib/HelpIcon.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.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" />
|
||||
</svg>
|
||||
|
||||
6
lib/SvelteUi/app/src/lib/InfoIcon.svelte
Normal file
6
lib/SvelteUi/app/src/lib/InfoIcon.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="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
|
||||
</svg>
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
ticks.push({ value: maxImport, label: '100%' });
|
||||
}
|
||||
|
||||
|
||||
config = {
|
||||
title: title,
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
|
||||
@@ -1,34 +1,49 @@
|
||||
<script>
|
||||
import { dataStore, realtimeStore } from './DataStores.js';
|
||||
import { dataStore, realtimeStore, getRealtime } from './DataStores.js';
|
||||
|
||||
export let title;
|
||||
|
||||
let dark = document.documentElement.classList.contains('dark');
|
||||
|
||||
let realtime;
|
||||
realtimeStore.subscribe(update => {
|
||||
realtime = update;
|
||||
});
|
||||
|
||||
let blankTimeout;
|
||||
let addTimeout;
|
||||
let lastUp = 0;
|
||||
let lastValue = 0;
|
||||
let lastUpdate = 0;
|
||||
|
||||
let realtimeRequested = false;
|
||||
let realtime = null;
|
||||
let realtimeTimeout;
|
||||
realtimeStore.subscribe(update => {
|
||||
realtime = update;
|
||||
lastUpdate = lastUp;
|
||||
if(realtimeTimeout) clearTimeout(realtimeTimeout);
|
||||
realtimeTimeout = setTimeout(getRealtime, 600000);
|
||||
});
|
||||
|
||||
function addValue() {
|
||||
if(blankTimeout) clearTimeout(blankTimeout);
|
||||
blankTimeout = setTimeout(addValue, 10000);
|
||||
realtime.data.unshift(lastValue);
|
||||
realtime.data = realtime.data.slice(0,realtime.size);
|
||||
lastUp += 10;
|
||||
if(addTimeout) clearTimeout(addTimeout);
|
||||
if(lastUpdate > lastUp || lastUpdate - lastUp > 300) {
|
||||
getRealtime();
|
||||
} else {
|
||||
while(lastUp > lastUpdate) {
|
||||
realtime.data.unshift(lastValue);
|
||||
realtime.data = realtime.data.slice(0,realtime.size);
|
||||
lastUpdate += 10;
|
||||
}
|
||||
addTimeout = setTimeout(addValue, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
dataStore.subscribe(update => {
|
||||
if(lastUp == 0) {
|
||||
if(blankTimeout) clearTimeout(blankTimeout);
|
||||
blankTimeout = setTimeout(addValue, 10000);
|
||||
}
|
||||
lastValue = update.i-update.e;
|
||||
lastUp = update.u;
|
||||
if(!realtimeRequested) {
|
||||
getRealtime();
|
||||
realtimeRequested = true;
|
||||
return;
|
||||
}
|
||||
if(!realtime?.data?.length) return;
|
||||
if(!addTimeout) addTimeout = setTimeout(addValue, 10000);
|
||||
});
|
||||
|
||||
let max;
|
||||
@@ -59,6 +74,7 @@
|
||||
if(realtime.data) {
|
||||
for(let p in realtime.data) {
|
||||
let val = realtime.data[p];
|
||||
if(isNaN(val)) val = 0;
|
||||
max = Math.max(Math.ceil(val/1000.0)*1000, max);
|
||||
min = Math.min(Math.ceil(val/1000.0)*1000, min);
|
||||
}
|
||||
@@ -95,6 +111,7 @@
|
||||
break;
|
||||
}
|
||||
let val = realtime.data[p];
|
||||
if(isNaN(val)) val = 0;
|
||||
points = xScale(i--)+","+yScale(val)+" "+points;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1991,18 +1991,32 @@ void AmsWebServer::realtimeJson() {
|
||||
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
|
||||
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
|
||||
|
||||
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
snprintf_P(buf, BufferSize, PSTR("{\"size\":\"%d\",\"data\":["), rtp->getSize());
|
||||
server.send(200, MIME_JSON, buf);
|
||||
uint16_t offset = 0;
|
||||
if(server.hasArg(F("offset"))) {
|
||||
offset = server.arg(F("offset")).toInt();
|
||||
}
|
||||
|
||||
uint16_t size = 60;
|
||||
if(server.hasArg(F("size"))) {
|
||||
size = server.arg(F("size")).toInt();
|
||||
}
|
||||
|
||||
if(size > rtp->getSize()) {
|
||||
size = rtp->getSize();
|
||||
}
|
||||
if(offset > rtp->getSize()) {
|
||||
offset = rtp->getSize();
|
||||
}
|
||||
|
||||
uint16_t pos = snprintf_P(buf, BufferSize, PSTR("{\"offset\":%d,\"size\":%d,\"total\":%d,\"data\":["), offset, size, rtp->getSize());
|
||||
bool first = true;
|
||||
for(uint16_t i = 0; i < rtp->getSize(); i++) {
|
||||
snprintf_P(buf, BufferSize, PSTR("%s%d"), first ? "" : ",", rtp->getValue(i));
|
||||
server.sendContent(buf);
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
pos += snprintf_P(buf+pos, BufferSize-pos, PSTR("%s%d"), first ? "" : ",", rtp->getValue(offset+i));
|
||||
first = false;
|
||||
delay(1);
|
||||
}
|
||||
snprintf_P(buf, BufferSize, PSTR("]}"));
|
||||
server.sendContent(buf);
|
||||
pos += snprintf_P(buf+pos, BufferSize-pos, PSTR("]}"));
|
||||
server.send(200, MIME_JSON, buf);
|
||||
}
|
||||
|
||||
void AmsWebServer::setPriceSettings(String region, String currency) {
|
||||
|
||||
Reference in New Issue
Block a user