mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-12 05:25:24 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdee066c33 | ||
|
|
dd23a0fa60 | ||
|
|
e8fc6d48bf | ||
|
|
4b15ac74fc |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
||||
custom: ["https://paypal.me/gskjold"]
|
||||
custom: ["https://amsleser.no"]
|
||||
|
||||
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -8,7 +8,6 @@ on:
|
||||
- scripts/**
|
||||
- web/**
|
||||
- platformio.ini
|
||||
- .github/workflows/**
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
@@ -23,12 +22,6 @@ jobs:
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -47,18 +40,6 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: true
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
- name: PlatformIO run
|
||||
|
||||
96
.github/workflows/release.yml
vendored
96
.github/workflows/release.yml
vendored
@@ -23,12 +23,6 @@ jobs:
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo "GITHUB_TAG=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV
|
||||
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -47,23 +41,12 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: false
|
||||
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
|
||||
- name: PlatformIO run
|
||||
run: pio run
|
||||
- name: Create zip files
|
||||
run: /bin/sh scripts/mkzip.sh
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
@@ -75,19 +58,6 @@ jobs:
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Build esp8266 firmware
|
||||
run: pio run -e esp8266
|
||||
- name: Create esp8266 zip file
|
||||
run: /bin/sh scripts/esp8266/mkzip.sh
|
||||
- name: Upload esp8266 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp8266/firmware.bin
|
||||
asset_name: ams2mqtt-esp8266-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp8266 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -97,20 +67,6 @@ jobs:
|
||||
asset_path: esp8266.zip
|
||||
asset_name: ams2mqtt-esp8266-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32 firmware
|
||||
run: pio run -e esp32
|
||||
- name: Create esp32 zip file
|
||||
run: /bin/sh scripts/esp32/mkzip.sh
|
||||
- name: Upload esp32 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32/firmware.bin
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -120,20 +76,6 @@ jobs:
|
||||
asset_path: esp32.zip
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32s2 firmware
|
||||
run: pio run -e esp32s2
|
||||
- name: Create esp32s2 zip file
|
||||
run: /bin/sh scripts/esp32s2/mkzip.sh
|
||||
- name: Upload esp32s2 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32s2/firmware.bin
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32s2 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -144,10 +86,24 @@ jobs:
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32solo firmware
|
||||
run: pio run -e esp32solo
|
||||
- name: Create esp32solo zip file
|
||||
run: /bin/sh scripts/esp32solo/mkzip.sh
|
||||
- name: Upload esp8266 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp8266/firmware.bin
|
||||
asset_name: ams2mqtt-esp8266-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32/firmware.bin
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32solo binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
@@ -157,12 +113,12 @@ jobs:
|
||||
asset_path: .pio/build/esp32solo/firmware.bin
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32solo zip to release
|
||||
- name: Upload esp32s2 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32solo.zip
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
asset_path: .pio/build/esp32s2/firmware.bin
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -7,7 +7,7 @@
|
||||
.vscode
|
||||
.pio
|
||||
platformio-user.ini
|
||||
/lib/AmsConfiguration/include/version.h
|
||||
/src/version.h
|
||||
/src/web/root
|
||||
/src/AmsToMqttBridge.ino.cpp
|
||||
/test
|
||||
@@ -15,6 +15,3 @@ platformio-user.ini
|
||||
/sdkconfig
|
||||
/.tmp
|
||||
/*.zip
|
||||
node_modules
|
||||
/gui/dist
|
||||
/scripts/*dev
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#include <Timezone.h>
|
||||
|
||||
#define JULY1970 15634800
|
||||
|
||||
TimeChangeRule TC_GMT = {"GMT", Last, Sun, Jan, 0, 0};
|
||||
TimeChangeRule TC_WET = {"WET", Last, Sun, Oct, 2, 0};
|
||||
TimeChangeRule TC_WEST = {"WEST", Last, Sun, Mar, 1, 60};
|
||||
TimeChangeRule TC_CET = {"CET", Last, Sun, Oct, 3, 60};
|
||||
TimeChangeRule TC_CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule TC_EET = {"EET", Last, Sun, Oct, 4, 120};
|
||||
TimeChangeRule TC_EEST = {"EEST", Last, Sun, Mar, 3, 180};
|
||||
|
||||
Timezone GMT = Timezone(TC_GMT);
|
||||
Timezone WesterEuropean = Timezone(TC_WET, TC_WEST);
|
||||
Timezone CentralEuropean = Timezone(TC_CET, TC_CEST);
|
||||
Timezone EasternEuropean = Timezone(TC_EET, TC_EEST);
|
||||
|
||||
Timezone* resolveTimezone(char* name) {
|
||||
if(strncmp_P(name, PSTR("Europe/"), 7) == 0) {
|
||||
if(strncmp_P(name+7, PSTR("Amsterdam"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Athens"), 6) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Belfast"), 7) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Berlin"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Bratislava"), 10) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Brussels"), 8) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Bucharest"), 9) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Budapest"), 8) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Copenhagen"), 10) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Dublin"), 6) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Helsinki"), 8) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Lisbon"), 6) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Ljubljana"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("London"), 6) == 0)
|
||||
return &WesterEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Luxembourg"), 10) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Madrid"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Malta"), 5) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Nicosia"), 7) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Oslo"), 4) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Paris"), 5) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Podgorica"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Prague"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Riga"), 4) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Rome"), 4) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Sofia"), 5) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Stockholm"), 9) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Tallinn"), 7) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Vienna"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Vilnius"), 7) == 0)
|
||||
return &EasternEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Warsaw"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Zagreb"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
if(strncmp_P(name+7, PSTR("Zurich"), 6) == 0)
|
||||
return &CentralEuropean;
|
||||
}
|
||||
return &GMT;
|
||||
}
|
||||
1
lib/ClassicUi/include/.gitignore
vendored
1
lib/ClassicUi/include/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
root/*.h
|
||||
1
lib/DomoticzMqttHandler/include/.gitignore
vendored
1
lib/DomoticzMqttHandler/include/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
json/*.h
|
||||
@@ -1,76 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/DomoticzMqttHandler/json"
|
||||
srcroot = "lib/DomoticzMqttHandler/include/json"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read().replace("${version}", version)
|
||||
|
||||
try:
|
||||
if (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#ifndef _PRICESCONTAINER_H
|
||||
#define _PRICESCONTAINER_H
|
||||
struct PricesContainer {
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
int32_t points[24];
|
||||
};
|
||||
#endif
|
||||
@@ -1,410 +0,0 @@
|
||||
#include "EntsoeApi.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Uptime.h"
|
||||
#include "TimeLib.h"
|
||||
#include "DnbCurrParser.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "GcmParser.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
|
||||
this->buf = (char*) malloc(BufferSize);
|
||||
|
||||
debugger = Debug;
|
||||
|
||||
// Entso-E uses CET/CEST
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
tz = new Timezone(CEST, CET);
|
||||
|
||||
tomorrowFetchMinute = 15 + random(45); // Random between 13:15 and 14:00
|
||||
}
|
||||
|
||||
void EntsoeApi::setup(EntsoeConfig& config) {
|
||||
if(this->config == NULL) {
|
||||
this->config = new EntsoeConfig();
|
||||
}
|
||||
memcpy(this->config, &config, sizeof(config));
|
||||
lastTodayFetch = lastTomorrowFetch = lastCurrencyFetch = 0;
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) delete tomorrow;
|
||||
today = tomorrow = NULL;
|
||||
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http.setReuse(false);
|
||||
http.setTimeout(60000);
|
||||
http.setUserAgent("ams2mqtt/" + String(VERSION));
|
||||
http.useHTTP10(true);
|
||||
|
||||
#if defined(AMS2MQTT_PRICE_KEY)
|
||||
key = new uint8_t[16] AMS2MQTT_PRICE_KEY;
|
||||
hub = true;
|
||||
#else
|
||||
hub = false;
|
||||
#endif
|
||||
#if defined(AMS2MQTT_PRICE_AUTHENTICATION)
|
||||
auth = new uint8_t[16] AMS2MQTT_PRICE_AUTHENTICATION;
|
||||
hub = hub && true;
|
||||
#else
|
||||
hub = false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
char* EntsoeApi::getToken() {
|
||||
return this->config->token;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getCurrency() {
|
||||
return this->config->currency;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getArea() {
|
||||
return this->config->area;
|
||||
}
|
||||
|
||||
float EntsoeApi::getValueForHour(int8_t hour) {
|
||||
time_t cur = time(nullptr);
|
||||
return getValueForHour(cur, hour);
|
||||
}
|
||||
|
||||
float EntsoeApi::getValueForHour(time_t cur, int8_t hour) {
|
||||
tmElements_t tm;
|
||||
if(tz != NULL)
|
||||
cur = tz->toLocal(cur);
|
||||
breakTime(cur, tm);
|
||||
int pos = tm.Hour + hour;
|
||||
if(pos >= 48)
|
||||
return ENTSOE_NO_VALUE;
|
||||
|
||||
double value = ENTSOE_NO_VALUE;
|
||||
double multiplier = config->multiplier / 1000.0;
|
||||
if(pos > 23) {
|
||||
if(tomorrow == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
if(tomorrow->points[pos-24] == ENTSOE_NO_VALUE)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = tomorrow->points[pos-24] / 10000.0;
|
||||
if(strcmp(tomorrow->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(tomorrow->currency, config->currency, cur);
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
} else if(pos >= 0) {
|
||||
if(today == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
if(today->points[pos] == ENTSOE_NO_VALUE)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = today->points[pos] / 10000.0;
|
||||
if(strcmp(today->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(today->measurementUnit, "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(today->currency, config->currency, cur);
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
}
|
||||
return value * multiplier;
|
||||
}
|
||||
|
||||
bool EntsoeApi::loop() {
|
||||
uint64_t now = millis64();
|
||||
if(now < 10000) return false; // Grace period
|
||||
|
||||
time_t t = time(nullptr);
|
||||
if(t < BUILD_EPOCH) return false;
|
||||
|
||||
#ifndef AMS2MQTT_PRICE_KEY
|
||||
if(strlen(getToken()) == 0) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if(!config->enabled)
|
||||
return false;
|
||||
if(strlen(config->area) == 0)
|
||||
return false;
|
||||
if(strlen(config->currency) == 0)
|
||||
return false;
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
|
||||
if(currentDay == 0) {
|
||||
currentDay = tm.Day;
|
||||
currentHour = tm.Hour;
|
||||
}
|
||||
|
||||
if(currentDay != tm.Day) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) {
|
||||
today = tomorrow;
|
||||
tomorrow = NULL;
|
||||
}
|
||||
currentDay = tm.Day;
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
} else if(currentHour != tm.Hour) {
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
|
||||
try {
|
||||
lastTodayFetch = now;
|
||||
today = fetchPrices(t);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
today = NULL;
|
||||
}
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
|
||||
// fetch with one hour (with some random delay) and retry every 15 minutes
|
||||
if(tomorrow == NULL && (tm.Hour > 13 || (tm.Hour == 13 && tm.Minute >= tomorrowFetchMinute)) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)) {
|
||||
try {
|
||||
lastTomorrowFetch = now;
|
||||
tomorrow = fetchPrices(t+SECS_PER_DAY);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) lastError = 900;
|
||||
tomorrow = NULL;
|
||||
}
|
||||
return tomorrow != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
#if defined(ESP32)
|
||||
if(http.begin(url)) {
|
||||
printD("Connection established");
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
int status = http.GET();
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(status == HTTP_CODE_OK) {
|
||||
printD("Receiving data");
|
||||
http.writeToStream(doc);
|
||||
http.end();
|
||||
lastError = 0;
|
||||
return true;
|
||||
} else {
|
||||
lastError = status;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Communication error, returned status: %d\n", status);
|
||||
printE(http.errorToString(status));
|
||||
printD(http.getString());
|
||||
|
||||
http.end();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to, time_t t) {
|
||||
if(strcmp(from, to) == 0)
|
||||
return 1.00;
|
||||
|
||||
uint64_t now = millis64();
|
||||
if(now > lastCurrencyFetch && (lastCurrencyFetch == 0 || (now - lastCurrencyFetch) > 60000)) {
|
||||
lastCurrencyFetch = now;
|
||||
|
||||
DnbCurrParser p;
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
snprintf(buf, BufferSize, "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", from);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", from);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
|
||||
if(retrieve(buf, &p)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
|
||||
currencyMultiplier = p.getValue();
|
||||
if(strncmp(to, "NOK", 3) != 0) {
|
||||
snprintf(buf, BufferSize, "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", to);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", to);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
|
||||
if(retrieve(buf, &p)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
|
||||
currencyMultiplier /= p.getValue();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Resulting currency multiplier: %.4f\n", currencyMultiplier);
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
|
||||
}
|
||||
return currencyMultiplier;
|
||||
}
|
||||
|
||||
PricesContainer* EntsoeApi::fetchPrices(time_t t) {
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
if(strlen(getToken()) > 0) {
|
||||
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // UTC midnight
|
||||
time_t e2 = e1 + SECS_PER_DAY;
|
||||
tmElements_t d1, d2;
|
||||
breakTime(tz->toUTC(e1), d1); // To get day and hour for CET/CEST at UTC midnight
|
||||
breakTime(tz->toUTC(e2), d2);
|
||||
|
||||
snprintf(buf, BufferSize, "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
|
||||
"https://transparency.entsoe.eu/api", getToken(),
|
||||
d1.Year+1970, d1.Month, d1.Day, d1.Hour, 00,
|
||||
d2.Year+1970, d2.Month, d2.Day, d2.Hour, 00,
|
||||
config->area, config->area);
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Fetching prices for %d.%d.%d\n", tm.Day, tm.Month, tm.Year+1970);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", buf);
|
||||
EntsoeA44Parser a44;
|
||||
if(retrieve(buf, &a44) && a44.getPoint(0) != ENTSOE_NO_VALUE) {
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
a44.get(ret);
|
||||
return ret;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
} else if(hub) {
|
||||
String data;
|
||||
snprintf(buf, BufferSize, "%s/%s/%d/%d/%d?currency=%s",
|
||||
"http://ams2mqtt.rewiredinvent.no/hub/price",
|
||||
config->area,
|
||||
tm.Year+1970,
|
||||
tm.Month,
|
||||
tm.Day,
|
||||
config->currency
|
||||
);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
if(http.begin(client, buf)) {
|
||||
#elif defined(ESP32)
|
||||
if(http.begin(buf)) {
|
||||
#endif
|
||||
int status = http.GET();
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(status == HTTP_CODE_OK) {
|
||||
printD("Receiving data");
|
||||
data = http.getString();
|
||||
http.end();
|
||||
lastError = 0;
|
||||
} else {
|
||||
lastError = status;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Communication error, returned status: %d\n", status);
|
||||
printE(http.errorToString(status));
|
||||
printD(http.getString());
|
||||
|
||||
http.end();
|
||||
}
|
||||
}
|
||||
uint8_t* content = (uint8_t*) (data.c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
printD("Received content for prices:");
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
|
||||
DataParserContext ctx;
|
||||
ctx.length = data.length();
|
||||
GCMParser gcm(key, auth);
|
||||
int8_t gcmRet = gcm.parse(content, ctx);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
printD("Decrypted content for prices:");
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
if(gcmRet > 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Price data starting at: %d\n", gcmRet);
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
memcpy(ret, content+gcmRet, sizeof(*ret));
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
ret->points[i] = ntohl(ret->points[i]);
|
||||
}
|
||||
lastError = 0;
|
||||
return ret;
|
||||
} else {
|
||||
lastError = gcmRet;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("(EntsoeApi) Error code while decrypting prices: %d\n", gcmRet);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void EntsoeApi::printD(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void EntsoeApi::printE(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void EntsoeApi::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
debugger->print("0");
|
||||
debugger->print(buffer[i], HEX);
|
||||
debugger->print(" ");
|
||||
if ((i - start + 1) % 16 == 0)
|
||||
debugger->println("");
|
||||
else if ((i - start + 1) % 4 == 0)
|
||||
debugger->print(" ");
|
||||
|
||||
yield(); // Let other get some resources too
|
||||
}
|
||||
debugger->println("");
|
||||
}
|
||||
|
||||
int16_t EntsoeApi::getLastError() {
|
||||
return lastError;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
json/*.h
|
||||
@@ -1,76 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/HomeAssistantMqttHandler/json"
|
||||
srcroot = "lib/HomeAssistantMqttHandler/include/json"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read().replace("${version}", version)
|
||||
|
||||
try:
|
||||
if (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
1
lib/JsonMqttHandler/include/.gitignore
vendored
1
lib/JsonMqttHandler/include/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
json/*.h
|
||||
@@ -1,76 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/JsonMqttHandler/json"
|
||||
srcroot = "lib/JsonMqttHandler/include/json"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read().replace("${version}", version)
|
||||
|
||||
try:
|
||||
if (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
24
lib/SvelteUi/app/.gitignore
vendored
24
lib/SvelteUi/app/.gitignore
vendored
@@ -1,24 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||
<title>AMS reader</title>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Node",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"importsNotUsedAsValues": "error",
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": true
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// HTTPS required for this to work
|
||||
// Remember: <link rel="manifest" href="manifest.json" />
|
||||
{
|
||||
"short_name": "amsreader",
|
||||
"name": "AMS reader",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"background_color": "#f3f4f6",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"theme_color": "#7c3aed"
|
||||
}
|
||||
4420
lib/SvelteUi/app/package-lock.json
generated
4420
lib/SvelteUi/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "svelte-gui",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"http-proxy-middleware": "^2.0.1",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-navigator": "^3.2.2",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tailwindcss": "^3.1.5",
|
||||
"vite": "^3.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "^5.1.14"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const tailwindcss = require("tailwindcss");
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const cssnano = require("cssnano");
|
||||
|
||||
const config = {
|
||||
plugins: [
|
||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
||||
tailwindcss(),
|
||||
//But others, like autoprefixer, need to run after,
|
||||
autoprefixer,
|
||||
cssnano()
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,11 +0,0 @@
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
return self.clients.claim();
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function(event) {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
<script>
|
||||
import { Router, Route, navigate } from "svelte-navigator";
|
||||
import { getSysinfo, sysinfoStore, dataStore } from './lib/DataStores.js';
|
||||
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 Mask from './lib/Mask.svelte';
|
||||
import FileUploadComponent from "./lib/FileUploadComponent.svelte";
|
||||
import ConsentComponent from "./lib/ConsentComponent.svelte";
|
||||
|
||||
let sysinfo = {};
|
||||
sysinfoStore.subscribe(update => {
|
||||
sysinfo = update;
|
||||
if(sysinfo.vndcfg === false) {
|
||||
navigate("/vendor");
|
||||
} else if(sysinfo.usrcfg === false) {
|
||||
navigate("/setup");
|
||||
} else if(sysinfo.fwconsent === 0) {
|
||||
navigate("/consent");
|
||||
}
|
||||
});
|
||||
getSysinfo();
|
||||
let data = {};
|
||||
dataStore.subscribe(update => {
|
||||
data = update;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto m-3">
|
||||
<Router>
|
||||
<Header data={data}/>
|
||||
<Route path="/">
|
||||
<Dashboard data={data} sysinfo={sysinfo}/>
|
||||
</Route>
|
||||
<Route path="/configuration">
|
||||
<ConfigurationPanel sysinfo={sysinfo}/>
|
||||
</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}/>
|
||||
</Route>
|
||||
<Route path="/setup">
|
||||
<SetupPanel sysinfo={sysinfo}/>
|
||||
</Route>
|
||||
<Route path="/vendor">
|
||||
<VendorPanel sysinfo={sysinfo}/>
|
||||
</Route>
|
||||
</Router>
|
||||
|
||||
{#if sysinfo.upgrading}
|
||||
<Mask active=true message="Device is upgrading, please wait"/>
|
||||
{:else if sysinfo.booting}
|
||||
<Mask active=true message="Device is booting, please wait"/>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,152 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.gh-logo {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.cnt {
|
||||
@apply bg-white m-2 p-2 rounded shadow-lg
|
||||
}
|
||||
|
||||
.in-pre {
|
||||
@apply flex items-center bg-gray-100 rounded-l-md border border-r-0 border-gray-300 px-3 whitespace-nowrap text-sm
|
||||
}
|
||||
|
||||
.in-post {
|
||||
@apply flex items-center bg-gray-100 rounded-r-md border border-l-0 border-gray-300 px-3 whitespace-nowrap text-sm
|
||||
}
|
||||
|
||||
.in-txt {
|
||||
@apply h-10 shadow-sm border-gray-300 disabled:bg-gray-200
|
||||
}
|
||||
.in-f {
|
||||
@apply in-txt rounded-l-md
|
||||
}
|
||||
.in-m {
|
||||
@apply in-txt border-l-0
|
||||
}
|
||||
.in-l {
|
||||
@apply in-txt border-l-0 rounded-r-md
|
||||
}
|
||||
.in-s {
|
||||
@apply in-txt rounded-md w-full
|
||||
}
|
||||
.tr {
|
||||
@apply text-right
|
||||
}
|
||||
|
||||
.bd-grn {
|
||||
@apply my-auto bg-green-500 text-green-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded
|
||||
}
|
||||
.bd-ylo {
|
||||
@apply my-auto bg-yellow-500 text-yellow-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded
|
||||
}
|
||||
.bd-red {
|
||||
@apply my-auto bg-red-500 text-red-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded
|
||||
}
|
||||
.bd-blu {
|
||||
@apply my-auto bg-blue-500 text-blue-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded
|
||||
}
|
||||
.bd-gry {
|
||||
@apply my-auto bg-gray-500 text-gray-100 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded
|
||||
}
|
||||
|
||||
.btn-pri {
|
||||
@apply py-2 px-4 rounded bg-blue-500 text-white mr-3
|
||||
}
|
||||
|
||||
.pl-root {
|
||||
position: relative;
|
||||
}
|
||||
.pl-ov {
|
||||
position: absolute;
|
||||
top: 27%;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
.pl-val {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
.pl-unt {
|
||||
font-size: 1.0rem;
|
||||
color: grey;
|
||||
}
|
||||
.pl-sub {
|
||||
padding-top: 10px;
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
.pl-snt {
|
||||
font-size: 0.7rem;
|
||||
color: grey;
|
||||
}
|
||||
.pl-lab {
|
||||
font-size: 1.0rem;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tick {
|
||||
font-family: Helvetica, Arial;
|
||||
font-size: 0.85em;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.tick line {
|
||||
stroke: #e2e2e2;
|
||||
stroke-dasharray: 2;
|
||||
}
|
||||
|
||||
.tick text {
|
||||
fill: #999;
|
||||
text-anchor: start;
|
||||
}
|
||||
|
||||
.tick.tick-0 line {
|
||||
stroke-dasharray: 0;
|
||||
}
|
||||
|
||||
.tick.tick-green line {
|
||||
stroke: #32d900 !important;
|
||||
}
|
||||
|
||||
.tick.tick-green text {
|
||||
fill: #32d900 !important;
|
||||
}
|
||||
|
||||
.tick.tick-orange line {
|
||||
stroke: #d95600 !important;
|
||||
}
|
||||
|
||||
.tick.tick-orange text {
|
||||
fill: #d95600 !important;
|
||||
}
|
||||
|
||||
.x-axis .tick text {
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
.bars rect {
|
||||
stroke: rgb(0,0,0);
|
||||
stroke-opacity: 0.25;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.bars text {
|
||||
font-family: Helvetica, Arial;
|
||||
font-size: 0.85em;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,6 +0,0 @@
|
||||
<?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 512 499.36" focusable="false">
|
||||
<title>GitHub</title>
|
||||
<path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="#f8f9fa" fill-rule="evenodd"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,54 +0,0 @@
|
||||
<script>
|
||||
export let data;
|
||||
export let currency;
|
||||
|
||||
let hasExport = data && (data.om || data.e > 0);
|
||||
</script>
|
||||
|
||||
<div class="mx-2 text-sm">
|
||||
<strong>Real time calculation</strong>
|
||||
|
||||
{#if data && data.h !== undefined}
|
||||
<div class="flex">
|
||||
<div>Hour</div>
|
||||
<div class="flex-auto text-right">{data.h.u ? data.h.u.toFixed(2) : '-'} kWh {#if currency && (hasExport)}/ {data.h.c ? data.h.c.toFixed(2) : '-'} {currency}{/if}</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>Day</div>
|
||||
<div class="flex-auto text-right">{data.d.u ? data.d.u.toFixed(1) : '-'} kWh {#if currency && (hasExport)}/ {data.d.c ? data.d.c.toFixed(2) : '-'} {currency}{/if}</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>Month</div>
|
||||
<div class="flex-auto text-right">{data.m.u ? data.m.u.toFixed(0) : '-'} kWh {#if currency && (hasExport)}/ {data.m.c ? data.m.c.toFixed(2) : '-'} {currency}{/if}</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
{#if hasExport}
|
||||
<div class="flex">
|
||||
<div>Hour</div>
|
||||
<div class="flex-auto text-right">{data.h.p ? data.h.p.toFixed(2) : '-'} kWh {#if currency}/ {data.h.i ? data.h.i.toFixed(2) : '-'} {currency}{/if}</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>Day</div>
|
||||
<div class="flex-auto text-right">{data.d.p ? data.d.p.toFixed(1) : '-'} kWh {#if currency}/ {data.d.i ? data.d.i.toFixed(2) : '-'} {currency}{/if}</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>Month</div>
|
||||
<div class="flex-auto text-right">{data.m.p ? data.m.p.toFixed(0) : '-'} kWh {#if currency}/ {data.m.i ? data.m.i.toFixed(2) : '-'} {currency}{/if}</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex">
|
||||
<div>Hour</div>
|
||||
<div class="flex-auto text-right">{data.h.c ? data.h.c.toFixed(2) : '-'} {currency}</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>Day</div>
|
||||
<div class="flex-auto text-right">{data.d.c ? data.d.c.toFixed(2) : '-'} {currency}</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div>Month</div>
|
||||
<div class="flex-auto text-right">{data.m.c ? data.m.c.toFixed(2) : '-'} {currency}</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,62 +0,0 @@
|
||||
<script>
|
||||
import BarChart from './BarChart.svelte';
|
||||
import { ampcol } from './Helpers.js';
|
||||
|
||||
export let u1;
|
||||
export let u2;
|
||||
export let u3;
|
||||
export let i1;
|
||||
export let i2;
|
||||
export let i3;
|
||||
export let max;
|
||||
|
||||
let config = {};
|
||||
|
||||
$: {
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
if(u1 > 0) {
|
||||
xTicks.push({ label: 'L1' });
|
||||
points.push({
|
||||
label: i1 ? (i1 > 10 ? i1.toFixed(0) : i1.toFixed(1)) + 'A' : '-',
|
||||
value: i1 ? i1 : 0,
|
||||
color: ampcol(i1 ? (i1)/(max)*100 : 0)
|
||||
});
|
||||
}
|
||||
if(u2 > 0) {
|
||||
xTicks.push({ label: 'L2' });
|
||||
points.push({
|
||||
label: i2 ? (i2 > 10 ? i2.toFixed(0) : i2.toFixed(1)) + 'A' : '-',
|
||||
value: i2 ? i2 : 0,
|
||||
color: ampcol(i2 ? (i2)/(max)*100 : 0)
|
||||
});
|
||||
}
|
||||
if(u3 > 0) {
|
||||
xTicks.push({ label: 'L3' });
|
||||
points.push({
|
||||
label: i3 ? (i3 > 10 ? i3.toFixed(0) : i3.toFixed(1)) + 'A' : '-',
|
||||
value: i3 ? i3 : 0,
|
||||
color: ampcol(i3 ? (i3)/(max)*100 : 0)
|
||||
});
|
||||
}
|
||||
config = {
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
y: {
|
||||
min: 0,
|
||||
max: max,
|
||||
ticks: [
|
||||
{ value: 0, label: '0%' },
|
||||
{ value: max/4, label: '25%' },
|
||||
{ value: max/2, label: '50%' },
|
||||
{ value: (max/4)*3, label: '75%' },
|
||||
{ value: max, label: '100%' }
|
||||
]
|
||||
},
|
||||
x: {
|
||||
ticks: xTicks
|
||||
},
|
||||
points: points
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<BarChart config={config} />
|
||||
@@ -1,16 +0,0 @@
|
||||
<script>
|
||||
export let color;
|
||||
export let title;
|
||||
export let text;
|
||||
</script>
|
||||
{#if color == 'green'}
|
||||
<span title={title} class="bd-grn">{text}</span>
|
||||
{:else if color === `yellow`}
|
||||
<span title={title} class="bd-ylo">{text}</span>
|
||||
{:else if color === `red`}
|
||||
<span title={title} class="bd-red">{text}</span>
|
||||
{:else if color === `blue`}
|
||||
<span title={title} class="bd-blu">{text}</span>
|
||||
{:else if color === `gray`}
|
||||
<span title={title} class="bd-gry">{text}</span>
|
||||
{/if}
|
||||
@@ -1,106 +0,0 @@
|
||||
<script>
|
||||
export let config;
|
||||
|
||||
let width;
|
||||
let height;
|
||||
let barWidth;
|
||||
let xScale;
|
||||
let yScale;
|
||||
let heightAvailable;
|
||||
let labelOffset;
|
||||
|
||||
$: {
|
||||
heightAvailable = height-(config.title ? 20 : 0);
|
||||
let innerWidth = width - (config.padding.left + config.padding.right);
|
||||
barWidth = innerWidth / config.points.length;
|
||||
labelOffset = barWidth < 25 ? 28 : 17;
|
||||
|
||||
let yPerUnit = (heightAvailable-config.padding.top-config.padding.bottom)/(config.y.max-config.y.min);
|
||||
|
||||
xScale = function(i) {
|
||||
return (i*barWidth)+config.padding.left;
|
||||
};
|
||||
yScale = function(i) {
|
||||
let ret = 0;
|
||||
if(i > config.y.max)
|
||||
ret = config.padding.bottom;
|
||||
else if(i < config.y.min)
|
||||
ret = heightAvailable-config.padding.bottom;
|
||||
else
|
||||
ret = heightAvailable-config.padding.bottom-((i-config.y.min)*yPerUnit);
|
||||
return ret > heightAvailable || ret < 0.0 ? 0.0 : ret;
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="chart" bind:clientWidth={width} bind:clientHeight={height}>
|
||||
{#if config.title}
|
||||
<strong class="text-sm">{config.title}</strong>
|
||||
{/if}
|
||||
<svg height="{heightAvailable}">
|
||||
<!-- y axis -->
|
||||
<g class="axis y-axis">
|
||||
{#each config.y.ticks as tick}
|
||||
<g class="tick tick-{tick.value} tick-{tick.color}" transform="translate(0, {yScale(tick.value)})">
|
||||
<line x2="100%"></line>
|
||||
<text y="-4" x={tick.align == 'right' ? '85%' : ''}>{tick.label}</text>
|
||||
</g>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<!-- x axis -->
|
||||
<g class="axis x-axis">
|
||||
{#each config.x.ticks as point, i}
|
||||
<g class="tick" transform="translate({xScale(i)},{heightAvailable})">
|
||||
<text x="{barWidth/2}" y="-4">{point.label}</text>
|
||||
</g>
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<g class='bars'>
|
||||
{#each config.points as point, i}
|
||||
{#if point.value !== undefined}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(point.value)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(Math.min(config.y.min, 0) + point.value)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(point.value) > yScale(0)-labelOffset ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value) > yScale(0)-labelOffset ? yScale(point.value) - labelOffset : yScale(point.value)+9})"
|
||||
>{point.label}</text>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if point.value2 > 0.0001}
|
||||
<rect
|
||||
x="{xScale(i) + 2}"
|
||||
y="{yScale(0)}"
|
||||
width="{barWidth - 4}"
|
||||
height="{yScale(config.y.min) - yScale(config.y.min + point.value2)}"
|
||||
fill="{point.color}"
|
||||
/>
|
||||
{#if barWidth > 15}
|
||||
<text
|
||||
y="{yScale(-point.value2) < yScale(0)+12 ? yScale(-point.value2) + 12 : yScale(-point.value2)-10}"
|
||||
x="{xScale(i) + barWidth/2}"
|
||||
width="{barWidth - 4}"
|
||||
dominant-baseline="middle"
|
||||
text-anchor="{barWidth < 25 ? 'left' : 'middle'}"
|
||||
fill="{yScale(-point.value2) < yScale(0)+12 ? point.color : 'white'}"
|
||||
transform="rotate({barWidth < 25 ? 90 : 0}, {xScale(i) + (barWidth/2)}, {yScale(point.value2 - config.y.min) > yScale(0)-12 ? yScale(point.value2 - config.y.min) - 12 : yScale(point.value2 - config.y.min)+9})"
|
||||
>{point.label2}</text>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -1,48 +0,0 @@
|
||||
<script>
|
||||
import {boardtype} from './Helpers.js'
|
||||
|
||||
export let chip;
|
||||
</script>
|
||||
|
||||
<option value={-1}></option>
|
||||
{#if chip == 'esp8266'}
|
||||
<optgroup label="amsleser.no">
|
||||
<option value={7}>{boardtype(chip, 7)}</option>
|
||||
<option value={5}>{boardtype(chip, 5)}</option>
|
||||
<option value={4}>{boardtype(chip, 4)}</option>
|
||||
<option value={3}>{boardtype(chip, 3)}</option>
|
||||
</optgroup>
|
||||
<optgroup label="Custom hardware">
|
||||
<option value={2}>{boardtype(chip, 2)}</option>
|
||||
<option value={1}>{boardtype(chip, 1)}</option>
|
||||
<option value={0}>{boardtype(chip, 0)}</option>
|
||||
</optgroup>
|
||||
<optgroup label="Generic hardware">
|
||||
<option value={101}>{boardtype(chip, 101)}</option>
|
||||
<option value={100}>{boardtype(chip, 100)}</option>
|
||||
</optgroup>
|
||||
{/if}
|
||||
{#if chip == 'esp32'}
|
||||
<optgroup label="Generic hardware">
|
||||
<option value={201}>{boardtype(chip, 201)}</option>
|
||||
<option value={202}>{boardtype(chip, 202)}</option>
|
||||
<option value={203}>{boardtype(chip, 203)}</option>
|
||||
<option value={200}>{boardtype(chip, 200)}</option>
|
||||
</optgroup>
|
||||
{/if}
|
||||
{#if chip == 'esp32s2'}
|
||||
<optgroup label="amsleser.no">
|
||||
<option value={7}>{boardtype(chip, 7)}</option>
|
||||
<option value={6}>{boardtype(chip, 6)}</option>
|
||||
<option value={5}>{boardtype(chip, 5)}</option>
|
||||
</optgroup>
|
||||
<optgroup label="Generic hardware">
|
||||
<option value={51}>{boardtype(chip, 51)}</option>
|
||||
<option value={50}>{boardtype(chip, 50)}</option>
|
||||
</optgroup>
|
||||
{/if}
|
||||
{#if chip == 'esp32solo'}
|
||||
<optgroup label="Generic hardware">
|
||||
<option value={200}>{boardtype(chip, 200)}</option>
|
||||
</optgroup>
|
||||
{/if}
|
||||
@@ -1,11 +0,0 @@
|
||||
<script>
|
||||
import { zeropad, monthnames } from './Helpers.js';
|
||||
|
||||
export let timestamp;
|
||||
</script>
|
||||
|
||||
{#if Math.abs(new Date().getTime()-timestamp.getTime()) < 300000 }
|
||||
{`${zeropad(timestamp.getDate())}. ${monthnames[timestamp.getMonth()]} ${zeropad(timestamp.getHours())}:${zeropad(timestamp.getMinutes())}`}
|
||||
{:else}
|
||||
<span class="text-red-500">{`${zeropad(timestamp.getDate())}.${zeropad(timestamp.getMonth())}.${timestamp.getFullYear()} ${zeropad(timestamp.getHours())}:${zeropad(timestamp.getMinutes())}`}</span>
|
||||
{/if}
|
||||
@@ -1,767 +0,0 @@
|
||||
<script>
|
||||
import { getConfiguration, configurationStore } from './ConfigurationStore'
|
||||
import { sysinfoStore } from './DataStores.js';
|
||||
import UartSelectOptions from './UartSelectOptions.svelte';
|
||||
import Mask from './Mask.svelte'
|
||||
import Badge from './Badge.svelte';
|
||||
import HelpIcon from './HelpIcon.svelte';
|
||||
import CountrySelectOptions from './CountrySelectOptions.svelte';
|
||||
import { Link, navigate } from 'svelte-navigator';
|
||||
|
||||
|
||||
export let sysinfo = {}
|
||||
|
||||
let loading = true;
|
||||
let saving = false;
|
||||
|
||||
let configuration = {
|
||||
g: {
|
||||
t: '', h: '', s: 0, u: '', p: ''
|
||||
},
|
||||
m: {
|
||||
b: 2400, p: 11, i: false, d: 0, f: 0, r: 0,
|
||||
e: { e: false, k: '', a: '' },
|
||||
m: { e: false, w: false, v: false, a: false, c: false }
|
||||
},
|
||||
w: { s: '', p: '', w: 0.0, z: 255, a: true },
|
||||
n: {
|
||||
m: '', i: '', s: '', g: '', d1: '', d2: '', d: false, n1: '', n2: '', h: false
|
||||
},
|
||||
q: {
|
||||
h: '', p: 1883, u: '', a: '', b: '',
|
||||
s: { e: false, c: false, r: true, k: false }
|
||||
},
|
||||
o: {
|
||||
e: '',
|
||||
c: '',
|
||||
u1: '',
|
||||
u2: '',
|
||||
u3: ''
|
||||
},
|
||||
t: {
|
||||
t: [0,0,0,0,0,0,0,0,0,0], h: 1
|
||||
},
|
||||
p: {
|
||||
e: false, t: '', r: '', c: '', m: 1.0
|
||||
},
|
||||
d: {
|
||||
s: false, t: false, l: 5
|
||||
},
|
||||
u: {
|
||||
i: 0, e: 0, v: 0, a: 0, r: 0, c: 0, t: 0, p: 0, d: 0, m: 0, s: 0
|
||||
},
|
||||
i: {
|
||||
h: null, a: null,
|
||||
l: { p: null, i: false },
|
||||
r: { r: null, g: null, b: null, i: false },
|
||||
t: { d: null, a: null },
|
||||
v: { p: null, d: { v: null, g: null }, o: null, m: null, b: null }
|
||||
}
|
||||
};
|
||||
configurationStore.subscribe(update => {
|
||||
if(update.version) {
|
||||
configuration = update;
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
getConfiguration();
|
||||
|
||||
let isFactoryReset = false;
|
||||
async function factoryReset() {
|
||||
if(confirm("Are you sure you want to factory reset the device?")) {
|
||||
const data = new URLSearchParams();
|
||||
data.append("perform", "true");
|
||||
const response = await fetch('/reset', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
let res = (await response.json());
|
||||
isFactoryReset = res.success;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(e) {
|
||||
saving = true;
|
||||
const formData = new FormData(e.target);
|
||||
const data = new URLSearchParams();
|
||||
for (let field of formData) {
|
||||
const [key, value] = field
|
||||
data.append(key, value)
|
||||
}
|
||||
|
||||
const response = await fetch('/save', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
let res = (await response.json())
|
||||
|
||||
sysinfoStore.update(s => {
|
||||
s.booting = res.reboot;
|
||||
s.ui = configuration.u;
|
||||
return s;
|
||||
});
|
||||
|
||||
saving = false;
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
async function reboot() {
|
||||
const response = await fetch('/reboot', {
|
||||
method: 'POST'
|
||||
});
|
||||
let res = (await response.json())
|
||||
}
|
||||
|
||||
const askReboot = function() {
|
||||
if(confirm('Are you sure you want to reboot the device?')) {
|
||||
sysinfoStore.update(s => {
|
||||
s.booting = true;
|
||||
return s;
|
||||
});
|
||||
reboot();
|
||||
}
|
||||
}
|
||||
|
||||
const updateMqttPort = function() {
|
||||
if(configuration.q.s.e) {
|
||||
if(configuration.q.p == 1883) configuration.q.p = 8883;
|
||||
} else {
|
||||
if(configuration.q.p == 8883) configuration.q.p = 1883;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">General</strong>
|
||||
<input type="hidden" name="g" value="true"/>
|
||||
<div class="my-1">
|
||||
<div class="flex">
|
||||
<div>
|
||||
Hostname<br/>
|
||||
<input name="gh" bind:value={configuration.g.h} type="text" class="in-f w-full"/>
|
||||
</div>
|
||||
<div>
|
||||
Time zone<br/>
|
||||
<select name="gt" bind:value={configuration.g.t} class="in-l">
|
||||
<CountrySelectOptions/>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="p" value="true"/>
|
||||
<div class="my-1">
|
||||
Price region<br/>
|
||||
<select name="pr" bind:value={configuration.p.r} class="in-s">
|
||||
<optgroup label="Norway">
|
||||
<option value="10YNO-1--------2">NO1</option>
|
||||
<option value="10YNO-2--------T">NO2</option>
|
||||
<option value="10YNO-3--------J">NO3</option>
|
||||
<option value="10YNO-4--------9">NO4</option>
|
||||
<option value="10Y1001A1001A48H">NO5</option>
|
||||
</optgroup>
|
||||
<optgroup label="Sweden">
|
||||
<option value="10Y1001A1001A44P">SE1</option>
|
||||
<option value="10Y1001A1001A45N">SE2</option>
|
||||
<option value="10Y1001A1001A46L">SE3</option>
|
||||
<option value="10Y1001A1001A47J">SE4</option>
|
||||
</optgroup>
|
||||
<optgroup label="Denmark">
|
||||
<option value="10YDK-1--------W">DK1</option>
|
||||
<option value="10YDK-2--------M">DK2</option>
|
||||
</optgroup>
|
||||
<option value="10YAT-APG------L">Austria</option>
|
||||
<option value="10YBE----------2">Belgium</option>
|
||||
<option value="10YCZ-CEPS-----N">Czech Republic</option>
|
||||
<option value="10Y1001A1001A39I">Estonia</option>
|
||||
<option value="10YFI-1--------U">Finland</option>
|
||||
<option value="10YFR-RTE------C">France</option>
|
||||
<option value="10Y1001A1001A83F">Germany</option>
|
||||
<option value="10YGB----------A">Great Britain</option>
|
||||
<option value="10YLV-1001A00074">Latvia</option>
|
||||
<option value="10YLT-1001A0008Q">Lithuania</option>
|
||||
<option value="10YNL----------L">Netherland</option>
|
||||
<option value="10YPL-AREA-----S">Poland</option>
|
||||
<option value="10YCH-SWISSGRIDZ">Switzerland</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
<div class="flex">
|
||||
<div class="w-1/2">
|
||||
Currency<br/>
|
||||
<select name="pc" bind:value={configuration.p.c} class="in-f w-full">
|
||||
<option value="NOK">NOK</option>
|
||||
<option value="SEK">SEK</option>
|
||||
<option value="DKK">DKK</option>
|
||||
<option value="EUR">EUR</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Multiplier<br/>
|
||||
<input name="pm" bind:value={configuration.p.m} type="number" min="0.001" max="1000" step="0.001" class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
<label><input type="checkbox" name="pe" value="true" bind:checked={configuration.p.e} class="rounded mb-1"/> Enable price fetch from remote server</label>
|
||||
{#if configuration.p.e && sysinfo.chip != 'esp8266'}
|
||||
<br/><input name="pt" bind:value={configuration.p.t} type="text" class="in-s" placeholder="ENTSO-E API key, optional, read docs"/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Security<br/>
|
||||
<select name="gs" bind:value={configuration.g.s} class="in-s">
|
||||
<option value={0}>None</option>
|
||||
<option value={1}>Only configuration</option>
|
||||
<option value={2}>Everything</option>
|
||||
</select>
|
||||
</div>
|
||||
{#if configuration.g.s > 0}
|
||||
<div class="my-1">
|
||||
Username<br/>
|
||||
<input name="gu" bind:value={configuration.g.u} type="text" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Password<br/>
|
||||
<input name="gp" bind:value={configuration.g.p} type="password" class="in-s"/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Meter</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/Meter-configuration" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="m" value="true"/>
|
||||
<div class="my-1">
|
||||
<span>Serial configuration</span>
|
||||
<div class="flex">
|
||||
<select name="mb" bind:value={configuration.m.b} class="in-f">
|
||||
<option value={0} disabled={configuration.m.b != 0}>Autodetect</option>
|
||||
<option value={2400}>2400</option>
|
||||
<option value={4800}>4800</option>
|
||||
<option value={9600}>9600</option>
|
||||
<option value={19200}>19200</option>
|
||||
<option value={38400}>38400</option>
|
||||
<option value={57600}>57600</option>
|
||||
<option value={115200}>115200</option>
|
||||
</select>
|
||||
<select name="mp" bind:value={configuration.m.p} class="in-l" disabled={configuration.m.b == 0}>
|
||||
<option value={0} disabled={configuration.m.b != 0}>-</option>
|
||||
<option value={2}>7N1</option>
|
||||
<option value={3}>8N1</option>
|
||||
<option value={10}>7E1</option>
|
||||
<option value={11}>8E1</option>
|
||||
</select>
|
||||
<label class="mt-2 ml-3 whitespace-nowrap"><input name="mi" value="true" bind:checked={configuration.m.i} type="checkbox" class="rounded mb-1"/> inverted</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-1">
|
||||
Voltage<br/>
|
||||
<select name="md" bind:value={configuration.m.d} class="in-s">
|
||||
<option value={0}></option>
|
||||
<option value={1}>230V (IT/TT)</option>
|
||||
<option value={2}>400V (TN)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="my-1 flex">
|
||||
<div class="mx-1">
|
||||
Main fuse<br/>
|
||||
<label class="flex">
|
||||
<input name="mf" bind:value={configuration.m.f} type="number" min="5" max="65535" class="in-f tr w-full"/>
|
||||
<span class="in-post">A</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mx-1">
|
||||
Production<br/>
|
||||
<label class="flex">
|
||||
<input name="mr" bind:value={configuration.m.r} type="number" min="0" max="65535" class="in-f tr w-full"/>
|
||||
<span class="in-post">kWp</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
</div>
|
||||
|
||||
<div class="my-1">
|
||||
<label><input type="checkbox" name="me" value="true" bind:checked={configuration.m.e.e} class="rounded mb-1"/> Meter is encrypted</label>
|
||||
{#if configuration.m.e.e}
|
||||
<br/><input name="mek" bind:value={configuration.m.e.k} type="text" class="in-s"/>
|
||||
{/if}
|
||||
</div>
|
||||
{#if configuration.m.e.e}
|
||||
<div class="my-1">
|
||||
Authentication key<br/>
|
||||
<input name="mea" bind:value={configuration.m.e.a} type="text" class="in-s"/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<label><input type="checkbox" name="mm" value="true" bind:checked={configuration.m.m.e} class="rounded mb-1"/> Multipliers</label>
|
||||
{#if configuration.m.m.e}
|
||||
<div class="flex my-1">
|
||||
<div class="w-1/4">
|
||||
Watt<br/>
|
||||
<input name="mmw" bind:value={configuration.m.m.w} type="number" min="0.00" max="655.35" step="0.01" class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
Volt<br/>
|
||||
<input name="mmv" bind:value={configuration.m.m.v} type="number" min="0.00" max="655.35" step="0.01" class="in-m tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
Amp<br/>
|
||||
<input name="mma" bind:value={configuration.m.m.a} type="number" min="0.00" max="655.35" step="0.01" class="in-m tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/4">
|
||||
kWh<br/>
|
||||
<input name="mmc" bind:value={configuration.m.m.c} type="number" min="0.00" max="655.35" step="0.01" class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">WiFi</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/WiFi-configuration" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="w" value="true"/>
|
||||
<div class="my-1">
|
||||
SSID<br/>
|
||||
<input name="ws" bind:value={configuration.w.s} type="text" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Password<br/>
|
||||
<input name="wp" bind:value={configuration.w.p} type="password" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1 flex">
|
||||
<div class="w-1/2">
|
||||
Power saving<br/>
|
||||
<select name="wz" bind:value={configuration.w.z} class="in-s">
|
||||
<option value={255}>Default</option>
|
||||
<option value={0}>Off</option>
|
||||
<option value={1}>Minimum</option>
|
||||
<option value={2}>Maximum</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ml-2 w-1/2">
|
||||
Power<br/>
|
||||
<div class="flex">
|
||||
<input name="ww" bind:value={configuration.w.w} type="number" min="0" max="20.5" step="0.5" class="in-f tr w-full"/>
|
||||
<span class="in-post">dBm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label><input type="checkbox" name="wa" value="true" bind:checked={configuration.w.a} class="rounded mb-1"/> Auto reboot on connection problem</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Network</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/Network-configuration" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<div class="my-1">
|
||||
IP<br/>
|
||||
<div class="flex">
|
||||
<select name="nm" bind:value={configuration.n.m} class="in-f">
|
||||
<option value="dhcp">DHCP</option>
|
||||
<option value="static">Static</option>
|
||||
</select>
|
||||
<input name="ni" bind:value={configuration.n.i} type="text" class="in-m w-full" disabled={configuration.n.m == 'dhcp'}/>
|
||||
<select name="ns" bind:value={configuration.n.s} class="in-l" disabled={configuration.n.m == 'dhcp'}>
|
||||
<option value="255.255.255.0">/24</option>
|
||||
<option value="255.255.0.0">/16</option>
|
||||
<option value="255.0.0.0">/8</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if configuration.n.m == 'static'}
|
||||
<div class="my-1">
|
||||
Gateway<br/>
|
||||
<input name="ng" bind:value={configuration.n.g} type="text" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
DNS<br/>
|
||||
<div class="flex">
|
||||
<input name="nd1" bind:value={configuration.n.d1} type="text" class="in-f w-full"/>
|
||||
<input name="nd2" bind:value={configuration.n.d2} type="text" class="in-l w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-1">
|
||||
<label><input name="nd" value="true" bind:checked={configuration.n.d} type="checkbox" class="rounded mb-1"/> enable mDNS</label>
|
||||
</div>
|
||||
<input type="hidden" name="ntp" value="true"/>
|
||||
<div class="my-1">
|
||||
NTP <label class="ml-4"><input name="ntpd" value="true" bind:checked={configuration.n.h} type="checkbox" class="rounded mb-1"/> obtain from DHCP</label><br/>
|
||||
<div class="flex">
|
||||
<input name="ntph" bind:value={configuration.n.n1} type="text" class="in-s"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">MQTT</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/MQTT-configuration" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="q" value="true"/>
|
||||
<div class="my-1">
|
||||
Server
|
||||
{#if sysinfo.chip != 'esp8266'}
|
||||
<label class="float-right mr-3"><input type="checkbox" name="qs" value="true" bind:checked={configuration.q.s.e} class="rounded mb-1" on:change={updateMqttPort}/> SSL</label>
|
||||
{/if}
|
||||
<br/>
|
||||
<div class="flex">
|
||||
<input name="qh" bind:value={configuration.q.h} type="text" class="in-f w-3/4"/>
|
||||
<input name="qp" bind:value={configuration.q.p} type="number" min="1024" max="65535" class="in-l tr w-1/4"/>
|
||||
</div>
|
||||
</div>
|
||||
{#if configuration.q.s.e}
|
||||
<div class="my-1">
|
||||
<div>
|
||||
<Link to="/mqtt-ca">
|
||||
{#if configuration.q.s.c}
|
||||
<Badge color="green" text="CA OK" title="Click here to replace CA"/>
|
||||
{:else}
|
||||
<Badge color="blue" text="Upload CA" title="Click here to upload CA"/>
|
||||
{/if}
|
||||
</Link>
|
||||
|
||||
<Link to="/mqtt-cert">
|
||||
{#if configuration.q.s.r}
|
||||
<Badge color="green" text="Cert OK" title="Click here to replace certificate"/>
|
||||
{:else}
|
||||
<Badge color="blue" text="Upload cert" title="Click here to upload certificate"/>
|
||||
{/if}
|
||||
</Link>
|
||||
|
||||
<Link to="/mqtt-key">
|
||||
{#if configuration.q.s.k}
|
||||
<Badge color="green" text="Key OK" title="Click here to replace key"/>
|
||||
{:else}
|
||||
<Badge color="blue" text="Upload key" title="Click here to upload key"/>
|
||||
{/if}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-1">
|
||||
Username<br/>
|
||||
<input name="qu" bind:value={configuration.q.u} type="text" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Password<br/>
|
||||
<input name="qa" bind:value={configuration.q.a} type="password" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1 flex">
|
||||
<div>
|
||||
Client ID<br/>
|
||||
<input name="qc" bind:value={configuration.q.c} type="text" class="in-f w-full"/>
|
||||
</div>
|
||||
<div>
|
||||
Payload<br/>
|
||||
<select name="qm" bind:value={configuration.q.m} class="in-l">
|
||||
<option value={0}>JSON</option>
|
||||
<option value={1}>Raw (minimal)</option>
|
||||
<option value={2}>Raw (full)</option>
|
||||
<option value={3}>Domoticz</option>
|
||||
<option value={4}>HomeAssistant</option>
|
||||
<option value={255}>HEX dump</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Publish topic<br/>
|
||||
<input name="qb" bind:value={configuration.q.b} type="text" class="in-s"/>
|
||||
</div>
|
||||
</div>
|
||||
{#if configuration.q.m == 3}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Domoticz</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/MQTT-configuration#domoticz" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="o" value="true"/>
|
||||
<div class="my-1 flex">
|
||||
<div class="w-1/2">
|
||||
Electricity IDX<br/>
|
||||
<input name="oe" bind:value={configuration.o.e} type="text" class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Current IDX<br/>
|
||||
<input name="oc" bind:value={configuration.o.c} type="text" class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Voltage IDX: L1, L2 & L3
|
||||
<div class="flex">
|
||||
<input name="ou1" bind:value={configuration.o.u1} type="text" class="in-f tr w-1/3"/>
|
||||
<input name="ou2" bind:value={configuration.o.u2} type="text" class="in-m tr w-1/3"/>
|
||||
<input name="ou2" bind:value={configuration.o.u3} type="text" class="in-l tr w-1/3"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if configuration.p.r.startsWith("10YNO") || configuration.p.r == '10Y1001A1001A48H'}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Tariff thresholds</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/Threshold-configuration" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="t" value="true"/>
|
||||
<div class="flex flex-wrap my-1">
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">1</span>
|
||||
<input name="t0" bind:value={configuration.t.t[0]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">2</span>
|
||||
<input name="t1" bind:value={configuration.t.t[1]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">3</span>
|
||||
<input name="t2" bind:value={configuration.t.t[2]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">4</span>
|
||||
<input name="t3" bind:value={configuration.t.t[3]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">5</span>
|
||||
<input name="t4" bind:value={configuration.t.t[4]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">6</span>
|
||||
<input name="t5" bind:value={configuration.t.t[5]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">7</span>
|
||||
<input name="t6" bind:value={configuration.t.t[6]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">8</span>
|
||||
<input name="t7" bind:value={configuration.t.t[7]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
<label class="flex w-40 m-1">
|
||||
<span class="in-pre">9</span>
|
||||
<input name="t8" bind:value={configuration.t.t[8]} type="number" min="0" max="255" class="in-txt w-full"/>
|
||||
<span class="in-post">kWh</span>
|
||||
</label>
|
||||
</div>
|
||||
<label class="flex m-1">
|
||||
<span class="in-pre">Average of</span>
|
||||
<input name="th" bind:value={configuration.t.h} type="number" min="0" max="255" class="in-txt tr w-full"/>
|
||||
<span class="in-post">hours</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">User interface</strong>
|
||||
<input type="hidden" name="u" value="true"/>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-1/2">
|
||||
Import gauge<br/>
|
||||
<select name="ui" bind:value={configuration.u.i} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Export gauge<br/>
|
||||
<select name="ue" bind:value={configuration.u.e} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Voltage<br/>
|
||||
<select name="uv" bind:value={configuration.u.v} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Amperage<br/>
|
||||
<select name="ua" bind:value={configuration.u.a} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Reactive<br/>
|
||||
<select name="ur" bind:value={configuration.u.r} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Realtime<br/>
|
||||
<select name="uc" bind:value={configuration.u.c} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Peaks<br/>
|
||||
<select name="ut" bind:value={configuration.u.t} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Price<br/>
|
||||
<select name="up" bind:value={configuration.u.p} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Day plot<br/>
|
||||
<select name="ud" bind:value={configuration.u.d} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Month plot<br/>
|
||||
<select name="um" bind:value={configuration.u.m} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
Temperature plot<br/>
|
||||
<select name="us" bind:value={configuration.u.s} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if sysinfo.board > 20 || sysinfo.chip == 'esp8266'}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Hardware</strong>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/GPIO-configuration" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
{#if sysinfo.board > 20}
|
||||
<input type="hidden" name="i" value="true"/>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-1/3">
|
||||
HAN<br/>
|
||||
<select name="ih" bind:value={configuration.i.h} class="in-f w-full">
|
||||
<UartSelectOptions chip={sysinfo.chip}/>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
AP button<br/>
|
||||
<input name="ia" bind:value={configuration.i.a} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-m tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
LED<label class="ml-4"><input name="ili" value="true" bind:checked={configuration.i.l.i} type="checkbox" class="rounded mb-1"/> inv</label><br/>
|
||||
<div class="flex">
|
||||
<input name="ilp" bind:value={configuration.i.l.p} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
RGB<label class="ml-4"><input name="iri" value="true" bind:checked={configuration.i.r.i} type="checkbox" class="rounded mb-1"/> inverted</label><br/>
|
||||
<div class="flex">
|
||||
<input name="irr" bind:value={configuration.i.r.r} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-f tr w-1/3"/>
|
||||
<input name="irg" bind:value={configuration.i.r.g} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-m tr w-1/3"/>
|
||||
<input name="irb" bind:value={configuration.i.r.b} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-l tr w-1/3"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1 w-1/3">
|
||||
Temperature<br/>
|
||||
<input name="itd" bind:value={configuration.i.t.d} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="my-1 pr-1 w-1/3">
|
||||
Analog temp<br/>
|
||||
<input name="ita" bind:value={configuration.i.t.a} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-l tr w-full"/>
|
||||
</div>
|
||||
{#if sysinfo.chip != 'esp8266'}
|
||||
<div class="my-1 pl-1 w-1/3">
|
||||
Vcc<br/>
|
||||
<input name="ivp" bind:value={configuration.i.v.p} type="number" min="0" max={sysinfo.chip == 'esp8266' ? 16 : sysinfo.chip == 'esp32s2' ? 44 : 39} class="in-s tr w-full"/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if configuration.i.v.p > 0}
|
||||
<div class="my-1">
|
||||
Voltage divider<br/>
|
||||
<div class="flex">
|
||||
<input name="ivdv" bind:value={configuration.i.v.d.v} type="number" min="0" max="65535" class="in-f tr w-full" placeholder="VCC"/>
|
||||
<input name="ivdg" bind:value={configuration.i.v.d.g} type="number" min="0" max="65535" class="in-l tr w-full" placeholder="GND"/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if sysinfo.chip == 'esp8266'}
|
||||
<input type="hidden" name="iv" value="true"/>
|
||||
<div class="my-1 flex flex-wrap">
|
||||
<div class="w-1/3">
|
||||
Vcc offset<br/>
|
||||
<input name="ivo" bind:value={configuration.i.v.o} type="number" min="0.0" max="3.5" step="0.01" class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/3 pr-1">
|
||||
Multiplier<br/>
|
||||
<input name="ivm" bind:value={configuration.i.v.m} type="number" min="0.1" max="10" step="0.01" class="in-l tr w-full"/>
|
||||
</div>
|
||||
{#if sysinfo.board == 2 || sysinfo.board == 100}
|
||||
<div class="w-1/3 pl-1">
|
||||
Boot limit<br/>
|
||||
<input name="ivb" bind:value={configuration.i.v.b} type="number" min="2.5" max="3.5" step="0.1" class="in-s tr w-full"/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Debugging</strong>
|
||||
<a href="https://amsleser.no/blog/post/24-telnet-debug" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="d" value="true"/>
|
||||
<div class="mt-3">
|
||||
<label><input type="checkbox" name="ds" value="true" bind:checked={configuration.d.s} class="rounded mb-1"/> Enable debugging</label>
|
||||
</div>
|
||||
{#if configuration.d.s}
|
||||
<div class="bd-red">Debug can cause sudden reboots. Do not leave on!</div>
|
||||
<div class="my-1">
|
||||
<label><input type="checkbox" name="dt" value="true" bind:checked={configuration.d.t} class="rounded mb-1"/> Enable telnet</label>
|
||||
</div>
|
||||
{#if configuration.d.t}
|
||||
<div class="bd-red">Telnet is unsafe and should be off when not in use</div>
|
||||
{/if}
|
||||
<div class="my-1">
|
||||
<select name="dl" bind:value={configuration.d.l} class="in-s">
|
||||
<option value={1}>Verbose</option>
|
||||
<option value={2}>Debug</option>
|
||||
<option value={3}>Info</option>
|
||||
<option value={4}>Warning</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-3">
|
||||
<div>
|
||||
<button type="button" on:click={factoryReset} class="py-2 px-4 rounded bg-red-500 text-white ml-2">Factory reset</button>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button type="button" on:click={askReboot} class="py-2 px-4 rounded bg-yellow-500 text-white">Reboot</button>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn-pri">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<Mask active={loading} message="Loading configuration"/>
|
||||
<Mask active={saving} message="Saving configuration"/>
|
||||
<Mask active={isFactoryReset} message="Device have been factory reset and switched to AP mode"/>
|
||||
@@ -1,10 +0,0 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
let configuration = {};
|
||||
export const configurationStore = writable(configuration);
|
||||
|
||||
export async function getConfiguration() {
|
||||
const response = await fetch("/configuration.json");
|
||||
configuration = (await response.json())
|
||||
configurationStore.set(configuration);
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
<script>
|
||||
import { sysinfoStore } from './DataStores.js';
|
||||
import Mask from './Mask.svelte'
|
||||
import { navigate } from 'svelte-navigator';
|
||||
|
||||
export let sysinfo = {}
|
||||
|
||||
let loadingOrSaving = false;
|
||||
|
||||
async function handleSubmit(e) {
|
||||
loadingOrSaving = true;
|
||||
const formData = new FormData(e.target)
|
||||
const data = new URLSearchParams()
|
||||
for (let field of formData) {
|
||||
const [key, value] = field
|
||||
data.append(key, value)
|
||||
}
|
||||
|
||||
const response = await fetch('/save', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
let res = (await response.json())
|
||||
loadingOrSaving = false;
|
||||
|
||||
sysinfoStore.update(s => {
|
||||
s.fwconsent = formData['sf'] === true ? 1 : formData['sf'] === false ? 2 : 0;
|
||||
s.booting = res.reboot;
|
||||
return s;
|
||||
});
|
||||
navigate("/");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-3 lg:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<div>
|
||||
Various permissions we need to do stuff:
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="my-3">
|
||||
Enable one-click upgrade? (implies data collection)<br/>
|
||||
<a href="https://github.com/gskjold/AmsToMqttBridge/wiki/Data-collection-on-one-click-firmware-upgrade" target="_blank" class="text-blue-600 hover:text-blue-800">Read more</a><br/>
|
||||
<label><input type="radio" name="sf" value={1} checked={sysinfo.fwconsent === 1} class="rounded m-2" required/> Yes</label><label><input type="radio" name="sf" value={2} checked={sysinfo.fwconsent === 2} class="rounded m-2" required/> No</label><br/>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<button type="submit" class="btn-pri">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Mask active={loadingOrSaving} message="Saving preferences"/>
|
||||
@@ -1,10 +0,0 @@
|
||||
<script>
|
||||
let europe = ["Amsterdam","Athens","Belfast","Berlin","Bratislava","Brussels","Bucharest","Budapest","Copenhagen","Dublin",
|
||||
"Helsinki","Lisbon","Ljubljana","London","Luxembourg","Madrid","Malta","Nicosia","Oslo","Paris","Prague","Riga","Rome",
|
||||
"Sofia","Stockholm","Tallinn","Vienna","Vilnius","Warsaw","Zagreb","Zurich"];
|
||||
</script>
|
||||
|
||||
<option>GMT</option>
|
||||
{#each europe as c}
|
||||
<option>Europe/{c}</option>
|
||||
{/each}
|
||||
@@ -1,103 +0,0 @@
|
||||
<script>
|
||||
import { pricesStore, dayPlotStore, monthPlotStore, temperaturesStore } from './DataStores.js';
|
||||
import { metertype, uiVisibility } from './Helpers.js';
|
||||
import PowerGauge from './PowerGauge.svelte';
|
||||
import VoltPlot from './VoltPlot.svelte';
|
||||
import AmpPlot from './AmpPlot.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';
|
||||
|
||||
export let data = {}
|
||||
export let sysinfo = {}
|
||||
let prices = {}
|
||||
let dayPlot = {}
|
||||
let monthPlot = {}
|
||||
let temperatures = {};
|
||||
pricesStore.subscribe(update => {
|
||||
prices = update;
|
||||
});
|
||||
dayPlotStore.subscribe(update => {
|
||||
dayPlot = update;
|
||||
});
|
||||
monthPlotStore.subscribe(update => {
|
||||
monthPlot = update;
|
||||
});
|
||||
temperaturesStore.subscribe(update => {
|
||||
temperatures = update;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-6 lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
|
||||
{#if uiVisibility(sysinfo.ui.i, data.i)}
|
||||
<div class="cnt">
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="col-span-2">
|
||||
<PowerGauge val={data.i ? data.i : 0} max={data.im} unit="W" label="Import" sub={data.p} subunit={prices.currency}/>
|
||||
</div>
|
||||
<div>{data.mt ? metertype(data.mt) : '-'}</div>
|
||||
<div class="text-right">{data.ic ? data.ic.toFixed(1) : '-'} kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.e, data.om || data.e > 0)}
|
||||
<div class="cnt">
|
||||
<div class="grid grid-cols-2">
|
||||
<div class="col-span-2">
|
||||
<PowerGauge val={data.e ? data.e : 0} max={data.om ? data.om : 10000} unit="W" label="Export"/>
|
||||
</div>
|
||||
<div></div>
|
||||
<div class="text-right">{data.ec ? data.ec.toFixed(1) : '-'} kWh</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.v, data.u1 > 100 || data.u2 > 100 || data.u3 > 100)}
|
||||
<div class="cnt">
|
||||
<VoltPlot u1={data.u1} u2={data.u2} u3={data.u3} ds={data.ds}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.a, data.i1 > 0.01 || data.i2 > 0.01 || data.i3 > 0.01)}
|
||||
<div class="cnt">
|
||||
<AmpPlot u1={data.u1} u2={data.u2} u3={data.u3} i1={data.i1} i2={data.i2} i3={data.i3} max={data.mf ? data.mf : 32}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.r, data.ri > 0 || data.re > 0 || data.ric > 0 || data.rec > 0)}
|
||||
<div class="cnt">
|
||||
<ReactiveData importInstant={data.ri} exportInstant={data.re} importTotal={data.ric} exportTotal={data.rec}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.c, data.ea)}
|
||||
<div class="cnt">
|
||||
<AccountingData data={data.ea} currency={prices.currency}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if data && data.pr && (data.pr.startsWith("10YNO") || data.pr == '10Y1001A1001A48H')}
|
||||
<div class="cnt h-64">
|
||||
<TariffPeakChart />
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.p, (typeof data.p == "number") && !Number.isNaN(data.p))}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<PricePlot json={prices}/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.d, dayPlot)}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<DayPlot json={dayPlot} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.m, monthPlot)}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<MonthPlot json={monthPlot} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.s, data.t && data.t != -127 && temperatures.c > 1)}
|
||||
<div class="cnt xl:col-span-6 lg:col-span-4 md:col-span-3 sm:col-span-2 h-64">
|
||||
<TemperaturePlot json={temperatures} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,183 +0,0 @@
|
||||
import { readable, writable } from 'svelte/store';
|
||||
import { isBusPowered } from './Helpers';
|
||||
|
||||
async function fetchWithTimeout(resource, options = {}) {
|
||||
const { timeout = 8000 } = options;
|
||||
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
const response = await fetch(resource, {
|
||||
...options,
|
||||
signal: controller.signal
|
||||
});
|
||||
clearTimeout(id);
|
||||
return response;
|
||||
}
|
||||
|
||||
let sysinfo = {
|
||||
version: '',
|
||||
chip: '',
|
||||
mac: null,
|
||||
apmac: null,
|
||||
vndcfg: null,
|
||||
usrcfg: null,
|
||||
fwconsent: null,
|
||||
booting: false,
|
||||
upgrading: false,
|
||||
ui: {},
|
||||
security: 0
|
||||
};
|
||||
export const sysinfoStore = writable(sysinfo);
|
||||
export async function getSysinfo() {
|
||||
const response = await fetchWithTimeout("/sysinfo.json?t=" + Math.floor(Date.now() / 1000));
|
||||
sysinfo = (await response.json())
|
||||
sysinfoStore.set(sysinfo);
|
||||
};
|
||||
|
||||
let tries = 0;
|
||||
let lastTemp = -127;
|
||||
let lastPrice = null;
|
||||
let data = {};
|
||||
export const dataStore = readable(data, (set) => {
|
||||
let timeout;
|
||||
async function getData() {
|
||||
fetchWithTimeout("/data.json")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
set(data);
|
||||
if(lastTemp != data.t) {
|
||||
lastTemp = data.t;
|
||||
setTimeout(getTemperatures, 2000);
|
||||
}
|
||||
if(lastPrice != data.p) {
|
||||
lastPrice = data.p;
|
||||
setTimeout(getPrices, 4000);
|
||||
}
|
||||
if(sysinfo.upgrading) {
|
||||
window.location.reload();
|
||||
} else if(!sysinfo || !sysinfo.chip || sysinfo.booting || (tries > 1 && !isBusPowered(sysinfo.board))) {
|
||||
getSysinfo();
|
||||
if(dayPlotTimeout) clearTimeout(dayPlotTimeout);
|
||||
dayPlotTimeout = setTimeout(getDayPlot, 2000);
|
||||
if(monthPlotTimeout) clearTimeout(monthPlotTimeout);
|
||||
monthPlotTimeout = setTimeout(getMonthPlot, 3000);
|
||||
}
|
||||
let to = 5000;
|
||||
if(isBusPowered(sysinfo.board) && data.v > 2.5) {
|
||||
let diff = (3.3 - Math.min(3.3, data.v));
|
||||
if(diff > 0) {
|
||||
to = Math.max(diff, 0.1) * 10 * 5000;
|
||||
}
|
||||
}
|
||||
if(to > 5000) console.log("Scheduling next data fetch in " + to + "ms");
|
||||
if(timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(getData, to);
|
||||
tries = 0;
|
||||
})
|
||||
.catch((err) => {
|
||||
tries++;
|
||||
if(tries > 3) {
|
||||
set({
|
||||
em: 3,
|
||||
hm: 0,
|
||||
wm: 0,
|
||||
mm: 0
|
||||
});
|
||||
timeout = setTimeout(getData, 15000);
|
||||
} else {
|
||||
timeout = setTimeout(getData, isBusPowered(sysinfo.board) ? 10000 : 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
getData();
|
||||
return function stop() {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
});
|
||||
|
||||
let prices = {};
|
||||
export const pricesStore = writable(prices);
|
||||
export async function getPrices() {
|
||||
const response = await fetchWithTimeout("/energyprice.json");
|
||||
prices = (await response.json())
|
||||
pricesStore.set(prices);
|
||||
}
|
||||
|
||||
let dayPlot = {};
|
||||
let dayPlotTimeout;
|
||||
export async function getDayPlot() {
|
||||
if(dayPlotTimeout) {
|
||||
clearTimeout(dayPlotTimeout);
|
||||
dayPlotTimeout = 0;
|
||||
}
|
||||
const response = await fetchWithTimeout("/dayplot.json");
|
||||
dayPlot = (await response.json())
|
||||
dayPlotStore.set(dayPlot);
|
||||
|
||||
let date = new Date();
|
||||
dayPlotTimeout = setTimeout(getDayPlot, ((60-date.getMinutes())*60000)+20)
|
||||
}
|
||||
|
||||
export const dayPlotStore = writable(dayPlot, (set) => {
|
||||
getDayPlot();
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let monthPlot = {};
|
||||
let monthPlotTimeout;
|
||||
export async function getMonthPlot() {
|
||||
if(monthPlotTimeout) {
|
||||
clearTimeout(monthPlotTimeout);
|
||||
monthPlotTimeout = 0;
|
||||
}
|
||||
const response = await fetchWithTimeout("/monthplot.json");
|
||||
monthPlot = (await response.json())
|
||||
monthPlotStore.set(monthPlot);
|
||||
|
||||
let date = new Date();
|
||||
monthPlotTimeout = setTimeout(getMonthPlot, ((24-date.getHours())*3600000)+40)
|
||||
}
|
||||
|
||||
export const monthPlotStore = writable(monthPlot, (set) => {
|
||||
getMonthPlot();
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let temperatures = {};
|
||||
export async function getTemperatures() {
|
||||
const response = await fetchWithTimeout("/temperature.json");
|
||||
temperatures = (await response.json())
|
||||
temperaturesStore.set(temperatures);
|
||||
}
|
||||
|
||||
export const temperaturesStore = writable(temperatures, (set) => {
|
||||
getTemperatures();
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let tariff = {};
|
||||
let tariffTimeout;
|
||||
export async function getTariff() {
|
||||
if(tariffTimeout) {
|
||||
clearTimeout(tariffTimeout);
|
||||
tariffTimeout = 0;
|
||||
}
|
||||
const response = await fetchWithTimeout("/tariff.json");
|
||||
tariff = (await response.json())
|
||||
tariffStore.set(tariff);
|
||||
let date = new Date();
|
||||
tariffTimeout = setTimeout(getTariff, ((60-date.getMinutes())*60000)+30)
|
||||
}
|
||||
|
||||
export const tariffStore = writable(tariff, (set) => {
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
let releases = [];
|
||||
export const gitHubReleaseStore = writable(releases);
|
||||
|
||||
export async function getGitHubReleases() {
|
||||
const response = await fetchWithTimeout("https://api.github.com/repos/gskjold/AmsToMqttBridge/releases");
|
||||
releases = (await response.json())
|
||||
gitHubReleaseStore.set(releases);
|
||||
};
|
||||
@@ -1,101 +0,0 @@
|
||||
<script>
|
||||
import { zeropad } from './Helpers.js';
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
let min = 0;
|
||||
|
||||
$: {
|
||||
let i = 0;
|
||||
let yTicks = [];
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
let cur = new Date();
|
||||
let offset = -cur.getTimezoneOffset()/60;
|
||||
for(i = cur.getUTCHours(); i<24; i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
if(imp === undefined) imp = 0;
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad((i+offset)%24)
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(1),
|
||||
value: imp*10,
|
||||
label2: exp.toFixed(1),
|
||||
value2: exp*10,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
min = Math.max(min, exp*10);
|
||||
max = Math.max(max, imp*10);
|
||||
};
|
||||
for(i = 0; i < cur.getUTCHours(); i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
if(imp === undefined) imp = 0;
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad((i+offset)%24)
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(1),
|
||||
value: imp*10,
|
||||
label2: exp.toFixed(1),
|
||||
value2: exp*10,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
min = Math.max(min, exp*10);
|
||||
max = Math.max(max, imp*10);
|
||||
};
|
||||
|
||||
let boundary = Math.ceil(Math.max(min, max));
|
||||
|
||||
max = boundary;
|
||||
min = min == 0 ? 0 : boundary*-1;
|
||||
|
||||
if(min < 0) {
|
||||
let yTickDistDown = min/4;
|
||||
for(i = 1; i < 5; i++) {
|
||||
let val = (yTickDistDown*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: (val/10).toFixed(1)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let yTickDistUp = max/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
let val = (yTickDistUp*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: (val/10).toFixed(1)
|
||||
});
|
||||
}
|
||||
|
||||
config = {
|
||||
title: "Energy use last 24 hours (kWh)",
|
||||
height: 226,
|
||||
width: 1520,
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
y: {
|
||||
min: min,
|
||||
max: max,
|
||||
ticks: yTicks
|
||||
},
|
||||
x: {
|
||||
ticks: xTicks
|
||||
},
|
||||
points: points
|
||||
};
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<BarChart config={config} />
|
||||
@@ -1,6 +0,0 @@
|
||||
<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,22 +0,0 @@
|
||||
<script>
|
||||
import Mask from "./Mask.svelte";
|
||||
|
||||
export let action;
|
||||
export let title;
|
||||
|
||||
let uploading = false;
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-2 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<strong>Upload {title}</strong>
|
||||
<p class="mb-4">Select a suitable file and click upload</p>
|
||||
<form action="{action}" 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">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<Mask active={uploading} message="Uploading file, please wait"/>
|
||||
@@ -1,7 +0,0 @@
|
||||
<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>
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<script>
|
||||
import { Link } from "svelte-navigator";
|
||||
import { sysinfoStore, getGitHubReleases, gitHubReleaseStore } from './DataStores.js';
|
||||
import { upgrade, getNextVersion } from './UpgradeHelper';
|
||||
import { boardtype, hanError, mqttError, priceError, isBusPowered } from './Helpers.js';
|
||||
import AmsleserSvg from "./../assets/favicon.svg";
|
||||
import GitHubLogo from './../assets/github.svg';
|
||||
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";
|
||||
import DownloadIcon from "./DownloadIcon.svelte";
|
||||
|
||||
export let data = {}
|
||||
let sysinfo = {}
|
||||
|
||||
let nextVersion = {};
|
||||
|
||||
function askUpgrade() {
|
||||
if(confirm('Do you want to upgrade this device to ' + nextVersion.tag_name + '?')) {
|
||||
if(!isBusPowered(sysinfo.board) || 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
sysinfoStore.subscribe(update => {
|
||||
sysinfo = update;
|
||||
if(update.fwconsent === 1) {
|
||||
getGitHubReleases();
|
||||
}
|
||||
});
|
||||
|
||||
gitHubReleaseStore.subscribe(releases => {
|
||||
nextVersion = getNextVersion(sysinfo.version, releases);
|
||||
});
|
||||
</script>
|
||||
|
||||
<nav class="bg-violet-600 p-1 rounded-md mx-2">
|
||||
<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>
|
||||
</div>
|
||||
<div class="flex-none my-auto p-2 flex space-x-4">
|
||||
<div class="flex-none my-auto"><Uptime epoch={data.u}/></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 flex-wrap my-auto justify-center p-2">
|
||||
<Badge title="ESP" text={sysinfo.booting ? 'Booting' : data.v > 2.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>
|
||||
{#if data.he < 0 || data.he > 0}
|
||||
<div class="bd-red">{ 'HAN: ' + hanError(data.he) }</div>
|
||||
{/if}
|
||||
{#if data.me < 0}
|
||||
<div class="bd-red">{ 'MQTT: ' + mqttError(data.me) }</div>
|
||||
{/if}
|
||||
{#if data.ee > 0 || data.ee < 0}
|
||||
<div class="bd-red">{ 'PriceAPI: ' + priceError(data.ee) }</div>
|
||||
{/if}
|
||||
<div class="flex-auto p-2 flex flex-row-reverse flex-wrap">
|
||||
<div class="flex-none">
|
||||
<a class="float-right" href='https://github.com/gskjold/AmsToMqttBridge' target='_blank' rel="noreferrer" aria-label="GitHub"><img class="gh-logo" src={GitHubLogo} alt="GitHub repo"/></a>
|
||||
</div>
|
||||
<div class="flex-none my-auto px-2">
|
||||
<Clock timestamp={ data.c ? new Date(data.c * 1000) : new Date(0) } />
|
||||
</div>
|
||||
{#if sysinfo.vndcfg && sysinfo.usrcfg}
|
||||
<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="Device information">
|
||||
<Link to="/status"><InfoIcon/></Link>
|
||||
</div>
|
||||
{/if}
|
||||
<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 === 1 && nextVersion}
|
||||
<div class="flex-none mr-3 text-yellow-500" title="New version: {nextVersion.tag_name}">
|
||||
{#if sysinfo.security == 0 || data.a}
|
||||
<button on:click={askUpgrade} class="flex"><span class="mt-1">New version: {nextVersion.tag_name}</span> <DownloadIcon/></button>
|
||||
{:else}
|
||||
<span>New version: {nextVersion.tag_name}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,6 +0,0 @@
|
||||
<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>
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
export let monthnames = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
||||
|
||||
export function voltcol(pct) {
|
||||
if(pct > 85) return '#d90000';
|
||||
else if(pct > 75) return'#e32100';
|
||||
else if(pct > 70) return '#ffb800';
|
||||
else if(pct > 65) return '#dcd800';
|
||||
else if(pct > 35) return '#32d900';
|
||||
else if(pct > 25) return '#dcd800';
|
||||
else if(pct > 20) return '#ffb800';
|
||||
else if(pct > 15) return'#e32100';
|
||||
else return '#d90000';
|
||||
};
|
||||
|
||||
export function ampcol(pct) {
|
||||
if(pct > 90) return '#d90000';
|
||||
else if(pct > 85) return'#e32100';
|
||||
else if(pct > 80) return '#ffb800';
|
||||
else if(pct > 75) return '#dcd800';
|
||||
else return '#32d900';
|
||||
};
|
||||
|
||||
export function metertype(mt) {
|
||||
switch(mt) {
|
||||
case 1:
|
||||
return "Aidon";
|
||||
case 2:
|
||||
return "Kaifa";
|
||||
case 3:
|
||||
return "Kamstrup";
|
||||
case 8:
|
||||
return "Iskra";
|
||||
case 9:
|
||||
return "Landis+Gyr";
|
||||
case 10:
|
||||
return "Sagemcom";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
export function zeropad(num) {
|
||||
num = num.toString();
|
||||
while (num.length < 2) num = "0" + num;
|
||||
return num;
|
||||
}
|
||||
|
||||
export function boardtype(c, b) {
|
||||
switch(b) {
|
||||
case 5:
|
||||
switch(c) {
|
||||
case 'esp8266':
|
||||
return "Pow-K (GPIO12)";
|
||||
case 'esp32s2':
|
||||
return "Pow-K+";
|
||||
}
|
||||
case 7:
|
||||
switch(c) {
|
||||
case 'esp8266':
|
||||
return "Pow-U (GPIO12)";
|
||||
case 'esp32s2':
|
||||
return "Pow-U+";
|
||||
}
|
||||
case 6:
|
||||
return "Pow-P1";
|
||||
case 51:
|
||||
return "Wemos S2 mini";
|
||||
case 50:
|
||||
return "Generic ESP32-S2";
|
||||
case 201:
|
||||
return "Wemos LOLIN D32";
|
||||
case 202:
|
||||
return "Adafruit HUZZAH32";
|
||||
case 203:
|
||||
return "DevKitC";
|
||||
case 200:
|
||||
return "Generic ESP32";
|
||||
case 2:
|
||||
return "HAN Reader 2.0 by Max Spencer";
|
||||
case 0:
|
||||
return "Custom hardware by Roar Fredriksen";
|
||||
case 1:
|
||||
return "Kamstrup module by Egil Opsahl"
|
||||
case 3:
|
||||
return "Pow-K (UART0)";
|
||||
case 4:
|
||||
return "Pow-U (UART0)";
|
||||
case 101:
|
||||
return "Wemos D1 mini";
|
||||
case 100:
|
||||
return "Generic ESP8266";
|
||||
}
|
||||
}
|
||||
|
||||
export function hanError(err) {
|
||||
switch(err) {
|
||||
case -1: return "Parse error";
|
||||
case -2: return "Incomplete data received";
|
||||
case -3: return "Payload boundry flag missing";
|
||||
case -4: return "Header checksum error";
|
||||
case -5: return "Footer checksum error";
|
||||
case -9: return "Unknown data received, check meter config";
|
||||
case -41: return "Frame length not equal";
|
||||
case -51: return "Authentication failed";
|
||||
case -52: return "Decryption failed";
|
||||
case -53: return "Encryption key invalid";
|
||||
case 90: return "No HAN data received last 30s";
|
||||
case 98: return "Exception in code, debugging necessary";
|
||||
case 99: return "Autodetection failed";
|
||||
}
|
||||
if(err < 0) return "Unspecified error "+err;
|
||||
return "";
|
||||
}
|
||||
|
||||
export function mqttError(err) {
|
||||
switch(err) {
|
||||
case -3: return "Connection failed";
|
||||
case -4: return "Network timeout";
|
||||
case -10: return "Connection denied";
|
||||
case -11: return "Failed to subscribe";
|
||||
case -13: return "Connection lost";
|
||||
}
|
||||
|
||||
if(err < 0) return "Unspecified error "+err;
|
||||
return "";
|
||||
}
|
||||
|
||||
export function priceError(err) {
|
||||
switch(err) {
|
||||
case 401:
|
||||
case 403:
|
||||
return "Unauthorized, check API key";
|
||||
case 404:
|
||||
return "Price unavailable, not found";
|
||||
case 500:
|
||||
return "Internal server error";
|
||||
case -2: return "Incomplete data received";
|
||||
case -3: return "Invalid data, tag missing";
|
||||
case -51: return "Authentication failed";
|
||||
case -52: return "Decryption failed";
|
||||
case -53: return "Encryption key invalid";
|
||||
}
|
||||
|
||||
if(err < 0) return "Unspecified error "+err;
|
||||
return "";
|
||||
}
|
||||
|
||||
export function isBusPowered(boardType) {
|
||||
switch(boardType) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 7:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function uiVisibility(choice, state) {
|
||||
return choice == 1 || (choice == 2 && state);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<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>
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
<script>
|
||||
export let active;
|
||||
export let message;
|
||||
</script>
|
||||
|
||||
{#if active}
|
||||
<div class="z-50" aria-modal="true">
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center">
|
||||
{#if message}
|
||||
<div class="bg-white m-2 p-3 rounded-md shadow-lg pb-4 text-gray-700 w-96">{message}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,103 +0,0 @@
|
||||
<script>
|
||||
import { zeropad } from './Helpers.js';
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
let min = 0;
|
||||
|
||||
$: {
|
||||
let i = 0;
|
||||
let yTicks = [];
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
let cur = new Date();
|
||||
let lm = new Date();
|
||||
lm.setDate(0);
|
||||
|
||||
for(i = cur.getDate(); i<=lm.getDate(); i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
if(imp === undefined) imp = 0;
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad(i)
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(0),
|
||||
value: imp,
|
||||
label2: exp.toFixed(0),
|
||||
value2: exp,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
min = Math.max(min, exp);
|
||||
max = Math.max(max, imp);
|
||||
}
|
||||
for(i = 1; i < cur.getDate(); i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
if(imp === undefined) imp = 0;
|
||||
if(exp === undefined) exp = 0;
|
||||
|
||||
xTicks.push({
|
||||
label: zeropad(i)
|
||||
});
|
||||
points.push({
|
||||
label: imp.toFixed(0),
|
||||
value: imp,
|
||||
label2: exp.toFixed(0),
|
||||
value2: exp,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
min = Math.max(min, exp);
|
||||
max = Math.max(max, imp);
|
||||
}
|
||||
|
||||
let boundary = Math.ceil(Math.max(min, max)/10)*10;
|
||||
|
||||
max = boundary;
|
||||
min = min == 0 ? 0 : boundary*-1;
|
||||
|
||||
if(min < 0) {
|
||||
let yTickDistDown = min/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
let val = (yTickDistDown*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: val.toFixed(0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let yTickDistUp = max/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
let val = (yTickDistUp*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: val.toFixed(0)
|
||||
});
|
||||
}
|
||||
|
||||
config = {
|
||||
title: "Energy use last month (kWh)",
|
||||
height: 226,
|
||||
width: 1520,
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
y: {
|
||||
min: min,
|
||||
max: max,
|
||||
ticks: yTicks
|
||||
},
|
||||
x: {
|
||||
ticks: xTicks
|
||||
},
|
||||
points: points
|
||||
};
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<BarChart config={config} />
|
||||
@@ -1,26 +0,0 @@
|
||||
<script>
|
||||
import PowerGaugeSvg from './PowerGaugeSvg.svelte';
|
||||
import { ampcol } from './Helpers.js';
|
||||
|
||||
export let val;
|
||||
export let max;
|
||||
export let unit;
|
||||
export let label;
|
||||
export let sub = "";
|
||||
export let subunit = "";
|
||||
</script>
|
||||
|
||||
<div class="pl-root">
|
||||
<PowerGaugeSvg pct={val/max * 100} color={ampcol(val/max * 100)}/>
|
||||
<span class="pl-ov">
|
||||
<span class="pl-lab">{label}</span>
|
||||
<br/>
|
||||
<span class="pl-val">{val}</span>
|
||||
<span class="pl-unt">{unit}</span>
|
||||
{#if sub}
|
||||
<br/>
|
||||
<span class="pl-sub">{sub}</span>
|
||||
<span class="pl-snt">{subunit}/kWh</span>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
<script>
|
||||
export let pct = 0;
|
||||
export let color = "red";
|
||||
|
||||
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
||||
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
|
||||
|
||||
return {
|
||||
x: centerX + (radius * Math.cos(angleInRadians)),
|
||||
y: centerY + (radius * Math.sin(angleInRadians))
|
||||
};
|
||||
}
|
||||
function describeArc(x, y, radius, startAngle, endAngle){
|
||||
var start = polarToCartesian(x, y, radius, endAngle);
|
||||
var end = polarToCartesian(x, y, radius, startAngle);
|
||||
|
||||
var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
|
||||
|
||||
var d = [
|
||||
"M", start.x, start.y,
|
||||
"A", radius, radius, 0, arcSweep, 0, end.x, end.y
|
||||
].join(" ");
|
||||
|
||||
return d;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" height="100%">
|
||||
<path d="{ describeArc(150, 150, 115, 210, 510) }" stroke="#eee" fill="none" stroke-width="55"/>
|
||||
<path d="{ describeArc(150, 150, 115, 210, 210 + (300*pct/100)) }" stroke={color} fill="none" stroke-width="55"/>
|
||||
</svg>
|
||||
@@ -1,97 +0,0 @@
|
||||
<script>
|
||||
import { zeropad } from './Helpers.js';
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
let min = 0;
|
||||
|
||||
$: {
|
||||
let hour = new Date().getUTCHours();
|
||||
let i = 0;
|
||||
let val = 0;
|
||||
let h = 0;
|
||||
let d = json["20"] == null ? 2 : 1;
|
||||
let yTicks = [];
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
let cur = new Date();
|
||||
for(i = hour; i<24; i++) {
|
||||
cur.setUTCHours(i);
|
||||
val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
xTicks.push({
|
||||
label: zeropad(cur.getHours())
|
||||
});
|
||||
points.push({
|
||||
label: val > 0 ? val.toFixed(d) : '',
|
||||
value: val > 0 ? Math.abs(val*100) : 0,
|
||||
label2: val < 0 ? val.toFixed(d) : '',
|
||||
value2: val < 0 ? Math.abs(val*100) : 0,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
min = Math.min(min, val*100);
|
||||
max = Math.max(max, val*100);
|
||||
};
|
||||
for(i = 0; i < 24; i++) {
|
||||
cur.setUTCHours(i);
|
||||
val = json[zeropad(h++)];
|
||||
if(val == null) break;
|
||||
xTicks.push({
|
||||
label: zeropad(cur.getHours())
|
||||
});
|
||||
points.push({
|
||||
label: val > 0 ? val.toFixed(d) : '',
|
||||
value: val > 0 ? Math.abs(val*100) : 0,
|
||||
label2: val < 0 ? val.toFixed(d) : '',
|
||||
value2: val < 0 ? Math.abs(val*100) : 0,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
min = Math.min(min, val*100);
|
||||
max = Math.max(max, val*100);
|
||||
};
|
||||
|
||||
max = Math.ceil(max);
|
||||
min = Math.floor(min);
|
||||
|
||||
if(min < 0) {
|
||||
let yTickDistDown = min/4;
|
||||
for(i = 1; i < 5; i++) {
|
||||
let val = (yTickDistDown*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: (val/100).toFixed(2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let yTickDistUp = max/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
let val = (yTickDistUp*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: (val/100).toFixed(2)
|
||||
});
|
||||
}
|
||||
|
||||
config = {
|
||||
title: "Future energy price (" + json.currency + ")",
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
y: {
|
||||
min: min,
|
||||
max: max,
|
||||
ticks: yTicks
|
||||
},
|
||||
x: {
|
||||
ticks: xTicks
|
||||
},
|
||||
points: points
|
||||
};
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<a href="https://transparency.entsoe.eu/" target="_blank" class="text-xs float-right z-40">Provided by ENTSO-E</a>
|
||||
<BarChart config={config} />
|
||||
@@ -1,24 +0,0 @@
|
||||
<script>
|
||||
export let importInstant;
|
||||
export let exportInstant;
|
||||
export let importTotal;
|
||||
export let exportTotal;
|
||||
</script>
|
||||
|
||||
<div class="mx-2">
|
||||
<strong class="text-sm">Reactive</strong>
|
||||
|
||||
<div class="grid grid-cols-2 mt-4">
|
||||
<div>Instant in</div>
|
||||
<div class="text-right">{typeof importInstant !== 'undefined' ? importInstant.toFixed(0) : '-'} VAr</div>
|
||||
<div>Instant out</div>
|
||||
<div class="text-right">{typeof exportInstant !== 'undefined' ? exportInstant.toFixed(0) : '-'} VAr</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 mt-4">
|
||||
<div>Total in</div>
|
||||
<div class="text-right">{typeof importTotal !== 'undefined' ? importTotal.toFixed(1) : '-'} kVArh</div>
|
||||
<div>Total out</div>
|
||||
<div class="text-right">{typeof exportTotal !== 'undefined' ? exportTotal.toFixed(1) : '-'} kVArh</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,121 +0,0 @@
|
||||
<script>
|
||||
import { sysinfoStore } from './DataStores.js';
|
||||
import Mask from './Mask.svelte'
|
||||
|
||||
export let sysinfo = {}
|
||||
|
||||
let staticIp = false;
|
||||
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);
|
||||
let hostname = sysinfo.hostname;
|
||||
const data = new URLSearchParams();
|
||||
for (let field of formData) {
|
||||
const [key, value] = field;
|
||||
data.append(key, value)
|
||||
if(key == 'sh') hostname = value;
|
||||
}
|
||||
|
||||
const response = await fetch('/save', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
let res = (await response.json())
|
||||
loadingOrSaving = false;
|
||||
|
||||
sysinfoStore.update(s => {
|
||||
s.hostname = hostname;
|
||||
s.usrcfg = res.success;
|
||||
s.booting = res.reboot;
|
||||
setTimeout(scanForDevice, 5000);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<input type="hidden" name="s" value="true"/>
|
||||
<strong class="text-sm">Setup</strong>
|
||||
<div class="my-3">
|
||||
SSID<br/>
|
||||
<input name="ss" type="text" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
PSK<br/>
|
||||
<input name="sp" type="password" class="in-s"/>
|
||||
</div>
|
||||
<div>
|
||||
Hostname:
|
||||
<input name="sh" bind:value={sysinfo.hostname} type="text" class="in-s" 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>
|
||||
{#if staticIp}
|
||||
<br/>
|
||||
<div class="flex">
|
||||
<input name="si" type="text" class="in-f w-full" required={staticIp}/>
|
||||
<select name="su" class="in-l" required={staticIp}>
|
||||
<option value="255.255.255.0">/24</option>
|
||||
<option value="255.255.0.0">/16</option>
|
||||
<option value="255.0.0.0">/8</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if staticIp}
|
||||
<div class="my-3 flex">
|
||||
<div>
|
||||
Gateway<br/>
|
||||
<input name="sg" type="text" class="in-f w-full"/>
|
||||
</div>
|
||||
<div>
|
||||
DNS<br/>
|
||||
<input name="sd" type="text" class="in-l w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-3">
|
||||
<button type="submit" class="btn-pri">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Mask active={loadingOrSaving} message="Saving your configuration to the device"/>
|
||||
@@ -1,188 +0,0 @@
|
||||
<script>
|
||||
import { metertype, boardtype, isBusPowered } from './Helpers.js';
|
||||
import { getSysinfo, gitHubReleaseStore, sysinfoStore } from './DataStores.js';
|
||||
import { upgrade, getNextVersion } from './UpgradeHelper';
|
||||
import DownloadIcon from './DownloadIcon.svelte';
|
||||
import { Link } from 'svelte-navigator';
|
||||
import Mask from './Mask.svelte';
|
||||
|
||||
export let data;
|
||||
export let sysinfo;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function reboot() {
|
||||
const response = await fetch('/reboot', {
|
||||
method: 'POST'
|
||||
});
|
||||
let res = (await response.json())
|
||||
}
|
||||
|
||||
const askReboot = function() {
|
||||
if(confirm('Are you sure you want to reboot the device?')) {
|
||||
sysinfoStore.update(s => {
|
||||
s.booting = true;
|
||||
return s;
|
||||
});
|
||||
reboot();
|
||||
}
|
||||
}
|
||||
|
||||
let firmwareFileInput;
|
||||
let firmwareFiles = [];
|
||||
let firmwareUploading = false;
|
||||
|
||||
let configFileInput;
|
||||
let configFiles = [];
|
||||
let configUploading = false;
|
||||
|
||||
getSysinfo();
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-5 lg:grid-cols-3 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Device information</strong>
|
||||
<div class="my-2">
|
||||
Chip: {sysinfo.chip}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
Device: {boardtype(sysinfo.chip, sysinfo.board)}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
MAC: {sysinfo.mac}
|
||||
</div>
|
||||
{#if sysinfo.apmac && sysinfo.apmac != sysinfo.mac}
|
||||
<div class="my-2">
|
||||
AP MAC: {sysinfo.apmac}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-2">
|
||||
<Link to="/consent">
|
||||
<span class="text-xs py-1 px-2 rounded bg-blue-500 text-white mr-3 ">Update consents</span>
|
||||
</Link>
|
||||
<button on:click={askReboot} class="text-xs py-1 px-2 rounded bg-yellow-500 text-white mr-3 float-right">Reboot</button>
|
||||
</div>
|
||||
</div>
|
||||
{#if sysinfo.meter}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Meter</strong>
|
||||
<div class="my-2">
|
||||
Manufacturer: {metertype(sysinfo.meter.mfg)}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
Model: {sysinfo.meter.model}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
ID: {sysinfo.meter.id}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if sysinfo.net}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Network</strong>
|
||||
<div class="my-2">
|
||||
IP: {sysinfo.net.ip}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
Mask: {sysinfo.net.mask}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
Gateway: {sysinfo.net.gw}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
DNS: {sysinfo.net.dns1} {#if sysinfo.net.dns2 && sysinfo.net.dns2 != '0.0.0.0'}/ {sysinfo.net.dns2}{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Firmware</strong>
|
||||
<div class="my-2">
|
||||
Installed version: {sysinfo.version}
|
||||
</div>
|
||||
{#if nextVersion}
|
||||
<div class="my-2 flex">
|
||||
Latest version:
|
||||
<a href={nextVersion.html_url} class="ml-2 text-blue-600 hover:text-blue-800" target='_blank' rel="noreferrer">{nextVersion.tag_name}</a>
|
||||
{#if (sysinfo.security == 0 || data.a) && sysinfo.fwconsent === 1 && nextVersion && nextVersion.tag_name}
|
||||
<div class="flex-none ml-2 text-green-500" title="Install this version">
|
||||
<button on:click={askUpgrade}><DownloadIcon/></button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if sysinfo.fwconsent === 2}
|
||||
<div class="my-2">
|
||||
<div class="bd-ylo">You have disabled one-click firmware upgrade, link to self-upgrade is disabled</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if (sysinfo.security == 0 || data.a) && isBusPowered(sysinfo.board) }
|
||||
<div class="bd-red">
|
||||
{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}
|
||||
{#if sysinfo.security == 0 || data.a}
|
||||
<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}
|
||||
<button type="button" on:click={()=>{firmwareFileInput.click();}} class="text-xs py-1 px-2 rounded bg-blue-500 text-white float-right mr-3">Select firmware file for upgrade</button>
|
||||
{:else}
|
||||
{firmwareFiles[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>
|
||||
{/if}
|
||||
</div>
|
||||
{#if sysinfo.security == 0 || data.a}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Configuration</strong>
|
||||
<form method="get" action="/configfile.cfg" autocomplete="off">
|
||||
<div class="grid grid-cols-2">
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="iw" value="true" checked/> WiFi</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="im" value="true" checked/> MQTT</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="ie" value="true" checked/> Web</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="it" value="true" checked/> Meter</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="ih" value="true" checked/> Thresholds</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="ig" value="true" checked/> GPIO</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="id" value="true" checked/> Domoticz</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="in" value="true" checked/> NTP</label>
|
||||
<label class="my-1 mx-3"><input type="checkbox" class="rounded" name="is" value="true" checked/> Price API</label>
|
||||
<label class="my-1 mx-3 col-span-2"><input type="checkbox" class="rounded" name="ic" value="true"/> Include Secrets<br/><small>(SSID, PSK, passwords and tokens)</small></label>
|
||||
</div>
|
||||
{#if configFiles.length == 0}
|
||||
<button type="submit" class="ml-2 text-xs py-1 px-2 rounded bg-blue-500 text-white float-right mr-3">Download</button>
|
||||
{/if}
|
||||
</form>
|
||||
<form action="/configfile" enctype="multipart/form-data" method="post" on:submit={() => configUploading=true} autocomplete="off">
|
||||
<input style="display:none" name="file" type="file" accept=".cfg" bind:this={configFileInput} bind:files={configFiles}>
|
||||
{#if configFiles.length == 0}
|
||||
<button type="button" on:click={()=>{configFileInput.click();}} class="text-xs py-1 px-2 rounded bg-blue-500 text-white mr-3">Select file...</button>
|
||||
{:else}
|
||||
{configFiles[0].name}
|
||||
<button type="submit" class="ml-2 text-xs py-1 px-2 rounded bg-blue-500 text-white mr-3">Upload</button>
|
||||
{/if}
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<Mask active={firmwareUploading} message="Uploading firmware, please wait"/>
|
||||
<Mask active={configUploading} message="Uploading configuration, please wait"/>
|
||||
@@ -1,88 +0,0 @@
|
||||
<script>
|
||||
import { monthnames, zeropad } from './Helpers.js';
|
||||
import BarChart from './BarChart.svelte';
|
||||
import { tariffStore, getTariff } from './DataStores';
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
let min = 0;
|
||||
|
||||
let tariffData;
|
||||
tariffStore.subscribe(update => {
|
||||
tariffData = update;
|
||||
});
|
||||
getTariff();
|
||||
|
||||
$: {
|
||||
let i = 0;
|
||||
let yTicks = [];
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
|
||||
yTicks.push({
|
||||
value: 0,
|
||||
label: 0
|
||||
});
|
||||
|
||||
if(tariffData && tariffData.p) {
|
||||
for(i = 0; i < tariffData.p.length; i++) {
|
||||
let peak = tariffData.p[i];
|
||||
points.push({
|
||||
label: peak.v.toFixed(2),
|
||||
value: peak.v,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
xTicks.push({
|
||||
label: peak.d > 0 ? zeropad(peak.d) + "." + monthnames[new Date().getMonth()] : "-"
|
||||
})
|
||||
max = Math.max(max, peak.v);
|
||||
}
|
||||
}
|
||||
|
||||
if(tariffData && tariffData.t) {
|
||||
for(i = 0; i < tariffData.t.length; i++) {
|
||||
let val = tariffData.t[i];
|
||||
if(val >= max) break;
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: val
|
||||
});
|
||||
}
|
||||
|
||||
yTicks.push({
|
||||
label: tariffData.m.toFixed(1),
|
||||
align: 'right',
|
||||
color: 'green',
|
||||
value: tariffData.m,
|
||||
});
|
||||
}
|
||||
|
||||
if(tariffData && tariffData.c) {
|
||||
yTicks.push({
|
||||
label: tariffData.c.toFixed(0),
|
||||
color: 'orange',
|
||||
value: tariffData.c,
|
||||
});
|
||||
max = Math.max(max, tariffData.c);
|
||||
}
|
||||
|
||||
max = Math.ceil(max);
|
||||
|
||||
config = {
|
||||
title: "Tariff peaks",
|
||||
padding: { top: 20, right: 35, bottom: 20, left: 35 },
|
||||
y: {
|
||||
min: min,
|
||||
max: max,
|
||||
ticks: yTicks
|
||||
},
|
||||
x: {
|
||||
ticks: xTicks
|
||||
},
|
||||
points: points
|
||||
};
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<BarChart config={config} />
|
||||
@@ -1,66 +0,0 @@
|
||||
<script>
|
||||
import BarChart from './BarChart.svelte';
|
||||
|
||||
export let json;
|
||||
|
||||
let config = {};
|
||||
let max = 0;
|
||||
let min = 0;
|
||||
|
||||
$: {
|
||||
let i = 0;
|
||||
let val = 0;
|
||||
let yTicks = [];
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
if(json.s) {
|
||||
json.s.forEach((obj, i) => {
|
||||
var name = obj.n ? obj.n : obj.a;
|
||||
val = obj.v;
|
||||
if(val == -127) val = 0;
|
||||
xTicks.push({
|
||||
label: name.slice(-4)
|
||||
});
|
||||
points.push({
|
||||
label: val.toFixed(1),
|
||||
value: val,
|
||||
color: '#7c3aed'
|
||||
});
|
||||
min = Math.min(min, val);
|
||||
max = Math.max(max, val);
|
||||
});
|
||||
}
|
||||
|
||||
max = Math.ceil(max);
|
||||
min = Math.floor(min);
|
||||
let range = max;
|
||||
if(min < 0) range += Math.abs(min);
|
||||
let yTickDist = range/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
val = min + (yTickDist*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: val.toFixed(1)
|
||||
});
|
||||
}
|
||||
|
||||
config = {
|
||||
title: "Temperature sensors (°C)",
|
||||
height: 226,
|
||||
width: 1520,
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
y: {
|
||||
min: min,
|
||||
max: max,
|
||||
ticks: yTicks
|
||||
},
|
||||
x: {
|
||||
ticks: xTicks
|
||||
},
|
||||
points: points
|
||||
};
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<BarChart config={config} />
|
||||
@@ -1,58 +0,0 @@
|
||||
<script>
|
||||
export let chip;
|
||||
</script>
|
||||
|
||||
<option value={3}>UART0</option>
|
||||
{#if chip == 'esp8266'}
|
||||
<option value={113}>UART2</option>
|
||||
{/if}
|
||||
{#if chip == 'esp32' || chip == 'esp32solo'}
|
||||
<option value={9}>UART1</option>
|
||||
<option value={16}>UART2</option>
|
||||
{/if}
|
||||
{#if chip == 'esp32s2'}
|
||||
<option value={18}>UART1</option>
|
||||
{/if}
|
||||
<option value={4}>GPIO4</option>
|
||||
<option value={5}>GPIO5</option>
|
||||
{#if chip.startsWith('esp32')}
|
||||
<option value={6}>GPIO6</option>
|
||||
<option value={7}>GPIO7</option>
|
||||
<option value={8}>GPIO8</option>
|
||||
{/if}
|
||||
{#if chip == 'esp8266'}
|
||||
<option value={9}>GPIO9</option>
|
||||
{/if}
|
||||
<option value={10}>GPIO10</option>
|
||||
{#if chip.startsWith('esp32')}
|
||||
<option value={11}>GPIO11</option>
|
||||
{/if}
|
||||
<option value={12}>GPIO12</option>
|
||||
<option value={13}>GPIO13</option>
|
||||
<option value={14}>GPIO14</option>
|
||||
<option value={15}>GPIO15</option>
|
||||
|
||||
{#if chip.startsWith('esp32')}
|
||||
<option value={17}>GPIO17</option>
|
||||
{#if chip != 'esp32s2'}
|
||||
<option value={18}>GPIO18</option>
|
||||
{/if}
|
||||
<option value={19}>GPIO19</option>
|
||||
<option value={21}>GPIO21</option>
|
||||
<option value={22}>GPIO22</option>
|
||||
<option value={23}>GPIO23</option>
|
||||
<option value={25}>GPIO25</option>
|
||||
<option value={32}>GPIO32</option>
|
||||
<option value={33}>GPIO33</option>
|
||||
<option value={34}>GPIO34</option>
|
||||
<option value={35}>GPIO35</option>
|
||||
<option value={36}>GPIO36</option>
|
||||
<option value={39}>GPIO39</option>
|
||||
{/if}
|
||||
{#if chip == 'esp32s2'}
|
||||
<option value={40}>GPIO40</option>
|
||||
<option value={41}>GPIO41</option>
|
||||
<option value={42}>GPIO42</option>
|
||||
<option value={43}>GPIO43</option>
|
||||
<option value={44}>GPIO44</option>
|
||||
{/if}
|
||||
@@ -1,63 +0,0 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<script>
|
||||
export let epoch;
|
||||
let days = 0;
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
$: {
|
||||
days = Math.floor(epoch/86400);
|
||||
hours = Math.floor(epoch/3600);
|
||||
minutes = Math.floor(epoch/60);
|
||||
}
|
||||
</script>
|
||||
{#if epoch}
|
||||
Up
|
||||
{#if days > 1}
|
||||
{days} days
|
||||
{:else if days > 0}
|
||||
{days} day
|
||||
{:else if hours > 1}
|
||||
{hours} hours
|
||||
{:else if hours > 0}
|
||||
{hours} hour
|
||||
{:else if minutes > 1}
|
||||
{minutes} minutes
|
||||
{:else if minutes > 0}
|
||||
{minutes} minute
|
||||
{:else}
|
||||
{epoch} seconds
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -1,65 +0,0 @@
|
||||
<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';
|
||||
|
||||
export let sysinfo = {}
|
||||
|
||||
let loadingOrSaving = false;
|
||||
async function handleSubmit(e) {
|
||||
loadingOrSaving = true;
|
||||
const formData = new FormData(e.target)
|
||||
const data = new URLSearchParams()
|
||||
for (let field of formData) {
|
||||
const [key, value] = field
|
||||
data.append(key, value)
|
||||
}
|
||||
|
||||
const response = await fetch('/save', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
});
|
||||
let res = (await response.json())
|
||||
loadingOrSaving = false;
|
||||
|
||||
sysinfoStore.update(s => {
|
||||
s.vndcfg = res.success;
|
||||
s.booting = res.reboot;
|
||||
return s;
|
||||
});
|
||||
navigate(sysinfo.usrcfg ? "/" : "/setup");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid xl:grid-cols-4 lg:grid-cols-3 md:grid-cols-2">
|
||||
<div class="cnt">
|
||||
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||
<input type="hidden" name="v" value="true"/>
|
||||
<strong class="text-sm">Initial configuration</strong>
|
||||
<div class="my-3">
|
||||
Board type<br/>
|
||||
<select name="vb" bind:value={sysinfo.board} class="in-s">
|
||||
<BoardTypeSelectOptions chip={sysinfo.chip}/>
|
||||
</select>
|
||||
</div>
|
||||
{#if sysinfo.board && sysinfo.board > 20}
|
||||
<div class="my-3">
|
||||
HAN GPIO<br/>
|
||||
<select name="vh" class="in-s">
|
||||
<UartSelectOptions chip={sysinfo.chip}/>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="my-3">
|
||||
<label><input type="checkbox" name="vr" value="true" class="rounded mb-1" checked /> Clear all other configuration</label>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<button type="submit" class="btn-pri">Save</button>
|
||||
</div>
|
||||
<span class="clear-both"> </span>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<Mask active={loadingOrSaving} message="Saving device configuration" />
|
||||
@@ -1,59 +0,0 @@
|
||||
<script>
|
||||
import BarChart from './BarChart.svelte';
|
||||
import { voltcol } from './Helpers.js';
|
||||
|
||||
export let u1;
|
||||
export let u2;
|
||||
export let u3;
|
||||
export let ds;
|
||||
|
||||
let min = 200;
|
||||
let max = 260;
|
||||
let config = {};
|
||||
|
||||
$: {
|
||||
let xTicks = [];
|
||||
let points = [];
|
||||
if(u1 > 0) {
|
||||
xTicks.push({ label: ds === 1 ? 'L1-L2' : 'L1' });
|
||||
points.push({
|
||||
label: u1 ? u1.toFixed(0) + 'V' : '-',
|
||||
value: u1 ? u1 : 0,
|
||||
color: voltcol(u1 ? (u1-min)/(max-min)*100 : 0)
|
||||
});
|
||||
}
|
||||
if(u2 > 0) {
|
||||
xTicks.push({ label: ds === 1 ? 'L1-L3' : 'L2' });
|
||||
points.push({
|
||||
label: u2 ? u2.toFixed(0) + 'V' : '-',
|
||||
value: u2 ? u2 : 0,
|
||||
color: voltcol(u2 ? (u2-min)/(max-min)*100 : 0)
|
||||
});
|
||||
}
|
||||
if(u3 > 0) {
|
||||
xTicks.push({ label: ds === 1 ? 'L2-L3' : 'L3' });
|
||||
points.push({
|
||||
label: u3 ? u3.toFixed(0) + 'V' : '-',
|
||||
value: u3 ? u3 : 0,
|
||||
color: voltcol(u3 ? (u3-min)/(max-min)*100 : 0)
|
||||
});
|
||||
}
|
||||
config = {
|
||||
padding: { top: 20, right: 15, bottom: 20, left: 35 },
|
||||
y: {
|
||||
min: min,
|
||||
max: max,
|
||||
ticks: [
|
||||
{ value: 207, label: '-10%' },
|
||||
{ value: 230, label: '230v' },
|
||||
{ value: 253, label: '+10%' }
|
||||
]
|
||||
},
|
||||
x: {
|
||||
ticks: xTicks
|
||||
},
|
||||
points: points
|
||||
};
|
||||
}
|
||||
</script>
|
||||
<BarChart config={config} />
|
||||
@@ -1,9 +0,0 @@
|
||||
import "./app.postcss";
|
||||
import App from "./App.svelte";
|
||||
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("app"),
|
||||
});
|
||||
|
||||
export default app;
|
||||
2
lib/SvelteUi/app/src/vite-env.d.ts
vendored
2
lib/SvelteUi/app/src/vite-env.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,11 +0,0 @@
|
||||
import preprocess from "svelte-preprocess";
|
||||
|
||||
const config = {
|
||||
preprocess: [
|
||||
preprocess({
|
||||
postcss: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,13 +0,0 @@
|
||||
const config = {
|
||||
content: ["./index.html","./src/**/*.{html,js,svelte,ts}"],
|
||||
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
require('@tailwindcss/forms')
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
assetsDir: '.',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
assetFileNames: '[name][extname]',
|
||||
chunkFileNames: '[name].js',
|
||||
entryFileNames: '[name].js'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [svelte()],
|
||||
server: {
|
||||
proxy: {
|
||||
"/data.json": "http://192.168.233.229",
|
||||
"/energyprice.json": "http://192.168.233.229",
|
||||
"/dayplot.json": "http://192.168.233.229",
|
||||
"/monthplot.json": "http://192.168.233.229",
|
||||
"/temperature.json": "http://192.168.233.229",
|
||||
"/sysinfo.json": "http://192.168.233.229",
|
||||
"/configuration.json": "http://192.168.233.229",
|
||||
"/tariff.json": "http://192.168.233.229",
|
||||
"/save": "http://192.168.233.229",
|
||||
"/reboot": "http://192.168.233.229",
|
||||
"/configfile": "http://192.168.233.229",
|
||||
"/upgrade": "http://192.168.233.229"
|
||||
}
|
||||
}
|
||||
})
|
||||
2
lib/SvelteUi/include/.gitignore
vendored
2
lib/SvelteUi/include/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
html/*.h
|
||||
json/*.h
|
||||
@@ -1,17 +0,0 @@
|
||||
static const char HEADER_CACHE_CONTROL[] PROGMEM = "Cache-Control";
|
||||
static const char HEADER_PRAGMA[] PROGMEM = "Pragma";
|
||||
static const char HEADER_EXPIRES[] PROGMEM = "Expires";
|
||||
static const char HEADER_AUTHENTICATE[] PROGMEM = "WWW-Authenticate";
|
||||
static const char HEADER_LOCATION[] PROGMEM = "Location";
|
||||
|
||||
static const char CACHE_CONTROL_NO_CACHE[] PROGMEM = "no-cache, no-store, must-revalidate";
|
||||
static const char CACHE_1HR[] PROGMEM = "public, max-age=3600";
|
||||
static const char PRAGMA_NO_CACHE[] PROGMEM = "no-cache";
|
||||
static const char EXPIRES_OFF[] PROGMEM = "-1";
|
||||
static const char AUTHENTICATE_BASIC[] PROGMEM = "Basic realm=\"Secure Area\"";
|
||||
|
||||
static const char MIME_PLAIN[] PROGMEM = "text/plain";
|
||||
static const char MIME_HTML[] PROGMEM = "text/html";
|
||||
static const char MIME_JSON[] PROGMEM = "application/json";
|
||||
static const char MIME_CSS[] PROGMEM = "text/css";
|
||||
static const char MIME_JS[] PROGMEM = "text/javascript";
|
||||
@@ -1,118 +0,0 @@
|
||||
#ifndef _AMSWEBSERVER_h
|
||||
#define _AMSWEBSERVER_h
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <MQTT.h>
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HwTools.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "Uptime.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <ESP8266httpUpdate.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HTTPUpdate.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
#include "LittleFS.h"
|
||||
|
||||
class AmsWebServer {
|
||||
public:
|
||||
AmsWebServer(uint8_t* buf, RemoteDebug* Debug, HwTools* hw);
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*);
|
||||
void loop();
|
||||
void setMqtt(MQTTClient* mqtt);
|
||||
void setTimezone(Timezone* tz);
|
||||
void setMqttEnabled(bool);
|
||||
void setEntsoeApi(EntsoeApi* eapi);
|
||||
void setPriceRegion(String);
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
bool mqttEnabled = false;
|
||||
int maxPwr = 0;
|
||||
HwTools* hw;
|
||||
Timezone* tz;
|
||||
EntsoeApi* eapi = NULL;
|
||||
AmsConfiguration* config;
|
||||
GpioConfig* gpioConfig;
|
||||
MeterConfig* meterConfig;
|
||||
WebConfig webConfig;
|
||||
AmsData* meterState;
|
||||
AmsDataStorage* ds;
|
||||
EnergyAccounting* ea = NULL;
|
||||
MQTTClient* mqtt = NULL;
|
||||
bool uploading = false;
|
||||
File file;
|
||||
bool performRestart = false;
|
||||
bool performUpgrade = false;
|
||||
bool rebootForUpgrade = false;
|
||||
String priceRegion = "";
|
||||
#if defined(AMS2MQTT_FIRMWARE_URL)
|
||||
String customFirmwareUrl = AMS2MQTT_FIRMWARE_URL;
|
||||
#else
|
||||
String customFirmwareUrl;
|
||||
#endif
|
||||
|
||||
static const uint16_t BufferSize = 2048;
|
||||
char* buf;
|
||||
|
||||
#if defined(ESP8266)
|
||||
ESP8266WebServer server;
|
||||
#elif defined(ESP32)
|
||||
WebServer server;
|
||||
#endif
|
||||
|
||||
bool checkSecurity(byte level, bool send401 = true);
|
||||
|
||||
void indexHtml();
|
||||
void indexJs();
|
||||
void indexCss();
|
||||
void githubSvg();
|
||||
void faviconSvg();
|
||||
|
||||
void sysinfoJson();
|
||||
void dataJson();
|
||||
void dayplotJson();
|
||||
void monthplotJson();
|
||||
void energyPriceJson();
|
||||
void temperatureJson();
|
||||
void tariffJson();
|
||||
|
||||
void configurationJson();
|
||||
void handleSave();
|
||||
void reboot();
|
||||
void upgrade();
|
||||
void firmwareHtml();
|
||||
void firmwarePost();
|
||||
void firmwareUpload();
|
||||
void isAliveCheck();
|
||||
|
||||
void mqttCaUpload();
|
||||
void mqttCertUpload();
|
||||
void mqttKeyUpload();
|
||||
HTTPUpload& uploadFile(const char* path);
|
||||
|
||||
void configFileDownload();
|
||||
void configFileUpload();
|
||||
void factoryResetPost();
|
||||
|
||||
void notFound();
|
||||
void redirectToMain();
|
||||
void robotstxt();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,5 +0,0 @@
|
||||
"d": {
|
||||
"s": %s,
|
||||
"t": %s,
|
||||
"l": %d
|
||||
},
|
||||
@@ -1,7 +0,0 @@
|
||||
"o": {
|
||||
"e" : %d,
|
||||
"c" : %d,
|
||||
"u1" : %d,
|
||||
"u2" : %d,
|
||||
"u3" : %d
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
"g": {
|
||||
"t": "%s",
|
||||
"h": "%s",
|
||||
"s": %d,
|
||||
"u": "%s",
|
||||
"p": "%s"
|
||||
},
|
||||
@@ -1,28 +0,0 @@
|
||||
"i": {
|
||||
"h": %s,
|
||||
"a": %s,
|
||||
"l": {
|
||||
"p": %s,
|
||||
"i": %s
|
||||
},
|
||||
"r": {
|
||||
"r": %s,
|
||||
"g": %s,
|
||||
"b": %s,
|
||||
"i": %s
|
||||
},
|
||||
"t": {
|
||||
"d": %s,
|
||||
"a": %s
|
||||
},
|
||||
"v": {
|
||||
"p": %s,
|
||||
"o": %.2f,
|
||||
"m": %.3f,
|
||||
"d": {
|
||||
"v": %d,
|
||||
"g": %d
|
||||
},
|
||||
"b": %.1f
|
||||
}
|
||||
},
|
||||
@@ -1,20 +0,0 @@
|
||||
"m": {
|
||||
"b": %d,
|
||||
"p": %d,
|
||||
"i": %s,
|
||||
"d": %d,
|
||||
"f": %d,
|
||||
"r": %d,
|
||||
"e": {
|
||||
"e": %s,
|
||||
"k": "%s",
|
||||
"a": "%s"
|
||||
},
|
||||
"m": {
|
||||
"e": %s,
|
||||
"w": %.3f,
|
||||
"v": %.3f,
|
||||
"a": %.3f,
|
||||
"c": %.3f
|
||||
}
|
||||
},
|
||||
@@ -1,15 +0,0 @@
|
||||
"q": {
|
||||
"h": "%s",
|
||||
"p": %d,
|
||||
"u": "%s",
|
||||
"a": "%s",
|
||||
"c": "%s",
|
||||
"b": "%s",
|
||||
"m": %d,
|
||||
"s": {
|
||||
"e": %s,
|
||||
"c": %s,
|
||||
"r": %s,
|
||||
"k": %s
|
||||
}
|
||||
},
|
||||
@@ -1,11 +0,0 @@
|
||||
"n": {
|
||||
"m": "%s",
|
||||
"i": "%s",
|
||||
"s": "%s",
|
||||
"g": "%s",
|
||||
"d1": "%s",
|
||||
"d2": "%s",
|
||||
"d": %s,
|
||||
"n1": "%s",
|
||||
"h": %s
|
||||
},
|
||||
@@ -1,7 +0,0 @@
|
||||
"p": {
|
||||
"e": %s,
|
||||
"t": "%s",
|
||||
"r": "%s",
|
||||
"c": "%s",
|
||||
"m": %.3f
|
||||
},
|
||||
@@ -1,15 +0,0 @@
|
||||
"t": {
|
||||
"t": [
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d
|
||||
],
|
||||
"h": %d
|
||||
},
|
||||
@@ -1,13 +0,0 @@
|
||||
"u": {
|
||||
"i": %d,
|
||||
"e": %d,
|
||||
"v": %d,
|
||||
"a": %d,
|
||||
"r": %d,
|
||||
"c": %d,
|
||||
"t": %d,
|
||||
"p": %d,
|
||||
"d": %d,
|
||||
"m": %d,
|
||||
"s": %d
|
||||
},
|
||||
@@ -1,7 +0,0 @@
|
||||
"w": {
|
||||
"s": "%s",
|
||||
"p": "%s",
|
||||
"w": %.1f,
|
||||
"z": %d,
|
||||
"a": %s
|
||||
},
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"im" : %d,
|
||||
"om" : %d,
|
||||
"mf" : %d,
|
||||
"i" : %d,
|
||||
"e" : %d,
|
||||
"ri" : %d,
|
||||
"re" : %d,
|
||||
"ic" : %.3f,
|
||||
"ec" : %.3f,
|
||||
"ric" : %.3f,
|
||||
"rec" : %.3f,
|
||||
"u1" : %.2f,
|
||||
"u2" : %.2f,
|
||||
"u3" : %.2f,
|
||||
"i1" : %.2f,
|
||||
"i2" : %.2f,
|
||||
"i3" : %.2f,
|
||||
"f" : %.2f,
|
||||
"f1" : %.2f,
|
||||
"f2" : %.2f,
|
||||
"f3" : %.2f,
|
||||
"v" : %.3f,
|
||||
"r" : %d,
|
||||
"t" : %.2f,
|
||||
"u" : %lu,
|
||||
"m" : %lu,
|
||||
"em" : %d,
|
||||
"hm" : %d,
|
||||
"wm" : %d,
|
||||
"mm" : %d,
|
||||
"me" : %d,
|
||||
"p" : %s,
|
||||
"mt" : %d,
|
||||
"ds" : %d,
|
||||
"ea" : {
|
||||
"x" : %.1f,
|
||||
"p" : [ %s ],
|
||||
"t" : %d,
|
||||
"h" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"d" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
},
|
||||
"m" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f,
|
||||
"p" : %.2f,
|
||||
"i" : %.2f
|
||||
}
|
||||
},
|
||||
"pr" : "%s",
|
||||
"he" : %d,
|
||||
"ee" : %d,
|
||||
"c" : %lu,
|
||||
"a" : %s
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"i00" : %.2f,
|
||||
"i01" : %.2f,
|
||||
"i02" : %.2f,
|
||||
"i03" : %.2f,
|
||||
"i04" : %.2f,
|
||||
"i05" : %.2f,
|
||||
"i06" : %.2f,
|
||||
"i07" : %.2f,
|
||||
"i08" : %.2f,
|
||||
"i09" : %.2f,
|
||||
"i10" : %.2f,
|
||||
"i11" : %.2f,
|
||||
"i12" : %.2f,
|
||||
"i13" : %.2f,
|
||||
"i14" : %.2f,
|
||||
"i15" : %.2f,
|
||||
"i16" : %.2f,
|
||||
"i17" : %.2f,
|
||||
"i18" : %.2f,
|
||||
"i19" : %.2f,
|
||||
"i20" : %.2f,
|
||||
"i21" : %.2f,
|
||||
"i22" : %.2f,
|
||||
"i23" : %.2f,
|
||||
"e00" : %.2f,
|
||||
"e01" : %.2f,
|
||||
"e02" : %.2f,
|
||||
"e03" : %.2f,
|
||||
"e04" : %.2f,
|
||||
"e05" : %.2f,
|
||||
"e06" : %.2f,
|
||||
"e07" : %.2f,
|
||||
"e08" : %.2f,
|
||||
"e09" : %.2f,
|
||||
"e10" : %.2f,
|
||||
"e11" : %.2f,
|
||||
"e12" : %.2f,
|
||||
"e13" : %.2f,
|
||||
"e14" : %.2f,
|
||||
"e15" : %.2f,
|
||||
"e16" : %.2f,
|
||||
"e17" : %.2f,
|
||||
"e18" : %.2f,
|
||||
"e19" : %.2f,
|
||||
"e20" : %.2f,
|
||||
"e21" : %.2f,
|
||||
"e22" : %.2f,
|
||||
"e23" : %.2f
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"currency" : "%s",
|
||||
"00" : %s,
|
||||
"01" : %s,
|
||||
"02" : %s,
|
||||
"03" : %s,
|
||||
"04" : %s,
|
||||
"05" : %s,
|
||||
"06" : %s,
|
||||
"07" : %s,
|
||||
"08" : %s,
|
||||
"09" : %s,
|
||||
"10" : %s,
|
||||
"11" : %s,
|
||||
"12" : %s,
|
||||
"13" : %s,
|
||||
"14" : %s,
|
||||
"15" : %s,
|
||||
"16" : %s,
|
||||
"17" : %s,
|
||||
"18" : %s,
|
||||
"19" : %s,
|
||||
"20" : %s,
|
||||
"21" : %s,
|
||||
"22" : %s,
|
||||
"23" : %s,
|
||||
"24" : %s,
|
||||
"25" : %s,
|
||||
"26" : %s,
|
||||
"27" : %s,
|
||||
"28" : %s,
|
||||
"29" : %s,
|
||||
"30" : %s,
|
||||
"31" : %s,
|
||||
"32" : %s,
|
||||
"33" : %s,
|
||||
"34" : %s,
|
||||
"35" : %s
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<html>
|
||||
<form action="/firmware" enctype="multipart/form-data" method="post" autocomplete="off">
|
||||
File: <input name="file" type="file" accept=".bin">
|
||||
<button type="submit" class="">Upload</button>
|
||||
</form>
|
||||
or<br/><br/>
|
||||
<form action="/firmware" method="post" autocomplete="off">
|
||||
URL: <input name="url" type="text"/>
|
||||
<button type="submit" class="">Install</button>
|
||||
</form>
|
||||
</html>
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"i01" : %.2f,
|
||||
"i02" : %.2f,
|
||||
"i03" : %.2f,
|
||||
"i04" : %.2f,
|
||||
"i05" : %.2f,
|
||||
"i06" : %.2f,
|
||||
"i07" : %.2f,
|
||||
"i08" : %.2f,
|
||||
"i09" : %.2f,
|
||||
"i10" : %.2f,
|
||||
"i11" : %.2f,
|
||||
"i12" : %.2f,
|
||||
"i13" : %.2f,
|
||||
"i14" : %.2f,
|
||||
"i15" : %.2f,
|
||||
"i16" : %.2f,
|
||||
"i17" : %.2f,
|
||||
"i18" : %.2f,
|
||||
"i19" : %.2f,
|
||||
"i20" : %.2f,
|
||||
"i21" : %.2f,
|
||||
"i22" : %.2f,
|
||||
"i23" : %.2f,
|
||||
"i24" : %.2f,
|
||||
"i25" : %.2f,
|
||||
"i26" : %.2f,
|
||||
"i27" : %.2f,
|
||||
"i28" : %.2f,
|
||||
"i29" : %.2f,
|
||||
"i30" : %.2f,
|
||||
"i31" : %.2f,
|
||||
"e01" : %.2f,
|
||||
"e02" : %.2f,
|
||||
"e03" : %.2f,
|
||||
"e04" : %.2f,
|
||||
"e05" : %.2f,
|
||||
"e06" : %.2f,
|
||||
"e07" : %.2f,
|
||||
"e08" : %.2f,
|
||||
"e09" : %.2f,
|
||||
"e10" : %.2f,
|
||||
"e11" : %.2f,
|
||||
"e12" : %.2f,
|
||||
"e13" : %.2f,
|
||||
"e14" : %.2f,
|
||||
"e15" : %.2f,
|
||||
"e16" : %.2f,
|
||||
"e17" : %.2f,
|
||||
"e18" : %.2f,
|
||||
"e19" : %.2f,
|
||||
"e20" : %.2f,
|
||||
"e21" : %.2f,
|
||||
"e22" : %.2f,
|
||||
"e23" : %.2f,
|
||||
"e24" : %.2f,
|
||||
"e25" : %.2f,
|
||||
"e26" : %.2f,
|
||||
"e27" : %.2f,
|
||||
"e28" : %.2f,
|
||||
"e29" : %.2f,
|
||||
"e30" : %.2f,
|
||||
"e31" : %.2f
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"d": %d,
|
||||
"v": %.2f
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"success": %s,
|
||||
"message": "%s",
|
||||
"reboot": %s
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"version": "%s",
|
||||
"chip": "%s",
|
||||
"chipId": "%s",
|
||||
"mac": "%s",
|
||||
"apmac": "%s",
|
||||
"board": %d,
|
||||
"vndcfg": %s,
|
||||
"usrcfg": %s,
|
||||
"fwconsent": %d,
|
||||
"hostname": "%s",
|
||||
"booting": %s,
|
||||
"upgrading": %s,
|
||||
"net": {
|
||||
"ip": "%s",
|
||||
"mask": "%s",
|
||||
"gw": "%s",
|
||||
"dns1": "%s",
|
||||
"dns2": "%s"
|
||||
},
|
||||
"meter": {
|
||||
"mfg": %d,
|
||||
"model": "%s",
|
||||
"id": "%s"
|
||||
},
|
||||
"ui": {
|
||||
"i": %d,
|
||||
"e": %d,
|
||||
"v": %d,
|
||||
"a": %d,
|
||||
"r": %d,
|
||||
"c": %d,
|
||||
"t": %d,
|
||||
"p": %d,
|
||||
"d": %d,
|
||||
"m": %d,
|
||||
"s": %d
|
||||
},
|
||||
"security": %d
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"t": [
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d,
|
||||
%d
|
||||
],
|
||||
"p": [ %s ],
|
||||
"c": %d,
|
||||
"m": %.2f
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"i" : %d,
|
||||
"a" : "%s",
|
||||
"n" : "%s",
|
||||
"c" : %d,
|
||||
"v" : %.1f
|
||||
},
|
||||
@@ -1,82 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import html_minify, js_minify, css_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import html_minify, js_minify, css_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
srcroot = "lib/SvelteUi/include/html"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for webroot in ["lib/SvelteUi/app/dist", "lib/SvelteUi/json"]:
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
content = content.replace("index.js", "index-"+version+".js")
|
||||
content = content.replace("index.css", "index-"+version+".css")
|
||||
|
||||
try:
|
||||
if filename.endswith(".html"):
|
||||
content = html_minify(content)
|
||||
elif filename.endswith(".css"):
|
||||
content = css_minify(content)
|
||||
elif filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,74 +2,63 @@
|
||||
extra_configs = platformio-user.ini
|
||||
|
||||
[common]
|
||||
lib_deps = EEPROM, LittleFS, DNSServer, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, EntsoePriceApi, EnergyAccounting, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, SvelteUi
|
||||
lib_deps = Timezone@1.2.4, 256dpi/MQTT@2.5.0, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1
|
||||
lib_ignore = OneWire
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
lib/JsonMqttHandler/scripts/generate_includes.py
|
||||
lib/DomoticzMqttHandler/scripts/generate_includes.py
|
||||
lib/HomeAssistantMqttHandler/scripts/generate_includes.py
|
||||
lib/SvelteUi/scripts/generate_includes.py
|
||||
build_flags =
|
||||
-D WEBSOCKET_DISABLED=1
|
||||
-D NO_AMS2MQTT_PRICE_KEY
|
||||
-D NO_AMS2MQTT_PRICE_AUTHENTICATION
|
||||
-fexceptions
|
||||
|
||||
[esp32]
|
||||
lib_deps = WiFi, ESPmDNS, WiFiClientSecure, HTTPClient, FS, Update, HTTPUpdate, WebServer, ${common.lib_deps}
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266@3.2.0
|
||||
framework = arduino
|
||||
board = esp12e
|
||||
board_build.ldscript = eagle.flash.4m2m.ld
|
||||
build_flags = ${common.build_flags}
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ESP8266WiFi, ESP8266mDNS, ESP8266WebServer, ESP8266HTTPClient, ESP8266httpUpdate, ${common.lib_deps}
|
||||
build_flags = -D WEBSOCKET_DISABLED=1
|
||||
lib_deps = ${common.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
# Sticking to v2.0.3 because of #298
|
||||
|
||||
[env:esp32]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.3/platform-espressif32-2.0.5.3.zip
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3/platform-espressif32-2.0.3.zip
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
board_build.f_cpu = 160000000L
|
||||
build_flags = ${common.build_flags}
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||
lib_deps = ${common.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
# Tasmota has pre-built platform for C3, S2, S3 and Solo, more information at:
|
||||
# https://github.com/Jason2866/esp32-arduino-lib-builder
|
||||
|
||||
[env:esp32s2]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.3/platform-espressif32-2.0.5.3.zip
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3/platform-espressif32-2.0.3.zip
|
||||
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
|
||||
framework = arduino
|
||||
board = esp32-s2-saola-1
|
||||
board = esp32dev
|
||||
board_build.mcu = esp32s2
|
||||
board_build.variant = esp32s2
|
||||
board_build.flash_mode = qio
|
||||
board_build.f_cpu = 160000000L
|
||||
board_build.f_flash = 40000000L
|
||||
build_flags = ${common.build_flags}
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
build_flags = -D WEBSOCKET_DISABLED=1
|
||||
lib_deps = ${common.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
[env:esp32solo]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.3/platform-espressif32-2.0.5.3.zip
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v.2.0.3/platform-espressif32-solo1-v.2.0.3.zip
|
||||
framework = arduino
|
||||
board = esp32-solo1
|
||||
board = esp32dev
|
||||
board_build.f_cpu = 160000000L
|
||||
build_flags = ${common.build_flags} -DFRAMEWORK_ARDUINO_SOLO1
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ${esp32.lib_deps}
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||
lib_deps = ${common.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
import subprocess
|
||||
from time import time
|
||||
|
||||
FILENAME_VERSION_H = 'lib/AmsConfiguration/include/version.h'
|
||||
FILENAME_VERSION_H = 'src/version.h'
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
|
||||
@@ -25,8 +25,8 @@ except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/ClassicUi/html"
|
||||
srcroot = "lib/ClassicUi/include/root"
|
||||
webroot = "web"
|
||||
srcroot = "src/web/root"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
@@ -1,18 +1,12 @@
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
|
||||
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_SYSTEM_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
} else {
|
||||
config.boardType = 0xFF;
|
||||
config.vendorConfigured = false;
|
||||
config.userConfigured = false;
|
||||
config.dataCollectionConsent = 0;
|
||||
strcpy(config.country, "");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -54,8 +48,6 @@ bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) {
|
||||
wifiChanged |= strcmp(config.hostname, existing.hostname) != 0;
|
||||
wifiChanged |= config.power != existing.power;
|
||||
wifiChanged |= config.sleep != existing.sleep;
|
||||
wifiChanged |= config.mode != existing.mode;
|
||||
wifiChanged |= config.autoreboot != existing.autoreboot;
|
||||
} else {
|
||||
wifiChanged = true;
|
||||
}
|
||||
@@ -218,8 +210,8 @@ bool AmsConfiguration::setMeterConfig(MeterConfig& config) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearMeter(MeterConfig& config) {
|
||||
config.baud = 0;
|
||||
config.parity = 0;
|
||||
config.baud = 2400;
|
||||
config.parity = 11; // 8E1
|
||||
config.invert = false;
|
||||
config.distributionSystem = 0;
|
||||
config.mainFuse = 0;
|
||||
@@ -230,8 +222,6 @@ void AmsConfiguration::clearMeter(MeterConfig& config) {
|
||||
config.voltageMultiplier = 0;
|
||||
config.amperageMultiplier = 0;
|
||||
config.accumulatedMultiplier = 0;
|
||||
config.source = 1; // Serial
|
||||
config.parser = 0; // Auto
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isMeterChanged() {
|
||||
@@ -440,8 +430,9 @@ bool AmsConfiguration::setNtpConfig(NtpConfig& config) {
|
||||
}
|
||||
}
|
||||
ntpChanged |= config.dhcp != existing.dhcp;
|
||||
ntpChanged |= config.offset != existing.offset;
|
||||
ntpChanged |= config.summerOffset != existing.summerOffset;
|
||||
ntpChanged |= strcmp(config.server, existing.server) != 0;
|
||||
ntpChanged |= strcmp(config.timezone, existing.timezone) != 0;
|
||||
} else {
|
||||
ntpChanged = true;
|
||||
}
|
||||
@@ -463,8 +454,9 @@ void AmsConfiguration::ackNtpChange() {
|
||||
void AmsConfiguration::clearNtp(NtpConfig& config) {
|
||||
config.enable = true;
|
||||
config.dhcp = true;
|
||||
config.offset = 360;
|
||||
config.summerOffset = 360;
|
||||
strcpy(config.server, "pool.ntp.org");
|
||||
strcpy(config.timezone, "Europe/Oslo");
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) {
|
||||
@@ -488,7 +480,6 @@ bool AmsConfiguration::setEntsoeConfig(EntsoeConfig& config) {
|
||||
entsoeChanged |= strcmp(config.area, existing.area) != 0;
|
||||
entsoeChanged |= strcmp(config.currency, existing.currency) != 0;
|
||||
entsoeChanged |= config.multiplier != existing.multiplier;
|
||||
entsoeChanged |= config.enabled != existing.enabled;
|
||||
} else {
|
||||
entsoeChanged = true;
|
||||
}
|
||||
@@ -549,6 +540,7 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearEnergyAccountingConfig(EnergyAccountingConfig& config) {
|
||||
@@ -573,53 +565,9 @@ void AmsConfiguration::ackEnergyAccountingChange() {
|
||||
energyAccountingChanged = false;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getUiConfig(UiConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_UI_START, config);
|
||||
if(config.showImport > 2) clearUiConfig(config); // Must be wrong
|
||||
EEPROM.end();
|
||||
return true;
|
||||
} else {
|
||||
clearUiConfig(config);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setUiConfig(UiConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_UI_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearUiConfig(UiConfig& config) {
|
||||
// 1 = Always, 2 = If value present, 0 = Hidden
|
||||
config.showImport = 1;
|
||||
config.showExport = 2;
|
||||
config.showVoltage = 2;
|
||||
config.showAmperage = 2;
|
||||
config.showReactive = 0;
|
||||
config.showRealtime = 1;
|
||||
config.showPeaks = 2;
|
||||
config.showPricePlot = 2;
|
||||
config.showDayPlot = 1;
|
||||
config.showMonthPlot = 1;
|
||||
config.showTemperaturePlot = 2;
|
||||
}
|
||||
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
SystemConfig sys;
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
sys.userConfigured = false;
|
||||
sys.dataCollectionConsent = 0;
|
||||
strcpy(sys.country, "");
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
MeterConfig meter;
|
||||
clearMeter(meter);
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
@@ -652,15 +600,7 @@ void AmsConfiguration::clear() {
|
||||
clearEnergyAccountingConfig(eac);
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
|
||||
DebugConfig debug;
|
||||
clearDebug(debug);
|
||||
EEPROM.put(CONFIG_DEBUG_START, debug);
|
||||
|
||||
UiConfig ui;
|
||||
clearUiConfig(ui);
|
||||
EEPROM.put(CONFIG_UI_START, ui);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CLEARED_INDICATOR);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, -1);
|
||||
EEPROM.commit();
|
||||
EEPROM.end();
|
||||
}
|
||||
@@ -679,6 +619,22 @@ bool AmsConfiguration::hasConfig() {
|
||||
}
|
||||
} else {
|
||||
switch(configVersion) {
|
||||
case 86:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig86()) {
|
||||
configVersion = 87;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 87:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig87()) {
|
||||
configVersion = 88;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 90:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig90()) {
|
||||
@@ -727,22 +683,6 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 96:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig96()) {
|
||||
configVersion = 100;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 100:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig100()) {
|
||||
configVersion = 101;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -795,6 +735,51 @@ void AmsConfiguration::saveTempSensors() {
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig86() {
|
||||
MqttConfig86 mqtt86;
|
||||
MqttConfig mqtt;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_MQTT_START_86, mqtt86);
|
||||
strcpy(mqtt.host, mqtt86.host);
|
||||
mqtt.port = mqtt86.port;
|
||||
strcpy(mqtt.clientId, mqtt86.clientId);
|
||||
strcpy(mqtt.publishTopic, mqtt86.publishTopic);
|
||||
strcpy(mqtt.subscribeTopic, mqtt86.subscribeTopic);
|
||||
strcpy(mqtt.username, mqtt86.username);
|
||||
strcpy(mqtt.password, mqtt86.password);
|
||||
mqtt.payloadFormat = mqtt86.payloadFormat;
|
||||
mqtt.ssl = mqtt86.ssl;
|
||||
EEPROM.put(CONFIG_MQTT_START, mqtt);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 87);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig87() {
|
||||
MeterConfig87 meter87 = {0,0,0,0,0,0,0};
|
||||
MeterConfig meter;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START_87, meter87);
|
||||
if(meter87.type < 5) {
|
||||
meter.baud = 2400;
|
||||
meter.parity = meter87.type == 3 || meter87.type == 4 ? 3 : 11;
|
||||
meter.invert = false;
|
||||
} else {
|
||||
meter.baud = 115200;
|
||||
meter.parity = 3;
|
||||
meter.invert = meter87.type == 6;
|
||||
}
|
||||
meter.distributionSystem = meter87.distributionSystem;
|
||||
meter.mainFuse = meter87.mainFuse;
|
||||
meter.productionCapacity = meter87.productionCapacity;
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 88);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig90() {
|
||||
EntsoeConfig entsoe;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
@@ -892,112 +877,6 @@ bool AmsConfiguration::relocateConfig95() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig96() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
SystemConfig sys;
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
MeterConfig meter;
|
||||
EEPROM.get(CONFIG_METER_START, meter);
|
||||
meter.source = 1; // Serial
|
||||
meter.parser = 0; // Auto
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
#if defined(ESP8266)
|
||||
GpioConfig gpio;
|
||||
EEPROM.get(CONFIG_GPIO_START, gpio);
|
||||
|
||||
switch(sys.boardType) {
|
||||
case 3: // Pow UART0 -- Now Pow-K UART0
|
||||
case 4: // Pow GPIO12 -- Now Pow-U UART0
|
||||
case 5: // Pow-K+ -- Now also Pow-K GPIO12
|
||||
case 7: // Pow-U+ -- Now also Pow-U GPIO12
|
||||
if(meter.baud == 2400 && meter.parity == 3) { // 3 == 8N1, assuming Pow-K
|
||||
if(gpio.hanPin == 3) { // UART0
|
||||
sys.boardType = 3;
|
||||
} else if(gpio.hanPin == 12) {
|
||||
sys.boardType = 5;
|
||||
}
|
||||
} else { // Assuming Pow-U
|
||||
if(gpio.hanPin == 3) { // UART0
|
||||
sys.boardType = 4;
|
||||
} else if(gpio.hanPin == 12) {
|
||||
sys.boardType = 7;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
sys.vendorConfigured = true;
|
||||
sys.userConfigured = true;
|
||||
sys.dataCollectionConsent = 0;
|
||||
strcpy(sys.country, "");
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
WiFiConfig wifi;
|
||||
EEPROM.get(CONFIG_WIFI_START, wifi);
|
||||
wifi.mode = 1; // WIFI_STA
|
||||
wifi.autoreboot = true;
|
||||
EEPROM.put(CONFIG_WIFI_START, wifi);
|
||||
|
||||
NtpConfig ntp;
|
||||
NtpConfig96 ntp96;
|
||||
EEPROM.get(CONFIG_NTP_START, ntp96);
|
||||
ntp.enable = ntp96.enable;
|
||||
ntp.dhcp = ntp96.dhcp;
|
||||
if(ntp96.offset == 360 && ntp96.summerOffset == 360) {
|
||||
strcpy(ntp.timezone, "Europe/Oslo");
|
||||
} else {
|
||||
strcpy(ntp.timezone, "GMT");
|
||||
}
|
||||
strcpy(ntp.server, ntp96.server);
|
||||
EEPROM.put(CONFIG_NTP_START, ntp);
|
||||
|
||||
EntsoeConfig entsoe;
|
||||
EEPROM.get(CONFIG_ENTSOE_START, entsoe);
|
||||
entsoe.enabled = strlen(entsoe.token) > 0;
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 100);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig100() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
MeterConfig100 meter100;
|
||||
EEPROM.get(CONFIG_METER_START, meter100);
|
||||
MeterConfig meter;
|
||||
meter.baud = meter100.baud;
|
||||
meter.parity = meter100.parity;
|
||||
meter.invert = meter100.invert;
|
||||
meter.distributionSystem = meter100.distributionSystem;
|
||||
meter.mainFuse = meter100.mainFuse;
|
||||
meter.productionCapacity = meter100.productionCapacity;
|
||||
memcpy(meter.encryptionKey, meter100.encryptionKey, 16);
|
||||
memcpy(meter.authenticationKey, meter100.authenticationKey, 16);
|
||||
meter.wattageMultiplier = meter100.wattageMultiplier;
|
||||
meter.voltageMultiplier = meter100.voltageMultiplier;
|
||||
meter.amperageMultiplier = meter100.amperageMultiplier;
|
||||
meter.accumulatedMultiplier = meter100.accumulatedMultiplier;
|
||||
meter.source = meter100.source;
|
||||
meter.parser = meter100.parser;
|
||||
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
UiConfig ui;
|
||||
clearUiConfig(ui);
|
||||
EEPROM.put(CONFIG_UI_START, ui);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 101);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
@@ -1201,7 +1080,8 @@ void AmsConfiguration::print(Print* debugger)
|
||||
debugger->println("--NTP configuration--");
|
||||
debugger->printf("Enabled: %s\r\n", ntp.enable ? "Yes" : "No");
|
||||
if(ntp.enable) {
|
||||
debugger->printf("Timezone: %s\r\n", ntp.timezone);
|
||||
debugger->printf("Offset: %i\r\n", ntp.offset);
|
||||
debugger->printf("Summer offset: %i\r\n", ntp.summerOffset);
|
||||
debugger->printf("Server: %s\r\n", ntp.server);
|
||||
debugger->printf("DHCP: %s\r\n", ntp.dhcp ? "Yes" : "No");
|
||||
}
|
||||
@@ -1212,12 +1092,12 @@ void AmsConfiguration::print(Print* debugger)
|
||||
|
||||
EntsoeConfig entsoe;
|
||||
if(getEntsoeConfig(entsoe)) {
|
||||
if(strlen(entsoe.area) > 0) {
|
||||
debugger->println("--ENTSO-E configuration--");
|
||||
debugger->println("--ENTSO-E configuration--");
|
||||
debugger->printf("Token: %s\r\n", entsoe.token);
|
||||
if(strlen(entsoe.token) > 0) {
|
||||
debugger->printf("Area: %s\r\n", entsoe.area);
|
||||
debugger->printf("Currency: %s\r\n", entsoe.currency);
|
||||
debugger->printf("Multiplier: %f\r\n", entsoe.multiplier / 1000.0);
|
||||
debugger->printf("Token: %s\r\n", entsoe.token);
|
||||
}
|
||||
debugger->println("");
|
||||
delay(10);
|
||||
@@ -4,14 +4,12 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 101 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CLEARED_INDICATOR 0xFC
|
||||
#define EEPROM_CHECK_SUM 96 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
#define CONFIG_SYSTEM_START 8
|
||||
#define CONFIG_METER_START 32
|
||||
#define CONFIG_UI_START 248
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_ENTSOE_START 290
|
||||
#define CONFIG_WIFI_START 360
|
||||
@@ -31,11 +29,7 @@
|
||||
|
||||
struct SystemConfig {
|
||||
uint8_t boardType;
|
||||
bool vendorConfigured;
|
||||
bool userConfigured;
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[2];
|
||||
}; // 6
|
||||
}; // 1
|
||||
|
||||
struct WiFiConfig91 {
|
||||
char ssid[32];
|
||||
@@ -61,9 +55,7 @@ struct WiFiConfig {
|
||||
bool mdns;
|
||||
uint8_t power;
|
||||
uint8_t sleep;
|
||||
uint8_t mode;
|
||||
bool autoreboot;
|
||||
}; // 213
|
||||
}; // 211
|
||||
|
||||
struct MqttConfig86 {
|
||||
char host[128];
|
||||
@@ -96,23 +88,6 @@ struct WebConfig {
|
||||
}; // 129
|
||||
|
||||
struct MeterConfig {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint16_t mainFuse;
|
||||
uint16_t productionCapacity;
|
||||
uint8_t encryptionKey[16];
|
||||
uint8_t authenticationKey[16];
|
||||
uint32_t wattageMultiplier;
|
||||
uint32_t voltageMultiplier;
|
||||
uint32_t amperageMultiplier;
|
||||
uint32_t accumulatedMultiplier;
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 52
|
||||
|
||||
struct MeterConfig100 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
@@ -190,13 +165,6 @@ struct DomoticzConfig {
|
||||
}; // 10
|
||||
|
||||
struct NtpConfig {
|
||||
bool enable;
|
||||
bool dhcp;
|
||||
char server[64];
|
||||
char timezone[32];
|
||||
}; // 98
|
||||
|
||||
struct NtpConfig96 {
|
||||
bool enable;
|
||||
bool dhcp;
|
||||
int16_t offset;
|
||||
@@ -209,7 +177,6 @@ struct EntsoeConfig {
|
||||
char area[17];
|
||||
char currency[4];
|
||||
uint32_t multiplier;
|
||||
bool enabled;
|
||||
}; // 62
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
@@ -217,20 +184,6 @@ struct EnergyAccountingConfig {
|
||||
uint8_t hours;
|
||||
}; // 11
|
||||
|
||||
struct UiConfig {
|
||||
uint8_t showImport;
|
||||
uint8_t showExport;
|
||||
uint8_t showVoltage;
|
||||
uint8_t showAmperage;
|
||||
uint8_t showReactive;
|
||||
uint8_t showRealtime;
|
||||
uint8_t showPeaks;
|
||||
uint8_t showPricePlot;
|
||||
uint8_t showDayPlot;
|
||||
uint8_t showMonthPlot;
|
||||
uint8_t showTemperaturePlot;
|
||||
}; // 11
|
||||
|
||||
struct TempSensorConfig {
|
||||
uint8_t address[8];
|
||||
char name[16];
|
||||
@@ -307,10 +260,6 @@ public:
|
||||
bool isEnergyAccountingChanged();
|
||||
void ackEnergyAccountingChange();
|
||||
|
||||
bool getUiConfig(UiConfig&);
|
||||
bool setUiConfig(UiConfig&);
|
||||
void clearUiConfig(UiConfig&);
|
||||
|
||||
void loadTempSensors();
|
||||
void saveTempSensors();
|
||||
uint8_t getTempSensorCount();
|
||||
@@ -331,14 +280,14 @@ private:
|
||||
uint8_t tempSensorCount = 0;
|
||||
TempSensorConfig** tempSensors = NULL;
|
||||
|
||||
bool relocateConfig86(); // 1.5.0
|
||||
bool relocateConfig87(); // 1.5.4
|
||||
bool relocateConfig90(); // 2.0.0
|
||||
bool relocateConfig91(); // 2.0.2
|
||||
bool relocateConfig92(); // 2.0.3
|
||||
bool relocateConfig93(); // 2.1.0
|
||||
bool relocateConfig94(); // 2.1.0
|
||||
bool relocateConfig95(); // 2.1.4
|
||||
bool relocateConfig96(); // 2.1.14
|
||||
bool relocateConfig100(); // 2.2-dev
|
||||
bool relocateConfig94(); // 2.1.4
|
||||
bool relocateConfig95(); // 2.1.13
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
@@ -64,6 +64,7 @@ void AmsData::apply(AmsData& other) {
|
||||
this->meterType = other.getMeterType();
|
||||
this->meterModel = other.getMeterModel();
|
||||
this->reactiveImportPower = other.getReactiveImportPower();
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
this->reactiveExportPower = other.getReactiveExportPower();
|
||||
this->l1current = other.getL1Current();
|
||||
this->l2current = other.getL2Current();
|
||||
@@ -73,13 +74,9 @@ void AmsData::apply(AmsData& other) {
|
||||
this->l3voltage = other.getL3Voltage();
|
||||
this->threePhase = other.isThreePhase();
|
||||
this->twoPhase = other.isTwoPhase();
|
||||
case 1:
|
||||
this->activeImportPower = other.getActiveImportPower();
|
||||
}
|
||||
|
||||
// Moved outside switch to handle meters alternating between sending active and accumulated values
|
||||
if(other.getListType() == 1 || (other.getActiveImportPower() > 0 || other.getActiveExportPower() > 0))
|
||||
this->activeImportPower = other.getActiveImportPower();
|
||||
if(other.getListType() == 2 || (other.getActiveImportPower() > 0 || other.getActiveExportPower() > 0))
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
}
|
||||
|
||||
unsigned long AmsData::getLastUpdateMillis() {
|
||||
@@ -217,16 +214,3 @@ bool AmsData::isThreePhase() {
|
||||
bool AmsData::isTwoPhase() {
|
||||
return this->twoPhase;
|
||||
}
|
||||
|
||||
int8_t AmsData::getLastError() {
|
||||
return lastErrorCount > 3 ? lastError : 0;
|
||||
}
|
||||
|
||||
void AmsData::setLastError(int8_t lastError) {
|
||||
this->lastError = lastError;
|
||||
if(lastError == 0) {
|
||||
lastErrorCount = 0;
|
||||
} else {
|
||||
lastErrorCount++;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ enum AmsType {
|
||||
AmsTypeIskra = 0x08,
|
||||
AmsTypeLandisGyr = 0x09,
|
||||
AmsTypeSagemcom = 0x0A,
|
||||
AmsTypeLng = 0x0B,
|
||||
AmsTypeCustom = 0x88,
|
||||
AmsTypeUnknown = 0xFF
|
||||
};
|
||||
@@ -69,9 +70,6 @@ public:
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
|
||||
int8_t getLastError();
|
||||
void setLastError(int8_t);
|
||||
|
||||
protected:
|
||||
unsigned long lastUpdateMillis = 0;
|
||||
unsigned long lastList2 = 0;
|
||||
@@ -86,9 +84,6 @@ protected:
|
||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||
bool threePhase = false, twoPhase = false, counterEstimated = false;
|
||||
|
||||
int8_t lastError = 0x00;
|
||||
uint8_t lastErrorCount = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -5,10 +5,8 @@
|
||||
#include "version.h"
|
||||
|
||||
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
|
||||
day.version = 5;
|
||||
day.accuracy = 1;
|
||||
month.version = 6;
|
||||
month.accuracy = 1;
|
||||
day.version = 4;
|
||||
month.version = 5;
|
||||
this->debugger = debugger;
|
||||
}
|
||||
|
||||
@@ -239,160 +237,44 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setHourImport(uint8_t hour, uint32_t val) {
|
||||
void AmsDataStorage::setHourImport(uint8_t hour, int32_t val) {
|
||||
if(hour < 0 || hour > 24) return;
|
||||
|
||||
uint8_t accuracy = day.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
|
||||
day.hImport[hour] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
if(day.hImport[i] > max)
|
||||
max = day.hImport[i];
|
||||
if(day.hExport[i] > max)
|
||||
max = day.hExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
day.hImport[hour] = val / 10;
|
||||
}
|
||||
|
||||
uint32_t AmsDataStorage::getHourImport(uint8_t hour) {
|
||||
int32_t AmsDataStorage::getHourImport(uint8_t hour) {
|
||||
if(hour < 0 || hour > 24) return 0;
|
||||
return day.hImport[hour] * pow(10, day.accuracy);
|
||||
return day.hImport[hour] * 10;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setHourExport(uint8_t hour, uint32_t val) {
|
||||
void AmsDataStorage::setHourExport(uint8_t hour, int32_t val) {
|
||||
if(hour < 0 || hour > 24) return;
|
||||
|
||||
uint8_t accuracy = day.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
|
||||
day.hExport[hour] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
if(day.hImport[i] > max)
|
||||
max = day.hImport[i];
|
||||
if(day.hExport[i] > max)
|
||||
max = day.hExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != day.accuracy) {
|
||||
setDayAccuracy(accuracy);
|
||||
}
|
||||
day.hExport[hour] = val / 10;
|
||||
}
|
||||
|
||||
uint32_t AmsDataStorage::getHourExport(uint8_t hour) {
|
||||
int32_t AmsDataStorage::getHourExport(uint8_t hour) {
|
||||
if(hour < 0 || hour > 24) return 0;
|
||||
return day.hExport[hour] * pow(10, day.accuracy);
|
||||
return day.hExport[hour] * 10;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDayImport(uint8_t day, uint32_t val) {
|
||||
void AmsDataStorage::setDayImport(uint8_t day, int32_t val) {
|
||||
if(day < 1 || day > 31) return;
|
||||
|
||||
uint8_t accuracy = month.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
|
||||
month.dImport[day-1] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
if(month.dImport[i] > max)
|
||||
max = month.dImport[i];
|
||||
if(month.dExport[i] > max)
|
||||
max = month.dExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
month.dImport[day-1] = val / 10;
|
||||
}
|
||||
|
||||
uint32_t AmsDataStorage::getDayImport(uint8_t day) {
|
||||
int32_t AmsDataStorage::getDayImport(uint8_t day) {
|
||||
if(day < 1 || day > 31) return 0;
|
||||
return (month.dImport[day-1] * pow(10, month.accuracy));
|
||||
return (month.dImport[day-1] * 10);
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDayExport(uint8_t day, uint32_t val) {
|
||||
void AmsDataStorage::setDayExport(uint8_t day, int32_t val) {
|
||||
if(day < 1 || day > 31) return;
|
||||
|
||||
uint8_t accuracy = month.accuracy;
|
||||
uint32_t update = val / pow(10, accuracy);
|
||||
while(update > UINT16_MAX) {
|
||||
accuracy++;
|
||||
update = val / pow(10, accuracy);
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
|
||||
month.dExport[day-1] = update;
|
||||
|
||||
uint32_t max = 0;
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
if(month.dImport[i] > max)
|
||||
max = month.dImport[i];
|
||||
if(month.dExport[i] > max)
|
||||
max = month.dExport[i];
|
||||
}
|
||||
|
||||
while(max < UINT16_MAX/10 && accuracy > 0) {
|
||||
accuracy--;
|
||||
max = max*10;
|
||||
}
|
||||
|
||||
if(accuracy != month.accuracy) {
|
||||
setMonthAccuracy(accuracy);
|
||||
}
|
||||
month.dExport[day-1] = val / 10;
|
||||
}
|
||||
|
||||
uint32_t AmsDataStorage::getDayExport(uint8_t day) {
|
||||
int32_t AmsDataStorage::getDayExport(uint8_t day) {
|
||||
if(day < 1 || day > 31) return 0;
|
||||
return (month.dExport[day-1] * pow(10, month.accuracy));
|
||||
return (month.dExport[day-1] * 10);
|
||||
}
|
||||
|
||||
bool AmsDataStorage::load() {
|
||||
@@ -466,74 +348,31 @@ MonthDataPoints AmsDataStorage::getMonthData() {
|
||||
}
|
||||
|
||||
bool AmsDataStorage::setDayData(DayDataPoints& day) {
|
||||
if(day.version == 5) {
|
||||
if(day.version == 4) {
|
||||
this->day = day;
|
||||
return true;
|
||||
} else if(day.version == 4) {
|
||||
this->day = day;
|
||||
this->day.accuracy = 1;
|
||||
this->day.version = 5;
|
||||
return true;
|
||||
} else if(day.version == 3) {
|
||||
this->day = day;
|
||||
for(uint8_t i = 0; i < 24; i++) this->day.hExport[i] = 0;
|
||||
this->day.accuracy = 1;
|
||||
this->day.version = 5;
|
||||
this->day.version = 4;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::setMonthData(MonthDataPoints& month) {
|
||||
if(month.version == 6) {
|
||||
if(month.version == 5) {
|
||||
this->month = month;
|
||||
return true;
|
||||
} else if(month.version == 5) {
|
||||
this->month = month;
|
||||
this->month.accuracy = 1;
|
||||
this->month.version = 6;
|
||||
return true;
|
||||
} else if(month.version == 4) {
|
||||
this->month = month;
|
||||
for(uint8_t i = 0; i < 31; i++) this->month.dExport[i] = 0;
|
||||
this->month.accuracy = 1;
|
||||
this->month.version = 6;
|
||||
this->month.version = 5;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t AmsDataStorage::getDayAccuracy() {
|
||||
return day.accuracy;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDayAccuracy(uint8_t accuracy) {
|
||||
if(day.accuracy != accuracy) {
|
||||
uint16_t multiplier = pow(10, day.accuracy)/pow(10, accuracy);
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
day.hImport[i] = day.hImport[i] * multiplier;
|
||||
day.hExport[i] = day.hExport[i] * multiplier;
|
||||
}
|
||||
day.accuracy = accuracy;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t AmsDataStorage::getMonthAccuracy() {
|
||||
return month.accuracy;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setMonthAccuracy(uint8_t accuracy) {
|
||||
if(month.accuracy != accuracy) {
|
||||
uint16_t multiplier = pow(10, month.accuracy)/pow(10, accuracy);
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
month.dImport[i] = month.dImport[i] * multiplier;
|
||||
month.dExport[i] = month.dExport[i] * multiplier;
|
||||
}
|
||||
month.accuracy = accuracy;
|
||||
}
|
||||
month.accuracy = accuracy;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isHappy() {
|
||||
return isDayHappy() && isMonthHappy();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user