mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-12 21:44:21 +00:00
Compare commits
10 Commits
fix/rdc_re
...
upgrade/sv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d128700c1 | ||
|
|
6743750d8f | ||
|
|
640e957065 | ||
|
|
d4f11c0412 | ||
|
|
01acc6d6e8 | ||
|
|
e89bb53941 | ||
|
|
009c4686ee | ||
|
|
33dc5fc177 | ||
|
|
faf047e25f | ||
|
|
b4322c5f9c |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '19.x'
|
||||
node-version: '22.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
|
||||
2
.github/workflows/release-deploy-env.yml
vendored
2
.github/workflows/release-deploy-env.yml
vendored
@@ -73,7 +73,7 @@ jobs:
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '19.x'
|
||||
node-version: '22.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
|
||||
59
lib/SvelteUi/app/README.md
Normal file
59
lib/SvelteUi/app/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# SvelteUi App
|
||||
|
||||
Web interface for AMS Reader firmware built with Svelte 5 and Vite 6.
|
||||
|
||||
## Development Setup
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 20.x or 22.x LTS (required for Vite 6)
|
||||
- npm
|
||||
|
||||
### Local Development Configuration
|
||||
|
||||
To develop against your AMS reader device, you need to configure the proxy target:
|
||||
|
||||
1. Copy the example config file:
|
||||
```bash
|
||||
cp vite.config.local.example.js vite.config.local.js
|
||||
```
|
||||
|
||||
2. Edit `vite.config.local.js` and update the IP address to match your device:
|
||||
```javascript
|
||||
export default {
|
||||
proxyTarget: "http://192.168.1.100" // Your device's IP
|
||||
}
|
||||
```
|
||||
|
||||
3. The `vite.config.local.js` file is gitignored, so your personal settings won't be committed.
|
||||
|
||||
### Running Development Server
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The dev server will proxy API requests to your configured device IP.
|
||||
|
||||
### Building for Production
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
The build output will be in the `dist/` directory.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `src/` - Application source code
|
||||
- `routes/` - Page components using svelte-spa-router
|
||||
- `lib/` - Shared components and utilities
|
||||
- `public/` - Static assets (favicon, etc.)
|
||||
- `dist/` - Build output (not committed to git)
|
||||
|
||||
## Key Technologies
|
||||
|
||||
- **Svelte 5.17.0** - UI framework
|
||||
- **Vite 6.0.7** - Build tool
|
||||
- **svelte-spa-router 4.0.1** - Hash-based routing
|
||||
- **Tailwind CSS** - Styling
|
||||
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
3
lib/SvelteUi/app/dist/index.html
vendored
3
lib/SvelteUi/app/dist/index.html
vendored
@@ -8,10 +8,9 @@
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||
<title>AMS reader</title>
|
||||
<script type="module" crossorigin src="/index.js"></script>
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
<link rel="stylesheet" crossorigin href="/index.css">
|
||||
</head>
|
||||
<body class="bg-gray-100 dark:bg-gray-900">
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
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
2909
lib/SvelteUi/app/package-lock.json
generated
2909
lib/SvelteUi/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,28 +9,22 @@
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"overrides": {
|
||||
"svelte-navigator": {
|
||||
"svelte": ">=4.x"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.1.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.2",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"http-proxy-middleware": "^2.0.9",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"svelte": "^4.2.19",
|
||||
"svelte-navigator": "^3.2.2",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"svelte": "^5.17.0",
|
||||
"svelte-spa-router": "^4.0.1",
|
||||
"svelte-preprocess": "^6.0.3",
|
||||
"svelte-qrcode": "^1.0.0",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"vite": "^4.5.14"
|
||||
"vite": "^6.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "^5.1.15",
|
||||
"esbuild": ">=0.25.0",
|
||||
"ipaddr.js": "^2.3.0"
|
||||
"esbuild": ">=0.25.0"
|
||||
}
|
||||
}
|
||||
|
||||
19
lib/SvelteUi/app/public/favicon.svg
Normal file
19
lib/SvelteUi/app/public/favicon.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||
<title>Amsleser</title>
|
||||
<g transform="translate(-29.5,-83)">
|
||||
<circle r="4.8016944" cy="123.56455" cx="55.064552"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 41.298717,103.9049 a 24,24 0 0 1 27.531669,0"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 35.562952,95.713384 a 34,34 0 0 1 39.003199,-2e-6"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 47.034482,112.09642 a 14,14 0 0 1 16.06014,0"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:3.3;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle r="3" cy="105.99158" cx="38.181862"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:2.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<circle r="3" cy="97.959579" cx="77.491386"
|
||||
style="fill:none;stroke:#045c7c;stroke-width:2.4;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -1,49 +1,27 @@
|
||||
<script>
|
||||
import { Router, Route, navigate } from "svelte-navigator";
|
||||
import { getTariff, tariffStore, sysinfoStore, dataStore, importPricesStore, exportPricesStore, dayPlotStore, monthPlotStore, temperaturesStore, getSysinfo } from './lib/DataStores.js';
|
||||
import Router from "svelte-spa-router";
|
||||
import { push } from "svelte-spa-router";
|
||||
import { getTariff, sysinfoStore, dataStore, getSysinfo } from './lib/DataStores.js';
|
||||
import { translationsStore, getTranslations } from "./lib/TranslationService.js";
|
||||
import Favicon from './assets/favicon.svg'; // Need this for the build
|
||||
import Header from './lib/Header.svelte';
|
||||
import Dashboard from './lib/Dashboard.svelte';
|
||||
import ConfigurationPanel from './lib/ConfigurationPanel.svelte';
|
||||
import StatusPage from './lib/StatusPage.svelte';
|
||||
import VendorPanel from './lib/VendorPanel.svelte';
|
||||
import SetupPanel from './lib/SetupPanel.svelte';
|
||||
import DashboardRoute from './routes/DashboardRoute.svelte';
|
||||
import ConfigurationRoute from './routes/ConfigurationRoute.svelte';
|
||||
import StatusRoute from './routes/StatusRoute.svelte';
|
||||
import PriceConfigRoute from './routes/PriceConfigRoute.svelte';
|
||||
import MqttCaRoute from './routes/MqttCaRoute.svelte';
|
||||
import MqttCertRoute from './routes/MqttCertRoute.svelte';
|
||||
import MqttKeyRoute from './routes/MqttKeyRoute.svelte';
|
||||
import ConsentRoute from './routes/ConsentRoute.svelte';
|
||||
import SetupRoute from './routes/SetupRoute.svelte';
|
||||
import VendorRoute from './routes/VendorRoute.svelte';
|
||||
import EditDayRoute from './routes/EditDayRoute.svelte';
|
||||
import EditMonthRoute from './routes/EditMonthRoute.svelte';
|
||||
import Mask from './lib/Mask.svelte';
|
||||
import FileUploadComponent from "./lib/FileUploadComponent.svelte";
|
||||
import ConsentComponent from "./lib/ConsentComponent.svelte";
|
||||
import PriceConfig from "./lib/PriceConfig.svelte";
|
||||
import DataEdit from "./lib/DataEdit.svelte";
|
||||
import { updateRealtime } from "./lib/RealtimeStore.js";
|
||||
|
||||
let basepath = document.getElementsByTagName('base')[0].getAttribute("href");
|
||||
if(!basepath) basepath = "/";
|
||||
|
||||
let importPrices;
|
||||
importPricesStore.subscribe(update => {
|
||||
importPrices = update;
|
||||
});
|
||||
|
||||
let exportPrices;
|
||||
exportPricesStore.subscribe(update => {
|
||||
exportPrices = update;
|
||||
});
|
||||
|
||||
let dayPlot;
|
||||
dayPlotStore.subscribe(update => {
|
||||
dayPlot = update;
|
||||
});
|
||||
|
||||
let monthPlot;
|
||||
monthPlotStore.subscribe(update => {
|
||||
monthPlot = update;
|
||||
});
|
||||
|
||||
let temperatures;
|
||||
temperaturesStore.subscribe(update => {
|
||||
temperatures = update;
|
||||
});
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
translations = update;
|
||||
@@ -56,11 +34,11 @@
|
||||
sysinfoStore.subscribe(update => {
|
||||
sysinfo = update;
|
||||
if(sysinfo.vndcfg === false) {
|
||||
navigate(basepath + "vendor");
|
||||
push("/vendor");
|
||||
} else if(sysinfo.usrcfg === false) {
|
||||
navigate(basepath + "setup");
|
||||
push("/setup");
|
||||
} else if(sysinfo.fwconsent === 0) {
|
||||
navigate(basepath + "consent");
|
||||
push("/consent");
|
||||
}
|
||||
|
||||
if(sysinfo.ui.k === 1) {
|
||||
@@ -94,53 +72,26 @@
|
||||
updateRealtime(update);
|
||||
});
|
||||
|
||||
let tariffData = {};
|
||||
tariffStore.subscribe(update => {
|
||||
tariffData = update;
|
||||
});
|
||||
getTariff();
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto m-3">
|
||||
<Router basepath={basepath}>
|
||||
<Header data={data} basepath={basepath}/>
|
||||
<Route path="/">
|
||||
<Dashboard data={data} sysinfo={sysinfo} importPrices={importPrices} exportPrices={exportPrices} dayPlot={dayPlot} monthPlot={monthPlot} temperatures={temperatures} translations={translations} tariffData={tariffData}/>
|
||||
</Route>
|
||||
<Route path="/configuration">
|
||||
<ConfigurationPanel sysinfo={sysinfo} basepath={basepath} data={data}/>
|
||||
</Route>
|
||||
<Route path="/priceconfig">
|
||||
<PriceConfig basepath={basepath}/>
|
||||
</Route>
|
||||
<Route path="/status">
|
||||
<StatusPage sysinfo={sysinfo} data={data}/>
|
||||
</Route>
|
||||
<Route path="/mqtt-ca">
|
||||
<FileUploadComponent title="CA" action="/mqtt-ca"/>
|
||||
</Route>
|
||||
<Route path="/mqtt-cert">
|
||||
<FileUploadComponent title="certificate" action="/mqtt-cert"/>
|
||||
</Route>
|
||||
<Route path="/mqtt-key">
|
||||
<FileUploadComponent title="private key" action="/mqtt-key"/>
|
||||
</Route>
|
||||
<Route path="/consent">
|
||||
<ConsentComponent sysinfo={sysinfo} basepath={basepath}/>
|
||||
</Route>
|
||||
<Route path="/setup">
|
||||
<SetupPanel sysinfo={sysinfo}/>
|
||||
</Route>
|
||||
<Route path="/vendor">
|
||||
<VendorPanel sysinfo={sysinfo} basepath={basepath}/>
|
||||
</Route>
|
||||
<Route path="/edit-day">
|
||||
<DataEdit prefix="UTC Hour" data={dayPlot} url="/dayplot" basepath={basepath}/>
|
||||
</Route>
|
||||
<Route path="/edit-month">
|
||||
<DataEdit prefix="Day" data={monthPlot} url="/monthplot" basepath={basepath}/>
|
||||
</Route>
|
||||
</Router>
|
||||
<Header data={data} basepath={basepath}/>
|
||||
|
||||
<Router routes={{
|
||||
'/': DashboardRoute,
|
||||
'/configuration': ConfigurationRoute,
|
||||
'/priceconfig': PriceConfigRoute,
|
||||
'/status': StatusRoute,
|
||||
'/mqtt-ca': MqttCaRoute,
|
||||
'/mqtt-cert': MqttCertRoute,
|
||||
'/mqtt-key': MqttKeyRoute,
|
||||
'/consent': ConsentRoute,
|
||||
'/setup': SetupRoute,
|
||||
'/vendor': VendorRoute,
|
||||
'/edit-day': EditDayRoute,
|
||||
'/edit-month': EditMonthRoute,
|
||||
}} />
|
||||
|
||||
{#if sysinfo.booting}
|
||||
{#if sysinfo.trying}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script>
|
||||
import { Link } from "svelte-navigator";
|
||||
import { tooltip } from './tooltip';
|
||||
|
||||
export let config;
|
||||
@@ -47,7 +46,7 @@
|
||||
{#if config.link}
|
||||
<div class="text-xs text-right">
|
||||
{#if config.link.route}
|
||||
<Link to={config.link.url}>{config.link.text}</Link>
|
||||
<a href={"#" + config.link.url}>{config.link.text}</a>
|
||||
{:else}
|
||||
<a href={config.link.url} target={config.link.target}>{config.link.text}</a>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { translationsStore } from './TranslationService';
|
||||
import { navigate } from 'svelte-navigator';
|
||||
import { push } from 'svelte-spa-router';
|
||||
import Mask from './Mask.svelte'
|
||||
|
||||
export let prefix;
|
||||
@@ -59,7 +59,7 @@
|
||||
let res = (await response.json())
|
||||
|
||||
saving = false;
|
||||
navigate(basepath);
|
||||
push(basepath);
|
||||
}
|
||||
</script>
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
@@ -70,7 +70,7 @@
|
||||
{#each importElements as el}
|
||||
<label class="flex w-60 m-1">
|
||||
<span class="in-pre">{el.name}</span>
|
||||
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.01" class="in-txt w-full text-right"/>
|
||||
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.001" class="in-txt w-full text-right"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
{/each}
|
||||
@@ -82,7 +82,7 @@
|
||||
{#each exportElements as el}
|
||||
<label class="flex w-60 m-1">
|
||||
<span class="in-pre">{el.name}</span>
|
||||
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.01" class="in-txt w-full text-right"/>
|
||||
<input name="{el.key}" bind:value={data[el.key]} type="number" step="0.001" class="in-txt w-full text-right"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
{/each}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script>
|
||||
import { Link } from "svelte-navigator";
|
||||
import { sysinfoStore } from './DataStores.js';
|
||||
import { upgrade, upgradeWarningText } from './UpgradeHelper';
|
||||
import { boardtype, isBusPowered, wiki, bcol } from './Helpers.js';
|
||||
@@ -46,7 +45,7 @@
|
||||
<nav class="hdr">
|
||||
<div class="flex flex-wrap space-x-4 text-sm text-gray-300">
|
||||
<div class="flex text-lg text-gray-100 p-2">
|
||||
<Link to="/">AMS reader <span>{sysinfo.version}</span></Link>
|
||||
<a href={basepath}>AMS reader <span>{sysinfo.version}</span></a>
|
||||
</div>
|
||||
<div class="flex-none my-auto p-2 flex space-x-4">
|
||||
<div class="flex-none my-auto"><Uptime epoch={data.u}/></div>
|
||||
@@ -79,10 +78,10 @@
|
||||
</div>
|
||||
{#if sysinfo.vndcfg && sysinfo.usrcfg}
|
||||
<div class="flex-none px-1 mt-1" title={translations.header?.config ?? ""}>
|
||||
<Link to="/configuration"><GearIcon/></Link>
|
||||
<a href="#/configuration"><GearIcon/></a>
|
||||
</div>
|
||||
<div class="flex-none px-1 mt-1" title={translations.header?.status ?? ""}>
|
||||
<Link to="/status"><InfoIcon/></Link>
|
||||
<a href="#/status"><InfoIcon/></a>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex-none px-1 mt-1" title={translations.header?.doc ?? ""}>
|
||||
|
||||
@@ -41,22 +41,22 @@
|
||||
for(i = 0; i < tariffData.p.length; i++) {
|
||||
let peak = tariffData.p[i];
|
||||
|
||||
let title = "";
|
||||
let peakTitle = "";
|
||||
let daylabel = "-";
|
||||
if(peak.d > 0) {
|
||||
daylabel = zeropad(peak.d) + ".";
|
||||
title = zeropad(peak.d) + "." + (translations.months ? translations.months?.[new Date().getMonth()] : zeropad(new Date().getMonth()+1));
|
||||
peakTitle = zeropad(peak.d) + "." + (translations.months ? translations.months?.[new Date().getMonth()] : zeropad(new Date().getMonth()+1));
|
||||
if(tariffData.p.length < 4) {
|
||||
daylabel = title;
|
||||
daylabel = peakTitle;
|
||||
}
|
||||
}
|
||||
if(!isNaN(peak.h))
|
||||
title = title + ' ' + zeropad(peak.h) + ':00';
|
||||
title = title + ': ' + peak.v.toFixed(2) + ' kWh';
|
||||
peakTitle = peakTitle + ' ' + zeropad(peak.h) + ':00';
|
||||
peakTitle = peakTitle + ': ' + peak.v.toFixed(2) + ' kWh';
|
||||
points.push({
|
||||
label: peak.v.toFixed(2),
|
||||
value: peak.v,
|
||||
title: title,
|
||||
title: peakTitle,
|
||||
color: dark ? '#5c2da5' : '#7c3aed'
|
||||
});
|
||||
xTicks.push({
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import "./app.postcss";
|
||||
import { mount } from "svelte";
|
||||
import App from "./App.svelte";
|
||||
|
||||
const app = new App({
|
||||
const app = mount(App, {
|
||||
target: document.getElementById("app"),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<script>
|
||||
import { getConfiguration, configurationStore } from './ConfigurationStore'
|
||||
import { sysinfoStore, networksStore } from './DataStores.js';
|
||||
import fetchWithTimeout from './fetchWithTimeout';
|
||||
import { translationsStore } from './TranslationService';
|
||||
import { wiki, ipPattern, asciiPattern, asciiPatternExt, charAndNumPattern, hexPattern, numPattern, isBusPowered } from './Helpers.js';
|
||||
import UartSelectOptions from './UartSelectOptions.svelte';
|
||||
import Mask from './Mask.svelte'
|
||||
import Badge from './Badge.svelte';
|
||||
import CountrySelectOptions from './CountrySelectOptions.svelte';
|
||||
import { Link, navigate } from 'svelte-navigator';
|
||||
import SubnetOptions from './SubnetOptions.svelte';
|
||||
import { getConfiguration, configurationStore } from '../lib/ConfigurationStore'
|
||||
import { sysinfoStore, networksStore, dataStore } from '../lib/DataStores.js';
|
||||
import fetchWithTimeout from '../lib/fetchWithTimeout';
|
||||
import { translationsStore } from '../lib/TranslationService';
|
||||
import { wiki, ipPattern, asciiPattern, asciiPatternExt, charAndNumPattern, hexPattern, numPattern, isBusPowered } from '../lib/Helpers.js';
|
||||
import UartSelectOptions from '../lib/UartSelectOptions.svelte';
|
||||
import Mask from '../lib/Mask.svelte'
|
||||
import Badge from '../lib/Badge.svelte';
|
||||
import CountrySelectOptions from '../lib/CountrySelectOptions.svelte';
|
||||
import { push } from 'svelte-spa-router';
|
||||
import SubnetOptions from '../lib/SubnetOptions.svelte';
|
||||
import QrCode from 'svelte-qrcode';
|
||||
|
||||
export let basepath = "/";
|
||||
export let sysinfo = {};
|
||||
export let data;
|
||||
let basepath = "/";
|
||||
let sysinfo = {};
|
||||
let data;
|
||||
|
||||
sysinfoStore.subscribe(v => sysinfo = v);
|
||||
dataStore.subscribe(v => data = v);
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
translations = update;
|
||||
@@ -150,7 +153,7 @@
|
||||
});
|
||||
|
||||
saving = false;
|
||||
navigate(basepath);
|
||||
push(basepath);
|
||||
}
|
||||
|
||||
async function reboot() {
|
||||
@@ -336,7 +339,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
<Link to="/priceconfig" class="text-blue-600 hover:text-blue-800">{translations.conf?.price?.conf ?? "Configure"}</Link>
|
||||
<a href="#/priceconfig" class="text-blue-600 hover:text-blue-800">{translations.conf?.price?.conf ?? "Configure"}</a>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
<label><input type="checkbox" name="pe" value="true" bind:checked={configuration.p.e} class="rounded mb-1"/> {translations.conf?.price?.enabled ?? "Enabled"}</label>
|
||||
@@ -603,28 +606,28 @@
|
||||
<div class="my-1 flex">
|
||||
<span class="flex pr-2">
|
||||
{#if configuration.q.s.c}
|
||||
<span class="bd-on"><Link to="/mqtt-ca">{translations.conf?.mqtt?.ca_ok ?? "CA OK"}</Link></span>
|
||||
<span class="bd-on"><a href="#/mqtt-ca">{translations.conf?.mqtt?.ca_ok ?? "CA OK"}</a></span>
|
||||
<span class="bd-off" on:click={askDeleteCa} on:keypress={askDeleteCa}>🗑</span>
|
||||
{:else}
|
||||
<Link to="/mqtt-ca"><Badge color="blue" text={translations.conf?.mqtt?.btn_ca_upload ?? "Upload CA"} title={translations.conf?.mqtt?.title_ca ?? ""}/></Link>
|
||||
<a href="#/mqtt-ca"><Badge color="blue" text={translations.conf?.mqtt?.btn_ca_upload ?? "Upload CA"} title={translations.conf?.mqtt?.title_ca ?? ""}/></a>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="flex pr-2">
|
||||
{#if configuration.q.s.r}
|
||||
<span class="bd-on"><Link to="/mqtt-cert">{translations.conf?.mqtt?.crt_ok ?? "Cert OK"}</Link></span>
|
||||
<span class="bd-on"><a href="#/mqtt-cert">{translations.conf?.mqtt?.crt_ok ?? "Cert OK"}</a></span>
|
||||
<span class="bd-off" on:click={askDeleteCert} on:keypress={askDeleteCert}>🗑</span>
|
||||
{:else}
|
||||
<Link to="/mqtt-cert"><Badge color="blue" text={translations.conf?.mqtt?.btn_crt_upload ?? "Upload cert"} title={translations.conf?.mqtt?.title_crt ?? ""}/></Link>
|
||||
<a href="#/mqtt-cert"><Badge color="blue" text={translations.conf?.mqtt?.btn_crt_upload ?? "Upload cert"} title={translations.conf?.mqtt?.title_crt ?? ""}/></a>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="flex pr-2">
|
||||
{#if configuration.q.s.k}
|
||||
<span class="bd-on"><Link to="/mqtt-key">{translations.conf?.mqtt?.key_ok ?? "Key OK"}</Link></span>
|
||||
<span class="bd-on"><a href="#/mqtt-key">{translations.conf?.mqtt?.key_ok ?? "Key OK"}</a></span>
|
||||
<span class="bd-off" on:click={askDeleteKey} on:keypress={askDeleteKey}>🗑</span>
|
||||
{:else}
|
||||
<Link to="/mqtt-key"><Badge color="blue" text={translations.conf?.mqtt?.btn_key_upload ?? "Upload key"} title={translations.conf?.mqtt?.title_key ?? ""}/></Link>
|
||||
<a href="#/mqtt-key"><Badge color="blue" text={translations.conf?.mqtt?.btn_key_upload ?? "Upload key"} title={translations.conf?.mqtt?.title_key ?? ""}/></a>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1,18 +1,19 @@
|
||||
<script>
|
||||
import { sysinfoStore } from './DataStores.js';
|
||||
import { translationsStore } from './TranslationService.js';
|
||||
import Mask from './Mask.svelte'
|
||||
import { navigate } from 'svelte-navigator';
|
||||
import { wiki } from './Helpers';
|
||||
import { sysinfoStore } from '../lib/DataStores.js';
|
||||
import { translationsStore } from '../lib/TranslationService.js';
|
||||
import Mask from '../lib/Mask.svelte'
|
||||
import { push } from 'svelte-spa-router';
|
||||
|
||||
export let basepath = "/";
|
||||
export let sysinfo = {};
|
||||
let basepath = "/";
|
||||
let sysinfo = {};
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
translations = update;
|
||||
});
|
||||
|
||||
sysinfoStore.subscribe(v => sysinfo = v);
|
||||
|
||||
let loadingOrSaving = false;
|
||||
|
||||
async function handleSubmit(e) {
|
||||
@@ -36,7 +37,7 @@
|
||||
s.booting = res.reboot;
|
||||
return s;
|
||||
});
|
||||
navigate(basepath);
|
||||
push(basepath);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
<script>
|
||||
import { ampcol, exportcol, metertype, uiVisibility, formatUnit, fmtnum, formatCurrency } from './Helpers.js';
|
||||
import PowerGauge from './PowerGauge.svelte';
|
||||
import VoltPlot from './VoltPlot.svelte';
|
||||
import ReactiveData from './ReactiveData.svelte';
|
||||
import AccountingData from './AccountingData.svelte';
|
||||
import PricePlot from './PricePlot.svelte';
|
||||
import DayPlot from './DayPlot.svelte';
|
||||
import MonthPlot from './MonthPlot.svelte';
|
||||
import TemperaturePlot from './TemperaturePlot.svelte';
|
||||
import TariffPeakChart from './TariffPeakChart.svelte';
|
||||
import RealtimePlot from './RealtimePlot.svelte';
|
||||
import PerPhasePlot from './PerPhasePlot.svelte';
|
||||
import { ampcol, exportcol, metertype, uiVisibility, formatUnit, fmtnum, formatCurrency } from '../lib/Helpers.js';
|
||||
import PowerGauge from '../lib/PowerGauge.svelte';
|
||||
import VoltPlot from '../lib/VoltPlot.svelte';
|
||||
import ReactiveData from '../lib/ReactiveData.svelte';
|
||||
import AccountingData from '../lib/AccountingData.svelte';
|
||||
import PricePlot from '../lib/PricePlot.svelte';
|
||||
import DayPlot from '../lib/DayPlot.svelte';
|
||||
import MonthPlot from '../lib/MonthPlot.svelte';
|
||||
import TemperaturePlot from '../lib/TemperaturePlot.svelte';
|
||||
import TariffPeakChart from '../lib/TariffPeakChart.svelte';
|
||||
import RealtimePlot from '../lib/RealtimePlot.svelte';
|
||||
import PerPhasePlot from '../lib/PerPhasePlot.svelte';
|
||||
import { dataStore, sysinfoStore, importPricesStore, exportPricesStore, dayPlotStore, monthPlotStore, temperaturesStore, tariffStore } from '../lib/DataStores.js';
|
||||
import { translationsStore } from '../lib/TranslationService.js';
|
||||
|
||||
export let data = {}
|
||||
export let sysinfo = {}
|
||||
export let importPrices = {}
|
||||
export let exportPrices = {}
|
||||
export let dayPlot = {}
|
||||
export let monthPlot = {}
|
||||
export let temperatures = {};
|
||||
export let translations = {};
|
||||
export let tariffData = {};
|
||||
let data = {}
|
||||
let sysinfo = {}
|
||||
let importPrices = {}
|
||||
let exportPrices = {}
|
||||
let dayPlot = {}
|
||||
let monthPlot = {}
|
||||
let temperatures = {};
|
||||
let translations = {};
|
||||
let tariffData = {};
|
||||
|
||||
dataStore.subscribe(v => data = v);
|
||||
sysinfoStore.subscribe(v => sysinfo = v);
|
||||
importPricesStore.subscribe(v => importPrices = v);
|
||||
exportPricesStore.subscribe(v => exportPrices = v);
|
||||
dayPlotStore.subscribe(v => dayPlot = v);
|
||||
monthPlotStore.subscribe(v => monthPlot = v);
|
||||
temperaturesStore.subscribe(v => temperatures = v);
|
||||
translationsStore.subscribe(v => translations = v);
|
||||
tariffStore.subscribe(v => tariffData = v);
|
||||
|
||||
let it,et,threePhase, l1e, l2e, l3e;
|
||||
$: {
|
||||
11
lib/SvelteUi/app/src/routes/EditDayRoute.svelte
Normal file
11
lib/SvelteUi/app/src/routes/EditDayRoute.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import DataEdit from '../lib/DataEdit.svelte';
|
||||
import { dayPlotStore } from '../lib/DataStores.js';
|
||||
|
||||
let basepath = "/";
|
||||
let dayPlot;
|
||||
|
||||
dayPlotStore.subscribe(v => dayPlot = v);
|
||||
</script>
|
||||
|
||||
<DataEdit prefix="UTC Hour" data={dayPlot} url="/dayplot" {basepath} />
|
||||
11
lib/SvelteUi/app/src/routes/EditMonthRoute.svelte
Normal file
11
lib/SvelteUi/app/src/routes/EditMonthRoute.svelte
Normal file
@@ -0,0 +1,11 @@
|
||||
<script>
|
||||
import DataEdit from '../lib/DataEdit.svelte';
|
||||
import { monthPlotStore } from '../lib/DataStores.js';
|
||||
|
||||
let basepath = "/";
|
||||
let monthPlot;
|
||||
|
||||
monthPlotStore.subscribe(v => monthPlot = v);
|
||||
</script>
|
||||
|
||||
<DataEdit prefix="Day" data={monthPlot} url="/monthplot" {basepath} />
|
||||
@@ -1,9 +1,6 @@
|
||||
<script>
|
||||
import Mask from "./Mask.svelte";
|
||||
import { translationsStore } from "./TranslationService";
|
||||
|
||||
export let action;
|
||||
export let title;
|
||||
import Mask from "../lib/Mask.svelte";
|
||||
import { translationsStore } from "../lib/TranslationService";
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
@@ -15,12 +12,12 @@
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<strong>{translations.upload?.title ?? "Upload"} {title}</strong>
|
||||
<strong>{translations.upload?.title ?? "Upload"} CA</strong>
|
||||
<p class="mb-4">{translations.upload?.desc ?? ""}</p>
|
||||
<form action="{action}" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||
<form action="/mqtt-ca" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||
<input name="file" type="file">
|
||||
<div class="w-full text-right mt-4">
|
||||
<button type="submit" class="btn-pri"><p class="mb-4">{translations.btn?.upload ?? "Upload"}</button>
|
||||
<button type="submit" class="btn-pri">{translations.btn?.upload ?? "Upload"}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
25
lib/SvelteUi/app/src/routes/MqttCertRoute.svelte
Normal file
25
lib/SvelteUi/app/src/routes/MqttCertRoute.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import Mask from "../lib/Mask.svelte";
|
||||
import { translationsStore } from "../lib/TranslationService";
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
translations = update;
|
||||
});
|
||||
|
||||
let uploading = false;
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<strong>{translations.upload?.title ?? "Upload"} certificate</strong>
|
||||
<p class="mb-4">{translations.upload?.desc ?? ""}</p>
|
||||
<form action="/mqtt-cert" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||
<input name="file" type="file">
|
||||
<div class="w-full text-right mt-4">
|
||||
<button type="submit" class="btn-pri">{translations.btn?.upload ?? "Upload"}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<Mask active={uploading} message={translations.upload?.mask ?? "Uploading"}/>
|
||||
25
lib/SvelteUi/app/src/routes/MqttKeyRoute.svelte
Normal file
25
lib/SvelteUi/app/src/routes/MqttKeyRoute.svelte
Normal file
@@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import Mask from "../lib/Mask.svelte";
|
||||
import { translationsStore } from "../lib/TranslationService";
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
translations = update;
|
||||
});
|
||||
|
||||
let uploading = false;
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<strong>{translations.upload?.title ?? "Upload"} private key</strong>
|
||||
<p class="mb-4">{translations.upload?.desc ?? ""}</p>
|
||||
<form action="/mqtt-key" enctype="multipart/form-data" method="post" on:submit={() => uploading=true} autocomplete="off">
|
||||
<input name="file" type="file">
|
||||
<div class="w-full text-right mt-4">
|
||||
<button type="submit" class="btn-pri">{translations.btn?.upload ?? "Upload"}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<Mask active={uploading} message={translations.upload?.mask ?? "Uploading"}/>
|
||||
@@ -1,11 +1,9 @@
|
||||
<script>
|
||||
import { priceConfigStore, getPriceConfig } from './ConfigurationStore'
|
||||
import { translationsStore } from './TranslationService';
|
||||
import { wiki, zeropad } from './Helpers.js';
|
||||
import Mask from './Mask.svelte'
|
||||
import { navigate } from 'svelte-navigator';
|
||||
|
||||
export let basepath = "/";
|
||||
import { priceConfigStore, getPriceConfig } from '../lib/ConfigurationStore'
|
||||
import { translationsStore } from '../lib/TranslationService';
|
||||
import { wiki, zeropad } from '../lib/Helpers.js';
|
||||
import Mask from '../lib/Mask.svelte'
|
||||
import { push } from 'svelte-spa-router';
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
@@ -53,7 +51,7 @@
|
||||
let res = (await response.json())
|
||||
|
||||
saving = false;
|
||||
navigate(basepath + "configuration");
|
||||
push("/configuration");
|
||||
}
|
||||
|
||||
let toggleDay = function(arr, day) {
|
||||
@@ -1,9 +1,9 @@
|
||||
<script>
|
||||
import { sysinfoStore, networksStore } from './DataStores.js';
|
||||
import { translationsStore } from './TranslationService.js';
|
||||
import Mask from './Mask.svelte'
|
||||
import SubnetOptions from './SubnetOptions.svelte';
|
||||
import { scanForDevice, charAndNumPattern, asciiPatternExt, ipPattern } from './Helpers.js';
|
||||
import { sysinfoStore, networksStore } from '../lib/DataStores.js';
|
||||
import { translationsStore } from '../lib/TranslationService.js';
|
||||
import Mask from '../lib/Mask.svelte'
|
||||
import SubnetOptions from '../lib/SubnetOptions.svelte';
|
||||
import { scanForDevice, charAndNumPattern, asciiPatternExt, ipPattern } from '../lib/Helpers.js';
|
||||
|
||||
let translations = {};
|
||||
translationsStore.subscribe(update => {
|
||||
@@ -16,7 +16,8 @@
|
||||
networks = update;
|
||||
});
|
||||
|
||||
export let sysinfo = {}
|
||||
let sysinfo = {}
|
||||
sysinfoStore.subscribe(v => sysinfo = v);
|
||||
|
||||
let staticIp = false;
|
||||
let connectionMode = 1;
|
||||
@@ -1,16 +1,70 @@
|
||||
<script>
|
||||
import { metertype, boardtype, isBusPowered, getBaseChip, wiki } from './Helpers.js';
|
||||
import { getSysinfo, sysinfoStore } from './DataStores.js';
|
||||
import { upgrade, upgradeWarningText } from './UpgradeHelper';
|
||||
import { translationsStore } from './TranslationService.js';
|
||||
import { Link } from 'svelte-navigator';
|
||||
import Clock from './Clock.svelte';
|
||||
import Mask from './Mask.svelte';
|
||||
import { scanForDevice } from './Helpers.js';
|
||||
import ipaddr from 'ipaddr.js';
|
||||
import { metertype, boardtype, isBusPowered, getBaseChip, wiki } from '../lib/Helpers.js';
|
||||
import { getSysinfo, sysinfoStore, dataStore } from '../lib/DataStores.js';
|
||||
import { upgrade, upgradeWarningText } from '../lib/UpgradeHelper';
|
||||
import { translationsStore } from '../lib/TranslationService.js';
|
||||
import Clock from '../lib/Clock.svelte';
|
||||
import Mask from '../lib/Mask.svelte';
|
||||
import { scanForDevice } from '../lib/Helpers.js';
|
||||
|
||||
export let data;
|
||||
export let sysinfo;
|
||||
let data;
|
||||
let sysinfo;
|
||||
|
||||
dataStore.subscribe(v => data = v);
|
||||
sysinfoStore.subscribe(v => sysinfo = v);
|
||||
|
||||
// Format IPv6 address to compact form (RFC 5952)
|
||||
const formatIPv6 = (addr) => {
|
||||
if (!addr) return addr;
|
||||
|
||||
// Split into groups
|
||||
const groups = addr.toLowerCase().split(':');
|
||||
|
||||
// Remove leading zeros from each group
|
||||
const normalized = groups.map(g => g.replace(/^0+/, '') || '0');
|
||||
|
||||
// Find longest sequence of consecutive zeros
|
||||
let maxStart = -1, maxLen = 0;
|
||||
let currStart = -1, currLen = 0;
|
||||
|
||||
for (let i = 0; i < normalized.length; i++) {
|
||||
if (normalized[i] === '0') {
|
||||
if (currStart === -1) currStart = i;
|
||||
currLen++;
|
||||
} else {
|
||||
if (currLen > maxLen) {
|
||||
maxStart = currStart;
|
||||
maxLen = currLen;
|
||||
}
|
||||
currStart = -1;
|
||||
currLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check final sequence
|
||||
if (currLen > maxLen) {
|
||||
maxStart = currStart;
|
||||
maxLen = currLen;
|
||||
}
|
||||
|
||||
// Only compress if we have 2 or more consecutive zeros
|
||||
if (maxLen > 1) {
|
||||
const before = normalized.slice(0, maxStart);
|
||||
const after = normalized.slice(maxStart + maxLen);
|
||||
|
||||
if (before.length === 0 && after.length === 0) {
|
||||
return '::';
|
||||
} else if (before.length === 0) {
|
||||
return '::' + after.join(':');
|
||||
} else if (after.length === 0) {
|
||||
return before.join(':') + '::';
|
||||
} else {
|
||||
return before.join(':') + '::' + after.join(':');
|
||||
}
|
||||
}
|
||||
|
||||
return normalized.join(':');
|
||||
};
|
||||
|
||||
let cfgItems = [{
|
||||
name: 'WiFi',
|
||||
@@ -73,11 +127,11 @@
|
||||
}
|
||||
|
||||
let firmwareFileInput;
|
||||
let firmwareFiles = [];
|
||||
let firmwareFiles = null;
|
||||
let firmwareUploading = false;
|
||||
|
||||
let configFileInput;
|
||||
let configFiles = [];
|
||||
let configFiles = null;
|
||||
let configUploading = false;
|
||||
|
||||
getSysinfo();
|
||||
@@ -119,7 +173,7 @@
|
||||
};
|
||||
|
||||
$: {
|
||||
if(configFiles.length == 1) {
|
||||
if(configFiles && configFiles.length == 1) {
|
||||
let file = configFiles[0];
|
||||
let reader = new FileReader();
|
||||
let parseConfigFile = ( e ) => {
|
||||
@@ -146,7 +200,7 @@
|
||||
{translations.status?.device?.chip ?? "Chip"}: {sysinfo.chip} {#if sysinfo.cpu}({sysinfo.cpu}MHz){/if}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
{translations.status?.device?.device ?? "Device"}: <Link to="/vendor">{boardtype(sysinfo.chip, sysinfo.board)}</Link>
|
||||
{translations.status?.device?.device ?? "Device"}: <a href="#/vendor">{boardtype(sysinfo.chip, sysinfo.board)}</a>
|
||||
</div>
|
||||
<div class="my-2">
|
||||
{translations.status?.device?.mac ?? "MAC"}: {sysinfo.mac}
|
||||
@@ -169,9 +223,9 @@
|
||||
{/if}
|
||||
{#if data?.a}
|
||||
<div class="my-2">
|
||||
<Link to="/consent">
|
||||
<a href="#/consent">
|
||||
<span class="btn-pri-sm">{translations.status?.device?.btn_consents ?? "Consents"}</span>
|
||||
</Link>
|
||||
</a>
|
||||
<button on:click={askReboot} class="btn-yellow-sm float-right">{translations.btn?.reboot ?? "Reboot"}</button>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -208,11 +262,11 @@
|
||||
</div>
|
||||
{#if sysinfo.net.ipv6}
|
||||
<div class="my-2">
|
||||
IPv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.ipv6)}</span>
|
||||
IPv6: <span style="font-size: 14px;">{formatIPv6(sysinfo.net.ipv6)}</span>
|
||||
</div>
|
||||
<div class="my-2">
|
||||
{#if sysinfo.net.dns1v6}DNSv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.dns1v6)}</span>{/if}
|
||||
{#if sysinfo.net.dns2v6}DNSv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.dns2v6)}</span>{/if}
|
||||
{#if sysinfo.net.dns1v6}DNSv6: <span style="font-size: 14px;">{formatIPv6(sysinfo.net.dns1v6)}</span>{/if}
|
||||
{#if sysinfo.net.dns2v6}DNSv6: <span style="font-size: 14px;">{formatIPv6(sysinfo.net.dns2v6)}</span>{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -267,7 +321,7 @@
|
||||
<div class="my-2 flex">
|
||||
<form action="firmware" enctype="multipart/form-data" method="post" on:submit={() => firmwareUploading=true} autocomplete="off">
|
||||
<input style="display:none" name="file" type="file" accept=".bin" bind:this={firmwareFileInput} bind:files={firmwareFiles}>
|
||||
{#if firmwareFiles.length == 0}
|
||||
{#if !firmwareFiles || firmwareFiles.length == 0}
|
||||
<button type="button" on:click={()=>{firmwareFileInput.click();}} class="btn-pri-sm float-right">{translations.status?.firmware?.btn_select_file ?? "Select file"}</button>
|
||||
{:else}
|
||||
{firmwareFiles[0].name}
|
||||
@@ -287,13 +341,13 @@
|
||||
{/each}
|
||||
<label class="my-1 mx-3 col-span-2"><input type="checkbox" class="rounded" name="ic" value="true"/> {translations.status?.backup?.secrets ?? "Include secrets"}<br/><small>{translations.status?.backup?.secrets_desc ?? ""}</small></label>
|
||||
</div>
|
||||
{#if configFiles.length == 0}
|
||||
{#if !configFiles || configFiles.length == 0}
|
||||
<button type="submit" class="btn-pri-sm float-right">{translations.status?.backup?.btn_download ?? "Download"}</button>
|
||||
{/if}
|
||||
</form>
|
||||
<form on:submit|preventDefault={uploadConfigFile} autocomplete="off">
|
||||
<input style="display:none" name="file" type="file" accept=".cfg" bind:this={configFileInput} bind:files={configFiles}>
|
||||
{#if configFiles.length == 0}
|
||||
{#if !configFiles || configFiles.length == 0}
|
||||
<button type="button" on:click={()=>{configFileInput.click();}} class="btn-pri-sm">{translations.status?.backup?.btn_select_file ?? "Select file"}</button>
|
||||
{:else}
|
||||
{configFiles[0].name}
|
||||
@@ -1,12 +1,11 @@
|
||||
<script>
|
||||
import { sysinfoStore } from './DataStores.js';
|
||||
import BoardTypeSelectOptions from './BoardTypeSelectOptions.svelte';
|
||||
import UartSelectOptions from './UartSelectOptions.svelte';
|
||||
import Mask from './Mask.svelte'
|
||||
import { navigate } from 'svelte-navigator';
|
||||
import { sysinfoStore } from '../lib/DataStores.js';
|
||||
import BoardTypeSelectOptions from '../lib/BoardTypeSelectOptions.svelte';
|
||||
import UartSelectOptions from '../lib/UartSelectOptions.svelte';
|
||||
import Mask from '../lib/Mask.svelte'
|
||||
import { push } from 'svelte-spa-router';
|
||||
|
||||
export let basepath = "/";
|
||||
export let sysinfo = {};
|
||||
let sysinfo = {};
|
||||
|
||||
let loadingOrSaving = false;
|
||||
async function handleSubmit(e) {
|
||||
@@ -32,7 +31,7 @@
|
||||
|
||||
return s;
|
||||
});
|
||||
navigate(basepath + (sysinfo.usrcfg ? "" : "setup"));
|
||||
push(sysinfo.usrcfg ? "/" : "/setup");
|
||||
}
|
||||
|
||||
let cc = true;
|
||||
@@ -1,45 +1,62 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// Try to import local config, fall back to default if not found
|
||||
let localConfig = { proxyTarget: "http://192.168.4.1" };
|
||||
try {
|
||||
const imported = await import('./vite.config.local.js');
|
||||
localConfig = imported.default;
|
||||
} catch (e) {
|
||||
console.log('No vite.config.local.js found, using default proxy target:', localConfig.proxyTarget);
|
||||
console.log('Copy vite.config.local.example.js to vite.config.local.js to customize');
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: '.',
|
||||
minify: 'esbuild',
|
||||
target: 'es2020',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: '[name][extname]',
|
||||
chunkFileNames: '[name].js',
|
||||
entryFileNames: '[name].js'
|
||||
entryFileNames: '[name].js',
|
||||
manualChunks: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [svelte()],
|
||||
plugins: [svelte({
|
||||
compilerOptions: {
|
||||
dev: false
|
||||
}
|
||||
})],
|
||||
server: {
|
||||
proxy: {
|
||||
"/data.json": "http://192.168.21.122",
|
||||
"/energyprice.json": "http://192.168.21.122",
|
||||
"/importprice.json": "http://192.168.21.122",
|
||||
"/exportprice.json": "http://192.168.21.122",
|
||||
"/dayplot.json": "http://192.168.21.122",
|
||||
"/monthplot.json": "http://192.168.21.122",
|
||||
"/temperature.json": "http://192.168.21.122",
|
||||
"/sysinfo.json": "http://192.168.21.122",
|
||||
"/configuration.json": "http://192.168.21.122",
|
||||
"/tariff.json": "http://192.168.21.122",
|
||||
"/realtime.json": "http://192.168.21.122",
|
||||
"/priceconfig.json": "http://192.168.21.122",
|
||||
"/translations.json": "http://192.168.21.122",
|
||||
"/cloudkey.json": "http://192.168.21.122",
|
||||
"/wifiscan.json": "http://192.168.21.122",
|
||||
"/save": "http://192.168.21.122",
|
||||
"/reboot": "http://192.168.21.122",
|
||||
"/configfile": "http://192.168.21.122",
|
||||
"/upgrade": "http://192.168.21.122",
|
||||
"/mqtt-ca": "http://192.168.21.122",
|
||||
"/mqtt-cert": "http://192.168.21.122",
|
||||
"/mqtt-key": "http://192.168.21.122",
|
||||
"/logo.svg": "http://192.168.21.122",
|
||||
"/data.json": localConfig.proxyTarget,
|
||||
"/energyprice.json": localConfig.proxyTarget,
|
||||
"/importprice.json": localConfig.proxyTarget,
|
||||
"/exportprice.json": localConfig.proxyTarget,
|
||||
"/dayplot.json": localConfig.proxyTarget,
|
||||
"/monthplot.json": localConfig.proxyTarget,
|
||||
"/temperature.json": localConfig.proxyTarget,
|
||||
"/sysinfo.json": localConfig.proxyTarget,
|
||||
"/configuration.json": localConfig.proxyTarget,
|
||||
"/tariff.json": localConfig.proxyTarget,
|
||||
"/realtime.json": localConfig.proxyTarget,
|
||||
"/priceconfig.json": localConfig.proxyTarget,
|
||||
"/translations.json": localConfig.proxyTarget,
|
||||
"/cloudkey.json": localConfig.proxyTarget,
|
||||
"/wifiscan.json": localConfig.proxyTarget,
|
||||
"/save": localConfig.proxyTarget,
|
||||
"/reboot": localConfig.proxyTarget,
|
||||
"/configfile": localConfig.proxyTarget,
|
||||
"/upgrade": localConfig.proxyTarget,
|
||||
"/mqtt-ca": localConfig.proxyTarget,
|
||||
"/mqtt-cert": localConfig.proxyTarget,
|
||||
"/mqtt-key": localConfig.proxyTarget,
|
||||
"/logo.svg": localConfig.proxyTarget,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
7
lib/SvelteUi/app/vite.config.local.example.js
Normal file
7
lib/SvelteUi/app/vite.config.local.example.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copy this file to vite.config.local.js and update with your device's IP address
|
||||
// vite.config.local.js is ignored by git so your settings won't be committed
|
||||
|
||||
export default {
|
||||
// The IP address of your AMS reader device for local development
|
||||
proxyTarget: "http://192.168.4.1"
|
||||
}
|
||||
@@ -61,6 +61,14 @@ AmsWebServer::AmsWebServer(uint8_t* buf, Stream* Debug, HwTools* hw, ResetDataCo
|
||||
this->hw = hw;
|
||||
this->buf = (char*) buf;
|
||||
this->rdc = rdc;
|
||||
if(rdc->magic != 0x4a) {
|
||||
rdc->last_cause = 0;
|
||||
rdc->cause = 0;
|
||||
rdc->magic = 0x4a;
|
||||
} else {
|
||||
rdc->last_cause = rdc->cause;
|
||||
rdc->cause = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, AmsData* meterState, AmsDataStorage* ds, EnergyAccounting* ea, RealtimePlot* rtp, AmsFirmwareUpdater* updater) {
|
||||
|
||||
@@ -30,7 +30,6 @@ ADC_MODE(ADC_VCC);
|
||||
#include "ZmartChargeCloudConnector.h"
|
||||
#endif
|
||||
|
||||
#define MAX_BOOT_CYCLES 8
|
||||
#define WDT_TIMEOUT 120
|
||||
#if defined(SLOW_PROC_TRIGGER_MS)
|
||||
#warning "Using predefined slow process trigger"
|
||||
@@ -192,6 +191,8 @@ CloudConnector *cloud = NULL;
|
||||
ZmartChargeCloudConnector *zcloud = NULL;
|
||||
#endif
|
||||
|
||||
#define MAX_BOOT_CYCLES 6
|
||||
|
||||
#if defined(ESP32)
|
||||
__NOINIT_ATTR EnergyAccountingRealtimeData rtd;
|
||||
RTC_DATA_ATTR uint8_t bootcount = 0;
|
||||
@@ -377,6 +378,10 @@ void setup() {
|
||||
|
||||
delay(1);
|
||||
hw.setup(&sysConfig, &gpioConfig);
|
||||
hw.ledOff(LED_INTERNAL);
|
||||
hw.ledOff(LED_RED);
|
||||
hw.ledOff(LED_GREEN);
|
||||
hw.ledOff(LED_BLUE);
|
||||
|
||||
if(gpioConfig.apPin >= 0) {
|
||||
pinMode(gpioConfig.apPin, INPUT_PULLUP);
|
||||
@@ -465,7 +470,7 @@ void setup() {
|
||||
float vcc = hw.getVcc();
|
||||
debugI_P(PSTR("Voltage: %.2fV"), vcc);
|
||||
|
||||
bool deepSleep = false; // Disable for now, as it makes it difficult to debug why devices rebooted
|
||||
bool deepSleep = true;
|
||||
#if defined(ESP32)
|
||||
float allowedDrift = bootcount * 0.01;
|
||||
#else
|
||||
@@ -499,16 +504,6 @@ void setup() {
|
||||
#if defined(ESP8266)
|
||||
resetBootCycleCounter(deepSleep);
|
||||
#endif
|
||||
|
||||
if(rdc.magic != 0x4a) {
|
||||
rdc.last_cause = 0;
|
||||
rdc.cause = 0;
|
||||
rdc.magic = 0x4a;
|
||||
} else {
|
||||
rdc.last_cause = rdc.cause;
|
||||
rdc.cause = 0;
|
||||
}
|
||||
|
||||
hw.ledOff(LED_YELLOW);
|
||||
hw.ledOff(LED_INTERNAL);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user