mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 04:57:28 +00:00
Compare commits
245 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55520cd7f6 | ||
|
|
e27d41839e | ||
|
|
569a0ddfaa | ||
|
|
de63263dbb | ||
|
|
8adb7055c0 | ||
|
|
11e09c5b56 | ||
|
|
ac32f74eba | ||
|
|
d49b8c3839 | ||
|
|
423c5da25e | ||
|
|
9a4a8a10f2 | ||
|
|
d15992142e | ||
|
|
fed3948f85 | ||
|
|
b77342a648 | ||
|
|
04f407aba0 | ||
|
|
b122fae04c | ||
|
|
8a4efd0047 | ||
|
|
4407526d96 | ||
|
|
1d91416348 | ||
|
|
a1bbcc20e3 | ||
|
|
2b779920d7 | ||
|
|
de79fd4c43 | ||
|
|
8dfaa34d03 | ||
|
|
0d0dc07903 | ||
|
|
e38a064928 | ||
|
|
81b3aacc4d | ||
|
|
6277d5880d | ||
|
|
8424d1f75e | ||
|
|
099b23d2d5 | ||
|
|
28d746415c | ||
|
|
fd373a5846 | ||
|
|
cd7315990f | ||
|
|
59bf0ce066 | ||
|
|
93e55f457a | ||
|
|
4d340c5482 | ||
|
|
1db0082103 | ||
|
|
f593e14e68 | ||
|
|
da6328c246 | ||
|
|
5eeab2ba89 | ||
|
|
eeaaf088ac | ||
|
|
32afea2817 | ||
|
|
e7b496280d | ||
|
|
d0621e98cd | ||
|
|
202d57843c | ||
|
|
770d662f2c | ||
|
|
da1d5993f4 | ||
|
|
7ad97daf86 | ||
|
|
3b155d78b1 | ||
|
|
37a1e3b93e | ||
|
|
846cf85331 | ||
|
|
a1a6953521 | ||
|
|
a137316cec | ||
|
|
faa468b287 | ||
|
|
703c68a2cf | ||
|
|
b6168a0082 | ||
|
|
38ec99f2ee | ||
|
|
22f8349f2f | ||
|
|
d98a021f39 | ||
|
|
77d899fe0a | ||
|
|
c03bfcbcd7 | ||
|
|
f3732674b0 | ||
|
|
5926ddfaf9 | ||
|
|
99e341e582 | ||
|
|
05506cdc9f | ||
|
|
3f1861deda | ||
|
|
efacbd4b31 | ||
|
|
beaaa191e8 | ||
|
|
43d49f21a3 | ||
|
|
95c495b773 | ||
|
|
68c680debf | ||
|
|
fec6cc7612 | ||
|
|
dc3cea80b6 | ||
|
|
ce67ef2bea | ||
|
|
3a980fac4c | ||
|
|
77873f4a38 | ||
|
|
d9d384ea02 | ||
|
|
16fb1ea87c | ||
|
|
c74e719327 | ||
|
|
8c8e14f60c | ||
|
|
3b93897a8e | ||
|
|
e080c7d535 | ||
|
|
64ea8c4888 | ||
|
|
e7ae24b26f | ||
|
|
84ff999c4c | ||
|
|
71e7e779da | ||
|
|
99904f9097 | ||
|
|
89015191de | ||
|
|
a7d3382947 | ||
|
|
abef32c73c | ||
|
|
b52c580f6f | ||
|
|
429f0ba699 | ||
|
|
d918a593ce | ||
|
|
fb410ecfef | ||
|
|
ee6c249370 | ||
|
|
05b340738e | ||
|
|
86663f53f6 | ||
|
|
bd11dee1e5 | ||
|
|
29c8011cda | ||
|
|
4884d3a0e2 | ||
|
|
6079b17e3d | ||
|
|
d069d4e102 | ||
|
|
e1162ad970 | ||
|
|
71be381e1a | ||
|
|
a19901b58f | ||
|
|
361d3a38ed | ||
|
|
c22bca3130 | ||
|
|
2bfd863882 | ||
|
|
17c87c40df | ||
|
|
10b76ab2e6 | ||
|
|
15c3b2067c | ||
|
|
e366f10632 | ||
|
|
fc1850195b | ||
|
|
b85d11b1f3 | ||
|
|
fff6d1b068 | ||
|
|
bdb0bf3df0 | ||
|
|
c6e111c347 | ||
|
|
063b960fc2 | ||
|
|
95967aaf59 | ||
|
|
34e103c1d8 | ||
|
|
bdd2ec10cd | ||
|
|
37f90cb267 | ||
|
|
db90dbfd8f | ||
|
|
61d23ab453 | ||
|
|
864cd1fbbb | ||
|
|
517869f43d | ||
|
|
766849d6fc | ||
|
|
67e6a51a8a | ||
|
|
4093b64dd0 | ||
|
|
4068f127ba | ||
|
|
71abe188ca | ||
|
|
5022f42692 | ||
|
|
6c1401d042 | ||
|
|
7041a29894 | ||
|
|
e292f79421 | ||
|
|
d7ce808321 | ||
|
|
210ddad515 | ||
|
|
449257ae3f | ||
|
|
def9867990 | ||
|
|
26a63e30e0 | ||
|
|
9efdf1daa5 | ||
|
|
f2e7879974 | ||
|
|
b8ac1a9565 | ||
|
|
14eb27e0d9 | ||
|
|
f68666bd4a | ||
|
|
2087c287bf | ||
|
|
d85da6c9cb | ||
|
|
6a99b0a6a7 | ||
|
|
dfca5e37dc | ||
|
|
1f74f1e6b2 | ||
|
|
bdba6a0254 | ||
|
|
7c4c096e94 | ||
|
|
1ab5a4d2cb | ||
|
|
4bd2b5230b | ||
|
|
c0945abfa0 | ||
|
|
d94f27949c | ||
|
|
6a427b513d | ||
|
|
47a7d4e13b | ||
|
|
263ce9749a | ||
|
|
13d0521984 | ||
|
|
e3511f0ee2 | ||
|
|
f4de3e6178 | ||
|
|
2bb5361b22 | ||
|
|
9caec71e1c | ||
|
|
7813d3ea08 | ||
|
|
73ed4f87e4 | ||
|
|
c06dabba51 | ||
|
|
23cbcf9a0a | ||
|
|
785cefabb5 | ||
|
|
aa6283de5b | ||
|
|
2b563c7230 | ||
|
|
45d0fa2bfa | ||
|
|
09fdd2dc22 | ||
|
|
ab2086f909 | ||
|
|
16d1ee9761 | ||
|
|
348488af72 | ||
|
|
9103a5b730 | ||
|
|
830e22d182 | ||
|
|
21bff28aee | ||
|
|
e9472513d2 | ||
|
|
b6e69ca5d0 | ||
|
|
d28ef99cee | ||
|
|
3b531fab5b | ||
|
|
e575ab755c | ||
|
|
1ba452213d | ||
|
|
a2c20575c8 | ||
|
|
9a767d9ac4 | ||
|
|
52bc2f6a9b | ||
|
|
cfa4502af8 | ||
|
|
6fe308b5f6 | ||
|
|
80be1ceef1 | ||
|
|
bfa1a65dfd | ||
|
|
afcc542e25 | ||
|
|
f334847e82 | ||
|
|
9eb56beb6c | ||
|
|
87c565763a | ||
|
|
87ddf00afa | ||
|
|
e7ca408baa | ||
|
|
1d5c45c43c | ||
|
|
183cb1e2b1 | ||
|
|
768dc97c9c | ||
|
|
fe3f100edb | ||
|
|
8a59fcb89a | ||
|
|
333169ef8f | ||
|
|
d497aa91f4 | ||
|
|
b951fe9099 | ||
|
|
48ab87ba50 | ||
|
|
bad107926c | ||
|
|
6012c19fc4 | ||
|
|
460238e99d | ||
|
|
64870cf9ca | ||
|
|
cd446f8cb8 | ||
|
|
f68a3a321a | ||
|
|
9ff6cf355f | ||
|
|
453492b5eb | ||
|
|
f11263a48f | ||
|
|
fffd95dcf2 | ||
|
|
fa87cfaa61 | ||
|
|
50de5abf93 | ||
|
|
b1294fdd86 | ||
|
|
ce2a67a284 | ||
|
|
39e42d5333 | ||
|
|
7503f9a077 | ||
|
|
651f72489c | ||
|
|
3596a87ff9 | ||
|
|
5c1acf5c45 | ||
|
|
c46b7972d0 | ||
|
|
df26c91a3b | ||
|
|
7a70bd7511 | ||
|
|
c583c2b44a | ||
|
|
4c28d512a1 | ||
|
|
1775d0abce | ||
|
|
8e65f1fe14 | ||
|
|
c43c07386a | ||
|
|
91958f5464 | ||
|
|
194905237b | ||
|
|
f3805ad111 | ||
|
|
0f22fd561e | ||
|
|
ee462ec468 | ||
|
|
111bd70763 | ||
|
|
afa013ca03 | ||
|
|
56b6a720c9 | ||
|
|
76d76844a9 | ||
|
|
ae82914795 | ||
|
|
0fa178a617 | ||
|
|
8cada69aaf | ||
|
|
7cbcbd4bc8 |
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -22,27 +22,25 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
uses: actions/checkout@v4
|
||||
- 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_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: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
|
||||
- name: Cache PlatformIO dependencies
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.pio/libdeps
|
||||
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
@@ -50,7 +48,7 @@ jobs:
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Build with node
|
||||
@@ -62,6 +60,6 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
run: pio pkg install
|
||||
- name: PlatformIO run
|
||||
run: pio run
|
||||
|
||||
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -28,6 +28,7 @@ jobs:
|
||||
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
|
||||
|
||||
@@ -146,6 +147,29 @@ jobs:
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32s3 firmware
|
||||
run: pio run -e esp32s3
|
||||
- name: Create esp32s3 zip file
|
||||
run: /bin/sh scripts/esp32s3/mkzip.sh
|
||||
- name: Upload esp32s3 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/esp32s3/firmware.bin
|
||||
asset_name: ams2mqtt-esp32s3-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32s3 zip 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: esp32s3.zip
|
||||
asset_name: ams2mqtt-esp32s3-${{ 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
|
||||
|
||||
63
.github/workflows/x-test-esp8266.yml
vendored
Normal file
63
.github/workflows/x-test-esp8266.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
name: Test ESP8266
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
esp8266:
|
||||
runs-on: esp8266
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get commit hash
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Check outputs
|
||||
run: echo ${{ steps.vars.outputs.sha_short }}
|
||||
- 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_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: '16.x'
|
||||
- name: Configure PlatformIO environment
|
||||
run: |
|
||||
echo "[platformio]
|
||||
default_envs = dev8266
|
||||
|
||||
[env:dev8266]
|
||||
platform = espressif8266@4.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, ESP8266SSDP, \${common.lib_deps}
|
||||
lib_ignore = \${common.lib_ignore}
|
||||
extra_scripts = \${common.extra_scripts}" > platformio-user.ini
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: true
|
||||
- name: PlatformIO lib install
|
||||
run: pio pkg update
|
||||
- name: PlatformIO run
|
||||
run: pio run -t upload --upload-port /dev/ttyUSB0
|
||||
- name: Wait for device to come online
|
||||
run: waitforhost 10.42.0.11 80
|
||||
- name: Confirm version
|
||||
run: curl -s http://10.42.0.11/sysinfo.json|jq -r .version | grep "${{ steps.vars.outputs.sha_short }}" || exit 1
|
||||
- name: Running amsreader-test
|
||||
run: amsreader-test 10.42.0.11
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.vs/
|
||||
.idea/
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
**/__vm/
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -10,7 +10,7 @@ Use Limitation: 5 users
|
||||
|
||||
License Grant. Licensor hereby grants to each recipient of the
|
||||
Software ("you") a non-exclusive, non-transferable, royalty-free and
|
||||
fully-paid-up license, under all of the Licensor’s copyright and
|
||||
fully-paid-up license, under all of the Licensor's copyright and
|
||||
patent rights, to use, copy, distribute, prepare derivative works of,
|
||||
publicly perform and display the Software, subject to the Use
|
||||
Limitation and the conditions set forth below.
|
||||
@@ -20,7 +20,7 @@ number of users per entity set forth above (the "Use Limitation"). For
|
||||
determining the number of users, "you" includes all affiliates,
|
||||
meaning legal entities controlling, controlled by, or under common
|
||||
control with you. If you exceed the Use Limitation, your use is
|
||||
subject to payment of Licensor’s then-current list price for licenses.
|
||||
subject to payment of Licensor's then-current list price for licenses.
|
||||
|
||||
Conditions. Redistribution in source code or other forms must include
|
||||
a copy of this license document to be provided in a reasonable
|
||||
|
||||
BIN
doc/Austria/Smart.meter.customer.interfaces.Austria.pdf
Normal file
BIN
doc/Austria/Smart.meter.customer.interfaces.Austria.pdf
Normal file
Binary file not shown.
@@ -1,31 +1,60 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSCONFIGURATION_h
|
||||
#define _AMSCONFIGURATION_h
|
||||
#include <EEPROM.h>
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 103 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 104 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CLEARED_INDICATOR 0xFC
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
#define CONFIG_SYSTEM_START 8
|
||||
#define CONFIG_METER_START 32
|
||||
#define CONFIG_UPGRADE_INFO_START 216
|
||||
#define CONFIG_UI_START 248
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_ENTSOE_START 290
|
||||
#define CONFIG_WIFI_START 360
|
||||
#define CONFIG_ENERGYACCOUNTING_START 576
|
||||
#define CONFIG_WEB_START 648
|
||||
#define CONFIG_DEBUG_START 824
|
||||
#define CONFIG_DOMOTICZ_START 856
|
||||
#define CONFIG_NTP_START 872
|
||||
#define CONFIG_MQTT_START 1004
|
||||
#define CONFIG_HA_START 1680
|
||||
#define CONFIG_UPGRADE_INFO_START 16
|
||||
#define CONFIG_NETWORK_START 40
|
||||
#define CONFIG_METER_START 296
|
||||
#define CONFIG_GPIO_START 368
|
||||
#define CONFIG_PRICE_START 400
|
||||
#define CONFIG_ENERGYACCOUNTING_START 472
|
||||
#define CONFIG_WEB_START 496
|
||||
#define CONFIG_DEBUG_START 632
|
||||
#define CONFIG_NTP_START 640
|
||||
#define CONFIG_MQTT_START 768
|
||||
#define CONFIG_DOMOTICZ_START 1536
|
||||
#define CONFIG_HA_START 1552
|
||||
#define CONFIG_UI_START 1720
|
||||
#define CONFIG_CLOUD_START 1742
|
||||
|
||||
#define CONFIG_METER_START_93 224
|
||||
#define CONFIG_METER_START_103 32
|
||||
#define CONFIG_UPGRADE_INFO_START_103 216
|
||||
#define CONFIG_UI_START_103 248
|
||||
#define CONFIG_GPIO_START_103 266
|
||||
#define CONFIG_ENTSOE_START_103 290
|
||||
#define CONFIG_WIFI_START_103 360
|
||||
#define CONFIG_ENERGYACCOUNTING_START_103 576
|
||||
#define CONFIG_WEB_START_103 648
|
||||
#define CONFIG_DEBUG_START_103 824
|
||||
#define CONFIG_DOMOTICZ_START_103 856
|
||||
#define CONFIG_NTP_START_103 872
|
||||
#define CONFIG_MQTT_START_103 1004
|
||||
#define CONFIG_HA_START_103 1680
|
||||
|
||||
#define LED_BEHAVIOUR_DEFAULT 0
|
||||
#define LED_BEHAVIOUR_BOOT 1
|
||||
#define LED_BEHAVIOUR_ERROR_ONLY 3
|
||||
#define LED_BEHAVIOUR_OFF 9
|
||||
|
||||
struct ResetDataContainer {
|
||||
uint8_t cause;
|
||||
uint8_t last_cause;
|
||||
uint8_t magic;
|
||||
};
|
||||
|
||||
struct SystemConfig {
|
||||
uint8_t boardType;
|
||||
@@ -34,9 +63,9 @@ struct SystemConfig {
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[3];
|
||||
uint8_t energyspeedometer;
|
||||
}; // 8
|
||||
}; // 9
|
||||
|
||||
struct WiFiConfig {
|
||||
struct NetworkConfig {
|
||||
char ssid[32];
|
||||
char psk[64];
|
||||
char ip[16];
|
||||
@@ -49,8 +78,9 @@ struct WiFiConfig {
|
||||
uint8_t power;
|
||||
uint8_t sleep;
|
||||
uint8_t use11b;
|
||||
bool unused;
|
||||
}; // 213
|
||||
bool ipv6;
|
||||
uint8_t mode;
|
||||
}; // 214
|
||||
|
||||
struct MqttConfig {
|
||||
char host[128];
|
||||
@@ -62,9 +92,19 @@ struct MqttConfig {
|
||||
char password[256];
|
||||
uint8_t payloadFormat;
|
||||
bool ssl;
|
||||
}; // 676
|
||||
uint8_t magic;
|
||||
bool stateUpdate;
|
||||
uint16_t stateUpdateInterval;
|
||||
}; // 680
|
||||
|
||||
struct WebConfig {
|
||||
uint8_t security;
|
||||
char username[37];
|
||||
char password[37];
|
||||
char context[37];
|
||||
}; // 112
|
||||
|
||||
struct WebConfig103 {
|
||||
uint8_t security;
|
||||
char username[64];
|
||||
char password[64];
|
||||
@@ -86,41 +126,10 @@ struct MeterConfig {
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
uint8_t bufferSize;
|
||||
}; // 62
|
||||
|
||||
struct MeterConfig100 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_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;
|
||||
}; // 59
|
||||
|
||||
struct MeterConfig95 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_t productionCapacity;
|
||||
uint8_t encryptionKey[16];
|
||||
uint8_t authenticationKey[16];
|
||||
uint16_t wattageMultiplier;
|
||||
uint16_t voltageMultiplier;
|
||||
uint16_t amperageMultiplier;
|
||||
uint16_t accumulatedMultiplier;
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 50
|
||||
uint8_t rxPin;
|
||||
bool rxPinPullup;
|
||||
uint8_t txPin;
|
||||
}; // 65
|
||||
|
||||
struct DebugConfig {
|
||||
bool telnet;
|
||||
@@ -129,6 +138,26 @@ struct DebugConfig {
|
||||
}; // 3
|
||||
|
||||
struct GpioConfig {
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
bool ledInverted;
|
||||
uint8_t ledPinRed;
|
||||
uint8_t ledPinGreen;
|
||||
uint8_t ledPinBlue;
|
||||
bool ledRgbInverted;
|
||||
uint8_t tempSensorPin;
|
||||
uint8_t tempAnalogSensorPin;
|
||||
uint8_t vccPin;
|
||||
int16_t vccOffset;
|
||||
uint16_t vccMultiplier;
|
||||
uint8_t vccBootLimit;
|
||||
uint16_t vccResistorGnd;
|
||||
uint16_t vccResistorVcc;
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 21
|
||||
|
||||
struct GpioConfig103 {
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
@@ -146,7 +175,9 @@ struct GpioConfig {
|
||||
uint16_t vccResistorGnd;
|
||||
uint16_t vccResistorVcc;
|
||||
bool hanPinPullup;
|
||||
}; // 21
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 23
|
||||
|
||||
struct DomoticzConfig {
|
||||
uint16_t elidx;
|
||||
@@ -169,21 +200,13 @@ struct NtpConfig {
|
||||
char timezone[32];
|
||||
}; // 98
|
||||
|
||||
struct NtpConfig96 {
|
||||
bool enable;
|
||||
bool dhcp;
|
||||
int16_t offset;
|
||||
int16_t summerOffset;
|
||||
char server[64];
|
||||
}; // 70
|
||||
|
||||
struct EntsoeConfig {
|
||||
char token[37];
|
||||
struct PriceServiceConfig {
|
||||
char entsoeToken[37];
|
||||
char area[17];
|
||||
char currency[4];
|
||||
uint32_t multiplier;
|
||||
uint32_t unused1;
|
||||
bool enabled;
|
||||
uint16_t fixedPrice;
|
||||
uint16_t unused2;
|
||||
}; // 64
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
@@ -191,11 +214,6 @@ struct EnergyAccountingConfig {
|
||||
uint8_t hours;
|
||||
}; // 21
|
||||
|
||||
struct EnergyAccountingConfig101 {
|
||||
uint8_t thresholds[10];
|
||||
uint8_t hours;
|
||||
}; // 11
|
||||
|
||||
struct UiConfig {
|
||||
uint8_t showImport;
|
||||
uint8_t showExport;
|
||||
@@ -208,13 +226,12 @@ struct UiConfig {
|
||||
uint8_t showDayPlot;
|
||||
uint8_t showMonthPlot;
|
||||
uint8_t showTemperaturePlot;
|
||||
}; // 11
|
||||
|
||||
struct TempSensorConfig {
|
||||
uint8_t address[8];
|
||||
char name[16];
|
||||
bool common;
|
||||
};
|
||||
uint8_t showRealtimePlot;
|
||||
uint8_t showPerPhasePower;
|
||||
uint8_t showPowerFactor;
|
||||
uint8_t darkMode;
|
||||
char language[3];
|
||||
}; // 15
|
||||
|
||||
struct UpgradeInformation {
|
||||
char fromVersion[8];
|
||||
@@ -223,6 +240,14 @@ struct UpgradeInformation {
|
||||
int16_t errorCode;
|
||||
}; // 20
|
||||
|
||||
struct CloudConfig {
|
||||
bool enabled;
|
||||
uint8_t interval;
|
||||
char hostname[64];
|
||||
uint16_t port;
|
||||
uint8_t clientId[16];
|
||||
}; // 69
|
||||
|
||||
class AmsConfiguration {
|
||||
public:
|
||||
bool hasConfig();
|
||||
@@ -235,12 +260,12 @@ public:
|
||||
bool isSystemConfigChanged();
|
||||
void ackSystemConfigChanged();
|
||||
|
||||
bool getWiFiConfig(WiFiConfig&);
|
||||
bool setWiFiConfig(WiFiConfig&);
|
||||
void clearWifi(WiFiConfig&);
|
||||
void clearWifiIp(WiFiConfig&);
|
||||
bool isWifiChanged();
|
||||
void ackWifiChange();
|
||||
bool getNetworkConfig(NetworkConfig&);
|
||||
bool setNetworkConfig(NetworkConfig&);
|
||||
void clearNetworkConfig(NetworkConfig&);
|
||||
void clearNetworkConfigIp(NetworkConfig&);
|
||||
bool isNetworkConfigChanged();
|
||||
void ackNetworkConfigChange();
|
||||
|
||||
bool getMqttConfig(MqttConfig&);
|
||||
bool setMqttConfig(MqttConfig&);
|
||||
@@ -251,7 +276,7 @@ public:
|
||||
|
||||
bool getWebConfig(WebConfig&);
|
||||
bool setWebConfig(WebConfig&);
|
||||
void clearAuth(WebConfig&);
|
||||
void clearWebConfig(WebConfig&);
|
||||
|
||||
bool getMeterConfig(MeterConfig&);
|
||||
bool setMeterConfig(MeterConfig&);
|
||||
@@ -268,7 +293,7 @@ public:
|
||||
|
||||
bool getGpioConfig(GpioConfig&);
|
||||
bool setGpioConfig(GpioConfig&);
|
||||
void clearGpio(GpioConfig&);
|
||||
void clearGpio(GpioConfig& config, bool all=true);
|
||||
|
||||
void print(Print* debugger);
|
||||
|
||||
@@ -286,11 +311,11 @@ public:
|
||||
bool isNtpChanged();
|
||||
void ackNtpChange();
|
||||
|
||||
bool getEntsoeConfig(EntsoeConfig&);
|
||||
bool setEntsoeConfig(EntsoeConfig&);
|
||||
void clearEntsoe(EntsoeConfig&);
|
||||
bool isEntsoeChanged();
|
||||
void ackEntsoeChange();
|
||||
bool getPriceServiceConfig(PriceServiceConfig&);
|
||||
bool setPriceServiceConfig(PriceServiceConfig&);
|
||||
void clearPriceServiceConfig(PriceServiceConfig&);
|
||||
bool isPriceServiceChanged();
|
||||
void ackPriceServiceChange();
|
||||
|
||||
bool getEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
bool setEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
@@ -301,19 +326,20 @@ public:
|
||||
bool getUiConfig(UiConfig&);
|
||||
bool setUiConfig(UiConfig&);
|
||||
void clearUiConfig(UiConfig&);
|
||||
|
||||
void loadTempSensors();
|
||||
void saveTempSensors();
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorConfig* getTempSensorConfig(uint8_t address[8]);
|
||||
void updateTempSensorConfig(uint8_t address[8], const char name[32], bool common);
|
||||
|
||||
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
|
||||
void setUiLanguageChanged();
|
||||
bool isUiLanguageChanged();
|
||||
void ackUiLanguageChange();
|
||||
|
||||
bool getUpgradeInformation(UpgradeInformation&);
|
||||
bool setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion);
|
||||
void clearUpgradeInformation(UpgradeInformation&);
|
||||
|
||||
bool getCloudConfig(CloudConfig&);
|
||||
bool setCloudConfig(CloudConfig&);
|
||||
void clearCloudConfig(CloudConfig&);
|
||||
bool isCloudChanged();
|
||||
void ackCloudConfig();
|
||||
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
@@ -321,18 +347,9 @@ protected:
|
||||
private:
|
||||
uint8_t configVersion = 0;
|
||||
|
||||
bool sysChanged = false, wifiChanged = false, mqttChanged = false, meterChanged = true, ntpChanged = true, entsoeChanged = false, energyAccountingChanged = true;
|
||||
bool sysChanged = false, networkChanged, mqttChanged, meterChanged = true, ntpChanged = true, priceChanged = false, energyAccountingChanged = true, cloudChanged = true, uiLanguageChanged = false;
|
||||
|
||||
uint8_t tempSensorCount = 0;
|
||||
TempSensorConfig** tempSensors = NULL;
|
||||
|
||||
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 relocateConfig101(); // 2.2.0 through 2.2.8
|
||||
bool relocateConfig102(); // 2.2.9 through 2.2.11
|
||||
bool relocateConfig103(); // 2.2.12, until, but not including 2.3
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSSTORAGE_H
|
||||
#define _AMSSTORAGE_H
|
||||
|
||||
@@ -12,5 +18,6 @@
|
||||
#define FILE_ENERGYACCOUNTING "/energyaccounting.bin"
|
||||
|
||||
#define FILE_CFG "/configfile.cfg"
|
||||
#define FILE_PRICE_CONF "/priceconf.bin"
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Timezone.h>
|
||||
|
||||
#define JULY1970 15634800
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HEXUTILS_H
|
||||
#define _HEXUTILS_H
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hexutils.h"
|
||||
|
||||
String toHex(uint8_t* in) {
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSDATA_H
|
||||
#define _AMSDATA_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Timezone.h>
|
||||
#include "OBIScodes.h"
|
||||
|
||||
enum AmsType {
|
||||
AmsTypeAutodetect = 0x00,
|
||||
@@ -21,6 +28,7 @@ public:
|
||||
AmsData();
|
||||
|
||||
void apply(AmsData& other);
|
||||
void apply(const OBIS_code_t obis, double value);
|
||||
|
||||
uint64_t getLastUpdateMillis();
|
||||
|
||||
@@ -53,13 +61,21 @@ public:
|
||||
float getL2PowerFactor();
|
||||
float getL3PowerFactor();
|
||||
|
||||
float getL1ActiveImportPower();
|
||||
float getL2ActiveImportPower();
|
||||
float getL3ActiveImportPower();
|
||||
uint32_t getL1ActiveImportPower();
|
||||
uint32_t getL2ActiveImportPower();
|
||||
uint32_t getL3ActiveImportPower();
|
||||
|
||||
float getL1ActiveExportPower();
|
||||
float getL2ActiveExportPower();
|
||||
float getL3ActiveExportPower();
|
||||
uint32_t getL1ActiveExportPower();
|
||||
uint32_t getL2ActiveExportPower();
|
||||
uint32_t getL3ActiveExportPower();
|
||||
|
||||
double getL1ActiveImportCounter();
|
||||
double getL2ActiveImportCounter();
|
||||
double getL3ActiveImportCounter();
|
||||
|
||||
double getL1ActiveExportCounter();
|
||||
double getL2ActiveExportCounter();
|
||||
double getL3ActiveExportCounter();
|
||||
|
||||
double getActiveImportCounter();
|
||||
double getReactiveImportCounter();
|
||||
@@ -68,6 +84,7 @@ public:
|
||||
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
bool isCounterEstimated();
|
||||
bool isL2currentMissing();
|
||||
|
||||
int8_t getLastError();
|
||||
@@ -82,10 +99,13 @@ protected:
|
||||
time_t meterTimestamp = 0;
|
||||
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||
float l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
uint32_t l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
uint32_t l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
double l1activeImportCounter = 0, l2activeImportCounter = 0, l3activeImportCounter = 0;
|
||||
double l1activeExportCounter = 0, l2activeExportCounter = 0, l3activeExportCounter = 0;
|
||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||
double lastKnownCounter = 0;
|
||||
bool threePhase = false, twoPhase = false, counterEstimated = false, l2currentMissing = false;;
|
||||
|
||||
int8_t lastError = 0x00;
|
||||
|
||||
93
lib/AmsData/include/OBIScodes.h
Normal file
93
lib/AmsData/include/OBIScodes.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _OBISCODES_H
|
||||
#define _OBISCODES_H
|
||||
|
||||
#include "lwip/def.h"
|
||||
|
||||
#define OBIS_MEDIUM_ABSTRACT 0
|
||||
#define OBIS_MEDIUM_ELECTRICITY 1
|
||||
|
||||
#define OBIS_CHAN_0 0
|
||||
#define OBIS_CHAN_1 1
|
||||
|
||||
#define OBIS_RANGE_NA 0xFF
|
||||
|
||||
struct OBIS_head_t {
|
||||
uint8_t medium;
|
||||
uint8_t channel;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OBIS_code_t {
|
||||
uint8_t sensor;
|
||||
uint8_t gr;
|
||||
uint8_t tariff;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OBIS_t {
|
||||
OBIS_head_t head;
|
||||
OBIS_code_t code;
|
||||
uint8_t range;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
const OBIS_code_t OBIS_NULL PROGMEM = { 0, 0, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_FIRMWARE_VERSION PROGMEM = { 0, 2, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_VERSION PROGMEM = { 0, 2, 129 };
|
||||
const OBIS_code_t OBIS_METER_MODEL PROGMEM = { 96, 1, 1 };
|
||||
const OBIS_code_t OBIS_METER_MODEL_2 PROGMEM = { 96, 1, 7 };
|
||||
const OBIS_code_t OBIS_METER_ID PROGMEM = { 96, 1, 0 };
|
||||
const OBIS_code_t OBIS_METER_ID_2 PROGMEM = { 0, 0, 5 };
|
||||
const OBIS_code_t OBIS_METER_TIMESTAMP PROGMEM = { 1, 0, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT PROGMEM = { 1, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT PROGMEM = { 1, 8, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT PROGMEM = { 2, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_COUNT PROGMEM = { 2, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT PROGMEM = { 3, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT PROGMEM = { 3, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT PROGMEM = { 4, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_COUNT PROGMEM = { 4, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_POWER_FACTOR PROGMEM = { 13, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L1 PROGMEM = { 21, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L1 PROGMEM = { 22, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_L1 PROGMEM = { 23, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_L1 PROGMEM = { 24, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L1 PROGMEM ={ 21, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L1 PROGMEM ={ 22, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L1 PROGMEM = { 31, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L1 PROGMEM = { 32, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L1 PROGMEM = { 33, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L2 PROGMEM = { 41, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L2 PROGMEM = { 42, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_L2 PROGMEM = { 43, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_L2 PROGMEM = { 44, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L2 PROGMEM ={ 41, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L2 PROGMEM ={ 42, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L2 PROGMEM = { 51, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L2 PROGMEM = { 52, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L2 PROGMEM = { 53, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L3 PROGMEM = { 61, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L3 PROGMEM = { 62, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_L3 PROGMEM = { 63, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_L3 PROGMEM = { 64, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L3 PROGMEM ={ 61, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L3 PROGMEM ={ 62, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L3 PROGMEM = { 71, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L3 PROGMEM = { 72, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L3 PROGMEM = { 73, 7, 0 };
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsData.h"
|
||||
|
||||
AmsData::AmsData() {}
|
||||
@@ -8,23 +14,27 @@ void AmsData::apply(AmsData& other) {
|
||||
|
||||
if(ms > 0) {
|
||||
if(other.getActiveImportPower() > 0) {
|
||||
float add = other.getActiveImportPower() * (((float) ms) / 3600000.0);
|
||||
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) {
|
||||
ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastList2;
|
||||
ms = this->lastList2 > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastList2;
|
||||
if(other.getActiveExportPower() > 0) {
|
||||
float add = other.getActiveExportPower() * (((float) ms) / 3600000.0);
|
||||
uint32_t power = (activeExportPower + other.getActiveExportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
activeExportCounter += add / 1000.0;
|
||||
}
|
||||
if(other.getReactiveImportPower() > 0) {
|
||||
float add = other.getReactiveImportPower() * (((float) ms) / 3600000.0);
|
||||
uint32_t power = (reactiveImportPower + other.getReactiveImportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
reactiveImportCounter += add / 1000.0;
|
||||
}
|
||||
if(other.getReactiveExportPower() > 0) {
|
||||
float add = other.getReactiveExportPower() * (((float) ms) / 3600000.0);
|
||||
uint32_t power = (reactiveExportPower + other.getReactiveExportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
reactiveExportCounter += add / 1000.0;
|
||||
}
|
||||
}
|
||||
@@ -51,12 +61,31 @@ void AmsData::apply(AmsData& other) {
|
||||
this->l1activeExportPower = other.getL1ActiveExportPower();
|
||||
this->l2activeExportPower = other.getL2ActiveExportPower();
|
||||
this->l3activeExportPower = other.getL3ActiveExportPower();
|
||||
this->l1activeImportCounter = other.getL1ActiveImportCounter();
|
||||
this->l2activeImportCounter = other.getL2ActiveImportCounter();
|
||||
this->l3activeImportCounter = other.getL3ActiveImportCounter();
|
||||
this->l1activeExportCounter = other.getL1ActiveExportCounter();
|
||||
this->l2activeExportCounter = other.getL2ActiveExportCounter();
|
||||
this->l3activeExportCounter = other.getL3ActiveExportCounter();
|
||||
case 3:
|
||||
this->meterTimestamp = other.getMeterTimestamp();
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
this->activeExportCounter = other.getActiveExportCounter();
|
||||
this->reactiveImportCounter = other.getReactiveImportCounter();
|
||||
this->reactiveExportCounter = other.getReactiveExportCounter();
|
||||
// Aidon tends to sometime send the same counter as last hour by accident
|
||||
if(meterType == AmsTypeAidon && counterEstimated && lastKnownCounter == other.getActiveImportCounter()-other.getActiveExportCounter()) {
|
||||
double diff = activeImportCounter - activeExportCounter - lastKnownCounter;
|
||||
if(diff < 1.0) { // In case a very low value have been calculated, use the new values
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
this->activeExportCounter = other.getActiveExportCounter();
|
||||
this->reactiveImportCounter = other.getReactiveImportCounter();
|
||||
this->reactiveExportCounter = other.getReactiveExportCounter();
|
||||
this->lastKnownCounter = activeImportCounter - activeExportCounter;
|
||||
}
|
||||
} else {
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
this->activeExportCounter = other.getActiveExportCounter();
|
||||
this->reactiveImportCounter = other.getReactiveImportCounter();
|
||||
this->reactiveExportCounter = other.getReactiveExportCounter();
|
||||
this->lastKnownCounter = activeImportCounter - activeExportCounter;
|
||||
}
|
||||
this->counterEstimated = false;
|
||||
case 2:
|
||||
this->listId = other.getListId();
|
||||
@@ -83,6 +112,159 @@ void AmsData::apply(AmsData& other) {
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
}
|
||||
|
||||
void AmsData::apply(OBIS_code_t obis, double value) {
|
||||
if(obis.sensor == 0 && obis.gr == 0 && obis.tariff == 0) {
|
||||
meterType = value;
|
||||
}
|
||||
if(obis.gr == 1) {
|
||||
if(obis.sensor == 96) {
|
||||
if(obis.tariff == 0) {
|
||||
meterId = String((long) value, 10);
|
||||
return;
|
||||
} else if(obis.tariff == 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
break;
|
||||
case 2:
|
||||
activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 13:
|
||||
powerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 21:
|
||||
l1activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 22:
|
||||
l1activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 31:
|
||||
l1current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 32:
|
||||
l1voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 33:
|
||||
l1PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 41:
|
||||
l2activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 42:
|
||||
l2activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 51:
|
||||
l2current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 52:
|
||||
l2voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 53:
|
||||
l2PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 61:
|
||||
l3activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 62:
|
||||
l3activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 71:
|
||||
l3current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 72:
|
||||
l3voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 73:
|
||||
l3PowerFactor = value;
|
||||
listType = 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);
|
||||
break;
|
||||
case 2:
|
||||
activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 21:
|
||||
l1activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 22:
|
||||
l1activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 41:
|
||||
l2activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 42:
|
||||
l2activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 61:
|
||||
l3activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 62:
|
||||
l3activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(listType > 0)
|
||||
lastUpdateMillis = millis();
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
if(!threePhase)
|
||||
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
|
||||
}
|
||||
|
||||
uint64_t AmsData::getLastUpdateMillis() {
|
||||
return this->lastUpdateMillis;
|
||||
}
|
||||
@@ -171,30 +353,54 @@ float AmsData::getL3PowerFactor() {
|
||||
return this->l3PowerFactor;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveImportPower() {
|
||||
uint32_t AmsData::getL1ActiveImportPower() {
|
||||
return this->l1activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveImportPower() {
|
||||
uint32_t AmsData::getL2ActiveImportPower() {
|
||||
return this->l2activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveImportPower() {
|
||||
uint32_t AmsData::getL3ActiveImportPower() {
|
||||
return this->l3activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveExportPower() {
|
||||
uint32_t AmsData::getL1ActiveExportPower() {
|
||||
return this->l1activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveExportPower() {
|
||||
uint32_t AmsData::getL2ActiveExportPower() {
|
||||
return this->l2activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveExportPower() {
|
||||
uint32_t AmsData::getL3ActiveExportPower() {
|
||||
return this->l3activeExportPower;
|
||||
}
|
||||
|
||||
double AmsData::getL1ActiveImportCounter() {
|
||||
return this->l1activeImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL2ActiveImportCounter() {
|
||||
return this->l2activeImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL3ActiveImportCounter() {
|
||||
return this->l3activeImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL1ActiveExportCounter() {
|
||||
return this->l1activeExportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL2ActiveExportCounter() {
|
||||
return this->l2activeExportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL3ActiveExportCounter() {
|
||||
return this->l3activeExportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getActiveImportCounter() {
|
||||
return this->activeImportCounter;
|
||||
}
|
||||
@@ -219,6 +425,10 @@ bool AmsData::isTwoPhase() {
|
||||
return this->twoPhase;
|
||||
}
|
||||
|
||||
bool AmsData::isCounterEstimated() {
|
||||
return this->counterEstimated;
|
||||
}
|
||||
|
||||
bool AmsData::isL2currentMissing() {
|
||||
return this->l2currentMissing;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSDATASTORAGE_H
|
||||
#define _AMSDATASTORAGE_H
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "Timezone.h"
|
||||
|
||||
struct DayDataPoints {
|
||||
struct DayDataPoints5 {
|
||||
uint8_t version;
|
||||
uint16_t hImport[24];
|
||||
time_t lastMeterReadTime;
|
||||
@@ -13,9 +21,9 @@ struct DayDataPoints {
|
||||
uint32_t activeExport;
|
||||
uint16_t hExport[24];
|
||||
uint8_t accuracy;
|
||||
}; // 113 bytes
|
||||
};
|
||||
|
||||
struct MonthDataPoints {
|
||||
struct MonthDataPoints6 {
|
||||
uint8_t version;
|
||||
uint16_t dImport[31];
|
||||
time_t lastMeterReadTime;
|
||||
@@ -23,13 +31,37 @@ struct MonthDataPoints {
|
||||
uint32_t activeExport;
|
||||
uint16_t dExport[31];
|
||||
uint8_t accuracy;
|
||||
}; // 142 bytes
|
||||
};
|
||||
|
||||
struct DayDataPoints {
|
||||
uint8_t version;
|
||||
uint16_t hImport[24];
|
||||
time_t lastMeterReadTime;
|
||||
uint64_t activeImport;
|
||||
uint64_t activeExport;
|
||||
uint16_t hExport[24];
|
||||
uint8_t accuracy;
|
||||
};
|
||||
|
||||
struct MonthDataPoints {
|
||||
uint8_t version;
|
||||
uint16_t dImport[31];
|
||||
time_t lastMeterReadTime;
|
||||
uint64_t activeImport;
|
||||
uint64_t activeExport;
|
||||
uint16_t dExport[31];
|
||||
uint8_t accuracy;
|
||||
};
|
||||
|
||||
class AmsDataStorage {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsDataStorage(RemoteDebug*);
|
||||
#else
|
||||
AmsDataStorage(Stream*);
|
||||
#endif
|
||||
void setTimezone(Timezone*);
|
||||
bool update(AmsData*);
|
||||
bool update(AmsData* data, time_t now);
|
||||
uint32_t getHourImport(uint8_t);
|
||||
uint32_t getHourExport(uint8_t);
|
||||
uint32_t getDayImport(uint8_t);
|
||||
@@ -47,9 +79,16 @@ public:
|
||||
uint8_t getMonthAccuracy();
|
||||
void setMonthAccuracy(uint8_t);
|
||||
|
||||
bool isHappy();
|
||||
bool isDayHappy();
|
||||
bool isMonthHappy();
|
||||
bool isHappy(time_t now);
|
||||
bool isDayHappy(time_t now);
|
||||
bool isMonthHappy(time_t now);
|
||||
|
||||
double getEstimatedImportCounter();
|
||||
|
||||
void setHourImport(uint8_t, uint32_t);
|
||||
void setHourExport(uint8_t, uint32_t);
|
||||
void setDayImport(uint8_t, uint32_t);
|
||||
void setDayExport(uint8_t, uint32_t);
|
||||
|
||||
private:
|
||||
Timezone* tz;
|
||||
@@ -67,11 +106,11 @@ private:
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
10
|
||||
};
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
void setHourImport(uint8_t, uint32_t);
|
||||
void setHourExport(uint8_t, uint32_t);
|
||||
void setDayImport(uint8_t, uint32_t);
|
||||
void setDayExport(uint8_t, uint32_t);
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsDataStorage.h"
|
||||
#include <lwip/apps/sntp.h>
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
|
||||
day.version = 5;
|
||||
#else
|
||||
AmsDataStorage::AmsDataStorage(Stream* debugger) {
|
||||
#endif
|
||||
day.version = 6;
|
||||
day.accuracy = 1;
|
||||
month.version = 6;
|
||||
month.version = 7;
|
||||
month.accuracy = 1;
|
||||
this->debugger = debugger;
|
||||
}
|
||||
@@ -16,35 +26,34 @@ void AmsDataStorage::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::update(AmsData* data) {
|
||||
if(isHappy()) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Data is up to date\n"));
|
||||
bool AmsDataStorage::update(AmsData* data, time_t now) {
|
||||
if(isHappy(now)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Happy, not updating\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Time is: %lu\n"), (int32_t) now);
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("No timezone, not updating\n"));
|
||||
return false;
|
||||
}
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
if(data->getMeterTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
now = data->getMeterTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Using meter timestamp, which is: %lu\n"), (int32_t) now);
|
||||
}
|
||||
} else if(data->getPackageTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
now = data->getPackageTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Using package timestamp, which is: %lu\n"), (int32_t) now);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Invalid time: %lu\n"), (int32_t) now);
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Before build time, not updating\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -54,52 +63,34 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
breakTime(now-3600, utcYesterday);
|
||||
breakTime(tz->toLocal(now-3600), ltzYesterDay);
|
||||
|
||||
uint32_t importCounter = data->getActiveImportCounter() * 1000;
|
||||
uint32_t exportCounter = data->getActiveExportCounter() * 1000;
|
||||
uint64_t importCounter = data->getActiveImportCounter() * 1000;
|
||||
uint64_t exportCounter = data->getActiveExportCounter() * 1000;
|
||||
|
||||
// Clear hours between last update and now
|
||||
if(day.lastMeterReadTime > now) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Invalid future timestamp for day plot, resetting\n"));
|
||||
}
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
} else if(day.activeImport == 0 || now - day.lastMeterReadTime > 86400) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) %lu == 0 || %lu - %lu > 86400"), day.activeImport, now, day.lastMeterReadTime);
|
||||
}
|
||||
} else if((importCounter > 0 && day.activeImport == 0) || now - day.lastMeterReadTime > 86400) {
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Too long since last day update, clearing data\n"));
|
||||
}
|
||||
for(int i = 0; i<24; i++) {
|
||||
setHourImport(i, 0);
|
||||
setHourExport(i, 0);
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Last day update: %lu\n"), (int32_t) day.lastMeterReadTime);
|
||||
}
|
||||
tmElements_t last;
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
uint8_t endHour = utc.Hour;
|
||||
if(last.Hour > utc.Hour){
|
||||
for(int i = 0; i < utc.Hour; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing hour: %d\n"), i);
|
||||
}
|
||||
setHourImport(i, 0);
|
||||
setHourExport(i, 0);
|
||||
}
|
||||
endHour = 24;
|
||||
}
|
||||
for(int i = last.Hour; i < endHour; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing hour: %d\n"), i);
|
||||
}
|
||||
setHourImport(i, 0);
|
||||
setHourExport(i, 0);
|
||||
}
|
||||
@@ -107,88 +98,84 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
|
||||
// Clear days between last update and now
|
||||
if(month.lastMeterReadTime > now) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Invalid future timestamp for month plot, resetting\n"));
|
||||
}
|
||||
month.activeImport = importCounter;
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
} else if(month.activeImport == 0 || now - month.lastMeterReadTime > 2682000) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) %lu == 0 || %lu - %lu > 2682000"), month.activeImport, now, month.lastMeterReadTime);
|
||||
}
|
||||
} else if((importCounter > 0 && month.activeImport == 0) || now - month.lastMeterReadTime > 2682000) {
|
||||
month.activeImport = importCounter;
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Too long since last month update, clearing data\n"));
|
||||
}
|
||||
for(int i = 1; i<=31; i++) {
|
||||
setDayImport(i, 0);
|
||||
setDayExport(i, 0);
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Last month update: %lu\n"), (int32_t) month.lastMeterReadTime);
|
||||
}
|
||||
tmElements_t last;
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
uint8_t endDay = ltz.Day;
|
||||
if(last.Day > ltz.Day) {
|
||||
for(int i = 1; i < ltz.Day; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing day: %d\n"), i);
|
||||
}
|
||||
setDayImport(i, 0);
|
||||
setDayExport(i, 0);
|
||||
}
|
||||
endDay = 31;
|
||||
}
|
||||
for(int i = last.Day; i < endDay; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Clearing day: %d\n"), i);
|
||||
}
|
||||
setDayImport(i, 0);
|
||||
setDayExport(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(data->getListType() < 3) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Not enough data in list type: %d\n"), data->getListType());
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Type %d, not updating\n"), data->getListType());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
|
||||
// Update day plot
|
||||
if(!isDayHappy()) {
|
||||
if(!isDayHappy(now)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Day is not happy\n"));
|
||||
if(day.activeImport > importCounter || day.activeExport > exportCounter) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" - reset\n"));
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
setHourImport(utcYesterday.Hour, 0);
|
||||
setHourExport(utcYesterday.Hour, 0);
|
||||
} else if(now - day.lastMeterReadTime < 4000) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" - normal\n"));
|
||||
uint32_t imp = importCounter - day.activeImport;
|
||||
uint32_t exp = exportCounter - day.activeExport;
|
||||
setHourImport(utcYesterday.Hour, imp);
|
||||
setHourExport(utcYesterday.Hour, exp);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(AmsDataStorage) Usage for hour %d: %d - %d\n"), ltzYesterDay.Hour, imp, exp);
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" - average\n"));
|
||||
float mins = (now - day.lastMeterReadTime) / 60.0;
|
||||
uint32_t im = importCounter - day.activeImport;
|
||||
uint32_t ex = exportCounter - day.activeExport;
|
||||
float ipm = im / mins;
|
||||
float epm = ex / mins;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Since last day update, minutes: %.1f, import: %d (%.2f/min), export: %d (%.2f/min)\n"), mins, im, ipm, ex, epm);
|
||||
}
|
||||
|
||||
tmElements_t last;
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
day.lastMeterReadTime = day.lastMeterReadTime - (last.Minute * 60) - last.Second;
|
||||
@@ -204,10 +191,6 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
setHourImport(last.Hour, imp);
|
||||
setHourExport(last.Hour, exp);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Estimated usage for hour %u: %.1f - %.1f (%lu)\n"), last.Hour, imp, exp, (int32_t) cur);
|
||||
}
|
||||
|
||||
day.activeImport += imp;
|
||||
day.activeExport += exp;
|
||||
day.lastMeterReadTime = cur;
|
||||
@@ -217,20 +200,28 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
|
||||
// Update month plot
|
||||
if(ltz.Hour == 0 && !isMonthHappy()) {
|
||||
if(ltz.Hour == 0 && !isMonthHappy(now)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Month is not happy\n"));
|
||||
if(month.activeImport > importCounter || month.activeExport > exportCounter) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" - reset\n"));
|
||||
month.activeImport = importCounter;
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
setDayImport(ltzYesterDay.Day, 0);
|
||||
setDayExport(ltzYesterDay.Day, 0);
|
||||
} else if(now - month.lastMeterReadTime < 90100 && now - month.lastMeterReadTime > 82700) { // DST days are 23h (82800s) and 25h (90000)
|
||||
int32_t imp = importCounter - month.activeImport;
|
||||
int32_t exp = exportCounter - month.activeExport;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Usage for day %d: %d - %d\n"), ltzYesterDay.Day, imp, exp);
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" - normal\n"));
|
||||
uint32_t imp = importCounter - month.activeImport;
|
||||
uint32_t exp = exportCounter - month.activeExport;
|
||||
|
||||
setDayImport(ltzYesterDay.Day, imp);
|
||||
setDayExport(ltzYesterDay.Day, exp);
|
||||
@@ -238,13 +229,14 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" - average\n"));
|
||||
// Make sure last month read is at midnight
|
||||
tmElements_t last;
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
month.lastMeterReadTime = month.lastMeterReadTime - (last.Hour * 3600) - (last.Minute * 60) - last.Second;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Last month read after resetting to midnight: %lu\n"), (int32_t) month.lastMeterReadTime);
|
||||
}
|
||||
|
||||
float hrs = (now - month.lastMeterReadTime) / 3600.0;
|
||||
uint32_t im = importCounter - month.activeImport;
|
||||
@@ -252,10 +244,6 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
float iph = im / hrs;
|
||||
float eph = ex / hrs;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Since last month update, hours: %.1f, import: %d (%.2f/hr), export: %d (%.2f/hr)\n"), hrs, im, iph, ex, eph);
|
||||
}
|
||||
|
||||
time_t stopAt = now - (ltz.Hour * 3600) - (ltz.Minute * 60) - ltz.Second;
|
||||
while(month.lastMeterReadTime < stopAt) {
|
||||
time_t cur = min(month.lastMeterReadTime + 86400, stopAt);
|
||||
@@ -267,10 +255,6 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
setDayImport(last.Day, imp);
|
||||
setDayExport(last.Day, exp);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Estimated usage for day %u: %.1f - %.1f (%lu)\n"), last.Day, imp, exp, (int32_t) cur);
|
||||
}
|
||||
|
||||
month.activeImport += imp;
|
||||
month.activeExport += exp;
|
||||
month.lastMeterReadTime = cur;
|
||||
@@ -439,9 +423,6 @@ uint32_t AmsDataStorage::getDayExport(uint8_t day) {
|
||||
|
||||
bool AmsDataStorage::load() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Unable to load LittleFS\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -450,18 +431,48 @@ bool AmsDataStorage::load() {
|
||||
File file = LittleFS.open(FILE_DAYPLOT, "r");
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
DayDataPoints* day = (DayDataPoints*) buf;
|
||||
if(buf[0] > 5) {
|
||||
DayDataPoints* day = (DayDataPoints*) buf;
|
||||
ret = setDayData(*day);
|
||||
} else {
|
||||
DayDataPoints5* old = (DayDataPoints5*) buf;
|
||||
DayDataPoints day = { old->version };
|
||||
day.lastMeterReadTime = old->lastMeterReadTime;
|
||||
day.activeImport = old->activeImport;
|
||||
day.activeExport = old->activeExport;
|
||||
day.accuracy = old->accuracy;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
day.hImport[i] = old->hImport[i];
|
||||
day.hExport[i] = old->hExport[i];
|
||||
}
|
||||
|
||||
ret = setDayData(day);
|
||||
}
|
||||
file.close();
|
||||
ret = setDayData(*day);
|
||||
}
|
||||
|
||||
if(LittleFS.exists(FILE_MONTHPLOT)) {
|
||||
File file = LittleFS.open(FILE_MONTHPLOT, "r");
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
MonthDataPoints* month = (MonthDataPoints*) buf;
|
||||
if(buf[0] > 6) {
|
||||
MonthDataPoints* month = (MonthDataPoints*) buf;
|
||||
ret &= setMonthData(*month);
|
||||
} else {
|
||||
MonthDataPoints6* old = (MonthDataPoints6*) buf;
|
||||
MonthDataPoints month = { old->version };
|
||||
month.lastMeterReadTime = old->lastMeterReadTime;
|
||||
month.activeImport = old->activeImport;
|
||||
month.activeExport = old->activeExport;
|
||||
month.accuracy = old->accuracy;
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
month.dImport[i] = old->dImport[i];
|
||||
month.dExport[i] = old->dExport[i];
|
||||
}
|
||||
|
||||
ret &= setMonthData(month);
|
||||
}
|
||||
file.close();
|
||||
ret = ret && setMonthData(*month);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -469,9 +480,6 @@ bool AmsDataStorage::load() {
|
||||
|
||||
bool AmsDataStorage::save() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Unable to load LittleFS\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
{
|
||||
@@ -504,38 +512,40 @@ MonthDataPoints AmsDataStorage::getMonthData() {
|
||||
}
|
||||
|
||||
bool AmsDataStorage::setDayData(DayDataPoints& day) {
|
||||
if(day.version == 5) {
|
||||
if(day.version == 5 || day.version == 6) {
|
||||
this->day = day;
|
||||
this->day.version = 6;
|
||||
return true;
|
||||
} else if(day.version == 4) {
|
||||
this->day = day;
|
||||
this->day.accuracy = 1;
|
||||
this->day.version = 5;
|
||||
this->day.version = 6;
|
||||
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 = 6;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::setMonthData(MonthDataPoints& month) {
|
||||
if(month.version == 6) {
|
||||
if(month.version == 6 || month.version == 7) {
|
||||
this->month = month;
|
||||
this->month.version = 7;
|
||||
return true;
|
||||
} else if(month.version == 5) {
|
||||
this->month = month;
|
||||
this->month.accuracy = 1;
|
||||
this->month.version = 6;
|
||||
this->month.version = 7;
|
||||
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 = 7;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -572,25 +582,23 @@ void AmsDataStorage::setMonthAccuracy(uint8_t accuracy) {
|
||||
month.accuracy = accuracy;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isHappy() {
|
||||
return isDayHappy() && isMonthHappy();
|
||||
bool AmsDataStorage::isHappy(time_t now) {
|
||||
return isDayHappy(now) && isMonthHappy(now);
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isDayHappy() {
|
||||
bool AmsDataStorage::isDayHappy(time_t now) {
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
if(now < day.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data timestamp %lu < %lu\n"), (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
if(now-day.lastMeterReadTime > 3600) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data timestamp age %lu - %lu > 3600\n"), (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
// 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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -598,39 +606,47 @@ bool AmsDataStorage::isDayHappy() {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(day.lastMeterReadTime), last);
|
||||
if(tm.Hour != last.Hour) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data hour of last timestamp %d > %d\n"), tm.Hour, last.Hour);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isMonthHappy() {
|
||||
bool AmsDataStorage::isMonthHappy(time_t now) {
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < month.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Month data timestamp %lu < %lu\n"), (int32_t) now, (int32_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 25 hours, because of DST
|
||||
if(now-month.lastMeterReadTime > 90000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tmElements_t tm, last;
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
if(tm.Day != last.Day) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Month data day of last timestamp %d > %d\n"), tm.Day, last.Day);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(now-month.lastMeterReadTime > 90100) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lu - %lu > 3600\n", (int32_t) now, (int32_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
double AmsDataStorage::getEstimatedImportCounter() {
|
||||
if(day.lastMeterReadTime == 0) return 0;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
double hours = (now - day.lastMeterReadTime) / 3600.0;
|
||||
uint64_t total = 0;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
total += getHourImport(i);
|
||||
}
|
||||
double perHour = total / 24.0;
|
||||
return (day.activeImport + (perHour * hours)) / 1000.0;
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _COSEM_H
|
||||
#define _COSEM_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATAPASERSER_H
|
||||
#define _DATAPASERSER_H
|
||||
|
||||
@@ -10,6 +16,10 @@
|
||||
#define DATA_TAG_MBUS 0x68
|
||||
#define DATA_TAG_GBT 0xE0
|
||||
#define DATA_TAG_GCM 0xDB
|
||||
#define DATA_TAG_SNRM 0x81
|
||||
#define DATA_TAG_AARQ 0x60
|
||||
#define DATA_TAG_AARE 0x61
|
||||
#define DATA_TAG_RES 0xC4 // Get Response
|
||||
|
||||
#define DATA_PARSE_OK 0
|
||||
#define DATA_PARSE_FAIL -1
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATAPASERSERS_H
|
||||
#define _DATAPASERSERS_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DLMSPARSER_H
|
||||
#define _DLMSPARSER_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DSMRPARSER_H
|
||||
#define _DSMRPARSER_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GBTPARSER_H
|
||||
#define _GBTPARSER_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GCMPARSER_H
|
||||
#define _GCMPARSER_H
|
||||
|
||||
@@ -9,12 +15,6 @@
|
||||
#define GCM_DECRYPT_FAILED -52
|
||||
#define GCM_ENCRYPTION_KEY_FAILED -53
|
||||
|
||||
typedef struct GCMSizeDef {
|
||||
uint8_t flag;
|
||||
uint16_t format;
|
||||
} __attribute__((packed)) GCMSizeDef;
|
||||
|
||||
|
||||
class GCMParser {
|
||||
public:
|
||||
GCMParser(uint8_t *encryption_key, uint8_t *authentication_key);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HDLCPARSER_H
|
||||
#define _HDLCPARSER_H
|
||||
|
||||
@@ -24,6 +30,11 @@ typedef struct HDLC3CtrlHcs {
|
||||
class HDLCParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
|
||||
private:
|
||||
uint8_t lastSequenceNumber = 0;
|
||||
uint16_t pos = 0;
|
||||
uint8_t *buf = NULL;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LLCPARSER_H
|
||||
#define _LLCPARSER_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MBUSPARSER_H
|
||||
#define _MBUSPARSER_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CRC_H
|
||||
#define _CRC_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _NTOHLL_H
|
||||
#define _NTOHLL_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Cosem.h"
|
||||
#include "lwip/def.h"
|
||||
#include <TimeLib.h>
|
||||
@@ -18,7 +24,7 @@ time_t decodeCosemDateTime(CosemDateTime timestamp) {
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(timestamp.deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
time += deviation * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DlmsParser.h"
|
||||
#include "Cosem.h"
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DsmrParser.h"
|
||||
#include "crc.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GbtParser.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GcmParser.h"
|
||||
#include "lwip/def.h"
|
||||
#if defined(ESP8266)
|
||||
@@ -27,33 +33,35 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
memcpy(ctx.system_title, ptr, systemTitleLength);
|
||||
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
|
||||
|
||||
int len = 0;
|
||||
int headersize = 2 + systemTitleLength;
|
||||
uint32_t len = 0;
|
||||
uint32_t headersize = 2 + systemTitleLength;
|
||||
ptr += systemTitleLength;
|
||||
if(((*ptr) & 0xFF) == 0x81) {
|
||||
ptr++;
|
||||
len = *ptr;
|
||||
// 1-byte payload length
|
||||
ptr++;
|
||||
len = *ptr++;
|
||||
headersize += 2;
|
||||
} else if(((*ptr) & 0xFF) == 0x82) {
|
||||
GCMSizeDef* h = (GCMSizeDef*) ptr;
|
||||
|
||||
// 2-byte payload length
|
||||
len = (ntohs(h->format) & 0xFFFF);
|
||||
|
||||
ptr += 3;
|
||||
headersize += 3;
|
||||
} else {
|
||||
len = *ptr;
|
||||
ptr++;
|
||||
len = *ptr++ << 8;
|
||||
len |= *ptr++;
|
||||
headersize += 3;
|
||||
} else if(((*ptr) & 0xFF) == 0x84) {
|
||||
// 4-byte payload length
|
||||
ptr++;
|
||||
len = *ptr++ << 24;
|
||||
len |= *ptr++ << 16;
|
||||
len |= *ptr++ << 8;
|
||||
len |= *ptr++;
|
||||
headersize += 5;
|
||||
} else {
|
||||
len = *ptr++;
|
||||
headersize++;
|
||||
}
|
||||
if(len + headersize > ctx.length)
|
||||
return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
//Serial.printf("\nL: %d : %d, %d\n", length, len, headersize);
|
||||
|
||||
uint8_t additional_authenticated_data[17];
|
||||
memcpy(additional_authenticated_data, ptr, 1);
|
||||
|
||||
@@ -70,6 +78,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
int footersize = 0;
|
||||
|
||||
// Authentication enabled
|
||||
bool authenticate = false;
|
||||
uint8_t authentication_tag[12];
|
||||
uint8_t authkeylen = 0, aadlen = 0;
|
||||
if((sec & 0x10) == 0x10) {
|
||||
@@ -78,6 +87,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
@@ -86,7 +96,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
br_aes_ct_ctr_init(&bc, encryption_key, 16);
|
||||
br_gcm_init(&gcmCtx, &bc.vtable, br_ghash_ctmul32);
|
||||
br_gcm_reset(&gcmCtx, initialization_vector, sizeof(initialization_vector));
|
||||
if(authkeylen > 0) {
|
||||
if(authenticate) {
|
||||
br_gcm_aad_inject(&gcmCtx, additional_authenticated_data, aadlen);
|
||||
}
|
||||
br_gcm_flip(&gcmCtx);
|
||||
@@ -104,7 +114,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
if (0 != success) {
|
||||
return GCM_ENCRYPTION_KEY_FAILED;
|
||||
}
|
||||
if (0 < authkeylen) {
|
||||
if (authenticate) {
|
||||
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), initialization_vector, sizeof(initialization_vector),
|
||||
additional_authenticated_data, aadlen, authentication_tag, authkeylen,
|
||||
cipher_text, (unsigned char*)(ptr));
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HdlcParser.h"
|
||||
#include "lwip/def.h"
|
||||
#include "crc.h"
|
||||
@@ -49,8 +55,41 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
ptr += 3;
|
||||
|
||||
// Exclude all of header and 3 byte footer
|
||||
ctx.length -= ptr-d+3;
|
||||
return ptr-d;
|
||||
ctx.length -= ptr-d;
|
||||
if(ctx.length > 1) {
|
||||
ctx.length -= 3;
|
||||
}
|
||||
|
||||
// Payload incomplete
|
||||
if((h->format & 0x08) == 0x08) {
|
||||
if(lastSequenceNumber == 0) {
|
||||
if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024);
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
|
||||
pos += ctx.length;
|
||||
|
||||
lastSequenceNumber++;
|
||||
return DATA_PARSE_INTERMEDIATE_SEGMENT;
|
||||
} else if(lastSequenceNumber > 0) {
|
||||
lastSequenceNumber = 0;
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
|
||||
pos += ctx.length;
|
||||
|
||||
memcpy((uint8_t *) d, buf, pos);
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
ctx.length = pos;
|
||||
pos = 0;
|
||||
return DATA_PARSE_OK;
|
||||
} else {
|
||||
return ptr-d;
|
||||
}
|
||||
}
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LlcParser.h"
|
||||
|
||||
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MbusParser.h"
|
||||
|
||||
int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "crc.h"
|
||||
|
||||
uint16_t crc16_x25(const uint8_t* p, int len)
|
||||
@@ -15,7 +21,7 @@ uint16_t crc16 (const uint8_t *p, int len) {
|
||||
uint16_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
int i;
|
||||
uint8_t i;
|
||||
crc ^= *p++;
|
||||
for (i = 0 ; i < 8 ; ++i) {
|
||||
if (crc & 1)
|
||||
@@ -26,4 +32,4 @@ uint16_t crc16 (const uint8_t *p, int len) {
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ntohll.h"
|
||||
|
||||
uint64_t ntohll(uint64_t x) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSMQTTHANDLER_H
|
||||
#define _AMSMQTTHANDLER_H
|
||||
|
||||
@@ -7,7 +13,7 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "EntsoeApi.h"
|
||||
#include "PriceService.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
@@ -15,12 +21,23 @@
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
mqtt.dropOverflow(true);
|
||||
};
|
||||
#else
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
mqtt.dropOverflow(true);
|
||||
};
|
||||
#endif
|
||||
|
||||
void setCaVerification(bool);
|
||||
void setConfig(MqttConfig& mqttConfig);
|
||||
@@ -33,10 +50,10 @@ public:
|
||||
|
||||
virtual uint8_t getFormat() { return 0; };
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) { return false; };
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) { return false; };
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*) { return false; };
|
||||
virtual bool publishPrices(EntsoeApi* eapi) { return false; };
|
||||
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*) { return false; };
|
||||
virtual bool publishPrices(PriceService* ps) { return false; };
|
||||
virtual bool publishSystem(HwTools*, PriceService*, EnergyAccounting*) { return false; };
|
||||
virtual bool publishRaw(String data) { return false; };
|
||||
virtual void onMessage(String &topic, String &payload) {};
|
||||
|
||||
@@ -48,8 +65,13 @@ public:
|
||||
};
|
||||
|
||||
protected:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
MqttConfig mqttConfig;
|
||||
bool mqttConfigChanged = true;
|
||||
MQTTClient mqtt = MQTTClient(256);
|
||||
unsigned long lastMqttRetry = -10000;
|
||||
bool caVerification = true;
|
||||
@@ -57,6 +79,7 @@ protected:
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
char* json;
|
||||
uint16_t BufferSize = 2048;
|
||||
uint64_t lastStateUpdate = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "AmsStorage.h"
|
||||
@@ -9,6 +15,7 @@ void AmsMqttHandler::setCaVerification(bool caVerification) {
|
||||
|
||||
void AmsMqttHandler::setConfig(MqttConfig& mqttConfig) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::connect() {
|
||||
@@ -19,112 +26,87 @@ bool AmsMqttHandler::connect() {
|
||||
lastMqttRetry = millis();
|
||||
|
||||
time_t epoch = time(nullptr);
|
||||
|
||||
WiFiClient *actualClient = NULL;
|
||||
|
||||
if(mqttConfig.ssl) {
|
||||
if(epoch < FirmwareVersion::BuildEpoch) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("NTP not ready for MQTT SSL\n"));
|
||||
return false;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("MQTT SSL is configured (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
if(mqttSecureClient == NULL) {
|
||||
mqttSecureClient = new WiFiClientSecure();
|
||||
#if defined(ESP8266)
|
||||
mqttSecureClient->setBufferSizes(512, 512);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("ESP8266 firmware does not have enough memory...\n"));
|
||||
return false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
if(mqttConfigChanged) {
|
||||
if(caVerification && LittleFS.begin()) {
|
||||
File file;
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_CA)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT CA file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CA, (char*) "r");
|
||||
#if defined(ESP8266)
|
||||
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
|
||||
mqttSecureClient->setTrustAnchors(serverTrustedCA);
|
||||
#elif defined(ESP32)
|
||||
if(mqttSecureClient->loadCACert(file, file.size())) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA accepted\n"));
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("CA was rejected\n"));
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
if(!mqttSecureClient->loadCACert(file, file.size())) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
file.close();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No CA, disabling validation\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
||||
file.close();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
||||
file.close();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Loading cert and key (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
if(LittleFS.exists(FILE_MQTT_CERT)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
mqttSecureClient->loadCertificate(file, file.size());
|
||||
file.close();
|
||||
}
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||
file.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
LittleFS.end();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA verification disabled\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
}
|
||||
mqttClient = mqttSecureClient;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("MQTT SSL setup complete (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
}
|
||||
} else if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient->stop();
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
mqttClient = NULL;
|
||||
}
|
||||
|
||||
if(mqttClient == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No SSL, using client without SSL support\n"));
|
||||
mqttClient = new WiFiClient();
|
||||
actualClient = mqttSecureClient;
|
||||
} else {
|
||||
if(mqttClient == NULL) {
|
||||
mqttClient = new WiFiClient();
|
||||
}
|
||||
actualClient = mqttClient;
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Connecting to MQTT %s:%d\n"), mqttConfig.host, mqttConfig.port);
|
||||
|
||||
mqtt.begin(mqttConfig.host, mqttConfig.port, *mqttClient);
|
||||
mqttConfigChanged = false;
|
||||
mqtt.begin(mqttConfig.host, mqttConfig.port, *actualClient);
|
||||
String statusTopic = String(mqttConfig.publishTopic) + "/status";
|
||||
mqtt.setWill(statusTopic.c_str(), "offline", true, 0);
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(mqttSecureClient) {
|
||||
time_t epoch = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Setting NTP time %lu for secure MQTT connection\n"), epoch);
|
||||
mqttSecureClient->setX509Time(epoch);
|
||||
}
|
||||
#endif
|
||||
@@ -132,17 +114,31 @@ bool AmsMqttHandler::connect() {
|
||||
// Connect to a unsecure or secure MQTT server
|
||||
if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) ||
|
||||
(strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
|
||||
mqtt.onMessage(std::bind(&AmsMqttHandler::onMessage, this, std::placeholders::_1, std::placeholders::_2));
|
||||
if(strlen(mqttConfig.subscribeTopic) > 0) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR(" Subscribing to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Subscribing to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
if(!mqtt.subscribe(mqttConfig.subscribeTopic)) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
}
|
||||
}
|
||||
mqtt.publish(statusTopic, "online", true, 0);
|
||||
mqtt.loop();
|
||||
return true;
|
||||
} else {
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Failed to connect to MQTT: %d\n"), mqtt.lastError());
|
||||
#if defined(ESP8266)
|
||||
if(mqttSecureClient) {
|
||||
@@ -160,15 +156,6 @@ void AmsMqttHandler::disconnect() {
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
yield();
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
mqttClient = NULL;
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lwmqtt_err_t AmsMqttHandler::lastError() {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CLOUDCONNECTOR_H
|
||||
#define _CLOUDCONNECTOR_H
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "mbedtls/ssl.h"
|
||||
#include "mbedtls/platform.h"
|
||||
#include "mbedtls/net.h"
|
||||
@@ -11,18 +19,32 @@
|
||||
#include "mbedtls/error.h"
|
||||
#include "mbedtls/certs.h"
|
||||
#include "mbedtls/rsa.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "AmsData.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "ConnectionHandler.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <WiFiUdp.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
const unsigned char PUBLIC_KEY[] = \
|
||||
"-----BEGIN PUBLIC KEY-----\n"\
|
||||
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoIo0CSuuX3tAdF7KPssdlzJNX\n"\
|
||||
"QryhgVV1rQIFPhHv3SxzyKtRrRM9s0CVfymcibhnEBXxxg3pxlGmwI/R6k7HHXJN\n"\
|
||||
"lBsXzzDtZ/GHDVnw+xRakTfRT0Zt+xdJSH5xJNWq4EwpvJfjA22L1Nz4dKSpgWMx\n"\
|
||||
"VRndAaXf0s7Q1XBz2wIDAQAB\n"\
|
||||
"-----END PUBLIC KEY-----\0";
|
||||
#define CC_BUF_SIZE 2048
|
||||
|
||||
|
||||
//const unsigned char PUBLIC_KEY[] = { 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xe8, 0x22, 0x8d, 0x02, 0x4a, 0xeb, 0x97, 0xde, 0xd0, 0x1d, 0x17, 0xb2, 0x8f, 0xb2, 0xc7, 0x65, 0xcc, 0x93, 0x57, 0x42, 0xbc, 0xa1, 0x81, 0x55, 0x75, 0xad, 0x02, 0x05, 0x3e, 0x11, 0xef, 0xdd, 0x2c, 0x73, 0xc8, 0xab, 0x51, 0xad, 0x13, 0x3d, 0xb3, 0x40, 0x95, 0x7f, 0x29, 0x9c, 0x89, 0xb8, 0x67, 0x10, 0x15, 0xf1, 0xc6, 0x0d, 0xe9, 0xc6, 0x51, 0xa6, 0xc0, 0x8f, 0xd1, 0xea, 0x4e, 0xc7, 0x1d, 0x72, 0x4d, 0x94, 0x1b, 0x17, 0xcf, 0x30, 0xed, 0x67, 0xf1, 0x87, 0x0d, 0x59, 0xf0, 0xfb, 0x14, 0x5a, 0x91, 0x37, 0xd1, 0x4f, 0x46, 0x6d, 0xfb, 0x17, 0x49, 0x48, 0x7e, 0x71, 0x24, 0xd5, 0xaa, 0xe0, 0x4c, 0x29, 0xbc, 0x97, 0xe3, 0x03, 0x6d, 0x8b, 0xd4, 0xdc, 0xf8, 0x74, 0xa4, 0xa9, 0x81, 0x63, 0x31, 0x55, 0x19, 0xdd, 0x01, 0xa5, 0xdf, 0xd2, 0xce, 0xd0, 0xd5, 0x70, 0x73, 0xdb, 0x02, 0x03, 0x01, 0x00, 0x01};
|
||||
static const char CC_JSON_POWER[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu}";
|
||||
static const char CC_JSON_POWER_LIST3[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu,\"tP\":%.3f,\"tQ\":%.3f}";
|
||||
static const char CC_JSON_PHASE[] PROGMEM = "%s\"%d\":{\"u\":%.2f,\"i\":%s}";
|
||||
static const char CC_JSON_PHASE_LIST4[] PROGMEM = "%s\"%d\":{\"u\":%.2f,\"i\":%s,\"Pim\":%lu,\"Pex\":%lu,\"pf\":%.2f}";
|
||||
static const char CC_JSON_STATUS[] PROGMEM = ",\"status\":{\"esp\":{\"state\":%d,\"error\":%d},\"han\":{\"state\":%d,\"error\":%d},\"wifi\":{\"state\":%d,\"error\":%d},\"mqtt\":{\"state\":%d,\"error\":%d}}";
|
||||
static const char CC_JSON_INIT[] PROGMEM = ",\"init\":{\"mac\":\"%s\",\"apmac\":\"%s\",\"version\":\"%s\",\"boardType\":%d,\"bootReason\":%d,\"bootCause\":%d,\"tz\":\"%s\"},\"meter\":{\"manufacturerId\":%d,\"manufacturer\":\"%s\",\"model\":\"%s\",\"id\":\"%s\",\"system\":\"%s\",\"fuse\":%d,\"import\":%d,\"export\":%d},\"network\":{\"ip\":\"%s\",\"mask\":\"%s\",\"gw\":\"%s\",\"dns1\":\"%s\",\"dns2\":\"%s\"}";
|
||||
|
||||
struct CloudData {
|
||||
uint8_t type;
|
||||
@@ -31,17 +53,80 @@ struct CloudData {
|
||||
|
||||
class CloudConnector {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
CloudConnector(RemoteDebug*);
|
||||
void setup(const unsigned char * key);
|
||||
void send();
|
||||
#else
|
||||
CloudConnector(Stream*);
|
||||
#endif
|
||||
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc);
|
||||
void setMqttHandler(AmsMqttHandler* mqttHandler);
|
||||
void update(AmsData& data, EnergyAccounting& ea);
|
||||
void setPriceConfig(PriceServiceConfig&);
|
||||
void setEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
void forceUpdate();
|
||||
void setConnectionHandler(ConnectionHandler* ch);
|
||||
String generateSeed();
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
HwTools* hw = NULL;
|
||||
ConnectionHandler* ch = NULL;
|
||||
ResetDataContainer* rdc = NULL;
|
||||
AmsMqttHandler* mqttHandler = NULL;
|
||||
CloudConfig config;
|
||||
PriceServiceConfig priceConfig;
|
||||
unsigned long lastPriceConfig = 0;
|
||||
EnergyAccountingConfig eac;
|
||||
unsigned long lastEac = 0;
|
||||
HTTPClient http;
|
||||
WiFiUDP udp;
|
||||
int maxPwr = 0;
|
||||
uint8_t boardType = 0;
|
||||
char timezone[32];
|
||||
uint8_t distributionSystem = 0;
|
||||
uint16_t mainFuse = 0, productionCapacity = 0;
|
||||
|
||||
unsigned char buf[4096];
|
||||
String uuid;
|
||||
bool initialized = false;
|
||||
unsigned long lastUpdate = 0;
|
||||
char mac[18];
|
||||
char apmac[18];
|
||||
|
||||
String seed = "";
|
||||
|
||||
char clearBuffer[CC_BUF_SIZE];
|
||||
unsigned char encryptedBuffer[256];
|
||||
mbedtls_rsa_context* rsa = nullptr;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_entropy_context entropy;
|
||||
char* pers = "amsreader";
|
||||
|
||||
bool init();
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
|
||||
String meterManufacturer(uint8_t type) {
|
||||
switch(type) {
|
||||
case AmsTypeAidon: return F("Aidon");
|
||||
case AmsTypeKaifa: return F("Kaifa");
|
||||
case AmsTypeKamstrup: return F("Kamstrup");
|
||||
case AmsTypeIskra: return F("Iskra");
|
||||
case AmsTypeLandisGyr: return F("Landis+Gyr");
|
||||
case AmsTypeSagemcom: return F("Sagemcom");
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
String distributionSystemStr(uint8_t ds) {
|
||||
switch(ds) {
|
||||
case 1: return F("IT");
|
||||
case 2: return F("TN");
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,44 +1,468 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CloudConnector.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "crc.h"
|
||||
#include "Uptime.h"
|
||||
#include "hexutils.h"
|
||||
#if defined(ESP32)
|
||||
#include <ESPRandom.h>
|
||||
#endif
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||
#include "esp32/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
#include "esp32s2/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
#include "esp32c3/rom/rtc.h"
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
#include "esp32s3/rom/rtc.h"
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
CloudConnector::CloudConnector(RemoteDebug* debugger) {
|
||||
#else
|
||||
CloudConnector::CloudConnector(Stream* debugger) {
|
||||
#endif
|
||||
this->debugger = debugger;
|
||||
mbedtls_pk_context pk;
|
||||
mbedtls_pk_init(&pk);
|
||||
|
||||
int error_code = 0;
|
||||
if((error_code = mbedtls_pk_parse_public_key(&pk, PUBLIC_KEY, sizeof(PUBLIC_KEY))) == 0){
|
||||
debugger->printf("RSA public key OK\n");
|
||||
rsa = mbedtls_pk_rsa(pk);
|
||||
} else {
|
||||
debugger->printf("RSA public key read error: ");
|
||||
mbedtls_strerror(error_code, (char*) buf, 4096);
|
||||
debugger->printf("%s\n", buf);
|
||||
}
|
||||
debugger->flush();
|
||||
//send();
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http.setReuse(false);
|
||||
http.setTimeout(60000);
|
||||
http.setUserAgent("ams2mqtt/" + String(FirmwareVersion::VersionString));
|
||||
http.useHTTP10(true);
|
||||
|
||||
uint8_t mac[6];
|
||||
uint8_t apmac[6];
|
||||
|
||||
#if defined(ESP8266)
|
||||
wifi_get_macaddr(STATION_IF, mac);
|
||||
wifi_get_macaddr(SOFTAP_IF, apmac);
|
||||
#elif defined(ESP32)
|
||||
esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_STA, mac);
|
||||
esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_AP, apmac);
|
||||
#endif
|
||||
sprintf_P(this->mac, PSTR("%02X:%02X:%02X:%02X:%02X:%02X"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
sprintf_P(this->apmac, PSTR("%02X:%02X:%02X:%02X:%02X:%02X"), apmac[0], apmac[1], apmac[2], apmac[3], apmac[4], apmac[5]);
|
||||
}
|
||||
|
||||
void CloudConnector::send() {
|
||||
if(rsa != nullptr && mbedtls_rsa_check_pubkey(rsa) == 0) {
|
||||
memset(buf, 0, 4096);
|
||||
|
||||
CloudData data = {65, 127};
|
||||
unsigned char toEncrypt[4096] = {0};
|
||||
|
||||
debugger->println("RSA clear data: ");
|
||||
debugPrint(toEncrypt, 0, 256);
|
||||
|
||||
mbedtls_rsa_rsaes_pkcs1_v15_encrypt(rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, 256, toEncrypt, buf);
|
||||
|
||||
//byte hashResult[32];
|
||||
//mbedtls_sha256(toEncrypt, strlen((char*) toEncrypt), hashResult, 0);
|
||||
//int success = mbedtls_rsa_rsassa_pkcs1_v15_sign(rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, strlen((char*) hashResult), hashResult, buf);
|
||||
debugger->println("RSA encrypted data: ");
|
||||
debugPrint(buf, 0, 256);
|
||||
} else {
|
||||
debugger->println("RSA key is invalid");
|
||||
bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc) {
|
||||
bool ret = false;
|
||||
#if defined(ESP32)
|
||||
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
|
||||
ESPRandom::uuid4(config.clientId);
|
||||
ret = true;
|
||||
}
|
||||
uuid = ESPRandom::uuidToString(config.clientId);
|
||||
#endif
|
||||
|
||||
this->config = config;
|
||||
this->hw = hw;
|
||||
this->rdc = rdc;
|
||||
|
||||
this->boardType = system.boardType;
|
||||
strcpy(this->timezone, ntp.timezone);
|
||||
|
||||
this->maxPwr = 0;
|
||||
this->distributionSystem = meter.distributionSystem;
|
||||
this->mainFuse = meter.mainFuse;
|
||||
this->productionCapacity = meter.productionCapacity;
|
||||
|
||||
this->initialized = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CloudConnector::setMqttHandler(AmsMqttHandler* mqttHandler) {
|
||||
this->mqttHandler = mqttHandler;
|
||||
}
|
||||
|
||||
bool CloudConnector::init() {
|
||||
if(config.enabled) {
|
||||
//if(config.port == 0)
|
||||
config.port = 7443;
|
||||
//if(strlen(config.hostname) == 0)
|
||||
strcpy_P(config.hostname, PSTR("cloud.amsleser.no"));
|
||||
|
||||
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/public.key"), config.hostname);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Downloading public key from %s\n"), clearBuffer);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
if(http.begin(client, clearBuffer)) {
|
||||
#elif defined(ESP32)
|
||||
if(http.begin(clearBuffer)) {
|
||||
#endif
|
||||
int status = http.GET();
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(status == HTTP_CODE_OK) {
|
||||
String pub = http.getString();
|
||||
http.end();
|
||||
|
||||
memset(clearBuffer, 0, CC_BUF_SIZE);
|
||||
snprintf(clearBuffer, CC_BUF_SIZE, pub.c_str());
|
||||
|
||||
mbedtls_pk_context pk;
|
||||
mbedtls_pk_init(&pk);
|
||||
|
||||
int error_code = 0;
|
||||
if((error_code = mbedtls_pk_parse_public_key(&pk, (unsigned char*) clearBuffer, strlen((const char*) clearBuffer)+1)) == 0){
|
||||
rsa = mbedtls_pk_rsa(pk);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
mbedtls_entropy_init(&entropy);
|
||||
|
||||
int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
|
||||
&entropy, (const unsigned char *) pers,
|
||||
strlen(pers));
|
||||
if(ret != 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("mbedtls_ctr_drbg_seed return code: %d\n"), ret);
|
||||
}
|
||||
return ret == 0;
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf("RSA public key read error: ");
|
||||
mbedtls_strerror(error_code, clearBuffer, CC_BUF_SIZE);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf("%s\n", clearBuffer);
|
||||
}
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Communication error, returned status: %d\n"), status);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf(http.errorToString(status).c_str());
|
||||
debugger->println();
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf(http.getString().c_str());
|
||||
|
||||
http.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
if(!config.enabled) return;
|
||||
unsigned long now = millis();
|
||||
if(now-lastUpdate < config.interval*1000) return;
|
||||
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Client ID is not valid\n"));
|
||||
return;
|
||||
}
|
||||
if(data.getListType() < 2) return;
|
||||
|
||||
if(!initialized && !init()) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
lastUpdate = now;
|
||||
config.enabled = false;
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
|
||||
memset(clearBuffer, 0, CC_BUF_SIZE);
|
||||
|
||||
int pos = 0;
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"id\":\"%s\""), uuid.c_str());
|
||||
if(!seed.isEmpty()) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"seed\":\"%s\""), seed.c_str());
|
||||
}
|
||||
|
||||
if(lastUpdate == 0) {
|
||||
seed.clear();
|
||||
if(mainFuse > 0 && distributionSystem > 0) {
|
||||
int voltage = distributionSystem == 2 ? 400 : 230;
|
||||
if(data.isThreePhase()) {
|
||||
maxPwr = mainFuse * sqrt(3) * voltage;
|
||||
} else if(data.isTwoPhase()) {
|
||||
maxPwr = mainFuse * voltage;
|
||||
} else {
|
||||
maxPwr = mainFuse * 230;
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress localIp;
|
||||
IPAddress subnet;
|
||||
IPAddress gateway;
|
||||
IPAddress dns1;
|
||||
IPAddress dns2;
|
||||
|
||||
if(ch == NULL) {
|
||||
localIp = WiFi.localIP();
|
||||
subnet = IPAddress(255,255,255,0);
|
||||
gateway = WiFi.subnetMask();
|
||||
dns1 = WiFi.dnsIP(0);
|
||||
dns2 = WiFi.dnsIP(1);
|
||||
} else {
|
||||
localIp = ch->getIP();
|
||||
subnet = ch->getSubnetMask();
|
||||
gateway = ch->getGateway();
|
||||
dns1 = ch->getDns(0);
|
||||
dns2 = ch->getDns(1);
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_INIT,
|
||||
mac,
|
||||
apmac,
|
||||
FirmwareVersion::VersionString,
|
||||
boardType,
|
||||
rtc_get_reset_reason(0),
|
||||
rdc == NULL ? 0 : rdc->last_cause,
|
||||
timezone,
|
||||
data.getMeterType(),
|
||||
meterManufacturer(data.getMeterType()).c_str(),
|
||||
data.getMeterModel().c_str(),
|
||||
data.getMeterId().c_str(),
|
||||
distributionSystemStr(distributionSystem).c_str(),
|
||||
mainFuse,
|
||||
maxPwr,
|
||||
productionCapacity,
|
||||
localIp.toString().c_str(),
|
||||
subnet.toString().c_str(),
|
||||
gateway.toString().c_str(),
|
||||
dns1.toString().c_str(),
|
||||
dns2.toString().c_str()
|
||||
);
|
||||
} else if(lastPriceConfig == 0) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"price\":{\"area\":\"%s\",\"currency\":\"%s\"}"), priceConfig.area, priceConfig.currency);
|
||||
lastPriceConfig = now;
|
||||
} else if(lastEac == 0) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"accounting\":{\"hours\":%d,\"thresholds\":[%d,%d,%d,%d,%d,%d,%d,%d,%d]}"),
|
||||
eac.hours,
|
||||
eac.thresholds[0],
|
||||
eac.thresholds[1],
|
||||
eac.thresholds[2],
|
||||
eac.thresholds[3],
|
||||
eac.thresholds[4],
|
||||
eac.thresholds[5],
|
||||
eac.thresholds[6],
|
||||
eac.thresholds[7],
|
||||
eac.thresholds[8]
|
||||
);
|
||||
lastEac = now;
|
||||
}
|
||||
|
||||
float vcc = 0.0;
|
||||
int rssi = 0;
|
||||
float temperature = -127;
|
||||
if(hw != NULL) {
|
||||
vcc = hw->getVcc();
|
||||
rssi = hw->getWifiRssi();
|
||||
temperature = hw->getTemperature();
|
||||
}
|
||||
|
||||
uint8_t espStatus;
|
||||
#if defined(ESP8266)
|
||||
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
|
||||
espStatus = 1;
|
||||
} else if(vcc > 2.8 && vcc < 3.5) {
|
||||
espStatus = 1;
|
||||
} else if(vcc > 2.7 && vcc < 3.6) {
|
||||
espStatus = 2;
|
||||
} else {
|
||||
espStatus = 3;
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.1 && vcc < 3.5) {
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.0 && vcc < 3.6) {
|
||||
espStatus = 2;
|
||||
} else {
|
||||
espStatus = 3;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t hanStatus;
|
||||
if(data.getLastError() != 0) {
|
||||
hanStatus = 3;
|
||||
} else if(data.getLastUpdateMillis() == 0 && now < 30000) {
|
||||
hanStatus = 0;
|
||||
} else if(now - data.getLastUpdateMillis() < 15000) {
|
||||
hanStatus = 1;
|
||||
} else if(now - data.getLastUpdateMillis() < 30000) {
|
||||
hanStatus = 2;
|
||||
} else {
|
||||
hanStatus = 3;
|
||||
}
|
||||
|
||||
uint8_t wifiStatus;
|
||||
if(rssi > -75) {
|
||||
wifiStatus = 1;
|
||||
} else if(rssi > -95) {
|
||||
wifiStatus = 2;
|
||||
} else {
|
||||
wifiStatus = 3;
|
||||
}
|
||||
|
||||
uint8_t mqttStatus;
|
||||
if(mqttHandler == NULL) {
|
||||
mqttStatus = 0;
|
||||
} else if(mqttHandler->connected()) {
|
||||
mqttStatus = 1;
|
||||
} else if(mqttHandler->lastError() == 0) {
|
||||
mqttStatus = 2;
|
||||
} else {
|
||||
mqttStatus = 3;
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
|
||||
(uint32_t) time(nullptr),
|
||||
(uint32_t) (millis64()/1000),
|
||||
(uint32_t) (data.getLastUpdateMillis()/1000),
|
||||
data.isCounterEstimated() ? "true" : "false"
|
||||
);
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
|
||||
}
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
|
||||
}
|
||||
|
||||
if(data.getListType() > 1) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
|
||||
bool first = true;
|
||||
if(data.getL1Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if(data.getL2Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), String(data.getL2Current(), 2).c_str(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.isL2currentMissing() ? "null" : String(data.getL2Current(), 2).c_str());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if(data.getL3Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
|
||||
}
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS,
|
||||
espStatus, 0,
|
||||
hanStatus, data.getLastError(),
|
||||
wifiStatus, 0,
|
||||
mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
|
||||
);
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
|
||||
|
||||
uint16_t crc = crc16((uint8_t*) clearBuffer, pos);
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"crc\":\"%04X\"}"), crc);
|
||||
|
||||
if(rsa == nullptr) return;
|
||||
int ret = mbedtls_rsa_check_pubkey(rsa);
|
||||
if(ret != 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
|
||||
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("%s\n"), clearBuffer);
|
||||
return;
|
||||
}
|
||||
memset(encryptedBuffer, 0, rsa->len);
|
||||
|
||||
int maxlen = rsa->len - 11; // 11 should be the correct padding size for PKCS1
|
||||
udp.beginPacket(config.hostname,7443);
|
||||
for(int i = 0; i < pos; i += maxlen) {
|
||||
int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, maxlen, (unsigned char*) (clearBuffer+i), encryptedBuffer);
|
||||
if(ret == 0) {
|
||||
udp.write(encryptedBuffer, rsa->len);
|
||||
delay(1);
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
|
||||
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("%s\n"), clearBuffer);
|
||||
}
|
||||
}
|
||||
udp.endPacket();
|
||||
|
||||
lastUpdate = now;
|
||||
}
|
||||
|
||||
void CloudConnector::forceUpdate() {
|
||||
lastUpdate = 0;
|
||||
lastPriceConfig = 0;
|
||||
lastEac = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::setConnectionHandler(ConnectionHandler* ch) {
|
||||
this->ch = ch;
|
||||
}
|
||||
|
||||
void CloudConnector::setPriceConfig(PriceServiceConfig& priceConfig) {
|
||||
this->priceConfig = priceConfig;
|
||||
this->lastPriceConfig = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::setEnergyAccountingConfig(EnergyAccountingConfig& eac) {
|
||||
this->eac = eac;
|
||||
this->lastEac = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
@@ -54,3 +478,10 @@ void CloudConnector::debugPrint(byte *buffer, int start, int length) {
|
||||
}
|
||||
debugger->println(F(""));
|
||||
}
|
||||
|
||||
String CloudConnector::generateSeed() {
|
||||
uint8_t key[16];
|
||||
ESPRandom::uuid4(key);
|
||||
seed = ESPRandom::uuidToString(key);
|
||||
return seed;
|
||||
}
|
||||
|
||||
48
lib/ConnectionHandler/include/ConnectionHandler.h
Normal file
48
lib/ConnectionHandler/include/ConnectionHandler.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CONNECTIONHANDLER_H
|
||||
#define _CONNECTIONHANDLER_H
|
||||
|
||||
#include "AmsConfiguration.h"
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
esp_netif_t* get_esp_interface_netif(esp_interface_t interface);
|
||||
#endif
|
||||
|
||||
#define NETWORK_MODE_WIFI_CLIENT 1
|
||||
#define NETWORK_MODE_WIFI_AP 2
|
||||
#define NETWORK_MODE_ETH_CLIENT 3
|
||||
|
||||
class ConnectionHandler {
|
||||
public:
|
||||
virtual ~ConnectionHandler() {};
|
||||
virtual bool connect(NetworkConfig config, SystemConfig sys);
|
||||
virtual void disconnect(unsigned long reconnectDelay);
|
||||
virtual bool isConnected();
|
||||
virtual bool isConfigChanged();
|
||||
virtual void getCurrentConfig(NetworkConfig& networkConfig);
|
||||
virtual IPAddress getIP();
|
||||
virtual IPAddress getSubnetMask();
|
||||
virtual IPAddress getGateway();
|
||||
virtual IPAddress getDns(uint8_t idx);
|
||||
#if defined(ESP32)
|
||||
virtual IPv6Address getIPv6();
|
||||
virtual IPv6Address getDNSv6(uint8_t idx);
|
||||
virtual void eventHandler(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
#endif
|
||||
|
||||
uint8_t getMode() {
|
||||
return this->mode;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t mode;
|
||||
};
|
||||
|
||||
#endif
|
||||
59
lib/ConnectionHandler/include/EthernetConnectionHandler.h
Normal file
59
lib/ConnectionHandler/include/EthernetConnectionHandler.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ETHERNETCONNECTIONHANDLER_H
|
||||
#define _ETHERNETCONNECTIONHANDLER_H
|
||||
|
||||
#include "ConnectionHandler.h"
|
||||
#include <Arduino.h>
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
|
||||
#define CONNECTION_TIMEOUT 30000
|
||||
|
||||
class EthernetConnectionHandler : public ConnectionHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
EthernetConnectionHandler(RemoteDebug* debugger);
|
||||
#else
|
||||
EthernetConnectionHandler(Stream* debugger);
|
||||
#endif
|
||||
|
||||
bool connect(NetworkConfig config, SystemConfig sys);
|
||||
void disconnect(unsigned long reconnectDelay);
|
||||
bool isConnected();
|
||||
bool isConfigChanged();
|
||||
void getCurrentConfig(NetworkConfig& networkConfig);
|
||||
IPAddress getIP();
|
||||
IPAddress getSubnetMask();
|
||||
IPAddress getGateway();
|
||||
IPAddress getDns(uint8_t idx);
|
||||
#if defined(ESP32)
|
||||
IPv6Address getIPv6();
|
||||
IPv6Address getDNSv6(uint8_t idx);
|
||||
void eventHandler(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
NetworkConfig config;
|
||||
|
||||
bool connected = false;
|
||||
bool configChanged = false;
|
||||
unsigned long timeout = CONNECTION_TIMEOUT;
|
||||
unsigned long lastRetry = 0;
|
||||
|
||||
int8_t ethPowerPin = -1;
|
||||
uint8_t ethEnablePin = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _WIFIACCESSPOINTCONNECTIONHANDLER_H
|
||||
#define _WIFIACCESSPOINTCONNECTIONHANDLER_H
|
||||
|
||||
#include "ConnectionHandler.h"
|
||||
#include <Arduino.h>
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include <DNSServer.h>
|
||||
|
||||
class WiFiAccessPointConnectionHandler : public ConnectionHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
WiFiAccessPointConnectionHandler(RemoteDebug* debugger);
|
||||
#else
|
||||
WiFiAccessPointConnectionHandler(Stream* debugger);
|
||||
#endif
|
||||
|
||||
bool connect(NetworkConfig config, SystemConfig sys);
|
||||
void disconnect(unsigned long reconnectDelay);
|
||||
bool isConnected();
|
||||
bool isConfigChanged();
|
||||
void getCurrentConfig(NetworkConfig& networkConfig);
|
||||
IPAddress getIP();
|
||||
IPAddress getSubnetMask();
|
||||
IPAddress getGateway();
|
||||
IPAddress getDns(uint8_t idx);
|
||||
#if defined(ESP32)
|
||||
IPv6Address getIPv6();
|
||||
IPv6Address getDNSv6(uint8_t idx);
|
||||
void eventHandler(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
NetworkConfig config;
|
||||
|
||||
DNSServer dnsServer;
|
||||
|
||||
bool connected = false;
|
||||
bool configChanged = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
66
lib/ConnectionHandler/include/WiFiClientConnectionHandler.h
Normal file
66
lib/ConnectionHandler/include/WiFiClientConnectionHandler.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _WIFICLIENTCONNECTIONHANDLER_H
|
||||
#define _WIFICLIENTCONNECTIONHANDLER_H
|
||||
|
||||
#include "ConnectionHandler.h"
|
||||
#include <Arduino.h>
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
|
||||
#define CONNECTION_TIMEOUT 30000
|
||||
#define RECONNECT_TIMEOUT 5000
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_err_t set_esp_interface_ip(esp_interface_t interface, IPAddress local_ip=INADDR_NONE, IPAddress gateway=INADDR_NONE, IPAddress subnet=INADDR_NONE, IPAddress dhcp_lease_start=INADDR_NONE);
|
||||
#endif
|
||||
|
||||
class WiFiClientConnectionHandler : public ConnectionHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
WiFiClientConnectionHandler(RemoteDebug* debugger);
|
||||
#else
|
||||
WiFiClientConnectionHandler(Stream* debugger);
|
||||
#endif
|
||||
|
||||
bool connect(NetworkConfig config, SystemConfig sys);
|
||||
void disconnect(unsigned long reconnectDelay);
|
||||
bool isConnected();
|
||||
bool isConfigChanged();
|
||||
void getCurrentConfig(NetworkConfig& networkConfig);
|
||||
IPAddress getIP();
|
||||
IPAddress getSubnetMask();
|
||||
IPAddress getGateway();
|
||||
IPAddress getDns(uint8_t idx);
|
||||
#if defined(ESP32)
|
||||
IPv6Address getIPv6();
|
||||
IPv6Address getDNSv6(uint8_t idx);
|
||||
void eventHandler(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
NetworkConfig config;
|
||||
bool busPowered = false;
|
||||
bool firstConnect = true;
|
||||
bool configChanged = false;
|
||||
|
||||
unsigned long timeout = CONNECTION_TIMEOUT;
|
||||
unsigned long lastRetry = 0;
|
||||
|
||||
wl_status_t begin(const char* ssid, const char* psk);
|
||||
#if defined(ESP32)
|
||||
void wifi_sta_config(wifi_config_t * wifi_config, const char * ssid=NULL, const char * password=NULL, const uint8_t * bssid=NULL, uint8_t channel=0, wifi_auth_mode_t min_security=WIFI_AUTH_WPA2_PSK, wifi_scan_method_t scan_method=WIFI_ALL_CHANNEL_SCAN, wifi_sort_method_t sort_method=WIFI_CONNECT_AP_BY_SIGNAL, uint16_t listen_interval=0, bool pmf_required=false);
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
250
lib/ConnectionHandler/src/EthernetConnectionHandler.cpp
Normal file
250
lib/ConnectionHandler/src/EthernetConnectionHandler.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EthernetConnectionHandler.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <ETH.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <lwip/dns.h>
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
EthernetConnectionHandler::EthernetConnectionHandler(RemoteDebug* debugger) {
|
||||
#else
|
||||
EthernetConnectionHandler::EthernetConnectionHandler(Stream* debugger) {
|
||||
#endif
|
||||
this->debugger = debugger;
|
||||
this->mode = NETWORK_MODE_ETH_CLIENT;
|
||||
}
|
||||
|
||||
bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys) {
|
||||
if(lastRetry > 0 && (millis() - lastRetry) < timeout) {
|
||||
delay(50);
|
||||
return false;
|
||||
}
|
||||
lastRetry = millis();
|
||||
|
||||
#if defined(ESP32)
|
||||
if (!connected) {
|
||||
eth_phy_type_t ethType = ETH_PHY_LAN8720;
|
||||
eth_clock_mode_t ethClkMode = ETH_CLOCK_GPIO0_IN;
|
||||
uint8_t ethAddr = 0;
|
||||
uint8_t ethMdc = 0;
|
||||
uint8_t ethMdio = 0;
|
||||
|
||||
if(sys.boardType == 241) { // LilyGO T-ETH-POE
|
||||
ethType = ETH_PHY_LAN8720;
|
||||
ethEnablePin = -1;
|
||||
ethAddr = 0;
|
||||
ethClkMode = ETH_CLOCK_GPIO17_OUT;
|
||||
ethPowerPin = 5;
|
||||
ethMdc = 23;
|
||||
ethMdio = 18;
|
||||
} else if(sys.boardType == 242) { // M5 PoESP32
|
||||
ethType = ETH_PHY_IP101;
|
||||
ethEnablePin = -1;
|
||||
ethAddr = 1;
|
||||
ethClkMode = ETH_CLOCK_GPIO0_IN;
|
||||
ethPowerPin = 5;
|
||||
ethMdc = 23;
|
||||
ethMdio = 18;
|
||||
} else if(sys.boardType == 243) { // WT32-ETH01
|
||||
ethType = ETH_PHY_LAN8720;
|
||||
ethEnablePin = -1;
|
||||
ethAddr = 1;
|
||||
ethClkMode = ETH_CLOCK_GPIO17_OUT;
|
||||
ethPowerPin = 16;
|
||||
ethMdc = 23;
|
||||
ethMdio = 18;
|
||||
} else if(sys.boardType == 244) {
|
||||
return false; // TODO
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Board type %d incompatible with ETH\n"), sys.boardType);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ethEnablePin > 0) {
|
||||
pinMode(ethEnablePin, OUTPUT);
|
||||
digitalWrite(ethEnablePin, 1);
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Connecting to Ethernet\n"));
|
||||
|
||||
if(ETH.begin(ethAddr, ethPowerPin, ethMdc, ethMdio, ethType, ethClkMode)) {
|
||||
#if defined(ESP32)
|
||||
if(strlen(config.hostname) > 0) {
|
||||
ETH.setHostname(config.hostname);
|
||||
}
|
||||
#endif
|
||||
|
||||
if(strlen(config.ip) > 0) {
|
||||
IPAddress ip, gw, sn(255,255,255,0), dns1, dns2;
|
||||
ip.fromString(config.ip);
|
||||
gw.fromString(config.gateway);
|
||||
sn.fromString(config.subnet);
|
||||
if(strlen(config.dns1) > 0) {
|
||||
dns1.fromString(config.dns1);
|
||||
} else if(strlen(config.gateway) > 0) {
|
||||
dns1.fromString(config.gateway); // If no DNS, set gateway by default
|
||||
}
|
||||
if(strlen(config.dns2) > 0) {
|
||||
dns2.fromString(config.dns2);
|
||||
} else if(dns1.toString().isEmpty()) {
|
||||
dns2.fromString(F("208.67.220.220")); // Add OpenDNS as second by default if nothing is configured
|
||||
}
|
||||
if(!ETH.config(ip, gw, sn, dns1, dns2)) {
|
||||
debugger->printf_P(PSTR("Static IP configuration is invalid, not using\n"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start Ethernet\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void EthernetConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
}
|
||||
|
||||
bool EthernetConnectionHandler::isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
connected = true;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Successfully connected to Ethernet!\n"));
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("IP: %s\n"), getIP().toString().c_str());
|
||||
debugger->printf_P(PSTR("GW: %s\n"), getGateway().toString().c_str());
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
IPAddress dns4 = getDns(i);
|
||||
if(!dns4.isAny()) debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP6: {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
IPv6Address ipv6 = getIPv6();
|
||||
if(ipv6 == IPv6Address()) {
|
||||
// No IP
|
||||
} else {
|
||||
debugger->printf_P(PSTR("IPv6: %s\n"), ipv6.toString().c_str());
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
IPv6Address dns6 = getDNSv6(i);
|
||||
if(dns6 == IPv6Address()) {
|
||||
// No IP
|
||||
} else {
|
||||
debugger->printf_P(PSTR("DNSv6: %s\n"), dns6.toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
connected = false;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Ethernet was disconnected!\n"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool EthernetConnectionHandler::isConfigChanged() {
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
void EthernetConnectionHandler::getCurrentConfig(NetworkConfig& networkConfig) {
|
||||
networkConfig = this->config;
|
||||
}
|
||||
|
||||
IPAddress EthernetConnectionHandler::getIP() {
|
||||
#if defined(ESP32)
|
||||
return ETH.localIP();
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
IPAddress EthernetConnectionHandler::getSubnetMask() {
|
||||
#if defined(ESP32)
|
||||
return ETH.subnetMask();
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
IPAddress EthernetConnectionHandler::getGateway() {
|
||||
#if defined(ESP32)
|
||||
return ETH.gatewayIP();
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
IPAddress EthernetConnectionHandler::getDns(uint8_t idx) {
|
||||
#if defined(ESP32)
|
||||
return ETH.dnsIP(idx);
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
IPv6Address EthernetConnectionHandler::getIPv6() {
|
||||
esp_ip6_addr_t addr;
|
||||
if(esp_netif_get_ip6_global(get_esp_interface_netif(ESP_IF_ETH), &addr) == ESP_OK) {
|
||||
return IPv6Address(addr.addr);
|
||||
}
|
||||
return IPv6Address();
|
||||
}
|
||||
|
||||
IPv6Address EthernetConnectionHandler::getDNSv6(uint8_t idx) {
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
const ip_addr_t * dns = dns_getserver(i);
|
||||
if(dns->type == IPADDR_TYPE_V6) {
|
||||
if(idx-- == 0) return IPv6Address(dns->u_addr.ip6.addr);
|
||||
}
|
||||
}
|
||||
return IPv6Address();
|
||||
}
|
||||
#endif
|
||||
121
lib/ConnectionHandler/src/WiFiAccessPointConnectionHandler.cpp
Normal file
121
lib/ConnectionHandler/src/WiFiAccessPointConnectionHandler.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "WiFiAccessPointConnectionHandler.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
WiFiAccessPointConnectionHandler::WiFiAccessPointConnectionHandler(RemoteDebug* debugger) {
|
||||
#else
|
||||
WiFiAccessPointConnectionHandler::WiFiAccessPointConnectionHandler(Stream* debugger) {
|
||||
#endif
|
||||
this->debugger = debugger;
|
||||
this->mode = NETWORK_MODE_WIFI_AP;
|
||||
}
|
||||
|
||||
bool WiFiAccessPointConnectionHandler::connect(NetworkConfig config, SystemConfig sys) {
|
||||
//wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, 0); // Disable default gw
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
WiFi.softAP(config.ssid, config.psk);
|
||||
|
||||
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer.start(53, PSTR("*"), WiFi.softAPIP());
|
||||
connected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WiFiAccessPointConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.enableAP(false);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
yield();
|
||||
}
|
||||
|
||||
bool WiFiAccessPointConnectionHandler::isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
void WiFiAccessPointConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
uint8_t mac[6];
|
||||
IPAddress stationIP;
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_WIFI_AP_START:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("WiFi access point started with SSID %s\n"), config.ssid);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STOP:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("WiFi access point stopped!\n"));
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
|
||||
memcpy(mac, info.wifi_ap_staconnected.mac, 6);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Client connected to AP, client MAC: %02x:%02x:%02x:%02x:%02x:%02x\n"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
|
||||
memcpy(mac, info.wifi_ap_staconnected.mac, 6);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Client disconnected from AP, client MAC: %02x:%02x:%02x:%02x:%02x:%02x\n"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
|
||||
stationIP = info.wifi_ap_staipassigned.ip.addr;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Client was assigned IP %s\n"), stationIP.toString().c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool WiFiAccessPointConnectionHandler::isConfigChanged() {
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
void WiFiAccessPointConnectionHandler::getCurrentConfig(NetworkConfig& networkConfig) {
|
||||
networkConfig = this->config;
|
||||
}
|
||||
|
||||
IPAddress WiFiAccessPointConnectionHandler::getIP() {
|
||||
return WiFi.softAPIP();
|
||||
}
|
||||
|
||||
IPAddress WiFiAccessPointConnectionHandler::getSubnetMask() {
|
||||
#if defined(ESP32)
|
||||
return WiFi.softAPSubnetMask();
|
||||
#else
|
||||
return IPAddress(255,255,255,0);
|
||||
#endif
|
||||
}
|
||||
|
||||
IPAddress WiFiAccessPointConnectionHandler::getGateway() {
|
||||
return WiFi.softAPIP();
|
||||
}
|
||||
|
||||
IPAddress WiFiAccessPointConnectionHandler::getDns(uint8_t idx) {
|
||||
return WiFi.softAPIP();
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
IPv6Address WiFiAccessPointConnectionHandler::getIPv6() {
|
||||
return IPv6Address();
|
||||
}
|
||||
|
||||
IPv6Address WiFiAccessPointConnectionHandler::getDNSv6(uint8_t idx) {
|
||||
return IPv6Address();
|
||||
}
|
||||
#endif
|
||||
420
lib/ConnectionHandler/src/WiFiClientConnectionHandler.cpp
Normal file
420
lib/ConnectionHandler/src/WiFiClientConnectionHandler.cpp
Normal file
@@ -0,0 +1,420 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "WiFiClientConnectionHandler.h"
|
||||
#if defined(ESP32)
|
||||
#include <esp_wifi.h>
|
||||
#include <lwip/dns.h>
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
WiFiClientConnectionHandler::WiFiClientConnectionHandler(RemoteDebug* debugger) {
|
||||
#else
|
||||
WiFiClientConnectionHandler::WiFiClientConnectionHandler(Stream* debugger) {
|
||||
#endif
|
||||
this->debugger = debugger;
|
||||
this->mode = NETWORK_MODE_WIFI_CLIENT;
|
||||
}
|
||||
|
||||
bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys) {
|
||||
if(lastRetry > 0 && (millis() - lastRetry) < timeout) {
|
||||
delay(50);
|
||||
return false;
|
||||
}
|
||||
lastRetry = millis();
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
if(config.mode != this->mode || strlen(config.ssid) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(WiFi.getMode() != WIFI_OFF) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Not connected to WiFi, closing resources\n"));
|
||||
|
||||
disconnect(RECONNECT_TIMEOUT);
|
||||
return false;
|
||||
}
|
||||
timeout = CONNECTION_TIMEOUT;
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Connecting to WiFi network: %s\n"), config.ssid);
|
||||
switch(sys.boardType) {
|
||||
case 2: // spenceme
|
||||
case 3: // Pow-K UART0
|
||||
case 4: // Pow-U UART0
|
||||
case 5: // Pow-K+
|
||||
case 6: // Pow-P1
|
||||
case 7: // Pow-U+
|
||||
case 8: // dbeinder: HAN mosquito
|
||||
busPowered = true;
|
||||
break;
|
||||
default:
|
||||
busPowered = false;
|
||||
}
|
||||
firstConnect = sys.dataCollectionConsent == 0;
|
||||
|
||||
#if defined(ESP32)
|
||||
if(strlen(config.hostname) > 0) {
|
||||
WiFi.setHostname(config.hostname);
|
||||
}
|
||||
#endif
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
if(strlen(config.ip) > 0) {
|
||||
IPAddress ip, gw, sn(255,255,255,0), dns1, dns2;
|
||||
ip.fromString(config.ip);
|
||||
gw.fromString(config.gateway);
|
||||
sn.fromString(config.subnet);
|
||||
if(strlen(config.dns1) > 0) {
|
||||
dns1.fromString(config.dns1);
|
||||
} else if(strlen(config.gateway) > 0) {
|
||||
dns1.fromString(config.gateway); // If no DNS, set gateway by default
|
||||
}
|
||||
if(strlen(config.dns2) > 0) {
|
||||
dns2.fromString(config.dns2);
|
||||
} else if(dns1.toString().isEmpty()) {
|
||||
dns2.fromString(F("208.67.220.220")); // Add OpenDNS as second by default if nothing is configured
|
||||
}
|
||||
if(!WiFi.config(ip, gw, sn, dns1, dns2)) {
|
||||
debugger->printf_P(PSTR("Static IP configuration is invalid, not using\n"));
|
||||
}
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
if(strlen(config.hostname) > 0) {
|
||||
WiFi.hostname(config.hostname);
|
||||
}
|
||||
//wifi_set_phy_mode(PHY_MODE_11N);
|
||||
if(!config.use11b) {
|
||||
wifi_set_user_sup_rate(RATE_11G6M, RATE_11G54M);
|
||||
wifi_set_user_rate_limit(RC_LIMIT_11G, 0x00, RATE_11G_G54M, RATE_11G_G6M);
|
||||
wifi_set_user_rate_limit(RC_LIMIT_11N, 0x00, RATE_11N_MCS7S, RATE_11N_MCS0);
|
||||
wifi_set_user_limit_rate_mask(LIMIT_RATE_MASK_ALL);
|
||||
}
|
||||
#endif
|
||||
WiFi.setAutoReconnect(true);
|
||||
this->config = config;
|
||||
#if defined(ESP32)
|
||||
if(begin(config.ssid, config.psk)) {
|
||||
#else
|
||||
if(WiFi.begin(config.ssid, config.psk)) {
|
||||
#endif
|
||||
if(config.sleep <= 2) {
|
||||
switch(config.sleep) {
|
||||
case 0:
|
||||
WiFi.setSleep(WIFI_PS_NONE);
|
||||
break;
|
||||
case 1:
|
||||
WiFi.setSleep(WIFI_PS_MIN_MODEM);
|
||||
break;
|
||||
case 2:
|
||||
WiFi.setSleep(WIFI_PS_MAX_MODEM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
yield();
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start WiFi\n"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
wl_status_t WiFiClientConnectionHandler::begin(const char* ssid, const char* passphrase) {
|
||||
if(!WiFi.enableSTA(true)) {
|
||||
log_e("STA enable failed!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
if(!ssid || *ssid == 0x00 || strlen(ssid) > 32) {
|
||||
log_e("SSID too long or missing!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
if(passphrase && strlen(passphrase) > 64) {
|
||||
log_e("passphrase too long!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
wifi_config_t conf;
|
||||
memset(&conf, 0, sizeof(wifi_config_t));
|
||||
|
||||
wifi_sta_config(&conf, ssid, passphrase, NULL, 0, WIFI_AUTH_WPA2_PSK, WIFI_ALL_CHANNEL_SCAN, WIFI_CONNECT_AP_BY_SIGNAL);
|
||||
|
||||
wifi_config_t current_conf;
|
||||
if(esp_wifi_get_config((wifi_interface_t)ESP_IF_WIFI_STA, ¤t_conf) != ESP_OK){
|
||||
log_e("get current config failed!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
if(memcmp(¤t_conf, &conf, sizeof(wifi_config_t)) == 0) {
|
||||
if(esp_wifi_disconnect()){
|
||||
log_e("disconnect failed!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
if(esp_wifi_set_config((wifi_interface_t)ESP_IF_WIFI_STA, &conf) != ESP_OK){
|
||||
log_e("set config failed!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
} else if(WiFi.status() == WL_CONNECTED){
|
||||
return WL_CONNECTED;
|
||||
} else {
|
||||
if(esp_wifi_set_config((wifi_interface_t)ESP_IF_WIFI_STA, &conf) != ESP_OK){
|
||||
log_e("set config failed!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
if(strlen(config.ip) == 0){
|
||||
if(set_esp_interface_ip(ESP_IF_WIFI_STA) != ESP_OK) {
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
if(esp_wifi_connect() != ESP_OK) {
|
||||
log_e("connect failed!");
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
return WiFi.status();
|
||||
}
|
||||
|
||||
void WiFiClientConnectionHandler::wifi_sta_config(wifi_config_t * wifi_config, const char * ssid, const char * password, const uint8_t * bssid, uint8_t channel, wifi_auth_mode_t min_security, wifi_scan_method_t scan_method, wifi_sort_method_t sort_method, uint16_t listen_interval, bool pmf_required){
|
||||
wifi_config->sta.channel = channel;
|
||||
wifi_config->sta.listen_interval = listen_interval;
|
||||
wifi_config->sta.scan_method = scan_method;//WIFI_ALL_CHANNEL_SCAN or WIFI_FAST_SCAN
|
||||
wifi_config->sta.sort_method = sort_method;//WIFI_CONNECT_AP_BY_SIGNAL or WIFI_CONNECT_AP_BY_SECURITY
|
||||
wifi_config->sta.threshold.rssi = -127;
|
||||
wifi_config->sta.pmf_cfg.capable = true;
|
||||
wifi_config->sta.pmf_cfg.required = pmf_required;
|
||||
wifi_config->sta.bssid_set = 0;
|
||||
memset(wifi_config->sta.bssid, 0, 6);
|
||||
wifi_config->sta.threshold.authmode = WIFI_AUTH_OPEN;
|
||||
wifi_config->sta.ssid[0] = 0;
|
||||
wifi_config->sta.password[0] = 0;
|
||||
if(ssid != NULL && ssid[0] != 0){
|
||||
strncpy((char*)wifi_config->sta.ssid, ssid, 32);
|
||||
if(password != NULL && password[0] != 0){
|
||||
wifi_config->sta.threshold.authmode = min_security;
|
||||
strncpy((char*)wifi_config->sta.password, password, 64);
|
||||
}
|
||||
if(bssid != NULL){
|
||||
wifi_config->sta.bssid_set = 1;
|
||||
memcpy(wifi_config->sta.bssid, bssid, 6);
|
||||
}
|
||||
}
|
||||
wifi_config->sta.rm_enabled = true;
|
||||
wifi_config->sta.btm_enabled = true;
|
||||
wifi_config->sta.mbo_enabled = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void WiFiClientConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
#if defined(ESP8266)
|
||||
WiFiClient::stopAll();
|
||||
#endif
|
||||
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.enableAP(false);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
yield();
|
||||
timeout = reconnectDelay;
|
||||
}
|
||||
|
||||
bool WiFiClientConnectionHandler::isConnected() {
|
||||
return WiFi.status() == WL_CONNECTED;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_WIFI_READY:
|
||||
if (!config.use11b) {
|
||||
esp_wifi_config_11b_rate(WIFI_IF_AP, true);
|
||||
esp_wifi_config_11b_rate(WIFI_IF_STA, true);
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Successfully connected to WiFi!\n"));
|
||||
if(config.ipv6 && !WiFi.enableIpV6()) {
|
||||
debugger->printf_P(PSTR("Unable to enable IPv6\n"));
|
||||
}
|
||||
#if defined(ESP32)
|
||||
if(firstConnect && config.use11b) {
|
||||
// If first boot and phyMode is better than 11b, disable 11b for BUS powered devices
|
||||
if(busPowered) {
|
||||
wifi_phy_mode_t phyMode;
|
||||
if(esp_wifi_sta_get_negotiated_phymode(&phyMode) == ESP_OK) {
|
||||
if(phyMode > WIFI_PHY_MODE_11B) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("WiFi supports better rates than 802.11b, disabling\n"));
|
||||
config.use11b = false;
|
||||
configChanged = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(config.power >= 195)
|
||||
WiFi.setTxPower(WIFI_POWER_19_5dBm);
|
||||
else if(config.power >= 190)
|
||||
WiFi.setTxPower(WIFI_POWER_19dBm);
|
||||
else if(config.power >= 185)
|
||||
WiFi.setTxPower(WIFI_POWER_18_5dBm);
|
||||
else if(config.power >= 170)
|
||||
WiFi.setTxPower(WIFI_POWER_17dBm);
|
||||
else if(config.power >= 150)
|
||||
WiFi.setTxPower(WIFI_POWER_15dBm);
|
||||
else if(config.power >= 130)
|
||||
WiFi.setTxPower(WIFI_POWER_13dBm);
|
||||
else if(config.power >= 110)
|
||||
WiFi.setTxPower(WIFI_POWER_11dBm);
|
||||
else if(config.power >= 85)
|
||||
WiFi.setTxPower(WIFI_POWER_8_5dBm);
|
||||
else if(config.power >= 70)
|
||||
WiFi.setTxPower(WIFI_POWER_7dBm);
|
||||
else if(config.power >= 50)
|
||||
WiFi.setTxPower(WIFI_POWER_5dBm);
|
||||
else if(config.power >= 20)
|
||||
WiFi.setTxPower(WIFI_POWER_2dBm);
|
||||
else
|
||||
WiFi.setTxPower(WIFI_POWER_MINUS_1dBm);
|
||||
#elif defined(ESP8266)
|
||||
WiFi.setOutputPower(config.power / 10.0);
|
||||
#endif
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP: {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("IP: %s\n"), getIP().toString().c_str());
|
||||
debugger->printf_P(PSTR("GW: %s\n"), getGateway().toString().c_str());
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
IPAddress dns4 = getDns(i);
|
||||
if(!dns4.isAny()) debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6: {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
IPv6Address ipv6 = getIPv6();
|
||||
if(ipv6 == IPv6Address()) {
|
||||
// No IP
|
||||
} else {
|
||||
debugger->printf_P(PSTR("IPv6: %s\n"), ipv6.toString().c_str());
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
IPv6Address dns6 = getDNSv6(i);
|
||||
if(dns6 == IPv6Address()) {
|
||||
// No IP
|
||||
} else {
|
||||
debugger->printf_P(PSTR("DNSv6: %s\n"), dns6.toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: {
|
||||
wifi_err_reason_t reason = (wifi_err_reason_t) info.wifi_sta_disconnected.reason;
|
||||
const char* descr = WiFi.disconnectReasonName(reason);
|
||||
switch(reason) {
|
||||
case WIFI_REASON_ASSOC_LEAVE:
|
||||
break;
|
||||
default:
|
||||
if(strlen(descr) > 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("WiFi disconnected, reason %s\n"), descr);
|
||||
}
|
||||
disconnect(RECONNECT_TIMEOUT);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool WiFiClientConnectionHandler::isConfigChanged() {
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
void WiFiClientConnectionHandler::getCurrentConfig(NetworkConfig& networkConfig) {
|
||||
networkConfig = this->config;
|
||||
}
|
||||
|
||||
IPAddress WiFiClientConnectionHandler::getIP() {
|
||||
return WiFi.localIP();
|
||||
}
|
||||
|
||||
IPAddress WiFiClientConnectionHandler::getSubnetMask() {
|
||||
return WiFi.subnetMask();
|
||||
}
|
||||
|
||||
IPAddress WiFiClientConnectionHandler::getGateway() {
|
||||
return WiFi.gatewayIP();
|
||||
}
|
||||
|
||||
IPAddress WiFiClientConnectionHandler::getDns(uint8_t idx) {
|
||||
#if defined(ESP32)
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
const ip_addr_t * dns = dns_getserver(i);
|
||||
if(dns->type == IPADDR_TYPE_V4) {
|
||||
if(idx-- == 0) return IPAddress(dns->u_addr.ip4.addr);
|
||||
}
|
||||
}
|
||||
#else
|
||||
return WiFi.dnsIP(idx);
|
||||
#endif
|
||||
return IPAddress();
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
IPv6Address WiFiClientConnectionHandler::getIPv6() {
|
||||
esp_ip6_addr_t addr;
|
||||
if(esp_netif_get_ip6_global(get_esp_interface_netif(ESP_IF_WIFI_STA), &addr) == ESP_OK) {
|
||||
return IPv6Address(addr.addr);
|
||||
}
|
||||
return IPv6Address();
|
||||
}
|
||||
|
||||
IPv6Address WiFiClientConnectionHandler::getDNSv6(uint8_t idx) {
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
const ip_addr_t * dns = dns_getserver(i);
|
||||
if(dns->type == IPADDR_TYPE_V6) {
|
||||
if(idx-- == 0) return IPv6Address(dns->u_addr.ip6.addr);
|
||||
}
|
||||
}
|
||||
return IPv6Address();
|
||||
}
|
||||
#endif
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DOMOTICZMQTTHANDLER_H
|
||||
#define _DOMOTICZMQTTHANDLER_H
|
||||
|
||||
@@ -6,13 +12,19 @@
|
||||
|
||||
class DomoticzMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->config = config;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
#else
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->config = config;
|
||||
};
|
||||
#endif
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
@@ -1,64 +1,88 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DomoticzMqttHandler.h"
|
||||
#include "json/domoticz_json.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
bool DomoticzMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
bool ret = false;
|
||||
|
||||
AmsData data;
|
||||
if(mqttConfig.stateUpdate) {
|
||||
uint64_t now = millis64();
|
||||
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
|
||||
data.apply(*previousState);
|
||||
data.apply(*update);
|
||||
lastStateUpdate = now;
|
||||
} else {
|
||||
data = *update;
|
||||
}
|
||||
|
||||
if (config.elidx > 0) {
|
||||
if(data->getActiveImportCounter() > 1.0) {
|
||||
energy = data->getActiveImportCounter();
|
||||
if(data.getActiveImportCounter() > 1.0 && !data.isCounterEstimated()) {
|
||||
energy = data.getActiveImportCounter();
|
||||
}
|
||||
if(energy > 0.0) {
|
||||
char val[16];
|
||||
snprintf_P(val, 16, PSTR("%.1f;%.1f"), (data->getActiveImportPower()/1.0), energy*1000.0);
|
||||
snprintf_P(val, 16, PSTR("%.1f;%.1f"), (data.getActiveImportPower()/1.0), energy*1000.0);
|
||||
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
|
||||
config.elidx,
|
||||
val
|
||||
);
|
||||
ret = mqtt.publish(F("domoticz/in"), json);
|
||||
mqtt.loop();
|
||||
}
|
||||
}
|
||||
|
||||
if(data->getListType() == 1)
|
||||
if(data.getListType() == 1)
|
||||
return ret;
|
||||
|
||||
if (config.vl1idx > 0){
|
||||
char val[16];
|
||||
snprintf_P(val, 16, PSTR("%.2f"), data->getL1Voltage());
|
||||
snprintf_P(val, 16, PSTR("%.2f"), data.getL1Voltage());
|
||||
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
|
||||
config.vl1idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if (config.vl2idx > 0){
|
||||
char val[16];
|
||||
snprintf_P(val, 16, PSTR("%.2f"), data->getL2Voltage());
|
||||
snprintf_P(val, 16, PSTR("%.2f"), data.getL2Voltage());
|
||||
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
|
||||
config.vl2idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if (config.vl3idx > 0){
|
||||
char val[16];
|
||||
snprintf(val, 16, "%.2f", data->getL3Voltage());
|
||||
snprintf(val, 16, "%.2f", data.getL3Voltage());
|
||||
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
|
||||
config.vl3idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if (config.cl1idx > 0){
|
||||
char val[16];
|
||||
snprintf(val, 16, "%.1f;%.1f;%.1f", data->getL1Current(), data->getL2Current(), data->getL3Current());
|
||||
snprintf(val, 16, "%.1f;%.1f;%.1f", data.getL1Current(), data.getL2Current(), data.getL3Current());
|
||||
snprintf_P(json, BufferSize, DOMOTICZ_JSON,
|
||||
config.cl1idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
mqtt.loop();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -67,11 +91,11 @@ bool DomoticzMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools*
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
bool DomoticzMqttHandler::publishPrices(PriceService* ps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ENERGYACCOUNTING_H
|
||||
#define _ENERGYACCOUNTING_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include "EntsoeApi.h"
|
||||
#include "PriceService.h"
|
||||
|
||||
struct EnergyAccountingPeak {
|
||||
uint8_t day;
|
||||
@@ -74,9 +80,13 @@ struct EnergyAccountingRealtimeData {
|
||||
|
||||
class EnergyAccounting {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
EnergyAccounting(RemoteDebug*, EnergyAccountingRealtimeData*);
|
||||
#else
|
||||
EnergyAccounting(Stream*, EnergyAccountingRealtimeData*);
|
||||
#endif
|
||||
void setup(AmsDataStorage *ds, EnergyAccountingConfig *config);
|
||||
void setEapi(EntsoeApi *eapi);
|
||||
void setPriceService(PriceService *ps);
|
||||
void setTimezone(Timezone*);
|
||||
EnergyAccountingConfig* getConfig();
|
||||
bool update(AmsData* amsData);
|
||||
@@ -113,19 +123,22 @@ public:
|
||||
EnergyAccountingData getData();
|
||||
void setData(EnergyAccountingData&);
|
||||
|
||||
void setFixedPrice(float price, String currency);
|
||||
float getPriceForHour(uint8_t h);
|
||||
void setCurrency(String currency);
|
||||
float getPriceForHour(uint8_t d, uint8_t h);
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
bool init = false, initPrice = false;
|
||||
AmsDataStorage *ds = NULL;
|
||||
EntsoeApi *eapi = NULL;
|
||||
PriceService *ps = NULL;
|
||||
EnergyAccountingConfig *config = NULL;
|
||||
Timezone *tz = NULL;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
EnergyAccountingRealtimeData* realtimeData = NULL;
|
||||
float fixedPrice = 0;
|
||||
String currency = "";
|
||||
|
||||
void calcDayCost();
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EnergyAccounting.h"
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger, EnergyAccountingRealtimeData* rtd) {
|
||||
#else
|
||||
EnergyAccounting::EnergyAccounting(Stream* Stream, EnergyAccountingRealtimeData* rtd) {
|
||||
#endif
|
||||
data.version = 1;
|
||||
this->debugger = debugger;
|
||||
if(rtd->magic != 0x6A) {
|
||||
@@ -28,8 +38,8 @@ void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config)
|
||||
this->config = config;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setEapi(EntsoeApi *eapi) {
|
||||
this->eapi = eapi;
|
||||
void EnergyAccounting::setPriceService(PriceService *ps) {
|
||||
this->ps = ps;
|
||||
}
|
||||
|
||||
EnergyAccountingConfig* EnergyAccounting::getConfig() {
|
||||
@@ -49,7 +59,6 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,9 +71,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
this->realtimeData->lastExportUpdateMillis = 0;
|
||||
this->realtimeData->currentHour = local.Hour;
|
||||
this->realtimeData->currentDay = local.Day;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Initializing data at %lu\n"), (int32_t) now);
|
||||
if(!load()) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Unable to load existing data\n"));
|
||||
data = { 6, local.Month,
|
||||
0, 0, 0, // Cost
|
||||
0, 0, 0, // Income
|
||||
@@ -75,28 +82,19 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
} else if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Peak hour from day %d: %d\n"), data.peaks[i].day, data.peaks[i].value*10);
|
||||
}
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n"), data.costYesterday / 100.0, data.costThisMonth / 100.0, data.costLastMonth / 100.0);
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded income yesterday: %.2f, this month: %d, last month: %d\n"), data.incomeYesterday / 100.0, data.incomeThisMonth / 100.0, data.incomeLastMonth / 100.0);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
float price = getPriceForHour(0);
|
||||
if(!initPrice && price != ENTSOE_NO_VALUE) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Initializing prices at %lu\n"), (int32_t) now);
|
||||
float importPrice = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
|
||||
if(!initPrice && importPrice != PRICE_NO_VALUE) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
if(local.Hour != this->realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New local hour %d\n"), local.Hour);
|
||||
|
||||
tmElements_t oneHrAgo, oneHrAgoLocal;
|
||||
breakTime(now-3600, oneHrAgo);
|
||||
uint16_t val = ds->getHourImport(oneHrAgo.Hour) / 10;
|
||||
uint16_t val = round(ds->getHourImport(oneHrAgo.Hour) / 10.0);
|
||||
|
||||
breakTime(tz->toLocal(now-3600), oneHrAgoLocal);
|
||||
ret |= updateMax(val, oneHrAgoLocal.Day);
|
||||
@@ -113,7 +111,6 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
|
||||
uint8_t prevDay = this->realtimeData->currentDay;
|
||||
if(local.Day != this->realtimeData->currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New day %d\n"), local.Day);
|
||||
data.costYesterday = this->realtimeData->costDay * 100;
|
||||
data.costThisMonth += this->realtimeData->costDay * 100;
|
||||
this->realtimeData->costDay = 0;
|
||||
@@ -127,7 +124,6 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
}
|
||||
|
||||
if(local.Month != data.month) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New month %d\n"), local.Month);
|
||||
data.costLastMonth = data.costThisMonth;
|
||||
data.costThisMonth = 0;
|
||||
data.incomeLastMonth = data.incomeThisMonth;
|
||||
@@ -162,11 +158,9 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastImportUpdateMillis;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhi > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh import\n"), kwhi);
|
||||
this->realtimeData->use += kwhi;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float cost = price * kwhi;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), cost / 100.0, currency.c_str());
|
||||
if(importPrice != PRICE_NO_VALUE) {
|
||||
float cost = importPrice * kwhi;
|
||||
this->realtimeData->costHour += cost;
|
||||
this->realtimeData->costDay += cost;
|
||||
}
|
||||
@@ -178,11 +172,10 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastExportUpdateMillis;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhe > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh export\n"), kwhe);
|
||||
this->realtimeData->produce += kwhe;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float income = price * kwhe;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), income / 100.0, currency.c_str());
|
||||
float exportPrice = getPriceForHour(PRICE_DIRECTION_EXPORT, 0);
|
||||
if(exportPrice != PRICE_NO_VALUE) {
|
||||
float income = exportPrice * kwhe;
|
||||
this->realtimeData->incomeHour += income;
|
||||
this->realtimeData->incomeDay += income;
|
||||
}
|
||||
@@ -191,9 +184,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
}
|
||||
|
||||
if(config != NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) calculating threshold, currently at %d\n"), this->realtimeData->currentThresholdIdx);
|
||||
while(getMonthMax() > config->thresholds[this->realtimeData->currentThresholdIdx] && this->realtimeData->currentThresholdIdx < 10) this->realtimeData->currentThresholdIdx++;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) new threshold %d\n"), this->realtimeData->currentThresholdIdx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -205,20 +196,25 @@ void EnergyAccounting::calcDayCost() {
|
||||
if(tz == NULL) return;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(getPriceForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(getPriceForHour(PRICE_DIRECTION_IMPORT, 0) != PRICE_NO_VALUE) {
|
||||
if(initPrice) {
|
||||
this->realtimeData->costDay = 0;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
}
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
float price = getPriceForHour(i - local.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
this->realtimeData->costDay += price * (wh / 1000.0);
|
||||
|
||||
wh = ds->getHourExport(utc.Hour);
|
||||
this->realtimeData->incomeDay += price * (wh / 1000.0);
|
||||
float priceIn = getPriceForHour(PRICE_DIRECTION_IMPORT, i - local.Hour);
|
||||
if(priceIn != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
this->realtimeData->costDay += priceIn * (wh / 1000.0);
|
||||
}
|
||||
|
||||
float priceOut = getPriceForHour(PRICE_DIRECTION_EXPORT, i - local.Hour);
|
||||
if(priceOut != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourExport(utc.Hour);
|
||||
this->realtimeData->incomeDay += priceOut * (wh / 1000.0);
|
||||
}
|
||||
}
|
||||
initPrice = true;
|
||||
}
|
||||
@@ -402,9 +398,6 @@ EnergyAccountingPeak EnergyAccounting::getPeak(uint8_t num) {
|
||||
|
||||
bool EnergyAccounting::load() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Unable to load LittleFS\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -414,7 +407,6 @@ bool EnergyAccounting::load() {
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Data version %d\n"), buf[0]);
|
||||
if(buf[0] == 6) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
@@ -492,14 +484,11 @@ bool EnergyAccounting::load() {
|
||||
}
|
||||
ret = true;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(EnergyAccounting) Unknown version\n"));
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(EnergyAccounting) File not found\n"));
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -507,9 +496,6 @@ bool EnergyAccounting::load() {
|
||||
|
||||
bool EnergyAccounting::save() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Unable to load LittleFS\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
{
|
||||
@@ -536,7 +522,6 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(data.peaks[i].day == day || data.peaks[i].day == 0) {
|
||||
if(val > data.peaks[i].value) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Adding new max %d for day %d which is larger than %d\n"), val*10, day, data.peaks[i].value*10);
|
||||
data.peaks[i].day = day;
|
||||
data.peaks[i].value = val;
|
||||
return true;
|
||||
@@ -555,7 +540,6 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
}
|
||||
}
|
||||
if(idx < 5) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Adding new max %d for day %d\n"), val*10, day);
|
||||
data.peaks[idx].value = val;
|
||||
data.peaks[idx].day = day;
|
||||
return true;
|
||||
@@ -563,13 +547,11 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setFixedPrice(float price, String currency) {
|
||||
this->fixedPrice = price;
|
||||
void EnergyAccounting::setCurrency(String currency) {
|
||||
this->currency = currency;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getPriceForHour(uint8_t h) {
|
||||
if(fixedPrice > 0.0) return fixedPrice;
|
||||
if(eapi == NULL) return ENTSOE_NO_VALUE;
|
||||
return eapi->getValueForHour(h);
|
||||
float EnergyAccounting::getPriceForHour(uint8_t d, uint8_t h) {
|
||||
if(ps == NULL) return PRICE_NO_VALUE;
|
||||
return ps->getValueForHour(d, h);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#ifndef _ENTSOEAPI_H
|
||||
#define _ENTSOEAPI_H
|
||||
|
||||
#include "TimeLib.h"
|
||||
#include "Timezone.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EntsoeA44Parser.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
#define SSL_BUF_SIZE 512
|
||||
|
||||
class EntsoeApi {
|
||||
public:
|
||||
EntsoeApi(RemoteDebug*);
|
||||
void setup(EntsoeConfig&);
|
||||
bool loop();
|
||||
|
||||
char* getToken();
|
||||
char* getCurrency();
|
||||
char* getArea();
|
||||
char* getSource();
|
||||
float getValueForHour(int8_t);
|
||||
float getValueForHour(time_t, int8_t);
|
||||
|
||||
int16_t getLastError();
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
EntsoeConfig* config = NULL;
|
||||
HTTPClient* http = NULL;
|
||||
|
||||
uint8_t currentDay = 0, currentHour = 0;
|
||||
uint8_t tomorrowFetchMinute = 15; // How many minutes over 13:00 should it fetch prices
|
||||
uint8_t nextFetchDelayMinutes = 15;
|
||||
uint64_t lastTodayFetch = 0;
|
||||
uint64_t lastTomorrowFetch = 0;
|
||||
uint64_t lastCurrencyFetch = 0;
|
||||
PricesContainer* today = NULL;
|
||||
PricesContainer* tomorrow = NULL;
|
||||
|
||||
Timezone* tz = NULL;
|
||||
|
||||
static const uint16_t BufferSize = 256;
|
||||
char* buf;
|
||||
|
||||
bool hub = false;
|
||||
uint8_t* key = NULL;
|
||||
uint8_t* auth = NULL;
|
||||
|
||||
float currencyMultiplier = 0;
|
||||
|
||||
int16_t lastError = 0;
|
||||
|
||||
PricesContainer* fetchPrices(time_t);
|
||||
bool retrieve(const char* url, Stream* doc);
|
||||
float getCurrencyMultiplier(const char* from, const char* to, time_t t);
|
||||
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
};
|
||||
#endif
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _FIRMWARE_VERSION_h
|
||||
#define _FIRMWARE_VERSION_h
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FirmwareVersion.h"
|
||||
#include "generated_version.h"
|
||||
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HOMEASSISTANTMQTTHANDLER_H
|
||||
#define _HOMEASSISTANTMQTTHANDLER_H
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "HomeAssistantStatic.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
class HomeAssistantMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#else
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#endif
|
||||
this->hw = hw;
|
||||
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = false;
|
||||
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
|
||||
@@ -26,13 +37,15 @@ public:
|
||||
deviceModel = boardTypeToString(boardType);
|
||||
manufacturer = boardManufacturerToString(boardType);
|
||||
|
||||
char hostname[32];
|
||||
#if defined(ESP8266)
|
||||
String hostname = WiFi.hostname();
|
||||
strcpy(hostname, WiFi.hostname().c_str());
|
||||
#elif defined(ESP32)
|
||||
String hostname = WiFi.getHostname();
|
||||
strcpy(hostname, WiFi.getHostname());
|
||||
#endif
|
||||
|
||||
deviceUid = hostname; // Maybe configurable in the future?
|
||||
stripNonAscii((uint8_t*) hostname, 32, false);
|
||||
deviceUid = String(hostname); // Maybe configurable in the future?
|
||||
|
||||
if(strlen(config.discoveryHostname) > 0) {
|
||||
if(strncmp_P(config.discoveryHostname, PSTR("http"), 4) == 0) {
|
||||
@@ -56,13 +69,12 @@ public:
|
||||
statusTopic = F("homeassistant/status");
|
||||
discoveryTopic = F("homeassistant/sensor/");
|
||||
}
|
||||
// strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
|
||||
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
|
||||
};
|
||||
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
@@ -82,9 +94,10 @@ private:
|
||||
String discoveryTopic;
|
||||
String sensorNamePrefix;
|
||||
|
||||
bool l1Init, l2Init, l2eInit, l3Init, l3eInit, l4Init, l4eInit, rtInit, rteInit, pInit, sInit;
|
||||
bool l1Init, l2Init, l2eInit, l3Init, l3eInit, l4Init, l4eInit, rtInit, rteInit, pInit, sInit, rInit;
|
||||
bool tInit[32] = {false};
|
||||
bool prInit[38] = {false};
|
||||
uint32_t lastThresholdPublish = 0;
|
||||
|
||||
HwTools* hw;
|
||||
|
||||
@@ -93,7 +106,7 @@ private:
|
||||
bool publishList3(AmsData* data, EnergyAccounting* ea);
|
||||
bool publishList4(AmsData* data, EnergyAccounting* ea);
|
||||
String getMeterModel(AmsData* data);
|
||||
bool publishRealtime(AmsData* data, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishRealtime(AmsData* data, EnergyAccounting* ea, PriceService* ps);
|
||||
void publishSensor(const HomeAssistantSensor sensor);
|
||||
void publishList1Sensors();
|
||||
void publishList1ExportSensors();
|
||||
@@ -103,11 +116,12 @@ private:
|
||||
void publishList3ExportSensors();
|
||||
void publishList4Sensors();
|
||||
void publishList4ExportSensors();
|
||||
void publishRealtimeSensors(EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
void publishRealtimeExportSensors(EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
void publishRealtimeSensors(EnergyAccounting* ea, PriceService* ps);
|
||||
void publishRealtimeExportSensors(EnergyAccounting* ea, PriceService* ps);
|
||||
void publishTemperatureSensor(uint8_t index, String id);
|
||||
void publishPriceSensors(EntsoeApi* eapi);
|
||||
void publishPriceSensors(PriceService* ps);
|
||||
void publishSystemSensors();
|
||||
void publishThresholdSensors();
|
||||
|
||||
String boardTypeToString(uint8_t b) {
|
||||
switch(b) {
|
||||
@@ -155,6 +169,8 @@ private:
|
||||
return F("ESP32-C3");
|
||||
case 71:
|
||||
return F("ESP32-C3-DevKitM-1");
|
||||
case 80:
|
||||
return F("ESP32-S3");
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
return F("ESP8266");
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HOMEASSISTANTSTATIC_H
|
||||
#define _HOMEASSISTANTSTATIC_H
|
||||
|
||||
@@ -48,28 +54,34 @@ const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = {
|
||||
{"Accumulated active export", "/energy", "tPO", 4000, "kWh", "energy", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t List4SensorCount PROGMEM = 7;
|
||||
const uint8_t List4SensorCount PROGMEM = 10;
|
||||
const HomeAssistantSensor List4Sensors[List4SensorCount] PROGMEM = {
|
||||
{"Power factor", "/power", "PF", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 power factor", "/power", "PF1", 30, "%", "power_factor", "measurement"},
|
||||
{"L2 power factor", "/power", "PF2", 30, "%", "power_factor", "measurement"},
|
||||
{"L3 power factor", "/power", "PF3", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 active import", "/power", "P1", 30, "W", "power", "measurement"},
|
||||
{"L2 active import", "/power", "P2", 30, "W", "power", "measurement"},
|
||||
{"L3 active import", "/power", "P3", 30, "W", "power", "measurement"}
|
||||
{"Power factor", "/power", "PF", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 power factor", "/power", "PF1", 30, "%", "power_factor", "measurement"},
|
||||
{"L2 power factor", "/power", "PF2", 30, "%", "power_factor", "measurement"},
|
||||
{"L3 power factor", "/power", "PF3", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 active import", "/power", "P1", 30, "W", "power", "measurement"},
|
||||
{"L2 active import", "/power", "P2", 30, "W", "power", "measurement"},
|
||||
{"L3 active import", "/power", "P3", 30, "W", "power", "measurement"},
|
||||
{"L1 accumulated active import","/power", "tPI1", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L2 accumulated active import","/power", "tPI2", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L3 accumulated active import","/power", "tPI3", 30, "kWh", "energy", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t List4ExportSensorCount PROGMEM = 3;
|
||||
const uint8_t List4ExportSensorCount PROGMEM = 6;
|
||||
const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
|
||||
{"L1 active export", "/power", "PO1", 30, "W", "power", "measurement"},
|
||||
{"L2 active export", "/power", "PO2", 30, "W", "power", "measurement"},
|
||||
{"L3 active export", "/power", "PO3", 30, "W", "power", "measurement"}
|
||||
{"L1 active export", "/power", "PO1", 30, "W", "power", "measurement"},
|
||||
{"L2 active export", "/power", "PO2", 30, "W", "power", "measurement"},
|
||||
{"L3 active export", "/power", "PO3", 30, "W", "power", "measurement"},
|
||||
{"L1 accumulated active export","/power", "tPO1", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L2 accumulated active export","/power", "tPO2", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L3 accumulated active export","/power", "tPO3", 30, "kWh", "energy", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeSensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = {
|
||||
{"Month max", "/realtime","max", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Month max", "/realtime","max", 120, "kWh", "energy", ""},
|
||||
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", ""},
|
||||
{"Current hour used", "/realtime","hour.use", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current hour cost", "/realtime","hour.cost", 120, "", "monetary", ""},
|
||||
{"Current day used", "/realtime","day.use", 120, "kWh", "energy", "total_increasing"},
|
||||
@@ -89,6 +101,7 @@ const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGM
|
||||
};
|
||||
|
||||
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", 4000, "kWh", "energy", ""};
|
||||
const HomeAssistantSensor RealtimeThresholdSensor PROGMEM = {"Tariff threshold %d", "/realtime", "thresholds[%d]", 4000, "kWh", "energy", ""};
|
||||
|
||||
const uint8_t PriceSensorCount PROGMEM = 5;
|
||||
const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
|
||||
@@ -101,10 +114,11 @@ const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
|
||||
|
||||
const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices", "prices['%d']", 4000, "", "monetary", ""};
|
||||
|
||||
const uint8_t SystemSensorCount PROGMEM = 2;
|
||||
const uint8_t SystemSensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
|
||||
{"Status", "/state", "rssi", 180, "dBm", "signal_strength", "measurement"},
|
||||
{"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement"}
|
||||
{"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement"},
|
||||
{"Uptime", "/state", "up", 180, "s", "duration", "measurement"}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", 900, "°C", "temperature", "measurement"};
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"P1" : %.2f,
|
||||
"P2" : %.2f,
|
||||
"P3" : %.2f,
|
||||
"P1" : %d,
|
||||
"P2" : %d,
|
||||
"P3" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"PO1" : %.2f,
|
||||
"PO2" : %.2f,
|
||||
"PO3" : %.2f,
|
||||
"PO1" : %d,
|
||||
"PO2" : %d,
|
||||
"PO3" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
@@ -21,5 +21,11 @@
|
||||
"PF" : %.2f,
|
||||
"PF1" : %.2f,
|
||||
"PF2" : %.2f,
|
||||
"PF3" : %.2f
|
||||
"PF3" : %.2f,
|
||||
"tPI1" : %.3f,
|
||||
"tPI2" : %.3f,
|
||||
"tPI3" : %.3f,
|
||||
"tPO1" : %.3f,
|
||||
"tPO2" : %.3f,
|
||||
"tPO3" : %.3f
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"max" : %.1f,
|
||||
"peaks" : [ %s ],
|
||||
"threshold" : %d,
|
||||
"hour" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f,
|
||||
"income" : %.2f
|
||||
},
|
||||
"day" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f,
|
||||
"income" : %.2f
|
||||
},
|
||||
"month" : {
|
||||
"use" : %.2f,
|
||||
"cost" : %.2f,
|
||||
"produced" : %.2f,
|
||||
"income" : %.2f
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HomeAssistantMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
@@ -6,41 +12,52 @@
|
||||
#include "json/ha2_json.h"
|
||||
#include "json/ha3_json.h"
|
||||
#include "json/ha4_json.h"
|
||||
#include "json/jsonsys_json.h"
|
||||
#include "json/jsonprices_json.h"
|
||||
#include "json/hadiscover_json.h"
|
||||
#include "json/realtime_json.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(time(nullptr) < FirmwareVersion::BuildEpoch)
|
||||
return false;
|
||||
|
||||
// if(data->getListType() >= 3) { // publish energy counts
|
||||
// publishList3(data, ea);
|
||||
// loop();
|
||||
// }
|
||||
AmsData data;
|
||||
if(mqttConfig.stateUpdate) {
|
||||
uint64_t now = millis64();
|
||||
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
|
||||
data.apply(*previousState);
|
||||
data.apply(*update);
|
||||
lastStateUpdate = now;
|
||||
} else {
|
||||
data = *update;
|
||||
}
|
||||
|
||||
// if(data->getListType() == 1) { // publish power counts
|
||||
// publishList1(data, ea);
|
||||
// } else if(data->getListType() <= 3) { // publish power counts and volts/amps
|
||||
// publishList2(data, ea);
|
||||
// } else if(data->getListType() == 4) { // publish power counts and volts/amps/phase power and PF
|
||||
// publishList4(data, ea);
|
||||
// }
|
||||
if(data.getListType() >= 3 && !data.isCounterEstimated()) { // publish energy counts
|
||||
publishList3(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if(data.getListType() == 1) { // publish power counts
|
||||
publishList1(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() <= 3) { // publish power counts and volts/amps
|
||||
publishList2(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 4) { // publish power counts and volts/amps/phase power and PF
|
||||
publishList4(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if(ea->isInitialized()) {
|
||||
publishRealtime(&data, ea, ps);
|
||||
mqtt.loop();
|
||||
}
|
||||
loop();
|
||||
|
||||
// if(ea->isInitialized()) {
|
||||
// publishRealtime(data, ea, eapi);
|
||||
// loop();
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -110,7 +127,13 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
|
||||
data->getPowerFactor() == 0 ? 1 : data->getPowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL1PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor()
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor(),
|
||||
data->getL1ActiveImportCounter(),
|
||||
data->getL2ActiveImportCounter(),
|
||||
data->getL3ActiveImportCounter(),
|
||||
data->getL1ActiveExportCounter(),
|
||||
data->getL2ActiveExportCounter(),
|
||||
data->getL3ActiveExportCounter()
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
}
|
||||
@@ -121,9 +144,10 @@ String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
return meterModel;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
publishRealtimeSensors(ea, eapi);
|
||||
if(ea->getProducedThisHour() > 0.0 || ea->getProducedToday() > 0.0 || ea->getProducedThisMonth() > 0.0) publishRealtimeExportSensors(ea, eapi);
|
||||
bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting* ea, PriceService* ps) {
|
||||
publishRealtimeSensors(ea, ps);
|
||||
if(ea->getProducedThisHour() > 0.0 || ea->getProducedToday() > 0.0 || ea->getProducedThisMonth() > 0.0) publishRealtimeExportSensors(ea, ps);
|
||||
if(lastThresholdPublish == 0) publishThresholdSensors();
|
||||
String peaks = "";
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
@@ -131,7 +155,7 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i).value / 100.0, 2);
|
||||
}
|
||||
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"max\":%.1f,\"peaks\":[%s],\"threshold\":%d,\"hour\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f},\"day\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f},\"month\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f}"),
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
@@ -148,10 +172,29 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
ea->getProducedThisMonth(),
|
||||
ea->getIncomeThisMonth()
|
||||
);
|
||||
uint32_t now = millis();
|
||||
if(lastThresholdPublish == 0 || now-lastThresholdPublish > 3600000) {
|
||||
EnergyAccountingConfig* conf = ea->getConfig();
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"thresholds\": [%d,%d,%d,%d,%d,%d,%d,%d,%d]"),
|
||||
conf->thresholds[0],
|
||||
conf->thresholds[1],
|
||||
conf->thresholds[2],
|
||||
conf->thresholds[3],
|
||||
conf->thresholds[4],
|
||||
conf->thresholds[5],
|
||||
conf->thresholds[6],
|
||||
conf->thresholds[7],
|
||||
conf->thresholds[8]
|
||||
);
|
||||
lastThresholdPublish = now;
|
||||
}
|
||||
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
|
||||
return mqtt.publish(topic + "/realtime", json);
|
||||
}
|
||||
|
||||
|
||||
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
int count = hw->getTempSensorCount();
|
||||
if(count < 2) return false;
|
||||
@@ -181,13 +224,13 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
publishPriceSensors(eapi);
|
||||
publishPriceSensors(ps);
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
@@ -195,12 +238,12 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, now, i);
|
||||
values[i] = val;
|
||||
|
||||
if(val == ENTSOE_NO_VALUE) break;
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
|
||||
if(val < min) min = val;
|
||||
if(val > max) max = val;
|
||||
@@ -215,7 +258,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val1 = values[i++];
|
||||
float val2 = values[i++];
|
||||
float val3 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE) continue;
|
||||
float val3hr = val1+val2+val3;
|
||||
if(min3hrIdx == -1 || min3hr > val3hr) {
|
||||
min3hr = val3hr;
|
||||
@@ -231,7 +274,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val4 = values[i++];
|
||||
float val5 = values[i++];
|
||||
float val6 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE || val4 == PRICE_NO_VALUE || val5 == PRICE_NO_VALUE || val6 == PRICE_NO_VALUE) continue;
|
||||
float val6hr = val1+val2+val3+val4+val5+val6;
|
||||
if(min6hrIdx == -1 || min6hr > val6hr) {
|
||||
min6hr = val6hr;
|
||||
@@ -265,65 +308,47 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
snprintf_P(json, BufferSize, JSONPRICES_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
values[0] == ENTSOE_NO_VALUE ? "null" : String(values[0], 4).c_str(),
|
||||
values[1] == ENTSOE_NO_VALUE ? "null" : String(values[1], 4).c_str(),
|
||||
values[2] == ENTSOE_NO_VALUE ? "null" : String(values[2], 4).c_str(),
|
||||
values[3] == ENTSOE_NO_VALUE ? "null" : String(values[3], 4).c_str(),
|
||||
values[4] == ENTSOE_NO_VALUE ? "null" : String(values[4], 4).c_str(),
|
||||
values[5] == ENTSOE_NO_VALUE ? "null" : String(values[5], 4).c_str(),
|
||||
values[6] == ENTSOE_NO_VALUE ? "null" : String(values[6], 4).c_str(),
|
||||
values[7] == ENTSOE_NO_VALUE ? "null" : String(values[7], 4).c_str(),
|
||||
values[8] == ENTSOE_NO_VALUE ? "null" : String(values[8], 4).c_str(),
|
||||
values[9] == ENTSOE_NO_VALUE ? "null" : String(values[9], 4).c_str(),
|
||||
values[10] == ENTSOE_NO_VALUE ? "null" : String(values[10], 4).c_str(),
|
||||
values[11] == ENTSOE_NO_VALUE ? "null" : String(values[11], 4).c_str(),
|
||||
values[12] == ENTSOE_NO_VALUE ? "null" : String(values[12], 4).c_str(),
|
||||
values[13] == ENTSOE_NO_VALUE ? "null" : String(values[13], 4).c_str(),
|
||||
values[14] == ENTSOE_NO_VALUE ? "null" : String(values[14], 4).c_str(),
|
||||
values[15] == ENTSOE_NO_VALUE ? "null" : String(values[15], 4).c_str(),
|
||||
values[16] == ENTSOE_NO_VALUE ? "null" : String(values[16], 4).c_str(),
|
||||
values[17] == ENTSOE_NO_VALUE ? "null" : String(values[17], 4).c_str(),
|
||||
values[18] == ENTSOE_NO_VALUE ? "null" : String(values[18], 4).c_str(),
|
||||
values[19] == ENTSOE_NO_VALUE ? "null" : String(values[19], 4).c_str(),
|
||||
values[20] == ENTSOE_NO_VALUE ? "null" : String(values[20], 4).c_str(),
|
||||
values[21] == ENTSOE_NO_VALUE ? "null" : String(values[21], 4).c_str(),
|
||||
values[22] == ENTSOE_NO_VALUE ? "null" : String(values[22], 4).c_str(),
|
||||
values[23] == ENTSOE_NO_VALUE ? "null" : String(values[23], 4).c_str(),
|
||||
values[24] == ENTSOE_NO_VALUE ? "null" : String(values[24], 4).c_str(),
|
||||
values[25] == ENTSOE_NO_VALUE ? "null" : String(values[25], 4).c_str(),
|
||||
values[26] == ENTSOE_NO_VALUE ? "null" : String(values[26], 4).c_str(),
|
||||
values[27] == ENTSOE_NO_VALUE ? "null" : String(values[27], 4).c_str(),
|
||||
values[28] == ENTSOE_NO_VALUE ? "null" : String(values[28], 4).c_str(),
|
||||
values[29] == ENTSOE_NO_VALUE ? "null" : String(values[29], 4).c_str(),
|
||||
values[30] == ENTSOE_NO_VALUE ? "null" : String(values[30], 4).c_str(),
|
||||
values[31] == ENTSOE_NO_VALUE ? "null" : String(values[31], 4).c_str(),
|
||||
values[32] == ENTSOE_NO_VALUE ? "null" : String(values[32], 4).c_str(),
|
||||
values[33] == ENTSOE_NO_VALUE ? "null" : String(values[33], 4).c_str(),
|
||||
values[34] == ENTSOE_NO_VALUE ? "null" : String(values[34], 4).c_str(),
|
||||
values[35] == ENTSOE_NO_VALUE ? "null" : String(values[35], 4).c_str(),
|
||||
values[36] == ENTSOE_NO_VALUE ? "null" : String(values[36], 4).c_str(),
|
||||
values[37] == ENTSOE_NO_VALUE ? "null" : String(values[37], 4).c_str(),
|
||||
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{"), WiFi.macAddress().c_str());
|
||||
for(uint8_t i = 0;i < 38; i++) {
|
||||
if(values[i] == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%d\":null,"), i);
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%d\":%.4f,"), i, values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"),
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
ts1hr,
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_EXPORT, now, 0);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"exportprices\":{\"0\":null}"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"exportprices\":{\"0\":%.4f}"), val);
|
||||
}
|
||||
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
|
||||
bool ret = mqtt.publish(topic + "/prices", json, true, 0);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
|
||||
publishSystemSensors();
|
||||
if(hw->getTemperature() > -50) publishTemperatureSensor(0, "");
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\"}"),
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
@@ -430,13 +455,13 @@ void HomeAssistantMqttHandler::publishList4ExportSensors() {
|
||||
l4eInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, PriceService* ps) {
|
||||
if(rtInit) return;
|
||||
for(uint8_t i = 0; i < RealtimeSensorCount; i++) {
|
||||
HomeAssistantSensor sensor = RealtimeSensors[i];
|
||||
if(strncmp_P(sensor.devcl, PSTR("monetary"), 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
sensor.uom = eapi->getCurrency();
|
||||
if(ps == NULL) continue;
|
||||
sensor.uom = ps->getCurrency();
|
||||
}
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -461,13 +486,13 @@ void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, Ents
|
||||
rtInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishRealtimeExportSensors(EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
void HomeAssistantMqttHandler::publishRealtimeExportSensors(EnergyAccounting* ea, PriceService* ps) {
|
||||
if(rteInit) return;
|
||||
for(uint8_t i = 0; i < RealtimeExportSensorCount; i++) {
|
||||
HomeAssistantSensor sensor = RealtimeExportSensors[i];
|
||||
if(strncmp_P(sensor.devcl, PSTR("monetary"), 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
sensor.uom = eapi->getCurrency();
|
||||
if(ps == NULL) continue;
|
||||
sensor.uom = ps->getCurrency();
|
||||
}
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -499,9 +524,9 @@ void HomeAssistantMqttHandler::publishTemperatureSensor(uint8_t index, String id
|
||||
tInit[index] = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
if(eapi == NULL) return;
|
||||
String uom = String(eapi->getCurrency()) + "/kWh";
|
||||
void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
|
||||
if(ps == NULL) return;
|
||||
String uom = String(ps->getCurrency()) + "/kWh";
|
||||
|
||||
if(!pInit) {
|
||||
for(uint8_t i = 0; i < PriceSensorCount; i++) {
|
||||
@@ -515,8 +540,8 @@ void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
}
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
if(prInit[i]) continue;
|
||||
float val = eapi->getValueForHour(i);
|
||||
if(val == ENTSOE_NO_VALUE) continue;
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, i);
|
||||
if(val == PRICE_NO_VALUE) continue;
|
||||
|
||||
char name[strlen(PriceSensor.name)+2];
|
||||
snprintf(name, strlen(PriceSensor.name)+2, PriceSensor.name, i, i == 1 ? "hour" : "hours");
|
||||
@@ -535,6 +560,21 @@ void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
prInit[i] = true;
|
||||
}
|
||||
|
||||
float exportPrice = ps->getValueForHour(PRICE_DIRECTION_EXPORT, 0);
|
||||
if(exportPrice != PRICE_NO_VALUE) {
|
||||
char path[20];
|
||||
snprintf(path, 20, "exportprices['%d']", 0);
|
||||
HomeAssistantSensor sensor = {
|
||||
"Export price current hour",
|
||||
PriceSensor.topic,
|
||||
path,
|
||||
PriceSensor.ttl,
|
||||
uom.c_str(),
|
||||
PriceSensor.devcl,
|
||||
"total"
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
@@ -545,6 +585,27 @@ void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
sInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishThresholdSensors() {
|
||||
if(rInit) return;
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
char name[strlen(RealtimeThresholdSensor.name)+1];
|
||||
snprintf(name, strlen(RealtimeThresholdSensor.name)+2, RealtimeThresholdSensor.name, i+1);
|
||||
char path[strlen(RealtimeThresholdSensor.path)+1];
|
||||
snprintf(path, strlen(RealtimeThresholdSensor.path)+1, RealtimeThresholdSensor.path, i);
|
||||
HomeAssistantSensor sensor = {
|
||||
name,
|
||||
RealtimeThresholdSensor.topic,
|
||||
path,
|
||||
RealtimeThresholdSensor.ttl,
|
||||
RealtimeThresholdSensor.uom,
|
||||
RealtimeThresholdSensor.devcl,
|
||||
RealtimeThresholdSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
rInit = true;
|
||||
}
|
||||
|
||||
uint8_t HomeAssistantMqttHandler::getFormat() {
|
||||
return 4;
|
||||
}
|
||||
@@ -556,8 +617,11 @@ bool HomeAssistantMqttHandler::publishRaw(String data) {
|
||||
void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
|
||||
if(topic.equals(statusTopic)) {
|
||||
if(payload.equals("online")) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = false;
|
||||
for(uint8_t i = 0; i < 32; i++) tInit[i] = false;
|
||||
for(uint8_t i = 0; i < 38; i++) prInit[i] = false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HWTOOLS_H
|
||||
#define _HWTOOLS_H
|
||||
|
||||
@@ -36,7 +42,8 @@ struct AdcConfig {
|
||||
|
||||
class HwTools {
|
||||
public:
|
||||
void setup(GpioConfig*, AmsConfiguration*);
|
||||
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
|
||||
void setup(GpioConfig*);
|
||||
float getVcc();
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorData* getTempSensorData(uint8_t);
|
||||
@@ -48,6 +55,7 @@ public:
|
||||
bool ledOn(uint8_t color);
|
||||
bool ledOff(uint8_t color);
|
||||
bool ledBlink(uint8_t color, uint8_t blink);
|
||||
void setBootSuccessful(bool value);
|
||||
|
||||
HwTools() {};
|
||||
private:
|
||||
@@ -57,13 +65,14 @@ private:
|
||||
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
|
||||
#endif
|
||||
GpioConfig* config;
|
||||
AmsConfiguration* amsConf;
|
||||
bool tempSensorInit;
|
||||
OneWire *oneWire = NULL;
|
||||
DallasTemperature *sensorApi = NULL;
|
||||
uint8_t sensorCount = 0;
|
||||
TempSensorData** tempSensors = NULL;
|
||||
|
||||
bool bootSuccessful = false;
|
||||
|
||||
bool writeLedPin(uint8_t color, uint8_t state);
|
||||
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
|
||||
void getAdcChannel(uint8_t pin, AdcConfig&);
|
||||
|
||||
@@ -1,8 +1,151 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HwTools.h"
|
||||
|
||||
void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
switch(boardType) {
|
||||
case 5: // Pow-K+
|
||||
meterConfig.txPin = 9;
|
||||
case 7: // Pow-U+
|
||||
case 6: // Pow-P1
|
||||
meterConfig.rxPin = 16;
|
||||
gpioConfig.apPin = 0;
|
||||
gpioConfig.ledPinRed = 13;
|
||||
gpioConfig.ledPinGreen = 14;
|
||||
gpioConfig.ledRgbInverted = true;
|
||||
gpioConfig.vccPin = 10;
|
||||
gpioConfig.vccResistorGnd = 22;
|
||||
gpioConfig.vccResistorVcc = 33;
|
||||
gpioConfig.ledDisablePin = 6;
|
||||
return true;
|
||||
case 51: // Wemos S2 mini
|
||||
gpioConfig.ledPin = 15;
|
||||
gpioConfig.ledInverted = false;
|
||||
gpioConfig.apPin = 0;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 18;
|
||||
if(meterConfig.rxPin != 18) {
|
||||
gpioConfig.vccPin = 18;
|
||||
gpioConfig.vccResistorGnd = 45;
|
||||
gpioConfig.vccResistorVcc = 10;
|
||||
}
|
||||
return true;
|
||||
case 50: // Generic ESP32-S2
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 18;
|
||||
return true;
|
||||
}
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
switch(boardType) {
|
||||
case 8: // dbeinder: HAN mosquito
|
||||
meterConfig.rxPin = 7;
|
||||
meterConfig.rxPinPullup = false;
|
||||
gpioConfig.apPin = 9;
|
||||
gpioConfig.ledRgbInverted = true;
|
||||
gpioConfig.ledPinRed = 5;
|
||||
gpioConfig.ledPinGreen = 6;
|
||||
gpioConfig.ledPinBlue = 4;
|
||||
return true;
|
||||
case 71: // ESP32-C3-DevKitM-1
|
||||
gpioConfig.apPin = 9;
|
||||
case 70: // Generic ESP32-C3
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 7;
|
||||
return true;
|
||||
}
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
switch(boardType) {
|
||||
case 80: // Generic ESP32-S3
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 18;
|
||||
return true;
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
switch(boardType) {
|
||||
case 241: // LilyGO T-ETH-POE
|
||||
gpioConfig.apPin = 0;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
return true;
|
||||
case 242: // M5 PoESP32
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
|
||||
return true;
|
||||
case 243: // WT32-ETH01
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
|
||||
return true;
|
||||
case 201: // D32
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
|
||||
gpioConfig.apPin = 4;
|
||||
gpioConfig.ledPin = 5;
|
||||
gpioConfig.ledInverted = true;
|
||||
return true;
|
||||
case 202: // Feather
|
||||
case 203: // DevKitC
|
||||
case 200: // ESP32
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = false;
|
||||
return true;
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
switch(boardType) {
|
||||
case 2: // spenceme
|
||||
gpioConfig.vccBootLimit = 32;
|
||||
meterConfig.rxPin = 3;
|
||||
gpioConfig.apPin = 0;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.tempSensorPin = 5;
|
||||
return true;
|
||||
case 0: // roarfred
|
||||
meterConfig.rxPin = 3;
|
||||
gpioConfig.apPin = 0;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.tempSensorPin = 5;
|
||||
return true;
|
||||
case 1: // Arnio Kamstrup
|
||||
case 3: // Pow-K UART0
|
||||
case 4: // Pow-U UART0
|
||||
meterConfig.rxPin = 3;
|
||||
gpioConfig.apPin = 0;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.ledPinRed = 13;
|
||||
gpioConfig.ledPinGreen = 14;
|
||||
gpioConfig.ledRgbInverted = true;
|
||||
return true;
|
||||
case 5: // Pow-K GPIO12
|
||||
case 7: // Pow-U GPIO12
|
||||
meterConfig.rxPin = 12;
|
||||
gpioConfig.apPin = 0;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.ledPinRed = 13;
|
||||
gpioConfig.ledPinGreen = 14;
|
||||
gpioConfig.ledRgbInverted = true;
|
||||
return true;
|
||||
case 101: // D1
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 5;
|
||||
gpioConfig.apPin = 4;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.vccMultiplier = 1100;
|
||||
return true;
|
||||
case 100: // ESP8266
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 3;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void HwTools::setup(GpioConfig* config) {
|
||||
this->config = config;
|
||||
this->amsConf = amsConf;
|
||||
this->tempSensorInit = false;
|
||||
if(sensorApi != NULL)
|
||||
delete sensorApi;
|
||||
@@ -92,6 +235,10 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
} else {
|
||||
config->ledPinBlue = 0xFF;
|
||||
}
|
||||
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
|
||||
pinMode(config->ledDisablePin, OUTPUT_OPEN_DRAIN);
|
||||
setBootSuccessful(false);
|
||||
}
|
||||
}
|
||||
|
||||
void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
||||
@@ -358,8 +505,7 @@ float HwTools::getTemperature() {
|
||||
}
|
||||
for(int x = 0; x < sensorCount; x++) {
|
||||
TempSensorData data = *tempSensors[x];
|
||||
TempSensorConfig* conf = amsConf->getTempSensorConfig(data.address);
|
||||
if((conf == NULL || conf->common) && data.lastValidRead > -85) {
|
||||
if(data.lastValidRead > -85) {
|
||||
ret += data.lastValidRead;
|
||||
c++;
|
||||
}
|
||||
@@ -380,7 +526,33 @@ int HwTools::getWifiRssi() {
|
||||
return isnan(rssi) ? -100.0 : rssi;
|
||||
}
|
||||
|
||||
void HwTools::setBootSuccessful(bool value) {
|
||||
if(bootSuccessful && value) return;
|
||||
bootSuccessful = value;
|
||||
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
|
||||
switch(config->ledBehaviour) {
|
||||
case LED_BEHAVIOUR_ERROR_ONLY:
|
||||
case LED_BEHAVIOUR_OFF:
|
||||
digitalWrite(config->ledDisablePin, LOW);
|
||||
break;
|
||||
case LED_BEHAVIOUR_BOOT:
|
||||
if(bootSuccessful) {
|
||||
digitalWrite(config->ledDisablePin, LOW);
|
||||
} else {
|
||||
digitalWrite(config->ledDisablePin, HIGH);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
digitalWrite(config->ledDisablePin, HIGH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HwTools::ledOn(uint8_t color) {
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_OFF) return false;
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
|
||||
|
||||
if(color == LED_INTERNAL) {
|
||||
return writeLedPin(color, config->ledInverted ? LOW : HIGH);
|
||||
} else {
|
||||
|
||||
1
lib/JsonMqttHandler/include/.gitignore
vendored
1
lib/JsonMqttHandler/include/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
json/*.h
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _JSONMQTTHANDLER_H
|
||||
#define _JSONMQTTHANDLER_H
|
||||
|
||||
@@ -5,13 +11,19 @@
|
||||
|
||||
class JsonMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->hw = hw;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
#else
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->hw = hw;
|
||||
};
|
||||
#endif
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
@@ -20,7 +32,8 @@ public:
|
||||
|
||||
private:
|
||||
HwTools* hw;
|
||||
|
||||
uint16_t appendJsonHeader(AmsData* data);
|
||||
uint16_t appendJsonFooter(EnergyAccounting* ea, uint16_t pos);
|
||||
bool publishList1(AmsData* data, EnergyAccounting* ea);
|
||||
bool publishList2(AmsData* data, EnergyAccounting* ea);
|
||||
bool publishList3(AmsData* data, EnergyAccounting* ea);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"P" : %d
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"tPI" : %.3f,
|
||||
"tPO" : %.3f,
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : %lu
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"P1" : %.2f,
|
||||
"P2" : %.2f,
|
||||
"P3" : %.2f,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"PO1" : %.2f,
|
||||
"PO2" : %.2f,
|
||||
"PO3" : %.2f,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"PF" : %.2f,
|
||||
"PF1" : %.2f,
|
||||
"PF2" : %.2f,
|
||||
"PF3" : %.2f,
|
||||
"tPI" : %.3f,
|
||||
"tPO" : %.3f,
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : %lu
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"prices" : {
|
||||
"0" : %s,
|
||||
"1" : %s,
|
||||
"2" : %s,
|
||||
"3" : %s,
|
||||
"4" : %s,
|
||||
"5" : %s,
|
||||
"6" : %s,
|
||||
"7" : %s,
|
||||
"8" : %s,
|
||||
"9" : %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,
|
||||
"36" : %s,
|
||||
"37" : %s,
|
||||
"min" : %.4f,
|
||||
"max" : %.4f,
|
||||
"cheapest1hr" : "%s",
|
||||
"cheapest3hr" : "%s",
|
||||
"cheapest6hr" : "%s"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %d,
|
||||
"vcc" : %.3f,
|
||||
"rssi" : %d,
|
||||
"temp" : %.2f,
|
||||
"version" : "%s"
|
||||
}
|
||||
@@ -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(";");
|
||||
|
||||
@@ -1,69 +1,115 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "JsonMqttHandler.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "json/json1_json.h"
|
||||
#include "json/json2_json.h"
|
||||
#include "json/json3_json.h"
|
||||
#include "json/json4_json.h"
|
||||
#include "json/jsonsys_json.h"
|
||||
#include "json/jsonprices_json.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
bool JsonMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, no publish topic\n"));
|
||||
return false;
|
||||
}
|
||||
if(!mqtt.connected()) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, not connected\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
memset(json, 0, BufferSize);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Publishing list ID %d!\n"), data->getListType());
|
||||
if(data->getListType() == 1) {
|
||||
ret = publishList1(data, ea);
|
||||
} else if(data->getListType() == 2) {
|
||||
ret = publishList2(data, ea);
|
||||
} else if(data->getListType() == 3) {
|
||||
ret = publishList3(data, ea);
|
||||
} else if(data->getListType() == 4) {
|
||||
ret = publishList4(data, ea);
|
||||
AmsData data;
|
||||
if(mqttConfig.stateUpdate) {
|
||||
uint64_t now = millis64();
|
||||
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
|
||||
data.apply(*previousState);
|
||||
data.apply(*update);
|
||||
lastStateUpdate = now;
|
||||
} else {
|
||||
data = *update;
|
||||
}
|
||||
|
||||
if(data.getListType() == 1) {
|
||||
ret = publishList1(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 2) {
|
||||
ret = publishList2(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 3) {
|
||||
ret = publishList3(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 4) {
|
||||
ret = publishList4(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON1_JSON,
|
||||
uint16_t JsonMqttHandler::appendJsonHeader(AmsData* data) {
|
||||
return snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%u,\"t\":%lu,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,"),
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
data->getActiveImportPower(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
hw->getTemperature()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
|
||||
uint16_t JsonMqttHandler::appendJsonFooter(EnergyAccounting* ea, uint16_t pos) {
|
||||
char pf[4];
|
||||
if(mqttConfig.payloadFormat == 6) {
|
||||
strcpy_P(pf, PSTR("rt_"));
|
||||
} else {
|
||||
memset(pf, 0, 4);
|
||||
}
|
||||
|
||||
return snprintf_P(json+pos, BufferSize-pos, PSTR("%s\"%sh\":%.2f,\"%sd\":%.1f,\"%st\":%d,\"%sx\":%.2f,\"%she\":%.2f,\"%sde\":%.1f%s"),
|
||||
strlen(pf) == 0 ? "},\"realtime\":{" : ",",
|
||||
pf,
|
||||
ea->getUseThisHour(),
|
||||
pf,
|
||||
ea->getUseToday(),
|
||||
pf,
|
||||
ea->getCurrentThreshold(),
|
||||
pf,
|
||||
ea->getMonthMax(),
|
||||
pf,
|
||||
ea->getProducedThisHour(),
|
||||
pf,
|
||||
ea->getProducedToday(),
|
||||
strlen(pf) == 0 ? "}" : ""
|
||||
);
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"P\":%d"), data->getActiveImportPower());
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list1"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON2_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"lv\":\"%s\",\"meterId\":\"%s\",\"type\":\"%s\",\"P\":%d,\"Q\":%d,\"PO\":%d,\"QO\":%d,\"I1\":%.2f,\"I2\":%.2f,\"I3\":%.2f,\"U1\":%.2f,\"U2\":%.2f,\"U3\":%.2f"),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
getMeterModel(data).c_str(),
|
||||
@@ -76,26 +122,26 @@ bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
data->getL3Voltage()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list2"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON3_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"lv\":\"%s\",\"meterId\":\"%s\",\"type\":\"%s\",\"P\":%d,\"Q\":%d,\"PO\":%d,\"QO\":%d,\"I1\":%.2f,\"I2\":%.2f,\"I3\":%.2f,\"U1\":%.2f,\"U2\":%.2f,\"U3\":%.2f,\"tPI\":%.3f,\"tPO\":%.3f,\"tQI\":%.3f,\"tQO\":%.3f,\"rtc\":%lu"),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
getMeterModel(data).c_str(),
|
||||
@@ -113,26 +159,26 @@ bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list3"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON4_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"lv\":\"%s\",\"meterId\":\"%s\",\"type\":\"%s\",\"P\":%d,\"P1\":%d,\"P2\":%d,\"P3\":%d,\"Q\":%d,\"PO\":%d,\"PO1\":%d,\"PO2\":%d,\"PO3\":%d,\"QO\":%d,\"I1\":%.2f,\"I2\":%.2f,\"I3\":%.2f,\"U1\":%.2f,\"U2\":%.2f,\"U3\":%.2f,\"PF\":%.2f,\"PF1\":%.2f,\"PF2\":%.2f,\"PF3\":%.2f,\"tPI\":%.3f,\"tPO\":%.3f,\"tQI\":%.3f,\"tQO\":%.3f,\"tPI1\":%.3f,\"tPI2\":%.3f,\"tPI3\":%.3f,\"tPO1\":%.3f,\"tPO2\":%.3f,\"tPO3\":%.3f,\"rtc\":%lu"),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
getMeterModel(data).c_str(),
|
||||
@@ -160,15 +206,24 @@ bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
data->getL1ActiveImportCounter(),
|
||||
data->getL2ActiveImportCounter(),
|
||||
data->getL3ActiveImportCounter(),
|
||||
data->getL1ActiveExportCounter(),
|
||||
data->getL2ActiveExportCounter(),
|
||||
data->getL3ActiveExportCounter(),
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list4"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
String JsonMqttHandler::getMeterModel(AmsData* data) {
|
||||
@@ -183,30 +238,43 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf_P(json, 24, PSTR("{\"temperatures\":{"));
|
||||
|
||||
uint16_t pos = 0;
|
||||
if(mqttConfig.payloadFormat == 6) {
|
||||
json[pos++] = '{';
|
||||
} else {
|
||||
pos = snprintf_P(json, 24, PSTR("{\"temperatures\":{"));
|
||||
}
|
||||
for(int i = 0; i < count; i++) {
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
if(data != NULL) {
|
||||
char* pos = json+strlen(json);
|
||||
snprintf_P(pos, 26, PSTR("\"%s\":%.2f,"),
|
||||
pos += snprintf_P(json+pos, 26, PSTR("\"%s\":%.2f,"),
|
||||
toHex(data->address, 8).c_str(),
|
||||
data->lastRead
|
||||
);
|
||||
data->changed = false;
|
||||
}
|
||||
}
|
||||
char* pos = json+strlen(json);
|
||||
snprintf_P(count == 0 ? pos : pos-1, 8, PSTR("}}"));
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
bool ret = false;
|
||||
json[pos-1] = '}';
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
}
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/temperatures"), mqttConfig.publishTopic);
|
||||
ret = mqtt.publish(topic, json);
|
||||
} else {
|
||||
ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
@@ -215,12 +283,12 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, now, i);
|
||||
values[i] = val;
|
||||
|
||||
if(val == ENTSOE_NO_VALUE) break;
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
|
||||
if(val < min) min = val;
|
||||
if(val > max) max = val;
|
||||
@@ -235,7 +303,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val1 = values[i++];
|
||||
float val2 = values[i++];
|
||||
float val3 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE) continue;
|
||||
float val3hr = val1+val2+val3;
|
||||
if(min3hrIdx == -1 || min3hr > val3hr) {
|
||||
min3hr = val3hr;
|
||||
@@ -251,7 +319,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val4 = values[i++];
|
||||
float val5 = values[i++];
|
||||
float val6 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE || val4 == PRICE_NO_VALUE || val5 == PRICE_NO_VALUE || val6 == PRICE_NO_VALUE) continue;
|
||||
float val6hr = val1+val2+val3+val4+val5+val6;
|
||||
if(min6hrIdx == -1 || min6hr > val6hr) {
|
||||
min6hr = val6hr;
|
||||
@@ -286,62 +354,56 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
snprintf_P(json, BufferSize, JSONPRICES_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
values[0] == ENTSOE_NO_VALUE ? "null" : String(values[0], 4).c_str(),
|
||||
values[1] == ENTSOE_NO_VALUE ? "null" : String(values[1], 4).c_str(),
|
||||
values[2] == ENTSOE_NO_VALUE ? "null" : String(values[2], 4).c_str(),
|
||||
values[3] == ENTSOE_NO_VALUE ? "null" : String(values[3], 4).c_str(),
|
||||
values[4] == ENTSOE_NO_VALUE ? "null" : String(values[4], 4).c_str(),
|
||||
values[5] == ENTSOE_NO_VALUE ? "null" : String(values[5], 4).c_str(),
|
||||
values[6] == ENTSOE_NO_VALUE ? "null" : String(values[6], 4).c_str(),
|
||||
values[7] == ENTSOE_NO_VALUE ? "null" : String(values[7], 4).c_str(),
|
||||
values[8] == ENTSOE_NO_VALUE ? "null" : String(values[8], 4).c_str(),
|
||||
values[9] == ENTSOE_NO_VALUE ? "null" : String(values[9], 4).c_str(),
|
||||
values[10] == ENTSOE_NO_VALUE ? "null" : String(values[10], 4).c_str(),
|
||||
values[11] == ENTSOE_NO_VALUE ? "null" : String(values[11], 4).c_str(),
|
||||
values[12] == ENTSOE_NO_VALUE ? "null" : String(values[12], 4).c_str(),
|
||||
values[13] == ENTSOE_NO_VALUE ? "null" : String(values[13], 4).c_str(),
|
||||
values[14] == ENTSOE_NO_VALUE ? "null" : String(values[14], 4).c_str(),
|
||||
values[15] == ENTSOE_NO_VALUE ? "null" : String(values[15], 4).c_str(),
|
||||
values[16] == ENTSOE_NO_VALUE ? "null" : String(values[16], 4).c_str(),
|
||||
values[17] == ENTSOE_NO_VALUE ? "null" : String(values[17], 4).c_str(),
|
||||
values[18] == ENTSOE_NO_VALUE ? "null" : String(values[18], 4).c_str(),
|
||||
values[19] == ENTSOE_NO_VALUE ? "null" : String(values[19], 4).c_str(),
|
||||
values[20] == ENTSOE_NO_VALUE ? "null" : String(values[20], 4).c_str(),
|
||||
values[21] == ENTSOE_NO_VALUE ? "null" : String(values[21], 4).c_str(),
|
||||
values[22] == ENTSOE_NO_VALUE ? "null" : String(values[22], 4).c_str(),
|
||||
values[23] == ENTSOE_NO_VALUE ? "null" : String(values[23], 4).c_str(),
|
||||
values[24] == ENTSOE_NO_VALUE ? "null" : String(values[24], 4).c_str(),
|
||||
values[25] == ENTSOE_NO_VALUE ? "null" : String(values[25], 4).c_str(),
|
||||
values[26] == ENTSOE_NO_VALUE ? "null" : String(values[26], 4).c_str(),
|
||||
values[27] == ENTSOE_NO_VALUE ? "null" : String(values[27], 4).c_str(),
|
||||
values[28] == ENTSOE_NO_VALUE ? "null" : String(values[28], 4).c_str(),
|
||||
values[29] == ENTSOE_NO_VALUE ? "null" : String(values[29], 4).c_str(),
|
||||
values[30] == ENTSOE_NO_VALUE ? "null" : String(values[30], 4).c_str(),
|
||||
values[31] == ENTSOE_NO_VALUE ? "null" : String(values[31], 4).c_str(),
|
||||
values[32] == ENTSOE_NO_VALUE ? "null" : String(values[32], 4).c_str(),
|
||||
values[33] == ENTSOE_NO_VALUE ? "null" : String(values[33], 4).c_str(),
|
||||
values[34] == ENTSOE_NO_VALUE ? "null" : String(values[34], 4).c_str(),
|
||||
values[35] == ENTSOE_NO_VALUE ? "null" : String(values[35], 4).c_str(),
|
||||
values[36] == ENTSOE_NO_VALUE ? "null" : String(values[36], 4).c_str(),
|
||||
values[37] == ENTSOE_NO_VALUE ? "null" : String(values[37], 4).c_str(),
|
||||
char pf[4];
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\","), WiFi.macAddress().c_str());
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
memset(pf, 0, 4);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"prices\":{"));
|
||||
} else {
|
||||
strcpy_P(pf, PSTR("pr_"));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0;i < 38; i++) {
|
||||
if(values[i] == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":null,"), pf, i);
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":%.4f,"), pf, i, values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%smin\":%.4f,\"%smax\":%.4f,\"%scheapest1hr\":\"%s\",\"%scheapest3hr\":\"%s\",\"%scheapest6hr\":\"%s\"}"),
|
||||
pf,
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
pf,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
pf,
|
||||
ts1hr,
|
||||
pf,
|
||||
ts3hr,
|
||||
pf,
|
||||
ts6hr
|
||||
);
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
}
|
||||
bool ret = false;
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/prices"), mqttConfig.publishTopic);
|
||||
ret = mqtt.publish(topic, json);
|
||||
} else {
|
||||
ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\"}"),
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
@@ -350,7 +412,14 @@ bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounti
|
||||
hw->getTemperature(),
|
||||
FirmwareVersion::VersionString
|
||||
);
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
bool ret = false;
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/system"), mqttConfig.publishTopic);
|
||||
ret = mqtt.publish(topic, json);
|
||||
} else {
|
||||
ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IEC62056_21_H
|
||||
#define _IEC62056_21_H
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IEC62056_7_5_H
|
||||
#define _IEC62056_7_5_H
|
||||
|
||||
@@ -25,6 +31,8 @@ private:
|
||||
float getNumber(CosemData*);
|
||||
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
|
||||
|
||||
uint8_t AMS_OBIS_UNKNOWN_1[4] = { 25, 9, 0, 255 };
|
||||
|
||||
uint8_t AMS_OBIS_VERSION[4] = { 0, 2, 129, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL[4] = { 96, 1, 1, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL_2[4] = { 96, 1, 7, 255 };
|
||||
@@ -55,6 +63,12 @@ private:
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L1_COUNT[4] = { 21, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2_COUNT[4] = { 41, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3_COUNT[4] = { 61, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1_COUNT[4] = { 22, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2_COUNT[4] = { 42, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3_COUNT[4] = { 62, 8, 0, 255 };
|
||||
|
||||
};
|
||||
#endif
|
||||
18
lib/MeterCommunicators/include/ImpulseAmsData.h
Normal file
18
lib/MeterCommunicators/include/ImpulseAmsData.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IMPULSEAMSDATA_H
|
||||
#define _IMPULSEAMSDATA_H
|
||||
|
||||
#include "AmsData.h"
|
||||
|
||||
class ImpulseAmsData : public AmsData {
|
||||
public:
|
||||
ImpulseAmsData(AmsData &state, uint16_t pulsePerKwh, uint8_t pulses);
|
||||
ImpulseAmsData(double activeImportCounter);
|
||||
};
|
||||
|
||||
#endif
|
||||
40
lib/MeterCommunicators/include/KmpCommunicator.h
Normal file
40
lib/MeterCommunicators/include/KmpCommunicator.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2024
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PassiveMeterCommunicator.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "Timezone.h"
|
||||
#include "ImpulseAmsData.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "SoftwareSerial.h"
|
||||
#endif
|
||||
|
||||
#include "KmpTalker.h"
|
||||
|
||||
class KmpCommunicator : public PassiveMeterCommunicator {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
KmpCommunicator(RemoteDebug* debugger) : PassiveMeterCommunicator(debugger) {};
|
||||
#else
|
||||
KmpCommunicator(Stream* debugger) : PassiveMeterCommunicator(debugger) {};
|
||||
#endif
|
||||
void configure(MeterConfig&);
|
||||
bool loop();
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
bool isConfigChanged() { return false; }
|
||||
void getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
private:
|
||||
KmpTalker* talker = NULL;
|
||||
};
|
||||
56
lib/MeterCommunicators/include/KmpTalker.h
Normal file
56
lib/MeterCommunicators/include/KmpTalker.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: All rights reserved
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Stream.h>
|
||||
|
||||
#define DATA_PARSE_OK 0
|
||||
#define DATA_PARSE_FAIL -1
|
||||
#define DATA_PARSE_INCOMPLETE -2
|
||||
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
|
||||
|
||||
struct KmpParserContext {
|
||||
uint8_t type;
|
||||
uint16_t length;
|
||||
};
|
||||
|
||||
struct KmpDataHolder {
|
||||
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||
uint32_t l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
uint32_t l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
double l1activeImportCounter = 0, l2activeImportCounter = 0, l3activeImportCounter = 0;
|
||||
double l1activeExportCounter = 0, l2activeExportCounter = 0, l3activeExportCounter = 0;
|
||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||
uint16_t meterId;
|
||||
};
|
||||
|
||||
class KmpTalker {
|
||||
public:
|
||||
KmpTalker(Stream *hanSerial, uint8_t* hanBuffer, uint16_t hanBufferSize);
|
||||
bool loop();
|
||||
void getData(KmpDataHolder& data);
|
||||
int getLastError();
|
||||
|
||||
private:
|
||||
Stream *hanSerial;
|
||||
uint8_t *hanBuffer = NULL;
|
||||
uint16_t hanBufferSize = 0;
|
||||
|
||||
bool dataAvailable = false;
|
||||
int len = 0;
|
||||
int pos = DATA_PARSE_INCOMPLETE;
|
||||
int lastError = DATA_PARSE_OK;
|
||||
bool serialInit = false;
|
||||
|
||||
uint64_t lastUpdate = 0;
|
||||
uint8_t batch = 0;
|
||||
KmpParserContext ctx;
|
||||
|
||||
KmpDataHolder state;
|
||||
};
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LNG_H
|
||||
#define _LNG_H
|
||||
|
||||
@@ -5,7 +11,6 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct LngHeader {
|
||||
uint8_t tag;
|
||||
@@ -25,7 +30,7 @@ struct LngObisDescriptor {
|
||||
|
||||
class LNG : public AmsData {
|
||||
public:
|
||||
LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
|
||||
LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
|
||||
uint64_t getNumber(CosemData* item);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LNG2_H
|
||||
#define _LNG2_H
|
||||
|
||||
@@ -5,7 +11,6 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct Lng2Data_3p {
|
||||
CosemBasic header;
|
||||
@@ -27,7 +32,7 @@ struct Lng2Data_3p {
|
||||
|
||||
class LNG2 : public AmsData {
|
||||
public:
|
||||
LNG2(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
|
||||
LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
|
||||
|
||||
private:
|
||||
uint8_t getString(CosemData* item, char* target);
|
||||
28
lib/MeterCommunicators/include/MeterCommunicator.h
Normal file
28
lib/MeterCommunicators/include/MeterCommunicator.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _METERCOMMUNICATOR_H
|
||||
#define _METERCOMMUNICATOR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
class MeterCommunicator {
|
||||
public:
|
||||
virtual ~MeterCommunicator() {};
|
||||
virtual void configure(MeterConfig&, Timezone*);
|
||||
virtual bool loop();
|
||||
virtual AmsData* getData(AmsData& meterState);
|
||||
virtual int getLastError();
|
||||
virtual bool isConfigChanged();
|
||||
virtual void getCurrentConfig(MeterConfig& meterConfig);
|
||||
};
|
||||
|
||||
#endif
|
||||
96
lib/MeterCommunicators/include/PassiveMeterCommunicator.h
Normal file
96
lib/MeterCommunicators/include/PassiveMeterCommunicator.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PASSIVEMETERCOMMUNICATOR_H
|
||||
#define _PASSIVEMETERCOMMUNICATOR_H
|
||||
|
||||
#include "MeterCommunicator.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParsers.h"
|
||||
#include "Timezone.h"
|
||||
#include "PassthroughMqttHandler.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "SoftwareSerial.h"
|
||||
#endif
|
||||
|
||||
const uint32_t AUTO_BAUD_RATES[] = { 2400, 115200 };
|
||||
|
||||
class PassiveMeterCommunicator : public MeterCommunicator {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PassiveMeterCommunicator(RemoteDebug* debugger);
|
||||
#else
|
||||
PassiveMeterCommunicator(Stream* debugger);
|
||||
#endif
|
||||
void configure(MeterConfig&, Timezone*);
|
||||
bool loop();
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
bool isConfigChanged();
|
||||
void getCurrentConfig(MeterConfig& meterConfig);
|
||||
void setPassthroughMqttHandler(PassthroughMqttHandler*);
|
||||
|
||||
HardwareSerial* getHwSerial();
|
||||
void rxerr(int err);
|
||||
|
||||
protected:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
MeterConfig meterConfig;
|
||||
bool configChanged = false;
|
||||
Timezone* tz;
|
||||
|
||||
PassthroughMqttHandler* pt = NULL;
|
||||
|
||||
uint8_t *hanBuffer = NULL;
|
||||
uint16_t hanBufferSize = 0;
|
||||
Stream *hanSerial;
|
||||
#if defined(ESP8266)
|
||||
SoftwareSerial *swSerial = NULL;
|
||||
#endif
|
||||
HardwareSerial *hwSerial = NULL;
|
||||
uint8_t rxBufferErrors = 0;
|
||||
|
||||
bool autodetect = false, validDataReceived = false;
|
||||
unsigned long meterAutodetectLastChange = 0;
|
||||
long rate = 10000;
|
||||
uint32_t autodetectBaud = 0;
|
||||
uint8_t autodetectParity = 11;
|
||||
bool autodetectInvert = false;
|
||||
uint8_t autodetectCount = 0;
|
||||
|
||||
bool dataAvailable = false;
|
||||
int len = 0;
|
||||
int pos = DATA_PARSE_INCOMPLETE;
|
||||
int lastError = DATA_PARSE_OK;
|
||||
bool serialInit = false;
|
||||
bool maxDetectPayloadDetectDone = false;
|
||||
uint8_t maxDetectedPayloadSize = 64;
|
||||
DataParserContext ctx = {0,0,0,0};
|
||||
|
||||
HDLCParser *hdlcParser = NULL;
|
||||
MBUSParser *mbusParser = NULL;
|
||||
GBTParser *gbtParser = NULL;
|
||||
GCMParser *gcmParser = NULL;
|
||||
LLCParser *llcParser = NULL;
|
||||
DLMSParser *dlmsParser = NULL;
|
||||
DSMRParser *dsmrParser = NULL;
|
||||
|
||||
void setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive = true);
|
||||
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
void printHanReadError(int pos);
|
||||
void handleAutodetect(unsigned long now);
|
||||
};
|
||||
|
||||
#endif
|
||||
51
lib/MeterCommunicators/include/PulseMeterCommunicator.h
Normal file
51
lib/MeterCommunicators/include/PulseMeterCommunicator.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2024
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PULSEMETERCOMMUNICATOR_H
|
||||
#define _PULSEMETERCOMMUNICATOR_H
|
||||
|
||||
#include "MeterCommunicator.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "Timezone.h"
|
||||
#include "ImpulseAmsData.h"
|
||||
|
||||
class PulseMeterCommunicator : public MeterCommunicator {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PulseMeterCommunicator(RemoteDebug* debugger);
|
||||
#else
|
||||
PulseMeterCommunicator(Stream* debugger);
|
||||
#endif
|
||||
void configure(MeterConfig& config, Timezone* tz);
|
||||
bool loop();
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
bool isConfigChanged();
|
||||
void getCurrentConfig(MeterConfig& meterConfig);
|
||||
|
||||
void onPulse(uint8_t pulses);
|
||||
|
||||
protected:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
MeterConfig meterConfig;
|
||||
bool configChanged = false;
|
||||
Timezone* tz;
|
||||
bool updated = false;
|
||||
bool initialized = false;
|
||||
AmsData state;
|
||||
uint64_t lastUpdate = 0;
|
||||
|
||||
void setupGpio();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "IEC6205621.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "IEC6205675.h"
|
||||
#include "lwip/def.h"
|
||||
#include "Timezone.h"
|
||||
@@ -19,7 +25,13 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
CosemData* data = getCosemDataAt(1, ((char *) (d)));
|
||||
|
||||
// Kaifa special case...
|
||||
if(data->base.type == CosemTypeOctetString) {
|
||||
if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
listType = 1;
|
||||
meterType = AmsTypeKaifa;
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(data->base.type == CosemTypeOctetString) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
@@ -202,15 +214,59 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
} else if(useMeterType == AmsTypeIskra && data->base.type == CosemTypeOctetString) { // Iskra special case
|
||||
meterType = AmsTypeIskra;
|
||||
uint8_t idx = 5;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
}
|
||||
|
||||
uint8_t str_len = 0;
|
||||
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
meterId = String(str);
|
||||
}
|
||||
|
||||
listType = 3;
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(useMeterType == AmsTypeUnknown) {
|
||||
uint8_t str_len = 0;
|
||||
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
meterType = AmsTypeIskra;
|
||||
meterId = String(str);
|
||||
lastUpdateMillis = millis64();
|
||||
listType = 3;
|
||||
}
|
||||
}
|
||||
} else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
listType = 1;
|
||||
meterType = AmsTypeKaifa;
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
// Kaifa end
|
||||
} else {
|
||||
listType = 1;
|
||||
activeImportPower = val;
|
||||
@@ -407,6 +463,38 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
listType = 4;
|
||||
l3activeExportPower = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L1_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L1_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l1activeImportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L2_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l2activeImportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L3_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L3_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l3activeImportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L1_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L1_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l1activeExportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l2activeExportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l3activeExportCounter = val/1000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(meterType == AmsTypeKamstrup) {
|
||||
if(listType >= 3) {
|
||||
@@ -414,6 +502,12 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
activeExportCounter *= 10;
|
||||
reactiveImportCounter *= 10;
|
||||
reactiveExportCounter *= 10;
|
||||
l1activeImportCounter *= 10;
|
||||
l2activeImportCounter *= 10;
|
||||
l3activeImportCounter *= 10;
|
||||
l1activeExportCounter *= 10;
|
||||
l2activeExportCounter *= 10;
|
||||
l3activeExportCounter *= 10;
|
||||
}
|
||||
if(l1current != 0)
|
||||
l1current /= 100;
|
||||
27
lib/MeterCommunicators/src/ImpulseAmsData.cpp
Normal file
27
lib/MeterCommunicators/src/ImpulseAmsData.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ImpulseAmsData.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
ImpulseAmsData::ImpulseAmsData(AmsData& state, uint16_t pulsePerKwh, uint8_t pulses) {
|
||||
listType = 1;
|
||||
if(pulses > 0) {
|
||||
lastUpdateMillis = millis64();
|
||||
uint64_t lastStateMillis = state.getLastUpdateMillis();
|
||||
if(lastStateMillis > 0) {
|
||||
uint64_t ms = (lastUpdateMillis - lastStateMillis) / pulses;
|
||||
activeImportPower = (1000.0 / pulsePerKwh) / (((float) ms) / 3600000.0);
|
||||
}
|
||||
} else {
|
||||
lastUpdateMillis = state.getLastUpdateMillis();
|
||||
}
|
||||
}
|
||||
|
||||
ImpulseAmsData::ImpulseAmsData(double activeImportCounter) {
|
||||
this->activeImportCounter = activeImportCounter;
|
||||
this->listType = 3;
|
||||
}
|
||||
84
lib/MeterCommunicators/src/KmpCommunicator.cpp
Normal file
84
lib/MeterCommunicators/src/KmpCommunicator.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "KmpCommunicator.h"
|
||||
#include "Uptime.h"
|
||||
#include "crc.h"
|
||||
#include "OBIScodes.h"
|
||||
|
||||
void KmpCommunicator::configure(MeterConfig& meterConfig) {
|
||||
this->meterConfig = meterConfig;
|
||||
this->configChanged = false;
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert, false);
|
||||
talker = new KmpTalker(hanSerial, hanBuffer, hanBufferSize);
|
||||
}
|
||||
|
||||
bool KmpCommunicator::loop() {
|
||||
uint64_t now = millis64();
|
||||
bool ret = talker->loop();
|
||||
int lastError = getLastError();
|
||||
if(ret) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Successful loop\n"));
|
||||
Serial.flush();
|
||||
} else if(lastError < 0 && lastError != DATA_PARSE_INCOMPLETE) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Error code: %d\n"), getLastError());
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, hanBufferSize);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int KmpCommunicator::getLastError() {
|
||||
return talker == NULL ? DATA_PARSE_FAIL : talker->getLastError();
|
||||
}
|
||||
|
||||
AmsData* KmpCommunicator::getData(AmsData& meterState) {
|
||||
if(talker == NULL) return NULL;
|
||||
KmpDataHolder kmpData;
|
||||
talker->getData(kmpData);
|
||||
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);
|
||||
return data;
|
||||
}
|
||||
247
lib/MeterCommunicators/src/LNG.cpp
Normal file
247
lib/MeterCommunicators/src/LNG.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LNG.h"
|
||||
#include "lwip/def.h"
|
||||
#include "ntohll.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
|
||||
LngHeader* h = (LngHeader*) payload;
|
||||
if(h->tag == CosemTypeStructure && h->arrayTag == CosemTypeArray) {
|
||||
apply(meterState);
|
||||
meterType = AmsTypeLandisGyr;
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
|
||||
uint8_t* ptr = (uint8_t*) &h[1];
|
||||
uint8_t* data = ptr + (18*h->arrayLength); // Skip descriptors
|
||||
|
||||
uint64_t o170 = 0, o270 = 0;
|
||||
uint64_t o180 = 0, o280 = 0;
|
||||
uint64_t o181 = 0, o182 = 0;
|
||||
uint64_t o281 = 0, o282 = 0;
|
||||
uint64_t o380 = 0, o480 = 0;
|
||||
uint64_t o580 = 0, o680 = 0;
|
||||
uint64_t o780 = 0, o880 = 0;
|
||||
LngObisDescriptor* descriptor = (LngObisDescriptor*) ptr;
|
||||
for(uint8_t x = 0; x < h->arrayLength-1; x++) {
|
||||
ptr = (uint8_t*) &descriptor[1];
|
||||
descriptor = (LngObisDescriptor*) ptr;
|
||||
|
||||
CosemData* item = (CosemData*) data;
|
||||
if(descriptor->obis[3] == 7) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
if(descriptor->obis[2] > 1) {
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
} else {
|
||||
listType = listType >= 1 ? listType : 1;
|
||||
}
|
||||
switch(descriptor->obis[2]) {
|
||||
case 1:
|
||||
o170 = getNumber(item);
|
||||
break;
|
||||
case 2:
|
||||
o270 = getNumber(item);
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportPower = getNumber(item);
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportPower = getNumber(item);
|
||||
break;
|
||||
case 31:
|
||||
l1current = getNumber(item) / 100.0;
|
||||
break;
|
||||
case 51:
|
||||
l2current = getNumber(item) / 100.0;
|
||||
break;
|
||||
case 71:
|
||||
l3current = getNumber(item) / 100.0;
|
||||
break;
|
||||
case 32:
|
||||
l1voltage = getNumber(item) / 10.0;
|
||||
break;
|
||||
case 52:
|
||||
l2voltage = getNumber(item) / 10.0;
|
||||
break;
|
||||
case 72:
|
||||
l3voltage = getNumber(item) / 10.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(descriptor->obis[3] == 8) {
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(descriptor->obis[4] == 0) {
|
||||
switch(descriptor->obis[2]) {
|
||||
case 1:
|
||||
o180 = getNumber(item);
|
||||
activeImportCounter = o180 / 1000.0;
|
||||
break;
|
||||
case 2:
|
||||
o280 = getNumber(item);
|
||||
activeExportCounter = o280 / 1000.0;
|
||||
break;
|
||||
case 3:
|
||||
o380 = getNumber(item);
|
||||
reactiveImportCounter = o380 / 1000.0;
|
||||
break;
|
||||
case 4:
|
||||
o480 = getNumber(item);
|
||||
reactiveExportCounter = o480 / 1000.0;
|
||||
break;
|
||||
case 5:
|
||||
o580 = getNumber(item);
|
||||
break;
|
||||
case 6:
|
||||
o680 = getNumber(item);
|
||||
break;
|
||||
case 7:
|
||||
o780 = getNumber(item);
|
||||
break;
|
||||
case 8:
|
||||
o880 = getNumber(item);
|
||||
break;
|
||||
}
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
switch(descriptor->obis[2]) {
|
||||
case 1:
|
||||
o181 = getNumber(item);
|
||||
break;
|
||||
case 2:
|
||||
o281 = getNumber(item);
|
||||
break;
|
||||
}
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
switch(descriptor->obis[2]) {
|
||||
case 1:
|
||||
o182 = getNumber(item);
|
||||
break;
|
||||
case 2:
|
||||
o282 = getNumber(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(descriptor->obis[2] == 96) {
|
||||
if(descriptor->obis[3] == 1) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
char str[item->oct.length+1];
|
||||
memcpy(str, item->oct.data, item->oct.length);
|
||||
str[item->oct.length] = '\0';
|
||||
meterId = String(str);
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
char str[item->oct.length+1];
|
||||
memcpy(str, item->oct.data, item->oct.length);
|
||||
str[item->oct.length] = '\0';
|
||||
meterModel = String(str);
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(o170 > 0 || o270 > 0) {
|
||||
int32_t sum = o170-o270;
|
||||
if(sum > 0) {
|
||||
activeImportPower = sum;
|
||||
activeExportPower = 0;
|
||||
} else {
|
||||
activeImportPower = 0;
|
||||
activeExportPower = sum * -1;
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
}
|
||||
}
|
||||
|
||||
if(o181 > 0 || o182 > 0) {
|
||||
activeImportCounter = (o181 + o182) / 1000.0;
|
||||
}
|
||||
if(o281 > 0 || o282 > 0) {
|
||||
activeExportCounter = (o281 + o282) / 1000.0;
|
||||
}
|
||||
|
||||
if(o580 > 0 || o680 > 0) {
|
||||
reactiveImportCounter = (o580 + o680) / 1000.0;
|
||||
}
|
||||
if(o780 > 0 || o880 > 0) {
|
||||
reactiveExportCounter = (o780 + o880) / 1000.0;
|
||||
}
|
||||
|
||||
if((*data) == 0x09) {
|
||||
data += (*(data+1))+2;
|
||||
} else if((*data) == 0x15) {
|
||||
data += 9;
|
||||
} else if((*data) == 0x06) {
|
||||
data += 5;
|
||||
} else if((*data) == 0x12) {
|
||||
data += 3;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
lastUpdateMillis = millis64();
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
if(meterConfig->voltageMultiplier > 0) {
|
||||
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
if(meterConfig->amperageMultiplier > 0) {
|
||||
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
if(!threePhase)
|
||||
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t LNG::getNumber(CosemData* item) {
|
||||
if(item != NULL) {
|
||||
uint64_t ret = 0.0;
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongSigned: {
|
||||
int16_t i16 = ntohs(item->ls.data);
|
||||
return i16;
|
||||
}
|
||||
case CosemTypeLongUnsigned: {
|
||||
uint16_t u16 = ntohs(item->lu.data);
|
||||
return u16;
|
||||
}
|
||||
case CosemTypeDLongSigned: {
|
||||
int32_t i32 = ntohl(item->dlu.data);
|
||||
return i32;
|
||||
}
|
||||
case CosemTypeDLongUnsigned: {
|
||||
uint32_t u32 = ntohl(item->dlu.data);
|
||||
return u32;
|
||||
}
|
||||
case CosemTypeLong64Signed: {
|
||||
int64_t i64 = ntohll(item->l64s.data);
|
||||
return i64;
|
||||
}
|
||||
case CosemTypeLong64Unsigned: {
|
||||
uint64_t u64 = ntohll(item->l64u.data);
|
||||
return u64;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LNG2.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
LNG2::LNG2(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
|
||||
LNG2::LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
|
||||
CosemBasic* h = (CosemBasic*) payload;
|
||||
if(h->length == 0x0e) {
|
||||
apply(meterState);
|
||||
meterType = AmsTypeLandisGyr;
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
|
||||
907
lib/MeterCommunicators/src/PassiveMeterCommunicator.cpp
Normal file
907
lib/MeterCommunicators/src/PassiveMeterCommunicator.cpp
Normal file
@@ -0,0 +1,907 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PassiveMeterCommunicator.h"
|
||||
#include "IEC6205675.h"
|
||||
#include "IEC6205621.h"
|
||||
#include "LNG.h"
|
||||
#include "LNG2.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <driver/uart.h>
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PassiveMeterCommunicator::PassiveMeterCommunicator(RemoteDebug* debugger) {
|
||||
this->debugger = debugger;
|
||||
}
|
||||
#else
|
||||
PassiveMeterCommunicator::PassiveMeterCommunicator(Stream* debugger) {
|
||||
this->debugger = debugger;
|
||||
}
|
||||
#endif
|
||||
|
||||
void PassiveMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
|
||||
this->meterConfig = meterConfig;
|
||||
if(meterConfig.baud == 0) {
|
||||
autodetect = true;
|
||||
}
|
||||
this->configChanged = false;
|
||||
this->tz = tz;
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
||||
if(gcmParser != NULL) {
|
||||
delete gcmParser;
|
||||
gcmParser = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool PassiveMeterCommunicator::loop() {
|
||||
if(hanBufferSize == 0) return false;
|
||||
|
||||
unsigned long now = millis();
|
||||
if(autodetect) handleAutodetect(now);
|
||||
|
||||
unsigned long start, end;
|
||||
if(!hanSerial->available()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Before reading, empty serial buffer to increase chance of getting first byte of a data transfer
|
||||
if(!serialInit) {
|
||||
hanSerial->readBytes(hanBuffer, hanBufferSize);
|
||||
serialInit = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
dataAvailable = false;
|
||||
ctx = {0,0,0,0};
|
||||
memset(ctx.system_title, 0, 8);
|
||||
pos = DATA_PARSE_INCOMPLETE;
|
||||
// For each byte received, check if we have a complete frame we can handle
|
||||
start = millis();
|
||||
while(hanSerial->available() && pos == DATA_PARSE_INCOMPLETE) {
|
||||
// If buffer was overflowed, reset
|
||||
if(len >= hanBufferSize) {
|
||||
hanSerial->readBytes(hanBuffer, hanBufferSize);
|
||||
len = 0;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
|
||||
return false;
|
||||
}
|
||||
hanBuffer[len++] = hanSerial->read();
|
||||
ctx.length = len;
|
||||
pos = unwrapData((uint8_t *) hanBuffer, ctx);
|
||||
if(ctx.type > 0 && pos >= 0) {
|
||||
switch(ctx.type) {
|
||||
case DATA_TAG_DLMS:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid DLMS at %d\n"), pos);
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid DSMR at %d\n"), pos);
|
||||
break;
|
||||
case DATA_TAG_SNRM:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid SNMR at %d\n"), pos);
|
||||
break;
|
||||
case DATA_TAG_AARE:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid AARE at %d\n"), pos);
|
||||
break;
|
||||
case DATA_TAG_RES:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid Get Response at %d\n"), pos);
|
||||
break;
|
||||
case DATA_TAG_HDLC:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid HDLC at %d\n"), pos);
|
||||
break;
|
||||
default:
|
||||
// TODO: Move this so that payload is sent to MQTT
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
end = millis();
|
||||
if(end-start > 1000) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
|
||||
}
|
||||
|
||||
if(pos == DATA_PARSE_INCOMPLETE) {
|
||||
return false;
|
||||
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data received\n"));
|
||||
lastError = pos;
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
}
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
if(pos == DATA_PARSE_INTERMEDIATE_SEGMENT) {
|
||||
len = 0;
|
||||
return false;
|
||||
} else if(pos < 0) {
|
||||
lastError = pos;
|
||||
printHanReadError(pos);
|
||||
len += hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(hanBuffer, len);
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
}
|
||||
while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ctx.type == 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
|
||||
lastError = pos;
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
}
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data is valid, clear the rest of the buffer to avoid tainted parsing
|
||||
for(int i = pos+ctx.length; i<hanBufferSize; i++) {
|
||||
hanBuffer[i] = 0x00;
|
||||
}
|
||||
dataAvailable = true;
|
||||
lastError = DATA_PARSE_OK;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
|
||||
if(!dataAvailable) return NULL;
|
||||
if(ctx.length > hanBufferSize) {
|
||||
debugger->printf_P(PSTR("Invalid context length\n"));
|
||||
dataAvailable = false;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
AmsData* data = NULL;
|
||||
char* payload = ((char *) (hanBuffer)) + pos;
|
||||
if(maxDetectedPayloadSize < pos) maxDetectedPayloadSize = pos;
|
||||
if(ctx.type == DATA_TAG_DLMS) {
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes((uint8_t*) payload, ctx.length);
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Using application data:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugPrint((byte*) payload, 0, ctx.length);
|
||||
|
||||
// Rudimentary detector for L&G proprietary format, this is terrible code... Fix later
|
||||
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LNG\n"));
|
||||
LNG lngData = LNG(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
|
||||
if(lngData.getListType() >= 1) {
|
||||
data = new AmsData();
|
||||
data->apply(meterState);
|
||||
data->apply(lngData);
|
||||
}
|
||||
} else if(payload[0] == CosemTypeStructure &&
|
||||
payload[2] == CosemTypeLongUnsigned &&
|
||||
payload[5] == CosemTypeLongUnsigned &&
|
||||
payload[8] == CosemTypeLongUnsigned &&
|
||||
payload[11] == CosemTypeLongUnsigned &&
|
||||
payload[14] == CosemTypeLongUnsigned &&
|
||||
payload[17] == CosemTypeLongUnsigned
|
||||
) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LNG2\n"));
|
||||
LNG2 lngData = LNG2(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
|
||||
if(lngData.getListType() >= 1) {
|
||||
data = new AmsData();
|
||||
data->apply(meterState);
|
||||
data->apply(lngData);
|
||||
}
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DLMS\n"));
|
||||
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
|
||||
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState);
|
||||
}
|
||||
} else if(ctx.type == DATA_TAG_DSMR) {
|
||||
data = new IEC6205621(payload, tz, &meterConfig);
|
||||
}
|
||||
len = 0;
|
||||
if(data != NULL) {
|
||||
if(data->getListType() > 0) {
|
||||
validDataReceived = true;
|
||||
if(rxBufferErrors > 0) rxBufferErrors--;
|
||||
}
|
||||
}
|
||||
dataAvailable = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
int PassiveMeterCommunicator::getLastError() {
|
||||
#if defined ESP8266
|
||||
if(hwSerial != NULL) {
|
||||
if(hwSerial->hasRxError()) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial RX error\n"));
|
||||
lastError = 96;
|
||||
}
|
||||
if(hwSerial->hasOverrun()) {
|
||||
rxerr(2);
|
||||
}
|
||||
} else if(swSerial != NULL) {
|
||||
if(swSerial->overflow()) {
|
||||
rxerr(2);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return lastError;
|
||||
}
|
||||
|
||||
bool PassiveMeterCommunicator::isConfigChanged() {
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
|
||||
|
||||
int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &context) {
|
||||
int16_t ret = 0;
|
||||
bool doRet = false;
|
||||
uint16_t end = hanBufferSize;
|
||||
uint8_t tag = (*buf);
|
||||
uint8_t lastTag = DATA_TAG_NONE;
|
||||
while(tag != DATA_TAG_NONE) {
|
||||
int16_t curLen = context.length;
|
||||
int8_t res = 0;
|
||||
switch(tag) {
|
||||
case DATA_TAG_HDLC:
|
||||
if(hdlcParser == NULL) hdlcParser = new HDLCParser();
|
||||
res = hdlcParser->parse(buf, context);
|
||||
if(context.length < 3) doRet = true;
|
||||
break;
|
||||
case DATA_TAG_MBUS:
|
||||
if(mbusParser == NULL) mbusParser = new MBUSParser();
|
||||
res = mbusParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_GBT:
|
||||
if(gbtParser == NULL) gbtParser = new GBTParser();
|
||||
res = gbtParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_GCM:
|
||||
if(gcmParser == NULL) gcmParser = new GCMParser(meterConfig.encryptionKey, meterConfig.authenticationKey);
|
||||
res = gcmParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_LLC:
|
||||
if(llcParser == NULL) llcParser = new LLCParser();
|
||||
res = llcParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_DLMS:
|
||||
if(dlmsParser == NULL) dlmsParser = new DLMSParser();
|
||||
res = dlmsParser->parse(buf, context);
|
||||
if(res >= 0) doRet = true;
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
if(dsmrParser == NULL) dsmrParser = new DSMRParser();
|
||||
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
|
||||
if(res >= 0) doRet = true;
|
||||
break;
|
||||
case DATA_TAG_SNRM:
|
||||
case DATA_TAG_AARE:
|
||||
case DATA_TAG_RES:
|
||||
res = DATA_PARSE_OK;
|
||||
doRet = true;
|
||||
break;
|
||||
default:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
lastTag = tag;
|
||||
if(res == DATA_PARSE_INCOMPLETE) {
|
||||
return res;
|
||||
}
|
||||
if(context.length > end) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
|
||||
context.type = 0;
|
||||
context.length = 0;
|
||||
return false;
|
||||
}
|
||||
switch(tag) {
|
||||
case DATA_TAG_HDLC:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HDLC frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(buf, curLen);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_MBUS:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("MBUS frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(buf, curLen);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_GBT:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GBT frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_GCM:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GCM frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_LLC:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LLC frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_DLMS:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DLMS frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DSMR frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishString((char*) buf);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_SNRM:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("SNMR frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_AARE:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("AARE frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_RES:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("RES frame:\n"));
|
||||
break;
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugPrint(buf, 0, curLen);
|
||||
if(res == DATA_PARSE_FINAL_SEGMENT) {
|
||||
if(tag == DATA_TAG_MBUS) {
|
||||
res = mbusParser->write(buf, context);
|
||||
}
|
||||
}
|
||||
|
||||
if(res < 0) {
|
||||
return res;
|
||||
}
|
||||
buf += res;
|
||||
end -= res;
|
||||
ret += res;
|
||||
|
||||
// If we are ready to return, do that
|
||||
if(doRet) {
|
||||
context.type = tag;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Use start byte of new buffer position as tag for next round in loop
|
||||
tag = (*buf);
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
debugger->print(F("0"));
|
||||
debugger->print(buffer[i], HEX);
|
||||
debugger->print(F(" "));
|
||||
if ((i - start + 1) % 16 == 0)
|
||||
debugger->println(F(""));
|
||||
else if ((i - start + 1) % 4 == 0)
|
||||
debugger->print(F(" "));
|
||||
|
||||
yield(); // Let other get some resources too
|
||||
}
|
||||
debugger->println(F(""));
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::printHanReadError(int pos) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
switch(pos) {
|
||||
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Boundry flag missing\n"));
|
||||
break;
|
||||
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Header checksum error\n"));
|
||||
break;
|
||||
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame checksum error\n"));
|
||||
break;
|
||||
case DATA_PARSE_INCOMPLETE:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received frame is incomplete\n"));
|
||||
break;
|
||||
case GCM_AUTH_FAILED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decrypt authentication failed\n"));
|
||||
break;
|
||||
case GCM_ENCRYPTION_KEY_FAILED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Setting decryption key failed\n"));
|
||||
break;
|
||||
case GCM_DECRYPT_FAILED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decryption failed\n"));
|
||||
break;
|
||||
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame length mismatch\n"));
|
||||
break;
|
||||
case DATA_PARSE_INTERMEDIATE_SEGMENT:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Intermediate segment received\n"));
|
||||
break;
|
||||
case DATA_PARSE_UNKNOWN_DATA:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
|
||||
break;
|
||||
default:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive) {
|
||||
int8_t rxpin = meterConfig.rxPin;
|
||||
int8_t txpin = passive ? -1 : meterConfig.txPin;
|
||||
|
||||
if(baud == 0) {
|
||||
baud = 2400;
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d and parity %d\n"), rxpin, txpin, baud, parityOrdinal);
|
||||
|
||||
if(parityOrdinal == 0) {
|
||||
parityOrdinal = 3; // 8N1
|
||||
}
|
||||
|
||||
if(rxpin == 3 || rxpin == 113) {
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
hwSerial = &Serial0;
|
||||
#else
|
||||
hwSerial = &Serial;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t uart_num = 0;
|
||||
#if defined(ESP32)
|
||||
hwSerial = &Serial1;
|
||||
uart_num = UART_NUM_1;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
if(rxpin == 16) {
|
||||
hwSerial = &Serial2;
|
||||
uart_num = UART_NUM_2;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if(rxpin == 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(meterConfig.bufferSize < 1) meterConfig.bufferSize = 1;
|
||||
if(meterConfig.bufferSize > 64) meterConfig.bufferSize = 64;
|
||||
|
||||
if(hwSerial != NULL) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Hardware serial\n"));
|
||||
Serial.flush();
|
||||
#if defined(ESP8266)
|
||||
SerialConfig serialConfig;
|
||||
#elif defined(ESP32)
|
||||
uint32_t serialConfig;
|
||||
#endif
|
||||
switch(parityOrdinal) {
|
||||
case 2:
|
||||
serialConfig = SERIAL_7N1;
|
||||
break;
|
||||
case 3:
|
||||
serialConfig = SERIAL_8N1;
|
||||
break;
|
||||
case 7:
|
||||
serialConfig = SERIAL_8N2;
|
||||
break;
|
||||
case 10:
|
||||
serialConfig = SERIAL_7E1;
|
||||
break;
|
||||
default:
|
||||
serialConfig = SERIAL_8E1;
|
||||
break;
|
||||
}
|
||||
if(meterConfig.bufferSize < 4) meterConfig.bufferSize = 4; // 64 bytes (1) is default for software serial, 256 bytes (4) for hardware
|
||||
|
||||
hwSerial->setRxBufferSize(64 * meterConfig.bufferSize);
|
||||
#if defined(ESP32)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
uart_set_pin(uart_num, txpin, rxpin, -1, -1);
|
||||
#else
|
||||
hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert);
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(rxpin == 3) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
|
||||
Serial.pins(1,3);
|
||||
} else if(rxpin == 113) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
|
||||
Serial.pins(15,13);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Prevent pullup on TX pin if not uart0
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
if(txpin != 17) pinMode(17, INPUT);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
if(txpin != 7) pinMode(7, INPUT);
|
||||
#elif defined(ESP32)
|
||||
if(rxpin == 9 && txpin != 10) {
|
||||
pinMode(10, INPUT);
|
||||
} else if(rxpin == 16 && txpin != 17) {
|
||||
pinMode(17, INPUT);
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
if(rxpin == 113) {
|
||||
pinMode(15, INPUT);
|
||||
}
|
||||
#endif
|
||||
|
||||
hanSerial = hwSerial;
|
||||
#if defined(ESP8266)
|
||||
if(swSerial != NULL) {
|
||||
swSerial->end();
|
||||
delete swSerial;
|
||||
swSerial = NULL;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#if defined(ESP8266)
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial\n"));
|
||||
Serial.flush();
|
||||
|
||||
if(swSerial == NULL) {
|
||||
swSerial = new SoftwareSerial(rxpin, txpin, invert);
|
||||
} else {
|
||||
swSerial->end();
|
||||
}
|
||||
|
||||
SoftwareSerialConfig serialConfig;
|
||||
switch(parityOrdinal) {
|
||||
case 2:
|
||||
serialConfig = SWSERIAL_7N1;
|
||||
break;
|
||||
case 3:
|
||||
serialConfig = SWSERIAL_8N1;
|
||||
break;
|
||||
case 7:
|
||||
serialConfig = SWSERIAL_8N2;
|
||||
break;
|
||||
case 10:
|
||||
serialConfig = SWSERIAL_7E1;
|
||||
break;
|
||||
default:
|
||||
serialConfig = SWSERIAL_8E1;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t bufferSize = meterConfig.bufferSize;
|
||||
#if defined(ESP8266)
|
||||
if(bufferSize > 2) bufferSize = 2;
|
||||
#endif
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Using serial buffer size %d\n"), 64 * bufferSize);
|
||||
swSerial->begin(baud, serialConfig, rxpin, txpin, invert, meterConfig.bufferSize * 64, meterConfig.bufferSize * 64);
|
||||
hanSerial = swSerial;
|
||||
hwSerial = NULL;
|
||||
#else
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial not available\n"));
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
if(hanBuffer != NULL) {
|
||||
free(hanBuffer);
|
||||
}
|
||||
hanBufferSize = max(64 * meterConfig.bufferSize * 3, 512);
|
||||
hanBuffer = (uint8_t*) malloc(hanBufferSize);
|
||||
|
||||
// The library automatically sets the pullup in Serial.begin()
|
||||
if(!meterConfig.rxPinPullup) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
|
||||
pinMode(meterConfig.rxPin, INPUT);
|
||||
}
|
||||
|
||||
if(meterConfig.txPin != 0xFF && passive) {
|
||||
pinMode(meterConfig.txPin, OUTPUT);
|
||||
digitalWrite(meterConfig.txPin, LOW);
|
||||
}
|
||||
|
||||
hanSerial->setTimeout(250);
|
||||
|
||||
// Empty buffer before starting
|
||||
while (hanSerial->available() > 0) {
|
||||
hanSerial->read();
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
if(hwSerial != NULL) {
|
||||
hwSerial->hasOverrun();
|
||||
} else if(swSerial != NULL) {
|
||||
swSerial->overflow();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
HardwareSerial* PassiveMeterCommunicator::getHwSerial() {
|
||||
return hwSerial;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::rxerr(int err) {
|
||||
if(err == 0) return;
|
||||
switch(err) {
|
||||
case 2:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial buffer overflow\n"));
|
||||
rxBufferErrors++;
|
||||
if(rxBufferErrors > 1 && meterConfig.bufferSize < 8) {
|
||||
meterConfig.bufferSize += 2;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
||||
configChanged = true;
|
||||
rxBufferErrors = 0;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial FIFO overflow\n"));
|
||||
break;
|
||||
case 4:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial frame error\n"));
|
||||
break;
|
||||
case 5:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial parity error\n"));
|
||||
unsigned long now = millis();
|
||||
if(now - meterAutodetectLastChange < 120000) {
|
||||
switch(autodetectParity) {
|
||||
case 2: // 7N1
|
||||
autodetectParity = 10;
|
||||
break;
|
||||
case 10: // 7E1
|
||||
autodetectParity = 6;
|
||||
break;
|
||||
case 6: // 7N2
|
||||
autodetectParity = 14;
|
||||
break;
|
||||
case 14: // 7E2
|
||||
autodetectParity = 2;
|
||||
break;
|
||||
|
||||
case 3: // 8N1
|
||||
autodetectParity = 11;
|
||||
break;
|
||||
case 11: // 8E1
|
||||
autodetectParity = 7;
|
||||
break;
|
||||
case 7: // 8N2
|
||||
autodetectParity = 15;
|
||||
break;
|
||||
case 15: // 8E2
|
||||
autodetectParity = 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
autodetectParity = 3;
|
||||
break;
|
||||
}
|
||||
if(validDataReceived) {
|
||||
meterConfig.parity = autodetectParity;
|
||||
configChanged = true;
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Do not include serial break
|
||||
if(err > 1) lastError = 90+err;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::handleAutodetect(unsigned long now) {
|
||||
if(!autodetect) return;
|
||||
|
||||
if(!validDataReceived) {
|
||||
if(now - meterAutodetectLastChange > 20000 && (meterConfig.baud == 0 || meterConfig.parity == 0)) {
|
||||
autodetect = true;
|
||||
if(autodetectCount == 2) {
|
||||
autodetectInvert = !autodetectInvert;
|
||||
autodetectCount = 0;
|
||||
}
|
||||
autodetectBaud = AUTO_BAUD_RATES[autodetectCount++];
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
|
||||
meterConfig.bufferSize = max((uint32_t) 1, autodetectBaud / 14400);
|
||||
setupHanPort(autodetectBaud, autodetectParity, autodetectInvert);
|
||||
meterAutodetectLastChange = now;
|
||||
}
|
||||
} else if(autodetect) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
|
||||
autodetect = false;
|
||||
meterConfig.baud = autodetectBaud;
|
||||
meterConfig.parity = autodetectParity;
|
||||
meterConfig.invert = autodetectInvert;
|
||||
configChanged = true;
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
||||
}
|
||||
}
|
||||
87
lib/MeterCommunicators/src/PulseMeterCommunicator.cpp
Normal file
87
lib/MeterCommunicators/src/PulseMeterCommunicator.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PulseMeterCommunicator.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PulseMeterCommunicator::PulseMeterCommunicator(RemoteDebug* debugger) {
|
||||
#else
|
||||
PulseMeterCommunicator::PulseMeterCommunicator(Stream* debugger) {
|
||||
#endif
|
||||
this->debugger = debugger;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
|
||||
this->meterConfig = meterConfig;
|
||||
this->configChanged = false;
|
||||
this->tz = tz;
|
||||
setupGpio();
|
||||
}
|
||||
|
||||
bool PulseMeterCommunicator::loop() {
|
||||
return updated || !initialized;
|
||||
}
|
||||
|
||||
AmsData* PulseMeterCommunicator::getData(AmsData& meterState) {
|
||||
if(!initialized) {
|
||||
state.apply(meterState);
|
||||
initialized = true;
|
||||
return NULL;
|
||||
}
|
||||
updated = false;
|
||||
|
||||
AmsData* ret = new AmsData();
|
||||
ret->apply(state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int PulseMeterCommunicator::getLastError() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool PulseMeterCommunicator::isConfigChanged() {
|
||||
return this->configChanged;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::setupGpio() {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Setting up Pulse Meter GPIO, rx: %d, tx: %d\n"), meterConfig.rxPin, meterConfig.txPin);
|
||||
if(meterConfig.rxPin != NOT_A_PIN) {
|
||||
pinMode(meterConfig.rxPin, meterConfig.rxPinPullup ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
// Export counter?
|
||||
// if(meterConfig.txPin != NOT_A_PIN) {
|
||||
// pinMode(meterConfig.txPin, meterConfig.rxPinPullup ? INPUT_PULLUP : INPUT);
|
||||
// }
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::onPulse(uint8_t pulses) {
|
||||
uint64_t now = millis64();
|
||||
if(initialized && pulses == 0) {
|
||||
if(now - lastUpdate > 10000) {
|
||||
ImpulseAmsData update(state, meterConfig.baud, pulses);
|
||||
state.apply(update);
|
||||
updated = true;
|
||||
lastUpdate = now;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImpulseAmsData update(state, meterConfig.baud, pulses);
|
||||
state.apply(update);
|
||||
updated = true;
|
||||
lastUpdate = now;
|
||||
}
|
||||
36
lib/PassthroughMqttHandler/include/PassthroughMqttHandler.h
Normal file
36
lib/PassthroughMqttHandler/include/PassthroughMqttHandler.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PASSTHROUGHMQTTHANDLER_H
|
||||
#define _PASSTHROUGHMQTTHANDLER_H
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
|
||||
class PassthroughMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PassthroughMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
#else
|
||||
PassthroughMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
#endif
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishBytes(uint8_t* buf, uint16_t len);
|
||||
bool publishString(char* str);
|
||||
|
||||
private:
|
||||
String topic;
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
uint8_t getFormat();
|
||||
};
|
||||
#endif
|
||||
45
lib/PassthroughMqttHandler/src/PassthroughMqttHandler.cpp
Normal file
45
lib/PassthroughMqttHandler/src/PassthroughMqttHandler.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PassthroughMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
bool PassthroughMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PassthroughMqttHandler::publishTemperatures(AmsConfiguration*, HwTools*) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PassthroughMqttHandler::publishPrices(PriceService*) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PassthroughMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PassthroughMqttHandler::publishBytes(uint8_t* buf, uint16_t len) {
|
||||
mqtt.publish(topic.c_str(), toHex(buf, len));
|
||||
bool ret = mqtt.loop();
|
||||
delay(10);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool PassthroughMqttHandler::publishString(char* str) {
|
||||
mqtt.publish(topic.c_str(), str);
|
||||
bool ret = mqtt.loop();
|
||||
delay(10);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t PassthroughMqttHandler::getFormat() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
void PassthroughMqttHandler::onMessage(String &topic, String &payload) {
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DNBCURRPARSER_H
|
||||
#define _DNBCURRPARSER_H
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ENTSOEA44PARSER_H
|
||||
#define _ENTSOEA44PARSER_H
|
||||
|
||||
@@ -10,8 +16,6 @@
|
||||
#define DOCPOS_POSITION 3
|
||||
#define DOCPOS_AMOUNT 4
|
||||
|
||||
#define ENTSOE_NO_VALUE -127
|
||||
|
||||
class EntsoeA44Parser: public Stream {
|
||||
public:
|
||||
EntsoeA44Parser();
|
||||
135
lib/PriceService/include/PriceService.h
Normal file
135
lib/PriceService/include/PriceService.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PRICESERVICE_H
|
||||
#define _PRICESERVICE_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "TimeLib.h"
|
||||
#include "Timezone.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EntsoeA44Parser.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
#define SSL_BUF_SIZE 512
|
||||
|
||||
#define PRICE_DIRECTION_IMPORT 0x01
|
||||
#define PRICE_DIRECTION_EXPORT 0x02
|
||||
#define PRICE_DIRECTION_BOTH 0x03
|
||||
|
||||
#define PRICE_DAY_MO 0x01
|
||||
#define PRICE_DAY_TU 0x02
|
||||
#define PRICE_DAY_WE 0x04
|
||||
#define PRICE_DAY_TH 0x08
|
||||
#define PRICE_DAY_FR 0x10
|
||||
#define PRICE_DAY_SA 0x12
|
||||
#define PRICE_DAY_SU 0x14
|
||||
|
||||
#define PRICE_TYPE_FIXED 0x00
|
||||
#define PRICE_TYPE_ADD 0x01
|
||||
#define PRICE_TYPE_PCT 0x02
|
||||
#define PRICE_TYPE_SUBTRACT 0x03
|
||||
|
||||
struct PriceConfig {
|
||||
char name[32];
|
||||
uint8_t direction;
|
||||
uint8_t days;
|
||||
uint32_t hours;
|
||||
uint8_t type;
|
||||
uint32_t value;
|
||||
uint8_t start_month;
|
||||
uint8_t start_dayofmonth;
|
||||
uint8_t end_month;
|
||||
uint8_t end_dayofmonth;
|
||||
};
|
||||
|
||||
struct PricePart {
|
||||
char name[32];
|
||||
char description[32];
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
class PriceService {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PriceService(RemoteDebug*);
|
||||
#else
|
||||
PriceService(Stream*);
|
||||
#endif
|
||||
void setup(PriceServiceConfig&);
|
||||
bool loop();
|
||||
|
||||
char* getToken();
|
||||
char* getCurrency();
|
||||
char* getArea();
|
||||
char* getSource();
|
||||
float getValueForHour(uint8_t direction, int8_t hour);
|
||||
float getValueForHour(uint8_t direction, time_t ts, int8_t hour);
|
||||
|
||||
float getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t hour);
|
||||
|
||||
std::vector<PriceConfig>& getPriceConfig();
|
||||
void setPriceConfig(uint8_t index, PriceConfig &priceConfig);
|
||||
void cropPriceConfig(uint8_t size);
|
||||
|
||||
PricePart getPricePart(uint8_t index);
|
||||
|
||||
int16_t getLastError();
|
||||
|
||||
bool load();
|
||||
bool save();
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
PriceServiceConfig* config = NULL;
|
||||
HTTPClient* http = NULL;
|
||||
|
||||
uint8_t currentDay = 0, currentHour = 0;
|
||||
uint8_t tomorrowFetchMinute = 15; // How many minutes over 13:00 should it fetch prices
|
||||
uint8_t nextFetchDelayMinutes = 15;
|
||||
uint64_t lastTodayFetch = 0;
|
||||
uint64_t lastTomorrowFetch = 0;
|
||||
uint64_t lastCurrencyFetch = 0;
|
||||
PricesContainer* today = NULL;
|
||||
PricesContainer* tomorrow = NULL;
|
||||
|
||||
std::vector<PriceConfig> priceConfig;
|
||||
|
||||
Timezone* tz = NULL;
|
||||
|
||||
static const uint16_t BufferSize = 256;
|
||||
char* buf;
|
||||
|
||||
bool hub = false;
|
||||
uint8_t* key = NULL;
|
||||
uint8_t* auth = NULL;
|
||||
|
||||
float currencyMultiplier = 0;
|
||||
|
||||
int16_t lastError = 0;
|
||||
|
||||
PricesContainer* fetchPrices(time_t);
|
||||
bool retrieve(const char* url, Stream* doc);
|
||||
float getCurrencyMultiplier(const char* from, const char* to, time_t t);
|
||||
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
};
|
||||
#endif
|
||||
@@ -1,5 +1,14 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PRICESCONTAINER_H
|
||||
#define _PRICESCONTAINER_H
|
||||
|
||||
#define PRICE_NO_VALUE -127
|
||||
|
||||
struct PricesContainer {
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DnbCurrParser.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EntsoeA44Parser.h"
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
EntsoeA44Parser::EntsoeA44Parser() {
|
||||
for(int i = 0; i < 25; i++) points[i] = ENTSOE_NO_VALUE;
|
||||
for(int i = 0; i < 25; i++) points[i] = PRICE_NO_VALUE;
|
||||
}
|
||||
|
||||
EntsoeA44Parser::~EntsoeA44Parser() {
|
||||
@@ -18,7 +24,7 @@ char* EntsoeA44Parser::getMeasurementUnit() {
|
||||
}
|
||||
|
||||
float EntsoeA44Parser::getPoint(uint8_t position) {
|
||||
if(position >= 25) return ENTSOE_NO_VALUE;
|
||||
if(position >= 25) return PRICE_NO_VALUE;
|
||||
return points[position];
|
||||
}
|
||||
|
||||
@@ -115,6 +121,6 @@ void EntsoeA44Parser::get(PricesContainer* container) {
|
||||
strcpy(container->source, "EOE");
|
||||
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
container->points[i] = points[i] == ENTSOE_NO_VALUE ? ENTSOE_NO_VALUE : points[i] * 10000;
|
||||
container->points[i] = points[i] == PRICE_NO_VALUE ? PRICE_NO_VALUE : points[i] * 10000;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user