Compare commits

..

22 Commits

Author SHA1 Message Date
Gunnar Skjold
8a8e26dcc4 Merge branch 'main' into upgrade/svelte 2026-04-09 12:14:56 +02:00
Copilot
4673feaaf3 Fix day dropdowns in price config to respect selected month (#1168)
* Initial plan

* Fix month-dependent day dropdowns in PriceConfig.svelte

Co-authored-by: gskjold <4446828+gskjold@users.noreply.github.com>
Agent-Logs-Url: https://github.com/UtilitechAS/amsreader-firmware/sessions/cc7b8eba-e39b-461a-bd3b-7a560279afcc

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gskjold <4446828+gskjold@users.noreply.github.com>
Co-authored-by: Gunnar Skjold <gunnar.skjold@gmail.com>
2026-04-09 12:10:28 +02:00
Gunnar Skjold
2c96b4d94f Fixed tariff peaks on wrong date and time (#1159)
* Trying to fix tariff on wrong date. Also some code cleanup

* Fix issue for ex DLMS where accumulated is always included

* Stricter time restrictions when updating history

* Adjustments after testing
2026-04-09 11:41:58 +02:00
Mads Fox
6011d3169e Fix uninitialized loop variable in GcmParser causing undefined behavior (#1163)
In GcmParser::parse(), the authentication check loop used an uninitialized
loop counter: `for(uint8_t i; i < 16; i++)`. This is undefined behavior in
C++ because `i` has an indeterminate value, potentially causing the
authentication check to be skipped entirely or to read out-of-bounds memory.

Fix: initialize `i` to 0 so the loop correctly iterates all 16 bytes of
the authentication key.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 09:51:27 +02:00
Gunnar Skjold
f0c3873635 Fixed price from config file reboot loop (#1172) 2026-04-09 09:48:02 +02:00
Gunnar Skjold
fb4eea8208 Prevent boot loop in voltage check (#1171)
* Prevent boot loop if voltage is outside operating range

* Fixed code error
2026-04-09 09:47:41 +02:00
Gunnar Skjold
e628056e56 Fixed parsing of Iskra (#1158) 2026-04-09 09:46:44 +02:00
Gunnar Skjold
df5611844f Moved reset of reboot reason to main program (#1153)
* Moved reset of reboot reason to main program

* Allow up to 8 cycles to charge capacitor
2026-04-09 09:46:23 +02:00
Gunnar Skjold
3cb6e09341 Use unique SSID on first boot (#1143)
* Use unique SSID on first boot

* Debugger adjustments
2026-04-09 09:41:16 +02:00
Gunnar Skjold
f7ccd2a96b Updated copyright (#1173) 2026-04-09 09:40:09 +02:00
Gunnar Skjold
13aff62aff Fixed PR workflow double zipping (#1162)
* Added .zip extension to avoid double zipping

* Fixed double zip
2026-03-15 10:07:45 +01:00
Gunnar Skjold
64a0667947 Added workflow to attach firmware in PR (#1160)
* Added PR workflow that creates a comment with firmware zip

* Fixed URL

* Show version in comment
2026-03-15 09:31:59 +01:00
Gunnar Skjold
4d128700c1 Added UI readme 2026-03-05 16:40:05 +01:00
Gunnar Skjold
6743750d8f Made proxy target configurable 2026-03-05 16:39:20 +01:00
Gunnar Skjold
640e957065 Optimizing footprint 2026-03-05 16:34:10 +01:00
Gunnar Skjold
d4f11c0412 Updated node version in workflows 2026-03-05 16:22:40 +01:00
Gunnar Skjold
01acc6d6e8 Consolidated new routes and old components 2026-03-05 16:22:19 +01:00
Gunnar Skjold
e89bb53941 Initial changes to migrate to Svelte 5 2026-03-05 15:51:06 +01:00
Gunnar Skjold
009c4686ee Fixed incorrect color LED on boot (#1149) 2026-03-05 14:54:51 +01:00
dependabot[bot]
33dc5fc177 Bump rollup from 3.29.5 to 3.30.0 in /lib/SvelteUi/app (#1147)
Bumps [rollup](https://github.com/rollup/rollup) from 3.29.5 to 3.30.0.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/v3.30.0/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v3.29.5...v3.30.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 3.30.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 14:54:01 +01:00
dependabot[bot]
faf047e25f Bump minimatch in /lib/SvelteUi/app (#1154)
Bumps  and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together.

Updates `minimatch` from 9.0.5 to 9.0.9
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v9.0.9)

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v9.0.5...v9.0.9)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 9.0.9
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 14:53:36 +01:00
dependabot[bot]
b4322c5f9c Bump svgo from 2.8.0 to 2.8.2 in /lib/SvelteUi/app (#1157)
Bumps [svgo](https://github.com/svg/svgo) from 2.8.0 to 2.8.2.
- [Release notes](https://github.com/svg/svgo/releases)
- [Commits](https://github.com/svg/svgo/compare/v2.8.0...v2.8.2)

---
updated-dependencies:
- dependency-name: svgo
  dependency-version: 2.8.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 14:53:05 +01:00
136 changed files with 2937 additions and 3356 deletions

View File

@@ -51,7 +51,7 @@ jobs:
- name: Set up node
uses: actions/setup-node@v4
with:
node-version: '19.x'
node-version: '22.x'
- name: Build with node
run: |
cd lib/SvelteUi/app

82
.github/workflows/pr-build-env.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: PR build with env
on:
workflow_call:
inputs:
env:
description: 'The environment to build for'
required: true
type: string
is_esp32:
description: 'Whether the build is for ESP32 based firmware'
required: false
type: boolean
default: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code from repo
uses: actions/checkout@v4
- name: Cache Python dependencies
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
- name: Cache PlatformIO dependencies
uses: actions/cache@v4
with:
path: ~/.pio/libdeps
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
- name: Set up Python 3.9
uses: actions/setup-python@v5
with:
python-version: 3.9
- 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
sed -i 's/NO_AMS2MQTT_SC_KEY/AMS2MQTT_SC_KEY=\\"${{secrets.AMS2MQTT_SC_KEY}}\\"/g' platformio.ini
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -U platformio css_html_js_minify
- name: Set up node
uses: actions/setup-node@v4
with:
node-version: '19.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: Build firmware
run: pio run -e ${{ inputs.env }}
- name: Create zip file
run: /bin/sh scripts/${{ inputs.env }}/mkzip.sh
- name: Upload zip as artifact
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.env }}.zip
path: ${{ inputs.env }}.zip
archive: false
retention-days: 7

110
.github/workflows/pull-request.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: Pull Request build
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
build-esp32s2:
uses: ./.github/workflows/pr-build-env.yml
secrets: inherit
with:
env: esp32s2
is_esp32: true
build-esp32s3:
uses: ./.github/workflows/pr-build-env.yml
secrets: inherit
with:
env: esp32s3
is_esp32: true
build-esp32c3:
uses: ./.github/workflows/pr-build-env.yml
secrets: inherit
with:
env: esp32c3
is_esp32: true
build-esp32:
uses: ./.github/workflows/pr-build-env.yml
secrets: inherit
with:
env: esp32
is_esp32: true
build-esp32solo:
uses: ./.github/workflows/pr-build-env.yml
secrets: inherit
with:
env: esp32solo
is_esp32: true
build-esp8266:
uses: ./.github/workflows/pr-build-env.yml
secrets: inherit
with:
env: esp8266
is_esp32: false
comment:
needs:
- build-esp32s2
- build-esp32s3
- build-esp32c3
- build-esp32
- build-esp32solo
- build-esp8266
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Post PR comment with download links
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const prNumber = context.payload.pull_request.number;
const runId = context.runId;
const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`;
// Get the commit SHA (short version)
const sha = context.payload.pull_request.head.sha;
const shortSha = sha.substring(0, 7);
// Fetch the list of artifacts for this run via the API
const artifactsResp = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id: runId });
const artifacts = artifactsResp.data.artifacts;
const envs = ['esp32s2', 'esp32s3', 'esp32c3', 'esp32', 'esp32solo', 'esp8266'];
const lines = envs.map(env => {
const artifact = artifacts.find(a => a.name === `${env}.zip`);
if (artifact) {
// The artifact download page URL - directly navigable in the browser
const artifactUrl = `${runUrl}#artifacts-${env}`;
return `- **${env}**: [Download ${env}.zip](https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${artifact.id})`;
}
return `- **${env}**: ⚠️ artifact not found`;
});
const body = [
'## 🔧 PR Build Artifacts',
'',
`**Version**: \`${shortSha}\``,
'',
'All environments built successfully. Download the zip files:',
'',
...lines,
'',
`> Artifacts expire after 7 days. [View workflow run](${runUrl})`,
].join('\n');
// Find and delete any previous bot comment to keep the PR clean
const comments = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber });
for (const comment of comments.data) {
if (comment.user.type === 'Bot' && comment.body.includes('PR Build Artifacts')) {
await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id });
}
}
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });

View File

@@ -73,7 +73,7 @@ jobs:
- name: Set up node
uses: actions/setup-node@v4
with:
node-version: '19.x'
node-version: '22.x'
- name: Build with node
run: |
cd lib/SvelteUi/app

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -381,6 +381,8 @@ public:
bool isZmartChargeConfigChanged();
void ackZmartChargeConfig();
uint32_t getChipId();
void getUniqueName(char* buffer, size_t length);
void clear();

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -124,16 +124,12 @@ void AmsConfiguration::clearNetworkConfig(NetworkConfig& config) {
memset(config.ssid, 0, 32);
memset(config.psk, 0, 64);
clearNetworkConfigIp(config);
uint16_t chipId;
getUniqueName(config.hostname, 32);
#if defined(ESP32)
chipId = ( ESP.getEfuseMac() >> 32 ) % 0xFFFFFFFF;
config.power = 195;
#else
chipId = ESP.getChipId();
config.power = 205;
#endif
strcpy(config.hostname, (String("ams-") + String(chipId, HEX)).c_str());
config.mdns = true;
config.sleep = 0xFF;
config.use11b = 1;
@@ -983,6 +979,23 @@ void AmsConfiguration::setUiLanguageChanged() {
uiLanguageChanged = true;
}
uint32_t AmsConfiguration::getChipId() {
uint32_t chipId;
#if defined(ESP32)
for(int i=0; i<17; i=i+8) {
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
#else
chipId = ESP.getChipId();
#endif
return chipId;
}
void AmsConfiguration::getUniqueName(char* buffer, size_t length) {
uint32_t chipId = getChipId();
snprintf(buffer, length, "ams-%06x", chipId);
}
void AmsConfiguration::clear() {
EEPROM.begin(EEPROM_SIZE);

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -7,8 +7,7 @@
#ifndef _AMSDATA_H
#define _AMSDATA_H
#include "Arduino.h"
#include <Timezone.h>
#include <WString.h>
#include "OBIScodes.h"
enum AmsType {
@@ -28,7 +27,7 @@ public:
AmsData();
void apply(AmsData& other);
void apply(const OBIS_code_t obis, double value);
void apply(const OBIS_code_t obis, double value, uint64_t millis64);
uint64_t getLastUpdateMillis();

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,10 +1,11 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
#include "AmsData.h"
#include <algorithm>
AmsData::AmsData() {}
@@ -17,7 +18,6 @@ void AmsData::apply(AmsData& other) {
uint32_t power = (activeImportPower + other.getActiveImportPower()) / 2;
float add = power * (((float) ms) / 3600000.0);
activeImportCounter += add / 1000.0;
//Serial.printf("%dW, %dms, %.6fkWh added\n", other.getActiveImportPower(), ms, add);
}
if(other.getListType() > 1) {
@@ -112,7 +112,7 @@ void AmsData::apply(AmsData& other) {
this->activeExportPower = other.getActiveExportPower();
}
void AmsData::apply(OBIS_code_t obis, double value) {
void AmsData::apply(OBIS_code_t obis, double value, uint64_t millis64) {
if(obis.sensor == 0 && obis.gr == 0 && obis.tariff == 0) {
meterType = value;
}
@@ -127,138 +127,137 @@ void AmsData::apply(OBIS_code_t obis, double value) {
}
}
if(obis.tariff != 0) {
Serial.println("Tariff not implemented");
return;
}
if(obis.gr == 7) { // Instant values
switch(obis.sensor) {
case 1:
activeImportPower = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 2:
activeExportPower = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 3:
reactiveImportPower = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 4:
reactiveExportPower = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 13:
powerFactor = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 21:
l1activeImportPower = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 22:
l1activeExportPower = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 31:
l1current = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 32:
l1voltage = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 33:
l1PowerFactor = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 41:
l2activeImportPower = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 42:
l2activeExportPower = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 51:
l2current = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 52:
l2voltage = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 53:
l2PowerFactor = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 61:
l3activeImportPower = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 62:
l3activeExportPower = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 71:
l3current = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 72:
l3voltage = value;
listType = max(listType, (uint8_t) 2);
listType = std::max(listType, (uint8_t) 2);
break;
case 73:
l3PowerFactor = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
}
} else if(obis.gr == 8) { // Accumulated values
switch(obis.sensor) {
case 1:
activeImportCounter = value;
listType = max(listType, (uint8_t) 3);
listType = std::max(listType, (uint8_t) 3);
break;
case 2:
activeExportCounter = value;
listType = max(listType, (uint8_t) 3);
listType = std::max(listType, (uint8_t) 3);
break;
case 3:
reactiveImportCounter = value;
listType = max(listType, (uint8_t) 3);
listType = std::max(listType, (uint8_t) 3);
break;
case 4:
reactiveExportCounter = value;
listType = max(listType, (uint8_t) 3);
listType = std::max(listType, (uint8_t) 3);
break;
case 21:
l1activeImportCounter = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 22:
l1activeExportCounter = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 41:
l2activeImportCounter = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 42:
l2activeExportCounter = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 61:
l3activeImportCounter = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
case 62:
l3activeExportCounter = value;
listType = max(listType, (uint8_t) 4);
listType = std::max(listType, (uint8_t) 4);
break;
}
}
if(listType > 0)
lastUpdateMillis = millis();
lastUpdateMillis = millis64;
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
if(!threePhase)

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -639,25 +639,22 @@ bool AmsDataStorage::isDayHappy(time_t now) {
return false;
}
if(now < FirmwareVersion::BuildEpoch) return false;
if(now < day.lastMeterReadTime) {
// If the timestamp is before the firware was built, there is something seriously wrong..
if(now < FirmwareVersion::BuildEpoch) {
return false;
}
// There are cases where the meter reports before the hour. The update method will then receive the meter timestamp as reference, thus there will not be 3600s between.
// Leaving a 100s buffer for these cases
if(now-day.lastMeterReadTime > 3500) {
// If the timestamp is before the last time we updated, there is also something wrong..
if(now < day.lastMeterReadTime) {
return false;
}
tmElements_t tm, last;
breakTime(tz->toLocal(now), tm);
breakTime(tz->toLocal(day.lastMeterReadTime), last);
if(tm.Hour != last.Hour) {
return false;
}
return true;
// If the timestamp is at the same day and hour as last update, we are happy
return tm.Day == last.Day && tm.Hour == last.Hour;
}
bool AmsDataStorage::isMonthHappy(time_t now) {

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -96,7 +96,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx, bool hastag) {
footersize += authkeylen;
memcpy(additional_authenticated_data + 1, authentication_key, 16);
memcpy(authentication_tag, ptr + len - footersize - 5, authkeylen);
for(uint8_t i; i < 16; i++) authenticate |= authentication_key[i] > 0;
for(uint8_t i = 0; i < 16; i++) authenticate |= authentication_key[i] > 0;
}
#if defined(ESP8266)

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,3 +1,8 @@
/**
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
#pragma once
#include <stdint.h>
#include <Print.h>

View File

@@ -1,3 +1,8 @@
/**
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
#include "AmsFirmwareUpdater.h"
#include "AmsStorage.h"
#include "FirmwareVersion.h"

View File

@@ -1,3 +1,8 @@
/**
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
#pragma once
#include "AmsDataStorage.h"

View File

@@ -1,3 +1,8 @@
/**
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
#include "AmsJsonGenerator.h"
void AmsJsonGenerator::generateDayPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize) {

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -7,8 +7,6 @@
#ifndef _ENERGYACCOUNTING_H
#define _ENERGYACCOUNTING_H
#include "Arduino.h"
#include "AmsData.h"
#include "AmsDataStorage.h"
#include "PriceService.h"
@@ -83,7 +81,7 @@ public:
void setPriceService(PriceService *ps);
void setTimezone(Timezone*);
EnergyAccountingConfig* getConfig();
bool update(AmsData* amsData);
bool update(time_t now, uint64_t lastUpdatedMillis, uint8_t listType, uint32_t activeImportPower, uint32_t activeExportPower);
bool load();
bool save();
bool isInitialized();

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -54,9 +54,8 @@ bool EnergyAccounting::isInitialized() {
return this->init;
}
bool EnergyAccounting::update(AmsData* amsData) {
bool EnergyAccounting::update(time_t now, uint64_t lastUpdatedMillis, uint8_t listType, uint32_t activeImportPower, uint32_t activeExportPower) {
if(config == NULL) return false;
time_t now = time(nullptr);
if(now < FirmwareVersion::BuildEpoch) return false;
if(tz == NULL) {
return false;
@@ -90,7 +89,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
calcDayCost();
}
if(local.Hour != realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
if(local.Hour != realtimeData->currentHour && (listType >= 3 || local.Minute == 1)) {
tmElements_t oneHrAgo, oneHrAgoLocal;
breakTime(now-3600, oneHrAgo);
uint16_t val = round(ds->getHourImport(oneHrAgo.Hour) / 10.0);
@@ -156,9 +155,9 @@ bool EnergyAccounting::update(AmsData* amsData) {
}
}
if(realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
unsigned long ms = amsData->getLastUpdateMillis() - realtimeData->lastImportUpdateMillis;
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
if(realtimeData->lastImportUpdateMillis < lastUpdatedMillis) {
unsigned long ms = lastUpdatedMillis - realtimeData->lastImportUpdateMillis;
float kwhi = (activeImportPower * (((float) ms) / 3600000.0)) / 1000.0;
if(kwhi > 0) {
realtimeData->use += kwhi;
float importPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_IMPORT);
@@ -168,12 +167,12 @@ bool EnergyAccounting::update(AmsData* amsData) {
realtimeData->costDay += cost;
}
}
realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
realtimeData->lastImportUpdateMillis = lastUpdatedMillis;
}
if(amsData->getListType() > 1 && realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
unsigned long ms = amsData->getLastUpdateMillis() - realtimeData->lastExportUpdateMillis;
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
if(listType > 1 && realtimeData->lastExportUpdateMillis < lastUpdatedMillis) {
unsigned long ms = lastUpdatedMillis - realtimeData->lastExportUpdateMillis;
float kwhe = (activeExportPower * (((float) ms) / 3600000.0)) / 1000.0;
if(kwhe > 0) {
realtimeData->produce += kwhe;
float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_EXPORT);
@@ -183,7 +182,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
realtimeData->incomeDay += income;
}
}
realtimeData->lastExportUpdateMillis = amsData->getLastUpdateMillis();
realtimeData->lastExportUpdateMillis = lastUpdatedMillis;
}
if(config != NULL) {

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -664,7 +664,8 @@ bool HwTools::isVoltageOptimal(float range) {
lastVccRead = now;
}
if(vcc > 3.4 || vcc < 2.8) {
maxVcc = 0; // Voltage is outside the operating range, we have to assume voltage is OK
maxVcc = 0;
return true; // Voltage is outside the operating range, we have to assume voltage is OK
} else if(vcc > maxVcc) {
maxVcc = vcc;
} else {

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -11,6 +11,7 @@
#include "AmsConfiguration.h"
#include "DataParser.h"
#include "Cosem.h"
#include "Timezone.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2024
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: All rights reserved
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2024
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -24,8 +24,6 @@ void KmpCommunicator::configure(MeterConfig& meterConfig) {
}
bool KmpCommunicator::loop() {
uint64_t now = millis64();
bool ret = talker->loop();
int lastError = getLastError();
if(ret) {
@@ -58,35 +56,36 @@ AmsData* KmpCommunicator::getData(AmsData& meterState) {
if(talker == NULL) return NULL;
KmpDataHolder kmpData;
talker->getData(kmpData);
uint64_t now = millis64();
AmsData* data = new AmsData();
data->apply(OBIS_ACTIVE_IMPORT_COUNT, kmpData.activeImportCounter);
data->apply(OBIS_ACTIVE_EXPORT_COUNT, kmpData.activeExportCounter);
data->apply(OBIS_REACTIVE_IMPORT_COUNT, kmpData.reactiveImportCounter);
data->apply(OBIS_REACTIVE_EXPORT_COUNT, kmpData.reactiveExportCounter);
data->apply(OBIS_ACTIVE_IMPORT, kmpData.activeImportPower);
data->apply(OBIS_ACTIVE_EXPORT, kmpData.activeExportPower);
data->apply(OBIS_REACTIVE_IMPORT, kmpData.reactiveImportPower);
data->apply(OBIS_REACTIVE_EXPORT, kmpData.reactiveExportPower);
data->apply(OBIS_VOLTAGE_L1, kmpData.l1voltage);
data->apply(OBIS_VOLTAGE_L2, kmpData.l2voltage);
data->apply(OBIS_VOLTAGE_L3, kmpData.l3voltage);
data->apply(OBIS_CURRENT_L1, kmpData.l1current);
data->apply(OBIS_CURRENT_L2, kmpData.l2current);
data->apply(OBIS_CURRENT_L3, kmpData.l3current);
data->apply(OBIS_POWER_FACTOR_L1, kmpData.l1PowerFactor);
data->apply(OBIS_POWER_FACTOR_L2, kmpData.l2PowerFactor);
data->apply(OBIS_POWER_FACTOR_L3, kmpData.l3PowerFactor);
data->apply(OBIS_POWER_FACTOR, kmpData.powerFactor);
data->apply(OBIS_ACTIVE_IMPORT_L1, kmpData.l1activeImportPower);
data->apply(OBIS_ACTIVE_IMPORT_L2, kmpData.l2activeImportPower);
data->apply(OBIS_ACTIVE_IMPORT_L3, kmpData.l3activeImportPower);
data->apply(OBIS_ACTIVE_EXPORT_L1, kmpData.l1activeExportPower);
data->apply(OBIS_ACTIVE_EXPORT_L2, kmpData.l2activeExportPower);
data->apply(OBIS_ACTIVE_EXPORT_L3, kmpData.l3activeExportPower);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L1, kmpData.l1activeImportCounter);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L2, kmpData.l2activeImportCounter);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L3, kmpData.l3activeImportCounter);
data->apply(OBIS_METER_ID, kmpData.meterId);
data->apply(OBIS_NULL, AmsTypeKamstrup);
data->apply(OBIS_ACTIVE_IMPORT_COUNT, kmpData.activeImportCounter, now);
data->apply(OBIS_ACTIVE_EXPORT_COUNT, kmpData.activeExportCounter, now);
data->apply(OBIS_REACTIVE_IMPORT_COUNT, kmpData.reactiveImportCounter, now);
data->apply(OBIS_REACTIVE_EXPORT_COUNT, kmpData.reactiveExportCounter, now);
data->apply(OBIS_ACTIVE_IMPORT, kmpData.activeImportPower, now);
data->apply(OBIS_ACTIVE_EXPORT, kmpData.activeExportPower, now);
data->apply(OBIS_REACTIVE_IMPORT, kmpData.reactiveImportPower, now);
data->apply(OBIS_REACTIVE_EXPORT, kmpData.reactiveExportPower, now);
data->apply(OBIS_VOLTAGE_L1, kmpData.l1voltage, now);
data->apply(OBIS_VOLTAGE_L2, kmpData.l2voltage, now);
data->apply(OBIS_VOLTAGE_L3, kmpData.l3voltage, now);
data->apply(OBIS_CURRENT_L1, kmpData.l1current, now);
data->apply(OBIS_CURRENT_L2, kmpData.l2current, now);
data->apply(OBIS_CURRENT_L3, kmpData.l3current, now);
data->apply(OBIS_POWER_FACTOR_L1, kmpData.l1PowerFactor, now);
data->apply(OBIS_POWER_FACTOR_L2, kmpData.l2PowerFactor, now);
data->apply(OBIS_POWER_FACTOR_L3, kmpData.l3PowerFactor, now);
data->apply(OBIS_POWER_FACTOR, kmpData.powerFactor, now);
data->apply(OBIS_ACTIVE_IMPORT_L1, kmpData.l1activeImportPower, now);
data->apply(OBIS_ACTIVE_IMPORT_L2, kmpData.l2activeImportPower, now);
data->apply(OBIS_ACTIVE_IMPORT_L3, kmpData.l3activeImportPower, now);
data->apply(OBIS_ACTIVE_EXPORT_L1, kmpData.l1activeExportPower, now);
data->apply(OBIS_ACTIVE_EXPORT_L2, kmpData.l2activeExportPower, now);
data->apply(OBIS_ACTIVE_EXPORT_L3, kmpData.l3activeExportPower, now);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L1, kmpData.l1activeImportCounter, now);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L2, kmpData.l2activeImportCounter, now);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L3, kmpData.l3activeImportCounter, now);
data->apply(OBIS_METER_ID, kmpData.meterId, now);
data->apply(OBIS_NULL, AmsTypeKamstrup, now);
return data;
}

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -102,6 +102,8 @@ char* PriceService::getSource() {
return this->today->getSource();
} else if(tomorrow != NULL) {
return this->tomorrow->getSource();
} else if(!this->config->enabled && this->priceConfig.capacity() != 0) {
return "FIX"; // Fixed price
}
return "";
}

View File

@@ -1,3 +1,8 @@
/**
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
#include "PricesContainer.h"
#include <cstring>

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/

View File

@@ -1,5 +1,5 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
@@ -7,7 +7,6 @@
#ifndef _REALTIMEPLOT_H
#define _REALTIMEPLOT_H
#include <stdint.h>
#include "AmsData.h"
#define REALTIME_SAMPLE 10000

View File

@@ -1,9 +1,10 @@
/**
* @copyright Utilitech AS 2023
* @copyright Utilitech AS 2023-2026
* License: Fair Source
*
*/
#include "Arduino.h"
#include "RealtimePlot.h"
#include <stdlib.h>

View File

@@ -0,0 +1,59 @@
# SvelteUi App
Web interface for AMS Reader firmware built with Svelte 5 and Vite 6.
## Development Setup
### Prerequisites
- Node.js 20.x or 22.x LTS (required for Vite 6)
- npm
### Local Development Configuration
To develop against your AMS reader device, you need to configure the proxy target:
1. Copy the example config file:
```bash
cp vite.config.local.example.js vite.config.local.js
```
2. Edit `vite.config.local.js` and update the IP address to match your device:
```javascript
export default {
proxyTarget: "http://192.168.1.100" // Your device's IP
}
```
3. The `vite.config.local.js` file is gitignored, so your personal settings won't be committed.
### Running Development Server
```bash
npm install
npm run dev
```
The dev server will proxy API requests to your configured device IP.
### Building for Production
```bash
npm run build
```
The build output will be in the `dist/` directory.
## Project Structure
- `src/` - Application source code
- `routes/` - Page components using svelte-spa-router
- `lib/` - Shared components and utilities
- `public/` - Static assets (favicon, etc.)
- `dist/` - Build output (not committed to git)
## Key Technologies
- **Svelte 5.17.0** - UI framework
- **Vite 6.0.7** - Build tool
- **svelte-spa-router 4.0.1** - Hash-based routing
- **Tailwind CSS** - Styling

File diff suppressed because one or more lines are too long

View File

@@ -8,10 +8,9 @@
<link rel="mask-icon" href="/favicon.svg" color="#000000">
<title>AMS reader</title>
<script type="module" crossorigin src="/index.js"></script>
<link rel="stylesheet" href="/index.css">
<link rel="stylesheet" crossorigin href="/index.css">
</head>
<body class="bg-gray-100 dark:bg-gray-900">
<div id="app"></div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More