mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 13:05:31 +00:00
Compare commits
439 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b4884652f | ||
|
|
82aeae8699 | ||
|
|
a7333653b0 | ||
|
|
24e63d5e32 | ||
|
|
eb7c266378 | ||
|
|
cf8c48ab99 | ||
|
|
78a1cd78ea | ||
|
|
fdfa6c1b52 | ||
|
|
4f1790a464 | ||
|
|
ca4cef5233 | ||
|
|
a0d7fd0d95 | ||
|
|
489dbf9254 | ||
|
|
a81aa11558 | ||
|
|
2e4a0fc0a3 | ||
|
|
fc6e9e8085 | ||
|
|
ad73821f1c | ||
|
|
98bf5b958f | ||
|
|
f323c5a4f6 | ||
|
|
ea91248e67 | ||
|
|
271ce2081f | ||
|
|
8438020dbd | ||
|
|
9252a810df | ||
|
|
c0c696a55c | ||
|
|
ef70d39f70 | ||
|
|
1cf890dc26 | ||
|
|
9d307e3192 | ||
|
|
61f0356a10 | ||
|
|
c648546b61 | ||
|
|
ffd8d46f2e | ||
|
|
eefbc08222 | ||
|
|
1a5b9542f4 | ||
|
|
19ff70782f | ||
|
|
0dfd2d9022 | ||
|
|
7a4ab77a83 | ||
|
|
46cd8c6e68 | ||
|
|
c307103605 | ||
|
|
d9ec111458 | ||
|
|
e3a1aa78a9 | ||
|
|
b06aa5f71b | ||
|
|
6a75b0fe71 | ||
|
|
e11fac3d11 | ||
|
|
031422f783 | ||
|
|
2ff8fddc14 | ||
|
|
e5d260ae3e | ||
|
|
633671851e | ||
|
|
69da9f9d48 | ||
|
|
19f78126d6 | ||
|
|
d3cc92949a | ||
|
|
f1089faab5 | ||
|
|
4a3ad6ab9b | ||
|
|
86449949c5 | ||
|
|
9bd9826835 | ||
|
|
94ff9d6da7 | ||
|
|
9518d1811b | ||
|
|
983426b36c | ||
|
|
bcb3c3b2ec | ||
|
|
9fd383c1ef | ||
|
|
a931f4cef8 | ||
|
|
fddecaab39 | ||
|
|
e5eab82d68 | ||
|
|
8ae1d46b2a | ||
|
|
99ccb03b45 | ||
|
|
b8f2d501a5 | ||
|
|
e042806619 | ||
|
|
16f9ed7ecb | ||
|
|
3eaefefd26 | ||
|
|
03c8c3ddbc | ||
|
|
08371b9078 | ||
|
|
a6ae25f3e7 | ||
|
|
8051db6a9b | ||
|
|
d0bfdae5d8 | ||
|
|
4d681ed2e2 | ||
|
|
8ee3f53714 | ||
|
|
e8cf8a98ed | ||
|
|
9153a98694 | ||
|
|
37aa6ae816 | ||
|
|
8a35346fcf | ||
|
|
792ae4c935 | ||
|
|
a7324d828a | ||
|
|
fe739c51d3 | ||
|
|
795d2d0375 | ||
|
|
5ef36a91f6 | ||
|
|
8491d6c471 | ||
|
|
f95f22058a | ||
|
|
b5c45cebfa | ||
|
|
19a953b269 | ||
|
|
6ae970ff68 | ||
|
|
0f0ee82af9 | ||
|
|
d84b9351e1 | ||
|
|
6668258b66 | ||
|
|
9c42aab04f | ||
|
|
c771870e3e | ||
|
|
fb59ee52c1 | ||
|
|
3483910136 | ||
|
|
13e70f7bd4 | ||
|
|
210001e232 | ||
|
|
094e588ad5 | ||
|
|
f9d0cdfa47 | ||
|
|
0476058958 | ||
|
|
3e337a5639 | ||
|
|
bf0e1d1bf3 | ||
|
|
d3b65b0175 | ||
|
|
2caa7252a0 | ||
|
|
f7596de166 | ||
|
|
f5178459e8 | ||
|
|
a55f7dc66a | ||
|
|
3d9cad8953 | ||
|
|
67535b2792 | ||
|
|
2b5f3f50df | ||
|
|
111807a743 | ||
|
|
348ba8cfc4 | ||
|
|
81c72f0ca3 | ||
|
|
20de294a8c | ||
|
|
e31faaa726 | ||
|
|
4ad2921132 | ||
|
|
1fa62fa97c | ||
|
|
affa66f78f | ||
|
|
84860bc684 | ||
|
|
0cadedf9df | ||
|
|
0c3eea8d37 | ||
|
|
1d4e7e564d | ||
|
|
067aa0c6cb | ||
|
|
3d540e2a65 | ||
|
|
61d4728ffa | ||
|
|
42e0ca963c | ||
|
|
3bc40cb136 | ||
|
|
1453bbbb7e | ||
|
|
9da2d0760e | ||
|
|
18af98511a | ||
|
|
40c84d2429 | ||
|
|
c0d95f918e | ||
|
|
0947d335ce | ||
|
|
43e2b2478b | ||
|
|
a5fc1f0cbe | ||
|
|
bf3059ba04 | ||
|
|
319b534d4f | ||
|
|
b7c2510098 | ||
|
|
07205b8008 | ||
|
|
165a385844 | ||
|
|
fa76233151 | ||
|
|
22b7877ee9 | ||
|
|
bd9a1bb330 | ||
|
|
1b5efaa937 | ||
|
|
05ce4c5c1a | ||
|
|
e022f630b2 | ||
|
|
a73ff87653 | ||
|
|
70d6a8699b | ||
|
|
118c633878 | ||
|
|
fa5985f60b | ||
|
|
c1309e9a13 | ||
|
|
718eef7999 | ||
|
|
afdd282adf | ||
|
|
1302f18fab | ||
|
|
fc89670ea4 | ||
|
|
225320ea7d | ||
|
|
b2cf3bb0c4 | ||
|
|
e36a8ade42 | ||
|
|
2ef67d92c4 | ||
|
|
1c9af6b1c2 | ||
|
|
4f47ffa21e | ||
|
|
3d1026f829 | ||
|
|
af6ecc5d47 | ||
|
|
025c556626 | ||
|
|
0b342d6c49 | ||
|
|
ada57b1052 | ||
|
|
cf773985b1 | ||
|
|
75a14ad9cb | ||
|
|
fbe90cbc5a | ||
|
|
f0bc895952 | ||
|
|
f5c10c1a50 | ||
|
|
c4736c730e | ||
|
|
d2c96c733c | ||
|
|
d0dd9de4d7 | ||
|
|
159244974d | ||
|
|
933926fbb4 | ||
|
|
2c95f036ba | ||
|
|
c5c0b52eb7 | ||
|
|
561b62cafe | ||
|
|
55520cd7f6 | ||
|
|
e27d41839e | ||
|
|
569a0ddfaa | ||
|
|
de63263dbb | ||
|
|
8adb7055c0 | ||
|
|
11e09c5b56 | ||
|
|
ac32f74eba | ||
|
|
d49b8c3839 | ||
|
|
054fd43a0d | ||
|
|
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 | ||
|
|
c1d1bf6bb5 | ||
|
|
fc09ab5cc9 | ||
|
|
6e3a6f71e2 | ||
|
|
557ba659d5 | ||
|
|
32ad71bba6 | ||
|
|
8816097bca | ||
|
|
378b67a5bd | ||
|
|
859868c99b | ||
|
|
d7ca741c92 | ||
|
|
d49753ed33 | ||
|
|
2ae15ac13a | ||
|
|
00278659f8 | ||
|
|
0c1525b018 | ||
|
|
ed778441d5 | ||
|
|
c43c07386a | ||
|
|
91958f5464 | ||
|
|
194905237b | ||
|
|
f3805ad111 | ||
|
|
0f22fd561e | ||
|
|
ee462ec468 | ||
|
|
ed899440ed | ||
|
|
111bd70763 | ||
|
|
afa013ca03 | ||
|
|
56b6a720c9 | ||
|
|
76d76844a9 | ||
|
|
ae82914795 | ||
|
|
0fa178a617 | ||
|
|
8cada69aaf | ||
|
|
7cbcbd4bc8 |
31
.devcontainer/devcontainer.json
Normal file
31
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,31 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
|
||||
{
|
||||
"name": "amsreader-devcontainer",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/base:jammy",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "19",
|
||||
"pnpmVersion": "none",
|
||||
"nvmVersion": "latest"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/python:1": {
|
||||
"version": "3.9"
|
||||
}
|
||||
},
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": ".devcontainer/postCreateCommand.sh",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"platformio.platformio-ide",
|
||||
"ms-vscode.cpptools",
|
||||
"svelte.svelte-vscode"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
18
.devcontainer/postCreateCommand.sh
Executable file
18
.devcontainer/postCreateCommand.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Upgrade pip
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
# Install Python packages
|
||||
pip install -U platformio css_html_js_minify
|
||||
|
||||
# Navigate to the Svelte app directory
|
||||
cd lib/SvelteUi/app
|
||||
|
||||
# Install npm dependencies and build the app
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
# Return to the previous directory
|
||||
cd -
|
||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -22,27 +22,26 @@ 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_AMS2MQTT_SC_KEY/AMS2MQTT_SC_KEY=\\"${{secrets.AMS2MQTT_SC_KEY}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
|
||||
- name: Cache PlatformIO dependencies
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.pio/libdeps
|
||||
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
@@ -50,9 +49,9 @@ 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'
|
||||
node-version: '19.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
@@ -62,6 +61,6 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
run: pio pkg install
|
||||
- name: PlatformIO run
|
||||
run: pio run
|
||||
|
||||
41
.github/workflows/localazy.yml
vendored
Normal file
41
.github/workflows/localazy.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Deploy language files from localazy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.x'
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: eu-north-1
|
||||
|
||||
- name: Generate localazy-keys.json
|
||||
run: |
|
||||
echo '{"writeKey": "", "readKey": "${{secrets.LOCALAZY_READ_KEY}}"}' > localazy/localazy-keys.json
|
||||
|
||||
- name: Create localazy language folder
|
||||
run: mkdir -p localazy/language
|
||||
|
||||
- name: Install Localazy CLI
|
||||
run: npm install -g @localazy/cli
|
||||
|
||||
- name: Download translations
|
||||
working-directory: localazy
|
||||
run: localazy download -k localazy-keys.json
|
||||
|
||||
- name: Upload translations to S3
|
||||
run: aws s3 sync ./localazy/language/ s3://${{ secrets.AWS_S3_BUCKET }}/language/
|
||||
85
.github/workflows/prerelease.yml
vendored
Normal file
85
.github/workflows/prerelease.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Release candidate build and upload
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+'
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Get release version for filenames
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
|
||||
|
||||
- name: Create release with release notes
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: Release candidate v${{ steps.release_tag.outputs.tag }}
|
||||
prerelease: true
|
||||
|
||||
outputs:
|
||||
version: ${{ steps.release_tag.outputs.tag }}
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
esp32s2:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s2
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32s3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32c3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32c3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32solo:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32solo
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp8266:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp8266
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
is_esp32: false
|
||||
136
.github/workflows/release-deploy-env.yml
vendored
Normal file
136
.github/workflows/release-deploy-env.yml
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
name: Build with env and deploy
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
env:
|
||||
description: 'The environment to build for'
|
||||
required: true
|
||||
type: string
|
||||
upload_url:
|
||||
description: 'The upload URL for the release assets'
|
||||
required: true
|
||||
type: string
|
||||
version:
|
||||
description: 'The version tag for the release assets'
|
||||
required: true
|
||||
type: string
|
||||
subfolder:
|
||||
description: 'The subfolder in S3 to upload the binary to'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
is_esp32:
|
||||
description: 'Whether the build is for ESP32 based firmware'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: eu-north-1
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache PlatformIO dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.pio/libdeps
|
||||
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_SC_KEY/AMS2MQTT_SC_KEY=\\"${{secrets.AMS2MQTT_SC_KEY}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '19.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: false
|
||||
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
|
||||
- name: Build firmware
|
||||
env:
|
||||
GITHUB_TAG: v${{ inputs.version }}
|
||||
run: pio run -e ${{ inputs.env }}
|
||||
|
||||
- name: Create zip file
|
||||
run: /bin/sh scripts/${{ inputs.env }}/mkzip.sh
|
||||
|
||||
- name: Upload binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ inputs.upload_url }}
|
||||
asset_path: .pio/build/${{ inputs.env }}/firmware.bin
|
||||
asset_name: ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ inputs.upload_url }}
|
||||
asset_path: ${{ inputs.env }}.zip
|
||||
asset_name: ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Create MD5 checksum file
|
||||
run: md5sum .pio/build/${{ inputs.env }}/firmware.bin | cut -z -d ' ' -f 1 > firmware.md5
|
||||
|
||||
- name: Upload binary to S3
|
||||
run: aws s3 cp .pio/build/${{ inputs.env }}/firmware.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.bin
|
||||
|
||||
- name: Upload MD5 checksum to S3
|
||||
run: aws s3 cp firmware.md5 s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.md5
|
||||
|
||||
- name: Upload bootloader to S3
|
||||
if: ${{ inputs.is_esp32 }}
|
||||
run: aws s3 cp .pio/build/${{ inputs.env }}/bootloader.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}-bootloader.bin
|
||||
|
||||
- name: Upload partition table to S3
|
||||
if: ${{ inputs.is_esp32 }}
|
||||
run: aws s3 cp .pio/build/${{ inputs.env }}/partitions.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}-partitions.bin
|
||||
|
||||
- name: Upload app0 to S3
|
||||
if: ${{ inputs.is_esp32 }}
|
||||
run: aws s3 cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}-app0.bin
|
||||
250
.github/workflows/release.yml
vendored
250
.github/workflows/release.yml
vendored
@@ -1,193 +1,79 @@
|
||||
name: Release
|
||||
name: Release build and upload
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v1
|
||||
- name: Get release version for filenames
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
|
||||
- name: Get release version for code
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo "GITHUB_TAG=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Get release version for filenames
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
|
||||
|
||||
- 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: Create release with release notes
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: Release v${{ steps.release_tag.outputs.tag }}
|
||||
generateReleaseNotes: true
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
|
||||
- name: Cache PlatformIO dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.pio/libdeps
|
||||
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
outputs:
|
||||
version: ${{ steps.release_tag.outputs.tag }}
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: false
|
||||
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Build esp8266 firmware
|
||||
run: pio run -e esp8266
|
||||
- name: Create esp8266 zip file
|
||||
run: /bin/sh scripts/esp8266/mkzip.sh
|
||||
- name: Upload esp8266 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp8266/firmware.bin
|
||||
asset_name: ams2mqtt-esp8266-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp8266 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp8266.zip
|
||||
asset_name: ams2mqtt-esp8266-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32 firmware
|
||||
run: pio run -e esp32
|
||||
- name: Create esp32 zip file
|
||||
run: /bin/sh scripts/esp32/mkzip.sh
|
||||
- name: Upload esp32 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32/firmware.bin
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32.zip
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32s2 firmware
|
||||
run: pio run -e esp32s2
|
||||
- name: Create esp32s2 zip file
|
||||
run: /bin/sh scripts/esp32s2/mkzip.sh
|
||||
- name: Upload esp32s2 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32s2/firmware.bin
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32s2 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32s2.zip
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32solo firmware
|
||||
run: pio run -e esp32solo
|
||||
- name: Create esp32solo zip file
|
||||
run: /bin/sh scripts/esp32solo/mkzip.sh
|
||||
- name: Upload esp32solo 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/esp32solo/firmware.bin
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32solo zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32solo.zip
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32c3 firmware
|
||||
run: pio run -e esp32c3
|
||||
- name: Create esp32c3 zip file
|
||||
run: /bin/sh scripts/esp32c3/mkzip.sh
|
||||
- name: Upload esp32c3 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/esp32c3/firmware.bin
|
||||
asset_name: ams2mqtt-esp32c3-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32c3 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: esp32c3.zip
|
||||
asset_name: ams2mqtt-esp32c3-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
esp32s2:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s2
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32s3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32c3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32c3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32solo:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32solo
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp8266:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp8266
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
is_esp32: false
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.vs/
|
||||
.idea/
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
**/__vm/
|
||||
@@ -18,3 +19,5 @@ platformio-user.ini
|
||||
node_modules
|
||||
/gui/dist
|
||||
/scripts/*dev
|
||||
localazy-keys.json
|
||||
localazy/language
|
||||
|
||||
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
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
# AMS Reader
|
||||
This code is designed to decode data from electric smart meters installed in many countries in Europe these days. The data is presented in a graphical web interface and can also send the data to a MQTT broker which makes it suitable for home automation project. Originally it was only designed to work with Norwegian meters, but has since been adapter to read any IEC-62056-7-5 or IEC-62056-21 compliant meters.
|
||||
|
||||
Later development have added Energy usage graph for both day and month, as well as future energy price. The code can run on any ESP8266 or ESP32 hardware which you can read more about in the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki). If you don't have the knowledge to set up a ESP device yourself, or you would like to support our work, please have a look at our shop at [amsleser.no](https://amsleser.no/).
|
||||
Later development have added Energy usage graph for both day and month, as well as future energy price. The code can run on any ESP8266 or ESP32 hardware which you can read more about in the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki). If you don't have the knowledge to set up a ESP device yourself, or you would like to support our work, please have a look at our shop at [amsleser.no](https://www.amsleser.no/).
|
||||
|
||||
|
||||
<img src="images/dashboard.png">
|
||||
|
||||
Go to the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki) for information on how to get your own device! And find the latest prebuilt firmware file at the [release section](https://github.com/UtilitechAS/amsreader-firmware/releases).
|
||||
## Installing pre-built firmware
|
||||
If you have a device already running this firmware and you for some reason need to upgrade via USB port, you can use a [this web-based tool](https://www.amsleser.cloud/flasher)
|
||||
|
||||
If you are using a development board and want to flash a pre-built firmware manually, get the necessary files from the [release](https://github.com/UtilitechAS/amsreader-firmware/releases) section and visit the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki) and have a look at the [Flashing](https://github.com/UtilitechAS/amsreader-firmware/wiki/flashinghttps://github.com/UtilitechAS/amsreader-firmware/wiki/flashing) section
|
||||
|
||||
## Building this project with PlatformIO
|
||||
To build this project, you need [PlatformIO](https://platformio.org/) installed.
|
||||
|
||||
7
custom_partition.csv
Normal file
7
custom_partition.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x1D0000,
|
||||
app1, app, ota_1, 0x1E0000,0x1D0000,
|
||||
spiffs, data, spiffs, 0x3B0000,0x40000,
|
||||
coredump, data, coredump,0x3F0000,0x10000,
|
||||
|
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.
BIN
doc/Norway/Norwegian_payload_breakdown.docx
Normal file
BIN
doc/Norway/Norwegian_payload_breakdown.docx
Normal file
Binary file not shown.
BIN
doc/Poland/Stoen/HAN_Interface_Stoen_Operator_EN.pdf
Normal file
BIN
doc/Poland/Stoen/HAN_Interface_Stoen_Operator_EN.pdf
Normal file
Binary file not shown.
BIN
doc/Poland/Stoen/interfejs-sieci-han-w-stoen-operator-v1-0-1.pdf
Normal file
BIN
doc/Poland/Stoen/interfejs-sieci-han-w-stoen-operator-v1-0-1.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
doc/Poland/Stoen/opis-profili-rejestrowane-kody-obis-v1-0-2.pdf
Normal file
BIN
doc/Poland/Stoen/opis-profili-rejestrowane-kody-obis-v1-0-2.pdf
Normal file
Binary file not shown.
1
doc/Poland/Stoen/placeholder.txt
Normal file
1
doc/Poland/Stoen/placeholder.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -31,3 +31,59 @@ Object with three values Value Object with two values
|
||||
02 03 09 06 01 00 04 08 00 FF 06 00 0A F5 EC 02 02 0F 01 16 20
|
||||
51 D7
|
||||
7E
|
||||
|
||||
|
||||
Received 21.09.24 20:00:10:
|
||||
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
|
||||
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 02 71 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 01 F0 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 00 06 02 02 0F FF 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 05 02 02 0F FF 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 17 02 02
|
||||
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 3F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 09 36 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 22 02 02 0F FF 16 23 02 02 09
|
||||
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 09 15 06 13 00
|
||||
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
|
||||
(V) 00 8D 98 13 02 02 0F 01 16 1E 02 03 09 06 01 00
|
||||
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
|
||||
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 6E 02 02
|
||||
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
|
||||
(V) 13 F2 06 02 02 0F 01 16 20 C8 1D 7E
|
||||
|
||||
|
||||
Received 27.10.24 11:00:13:
|
||||
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
|
||||
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 02 AA 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 01 CC 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 00 02 02 02 0F FF 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 0B 02 02 0F FF 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 16 02 02
|
||||
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 09 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 0F 02 02 0F FF 16 23 02 02 09
|
||||
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 0A 1B 00 0B 00
|
||||
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
|
||||
(V) 00 8F 25 6F 02 02 0F 01 16 1E 02 03 09 06 01 00
|
||||
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
|
||||
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 8D 02 02
|
||||
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
|
||||
(V) 14 44 26 02 02 0F 01 16 20 84 09 7E
|
||||
|
||||
14
frames/EGM5G35.txt
Normal file
14
frames/EGM5G35.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
2F 45 47 4D 35 47 33 35 // DSMR Identifier
|
||||
0D 0A // CRLF
|
||||
0D 0A // CRLF
|
||||
00 // System title, blank
|
||||
82 // Two-byte length follows
|
||||
02 30 // 560 bytes
|
||||
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 1=Authentication, 0000= Security Suite ID
|
||||
00 00 00 00 // Frame counter, blank
|
||||
|
||||
// Encrypted payload follows (560 - security tag - frame counter = 555 bytes)
|
||||
30 10 73 D6 59 85 96 B0 2A 8B CC E7 33 AD 19 A3 A5 4A 4D 0E 3A CE F6 33 DB 57 0A 45 9A C3 5B F0 D4 C0 AA 15 B9 93 B0 E8 67 85 EC 22 CA 40 8C D7 1A B5 62 AE DF 0F 48 EA D9 E6 56 FB 84 B3 7B 47 C6 29 12 F1 EE BB 22 88 26 75 5C F8 A2 20 F4 93 F3 64 CA 7C C6 60 32 62 B0 7F F9 7F 71 0A 9F DF AB 61 89 6A 9A 10 B1 DF 94 2C 74 8F 51 B6 09 5C F2 45 6C 38 54 52 FE B6 CB 1F 42 F2 93 DC 57 BE EE 5A 0D F8 1F 53 55 0E 21 EF 46 0A 72 74 CA AB D8 9C A9 05 9E FD 54 5E CC 7B 71 40 E1 22 B2 25 5C D5 63 80 BD 97 6E 54 4B 24 A6 58 1F 79 1F 45 C7 DF C0 83 3B 7F 1E C7 43 B9 26 F6 EB 6E F2 B1 7C 99 3A 91 EA 39 72 E5 FB 72 B1 E1 24 AF 9D 19 8A 6C 50 69 97 90 D3 3D 68 97 57 C2 EC 49 EF 12 1D 11 44 46 70 46 88 88 98 37 6F B0 93 FC D2 3F 37 66 1C C7 F0 93 E3 AF 4D 3C BE 41 8E B9 48 77 A9 92 5B 42 0A 96 E5 34 4D 30 56 90 5A 08 03 CC 41 78 58 79 FD 89 82 E0 46 3D 66 AA 42 85 F4 A8 46 2D 4A E4 81 12 40 68 D6 F5 F9 11 5A 94 51 21 3C 9F 4F F7 FE B3 B5 BF 1E D2 12 CF B9 FB 28 D0 B4 82 9E 11 D8 1C FC E3 36 EC DE 8A 7C 83 76 F7 EB 3D 2C 4A B7 7F 75 91 F1 F5 18 D4 70 6A C9 3E 3D 3F 9E 0F C6 0F A7 E7 20 11 03 E7 75 4E 40 F1 5E BA 86 30 6D C0 28 C4 14 47 DF 6A EF CC FE E4 B6 23 58 2B 9D 4F BF 6A A8 93 84 F4 CD 4D FF 47 B5 C4 81 D2 DA D3 6B C3 F4 1D C4 6E E1 9B 24 30 91 7B 33 57 67 B6 E4 93 FE 8B 5B AE 8A 0D 81 DE A5 C3 3F 82 9A CE E2 BA 6D 50 39 FC 99 E6 7A 7B B4 E9 A9 84 78 AF 2D 4C D2 15 53 6B 98 2B 1F C8 3D 57 9E 49 68 40 0C D3 4A 9E D6 4F 1F 12 50 84 32 AD 62 C6 D3 42 34 46 66 65 56 49 81 F4 BC 8E C6 75 77 11 1F 29 BB 80 20 23 0A 80 D5 31 50 16 89 6F 07 4A 25 BE F0 4B 60 36 87 AE A6 2A 53 79 22 15 A5 2A C7 1D 15 7B 87 DD B5 07 3D 1C 28 C1 B6 51 47 2F 39 DE D9 B9 29 B6 78
|
||||
21 // End of DSMR (!)
|
||||
46 45 30 43 // FE0C - Checksum
|
||||
0D 0A // CRLF
|
||||
@@ -31,4 +31,183 @@ C9 95 7E // CRC and end tag
|
||||
06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05
|
||||
06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B
|
||||
09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E
|
||||
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
|
||||
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
|
||||
|
||||
|
||||
01.10.24 19:00 ish:
|
||||
(D) READY to update (internal clock 16:59:58 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 78 01 02 01 10 C4 98 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 00 FF 80 00 00 02
|
||||
(V) 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
|
||||
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
|
||||
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
|
||||
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06 00
|
||||
(V) 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00 00
|
||||
(V) 09 69 06 00 00 09 59 18 EB 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 00 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30
|
||||
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
|
||||
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
|
||||
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
|
||||
(V) 06 00 00 4C DF 06 00 00 05 0C 06 00 00 03 9B 06
|
||||
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 00 FF
|
||||
(V) 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10
|
||||
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
|
||||
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
|
||||
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
|
||||
(V) 4C DF 06 00 00 05 0C 06 00 00 03 9B 06 00 00 09
|
||||
(V) 34 06 00 00 09 69 06 00 00 09 59
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
|
||||
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
|
||||
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
|
||||
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06
|
||||
(V) 00 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00
|
||||
(V) 00 09 69 06 00 00 09 59
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:01 UTC, meter clock: 00:00:00, list type 2, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 02 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 C1 56 F5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 02 FF 80 00 00 02 01 06 00 00 13 C1
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 02 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 C1
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 C1
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:02 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 04 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC F9 5A 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 04 FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 04 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:04 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 06 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC 42 58 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 06 FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 06 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:06 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 08 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BB DC 21 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 08 FF 80 00 00 02 01 06 00 00 13 BB
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 08 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BB
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BB
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:08 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0A FF 80 00 00 02
|
||||
(V) 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
|
||||
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
|
||||
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
|
||||
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06 00
|
||||
(V) 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00 00
|
||||
(V) 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13 00
|
||||
(V) 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00 06
|
||||
(V) 00 97 35 DE 06 00 08 97 2F FA E5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30
|
||||
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
|
||||
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
|
||||
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
|
||||
(V) 06 00 00 4C DD 06 00 00 05 0E 06 00 00 03 90 06
|
||||
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59 09 0C
|
||||
(V) 07 E8 0A 01 02 13 00 0A FF 80 00 00 06 02 8E 4B
|
||||
(V) 5E 06 00 00 00 00 06 00 97 35 DE 06 00 08 97 2F
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0A FF
|
||||
(V) 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10
|
||||
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
|
||||
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
|
||||
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
|
||||
(V) 4C DD 06 00 00 05 0E 06 00 00 03 90 06 00 00 09
|
||||
(V) 34 06 00 00 09 69 06 00 00 09 59 09 0C 07 E8 0A
|
||||
(V) 01 02 13 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00
|
||||
(V) 00 00 00 06 00 97 35 DE 06 00 08 97 2F
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
|
||||
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
|
||||
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
|
||||
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06
|
||||
(V) 00 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00
|
||||
(V) 00 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00
|
||||
(V) 06 00 97 35 DE 06 00 08 97 2F
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:12 UTC, meter clock: 17:00:10, list type 3, est: 1, using clock: 0)
|
||||
(D) Updating data storage using actual data
|
||||
(D) Clearing hours from 16 to 17
|
||||
(D) Clearing days from 1 to 1
|
||||
(D) Day is not happy
|
||||
(D) - normal
|
||||
(I) Saving data
|
||||
(I) Saving energy accounting
|
||||
(W) Used 1249ms to read HAN port (true)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0C FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC 15 50 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0C FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0C FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 17:00:12 UTC, meter clock: 00:00:00, list type 1, est: 0)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0E FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 B7 7D EC 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0E FF 80 00 00 02 01 06 00 00 13 B7
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0E FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 B7
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 B7
|
||||
|
||||
1375
frames/KamstrupOmnipower_Bornholm_DK_5digitVoltage_9sep25.log
Normal file
1375
frames/KamstrupOmnipower_Bornholm_DK_5digitVoltage_9sep25.log
Normal file
File diff suppressed because it is too large
Load Diff
682
frames/L&G-E350_Norway.raw
Normal file
682
frames/L&G-E350_Norway.raw
Normal file
@@ -0,0 +1,682 @@
|
||||
*** Remote debug - over telnet - for ESP32 - version 3.0.5
|
||||
* Host name: ams-e603 IP:10.10.10.62 Mac address:D8:3B:DA:C4:03:E6
|
||||
* Free Heap RAM: 87952
|
||||
* ESP SDK version: 4.4.5.230722
|
||||
******************************************************
|
||||
* Commands:
|
||||
? or help -> display these help of commands
|
||||
q -> quit (close this connection)
|
||||
m -> display memory available
|
||||
v -> set debug level to verbose
|
||||
d -> set debug level to debug
|
||||
i -> set debug level to info
|
||||
w -> set debug level to warning
|
||||
e -> set debug level to errors
|
||||
s -> set debug silence on/off
|
||||
l -> show debug level
|
||||
t -> show time (millis)
|
||||
profiler:
|
||||
p -> show time between actual and last message (in millis)
|
||||
p min -> show only if time is this minimal
|
||||
P time -> set debug level to profiler
|
||||
c -> show colors
|
||||
filter:
|
||||
filter <string> -> show only debugs with this
|
||||
nofilter -> disable the filter
|
||||
* Please type the command and press enter to execute.(? or h for this help)
|
||||
***
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 2F AF 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 F4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 68 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 68 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:06 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) F4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:07 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) F4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:10 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) E0 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:12 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 00 00 00 00 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 28
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 AE 9D 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 00 00 00 00 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 E0 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 28 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 68 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 00 00 00 00 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 28 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 68 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 00 00 00 00 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 28 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:16 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) E0 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:17 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:20 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:22 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 38 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 4A DF 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5C 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 38 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5C 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 38 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 38 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:26 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:27 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:29 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:32 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 46
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 3A 49 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 46 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 67 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 46 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 67 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 46 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 06 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:36 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:37 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:39 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) D5 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:42 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 4A 9D 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 D5 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 3C 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 77 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5F 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 3C 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 77 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5F 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 06 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:46 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
qq(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) D5 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:47 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q
|
||||
* Debug: Command received: qqqqq
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:49 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:51 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q
|
||||
* Debug: Command received: q
|
||||
* Closing client connection ...
|
||||
1
frames/dsmr.raw
Normal file
1
frames/dsmr.raw
Normal file
@@ -0,0 +1 @@
|
||||
2F454C4C355C3235333833333633355F410D0A0D0A302D303A312E302E302832343132313232303133343857290D0A312D303A312E382E302830303034373532322E3237382A6B5768290D0A312D303A322E382E302830303030303030302E3030312A6B5768290D0A312D303A332E382E302830303030303035392E3033392A6B76617268290D0A312D303A342E382E302830303031323537302E3937362A6B76617268290D0A312D303A312E372E3028303030312E3233312A6B57290D0A312D303A322E372E3028303030302E3030302A6B57290D0A312D303A332E372E3028303030302E3030302A6B766172290D0A312D303A342E372E3028303030302E3435312A6B766172290D0A312D303A32312E372E3028303030302E3337362A6B57290D0A312D303A34312E372E3028303030302E3832342A6B57290D0A312D303A36312E372E3028303030302E3033302A6B57290D0A312D303A32322E372E3028303030302E3030302A6B57290D0A312D303A34322E372E3028303030302E3030302A6B57290D0A312D303A36322E372E3028303030302E3030302A6B57290D0A312D303A32332E372E3028303030302E3030302A6B766172290D0A312D303A34332E372E3028303030302E3030302A6B766172290D0A312D303A36332E372E3028303030302E3030302A6B766172290D0A312D303A32342E372E3028303030302E3037322A6B766172290D0A312D303A34342E372E3028303030302E3334392A6B766172290D0A312D303A36342E372E3028303030302E3032392A6B766172290D0A312D303A33322E372E30283233322E382A56290D0A312D303A35322E372E30283233332E352A56290D0A312D303A37322E372E30283233382E352A56290D0A312D303A33312E372E30283030312E382A41290D0A312D303A35312E372E30283030342E302A41290D0A312D303A37312E372E30283030302E312A41290D0A21343736300D0A
|
||||
48
frames/iskra_croatia.txt
Normal file
48
frames/iskra_croatia.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
They actually use multiple frames, so this is a "fake" frame combining the two into one, but without checksum fields.
|
||||
|
||||
7E
|
||||
A0 BD
|
||||
CF 02 23 03 00 00
|
||||
E6 E7 00
|
||||
0F 00 03 46 3B
|
||||
0C 07 E9 0C 13 05 17 37 28 00 FF C4 00
|
||||
|
||||
02 21
|
||||
09 08 39 32 30 32 39 36 39 31
|
||||
09 04 17 37 28 00
|
||||
09 05 07 E9 0C 13 05
|
||||
06 00 6C 28 5A
|
||||
06 00 4B 76 1A
|
||||
06 00 20 B2 40
|
||||
06 00 58 68 AA
|
||||
06 00 57 A1 62
|
||||
06 00 00 C7 48
|
||||
06 00 17 EE D7
|
||||
06 00 12 F5 5C
|
||||
06 00 00 D9 6A
|
||||
06 00 15 36 84
|
||||
06 00 00 01 7E
|
||||
06 00 00 00 00
|
||||
12 03 79
|
||||
06 00 00 00 7F
|
||||
06 00 00 00 BD
|
||||
06 00 00 00 41
|
||||
06 00 00 00 00
|
||||
06 00 00 00 00
|
||||
06 00 00 00 00
|
||||
12 09 54
|
||||
12 09 35
|
||||
12 09 49
|
||||
12 00 37
|
||||
12 00 59
|
||||
12 00 4D
|
||||
06 00 00 43 62
|
||||
01 01
|
||||
12 24 B8
|
||||
01 01
|
||||
12 24 B8
|
||||
01 01
|
||||
12 24 B8
|
||||
03 01
|
||||
|
||||
00 00 7E
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 199 KiB |
@@ -1,31 +1,76 @@
|
||||
/**
|
||||
* @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_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_UPGRADE_INFO_START 1934
|
||||
#define CONFIG_ZC_START 2000
|
||||
|
||||
#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
|
||||
|
||||
#define FIRMWARE_CHANNEL_STABLE 0
|
||||
#define FIRMWARE_CHANNEL_EARLY 1
|
||||
#define FIRMWARE_CHANNEL_RC 2
|
||||
#define FIRMWARE_CHANNEL_SNAPSHOT 3
|
||||
|
||||
#define REBOOT_CAUSE_WEB_SYSINFO_JSON 1
|
||||
#define REBOOT_CAUSE_WEB_SAVE 2
|
||||
#define REBOOT_CAUSE_WEB_REBOOT 3
|
||||
#define REBOOT_CAUSE_WEB_FACTORY_RESET 4
|
||||
#define REBOOT_CAUSE_BTN_FACTORY_RESET 5
|
||||
#define REBOOT_CAUSE_REPARTITION 6
|
||||
#define REBOOT_CAUSE_CONFIG_FILE_UPDATE 7
|
||||
#define REBOOT_CAUSE_FIRMWARE_UPDATE 8
|
||||
#define REBOOT_CAUSE_MQTT_DISCONNECTED 9
|
||||
#define REBOOT_CAUSE_SMART_CONFIG 10
|
||||
|
||||
struct ResetDataContainer {
|
||||
uint8_t cause;
|
||||
uint8_t last_cause;
|
||||
uint8_t magic;
|
||||
};
|
||||
|
||||
struct SystemConfig {
|
||||
uint8_t boardType;
|
||||
@@ -34,9 +79,10 @@ struct SystemConfig {
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[3];
|
||||
uint8_t energyspeedometer;
|
||||
}; // 8
|
||||
uint8_t firmwareChannel;
|
||||
}; // 9
|
||||
|
||||
struct WiFiConfig {
|
||||
struct NetworkConfig {
|
||||
char ssid[32];
|
||||
char psk[64];
|
||||
char ip[16];
|
||||
@@ -49,8 +95,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 +109,22 @@ struct MqttConfig {
|
||||
char password[256];
|
||||
uint8_t payloadFormat;
|
||||
bool ssl;
|
||||
}; // 676
|
||||
uint8_t magic;
|
||||
bool stateUpdate;
|
||||
uint16_t stateUpdateInterval;
|
||||
uint16_t timeout;
|
||||
uint8_t keepalive;
|
||||
uint8_t rebootMinutes;
|
||||
}; // 684
|
||||
|
||||
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 +146,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 +158,27 @@ 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;
|
||||
uint8_t powersaving;
|
||||
}; // 22
|
||||
|
||||
struct GpioConfig103 {
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
@@ -146,7 +196,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,33 +221,22 @@ 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;
|
||||
uint8_t resolutionInMinutes;
|
||||
uint16_t unused2;
|
||||
uint16_t unused3;
|
||||
bool enabled;
|
||||
uint16_t fixedPrice;
|
||||
}; // 64
|
||||
uint16_t unused6;
|
||||
};
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
uint16_t thresholds[10];
|
||||
uint8_t hours;
|
||||
}; // 21
|
||||
|
||||
struct EnergyAccountingConfig101 {
|
||||
uint8_t thresholds[10];
|
||||
uint8_t hours;
|
||||
}; // 11
|
||||
|
||||
struct UiConfig {
|
||||
uint8_t showImport;
|
||||
uint8_t showExport;
|
||||
@@ -208,20 +249,37 @@ 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];
|
||||
char toVersion[8];
|
||||
int16_t exitCode;
|
||||
int16_t errorCode;
|
||||
}; // 20
|
||||
char fromVersion[16];
|
||||
char toVersion[16];
|
||||
uint32_t size;
|
||||
uint16_t block_position;
|
||||
uint8_t retry_count;
|
||||
uint8_t reboot_count;
|
||||
int8_t errorCode;
|
||||
}; // 41+3
|
||||
|
||||
struct CloudConfig {
|
||||
bool enabled;
|
||||
uint8_t interval;
|
||||
char hostname[64];
|
||||
uint16_t port;
|
||||
uint8_t clientId[16];
|
||||
uint8_t proto;
|
||||
}; // 88
|
||||
|
||||
struct ZmartChargeConfig {
|
||||
bool enabled;
|
||||
char token[21];
|
||||
char baseUrl[64];
|
||||
}; // 86
|
||||
|
||||
class AmsConfiguration {
|
||||
public:
|
||||
@@ -235,12 +293,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 +309,9 @@ public:
|
||||
|
||||
bool getWebConfig(WebConfig&);
|
||||
bool setWebConfig(WebConfig&);
|
||||
void clearAuth(WebConfig&);
|
||||
void clearWebConfig(WebConfig&);
|
||||
bool isWebChanged();
|
||||
void ackWebChange();
|
||||
|
||||
bool getMeterConfig(MeterConfig&);
|
||||
bool setMeterConfig(MeterConfig&);
|
||||
@@ -268,7 +328,7 @@ public:
|
||||
|
||||
bool getGpioConfig(GpioConfig&);
|
||||
bool setGpioConfig(GpioConfig&);
|
||||
void clearGpio(GpioConfig&);
|
||||
void clearGpio(GpioConfig& config, bool all=true);
|
||||
|
||||
void print(Print* debugger);
|
||||
|
||||
@@ -286,11 +346,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 +361,27 @@ 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);
|
||||
bool setUpgradeInformation(UpgradeInformation&);
|
||||
void clearUpgradeInformation(UpgradeInformation&);
|
||||
|
||||
bool getCloudConfig(CloudConfig&);
|
||||
bool setCloudConfig(CloudConfig&);
|
||||
void clearCloudConfig(CloudConfig&);
|
||||
bool isCloudChanged();
|
||||
void ackCloudConfig();
|
||||
|
||||
bool getZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool setZmartChargeConfig(ZmartChargeConfig&);
|
||||
void clearZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool isZmartChargeConfigChanged();
|
||||
void ackZmartChargeConfig();
|
||||
|
||||
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
@@ -321,18 +389,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 = false, mqttChanged = false, webChanged = false, meterChanged = true, ntpChanged = true, priceChanged = false, energyAccountingChanged = true, cloudChanged = true, uiLanguageChanged = false, zcChanged = true;
|
||||
|
||||
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,7 +1,13 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSSTORAGE_H
|
||||
#define _AMSSTORAGE_H
|
||||
|
||||
#define FILE_FIRMWARE "/firmware.bin"
|
||||
#define FILE_FIRMWARE_DELETE "/firmware.bin"
|
||||
|
||||
#define FILE_MQTT_CA "/mqtt-ca.pem"
|
||||
#define FILE_MQTT_CERT "/mqtt-cert.pem"
|
||||
@@ -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
|
||||
|
||||
@@ -7,6 +13,7 @@
|
||||
String toHex(uint8_t* in);
|
||||
String toHex(uint8_t* in, uint16_t size);
|
||||
void fromHex(uint8_t *out, String in, uint16_t size);
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended = false);
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended = false, bool trim = true);
|
||||
void debugPrint(uint8_t *buffer, uint16_t start, uint16_t length, Print* debugger);
|
||||
|
||||
#endif
|
||||
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) {
|
||||
@@ -22,14 +28,14 @@ void fromHex(uint8_t *out, String in, uint16_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended, bool trim) {
|
||||
bool ret = false;
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
if(in[i] == 0) { // Clear the rest with null-terminator
|
||||
memset(in+i, 0, size-i);
|
||||
break;
|
||||
}
|
||||
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157)) {
|
||||
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157 || in[i] == 160)) {
|
||||
memset(in+i, ' ', 1);
|
||||
ret = true;
|
||||
} else if(!extended && (in[i] < 32 || in[i] > 126)) {
|
||||
@@ -37,6 +43,38 @@ bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
if(trim) {
|
||||
// Strip leading spaces
|
||||
while(in[0] == ' ') {
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
in[i] = in[i+1];
|
||||
}
|
||||
}
|
||||
// Strip trailing spaces
|
||||
for(int i = size-1; i > 0; i--) {
|
||||
if(in[i] == ' ' || in[i] == 0) {
|
||||
memset(in+i, 0, 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
memset(in+size-1, 0, 1); // Make sure the last character is null-terminator
|
||||
return ret;
|
||||
}
|
||||
|
||||
void debugPrint(uint8_t *buffer, uint16_t start, uint16_t length, Print* debugger) {
|
||||
for (uint16_t 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(""));
|
||||
}
|
||||
@@ -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,7 +84,8 @@ public:
|
||||
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
bool isL2currentEstimated();
|
||||
bool isCounterEstimated();
|
||||
bool isL2currentMissing();
|
||||
|
||||
int8_t getLastError();
|
||||
void setLastError(int8_t);
|
||||
@@ -82,11 +99,14 @@ 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;
|
||||
bool threePhase = false, twoPhase = false, counterEstimated = false, l2currentEstimated = false;
|
||||
double lastKnownCounter = 0;
|
||||
bool threePhase = false, twoPhase = false, counterEstimated = false, l2currentMissing = false;;
|
||||
|
||||
int8_t lastError = 0x00;
|
||||
uint8_t lastErrorCount = 0;
|
||||
|
||||
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();
|
||||
@@ -67,7 +96,7 @@ void AmsData::apply(AmsData& other) {
|
||||
this->reactiveExportPower = other.getReactiveExportPower();
|
||||
this->l1current = other.getL1Current();
|
||||
this->l2current = other.getL2Current();
|
||||
this->l2currentEstimated = other.isL2currentEstimated();
|
||||
this->l2currentMissing = other.isL2currentMissing();
|
||||
this->l3current = other.getL3Current();
|
||||
this->l1voltage = other.getL1Voltage();
|
||||
this->l2voltage = other.getL2Voltage();
|
||||
@@ -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,8 +425,12 @@ bool AmsData::isTwoPhase() {
|
||||
return this->twoPhase;
|
||||
}
|
||||
|
||||
bool AmsData::isL2currentEstimated() {
|
||||
return this->l2currentEstimated;
|
||||
bool AmsData::isCounterEstimated() {
|
||||
return this->counterEstimated;
|
||||
}
|
||||
|
||||
bool AmsData::isL2currentMissing() {
|
||||
return this->l2currentMissing;
|
||||
}
|
||||
|
||||
int8_t AmsData::getLastError() {
|
||||
|
||||
@@ -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,141 +63,167 @@ 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"));
|
||||
}
|
||||
if(!isDayHappy(now) && day.lastMeterReadTime > now) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Day was updated in the future, 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) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Initializing day data\n"));
|
||||
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(now - day.lastMeterReadTime > 86400) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Day was updated to long ago, clearing\n"));
|
||||
day.activeImport = importCounter;
|
||||
day.activeExport = exportCounter;
|
||||
day.lastMeterReadTime = now;
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Clearing hours from %d to %d\n"), last.Hour, endHour);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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"));
|
||||
}
|
||||
if(!isMonthHappy(now) && month.lastMeterReadTime > now) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Month was updated in the future, 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) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Initializing month data\n"));
|
||||
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(now - month.lastMeterReadTime > 2682000) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Month was updated to long ago, clearing\n"));
|
||||
month.activeImport = importCounter;
|
||||
month.activeExport = exportCounter;
|
||||
month.lastMeterReadTime = now;
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Clearing days from %d to %d\n"), last.Day, endDay);
|
||||
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 +239,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 +248,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 +277,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 +292,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 +303,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 +471,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 +479,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 +528,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 +560,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 +630,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 +654,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
|
||||
|
||||
@@ -8,14 +14,18 @@ enum CosemType {
|
||||
CosemTypeNull = 0x00,
|
||||
CosemTypeArray = 0x01,
|
||||
CosemTypeStructure = 0x02,
|
||||
CosemTypeBoolean = 0x03,
|
||||
CosemTypeOctetString = 0x09,
|
||||
CosemTypeString = 0x0A,
|
||||
CosemTypeDLongSigned = 0x05,
|
||||
CosemTypeDLongUnsigned = 0x06,
|
||||
CosemTypeSigned = 0x0F,
|
||||
CosemTypeLongSigned = 0x10,
|
||||
CosemTypeUnsigned = 0x11,
|
||||
CosemTypeLongUnsigned = 0x12,
|
||||
CosemTypeLong64Signed = 0x14,
|
||||
CosemTypeLong64Unsigned = 0x15,
|
||||
CosemTypeEnum = 0x16,
|
||||
CosemTypeDateTime = 0x19
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATAPASERSER_H
|
||||
#define _DATAPASERSER_H
|
||||
|
||||
@@ -10,11 +16,15 @@
|
||||
#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
|
||||
#define DATA_PARSE_INCOMPLETE -2
|
||||
#define DATA_PARSE_BOUNDRY_FLAG_MISSING -3
|
||||
#define DATA_PARSE_BOUNDARY_FLAG_MISSING -3
|
||||
#define DATA_PARSE_HEADER_CHECKSUM_ERROR -4
|
||||
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
|
||||
#define DATA_PARSE_INTERMEDIATE_SEGMENT -6
|
||||
|
||||
@@ -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,13 +1,27 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DSMRPARSER_H
|
||||
#define _DSMRPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
#include "GcmParser.h"
|
||||
|
||||
class DSMRParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified);
|
||||
DSMRParser(GCMParser* gcmParser) { this->gcmParser = gcmParser; };
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified, Print* debugger);
|
||||
uint16_t getCrc();
|
||||
uint16_t getCrcCalc();
|
||||
private:
|
||||
uint16_t crc;
|
||||
uint16_t crc_calc;
|
||||
|
||||
GCMParser* gcmParser;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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,16 +15,10 @@
|
||||
#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);
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool hastag = true);
|
||||
private:
|
||||
uint8_t encryption_key[16];
|
||||
uint8_t authentication_key[16];
|
||||
|
||||
@@ -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>
|
||||
@@ -13,12 +19,10 @@ time_t decodeCosemDateTime(CosemDateTime timestamp) {
|
||||
tm.Minute = timestamp.minute;
|
||||
tm.Second = timestamp.second;
|
||||
|
||||
//Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status);
|
||||
|
||||
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,29 +1,92 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DsmrParser.h"
|
||||
#include "crc.h"
|
||||
#include "hexutils.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
|
||||
// verified indicates that this data was encapsulated in something else, so we know this has the correct size etc
|
||||
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified, Print* debugger) {
|
||||
uint16_t lenBefore = ctx.length;
|
||||
uint16_t crcPos = 0;
|
||||
bool reachedEnd = verified;
|
||||
uint8_t lastByte = 0x00;
|
||||
for(int pos = 0; pos < ctx.length; pos++) {
|
||||
for(uint16_t pos = 0; pos < ctx.length; pos++) {
|
||||
uint8_t b = *(buf+pos);
|
||||
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
if(pos > 0 && b == '!' && lastByte == '\n') crcPos = pos+1;
|
||||
if(crcPos > 0 && b == '\n') reachedEnd = true;
|
||||
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
if(pos > 0 && b == '!') crcPos = pos+1;
|
||||
if(crcPos > 0 && b == 0x0A && lastByte == 0x0D) {
|
||||
reachedEnd = true;
|
||||
ctx.length = pos;
|
||||
break;
|
||||
}
|
||||
lastByte = b;
|
||||
}
|
||||
if(!reachedEnd) return DATA_PARSE_INCOMPLETE;
|
||||
buf[ctx.length+1] = '\0';
|
||||
if(crcPos > 0) {
|
||||
uint16_t crc_calc = crc16(buf, crcPos);
|
||||
uint16_t crc = 0x0000;
|
||||
|
||||
// If we expect data to be encrypted and it was not previously verified, decrypt content
|
||||
if(gcmParser != NULL && !verified) {
|
||||
uint8_t* ptr = (uint8_t*) buf;
|
||||
while(*ptr != 0x0D && *ptr != 0x0A) ptr++;
|
||||
while(*ptr == 0x0D || *ptr == 0x0A) ptr++;
|
||||
uint16_t pos = ptr-buf;
|
||||
DataParserContext gcmCtx = {
|
||||
DATA_TAG_GCM,
|
||||
crcPos - pos - 1,
|
||||
ctx.timestamp
|
||||
};
|
||||
if(debugger != NULL) {
|
||||
debugger->printf_P(PSTR("DSMR wants to decrypt at position %lu, length: %d, payload:\n"), pos, gcmCtx.length);
|
||||
debugPrint(ptr, 0, gcmCtx.length, debugger);
|
||||
}
|
||||
int8_t gcmRet = gcmParser->parse(ptr, gcmCtx, false);
|
||||
if(gcmRet < 0) {
|
||||
if(debugger != NULL) {
|
||||
debugger->printf_P(PSTR(" - Failed! (%d)\n"), gcmRet);
|
||||
}
|
||||
return gcmRet;
|
||||
} else {
|
||||
if(debugger != NULL) {
|
||||
debugger->printf_P(PSTR(" - Success! (%d)\n"), gcmRet);
|
||||
}
|
||||
ptr += gcmRet;
|
||||
for(uint16_t i = 0; i < gcmCtx.length; i++) {
|
||||
buf[pos++] = ptr[i];
|
||||
}
|
||||
ptr = buf + crcPos - 1;
|
||||
crcPos = pos + 1;
|
||||
while(*ptr != '\0') {
|
||||
ctx.length = pos;
|
||||
buf[pos++] = *(ptr++);
|
||||
}
|
||||
while(pos < lenBefore) {
|
||||
buf[pos++] = '\0';
|
||||
}
|
||||
}
|
||||
} else if(crcPos > 0) {
|
||||
crc_calc = crc16(buf, crcPos);
|
||||
crc = 0x0000;
|
||||
fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2);
|
||||
crc = ntohs(crc);
|
||||
|
||||
if(crc != crc_calc)
|
||||
if(crc > 0 && crc != crc_calc) {
|
||||
if(debugger != NULL) {
|
||||
debugger->printf_P(PSTR("CRC incorrrect, %04X != %04X at position %lu\n"), crc, crc_calc, crcPos);
|
||||
}
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
}
|
||||
}
|
||||
return DATA_PARSE_OK;
|
||||
}
|
||||
|
||||
uint16_t DSMRParser::getCrc() {
|
||||
return crc;
|
||||
}
|
||||
uint16_t DSMRParser::getCrcCalc() {
|
||||
return crc_calc;
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GbtParser.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
@@ -5,7 +11,7 @@ int8_t GBTParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
GBTHeader* h = (GBTHeader*) (d);
|
||||
uint16_t sequence = ntohs(h->sequence);
|
||||
|
||||
if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
|
||||
if(sequence == 1) {
|
||||
if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ?
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GcmParser.h"
|
||||
#include "lwip/def.h"
|
||||
#if defined(ESP8266)
|
||||
@@ -11,49 +17,60 @@ GCMParser::GCMParser(uint8_t *encryption_key, uint8_t *authentication_key) {
|
||||
memcpy(this->authentication_key, authentication_key, 16);
|
||||
}
|
||||
|
||||
int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx, bool hastag) {
|
||||
if(ctx.length < 12) return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
uint32_t headersize = 0;
|
||||
uint8_t* ptr = (uint8_t*) d;
|
||||
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
ptr++;
|
||||
if(hastag) {
|
||||
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
ptr++;
|
||||
headersize++;
|
||||
}
|
||||
// Encrypted APDU
|
||||
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
|
||||
|
||||
uint8_t systemTitleLength = *ptr;
|
||||
ptr++;
|
||||
headersize++;
|
||||
|
||||
uint8_t initialization_vector[12];
|
||||
memcpy(ctx.system_title, ptr, systemTitleLength);
|
||||
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
|
||||
memset(ctx.system_title, 0, 8);
|
||||
memset(initialization_vector, 0, 12);
|
||||
if(systemTitleLength > 0) {
|
||||
memcpy(ctx.system_title, ptr, systemTitleLength);
|
||||
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
|
||||
ptr += systemTitleLength;
|
||||
headersize += systemTitleLength;
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
int headersize = 2 + systemTitleLength;
|
||||
ptr += systemTitleLength;
|
||||
uint32_t len = 0;
|
||||
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 +87,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 +96,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 +105,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 +123,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"
|
||||
@@ -23,10 +29,10 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
// First and last byte should be HDLC_FLAG
|
||||
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
|
||||
// Verify FCS
|
||||
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
if(f->fcs > 0 && ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
|
||||
// Skip destination address, LSB marks last byte
|
||||
@@ -44,13 +50,56 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
||||
|
||||
// Verify HCS
|
||||
if(ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
||||
if(t3->hcs > 0 && ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
||||
return DATA_PARSE_HEADER_CHECKSUM_ERROR;
|
||||
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;
|
||||
|
||||
if((*ptr) == DATA_TAG_LLC) {
|
||||
ptr += 3; // Skip LLC
|
||||
ctx.length -= 3;
|
||||
}
|
||||
|
||||
memcpy(buf + pos, ptr, ctx.length);
|
||||
pos += ctx.length;
|
||||
|
||||
lastSequenceNumber++;
|
||||
return DATA_PARSE_INTERMEDIATE_SEGMENT;
|
||||
} else if(lastSequenceNumber > 0) {
|
||||
lastSequenceNumber = 0;
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
if((*ptr) == DATA_TAG_LLC) {
|
||||
ptr += 3; // Skip LLC
|
||||
ctx.length -= 3;
|
||||
}
|
||||
|
||||
memcpy(buf + pos, ptr, ctx.length);
|
||||
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) {
|
||||
@@ -13,7 +19,7 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
MbusHeader* mh = (MbusHeader*) d;
|
||||
if(mh->flag1 != MBUS_START || mh->flag2 != MBUS_START)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
|
||||
// First two bytes is 1-byte length value repeated. Only used for last segment
|
||||
if(mh->len1 != mh->len2)
|
||||
@@ -34,7 +40,7 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
MbusFooter* mf = (MbusFooter*) (d + len + headersize);
|
||||
if(mf->flag != MBUS_END)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
if(checksum(d + headersize, len) != mf->fcs)
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
153
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
153
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <Print.h>
|
||||
#include "HwTools.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include "esp_flash_partitions.h"
|
||||
#include "LittleFS.h"
|
||||
#include "WiFi.h"
|
||||
#include "HTTPClient.h"
|
||||
|
||||
#define AMS_PARTITION_TABLE_OFFSET 0x8000
|
||||
#define AMS_PARTITION_APP0_OFFSET 0x10000
|
||||
#define AMS_PARTITION_APP_SIZE 0x1D0000
|
||||
#define AMS_PARTITION_MIN_SPIFFS_SIZE 0x20000
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
|
||||
#define AMS_FLASH_SKETCH_SIZE 0xFEFF0
|
||||
#define AMS_FLASH_OTA_START AMS_FLASH_OTA_SIZE
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
|
||||
#define AMS_UPDATE_ERR_OK 0
|
||||
#define AMS_UPDATE_ERR_DETAILS 1
|
||||
#define AMS_UPDATE_ERR_FETCH 2
|
||||
#define AMS_UPDATE_ERR_ERASE 3
|
||||
#define AMS_UPDATE_ERR_WRITE 4
|
||||
#define AMS_UPDATE_ERR_READ 5
|
||||
#define AMS_UPDATE_ERR_MD5 6
|
||||
#define AMS_UPDATE_ERR_ACTIVATE 7
|
||||
#define AMS_UPDATE_ERR_REBOOT 64
|
||||
#define AMS_UPDATE_ERR_SUCCESS_SIGNAL 122
|
||||
#define AMS_UPDATE_ERR_SUCCESS_CONFIRMED 123
|
||||
|
||||
#define UPDATE_BUF_SIZE 4096
|
||||
#define UPDATE_MAX_BLOCK_RETRY 25
|
||||
#define UPDATE_MAX_REBOOT_RETRY 12
|
||||
|
||||
class AmsFirmwareUpdater {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsFirmwareUpdater(RemoteDebug* debugger, HwTools* hw, AmsData* meterState);
|
||||
#else
|
||||
AmsFirmwareUpdater(Print* debugger, HwTools* hw, AmsData* meterState);
|
||||
#endif
|
||||
bool relocateOrRepartitionIfNecessary();
|
||||
void loop();
|
||||
|
||||
char* getNextVersion();
|
||||
bool setTargetVersion(const char* version);
|
||||
void getUpgradeInformation(UpgradeInformation&);
|
||||
float getProgress();
|
||||
bool activateDownloadedFirmware();
|
||||
|
||||
void setUpgradeInformation(UpgradeInformation&);
|
||||
bool isUpgradeInformationChanged();
|
||||
void ackUpgradeInformationChanged();
|
||||
|
||||
void setFirmwareChannel(uint8_t channel) {
|
||||
if(firmwareChannel != channel) {
|
||||
firmwareChannel = channel;
|
||||
lastVersionCheck = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool startFirmwareUpload(uint32_t size, const char* version);
|
||||
bool addFirmwareUploadChunk(uint8_t* buf, size_t length);
|
||||
bool completeFirmwareUpload(uint32_t size);
|
||||
|
||||
private:
|
||||
#if defined(ESP8266)
|
||||
char chipType[10] = "esp8266";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
char chipType[10] = "esp32s2";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
char chipType[10] = "esp32s3";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
char chipType[10] = "esp32c3";
|
||||
#elif defined(ESP32)
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
char chipType[10] = "esp32solo";
|
||||
#else
|
||||
char chipType[10] = "esp32";
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Print* debugger;
|
||||
#endif
|
||||
HwTools* hw;
|
||||
AmsData* meterState;
|
||||
|
||||
bool updateStatusChanged = false;
|
||||
UpgradeInformation updateStatus = {"","",0,0,0,0,0};
|
||||
uint16_t lastSaveBlocksWritten = 0;
|
||||
String md5;
|
||||
|
||||
uint32_t lastVersionCheck = 0;
|
||||
uint8_t firmwareChannel;
|
||||
bool autoUpgrade;
|
||||
char nextVersion[17];
|
||||
|
||||
void getChannelName(char * buffer);
|
||||
|
||||
bool fetchNextVersion();
|
||||
bool fetchVersionDetails();
|
||||
bool fetchFirmwareChunk(HTTPClient& http);
|
||||
bool writeBufferToFlash();
|
||||
bool verifyChecksum();
|
||||
bool activateNewFirmware();
|
||||
bool writeUpdateStatus();
|
||||
bool isFlashReadyForNextUpdateVersion(uint32_t size);
|
||||
|
||||
uint8_t* buf = NULL;
|
||||
uint16_t bufPos = 0;
|
||||
|
||||
#if defined(ESP32)
|
||||
bool readPartition(uint8_t num, const esp_partition_info_t* info);
|
||||
bool writePartition(uint8_t num, const esp_partition_info_t* info);
|
||||
bool copyData(const esp_partition_info_t* src, esp_partition_info_t* dst, bool eraseFirst=true);
|
||||
bool copyFile(fs::LittleFSFS* src, fs::LittleFSFS* dst, const char* filename);
|
||||
uint8_t* extractFileData(const char* filename, size_t& size);
|
||||
void saveFileData(const char* filename, uint8_t* data, size_t size);
|
||||
|
||||
bool relocateAppToFirst(const esp_partition_t* active);
|
||||
bool findPartition(const char* label, const esp_partition_info_t* info);
|
||||
|
||||
bool hasLargeEnoughAppPartitions();
|
||||
bool canMigratePartitionTable();
|
||||
bool hasTwoSpiffs();
|
||||
bool spiffsOnCorrectLocation();
|
||||
bool hasFiles();
|
||||
|
||||
bool clearPartitionTable();
|
||||
bool writeNewPartitionChecksum(uint8_t num);
|
||||
bool writePartitionTableWithSpiffsAtOldAndApp1();
|
||||
bool writePartitionTableWithSpiffsAtApp1AndNew();
|
||||
bool writePartitionTableFinal();
|
||||
|
||||
bool moveLittleFsFromOldToApp1();
|
||||
bool moveLittleFsFromApp1ToNew();
|
||||
#elif defined(ESP8266)
|
||||
uintptr_t getFirmwareUpdateStart();
|
||||
#endif
|
||||
};
|
||||
1297
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
1297
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
File diff suppressed because it is too large
Load Diff
9
lib/AmsJsonGenerator/include/AmsJsonGenerator.h
Normal file
9
lib/AmsJsonGenerator/include/AmsJsonGenerator.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "AmsDataStorage.h"
|
||||
|
||||
class AmsJsonGenerator {
|
||||
public:
|
||||
static void generateDayPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize);
|
||||
static void generateMonthPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize);
|
||||
};
|
||||
17
lib/AmsJsonGenerator/src/AmsJsonGenerator.cpp
Normal file
17
lib/AmsJsonGenerator/src/AmsJsonGenerator.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "AmsJsonGenerator.h"
|
||||
|
||||
void AmsJsonGenerator::generateDayPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize) {
|
||||
uint16_t pos = snprintf_P(buf, bufSize, PSTR("{\"unit\":\"kwh\""));
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
pos += snprintf_P(buf+pos, bufSize-pos, PSTR(",\"i%02d\":%.3f,\"e%02d\":%.3f"), i, ds->getHourImport(i) / 1000.0, i, ds->getHourExport(i) / 1000.0);
|
||||
}
|
||||
snprintf_P(buf+pos, bufSize-pos, PSTR("}"));
|
||||
}
|
||||
|
||||
void AmsJsonGenerator::generateMonthPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize) {
|
||||
uint16_t pos = snprintf_P(buf, bufSize, PSTR("{\"unit\":\"kwh\""));
|
||||
for(uint8_t i = 1; i < 32; i++) {
|
||||
pos += snprintf_P(buf+pos, bufSize-pos, PSTR(",\"i%02d\":%.3f,\"e%02d\":%.3f"), i, ds->getDayImport(i) / 1000.0, i, ds->getDayExport(i) / 1000.0);
|
||||
}
|
||||
snprintf_P(buf+pos, bufSize-pos, PSTR("}"));
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSMQTTHANDLER_H
|
||||
#define _AMSMQTTHANDLER_H
|
||||
|
||||
@@ -7,7 +13,8 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "EntsoeApi.h"
|
||||
#include "PriceService.h"
|
||||
#include "AmsFirmwareUpdater.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
@@ -15,30 +22,51 @@
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) {
|
||||
#else
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, AmsFirmwareUpdater* updater) {
|
||||
#endif
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
this->updater = updater;
|
||||
mqtt.dropOverflow(true);
|
||||
|
||||
pubTopic = String(mqttConfig.publishTopic);
|
||||
subTopic = String(mqttConfig.subscribeTopic);
|
||||
if(subTopic.isEmpty()) subTopic = pubTopic+"/command";
|
||||
};
|
||||
|
||||
void setCaVerification(bool);
|
||||
void setConfig(MqttConfig& mqttConfig);
|
||||
|
||||
bool connect();
|
||||
bool defaultSubscribe();
|
||||
void disconnect();
|
||||
lwmqtt_err_t lastError();
|
||||
bool connected();
|
||||
bool loop();
|
||||
bool isRebootSuggested();
|
||||
|
||||
virtual uint8_t getFormat() { return 0; };
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) { return false; };
|
||||
virtual bool postConnect() { 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 publishRaw(String data) { return false; };
|
||||
virtual bool publishPrices(PriceService* ps) { return false; };
|
||||
virtual bool publishSystem(HwTools*, PriceService*, EnergyAccounting*) { return false; };
|
||||
virtual bool publishRaw(uint8_t* raw, size_t length) { return false; };
|
||||
virtual bool publishFirmware() { return false; };
|
||||
virtual void onMessage(String &topic, String &payload) {};
|
||||
|
||||
virtual ~AmsMqttHandler() {
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient->stop();
|
||||
delete mqttSecureClient;
|
||||
}
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
@@ -46,15 +74,29 @@ 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;
|
||||
WiFiClient *mqttClient = NULL;
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
boolean _connected = false;
|
||||
char* json;
|
||||
uint16_t BufferSize = 2048;
|
||||
uint64_t lastStateUpdate = 0;
|
||||
uint64_t lastSuccessfulLoop = 0;
|
||||
|
||||
String pubTopic;
|
||||
String subTopic;
|
||||
|
||||
AmsFirmwareUpdater* updater = NULL;
|
||||
bool rebootSuggested = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "LittleFS.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
void AmsMqttHandler::setCaVerification(bool caVerification) {
|
||||
this->caVerification = caVerification;
|
||||
}
|
||||
|
||||
void AmsMqttHandler::setConfig(MqttConfig& mqttConfig) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::connect() {
|
||||
if(millis() - lastMqttRetry < 10000) {
|
||||
yield();
|
||||
@@ -15,96 +27,103 @@ 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());
|
||||
|
||||
bool applySslConfiguration = mqttConfigChanged;
|
||||
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
|
||||
|
||||
applySslConfiguration = true;
|
||||
}
|
||||
|
||||
if(applySslConfiguration) {
|
||||
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();
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
#if defined(ESP8266)
|
||||
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::DEBUG)) debugger->printf_P(PSTR("Setting client certificates (%dkb free heap)"), ESP.getFreeHeap());
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
#elif defined(ESP32)
|
||||
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(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
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No CA, disabling validation\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
LittleFS.end();
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
||||
file.close();
|
||||
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
||||
file.close();
|
||||
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
if(LittleFS.exists(FILE_MQTT_CERT)) {
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
mqttSecureClient->loadCertificate(file, file.size());
|
||||
file.close();
|
||||
}
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||
file.close();
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA verification disabled\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
mqttClient = mqttSecureClient;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("MQTT SSL setup complete (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// This section helps with power saving on ESP32 devices by reducing timeouts
|
||||
// The timeout is multiplied by 10 because WiFiClient is retrying 10 times internally
|
||||
// Power drain for this timeout is too great when using the default 3s timeout
|
||||
// On ESP8266 the timeout is used differently and the following code causes MQTT instability
|
||||
#if defined(ESP32)
|
||||
int clientTimeout = mqttConfig.timeout / 1000;
|
||||
if(clientTimeout > 3) clientTimeout = 3; // 3000ms is default, see WiFiClient.cpp WIFI_CLIENT_DEF_CONN_TIMEOUT_MS
|
||||
actualClient->setTimeout(clientTimeout);
|
||||
// Why can't we set number of retries for write here? WiFiClient defaults to 10 (10*3s == 30s)
|
||||
#endif
|
||||
|
||||
mqttConfigChanged = false;
|
||||
mqtt.setTimeout(mqttConfig.timeout);
|
||||
mqtt.setKeepAlive(mqttConfig.keepalive);
|
||||
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
|
||||
@@ -112,10 +131,21 @@ 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));
|
||||
_connected = mqtt.publish(statusTopic, "online", true, 0);
|
||||
mqtt.loop();
|
||||
defaultSubscribe();
|
||||
postConnect();
|
||||
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) {
|
||||
@@ -128,20 +158,31 @@ bool AmsMqttHandler::connect() {
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::defaultSubscribe() {
|
||||
bool ret = true;
|
||||
if(!subTopic.isEmpty()) {
|
||||
if(mqtt.subscribe(subTopic)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Subscribed to [%s]\n"), subTopic.c_str());
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to [%s]\n"), subTopic.c_str());
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsMqttHandler::disconnect() {
|
||||
mqtt.disconnect();
|
||||
mqtt.loop();
|
||||
_connected = false;
|
||||
delay(10);
|
||||
yield();
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
mqttClient = NULL;
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lwmqtt_err_t AmsMqttHandler::lastError() {
|
||||
@@ -149,12 +190,25 @@ lwmqtt_err_t AmsMqttHandler::lastError() {
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::connected() {
|
||||
return mqtt.connected();
|
||||
return _connected && mqtt.connected();
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::loop() {
|
||||
bool ret = mqtt.loop();
|
||||
delay(10);
|
||||
uint64_t now = millis64();
|
||||
bool ret = connected() && mqtt.loop();
|
||||
if(ret) {
|
||||
lastSuccessfulLoop = now;
|
||||
} else if(mqttConfig.rebootMinutes > 0) {
|
||||
if(now - lastSuccessfulLoop > (uint64_t) mqttConfig.rebootMinutes * 60000) {
|
||||
// Reboot the device if the MQTT connection is lost for too long
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("MQTT connection lost for over %d minutes, rebooting device\n"), mqttConfig.rebootMinutes);
|
||||
rebootSuggested = true;
|
||||
}
|
||||
}
|
||||
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
|
||||
yield();
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
@@ -162,4 +216,8 @@ bool AmsMqttHandler::loop() {
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::isRebootSuggested() {
|
||||
return rebootSuggested;
|
||||
}
|
||||
@@ -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 4096
|
||||
|
||||
|
||||
//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,83 @@ 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, PriceService* ps);
|
||||
void setMqttHandler(AmsMqttHandler* mqttHandler);
|
||||
void update(AmsData& data, EnergyAccounting& ea);
|
||||
void setPriceConfig(PriceServiceConfig&);
|
||||
void setEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
void forceUpdate();
|
||||
void forcePriceUpdate();
|
||||
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;
|
||||
PriceService* ps = NULL;
|
||||
AmsMqttHandler* mqttHandler = NULL;
|
||||
CloudConfig config;
|
||||
PriceServiceConfig priceConfig;
|
||||
unsigned long lastPriceConfig = 0;
|
||||
EnergyAccountingConfig eac;
|
||||
unsigned long lastEac = 0;
|
||||
HTTPClient http;
|
||||
WiFiUDP udp;
|
||||
WiFiClient tcp;
|
||||
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];
|
||||
uint8_t* httpBuffer = NULL;
|
||||
unsigned char encryptedBuffer[256];
|
||||
mbedtls_rsa_context* rsa = nullptr;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_entropy_context entropy;
|
||||
char* pers = "amsreader";
|
||||
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
bool init();
|
||||
|
||||
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,56 +1,630 @@
|
||||
/**
|
||||
* @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);
|
||||
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]);
|
||||
}
|
||||
|
||||
bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc, PriceService* ps) {
|
||||
bool ret = false;
|
||||
#if defined(ESP32)
|
||||
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
|
||||
ESPRandom::uuid4(config.clientId);
|
||||
ret = true;
|
||||
}
|
||||
debugger->flush();
|
||||
//send();
|
||||
uuid = ESPRandom::uuidToString(config.clientId);
|
||||
#endif
|
||||
|
||||
this->config = config;
|
||||
this->hw = hw;
|
||||
this->rdc = rdc;
|
||||
this->ps = ps;
|
||||
|
||||
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::send() {
|
||||
if(rsa != nullptr && mbedtls_rsa_check_pubkey(rsa) == 0) {
|
||||
memset(buf, 0, 4096);
|
||||
void CloudConnector::setMqttHandler(AmsMqttHandler* mqttHandler) {
|
||||
this->mqttHandler = mqttHandler;
|
||||
}
|
||||
|
||||
CloudData data = {65, 127};
|
||||
unsigned char toEncrypt[4096] = {0};
|
||||
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"));
|
||||
|
||||
debugger->println("RSA clear data: ");
|
||||
debugPrint(toEncrypt, 0, 256);
|
||||
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();
|
||||
|
||||
mbedtls_rsa_rsaes_pkcs1_v15_encrypt(rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, 256, toEncrypt, buf);
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
//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");
|
||||
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::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 CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
unsigned long now = millis();
|
||||
if(now-lastUpdate < ((unsigned long)config.interval)*1000) {
|
||||
return;
|
||||
};
|
||||
bool sendFirst = lastUpdate == 0;
|
||||
lastUpdate = now;
|
||||
if(config.enabled) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Enabled\n"));
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Not enabled\n"));
|
||||
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) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) List type not enough data\n"));
|
||||
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"));
|
||||
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());
|
||||
}
|
||||
|
||||
bool sendData = true;
|
||||
if(sendFirst) {
|
||||
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\",\"modifiers\":["), priceConfig.area, priceConfig.currency);
|
||||
if(ps != NULL) {
|
||||
std::vector<PriceConfig> pc = ps->getPriceConfig();
|
||||
if(pc.size() > 0) {
|
||||
for(uint8_t i = 0; i < pc.size(); i++) {
|
||||
PriceConfig& p = pc.at(i);
|
||||
|
||||
String days;
|
||||
for(uint8_t d = 0; d < 7; d++) {
|
||||
if((p.days >> d) & 0x1 == 0x1) {
|
||||
days += String(d, 10) + ",";
|
||||
}
|
||||
}
|
||||
days = days.substring(0, days.length()-1);
|
||||
|
||||
String hours;
|
||||
for(uint8_t h = 0; h < 24; h++) {
|
||||
if((p.hours >> h) & 0x1 == 0x1) {
|
||||
hours += String(h, 10) + ",";
|
||||
}
|
||||
}
|
||||
hours = hours.substring(0, hours.length()-1);
|
||||
|
||||
char start[8];
|
||||
memset(start, 0, 8);
|
||||
if(p.start_dayofmonth > 0 && p.start_month > 0) {
|
||||
snprintf_P(start, 8, PSTR("[%d,%d]"), p.start_month, p.start_dayofmonth);
|
||||
} else {
|
||||
strcpy_P(start, PSTR("null"));
|
||||
}
|
||||
|
||||
char end[8];
|
||||
memset(end, 0, 8);
|
||||
if(p.end_dayofmonth > 0 && p.end_month > 0) {
|
||||
snprintf_P(end, 8, PSTR("[%d,%d]"), p.end_month, p.end_dayofmonth);
|
||||
} else {
|
||||
strcpy_P(end, PSTR("null"));
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"type\":%d,\"name\":\"%s\",\"dir\":%d,\"days\":[%s],\"hours\":[%s],\"value\":%.4f,\"start\":%s,\"end\":%s}%s"),
|
||||
p.type,
|
||||
p.name,
|
||||
p.direction,
|
||||
days.c_str(),
|
||||
hours.c_str(),
|
||||
p.value / 10000.0,
|
||||
start,
|
||||
end,
|
||||
i == pc.size()-1 ? "" : ","
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("]}"));
|
||||
lastPriceConfig = now;
|
||||
sendData = false;
|
||||
} 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;
|
||||
}
|
||||
|
||||
if(sendData) {
|
||||
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) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("RSA is null\n"));
|
||||
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
|
||||
|
||||
Stream *stream = NULL;
|
||||
|
||||
if(config.proto == 0) {
|
||||
udp.beginPacket(config.hostname, config.port);
|
||||
stream = &udp;
|
||||
} else if(config.proto == 1) {
|
||||
if(!tcp.connected()) {
|
||||
int ret = tcp.connect(config.hostname, config.port, 1000);
|
||||
if(ret != 1) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("tcp.connect(%s, %d) return code: %d\n"), config.hostname, config.port, ret);
|
||||
return;
|
||||
}
|
||||
tcp.setTimeout((config.interval * 1000) / 2);
|
||||
}
|
||||
while(tcp.available()) tcp.read(); // Empty incoming buffer
|
||||
stream = &tcp;
|
||||
} else if(config.proto == 2) {
|
||||
if(!http.connected()) {
|
||||
http.setReuse(true);
|
||||
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/data"), config.hostname);
|
||||
if(!http.begin(clearBuffer)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Unable to start HTTP connector\n"));
|
||||
http.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(httpBuffer == NULL) {
|
||||
httpBuffer = (uint8_t*) malloc(CC_BUF_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
int sendBytes = 0;
|
||||
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) {
|
||||
if(stream != NULL) {
|
||||
stream->write(encryptedBuffer, rsa->len);
|
||||
stream->flush();
|
||||
} else {
|
||||
memcpy(httpBuffer + sendBytes, encryptedBuffer, rsa->len);
|
||||
}
|
||||
sendBytes += 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);
|
||||
}
|
||||
}
|
||||
|
||||
if(config.proto == 0) {
|
||||
udp.endPacket();
|
||||
} else if(config.proto == 1) {
|
||||
tcp.write("\r\n");
|
||||
tcp.flush();
|
||||
} else if(config.proto == 2) {
|
||||
http.addHeader("Content-Type", "application/octet-stream");
|
||||
int status = http.POST(httpBuffer, sendBytes);
|
||||
if(status != 200) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Communication error 2, 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();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) %d bytes sent to %s:%d from %s\n"), sendBytes, config.hostname, config.proto == 2 ? 80 : config.port, uuid.c_str());
|
||||
}
|
||||
|
||||
void CloudConnector::forceUpdate() {
|
||||
lastUpdate = 0;
|
||||
lastPriceConfig = 0;
|
||||
lastEac = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::forcePriceUpdate() {
|
||||
lastPriceConfig = 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;
|
||||
}
|
||||
|
||||
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
|
||||
266
lib/ConnectionHandler/src/EthernetConnectionHandler.cpp
Normal file
266
lib/ConnectionHandler/src/EthernetConnectionHandler.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* @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 == 245) { // wESP32
|
||||
ethType = ETH_PHY_RTL8201;
|
||||
ethEnablePin = -1;
|
||||
ethAddr = 0;
|
||||
ethClkMode = ETH_CLOCK_GPIO0_IN;
|
||||
ethPowerPin = -1;
|
||||
ethMdc = 16;
|
||||
ethMdio = 17;
|
||||
} 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"));
|
||||
}
|
||||
}
|
||||
this->config = config;
|
||||
} 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"));
|
||||
}
|
||||
if(config.ipv6 && !ETH.enableIpV6()) {
|
||||
debugger->printf_P(PSTR("Unable to enable IPv6\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 == IPAddress()) {
|
||||
// No IP
|
||||
} else {
|
||||
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
|
||||
424
lib/ConnectionHandler/src/WiFiClientConnectionHandler.cpp
Normal file
424
lib/ConnectionHandler/src/WiFiClientConnectionHandler.cpp
Normal file
@@ -0,0 +1,424 @@
|
||||
/**
|
||||
* @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 == IPAddress()) {
|
||||
// No IP
|
||||
} else {
|
||||
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,17 +12,29 @@
|
||||
|
||||
class DomoticzMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
this->config = config;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
#else
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, DomoticzConfig config, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
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 publishRaw(String data);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(uint8_t* raw, size_t length);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
void setDomoticzConfig(DomoticzConfig config) {
|
||||
this->config = config;
|
||||
}
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
double energy = 0.0;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -79,6 +103,9 @@ uint8_t DomoticzMqttHandler::getFormat() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishRaw(String data) {
|
||||
bool DomoticzMqttHandler::publishRaw(uint8_t* raw, size_t length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DomoticzMqttHandler::onMessage(String &topic, String &payload) {
|
||||
}
|
||||
|
||||
@@ -1,17 +1,47 @@
|
||||
/**
|
||||
* @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;
|
||||
uint8_t hour;
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
struct EnergyAccountingPeak6 {
|
||||
uint8_t day;
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
struct EnergyAccountingData {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
int32_t costToday;
|
||||
int32_t costYesterday;
|
||||
int32_t costThisMonth;
|
||||
int32_t costLastMonth;
|
||||
int32_t incomeToday;
|
||||
int32_t incomeYesterday;
|
||||
int32_t incomeThisMonth;
|
||||
int32_t incomeLastMonth;
|
||||
uint32_t lastMonthImport;
|
||||
uint32_t lastMonthExport;
|
||||
uint8_t lastMonthAccuracy;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
time_t lastUpdated;
|
||||
};
|
||||
|
||||
struct EnergyAccountingData6 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
int32_t costYesterday;
|
||||
@@ -23,37 +53,7 @@ struct EnergyAccountingData {
|
||||
uint32_t lastMonthImport;
|
||||
uint32_t lastMonthExport;
|
||||
uint8_t lastMonthAccuracy;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData5 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
uint16_t incomeYesterday;
|
||||
uint16_t incomeThisMonth;
|
||||
uint16_t incomeLastMonth;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData4 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData2 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t maxHour;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
EnergyAccountingPeak6 peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingRealtimeData {
|
||||
@@ -74,9 +74,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,23 +117,25 @@ public:
|
||||
EnergyAccountingData getData();
|
||||
void setData(EnergyAccountingData&);
|
||||
|
||||
void setFixedPrice(float price, String currency);
|
||||
float getPriceForHour(uint8_t h);
|
||||
void setCurrency(String currency);
|
||||
|
||||
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 };
|
||||
EnergyAccountingData data = { 0, 0, 0, 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();
|
||||
bool updateMax(uint16_t val, uint8_t day);
|
||||
bool updateMax(uint16_t val, uint8_t day, uint8_t hour);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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) {
|
||||
@@ -20,7 +30,7 @@ EnergyAccounting::EnergyAccounting(RemoteDebug* debugger, EnergyAccountingRealti
|
||||
rtd->lastImportUpdateMillis = 0;
|
||||
rtd->lastExportUpdateMillis = 0;
|
||||
}
|
||||
this->realtimeData = rtd;
|
||||
realtimeData = rtd;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config) {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -58,76 +67,59 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(!init) {
|
||||
this->realtimeData->lastImportUpdateMillis = 0;
|
||||
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);
|
||||
realtimeData->lastImportUpdateMillis = 0;
|
||||
realtimeData->lastExportUpdateMillis = 0;
|
||||
realtimeData->currentHour = local.Hour;
|
||||
realtimeData->currentDay = local.Day;
|
||||
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
|
||||
data = { 7, local.Month,
|
||||
0, 0, 0, 0, // Cost
|
||||
0, 0, 0, 0, // Income
|
||||
0, 0, 0, // Last month import, export and accuracy
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
0, 0, 0, // Peak 1
|
||||
0, 0, 0, // Peak 2
|
||||
0, 0, 0, // Peak 3
|
||||
0, 0, 0, // Peak 4
|
||||
0, 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);
|
||||
if(!initPrice && ps != NULL && ps->hasPrice()) {
|
||||
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);
|
||||
|
||||
if(local.Hour != realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
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);
|
||||
ret |= updateMax(val, oneHrAgoLocal.Day, oneHrAgoLocal.Hour);
|
||||
|
||||
this->realtimeData->currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
|
||||
if(local.Hour > 0) {
|
||||
calcDayCost();
|
||||
}
|
||||
realtimeData->currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
|
||||
|
||||
this->realtimeData->use = 0;
|
||||
this->realtimeData->produce = 0;
|
||||
this->realtimeData->costHour = 0;
|
||||
this->realtimeData->incomeHour = 0;
|
||||
realtimeData->use = 0;
|
||||
realtimeData->produce = 0;
|
||||
realtimeData->costHour = 0;
|
||||
realtimeData->incomeHour = 0;
|
||||
|
||||
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;
|
||||
uint8_t prevDay = realtimeData->currentDay;
|
||||
if(local.Day != realtimeData->currentDay) {
|
||||
data.costYesterday = realtimeData->costDay * 100;
|
||||
data.costThisMonth += realtimeData->costDay * 100;
|
||||
realtimeData->costDay = 0;
|
||||
|
||||
data.incomeYesterday = this->realtimeData->incomeDay * 100;
|
||||
data.incomeThisMonth += this->realtimeData->incomeDay * 100;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
data.incomeYesterday = realtimeData->incomeDay * 100;
|
||||
data.incomeThisMonth += realtimeData->incomeDay * 100;
|
||||
realtimeData->incomeDay = 0;
|
||||
|
||||
this->realtimeData->currentDay = local.Day;
|
||||
realtimeData->currentDay = local.Day;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -153,47 +145,49 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
data.lastMonthAccuracy = accuracy;
|
||||
|
||||
data.month = local.Month;
|
||||
this->realtimeData->currentThresholdIdx = 0;
|
||||
realtimeData->currentThresholdIdx = 0;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if(ret) {
|
||||
data.costToday = realtimeData->costDay * 100;
|
||||
data.incomeToday = realtimeData->incomeDay * 100;
|
||||
data.lastUpdated = now;
|
||||
}
|
||||
}
|
||||
|
||||
if(this->realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastImportUpdateMillis;
|
||||
if(realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - 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());
|
||||
this->realtimeData->costHour += cost;
|
||||
this->realtimeData->costDay += cost;
|
||||
realtimeData->use += kwhi;
|
||||
float importPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_IMPORT);
|
||||
if(importPrice != PRICE_NO_VALUE) {
|
||||
float cost = importPrice * kwhi;
|
||||
realtimeData->costHour += cost;
|
||||
realtimeData->costDay += cost;
|
||||
}
|
||||
}
|
||||
this->realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
}
|
||||
|
||||
if(amsData->getListType() > 1 && this->realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastExportUpdateMillis;
|
||||
if(amsData->getListType() > 1 && realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - realtimeData->lastExportUpdateMillis;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(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());
|
||||
this->realtimeData->incomeHour += income;
|
||||
this->realtimeData->incomeDay += income;
|
||||
realtimeData->produce += kwhe;
|
||||
float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_EXPORT);
|
||||
if(exportPrice != PRICE_NO_VALUE) {
|
||||
float income = exportPrice * kwhe;
|
||||
realtimeData->incomeHour += income;
|
||||
realtimeData->incomeDay += income;
|
||||
}
|
||||
}
|
||||
this->realtimeData->lastExportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
realtimeData->lastExportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
}
|
||||
|
||||
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);
|
||||
while(getMonthMax() > config->thresholds[realtimeData->currentThresholdIdx] && realtimeData->currentThresholdIdx < 10) realtimeData->currentThresholdIdx++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -201,31 +195,44 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
|
||||
void EnergyAccounting::calcDayCost() {
|
||||
time_t now = time(nullptr);
|
||||
tmElements_t local, utc;
|
||||
tmElements_t local, utc, lastUpdateUtc;
|
||||
if(tz == NULL) return;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
if(ps == NULL) return;
|
||||
|
||||
if(getPriceForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(initPrice) {
|
||||
this->realtimeData->costDay = 0;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
if(ps->hasPrice()) {
|
||||
breakTime(data.lastUpdated, lastUpdateUtc);
|
||||
uint8_t calcFromHour = 0;
|
||||
if(lastUpdateUtc.Day != local.Day || lastUpdateUtc.Month != local.Month || lastUpdateUtc.Year != local.Year) {
|
||||
realtimeData->costDay = 0;
|
||||
realtimeData->incomeDay = 0;
|
||||
calcFromHour = 0;
|
||||
} else {
|
||||
realtimeData->costDay = data.costToday / 100.0;
|
||||
realtimeData->incomeDay = data.incomeToday / 100.0;
|
||||
calcFromHour = lastUpdateUtc.Hour;
|
||||
}
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
float price = getPriceForHour(i - local.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
for(uint8_t i = calcFromHour; i < realtimeData->currentHour; i++) {
|
||||
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 = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i - local.Hour);
|
||||
if(priceIn != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
realtimeData->costDay += priceIn * (wh / 1000.0);
|
||||
}
|
||||
|
||||
float priceOut = ps->getPriceForRelativeHour(PRICE_DIRECTION_EXPORT, i - local.Hour);
|
||||
if(priceOut != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourExport(utc.Hour);
|
||||
realtimeData->incomeDay += priceOut * (wh / 1000.0);
|
||||
}
|
||||
}
|
||||
initPrice = true;
|
||||
}
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseThisHour() {
|
||||
return this->realtimeData->use;
|
||||
return realtimeData->use;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseToday() {
|
||||
@@ -235,7 +242,7 @@ float EnergyAccounting::getUseToday() {
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
for(uint8_t i = 0; i < realtimeData->currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourImport(utc.Hour) / 1000.0;
|
||||
}
|
||||
@@ -246,18 +253,20 @@ float EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseLastMonth() {
|
||||
return (data.lastMonthImport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
float ret = (data.lastMonthImport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
if(std::isnan(ret)) return 0.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedThisHour() {
|
||||
return this->realtimeData->produce;
|
||||
return realtimeData->produce;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedToday() {
|
||||
@@ -267,7 +276,7 @@ float EnergyAccounting::getProducedToday() {
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
for(uint8_t i = 0; i < realtimeData->currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourExport(utc.Hour) / 1000.0;
|
||||
}
|
||||
@@ -278,22 +287,24 @@ float EnergyAccounting::getProducedThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayExport(i) / 1000.0;
|
||||
}
|
||||
return ret + getProducedToday();
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedLastMonth() {
|
||||
return (data.lastMonthExport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
float ret = (data.lastMonthExport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
if(std::isnan(ret)) return 0.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostThisHour() {
|
||||
return this->realtimeData->costHour;
|
||||
return realtimeData->costHour;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostToday() {
|
||||
return this->realtimeData->costDay;
|
||||
return realtimeData->costDay;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostYesterday() {
|
||||
@@ -309,11 +320,11 @@ float EnergyAccounting::getCostLastMonth() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeThisHour() {
|
||||
return this->realtimeData->incomeHour;
|
||||
return realtimeData->incomeHour;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeToday() {
|
||||
return this->realtimeData->incomeDay;
|
||||
return realtimeData->incomeDay;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeYesterday() {
|
||||
@@ -331,7 +342,7 @@ float EnergyAccounting::getIncomeLastMonth() {
|
||||
uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
if(config == NULL)
|
||||
return 0;
|
||||
return config->thresholds[this->realtimeData->currentThresholdIdx];
|
||||
return config->thresholds[realtimeData->currentThresholdIdx];
|
||||
}
|
||||
|
||||
float EnergyAccounting::getMonthMax() {
|
||||
@@ -402,9 +413,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,92 +422,34 @@ 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) {
|
||||
if(buf[0] == 7) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
ret = true;
|
||||
} else if(buf[0] == 5) {
|
||||
EnergyAccountingData5* data = (EnergyAccountingData5*) buf;
|
||||
this->data = { 6, data->month,
|
||||
((uint32_t) data->costYesterday) * 10,
|
||||
((uint32_t) data->costThisMonth) * 100,
|
||||
((uint32_t) data->costLastMonth) * 100,
|
||||
((uint32_t) data->incomeYesterday) * 10,
|
||||
((uint32_t) data->incomeThisMonth) * 100,
|
||||
((uint32_t) data->incomeLastMonth) * 100,
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else if(buf[0] == 4) {
|
||||
EnergyAccountingData4* data = (EnergyAccountingData4*) buf;
|
||||
this->data = { 5, data->month,
|
||||
((uint32_t) data->costYesterday) * 10,
|
||||
((uint32_t) data->costThisMonth) * 100,
|
||||
((uint32_t) data->costLastMonth) * 100,
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else if(buf[0] == 3) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
this->data = { 5, data->month,
|
||||
data->costYesterday * 10,
|
||||
} else if(buf[0] == 6) {
|
||||
EnergyAccountingData6* data = (EnergyAccountingData6*) buf;
|
||||
this->data = { 7, data->month,
|
||||
0, // Cost today
|
||||
data->costYesterday,
|
||||
data->costThisMonth,
|
||||
data->costLastMonth,
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
0, // Income today
|
||||
data->incomeYesterday,
|
||||
data->incomeThisMonth,
|
||||
data->incomeLastMonth,
|
||||
data->lastMonthImport,
|
||||
data->lastMonthExport,
|
||||
data->lastMonthAccuracy,
|
||||
data->peaks[0].day, 0, data->peaks[0].value,
|
||||
data->peaks[1].day, 0, data->peaks[1].value,
|
||||
data->peaks[2].day, 0, data->peaks[2].value,
|
||||
data->peaks[3].day, 0, data->peaks[3].value,
|
||||
data->peaks[4].day, 0, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else {
|
||||
data = { 5, 0,
|
||||
0, 0, 0, // Cost
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
if(buf[0] == 2) {
|
||||
EnergyAccountingData2* data = (EnergyAccountingData2*) buf;
|
||||
this->data.month = data->month;
|
||||
this->data.costYesterday = data->costYesterday * 10;
|
||||
this->data.costThisMonth = data->costThisMonth;
|
||||
this->data.costLastMonth = data->costLastMonth;
|
||||
uint8_t b = 0;
|
||||
for(uint8_t i = sizeof(this->data); i < file.size(); i+=2) {
|
||||
this->data.peaks[b].day = b;
|
||||
memcpy(&this->data.peaks[b].value, buf+i, 2);
|
||||
b++;
|
||||
if(b >= config->hours || b >= 5) break;
|
||||
}
|
||||
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 +457,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;
|
||||
}
|
||||
{
|
||||
@@ -532,12 +479,12 @@ void EnergyAccounting::setData(EnergyAccountingData& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
bool EnergyAccounting::updateMax(uint16_t val, uint8_t day, uint8_t hour) {
|
||||
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].hour = hour;
|
||||
data.peaks[i].value = val;
|
||||
return true;
|
||||
}
|
||||
@@ -555,7 +502,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 +509,6 @@ 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);
|
||||
}
|
||||
@@ -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,9 +0,0 @@
|
||||
#ifndef _PRICESCONTAINER_H
|
||||
#define _PRICESCONTAINER_H
|
||||
struct PricesContainer {
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
int32_t points[25];
|
||||
char source[4];
|
||||
};
|
||||
#endif
|
||||
@@ -1,468 +0,0 @@
|
||||
#include "EntsoeApi.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Uptime.h"
|
||||
#include "TimeLib.h"
|
||||
#include "DnbCurrParser.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#include "GcmParser.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
|
||||
this->buf = (char*) malloc(BufferSize);
|
||||
|
||||
debugger = Debug;
|
||||
|
||||
// Entso-E uses CET/CEST
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
tz = new Timezone(CEST, CET);
|
||||
|
||||
tomorrowFetchMinute = 15 + random(45); // Random between 13:15 and 14:00
|
||||
}
|
||||
|
||||
void EntsoeApi::setup(EntsoeConfig& config) {
|
||||
if(this->config == NULL) {
|
||||
this->config = new EntsoeConfig();
|
||||
}
|
||||
memcpy(this->config, &config, sizeof(config));
|
||||
lastTodayFetch = lastTomorrowFetch = lastCurrencyFetch = 0;
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) delete tomorrow;
|
||||
today = tomorrow = NULL;
|
||||
|
||||
if(http != NULL) {
|
||||
delete http;
|
||||
}
|
||||
http = new HTTPClient();
|
||||
http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http->setReuse(false);
|
||||
http->setTimeout(60000);
|
||||
http->setUserAgent("ams2mqtt/" + String(FirmwareVersion::VersionString));
|
||||
|
||||
#if defined(AMS2MQTT_PRICE_KEY)
|
||||
key = new uint8_t[16] AMS2MQTT_PRICE_KEY;
|
||||
hub = true;
|
||||
#else
|
||||
hub = false;
|
||||
#endif
|
||||
#if defined(AMS2MQTT_PRICE_AUTHENTICATION)
|
||||
auth = new uint8_t[16] AMS2MQTT_PRICE_AUTHENTICATION;
|
||||
hub = hub && true;
|
||||
#else
|
||||
hub = false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
char* EntsoeApi::getToken() {
|
||||
return this->config->token;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getCurrency() {
|
||||
return this->config->currency;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getArea() {
|
||||
return this->config->area;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getSource() {
|
||||
if(this->today != NULL && this->tomorrow != NULL) {
|
||||
if(strcmp(this->today->source, this->tomorrow->source) == 0) {
|
||||
return this->today->source;
|
||||
} else {
|
||||
return "MIX";
|
||||
}
|
||||
} else if(today != NULL) {
|
||||
return this->today->source;
|
||||
} else if(tomorrow != NULL) {
|
||||
return this->tomorrow->source;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
float EntsoeApi::getValueForHour(int8_t hour) {
|
||||
time_t cur = time(nullptr);
|
||||
return getValueForHour(cur, hour);
|
||||
}
|
||||
|
||||
float EntsoeApi::getValueForHour(time_t ts, int8_t hour) {
|
||||
tmElements_t tm;
|
||||
int8_t pos = hour;
|
||||
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
while(tm.Hour > 0) {
|
||||
ts -= 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
pos++;
|
||||
}
|
||||
uint8_t hoursToday = 0;
|
||||
uint8_t todayDate = tm.Day;
|
||||
while(tm.Day == todayDate) {
|
||||
ts += 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
hoursToday++;
|
||||
}
|
||||
if(pos > 49)
|
||||
return ENTSOE_NO_VALUE;
|
||||
|
||||
float value = ENTSOE_NO_VALUE;
|
||||
float multiplier = config->multiplier / 1000.0;
|
||||
if(pos >= hoursToday) {
|
||||
if(tomorrow == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
if(tomorrow->points[pos-hoursToday] == ENTSOE_NO_VALUE)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = tomorrow->points[pos-hoursToday] / 10000.0;
|
||||
if(strcmp(tomorrow->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(tomorrow->currency, config->currency, time(nullptr));
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
} else if(pos >= 0) {
|
||||
if(today == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
if(today->points[pos] == ENTSOE_NO_VALUE)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = today->points[pos] / 10000.0;
|
||||
if(strcmp(today->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(today->measurementUnit, "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(today->currency, config->currency, time(nullptr));
|
||||
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
}
|
||||
return value * multiplier;
|
||||
}
|
||||
|
||||
bool EntsoeApi::loop() {
|
||||
uint64_t now = millis64();
|
||||
if(now < 10000) return false; // Grace period
|
||||
|
||||
time_t t = time(nullptr);
|
||||
if(t < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
#ifndef AMS2MQTT_PRICE_KEY
|
||||
if(strlen(getToken()) == 0) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if(!config->enabled)
|
||||
return false;
|
||||
if(strlen(config->area) == 0)
|
||||
return false;
|
||||
if(strlen(config->currency) == 0)
|
||||
return false;
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
|
||||
if(currentDay == 0) {
|
||||
currentDay = tm.Day;
|
||||
currentHour = tm.Hour;
|
||||
}
|
||||
|
||||
if(currentDay != tm.Day) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EntsoeApi) Rotating price objects at %lu\n"), t);
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) {
|
||||
today = tomorrow;
|
||||
tomorrow = NULL;
|
||||
}
|
||||
currentDay = tm.Day;
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
} else if(currentHour != tm.Hour) {
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
bool readyToFetchForTomorrow = tomorrow == NULL && (tm.Hour > 13 || (tm.Hour == 13 && tm.Minute >= tomorrowFetchMinute)) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > (nextFetchDelayMinutes*60000));
|
||||
|
||||
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > (nextFetchDelayMinutes*60000))) {
|
||||
try {
|
||||
lastTodayFetch = now;
|
||||
today = fetchPrices(t);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) {
|
||||
lastError = 900;
|
||||
nextFetchDelayMinutes = 60;
|
||||
}
|
||||
today = NULL;
|
||||
}
|
||||
return today != NULL && !readyToFetchForTomorrow; // Only trigger MQTT publish if we have todays prices and we are not immediately ready to fetch price for tomorrow.
|
||||
}
|
||||
|
||||
// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
|
||||
// fetch with one hour (with some random delay) and retry every 15 minutes
|
||||
if(readyToFetchForTomorrow) {
|
||||
try {
|
||||
lastTomorrowFetch = now;
|
||||
tomorrow = fetchPrices(t+SECS_PER_DAY);
|
||||
} catch(const std::exception& e) {
|
||||
if(lastError == 0) {
|
||||
lastError = 900;
|
||||
nextFetchDelayMinutes = 60;
|
||||
}
|
||||
tomorrow = NULL;
|
||||
}
|
||||
return tomorrow != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
#if defined(ESP32)
|
||||
if(http->begin(url)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Connection established\n"));
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
int status = http->GET();
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(status == HTTP_CODE_OK) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Receiving data\n"));
|
||||
http->writeToStream(doc);
|
||||
http->end();
|
||||
lastError = 0;
|
||||
nextFetchDelayMinutes = 1;
|
||||
return true;
|
||||
} else {
|
||||
lastError = status;
|
||||
if(status == 429) {
|
||||
nextFetchDelayMinutes = 15;
|
||||
} else if(status == 404) {
|
||||
nextFetchDelayMinutes = 10;
|
||||
} else {
|
||||
nextFetchDelayMinutes = 2;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(EntsoeApi) Communication error, returned status: %d\n"), status);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(http->errorToString(status).c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http->getString().c_str());
|
||||
|
||||
http->end();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to, time_t t) {
|
||||
if(strcmp(from, to) == 0)
|
||||
return 1.00;
|
||||
|
||||
uint64_t now = millis64();
|
||||
if(now > lastCurrencyFetch && (lastCurrencyFetch == 0 || (now - lastCurrencyFetch) > 60000)) {
|
||||
lastCurrencyFetch = now;
|
||||
|
||||
DnbCurrParser p;
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
float currencyMultiplier = 0;
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), from);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EntsoeApi) Retrieving %s to NOK conversion\n"), from);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) url: %s\n"), buf);
|
||||
if(retrieve(buf, &p)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) got exchange rate %.4f\n"), p.getValue());
|
||||
currencyMultiplier = p.getValue();
|
||||
if(strncmp(to, "NOK", 3) != 0) {
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), to);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EntsoeApi) Retrieving %s to NOK conversion\n"), to);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) url: %s\n"), buf);
|
||||
if(retrieve(buf, &p)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) got exchange rate %.4f\n"), p.getValue());
|
||||
if(p.getValue() > 0.0) {
|
||||
currencyMultiplier /= p.getValue();
|
||||
} else {
|
||||
currencyMultiplier = 0;
|
||||
}
|
||||
} else {
|
||||
currencyMultiplier = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(currencyMultiplier != 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) Resulting currency multiplier: %.4f\n"), currencyMultiplier);
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000) + (3600000 * 6) + (tomorrowFetchMinute * 60);
|
||||
this->currencyMultiplier = currencyMultiplier;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(EntsoeApi) Multiplier ended in success, but without value\n"));
|
||||
lastCurrencyFetch = now + (SECS_PER_HOUR * 1000);
|
||||
if(this->currencyMultiplier == 1) return 0;
|
||||
}
|
||||
}
|
||||
return currencyMultiplier;
|
||||
}
|
||||
|
||||
PricesContainer* EntsoeApi::fetchPrices(time_t t) {
|
||||
if(strlen(getToken()) > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // Local midnight
|
||||
time_t e2 = e1 + SECS_PER_DAY;
|
||||
tmElements_t d1, d2;
|
||||
breakTime(e1, d1);
|
||||
breakTime(e2, d2);
|
||||
|
||||
snprintf_P(buf, BufferSize, PSTR("https://web-api.tp.entsoe.eu/api?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s"),
|
||||
getToken(),
|
||||
d1.Year+1970, d1.Month, d1.Day, d1.Hour, 00,
|
||||
d2.Year+1970, d2.Month, d2.Day, d2.Hour, 00,
|
||||
config->area, config->area);
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EntsoeApi) Fetching prices for %d.%d.%d\n"), tm.Day, tm.Month, tm.Year+1970);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) url: %s\n"), buf);
|
||||
EntsoeA44Parser a44;
|
||||
if(retrieve(buf, &a44) && a44.getPoint(0) != ENTSOE_NO_VALUE) {
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
a44.get(ret);
|
||||
return ret;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
} else if(hub) {
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
|
||||
String data;
|
||||
snprintf_P(buf, BufferSize, PSTR("http://hub.amsleser.no/hub/price/%s/%d/%d/%d?currency=%s"),
|
||||
config->area,
|
||||
tm.Year+1970,
|
||||
tm.Month,
|
||||
tm.Day,
|
||||
config->currency
|
||||
);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
if(http->begin(client, buf)) {
|
||||
#elif defined(ESP32)
|
||||
if(http->begin(buf)) {
|
||||
#endif
|
||||
int status = http->GET();
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(status == HTTP_CODE_OK) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Receiving data\n"));
|
||||
data = http->getString();
|
||||
http->end();
|
||||
|
||||
uint8_t* content = (uint8_t*) (data.c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received content for prices:\n"));
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
|
||||
DataParserContext ctx = {0,0,0,0};
|
||||
ctx.length = data.length();
|
||||
GCMParser gcm(key, auth);
|
||||
int8_t gcmRet = gcm.parse(content, ctx);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Decrypted content for prices:\n"));
|
||||
debugPrint(content, 0, data.length());
|
||||
}
|
||||
if(gcmRet > 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EntsoeApi) Price data starting at: %d\n"), gcmRet);
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
ret->points[i] = ENTSOE_NO_VALUE;
|
||||
}
|
||||
memcpy(ret, content+gcmRet, sizeof(*ret));
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
ret->points[i] = ntohl(ret->points[i]);
|
||||
}
|
||||
lastError = 0;
|
||||
nextFetchDelayMinutes = 1;
|
||||
return ret;
|
||||
} else {
|
||||
lastError = gcmRet;
|
||||
nextFetchDelayMinutes = 60;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(EntsoeApi) Error code while decrypting prices: %d\n"), gcmRet);
|
||||
}
|
||||
} else {
|
||||
lastError = status;
|
||||
if(status == 429) {
|
||||
nextFetchDelayMinutes = 60;
|
||||
} else if(status == 404) {
|
||||
nextFetchDelayMinutes = 15;
|
||||
} else {
|
||||
nextFetchDelayMinutes = 5;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(EntsoeApi) Communication error, returned status: %d\n"), status);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf(http->errorToString(status).c_str());
|
||||
debugger->println();
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http->getString().c_str());
|
||||
|
||||
http->end();
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void EntsoeApi::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(""));
|
||||
}
|
||||
|
||||
int16_t EntsoeApi::getLastError() {
|
||||
return lastError;
|
||||
}
|
||||
@@ -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,67 +1,44 @@
|
||||
/**
|
||||
* @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:
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw, AmsFirmwareUpdater* updater, char* hostname) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#else
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw, AmsFirmwareUpdater* updater, char* hostname) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#endif
|
||||
this->boardType = boardType;
|
||||
this->hw = hw;
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
|
||||
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
|
||||
if(strlen(config.discoveryNameTag) > 0) {
|
||||
snprintf_P(buf, 128, PSTR("AMS reader (%s)"), config.discoveryNameTag);
|
||||
deviceName = String(buf);
|
||||
snprintf_P(buf, 128, PSTR("[%s] "), config.discoveryNameTag);
|
||||
sensorNamePrefix = String(buf);
|
||||
} else {
|
||||
deviceName = F("AMS reader");
|
||||
sensorNamePrefix = "";
|
||||
}
|
||||
deviceModel = boardTypeToString(boardType);
|
||||
manufacturer = boardManufacturerToString(boardType);
|
||||
|
||||
#if defined(ESP8266)
|
||||
String hostname = WiFi.hostname();
|
||||
#elif defined(ESP32)
|
||||
String hostname = WiFi.getHostname();
|
||||
#endif
|
||||
|
||||
deviceUid = hostname; // Maybe configurable in the future?
|
||||
|
||||
if(strlen(config.discoveryHostname) > 0) {
|
||||
if(strncmp_P(config.discoveryHostname, PSTR("http"), 4) == 0) {
|
||||
deviceUrl = String(config.discoveryHostname);
|
||||
} else {
|
||||
snprintf_P(buf, 128, PSTR("http://%s/"), config.discoveryHostname);
|
||||
deviceUrl = String(buf);
|
||||
}
|
||||
} else {
|
||||
snprintf_P(buf, 128, PSTR("http://%s.local/"), hostname);
|
||||
deviceUrl = String(buf);
|
||||
}
|
||||
|
||||
if(strlen(config.discoveryPrefix) > 0) {
|
||||
snprintf_P(buf, 128, PSTR("%s/sensor/"), config.discoveryPrefix);
|
||||
discoveryTopic = String(buf);
|
||||
} else {
|
||||
discoveryTopic = F("homeassistant/sensor/");
|
||||
}
|
||||
setHomeAssistantConfig(config, hostname);
|
||||
};
|
||||
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 publishRaw(String data);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(uint8_t* raw, size_t length);
|
||||
bool publishFirmware();
|
||||
|
||||
bool postConnect();
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
void setHomeAssistantConfig(HomeAssistantConfig config, char* hostname);
|
||||
private:
|
||||
String topic;
|
||||
uint8_t boardType;
|
||||
|
||||
String deviceName;
|
||||
String deviceModel;
|
||||
@@ -69,12 +46,15 @@ private:
|
||||
String manufacturer;
|
||||
String deviceUrl;
|
||||
|
||||
String discoveryTopic;
|
||||
String statusTopic;
|
||||
String sensorTopic;
|
||||
String updateTopic;
|
||||
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, fInit, dInit;
|
||||
bool tInit[32] = {false};
|
||||
bool prInit[38] = {false};
|
||||
uint8_t priceImportInit = 0, priceExportInit = 0;
|
||||
uint32_t lastThresholdPublish = 0;
|
||||
|
||||
HwTools* hw;
|
||||
|
||||
@@ -83,8 +63,8 @@ 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);
|
||||
void publishSensor(const HomeAssistantSensor& sensor);
|
||||
bool publishRealtime(AmsData* data, EnergyAccounting* ea, PriceService* ps);
|
||||
void publishSensor(const HomeAssistantSensor sensor);
|
||||
void publishList1Sensors();
|
||||
void publishList1ExportSensors();
|
||||
void publishList2Sensors();
|
||||
@@ -93,11 +73,13 @@ 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();
|
||||
void toJsonIsoTimestamp(time_t t, char* buf, size_t buflen);
|
||||
|
||||
String boardTypeToString(uint8_t b) {
|
||||
switch(b) {
|
||||
@@ -145,6 +127,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
|
||||
|
||||
@@ -7,106 +13,117 @@ struct HomeAssistantSensor {
|
||||
const char* name;
|
||||
const char* topic;
|
||||
const char* path;
|
||||
uint16_t ttl;
|
||||
const char* uom;
|
||||
const char* devcl;
|
||||
const char* stacl;
|
||||
const char* uid;
|
||||
};
|
||||
|
||||
|
||||
const uint8_t List1SensorCount PROGMEM = 1;
|
||||
const uint8_t List1SensorCount PROGMEM = 2;
|
||||
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
|
||||
{"Active import", "/power", "P", "W", "power", "measurement"}
|
||||
{"Active import", "/power", "P", 30, "W", "power", "measurement", ""},
|
||||
{"Data timestamp", "/power", "t", 30, "", "timestamp", "", ""}
|
||||
};
|
||||
|
||||
const uint8_t List2SensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor List2Sensors[List2SensorCount] PROGMEM = {
|
||||
{"Reactive import", "/power", "Q", "var", "reactive_power", "measurement"},
|
||||
{"Reactive export", "/power", "QO", "var", "reactive_power", "measurement"},
|
||||
{"L1 current", "/power", "I1", "A", "current", "measurement"},
|
||||
{"L2 current", "/power", "I2", "A", "current", "measurement"},
|
||||
{"L3 current", "/power", "I3", "A", "current", "measurement"},
|
||||
{"L1 voltage", "/power", "U1", "V", "voltage", "measurement"},
|
||||
{"L2 voltage", "/power", "U2", "V", "voltage", "measurement"},
|
||||
{"L3 voltage", "/power", "U3", "V", "voltage", "measurement"}
|
||||
{"Reactive import", "/power", "Q", 30, "var", "reactive_power", "measurement", ""},
|
||||
{"Reactive export", "/power", "QO", 30, "var", "reactive_power", "measurement", ""},
|
||||
{"L1 current", "/power", "I1", 30, "A", "current", "measurement", ""},
|
||||
{"L2 current", "/power", "I2", 30, "A", "current", "measurement", ""},
|
||||
{"L3 current", "/power", "I3", 30, "A", "current", "measurement", ""},
|
||||
{"L1 voltage", "/power", "U1", 30, "V", "voltage", "measurement", ""},
|
||||
{"L2 voltage", "/power", "U2", 30, "V", "voltage", "measurement", ""},
|
||||
{"L3 voltage", "/power", "U3", 30, "V", "voltage", "measurement", ""}
|
||||
};
|
||||
|
||||
const uint8_t List2ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = {
|
||||
{"Active export", "/power", "PO", "W", "power", "measurement"}
|
||||
{"Active export", "/power", "PO", 30, "W", "power", "measurement", ""}
|
||||
};
|
||||
|
||||
const uint8_t List3SensorCount PROGMEM = 3;
|
||||
const uint8_t List3SensorCount PROGMEM = 4;
|
||||
const HomeAssistantSensor List3Sensors[List3SensorCount] PROGMEM = {
|
||||
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "total_increasing"},
|
||||
{"Accumulated reactive import","/energy", "tQI", "kvarh","", "total_increasing"},
|
||||
{"Accumulated reactive export","/energy", "tQO", "kvarh","", "total_increasing"}
|
||||
{"Accumulated active import", "/energy", "tPI", 4000, "kWh", "energy", "total_increasing", ""},
|
||||
{"Accumulated reactive import","/energy", "tQI", 4000, "kvarh","", "total_increasing", ""},
|
||||
{"Accumulated reactive export","/energy", "tQO", 4000, "kvarh","", "total_increasing", ""},
|
||||
{"Meter timestamp", "/energy", "rtc", 4000, "", "timestamp", "", ""}
|
||||
};
|
||||
|
||||
const uint8_t List3ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = {
|
||||
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "total_increasing"}
|
||||
{"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", "%", "power_factor", "measurement"},
|
||||
{"L1 power factor", "/power", "PF1", "%", "power_factor", "measurement"},
|
||||
{"L2 power factor", "/power", "PF2", "%", "power_factor", "measurement"},
|
||||
{"L3 power factor", "/power", "PF3", "%", "power_factor", "measurement"},
|
||||
{"L1 active import", "/power", "P1", "W", "power", "measurement"},
|
||||
{"L2 active import", "/power", "P2", "W", "power", "measurement"},
|
||||
{"L3 active import", "/power", "P3", "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", "W", "power", "measurement"},
|
||||
{"L2 active export", "/power", "PO2", "W", "power", "measurement"},
|
||||
{"L3 active export", "/power", "PO3", "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", "kWh", "energy", "total_increasing"},
|
||||
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "total_increasing"},
|
||||
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "total_increasing"},
|
||||
{"Current hour cost", "/realtime","hour.cost", "", "monetary", ""},
|
||||
{"Current day used", "/realtime","day.use", "kWh", "energy", "total_increasing"},
|
||||
{"Current day cost", "/realtime","day.cost", "", "monetary", ""},
|
||||
{"Current month used", "/realtime","month.use", "kWh", "energy", "total_increasing"},
|
||||
{"Current month cost", "/realtime","month.cost", "", "monetary", ""}
|
||||
{"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", ""},
|
||||
{"Current day cost", "/realtime","day.cost", 120, "", "monetary", "", ""},
|
||||
{"Current month used", "/realtime","month.use", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current month cost", "/realtime","month.cost", 120, "", "monetary", "", ""}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeExportSensorCount PROGMEM = 6;
|
||||
const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGMEM = {
|
||||
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "total_increasing"},
|
||||
{"Current hour income", "/realtime","hour.income", "", "monetary", ""},
|
||||
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "total_increasing"},
|
||||
{"Current day income", "/realtime","day.income", "", "monetary", ""},
|
||||
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "total_increasing"},
|
||||
{"Current month income", "/realtime","month.income", "", "monetary", ""}
|
||||
{"Current hour produced", "/realtime","hour.produced", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current hour income", "/realtime","hour.income", 120, "", "monetary", "", ""},
|
||||
{"Current day produced", "/realtime","day.produced", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current day income", "/realtime","day.income", 120, "", "monetary", "", ""},
|
||||
{"Current month produced", "/realtime","month.produced", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current month income", "/realtime","month.income", 120, "", "monetary", "", ""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", "kWh", "energy", ""};
|
||||
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 = {
|
||||
{"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""},
|
||||
{"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""},
|
||||
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""},
|
||||
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""}
|
||||
{"Minimum price ahead", "/prices", "prices.min", 4000, "", "monetary", "", ""},
|
||||
{"Maximum price ahead", "/prices", "prices.max", 4000, "", "monetary", "", ""},
|
||||
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr", 4000, "", "timestamp", "", ""},
|
||||
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr", 4000, "", "timestamp", "", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr", 4000, "", "timestamp", "", ""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices", "prices['%d']", "", "monetary", ""};
|
||||
|
||||
const uint8_t SystemSensorCount PROGMEM = 2;
|
||||
const uint8_t SystemSensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
|
||||
{"Status", "/state", "rssi", "dBm", "signal_strength", "measurement"},
|
||||
{"Supply volt", "/state", "vcc", "V", "voltage", "measurement"}
|
||||
{"Status", "/state", "rssi", 180, "dBm", "signal_strength", "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']", "°C", "temperature", "measurement"};
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", 900, "°C", "temperature", "measurement", ""};
|
||||
|
||||
const HomeAssistantSensor DataSensor PROGMEM = {"Data", "/data", "data", 900, "", "", "", ""};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"P" : %d
|
||||
"P" : %lu,
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"tPO" : %.3f,
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : %lu
|
||||
"rtc" : %s,
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"P" : %lu,
|
||||
"Q" : %lu,
|
||||
"PO" : %lu,
|
||||
"QO" : %lu,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f
|
||||
"U3" : %.2f,
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -2,24 +2,31 @@
|
||||
"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,
|
||||
"P" : %lu,
|
||||
"P1" : %lu,
|
||||
"P2" : %lu,
|
||||
"P3" : %lu,
|
||||
"Q" : %lu,
|
||||
"PO" : %lu,
|
||||
"PO1" : %lu,
|
||||
"PO2" : %lu,
|
||||
"PO3" : %lu,
|
||||
"QO" : %lu,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"PF" : %.2f,
|
||||
"PF1" : %.2f,
|
||||
"PF2" : %.2f,
|
||||
"PF3" : %.2f
|
||||
"PF" : %d,
|
||||
"PF1" : %d,
|
||||
"PF2" : %d,
|
||||
"PF3" : %d,
|
||||
"tPI1" : %.3f,
|
||||
"tPI2" : %.3f,
|
||||
"tPI3" : %.3f,
|
||||
"tPO1" : %.3f,
|
||||
"tPO2" : %.3f,
|
||||
"tPO3" : %.3f,
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"name" : "%s%s",
|
||||
"stat_t" : "%s%s",
|
||||
"uniq_id" : "%s_%s",
|
||||
"obj_id" : "%s_%s",
|
||||
"unit_of_meas" : "%s",
|
||||
"default_entity_id" : "sensor.%s_%s",
|
||||
"val_tpl" : "{{ value_json.%s | is_defined }}",
|
||||
"expire_after" : %d,
|
||||
"dev" : {
|
||||
"ids" : [ "%s" ],
|
||||
"name" : "%s",
|
||||
@@ -12,5 +12,8 @@
|
||||
"sw" : "%s",
|
||||
"mf" : "%s",
|
||||
"cu" : "%s"
|
||||
}%s%s%s%s%s%s
|
||||
}
|
||||
%s%s%s
|
||||
%s%s%s
|
||||
%s%s%s
|
||||
}
|
||||
@@ -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,51 +12,140 @@
|
||||
#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) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config, char* hostname) {
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = fInit = dInit = false;
|
||||
|
||||
if(strlen(config.discoveryNameTag) > 0) {
|
||||
snprintf_P(json, 128, PSTR("AMS reader (%s)"), config.discoveryNameTag);
|
||||
deviceName = String(json);
|
||||
snprintf_P(json, 128, PSTR("[%s] "), config.discoveryNameTag);
|
||||
sensorNamePrefix = String(json);
|
||||
} else {
|
||||
snprintf_P(json, 128, PSTR("AMS reader"));
|
||||
deviceName = String(json);
|
||||
sensorNamePrefix = "";
|
||||
}
|
||||
deviceModel = boardTypeToString(boardType);
|
||||
manufacturer = boardManufacturerToString(boardType);
|
||||
|
||||
deviceUid = String(hostname); // Maybe configurable in the future?
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Hostname is [%s]\n"), hostname);
|
||||
|
||||
if(strlen(config.discoveryHostname) > 0) {
|
||||
if(strncmp_P(config.discoveryHostname, PSTR("http"), 4) == 0) {
|
||||
deviceUrl = String(config.discoveryHostname);
|
||||
} else {
|
||||
snprintf_P(json, 128, PSTR("http://%s/"), config.discoveryHostname);
|
||||
deviceUrl = String(json);
|
||||
}
|
||||
} else {
|
||||
snprintf_P(json, 128, PSTR("http://%s.local/"), hostname);
|
||||
deviceUrl = String(json);
|
||||
}
|
||||
|
||||
if(strlen(config.discoveryPrefix) == 0) {
|
||||
snprintf_P(config.discoveryPrefix, 64, PSTR("homeassistant"));
|
||||
}
|
||||
|
||||
snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix);
|
||||
statusTopic = String(json);
|
||||
|
||||
snprintf_P(json, 128, PSTR("%s/sensor"), config.discoveryPrefix);
|
||||
sensorTopic = String(json);
|
||||
|
||||
snprintf_P(json, 128, PSTR("%s/update"), config.discoveryPrefix);
|
||||
updateTopic = String(json);
|
||||
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::postConnect() {
|
||||
bool ret = true;
|
||||
if(!statusTopic.isEmpty()) {
|
||||
if(mqtt.subscribe(statusTopic)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Subscribed to [%s]\n"), statusTopic.c_str());
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to [%s]\n"), statusTopic.c_str());
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(pubTopic.isEmpty() || !connected())
|
||||
return false;
|
||||
|
||||
if(data->getListType() >= 3) { // publish energy counts
|
||||
publishList3(data, ea);
|
||||
loop();
|
||||
if(time(nullptr) < FirmwareVersion::BuildEpoch)
|
||||
return 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(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();
|
||||
}
|
||||
loop();
|
||||
|
||||
if(ea->isInitialized()) {
|
||||
publishRealtime(data, ea, eapi);
|
||||
loop();
|
||||
publishRealtime(&data, ea, ps);
|
||||
mqtt.loop();
|
||||
}
|
||||
loop();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
publishList1Sensors();
|
||||
snprintf_P(json, BufferSize, HA1_JSON,
|
||||
data->getActiveImportPower()
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower(), pt);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
publishList2Sensors();
|
||||
if(data->getActiveExportPower() > 0) publishList2ExportSensors();
|
||||
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA3_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -64,27 +159,41 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea)
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage()
|
||||
data->getL3Voltage(),
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
publishList3Sensors();
|
||||
if(data->getActiveExportCounter() > 0.0) publishList3ExportSensors();
|
||||
|
||||
char mt[24];
|
||||
toJsonIsoTimestamp(data->getMeterTimestamp(), mt, sizeof(mt));
|
||||
|
||||
char pt[24];
|
||||
memset(pt, 0, 24);
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA2_JSON,
|
||||
data->getActiveImportCounter(),
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp()
|
||||
mt,
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/energy", json);
|
||||
return mqtt.publish(pubTopic + "/energy", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
publishList4Sensors();
|
||||
if(data->getL1ActiveExportPower() > 0 || data->getL2ActiveExportPower() > 0 || data->getL3ActiveExportPower() > 0) publishList4ExportSensors();
|
||||
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA4_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -105,12 +214,19 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage(),
|
||||
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 ? 100 : (int) (data->getPowerFactor() * 100),
|
||||
data->getPowerFactor() == 0 ? 100 : (int) (data->getL1PowerFactor() * 100),
|
||||
data->getPowerFactor() == 0 ? 100 : (int) (data->getL2PowerFactor() * 100),
|
||||
data->getPowerFactor() == 0 ? 100 : (int) (data->getL3PowerFactor() * 100),
|
||||
data->getL1ActiveImportCounter(),
|
||||
data->getL2ActiveImportCounter(),
|
||||
data->getL3ActiveImportCounter(),
|
||||
data->getL1ActiveExportCounter(),
|
||||
data->getL2ActiveExportCounter(),
|
||||
data->getL3ActiveExportCounter(),
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
@@ -119,9 +235,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;
|
||||
@@ -129,7 +246,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(),
|
||||
@@ -146,25 +263,45 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
ea->getProducedThisMonth(),
|
||||
ea->getIncomeThisMonth()
|
||||
);
|
||||
return mqtt.publish(topic + "/realtime", json);
|
||||
}
|
||||
uint32_t ms = millis();
|
||||
if(lastThresholdPublish == 0 || ms-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 = ms;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
|
||||
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
|
||||
return mqtt.publish(pubTopic + "/realtime", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
int count = hw->getTempSensorCount();
|
||||
if(count < 2) return false;
|
||||
|
||||
int size = 32 + (count * 26);
|
||||
|
||||
char buf[size];
|
||||
snprintf_P(buf, 24, PSTR("{\"temperatures\":{"));
|
||||
uint16_t pos = snprintf_P(json, 24, PSTR("{\"temperatures\":{"));
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
if(data != NULL) {
|
||||
char* pos = buf+strlen(buf);
|
||||
String id = toHex(data->address, 8);
|
||||
snprintf_P(pos, 26, PSTR("\"%s\":%.2f,"),
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s\":%.2f,"),
|
||||
id.c_str(),
|
||||
data->lastRead
|
||||
);
|
||||
@@ -172,20 +309,26 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
|
||||
publishTemperatureSensor(i+1, id);
|
||||
}
|
||||
}
|
||||
char* pos = buf+strlen(buf);
|
||||
snprintf_P(count == 0 ? pos : pos-1, 8, PSTR("}}"));
|
||||
bool ret = mqtt.publish(topic + "/temperatures", buf);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}"));
|
||||
|
||||
time_t now = time(nullptr);
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}"));
|
||||
|
||||
bool ret = mqtt.publish(pubTopic + "/temperatures", json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
|
||||
if(pubTopic.isEmpty() || !connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
if(!ps->hasPrice())
|
||||
return false;
|
||||
|
||||
publishPriceSensors(eapi);
|
||||
publishPriceSensors(ps);
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
@@ -193,12 +336,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->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, 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;
|
||||
@@ -213,7 +356,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;
|
||||
@@ -229,7 +372,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;
|
||||
@@ -243,127 +386,148 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
memset(ts1hr, 0, 24);
|
||||
if(min1hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts1hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts1hr, sizeof(ts1hr));
|
||||
}
|
||||
char ts3hr[24];
|
||||
memset(ts3hr, 0, 24);
|
||||
if(min3hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts3hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts3hr, sizeof(ts3hr));
|
||||
}
|
||||
char ts6hr[24];
|
||||
memset(ts6hr, 0, 24);
|
||||
if(min6hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts6hr, sizeof(ts6hr));
|
||||
}
|
||||
|
||||
snprintf_P(json, BufferSize, JSONPRICES_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
values[0],
|
||||
values[1],
|
||||
values[2],
|
||||
values[3],
|
||||
values[4],
|
||||
values[5],
|
||||
values[6],
|
||||
values[7],
|
||||
values[8],
|
||||
values[9],
|
||||
values[10],
|
||||
values[11],
|
||||
values[12],
|
||||
values[13],
|
||||
values[14],
|
||||
values[15],
|
||||
values[16],
|
||||
values[17],
|
||||
values[18],
|
||||
values[19],
|
||||
values[20],
|
||||
values[21],
|
||||
values[22],
|
||||
values[23],
|
||||
values[24],
|
||||
values[25],
|
||||
values[26],
|
||||
values[27],
|
||||
values[28],
|
||||
values[29],
|
||||
values[30],
|
||||
values[31],
|
||||
values[32],
|
||||
values[33],
|
||||
values[34],
|
||||
values[35],
|
||||
values[36],
|
||||
values[37],
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{\"import\":["), WiFi.macAddress().c_str());
|
||||
|
||||
uint8_t currentPricePointIndex = ps->getCurrentPricePointIndex();
|
||||
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
|
||||
for(int i = currentPricePointIndex; i < numberOfPoints; i++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("null,"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("%.4f,"), val);
|
||||
}
|
||||
}
|
||||
if(rteInit && ps->isExportPricesDifferentFromImport()) {
|
||||
pos--;
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"export\":["));
|
||||
for(int i = currentPricePointIndex; i < numberOfPoints; i++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("null,"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("%.4f,"), val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos--;
|
||||
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
|
||||
);
|
||||
bool ret = mqtt.publish(topic + "/prices", json, true, 0);
|
||||
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
|
||||
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
|
||||
bool ret = mqtt.publish(pubTopic + "/prices", json, true, 0);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
if(pubTopic.isEmpty() || !connected())
|
||||
return false;
|
||||
|
||||
publishSystemSensors();
|
||||
if(hw->getTemperature() > -50) publishTemperatureSensor(0, "");
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
time_t now = time(nullptr);
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\",\"t\":%s}"),
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
FirmwareVersion::VersionString
|
||||
FirmwareVersion::VersionString,
|
||||
pt
|
||||
);
|
||||
bool ret = mqtt.publish(topic + "/state", json);
|
||||
bool ret = mqtt.publish(pubTopic + "/state", json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor& sensor) {
|
||||
String uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor sensor) {
|
||||
String uid;
|
||||
if(strlen(sensor.uid) > 0) {
|
||||
uid = String(sensor.uid);
|
||||
} else {
|
||||
uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
}
|
||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||
sensorNamePrefix.c_str(),
|
||||
sensor.name,
|
||||
mqttConfig.publishTopic, sensor.topic,
|
||||
deviceUid.c_str(), uid.c_str(),
|
||||
deviceUid.c_str(), uid.c_str(),
|
||||
sensor.uom,
|
||||
sensor.path,
|
||||
sensor.ttl,
|
||||
deviceUid.c_str(),
|
||||
deviceName.c_str(),
|
||||
deviceModel.c_str(),
|
||||
FirmwareVersion::VersionString,
|
||||
manufacturer.c_str(),
|
||||
deviceUrl.c_str(),
|
||||
|
||||
strlen_P(sensor.devcl) > 0 ? ",\"dev_cla\":\"" : "",
|
||||
strlen_P(sensor.devcl) > 0 ? (char *) FPSTR(sensor.devcl) : "",
|
||||
strlen_P(sensor.devcl) > 0 ? "\"" : "",
|
||||
|
||||
strlen_P(sensor.stacl) > 0 ? ",\"stat_cla\":\"" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : "",
|
||||
strlen_P(sensor.stacl) > 0 ? "\"" : ""
|
||||
strlen_P(sensor.stacl) > 0 ? "\"" : "",
|
||||
|
||||
strlen_P(sensor.uom) > 0 ? ",\"unit_of_meas\":\"" : "",
|
||||
strlen_P(sensor.uom) > 0 ? (char *) FPSTR(sensor.uom) : "",
|
||||
strlen_P(sensor.uom) > 0 ? "\"" : ""
|
||||
);
|
||||
mqtt.publish(discoveryTopic + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
|
||||
mqtt.publish(sensorTopic + "/" + deviceUid + "_" + uid + "/config", json, true, 0);
|
||||
loop();
|
||||
}
|
||||
|
||||
@@ -428,13 +592,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);
|
||||
}
|
||||
@@ -449,22 +613,24 @@ void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, Ents
|
||||
name,
|
||||
RealtimePeakSensor.topic,
|
||||
path,
|
||||
RealtimePeakSensor.ttl,
|
||||
RealtimePeakSensor.uom,
|
||||
RealtimePeakSensor.devcl,
|
||||
RealtimePeakSensor.stacl
|
||||
RealtimePeakSensor.stacl,
|
||||
RealtimePeakSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -487,17 +653,19 @@ void HomeAssistantMqttHandler::publishTemperatureSensor(uint8_t index, String id
|
||||
name,
|
||||
index == 0 ? SystemSensors[0].topic : TemperatureSensor.topic,
|
||||
path,
|
||||
TemperatureSensor.ttl,
|
||||
TemperatureSensor.uom,
|
||||
TemperatureSensor.devcl,
|
||||
TemperatureSensor.stacl
|
||||
TemperatureSensor.stacl,
|
||||
TemperatureSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
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++) {
|
||||
@@ -509,29 +677,96 @@ void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
}
|
||||
pInit = true;
|
||||
}
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
if(prInit[i]) continue;
|
||||
float val = eapi->getValueForHour(i);
|
||||
if(val == ENTSOE_NO_VALUE) continue;
|
||||
|
||||
char name[strlen(PriceSensor.name)+2];
|
||||
snprintf(name, strlen(PriceSensor.name)+2, PriceSensor.name, i, i == 1 ? "hour" : "hours");
|
||||
char path[strlen(PriceSensor.path)+1];
|
||||
snprintf(path, strlen(PriceSensor.path)+1, PriceSensor.path, i);
|
||||
HomeAssistantSensor sensor = {
|
||||
i == 0 ? "Price current hour" : name,
|
||||
PriceSensor.topic,
|
||||
path,
|
||||
uom.c_str(),
|
||||
PriceSensor.devcl,
|
||||
i == 0 ? "total" : PriceSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
prInit[i] = true;
|
||||
|
||||
uint8_t currentPricePointIndex = ps->getCurrentPricePointIndex();
|
||||
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
|
||||
|
||||
if(priceImportInit < numberOfPoints-currentPricePointIndex) {
|
||||
uint8_t importPriceSensorNo = 0;
|
||||
for(int pricePointIndex = currentPricePointIndex; pricePointIndex < numberOfPoints; pricePointIndex++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, pricePointIndex);
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
if(importPriceSensorNo < priceImportInit) {
|
||||
importPriceSensorNo++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t resolution = ps->getResolutionInMinutes();
|
||||
|
||||
char path[64];
|
||||
memset(path, 0, 64);
|
||||
snprintf_P(path, 64, PSTR("prices.import[%d]"), importPriceSensorNo);
|
||||
|
||||
char uid[32];
|
||||
memset(uid, 0, 32);
|
||||
snprintf_P(uid, 32, PSTR("prices%d"), importPriceSensorNo);
|
||||
|
||||
char name[64];
|
||||
if(resolution == 60)
|
||||
snprintf_P(name, 64, PSTR("Import price in %02d hour%s"), importPriceSensorNo, importPriceSensorNo == 1 ? "" : "s");
|
||||
else
|
||||
snprintf_P(name, 64, PSTR("Import price in %03d minutes"), importPriceSensorNo * resolution);
|
||||
|
||||
HomeAssistantSensor sensor = {
|
||||
importPriceSensorNo == 0 ? "Current import price" : name,
|
||||
"/prices",
|
||||
path,
|
||||
resolution * 60 + 300,
|
||||
uom.c_str(),
|
||||
"monetary",
|
||||
importPriceSensorNo == 0 ? "total" : "",
|
||||
uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
|
||||
priceImportInit = importPriceSensorNo++;
|
||||
}
|
||||
}
|
||||
|
||||
if(priceExportInit < numberOfPoints-currentPricePointIndex) {
|
||||
uint8_t exportPriceSensorNo = 0;
|
||||
for(int pricePointIndex = currentPricePointIndex; pricePointIndex < numberOfPoints; pricePointIndex++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, pricePointIndex);
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
if(exportPriceSensorNo < priceExportInit) {
|
||||
exportPriceSensorNo++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t resolution = ps->getResolutionInMinutes();
|
||||
|
||||
char path[64];
|
||||
memset(path, 0, 64);
|
||||
snprintf_P(path, 64, PSTR("prices.export[%d]"), exportPriceSensorNo);
|
||||
|
||||
char uid[32];
|
||||
memset(uid, 0, 32);
|
||||
snprintf_P(uid, 32, PSTR("exportprices%d"), exportPriceSensorNo);
|
||||
|
||||
char name[64];
|
||||
if(resolution == 60)
|
||||
snprintf_P(name, 64, PSTR("Export price in %02d hour%s"), exportPriceSensorNo, exportPriceSensorNo == 1 ? "" : "s");
|
||||
else
|
||||
snprintf_P(name, 64, PSTR("Export price in %03d minutes"), exportPriceSensorNo * resolution);
|
||||
|
||||
HomeAssistantSensor sensor = {
|
||||
exportPriceSensorNo == 0 ? "Current export price" : name,
|
||||
"/prices",
|
||||
path,
|
||||
resolution * 60 + 300,
|
||||
uom.c_str(),
|
||||
"monetary",
|
||||
exportPriceSensorNo == 0 ? "total" : "",
|
||||
uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
|
||||
priceExportInit = exportPriceSensorNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
if(sInit) return;
|
||||
for(uint8_t i = 0; i < SystemSensorCount; i++) {
|
||||
@@ -540,10 +775,105 @@ 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,
|
||||
RealtimeThresholdSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
rInit = true;
|
||||
}
|
||||
|
||||
uint8_t HomeAssistantMqttHandler::getFormat() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
bool HomeAssistantMqttHandler::publishRaw(uint8_t* raw, size_t length) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(length <= 0 || length > BufferSize) return false;
|
||||
|
||||
if(!dInit) {
|
||||
// Not sure how this sensor should be defined in HA, so skipping for now
|
||||
//publishSensor(DataSensor);
|
||||
dInit = true;
|
||||
}
|
||||
|
||||
String str = toHex(raw, length);
|
||||
|
||||
snprintf_P(json, BufferSize, PSTR("{\"data\":\"%s\"}"), str.c_str());
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/data"), mqttConfig.publishTopic);
|
||||
bool ret = mqtt.publish(topic, json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishFirmware() {
|
||||
if(!fInit) {
|
||||
snprintf_P(json, BufferSize, PSTR("{\"name\":\"%sFirmware\",\"stat_t\":\"%s/firmware\",\"uniq_id\":\"%s_fwupgrade\",\"dev_cla\":\"firmware\",\"cmd_t\":\"%s\",\"pl_inst\":\"fwupgrade\"}"),
|
||||
sensorNamePrefix.c_str(),
|
||||
pubTopic.c_str(),
|
||||
deviceUid.c_str(),
|
||||
subTopic.c_str()
|
||||
);
|
||||
fInit = mqtt.publish(updateTopic + "/" + deviceUid + "/config", json, true, 0);
|
||||
loop();
|
||||
return fInit;
|
||||
}
|
||||
snprintf_P(json, BufferSize, PSTR("{\"installed_version\":\"%s\",\"latest_version\":\"%s\",\"title\":\"amsreader firmware\",\"release_url\":\"https://github.com/UtilitechAS/amsreader-firmware/releases\",\"release_summary\":\"New version %s is available\",\"update_percentage\":%s}"),
|
||||
FirmwareVersion::VersionString,
|
||||
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
|
||||
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
|
||||
updater->getProgress() < 0 ? "null" : String(updater->getProgress(), 0)
|
||||
);
|
||||
bool ret = mqtt.publish(pubTopic + "/firmware", json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
|
||||
if(topic.equals(statusTopic)) {
|
||||
if(payload.equals("online")) {
|
||||
#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 = fInit = dInit = false;
|
||||
for(uint8_t i = 0; i < 32; i++) tInit[i] = false;
|
||||
priceImportInit = 0;
|
||||
priceExportInit = 0;
|
||||
}
|
||||
} else if(topic.equals(subTopic)) {
|
||||
if(payload.equals("fwupgrade")) {
|
||||
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
|
||||
updater->setTargetVersion(updater->getNextVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::toJsonIsoTimestamp(time_t t, char* buf, size_t buflen) {
|
||||
memset(buf, 0, buflen);
|
||||
if(t > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
snprintf_P(buf, buflen, PSTR("\"%04d-%02d-%02dT%02d:%02d:%02dZ\""), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
} else {
|
||||
snprintf_P(buf, buflen, PSTR("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HWTOOLS_H
|
||||
#define _HWTOOLS_H
|
||||
|
||||
@@ -36,8 +42,10 @@ struct AdcConfig {
|
||||
|
||||
class HwTools {
|
||||
public:
|
||||
void setup(GpioConfig*, AmsConfiguration*);
|
||||
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
|
||||
void setup(SystemConfig* sys, GpioConfig* gpio);
|
||||
float getVcc();
|
||||
void setMaxVcc(float maxVcc);
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorData* getTempSensorData(uint8_t);
|
||||
bool updateTemperatures();
|
||||
@@ -48,22 +56,35 @@ public:
|
||||
bool ledOn(uint8_t color);
|
||||
bool ledOff(uint8_t color);
|
||||
bool ledBlink(uint8_t color, uint8_t blink);
|
||||
void setBootSuccessful(bool value);
|
||||
bool isVoltageOptimal(float range = 0.4);
|
||||
uint8_t getBoardType();
|
||||
|
||||
HwTools() {};
|
||||
private:
|
||||
uint8_t boardType;
|
||||
uint8_t ledPin, redPin, greenPin, bluePin, tempPin, atempPin;
|
||||
uint8_t ledDisablePin, ledBehaviour;
|
||||
bool ledInvert, rgbInvert;
|
||||
uint8_t vccPin, vccGnd_r, vccVcc_r;
|
||||
float vccOffset, vccMultiplier;
|
||||
float vcc = 3.3; // Last known Vcc
|
||||
float maxVcc = 3.28; // Best to have this close to max as a start, in case Pow-U reboots and starts off with a low voltage, we dont want that to be perceived as max
|
||||
unsigned long lastVccRead = 0;
|
||||
|
||||
uint16_t analogRange = 1024;
|
||||
AdcConfig voltAdc, tempAdc;
|
||||
#if defined(ESP32)
|
||||
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,157 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HwTools.h"
|
||||
|
||||
void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
this->config = config;
|
||||
this->amsConf = 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;
|
||||
gpioConfig.vccBootLimit = 0;
|
||||
gpioConfig.vccOffset = 0;
|
||||
gpioConfig.vccMultiplier = 0;
|
||||
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 245: // wESP32
|
||||
gpioConfig.apPin = 0;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
|
||||
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(SystemConfig* sys, GpioConfig* config) {
|
||||
this->boardType = sys->boardType;
|
||||
this->tempSensorInit = false;
|
||||
if(sensorApi != NULL)
|
||||
delete sensorApi;
|
||||
@@ -10,8 +159,9 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
delete oneWire;
|
||||
if(config->tempSensorPin > 0 && config->tempSensorPin < 40) {
|
||||
pinMode(config->tempSensorPin, INPUT);
|
||||
tempPin = config->tempSensorPin;
|
||||
} else {
|
||||
config->tempSensorPin = 0xFF;
|
||||
tempPin = config->tempSensorPin = 0xFF;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
@@ -53,44 +203,64 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
#else
|
||||
pinMode(config->vccPin, INPUT);
|
||||
#endif
|
||||
vccPin = config->vccPin;
|
||||
} else {
|
||||
voltAdc.unit = 0xFF;
|
||||
voltAdc.channel = 0xFF;
|
||||
config->vccPin = 0xFF;
|
||||
vccPin = config->vccPin = 0xFF;
|
||||
}
|
||||
vccOffset = config->vccOffset / 100.0;
|
||||
vccMultiplier = config->vccMultiplier / 1000.0;
|
||||
vccGnd_r = config->vccResistorGnd;
|
||||
vccVcc_r = config->vccResistorVcc;
|
||||
|
||||
if(config->tempAnalogSensorPin > 0 && config->tempAnalogSensorPin < 40) {
|
||||
pinMode(config->tempAnalogSensorPin, INPUT);
|
||||
atempPin = config->tempAnalogSensorPin;
|
||||
} else {
|
||||
config->tempAnalogSensorPin = 0xFF;
|
||||
atempPin = config->tempAnalogSensorPin = 0xFF;
|
||||
}
|
||||
|
||||
if(config->ledPin > 0 && config->ledPin < 40) {
|
||||
pinMode(config->ledPin, OUTPUT);
|
||||
ledPin = config->ledPin;
|
||||
ledInvert = config->ledInverted;
|
||||
ledOff(LED_INTERNAL);
|
||||
} else {
|
||||
config->ledPin = 0xFF;
|
||||
ledPin = config->ledPin = 0xFF;
|
||||
}
|
||||
|
||||
if(config->ledPinRed > 0 && config->ledPinRed < 40) {
|
||||
pinMode(config->ledPinRed, OUTPUT);
|
||||
redPin = config->ledPinRed;
|
||||
ledOff(LED_RED);
|
||||
} else {
|
||||
config->ledPinRed = 0xFF;
|
||||
redPin = config->ledPinRed = 0xFF;
|
||||
}
|
||||
|
||||
if(config->ledPinGreen > 0 && config->ledPinGreen < 40) {
|
||||
pinMode(config->ledPinGreen, OUTPUT);
|
||||
greenPin = config->ledPinGreen;
|
||||
ledOff(LED_GREEN);
|
||||
} else {
|
||||
config->ledPinGreen = 0xFF;
|
||||
greenPin = config->ledPinGreen = 0xFF;
|
||||
}
|
||||
|
||||
if(config->ledPinBlue > 0 && config->ledPinBlue < 40) {
|
||||
pinMode(config->ledPinBlue, OUTPUT);
|
||||
bluePin = config->ledPinBlue;
|
||||
ledOff(LED_BLUE);
|
||||
} else {
|
||||
config->ledPinBlue = 0xFF;
|
||||
bluePin = config->ledPinBlue = 0xFF;
|
||||
}
|
||||
|
||||
rgbInvert = config->ledRgbInverted;
|
||||
|
||||
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
|
||||
pinMode(config->ledDisablePin, OUTPUT_OPEN_DRAIN);
|
||||
ledDisablePin = config->ledDisablePin;
|
||||
ledBehaviour = config->ledBehaviour;
|
||||
setBootSuccessful(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +385,7 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
||||
|
||||
float HwTools::getVcc() {
|
||||
float volts = 0.0;
|
||||
if(config->vccPin != 0xFF) {
|
||||
if(vccPin != 0xFF) {
|
||||
#if defined(ESP32)
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
uint32_t x = 0;
|
||||
@@ -238,32 +408,35 @@ float HwTools::getVcc() {
|
||||
} else {
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
x += analogRead(vccPin);
|
||||
}
|
||||
volts = (x * 3.3) / 10.0 / analogRange;
|
||||
}
|
||||
#else
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
x += analogRead(vccPin);
|
||||
}
|
||||
volts = (x * 3.3) / 10.0 / analogRange;
|
||||
#endif
|
||||
} else {
|
||||
}
|
||||
if(volts == 0.0) {
|
||||
#if defined(ESP8266)
|
||||
volts = ESP.getVcc() / 1024.0;
|
||||
#else
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
if(volts == 0.0) return 0.0;
|
||||
|
||||
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
|
||||
volts *= ((float) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
|
||||
}
|
||||
|
||||
|
||||
float vccOffset = config->vccOffset / 100.0;
|
||||
float vccMultiplier = config->vccMultiplier / 1000.0;
|
||||
return vccOffset + (volts > 0.0 ? volts * vccMultiplier : 0.0);
|
||||
if(vccGnd_r > 0 && vccVcc_r > 0)
|
||||
volts *= ((float) (vccGnd_r + vccVcc_r) / vccGnd_r);
|
||||
if(vccOffset != 0.0)
|
||||
volts += vccOffset;
|
||||
if(vccMultiplier != 0.0)
|
||||
volts *= vccMultiplier;
|
||||
|
||||
return volts;
|
||||
}
|
||||
|
||||
uint8_t HwTools::getTempSensorCount() {
|
||||
@@ -278,9 +451,9 @@ TempSensorData* HwTools::getTempSensorData(uint8_t i) {
|
||||
}
|
||||
|
||||
bool HwTools::updateTemperatures() {
|
||||
if(config->tempSensorPin != 0xFF) {
|
||||
if(tempPin != 0xFF) {
|
||||
if(!tempSensorInit) {
|
||||
oneWire = new OneWire(config->tempSensorPin);
|
||||
oneWire = new OneWire(tempPin);
|
||||
sensorApi = new DallasTemperature(this->oneWire);
|
||||
sensorApi->begin();
|
||||
delay(100);
|
||||
@@ -358,8 +531,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++;
|
||||
}
|
||||
@@ -367,9 +539,9 @@ float HwTools::getTemperature() {
|
||||
return c == 0 ? DEVICE_DISCONNECTED_C : ret/c;
|
||||
}
|
||||
float HwTools::getTemperatureAnalog() {
|
||||
if(config->tempAnalogSensorPin != 0xFF) {
|
||||
if(atempPin != 0xFF) {
|
||||
float adcCalibrationFactor = 1.06587;
|
||||
int volts = ((float) analogRead(config->tempAnalogSensorPin) / analogRange) * 3.3;
|
||||
int volts = ((float) analogRead(atempPin) / analogRange) * 3.3;
|
||||
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
|
||||
}
|
||||
return DEVICE_DISCONNECTED_C;
|
||||
@@ -380,19 +552,45 @@ int HwTools::getWifiRssi() {
|
||||
return isnan(rssi) ? -100.0 : rssi;
|
||||
}
|
||||
|
||||
void HwTools::setBootSuccessful(bool value) {
|
||||
if(bootSuccessful && value) return;
|
||||
bootSuccessful = value;
|
||||
if(ledDisablePin > 0 && ledDisablePin < 40) {
|
||||
switch(ledBehaviour) {
|
||||
case LED_BEHAVIOUR_ERROR_ONLY:
|
||||
case LED_BEHAVIOUR_OFF:
|
||||
digitalWrite(ledDisablePin, LOW);
|
||||
break;
|
||||
case LED_BEHAVIOUR_BOOT:
|
||||
if(bootSuccessful) {
|
||||
digitalWrite(ledDisablePin, LOW);
|
||||
} else {
|
||||
digitalWrite(ledDisablePin, HIGH);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
digitalWrite(ledDisablePin, HIGH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HwTools::ledOn(uint8_t color) {
|
||||
if(ledBehaviour == LED_BEHAVIOUR_OFF) return false;
|
||||
if(ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
|
||||
if(ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
|
||||
|
||||
if(color == LED_INTERNAL) {
|
||||
return writeLedPin(color, config->ledInverted ? LOW : HIGH);
|
||||
return writeLedPin(color, ledInvert ? LOW : HIGH);
|
||||
} else {
|
||||
return writeLedPin(color, config->ledRgbInverted ? LOW : HIGH);
|
||||
return writeLedPin(color, rgbInvert ? LOW : HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
bool HwTools::ledOff(uint8_t color) {
|
||||
if(color == LED_INTERNAL) {
|
||||
return writeLedPin(color, config->ledInverted ? HIGH : LOW);
|
||||
return writeLedPin(color, ledInvert ? HIGH : LOW);
|
||||
} else {
|
||||
return writeLedPin(color, config->ledRgbInverted ? HIGH : LOW);
|
||||
return writeLedPin(color, rgbInvert ? HIGH : LOW);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,8 +607,8 @@ bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
|
||||
bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
switch(color) {
|
||||
case LED_INTERNAL: {
|
||||
if(config->ledPin != 0xFF) {
|
||||
digitalWrite(config->ledPin, state);
|
||||
if(ledPin != 0xFF) {
|
||||
digitalWrite(ledPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -418,8 +616,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_RED: {
|
||||
if(config->ledPinRed != 0xFF) {
|
||||
digitalWrite(config->ledPinRed, state);
|
||||
if(redPin != 0xFF) {
|
||||
digitalWrite(redPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -427,8 +625,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_GREEN: {
|
||||
if(config->ledPinGreen != 0xFF) {
|
||||
digitalWrite(config->ledPinGreen, state);
|
||||
if(greenPin != 0xFF) {
|
||||
digitalWrite(greenPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -436,8 +634,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_BLUE: {
|
||||
if(config->ledPinBlue != 0xFF) {
|
||||
digitalWrite(config->ledPinBlue, state);
|
||||
if(bluePin != 0xFF) {
|
||||
digitalWrite(bluePin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -445,9 +643,9 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_YELLOW: {
|
||||
if(config->ledPinRed != 0xFF && config->ledPinGreen != 0xFF) {
|
||||
digitalWrite(config->ledPinRed, state);
|
||||
digitalWrite(config->ledPinGreen, state);
|
||||
if(redPin != 0xFF && greenPin != 0xFF) {
|
||||
digitalWrite(redPin, state);
|
||||
digitalWrite(greenPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -457,3 +655,30 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HwTools::isVoltageOptimal(float range) {
|
||||
if(boardType >= 1 && boardType <= 8 && maxVcc > 2.8) { // BUS-Power boards
|
||||
unsigned long now = millis();
|
||||
if(now - lastVccRead > 250) {
|
||||
vcc = getVcc();
|
||||
lastVccRead = now;
|
||||
}
|
||||
if(vcc > 3.4 || vcc < 2.8) {
|
||||
maxVcc = 0; // Voltage is outside the operating range, we have to assume voltage is OK
|
||||
} else if(vcc > maxVcc) {
|
||||
maxVcc = vcc;
|
||||
} else {
|
||||
float diff = min(maxVcc, (float) 3.3) - vcc;
|
||||
return diff < range;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t HwTools::getBoardType() {
|
||||
return boardType;
|
||||
}
|
||||
|
||||
void HwTools::setMaxVcc(float vcc) {
|
||||
this->maxVcc = min(3.3f, vcc);
|
||||
}
|
||||
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,24 +11,37 @@
|
||||
|
||||
class JsonMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw, AmsDataStorage* ds, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#else
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, HwTools* hw, AmsDataStorage* ds, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#endif
|
||||
this->hw = hw;
|
||||
this->ds = ds;
|
||||
};
|
||||
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 publishRaw(String data);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(uint8_t* raw, size_t length);
|
||||
bool publishFirmware();
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
private:
|
||||
HwTools* hw;
|
||||
bool hasExport = false;
|
||||
AmsDataStorage* ds;
|
||||
|
||||
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);
|
||||
bool publishList4(AmsData* data, EnergyAccounting* ea);
|
||||
String getMeterModel(AmsData* data);
|
||||
void toJsonIsoTimestamp(time_t t, char* buf, size_t buflen);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user