mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 04:57:28 +00:00
Compare commits
346 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c43c07386a | ||
|
|
91958f5464 | ||
|
|
194905237b | ||
|
|
f3805ad111 | ||
|
|
0f22fd561e | ||
|
|
ee462ec468 | ||
|
|
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
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -22,27 +22,25 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
uses: actions/checkout@v4
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
uses: actions/cache@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 +48,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 +60,6 @@ jobs:
|
||||
env:
|
||||
CI: true
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
run: pio pkg install
|
||||
- name: PlatformIO run
|
||||
run: pio run
|
||||
|
||||
38
.github/workflows/release.yml
vendored
38
.github/workflows/release.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v1
|
||||
uses: actions/checkout@v4
|
||||
- name: Get release version for filenames
|
||||
id: release_tag
|
||||
env:
|
||||
@@ -28,21 +28,22 @@ jobs:
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_SC_KEY/AMS2MQTT_SC_KEY=\\"${{secrets.AMS2MQTT_SC_KEY}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
|
||||
- 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
|
||||
@@ -51,9 +52,9 @@ jobs:
|
||||
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
|
||||
@@ -68,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -146,6 +147,29 @@ jobs:
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32s3 firmware
|
||||
run: pio run -e esp32s3
|
||||
- name: Create esp32s3 zip file
|
||||
run: /bin/sh scripts/esp32s3/mkzip.sh
|
||||
- name: Upload esp32s3 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32s3/firmware.bin
|
||||
asset_name: ams2mqtt-esp32s3-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32s3 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32s3.zip
|
||||
asset_name: ams2mqtt-esp32s3-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32solo firmware
|
||||
run: pio run -e esp32solo
|
||||
- name: Create esp32solo zip file
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.vs/
|
||||
.idea/
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
**/__vm/
|
||||
|
||||
4
LICENSE
4
LICENSE
@@ -10,7 +10,7 @@ Use Limitation: 5 users
|
||||
|
||||
License Grant. Licensor hereby grants to each recipient of the
|
||||
Software ("you") a non-exclusive, non-transferable, royalty-free and
|
||||
fully-paid-up license, under all of the Licensor’s copyright and
|
||||
fully-paid-up license, under all of the Licensor's copyright and
|
||||
patent rights, to use, copy, distribute, prepare derivative works of,
|
||||
publicly perform and display the Software, subject to the Use
|
||||
Limitation and the conditions set forth below.
|
||||
@@ -20,7 +20,7 @@ number of users per entity set forth above (the "Use Limitation"). For
|
||||
determining the number of users, "you" includes all affiliates,
|
||||
meaning legal entities controlling, controlled by, or under common
|
||||
control with you. If you exceed the Use Limitation, your use is
|
||||
subject to payment of Licensor’s then-current list price for licenses.
|
||||
subject to payment of Licensor's then-current list price for licenses.
|
||||
|
||||
Conditions. Redistribution in source code or other forms must include
|
||||
a copy of this license document to be provided in a reasonable
|
||||
|
||||
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.
@@ -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
|
||||
|
||||
1
frames/dsmr.raw
Normal file
1
frames/dsmr.raw
Normal file
@@ -0,0 +1 @@
|
||||
2F454C4C355C3235333833333633355F410D0A0D0A302D303A312E302E302832343132313232303133343857290D0A312D303A312E382E302830303034373532322E3237382A6B5768290D0A312D303A322E382E302830303030303030302E3030312A6B5768290D0A312D303A332E382E302830303030303035392E3033392A6B76617268290D0A312D303A342E382E302830303031323537302E3937362A6B76617268290D0A312D303A312E372E3028303030312E3233312A6B57290D0A312D303A322E372E3028303030302E3030302A6B57290D0A312D303A332E372E3028303030302E3030302A6B766172290D0A312D303A342E372E3028303030302E3435312A6B766172290D0A312D303A32312E372E3028303030302E3337362A6B57290D0A312D303A34312E372E3028303030302E3832342A6B57290D0A312D303A36312E372E3028303030302E3033302A6B57290D0A312D303A32322E372E3028303030302E3030302A6B57290D0A312D303A34322E372E3028303030302E3030302A6B57290D0A312D303A36322E372E3028303030302E3030302A6B57290D0A312D303A32332E372E3028303030302E3030302A6B766172290D0A312D303A34332E372E3028303030302E3030302A6B766172290D0A312D303A36332E372E3028303030302E3030302A6B766172290D0A312D303A32342E372E3028303030302E3037322A6B766172290D0A312D303A34342E372E3028303030302E3334392A6B766172290D0A312D303A36342E372E3028303030302E3032392A6B766172290D0A312D303A33322E372E30283233322E382A56290D0A312D303A35322E372E30283233332E352A56290D0A312D303A37322E372E30283233382E352A56290D0A312D303A33312E372E30283030312E382A41290D0A312D303A35312E372E30283030342E302A41290D0A312D303A37312E372E30283030302E312A41290D0A21343736300D0A
|
||||
@@ -1,31 +1,60 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSCONFIGURATION_h
|
||||
#define _AMSCONFIGURATION_h
|
||||
#include <EEPROM.h>
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 103 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 104 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CLEARED_INDICATOR 0xFC
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
#define CONFIG_SYSTEM_START 8
|
||||
#define CONFIG_METER_START 32
|
||||
#define CONFIG_UPGRADE_INFO_START 216
|
||||
#define CONFIG_UI_START 248
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_ENTSOE_START 290
|
||||
#define CONFIG_WIFI_START 360
|
||||
#define CONFIG_ENERGYACCOUNTING_START 576
|
||||
#define CONFIG_WEB_START 648
|
||||
#define CONFIG_DEBUG_START 824
|
||||
#define CONFIG_DOMOTICZ_START 856
|
||||
#define CONFIG_NTP_START 872
|
||||
#define CONFIG_MQTT_START 1004
|
||||
#define CONFIG_HA_START 1680
|
||||
#define CONFIG_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_METER_START_93 224
|
||||
#define CONFIG_METER_START_103 32
|
||||
#define CONFIG_UPGRADE_INFO_START_103 216
|
||||
#define CONFIG_UI_START_103 248
|
||||
#define CONFIG_GPIO_START_103 266
|
||||
#define CONFIG_ENTSOE_START_103 290
|
||||
#define CONFIG_WIFI_START_103 360
|
||||
#define CONFIG_ENERGYACCOUNTING_START_103 576
|
||||
#define CONFIG_WEB_START_103 648
|
||||
#define CONFIG_DEBUG_START_103 824
|
||||
#define CONFIG_DOMOTICZ_START_103 856
|
||||
#define CONFIG_NTP_START_103 872
|
||||
#define CONFIG_MQTT_START_103 1004
|
||||
#define CONFIG_HA_START_103 1680
|
||||
|
||||
#define LED_BEHAVIOUR_DEFAULT 0
|
||||
#define LED_BEHAVIOUR_BOOT 1
|
||||
#define LED_BEHAVIOUR_ERROR_ONLY 3
|
||||
#define LED_BEHAVIOUR_OFF 9
|
||||
|
||||
struct ResetDataContainer {
|
||||
uint8_t cause;
|
||||
uint8_t last_cause;
|
||||
uint8_t magic;
|
||||
};
|
||||
|
||||
struct SystemConfig {
|
||||
uint8_t boardType;
|
||||
@@ -36,7 +65,7 @@ struct SystemConfig {
|
||||
uint8_t energyspeedometer;
|
||||
}; // 8
|
||||
|
||||
struct WiFiConfig {
|
||||
struct NetworkConfig {
|
||||
char ssid[32];
|
||||
char psk[64];
|
||||
char ip[16];
|
||||
@@ -49,8 +78,9 @@ struct WiFiConfig {
|
||||
uint8_t power;
|
||||
uint8_t sleep;
|
||||
uint8_t use11b;
|
||||
bool unused;
|
||||
}; // 213
|
||||
bool ipv6;
|
||||
uint8_t mode;
|
||||
}; // 214
|
||||
|
||||
struct MqttConfig {
|
||||
char host[128];
|
||||
@@ -62,9 +92,21 @@ 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;
|
||||
}; // 685
|
||||
|
||||
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 +128,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 +140,26 @@ struct DebugConfig {
|
||||
}; // 3
|
||||
|
||||
struct GpioConfig {
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
bool ledInverted;
|
||||
uint8_t ledPinRed;
|
||||
uint8_t ledPinGreen;
|
||||
uint8_t ledPinBlue;
|
||||
bool ledRgbInverted;
|
||||
uint8_t tempSensorPin;
|
||||
uint8_t tempAnalogSensorPin;
|
||||
uint8_t vccPin;
|
||||
int16_t vccOffset;
|
||||
uint16_t vccMultiplier;
|
||||
uint8_t vccBootLimit;
|
||||
uint16_t vccResistorGnd;
|
||||
uint16_t vccResistorVcc;
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 21
|
||||
|
||||
struct GpioConfig103 {
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
@@ -146,7 +177,9 @@ struct GpioConfig {
|
||||
uint16_t vccResistorGnd;
|
||||
uint16_t vccResistorVcc;
|
||||
bool hanPinPullup;
|
||||
}; // 21
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 23
|
||||
|
||||
struct DomoticzConfig {
|
||||
uint16_t elidx;
|
||||
@@ -169,21 +202,13 @@ struct NtpConfig {
|
||||
char timezone[32];
|
||||
}; // 98
|
||||
|
||||
struct NtpConfig96 {
|
||||
bool enable;
|
||||
bool dhcp;
|
||||
int16_t offset;
|
||||
int16_t summerOffset;
|
||||
char server[64];
|
||||
}; // 70
|
||||
|
||||
struct EntsoeConfig {
|
||||
char token[37];
|
||||
struct PriceServiceConfig {
|
||||
char entsoeToken[37];
|
||||
char area[17];
|
||||
char currency[4];
|
||||
uint32_t multiplier;
|
||||
uint32_t unused1;
|
||||
bool enabled;
|
||||
uint16_t fixedPrice;
|
||||
uint16_t unused2;
|
||||
}; // 64
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
@@ -191,11 +216,6 @@ struct EnergyAccountingConfig {
|
||||
uint8_t hours;
|
||||
}; // 21
|
||||
|
||||
struct EnergyAccountingConfig101 {
|
||||
uint8_t thresholds[10];
|
||||
uint8_t hours;
|
||||
}; // 11
|
||||
|
||||
struct UiConfig {
|
||||
uint8_t showImport;
|
||||
uint8_t showExport;
|
||||
@@ -208,20 +228,31 @@ 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
|
||||
uint32_t size;
|
||||
uint16_t block_position;
|
||||
uint8_t retry_count;
|
||||
uint8_t reboot_count;
|
||||
int8_t errorCode;
|
||||
}; // 25
|
||||
|
||||
struct CloudConfig {
|
||||
bool enabled;
|
||||
uint8_t interval;
|
||||
char hostname[64];
|
||||
uint16_t port;
|
||||
uint8_t clientId[16];
|
||||
uint8_t proto;
|
||||
}; // 88
|
||||
|
||||
class AmsConfiguration {
|
||||
public:
|
||||
@@ -235,12 +266,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 +282,7 @@ public:
|
||||
|
||||
bool getWebConfig(WebConfig&);
|
||||
bool setWebConfig(WebConfig&);
|
||||
void clearAuth(WebConfig&);
|
||||
void clearWebConfig(WebConfig&);
|
||||
|
||||
bool getMeterConfig(MeterConfig&);
|
||||
bool setMeterConfig(MeterConfig&);
|
||||
@@ -268,7 +299,7 @@ public:
|
||||
|
||||
bool getGpioConfig(GpioConfig&);
|
||||
bool setGpioConfig(GpioConfig&);
|
||||
void clearGpio(GpioConfig&);
|
||||
void clearGpio(GpioConfig& config, bool all=true);
|
||||
|
||||
void print(Print* debugger);
|
||||
|
||||
@@ -286,11 +317,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 +332,20 @@ public:
|
||||
bool getUiConfig(UiConfig&);
|
||||
bool setUiConfig(UiConfig&);
|
||||
void clearUiConfig(UiConfig&);
|
||||
|
||||
void loadTempSensors();
|
||||
void saveTempSensors();
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorConfig* getTempSensorConfig(uint8_t address[8]);
|
||||
void updateTempSensorConfig(uint8_t address[8], const char name[32], bool common);
|
||||
|
||||
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
|
||||
void setUiLanguageChanged();
|
||||
bool isUiLanguageChanged();
|
||||
void ackUiLanguageChange();
|
||||
|
||||
bool getUpgradeInformation(UpgradeInformation&);
|
||||
bool setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion);
|
||||
bool setUpgradeInformation(UpgradeInformation&);
|
||||
void clearUpgradeInformation(UpgradeInformation&);
|
||||
|
||||
bool getCloudConfig(CloudConfig&);
|
||||
bool setCloudConfig(CloudConfig&);
|
||||
void clearCloudConfig(CloudConfig&);
|
||||
bool isCloudChanged();
|
||||
void ackCloudConfig();
|
||||
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
@@ -321,18 +353,9 @@ protected:
|
||||
private:
|
||||
uint8_t configVersion = 0;
|
||||
|
||||
bool sysChanged = false, wifiChanged = false, mqttChanged = false, meterChanged = true, ntpChanged = true, entsoeChanged = false, energyAccountingChanged = true;
|
||||
bool sysChanged = false, networkChanged, mqttChanged, meterChanged = true, ntpChanged = true, priceChanged = false, energyAccountingChanged = true, cloudChanged = true, uiLanguageChanged = false;
|
||||
|
||||
uint8_t tempSensorCount = 0;
|
||||
TempSensorConfig** tempSensors = NULL;
|
||||
|
||||
bool relocateConfig93(); // 2.1.0
|
||||
bool relocateConfig94(); // 2.1.0
|
||||
bool relocateConfig95(); // 2.1.4
|
||||
bool relocateConfig96(); // 2.1.14
|
||||
bool relocateConfig100(); // 2.2-dev
|
||||
bool relocateConfig101(); // 2.2.0 through 2.2.8
|
||||
bool relocateConfig102(); // 2.2.9 through 2.2.11
|
||||
bool relocateConfig103(); // 2.2.12, until, but not including 2.3
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
|
||||
@@ -1,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
|
||||
|
||||
@@ -8,5 +14,6 @@ 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);
|
||||
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) {
|
||||
@@ -29,7 +35,7 @@ bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
|
||||
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)) {
|
||||
@@ -39,4 +45,20 @@ bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
|
||||
}
|
||||
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,6 +84,7 @@ public:
|
||||
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
bool isCounterEstimated();
|
||||
bool isL2currentMissing();
|
||||
|
||||
int8_t getLastError();
|
||||
@@ -82,10 +99,13 @@ protected:
|
||||
time_t meterTimestamp = 0;
|
||||
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||
float l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
uint32_t l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
uint32_t l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
double l1activeImportCounter = 0, l2activeImportCounter = 0, l3activeImportCounter = 0;
|
||||
double l1activeExportCounter = 0, l2activeExportCounter = 0, l3activeExportCounter = 0;
|
||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||
double lastKnownCounter = 0;
|
||||
bool threePhase = false, twoPhase = false, counterEstimated = false, l2currentMissing = false;;
|
||||
|
||||
int8_t lastError = 0x00;
|
||||
|
||||
93
lib/AmsData/include/OBIScodes.h
Normal file
93
lib/AmsData/include/OBIScodes.h
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _OBISCODES_H
|
||||
#define _OBISCODES_H
|
||||
|
||||
#include "lwip/def.h"
|
||||
|
||||
#define OBIS_MEDIUM_ABSTRACT 0
|
||||
#define OBIS_MEDIUM_ELECTRICITY 1
|
||||
|
||||
#define OBIS_CHAN_0 0
|
||||
#define OBIS_CHAN_1 1
|
||||
|
||||
#define OBIS_RANGE_NA 0xFF
|
||||
|
||||
struct OBIS_head_t {
|
||||
uint8_t medium;
|
||||
uint8_t channel;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OBIS_code_t {
|
||||
uint8_t sensor;
|
||||
uint8_t gr;
|
||||
uint8_t tariff;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OBIS_t {
|
||||
OBIS_head_t head;
|
||||
OBIS_code_t code;
|
||||
uint8_t range;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
const OBIS_code_t OBIS_NULL PROGMEM = { 0, 0, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_FIRMWARE_VERSION PROGMEM = { 0, 2, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_VERSION PROGMEM = { 0, 2, 129 };
|
||||
const OBIS_code_t OBIS_METER_MODEL PROGMEM = { 96, 1, 1 };
|
||||
const OBIS_code_t OBIS_METER_MODEL_2 PROGMEM = { 96, 1, 7 };
|
||||
const OBIS_code_t OBIS_METER_ID PROGMEM = { 96, 1, 0 };
|
||||
const OBIS_code_t OBIS_METER_ID_2 PROGMEM = { 0, 0, 5 };
|
||||
const OBIS_code_t OBIS_METER_TIMESTAMP PROGMEM = { 1, 0, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT PROGMEM = { 1, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT PROGMEM = { 1, 8, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT PROGMEM = { 2, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_COUNT PROGMEM = { 2, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT PROGMEM = { 3, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT PROGMEM = { 3, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT PROGMEM = { 4, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_COUNT PROGMEM = { 4, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_POWER_FACTOR PROGMEM = { 13, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L1 PROGMEM = { 21, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L1 PROGMEM = { 22, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_L1 PROGMEM = { 23, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_L1 PROGMEM = { 24, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L1 PROGMEM ={ 21, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L1 PROGMEM ={ 22, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L1 PROGMEM = { 31, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L1 PROGMEM = { 32, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L1 PROGMEM = { 33, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L2 PROGMEM = { 41, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L2 PROGMEM = { 42, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_L2 PROGMEM = { 43, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_L2 PROGMEM = { 44, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L2 PROGMEM ={ 41, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L2 PROGMEM ={ 42, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L2 PROGMEM = { 51, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L2 PROGMEM = { 52, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L2 PROGMEM = { 53, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L3 PROGMEM = { 61, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L3 PROGMEM = { 62, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_L3 PROGMEM = { 63, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_L3 PROGMEM = { 64, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT_L3 PROGMEM ={ 61, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT_L3 PROGMEM ={ 62, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L3 PROGMEM = { 71, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L3 PROGMEM = { 72, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L3 PROGMEM = { 73, 7, 0 };
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsData.h"
|
||||
|
||||
AmsData::AmsData() {}
|
||||
@@ -8,23 +14,27 @@ void AmsData::apply(AmsData& other) {
|
||||
|
||||
if(ms > 0) {
|
||||
if(other.getActiveImportPower() > 0) {
|
||||
float add = other.getActiveImportPower() * (((float) ms) / 3600000.0);
|
||||
uint32_t power = (activeImportPower + other.getActiveImportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
activeImportCounter += add / 1000.0;
|
||||
//Serial.printf("%dW, %dms, %.6fkWh added\n", other.getActiveImportPower(), ms, add);
|
||||
}
|
||||
|
||||
if(other.getListType() > 1) {
|
||||
ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastList2;
|
||||
ms = this->lastList2 > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastList2;
|
||||
if(other.getActiveExportPower() > 0) {
|
||||
float add = other.getActiveExportPower() * (((float) ms) / 3600000.0);
|
||||
uint32_t power = (activeExportPower + other.getActiveExportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
activeExportCounter += add / 1000.0;
|
||||
}
|
||||
if(other.getReactiveImportPower() > 0) {
|
||||
float add = other.getReactiveImportPower() * (((float) ms) / 3600000.0);
|
||||
uint32_t power = (reactiveImportPower + other.getReactiveImportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
reactiveImportCounter += add / 1000.0;
|
||||
}
|
||||
if(other.getReactiveExportPower() > 0) {
|
||||
float add = other.getReactiveExportPower() * (((float) ms) / 3600000.0);
|
||||
uint32_t power = (reactiveExportPower + other.getReactiveExportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
reactiveExportCounter += add / 1000.0;
|
||||
}
|
||||
}
|
||||
@@ -51,12 +61,31 @@ void AmsData::apply(AmsData& other) {
|
||||
this->l1activeExportPower = other.getL1ActiveExportPower();
|
||||
this->l2activeExportPower = other.getL2ActiveExportPower();
|
||||
this->l3activeExportPower = other.getL3ActiveExportPower();
|
||||
this->l1activeImportCounter = other.getL1ActiveImportCounter();
|
||||
this->l2activeImportCounter = other.getL2ActiveImportCounter();
|
||||
this->l3activeImportCounter = other.getL3ActiveImportCounter();
|
||||
this->l1activeExportCounter = other.getL1ActiveExportCounter();
|
||||
this->l2activeExportCounter = other.getL2ActiveExportCounter();
|
||||
this->l3activeExportCounter = other.getL3ActiveExportCounter();
|
||||
case 3:
|
||||
this->meterTimestamp = other.getMeterTimestamp();
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
this->activeExportCounter = other.getActiveExportCounter();
|
||||
this->reactiveImportCounter = other.getReactiveImportCounter();
|
||||
this->reactiveExportCounter = other.getReactiveExportCounter();
|
||||
// Aidon tends to sometime send the same counter as last hour by accident
|
||||
if(meterType == AmsTypeAidon && counterEstimated && lastKnownCounter == other.getActiveImportCounter()-other.getActiveExportCounter()) {
|
||||
double diff = activeImportCounter - activeExportCounter - lastKnownCounter;
|
||||
if(diff < 1.0) { // In case a very low value have been calculated, use the new values
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
this->activeExportCounter = other.getActiveExportCounter();
|
||||
this->reactiveImportCounter = other.getReactiveImportCounter();
|
||||
this->reactiveExportCounter = other.getReactiveExportCounter();
|
||||
this->lastKnownCounter = activeImportCounter - activeExportCounter;
|
||||
}
|
||||
} else {
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
this->activeExportCounter = other.getActiveExportCounter();
|
||||
this->reactiveImportCounter = other.getReactiveImportCounter();
|
||||
this->reactiveExportCounter = other.getReactiveExportCounter();
|
||||
this->lastKnownCounter = activeImportCounter - activeExportCounter;
|
||||
}
|
||||
this->counterEstimated = false;
|
||||
case 2:
|
||||
this->listId = other.getListId();
|
||||
@@ -83,6 +112,159 @@ void AmsData::apply(AmsData& other) {
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
}
|
||||
|
||||
void AmsData::apply(OBIS_code_t obis, double value) {
|
||||
if(obis.sensor == 0 && obis.gr == 0 && obis.tariff == 0) {
|
||||
meterType = value;
|
||||
}
|
||||
if(obis.gr == 1) {
|
||||
if(obis.sensor == 96) {
|
||||
if(obis.tariff == 0) {
|
||||
meterId = String((long) value, 10);
|
||||
return;
|
||||
} else if(obis.tariff == 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(obis.tariff != 0) {
|
||||
Serial.println("Tariff not implemented");
|
||||
return;
|
||||
}
|
||||
if(obis.gr == 7) { // Instant values
|
||||
switch(obis.sensor) {
|
||||
case 1:
|
||||
activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 2:
|
||||
activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 13:
|
||||
powerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 21:
|
||||
l1activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 22:
|
||||
l1activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 31:
|
||||
l1current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 32:
|
||||
l1voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 33:
|
||||
l1PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 41:
|
||||
l2activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 42:
|
||||
l2activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 51:
|
||||
l2current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 52:
|
||||
l2voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 53:
|
||||
l2PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 61:
|
||||
l3activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 62:
|
||||
l3activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 71:
|
||||
l3current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 72:
|
||||
l3voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 73:
|
||||
l3PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
}
|
||||
} else if(obis.gr == 8) { // Accumulated values
|
||||
switch(obis.sensor) {
|
||||
case 1:
|
||||
activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 2:
|
||||
activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 21:
|
||||
l1activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 22:
|
||||
l1activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 41:
|
||||
l2activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 42:
|
||||
l2activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 61:
|
||||
l3activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 62:
|
||||
l3activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(listType > 0)
|
||||
lastUpdateMillis = millis();
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
if(!threePhase)
|
||||
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
|
||||
}
|
||||
|
||||
uint64_t AmsData::getLastUpdateMillis() {
|
||||
return this->lastUpdateMillis;
|
||||
}
|
||||
@@ -171,30 +353,54 @@ float AmsData::getL3PowerFactor() {
|
||||
return this->l3PowerFactor;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveImportPower() {
|
||||
uint32_t AmsData::getL1ActiveImportPower() {
|
||||
return this->l1activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveImportPower() {
|
||||
uint32_t AmsData::getL2ActiveImportPower() {
|
||||
return this->l2activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveImportPower() {
|
||||
uint32_t AmsData::getL3ActiveImportPower() {
|
||||
return this->l3activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveExportPower() {
|
||||
uint32_t AmsData::getL1ActiveExportPower() {
|
||||
return this->l1activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveExportPower() {
|
||||
uint32_t AmsData::getL2ActiveExportPower() {
|
||||
return this->l2activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveExportPower() {
|
||||
uint32_t AmsData::getL3ActiveExportPower() {
|
||||
return this->l3activeExportPower;
|
||||
}
|
||||
|
||||
double AmsData::getL1ActiveImportCounter() {
|
||||
return this->l1activeImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL2ActiveImportCounter() {
|
||||
return this->l2activeImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL3ActiveImportCounter() {
|
||||
return this->l3activeImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL1ActiveExportCounter() {
|
||||
return this->l1activeExportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL2ActiveExportCounter() {
|
||||
return this->l2activeExportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getL3ActiveExportCounter() {
|
||||
return this->l3activeExportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getActiveImportCounter() {
|
||||
return this->activeImportCounter;
|
||||
}
|
||||
@@ -219,6 +425,10 @@ bool AmsData::isTwoPhase() {
|
||||
return this->twoPhase;
|
||||
}
|
||||
|
||||
bool AmsData::isCounterEstimated() {
|
||||
return this->counterEstimated;
|
||||
}
|
||||
|
||||
bool AmsData::isL2currentMissing() {
|
||||
return this->l2currentMissing;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSDATASTORAGE_H
|
||||
#define _AMSDATASTORAGE_H
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "Timezone.h"
|
||||
|
||||
struct DayDataPoints {
|
||||
struct DayDataPoints5 {
|
||||
uint8_t version;
|
||||
uint16_t hImport[24];
|
||||
time_t lastMeterReadTime;
|
||||
@@ -13,9 +21,9 @@ struct DayDataPoints {
|
||||
uint32_t activeExport;
|
||||
uint16_t hExport[24];
|
||||
uint8_t accuracy;
|
||||
}; // 113 bytes
|
||||
};
|
||||
|
||||
struct MonthDataPoints {
|
||||
struct MonthDataPoints6 {
|
||||
uint8_t version;
|
||||
uint16_t dImport[31];
|
||||
time_t lastMeterReadTime;
|
||||
@@ -23,13 +31,37 @@ struct MonthDataPoints {
|
||||
uint32_t activeExport;
|
||||
uint16_t dExport[31];
|
||||
uint8_t accuracy;
|
||||
}; // 142 bytes
|
||||
};
|
||||
|
||||
struct DayDataPoints {
|
||||
uint8_t version;
|
||||
uint16_t hImport[24];
|
||||
time_t lastMeterReadTime;
|
||||
uint64_t activeImport;
|
||||
uint64_t activeExport;
|
||||
uint16_t hExport[24];
|
||||
uint8_t accuracy;
|
||||
};
|
||||
|
||||
struct MonthDataPoints {
|
||||
uint8_t version;
|
||||
uint16_t dImport[31];
|
||||
time_t lastMeterReadTime;
|
||||
uint64_t activeImport;
|
||||
uint64_t activeExport;
|
||||
uint16_t dExport[31];
|
||||
uint8_t accuracy;
|
||||
};
|
||||
|
||||
class AmsDataStorage {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsDataStorage(RemoteDebug*);
|
||||
#else
|
||||
AmsDataStorage(Stream*);
|
||||
#endif
|
||||
void setTimezone(Timezone*);
|
||||
bool update(AmsData*);
|
||||
bool update(AmsData* data, time_t now);
|
||||
uint32_t getHourImport(uint8_t);
|
||||
uint32_t getHourExport(uint8_t);
|
||||
uint32_t getDayImport(uint8_t);
|
||||
@@ -47,9 +79,16 @@ public:
|
||||
uint8_t getMonthAccuracy();
|
||||
void setMonthAccuracy(uint8_t);
|
||||
|
||||
bool isHappy();
|
||||
bool isDayHappy();
|
||||
bool isMonthHappy();
|
||||
bool isHappy(time_t now);
|
||||
bool isDayHappy(time_t now);
|
||||
bool isMonthHappy(time_t now);
|
||||
|
||||
double getEstimatedImportCounter();
|
||||
|
||||
void setHourImport(uint8_t, uint32_t);
|
||||
void setHourExport(uint8_t, uint32_t);
|
||||
void setDayImport(uint8_t, uint32_t);
|
||||
void setDayExport(uint8_t, uint32_t);
|
||||
|
||||
private:
|
||||
Timezone* tz;
|
||||
@@ -67,11 +106,11 @@ private:
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
10
|
||||
};
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
void setHourImport(uint8_t, uint32_t);
|
||||
void setHourExport(uint8_t, uint32_t);
|
||||
void setDayImport(uint8_t, uint32_t);
|
||||
void setDayExport(uint8_t, uint32_t);
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsDataStorage.h"
|
||||
#include <lwip/apps/sntp.h>
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
|
||||
day.version = 5;
|
||||
#else
|
||||
AmsDataStorage::AmsDataStorage(Stream* debugger) {
|
||||
#endif
|
||||
day.version = 6;
|
||||
day.accuracy = 1;
|
||||
month.version = 6;
|
||||
month.version = 7;
|
||||
month.accuracy = 1;
|
||||
this->debugger = debugger;
|
||||
}
|
||||
@@ -16,35 +26,34 @@ void AmsDataStorage::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::update(AmsData* data) {
|
||||
if(isHappy()) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Data is up to date\n"));
|
||||
bool AmsDataStorage::update(AmsData* data, time_t now) {
|
||||
if(isHappy(now)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Happy, not updating\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Time is: %lu\n"), (int32_t) now);
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("No timezone, not updating\n"));
|
||||
return false;
|
||||
}
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
if(data->getMeterTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
now = data->getMeterTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Using meter timestamp, which is: %lu\n"), (int32_t) now);
|
||||
}
|
||||
} else if(data->getPackageTimestamp() > FirmwareVersion::BuildEpoch) {
|
||||
now = data->getPackageTimestamp();
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Using package timestamp, which is: %lu\n"), (int32_t) now);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf_P(PSTR("(AmsDataStorage) Invalid time: %lu\n"), (int32_t) now);
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Before build time, not updating\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -54,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,6 +16,10 @@
|
||||
#define DATA_TAG_MBUS 0x68
|
||||
#define DATA_TAG_GBT 0xE0
|
||||
#define DATA_TAG_GCM 0xDB
|
||||
#define DATA_TAG_SNRM 0x81
|
||||
#define DATA_TAG_AARQ 0x60
|
||||
#define DATA_TAG_AARE 0x61
|
||||
#define DATA_TAG_RES 0xC4 // Get Response
|
||||
|
||||
#define DATA_PARSE_OK 0
|
||||
#define DATA_PARSE_FAIL -1
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATAPASERSERS_H
|
||||
#define _DATAPASERSERS_H
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DLMSPARSER_H
|
||||
#define _DLMSPARSER_H
|
||||
|
||||
|
||||
@@ -1,17 +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>
|
||||
@@ -18,7 +24,7 @@ time_t decodeCosemDateTime(CosemDateTime timestamp) {
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(timestamp.deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
time += deviation * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DlmsParser.h"
|
||||
#include "Cosem.h"
|
||||
|
||||
|
||||
@@ -1,29 +1,85 @@
|
||||
/**
|
||||
* @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 == '!') 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) {
|
||||
|
||||
// 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 != 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;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GbtParser.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GcmParser.h"
|
||||
#include "lwip/def.h"
|
||||
#if defined(ESP8266)
|
||||
@@ -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_BOUNDRY_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"
|
||||
@@ -49,8 +55,41 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
ptr += 3;
|
||||
|
||||
// Exclude all of header and 3 byte footer
|
||||
ctx.length -= ptr-d+3;
|
||||
return ptr-d;
|
||||
ctx.length -= ptr-d;
|
||||
if(ctx.length > 1) {
|
||||
ctx.length -= 3;
|
||||
}
|
||||
|
||||
// Payload incomplete
|
||||
if((h->format & 0x08) == 0x08) {
|
||||
if(lastSequenceNumber == 0) {
|
||||
if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024);
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
|
||||
pos += ctx.length;
|
||||
|
||||
lastSequenceNumber++;
|
||||
return DATA_PARSE_INTERMEDIATE_SEGMENT;
|
||||
} else if(lastSequenceNumber > 0) {
|
||||
lastSequenceNumber = 0;
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
|
||||
pos += ctx.length;
|
||||
|
||||
memcpy((uint8_t *) d, buf, pos);
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
ctx.length = pos;
|
||||
pos = 0;
|
||||
return DATA_PARSE_OK;
|
||||
} else {
|
||||
return ptr-d;
|
||||
}
|
||||
}
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LlcParser.h"
|
||||
|
||||
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MbusParser.h"
|
||||
|
||||
int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "crc.h"
|
||||
|
||||
uint16_t crc16_x25(const uint8_t* p, int len)
|
||||
@@ -15,7 +21,7 @@ uint16_t crc16 (const uint8_t *p, int len) {
|
||||
uint16_t crc = 0;
|
||||
|
||||
while (len--) {
|
||||
int i;
|
||||
uint8_t i;
|
||||
crc ^= *p++;
|
||||
for (i = 0 ; i < 8 ; ++i) {
|
||||
if (crc & 1)
|
||||
@@ -26,4 +32,4 @@ uint16_t crc16 (const uint8_t *p, int len) {
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ntohll.h"
|
||||
|
||||
uint64_t ntohll(uint64_t x) {
|
||||
|
||||
140
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
140
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#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>
|
||||
#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
|
||||
|
||||
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();
|
||||
|
||||
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 firmwareVariant;
|
||||
bool autoUpgrade;
|
||||
char nextVersion[10];
|
||||
|
||||
|
||||
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
|
||||
};
|
||||
1252
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
1252
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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,12 +22,24 @@
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
this->updater = updater;
|
||||
mqtt.dropOverflow(true);
|
||||
};
|
||||
#else
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
mqtt.dropOverflow(true);
|
||||
};
|
||||
#endif
|
||||
|
||||
void setCaVerification(bool);
|
||||
void setConfig(MqttConfig& mqttConfig);
|
||||
@@ -33,11 +52,13 @@ public:
|
||||
|
||||
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 publishPrices(PriceService* ps) { return false; };
|
||||
virtual bool publishSystem(HwTools*, PriceService*, EnergyAccounting*) { return false; };
|
||||
virtual bool publishRaw(String data) { return false; };
|
||||
virtual bool publishFirmware() { return false; };
|
||||
virtual void onMessage(String &topic, String &payload) {};
|
||||
|
||||
virtual ~AmsMqttHandler() {
|
||||
@@ -48,8 +69,13 @@ public:
|
||||
};
|
||||
|
||||
protected:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
MqttConfig mqttConfig;
|
||||
bool mqttConfigChanged = true;
|
||||
MQTTClient mqtt = MQTTClient(256);
|
||||
unsigned long lastMqttRetry = -10000;
|
||||
bool caVerification = true;
|
||||
@@ -57,6 +83,9 @@ protected:
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
char* json;
|
||||
uint16_t BufferSize = 2048;
|
||||
uint64_t lastStateUpdate = 0;
|
||||
|
||||
AmsFirmwareUpdater* updater = NULL;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "AmsStorage.h"
|
||||
@@ -9,6 +15,7 @@ void AmsMqttHandler::setCaVerification(bool caVerification) {
|
||||
|
||||
void AmsMqttHandler::setConfig(MqttConfig& mqttConfig) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::connect() {
|
||||
@@ -19,112 +26,89 @@ bool AmsMqttHandler::connect() {
|
||||
lastMqttRetry = millis();
|
||||
|
||||
time_t epoch = time(nullptr);
|
||||
|
||||
WiFiClient *actualClient = NULL;
|
||||
|
||||
if(mqttConfig.ssl) {
|
||||
if(epoch < FirmwareVersion::BuildEpoch) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("NTP not ready for MQTT SSL\n"));
|
||||
return false;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("MQTT SSL is configured (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
if(mqttSecureClient == NULL) {
|
||||
mqttSecureClient = new WiFiClientSecure();
|
||||
#if defined(ESP8266)
|
||||
mqttSecureClient->setBufferSizes(512, 512);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("ESP8266 firmware does not have enough memory...\n"));
|
||||
return false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
if(mqttConfigChanged) {
|
||||
if(caVerification && LittleFS.begin()) {
|
||||
File file;
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_CA)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT CA file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CA, (char*) "r");
|
||||
#if defined(ESP8266)
|
||||
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
|
||||
mqttSecureClient->setTrustAnchors(serverTrustedCA);
|
||||
#elif defined(ESP32)
|
||||
if(mqttSecureClient->loadCACert(file, file.size())) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA accepted\n"));
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("CA was rejected\n"));
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
if(!mqttSecureClient->loadCACert(file, file.size())) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
file.close();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No CA, disabling validation\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
||||
file.close();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
||||
file.close();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Loading cert and key (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
if(LittleFS.exists(FILE_MQTT_CERT)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
mqttSecureClient->loadCertificate(file, file.size());
|
||||
file.close();
|
||||
}
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||
file.close();
|
||||
}
|
||||
#endif
|
||||
|
||||
LittleFS.end();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA verification disabled\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
}
|
||||
mqttClient = mqttSecureClient;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("MQTT SSL setup complete (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
}
|
||||
} else if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient->stop();
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
mqttClient = NULL;
|
||||
}
|
||||
|
||||
if(mqttClient == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No SSL, using client without SSL support\n"));
|
||||
mqttClient = new WiFiClient();
|
||||
actualClient = mqttSecureClient;
|
||||
} else {
|
||||
if(mqttClient == NULL) {
|
||||
mqttClient = new WiFiClient();
|
||||
}
|
||||
actualClient = mqttClient;
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Connecting to MQTT %s:%d\n"), mqttConfig.host, mqttConfig.port);
|
||||
|
||||
mqtt.begin(mqttConfig.host, mqttConfig.port, *mqttClient);
|
||||
mqttConfigChanged = false;
|
||||
mqtt.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
|
||||
@@ -132,17 +116,32 @@ bool AmsMqttHandler::connect() {
|
||||
// Connect to a unsecure or secure MQTT server
|
||||
if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) ||
|
||||
(strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
|
||||
mqtt.onMessage(std::bind(&AmsMqttHandler::onMessage, this, std::placeholders::_1, std::placeholders::_2));
|
||||
if(strlen(mqttConfig.subscribeTopic) > 0) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR(" Subscribing to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Subscribing to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
if(!mqtt.subscribe(mqttConfig.subscribeTopic)) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), mqttConfig.subscribeTopic);
|
||||
}
|
||||
}
|
||||
mqtt.publish(statusTopic, "online", true, 0);
|
||||
mqtt.loop();
|
||||
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) {
|
||||
@@ -158,17 +157,12 @@ bool AmsMqttHandler::connect() {
|
||||
void AmsMqttHandler::disconnect() {
|
||||
mqtt.disconnect();
|
||||
mqtt.loop();
|
||||
if(mqttSecureClient != NULL) {
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
delay(10);
|
||||
yield();
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
mqttClient = NULL;
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lwmqtt_err_t AmsMqttHandler::lastError() {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CLOUDCONNECTOR_H
|
||||
#define _CLOUDCONNECTOR_H
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "mbedtls/ssl.h"
|
||||
#include "mbedtls/platform.h"
|
||||
#include "mbedtls/net.h"
|
||||
@@ -11,18 +19,32 @@
|
||||
#include "mbedtls/error.h"
|
||||
#include "mbedtls/certs.h"
|
||||
#include "mbedtls/rsa.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "AmsData.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "ConnectionHandler.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <WiFiUdp.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
const unsigned char PUBLIC_KEY[] = \
|
||||
"-----BEGIN PUBLIC KEY-----\n"\
|
||||
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoIo0CSuuX3tAdF7KPssdlzJNX\n"\
|
||||
"QryhgVV1rQIFPhHv3SxzyKtRrRM9s0CVfymcibhnEBXxxg3pxlGmwI/R6k7HHXJN\n"\
|
||||
"lBsXzzDtZ/GHDVnw+xRakTfRT0Zt+xdJSH5xJNWq4EwpvJfjA22L1Nz4dKSpgWMx\n"\
|
||||
"VRndAaXf0s7Q1XBz2wIDAQAB\n"\
|
||||
"-----END PUBLIC KEY-----\0";
|
||||
#define CC_BUF_SIZE 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
|
||||
262
lib/ConnectionHandler/src/EthernetConnectionHandler.cpp
Normal file
262
lib/ConnectionHandler/src/EthernetConnectionHandler.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* @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"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start Ethernet\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void EthernetConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
}
|
||||
|
||||
bool EthernetConnectionHandler::isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
connected = true;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Successfully connected to Ethernet!\n"));
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("IP: %s\n"), getIP().toString().c_str());
|
||||
debugger->printf_P(PSTR("GW: %s\n"), getGateway().toString().c_str());
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
IPAddress dns4 = getDns(i);
|
||||
if(dns4 == 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,19 +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) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->config = config;
|
||||
};
|
||||
#endif
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ENERGYACCOUNTING_H
|
||||
#define _ENERGYACCOUNTING_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include "EntsoeApi.h"
|
||||
#include "PriceService.h"
|
||||
|
||||
struct EnergyAccountingPeak {
|
||||
uint8_t day;
|
||||
@@ -74,9 +80,13 @@ struct EnergyAccountingRealtimeData {
|
||||
|
||||
class EnergyAccounting {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
EnergyAccounting(RemoteDebug*, EnergyAccountingRealtimeData*);
|
||||
#else
|
||||
EnergyAccounting(Stream*, EnergyAccountingRealtimeData*);
|
||||
#endif
|
||||
void setup(AmsDataStorage *ds, EnergyAccountingConfig *config);
|
||||
void setEapi(EntsoeApi *eapi);
|
||||
void setPriceService(PriceService *ps);
|
||||
void setTimezone(Timezone*);
|
||||
EnergyAccountingConfig* getConfig();
|
||||
bool update(AmsData* amsData);
|
||||
@@ -113,19 +123,22 @@ public:
|
||||
EnergyAccountingData getData();
|
||||
void setData(EnergyAccountingData&);
|
||||
|
||||
void setFixedPrice(float price, String currency);
|
||||
float getPriceForHour(uint8_t h);
|
||||
void setCurrency(String currency);
|
||||
float getPriceForHour(uint8_t d, uint8_t h);
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
bool init = false, initPrice = false;
|
||||
AmsDataStorage *ds = NULL;
|
||||
EntsoeApi *eapi = NULL;
|
||||
PriceService *ps = NULL;
|
||||
EnergyAccountingConfig *config = NULL;
|
||||
Timezone *tz = NULL;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
EnergyAccountingRealtimeData* realtimeData = NULL;
|
||||
float fixedPrice = 0;
|
||||
String currency = "";
|
||||
|
||||
void calcDayCost();
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EnergyAccounting.h"
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger, EnergyAccountingRealtimeData* rtd) {
|
||||
#else
|
||||
EnergyAccounting::EnergyAccounting(Stream* Stream, EnergyAccountingRealtimeData* rtd) {
|
||||
#endif
|
||||
data.version = 1;
|
||||
this->debugger = debugger;
|
||||
if(rtd->magic != 0x6A) {
|
||||
@@ -28,8 +38,8 @@ void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config)
|
||||
this->config = config;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setEapi(EntsoeApi *eapi) {
|
||||
this->eapi = eapi;
|
||||
void EnergyAccounting::setPriceService(PriceService *ps) {
|
||||
this->ps = ps;
|
||||
}
|
||||
|
||||
EnergyAccountingConfig* EnergyAccounting::getConfig() {
|
||||
@@ -49,7 +59,6 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,9 +71,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
this->realtimeData->lastExportUpdateMillis = 0;
|
||||
this->realtimeData->currentHour = local.Hour;
|
||||
this->realtimeData->currentDay = local.Day;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Initializing data at %lu\n"), (int32_t) now);
|
||||
if(!load()) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Unable to load existing data\n"));
|
||||
data = { 6, local.Month,
|
||||
0, 0, 0, // Cost
|
||||
0, 0, 0, // Income
|
||||
@@ -75,28 +82,19 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
} else if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Peak hour from day %d: %d\n"), data.peaks[i].day, data.peaks[i].value*10);
|
||||
}
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n"), data.costYesterday / 100.0, data.costThisMonth / 100.0, data.costLastMonth / 100.0);
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Loaded income yesterday: %.2f, this month: %d, last month: %d\n"), data.incomeYesterday / 100.0, data.incomeThisMonth / 100.0, data.incomeLastMonth / 100.0);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
float price = getPriceForHour(0);
|
||||
if(!initPrice && price != ENTSOE_NO_VALUE) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Initializing prices at %lu\n"), (int32_t) now);
|
||||
float importPrice = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
|
||||
if(!initPrice && importPrice != PRICE_NO_VALUE) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
if(local.Hour != this->realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New local hour %d\n"), local.Hour);
|
||||
|
||||
tmElements_t oneHrAgo, oneHrAgoLocal;
|
||||
breakTime(now-3600, oneHrAgo);
|
||||
uint16_t val = ds->getHourImport(oneHrAgo.Hour) / 10;
|
||||
uint16_t val = round(ds->getHourImport(oneHrAgo.Hour) / 10.0);
|
||||
|
||||
breakTime(tz->toLocal(now-3600), oneHrAgoLocal);
|
||||
ret |= updateMax(val, oneHrAgoLocal.Day);
|
||||
@@ -113,7 +111,6 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
|
||||
uint8_t prevDay = this->realtimeData->currentDay;
|
||||
if(local.Day != this->realtimeData->currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New day %d\n"), local.Day);
|
||||
data.costYesterday = this->realtimeData->costDay * 100;
|
||||
data.costThisMonth += this->realtimeData->costDay * 100;
|
||||
this->realtimeData->costDay = 0;
|
||||
@@ -127,7 +124,6 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
}
|
||||
|
||||
if(local.Month != data.month) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New month %d\n"), local.Month);
|
||||
data.costLastMonth = data.costThisMonth;
|
||||
data.costThisMonth = 0;
|
||||
data.incomeLastMonth = data.incomeThisMonth;
|
||||
@@ -162,11 +158,9 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastImportUpdateMillis;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhi > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh import\n"), kwhi);
|
||||
this->realtimeData->use += kwhi;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float cost = price * kwhi;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), cost / 100.0, currency.c_str());
|
||||
if(importPrice != PRICE_NO_VALUE) {
|
||||
float cost = importPrice * kwhi;
|
||||
this->realtimeData->costHour += cost;
|
||||
this->realtimeData->costDay += cost;
|
||||
}
|
||||
@@ -178,11 +172,10 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastExportUpdateMillis;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhe > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh export\n"), kwhe);
|
||||
this->realtimeData->produce += kwhe;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float income = price * kwhe;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), income / 100.0, currency.c_str());
|
||||
float exportPrice = getPriceForHour(PRICE_DIRECTION_EXPORT, 0);
|
||||
if(exportPrice != PRICE_NO_VALUE) {
|
||||
float income = exportPrice * kwhe;
|
||||
this->realtimeData->incomeHour += income;
|
||||
this->realtimeData->incomeDay += income;
|
||||
}
|
||||
@@ -191,9 +184,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
}
|
||||
|
||||
if(config != NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) calculating threshold, currently at %d\n"), this->realtimeData->currentThresholdIdx);
|
||||
while(getMonthMax() > config->thresholds[this->realtimeData->currentThresholdIdx] && this->realtimeData->currentThresholdIdx < 10) this->realtimeData->currentThresholdIdx++;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) new threshold %d\n"), this->realtimeData->currentThresholdIdx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -205,20 +196,25 @@ void EnergyAccounting::calcDayCost() {
|
||||
if(tz == NULL) return;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(getPriceForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(getPriceForHour(PRICE_DIRECTION_IMPORT, 0) != PRICE_NO_VALUE) {
|
||||
if(initPrice) {
|
||||
this->realtimeData->costDay = 0;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
}
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
float price = getPriceForHour(i - local.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
this->realtimeData->costDay += price * (wh / 1000.0);
|
||||
|
||||
wh = ds->getHourExport(utc.Hour);
|
||||
this->realtimeData->incomeDay += price * (wh / 1000.0);
|
||||
float priceIn = getPriceForHour(PRICE_DIRECTION_IMPORT, i - local.Hour);
|
||||
if(priceIn != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
this->realtimeData->costDay += priceIn * (wh / 1000.0);
|
||||
}
|
||||
|
||||
float priceOut = getPriceForHour(PRICE_DIRECTION_EXPORT, i - local.Hour);
|
||||
if(priceOut != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourExport(utc.Hour);
|
||||
this->realtimeData->incomeDay += priceOut * (wh / 1000.0);
|
||||
}
|
||||
}
|
||||
initPrice = true;
|
||||
}
|
||||
@@ -402,9 +398,6 @@ EnergyAccountingPeak EnergyAccounting::getPeak(uint8_t num) {
|
||||
|
||||
bool EnergyAccounting::load() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Unable to load LittleFS\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -414,7 +407,6 @@ bool EnergyAccounting::load() {
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Data version %d\n"), buf[0]);
|
||||
if(buf[0] == 6) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
@@ -492,14 +484,11 @@ bool EnergyAccounting::load() {
|
||||
}
|
||||
ret = true;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(EnergyAccounting) Unknown version\n"));
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(EnergyAccounting) File not found\n"));
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -507,9 +496,6 @@ bool EnergyAccounting::load() {
|
||||
|
||||
bool EnergyAccounting::save() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf_P(PSTR("(EnergyAccounting) Unable to load LittleFS\n"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
{
|
||||
@@ -536,7 +522,6 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(data.peaks[i].day == day || data.peaks[i].day == 0) {
|
||||
if(val > data.peaks[i].value) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Adding new max %d for day %d which is larger than %d\n"), val*10, day, data.peaks[i].value*10);
|
||||
data.peaks[i].day = day;
|
||||
data.peaks[i].value = val;
|
||||
return true;
|
||||
@@ -555,7 +540,6 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
}
|
||||
}
|
||||
if(idx < 5) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Adding new max %d for day %d\n"), val*10, day);
|
||||
data.peaks[idx].value = val;
|
||||
data.peaks[idx].day = day;
|
||||
return true;
|
||||
@@ -563,13 +547,11 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setFixedPrice(float price, String currency) {
|
||||
this->fixedPrice = price;
|
||||
void EnergyAccounting::setCurrency(String currency) {
|
||||
this->currency = currency;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getPriceForHour(uint8_t h) {
|
||||
if(fixedPrice > 0.0) return fixedPrice;
|
||||
if(eapi == NULL) return ENTSOE_NO_VALUE;
|
||||
return eapi->getValueForHour(h);
|
||||
float EnergyAccounting::getPriceForHour(uint8_t d, uint8_t h) {
|
||||
if(ps == NULL) return PRICE_NO_VALUE;
|
||||
return ps->getValueForHour(d, h);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
#ifndef _ENTSOEAPI_H
|
||||
#define _ENTSOEAPI_H
|
||||
|
||||
#include "TimeLib.h"
|
||||
#include "Timezone.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EntsoeA44Parser.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
#define SSL_BUF_SIZE 512
|
||||
|
||||
class EntsoeApi {
|
||||
public:
|
||||
EntsoeApi(RemoteDebug*);
|
||||
void setup(EntsoeConfig&);
|
||||
bool loop();
|
||||
|
||||
char* getToken();
|
||||
char* getCurrency();
|
||||
char* getArea();
|
||||
char* getSource();
|
||||
float getValueForHour(int8_t);
|
||||
float getValueForHour(time_t, int8_t);
|
||||
|
||||
int16_t getLastError();
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
EntsoeConfig* config = NULL;
|
||||
HTTPClient* http = NULL;
|
||||
|
||||
uint8_t currentDay = 0, currentHour = 0;
|
||||
uint8_t tomorrowFetchMinute = 15; // How many minutes over 13:00 should it fetch prices
|
||||
uint8_t nextFetchDelayMinutes = 15;
|
||||
uint64_t lastTodayFetch = 0;
|
||||
uint64_t lastTomorrowFetch = 0;
|
||||
uint64_t lastCurrencyFetch = 0;
|
||||
PricesContainer* today = NULL;
|
||||
PricesContainer* tomorrow = NULL;
|
||||
|
||||
Timezone* tz = NULL;
|
||||
|
||||
static const uint16_t BufferSize = 256;
|
||||
char* buf;
|
||||
|
||||
bool hub = false;
|
||||
uint8_t* key = NULL;
|
||||
uint8_t* auth = NULL;
|
||||
|
||||
float currencyMultiplier = 0;
|
||||
|
||||
int16_t lastError = 0;
|
||||
|
||||
PricesContainer* fetchPrices(time_t);
|
||||
bool retrieve(const char* url, Stream* doc);
|
||||
float getCurrencyMultiplier(const char* from, const char* to, time_t t);
|
||||
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
};
|
||||
#endif
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _FIRMWARE_VERSION_h
|
||||
#define _FIRMWARE_VERSION_h
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FirmwareVersion.h"
|
||||
#include "generated_version.h"
|
||||
|
||||
|
||||
@@ -1,76 +1,48 @@
|
||||
/**
|
||||
* @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) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#else
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#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(json, 128, PSTR("%s/status"), config.discoveryPrefix);
|
||||
statusTopic = String(buf);
|
||||
|
||||
snprintf_P(buf, 128, PSTR("%s/sensor/"), config.discoveryPrefix);
|
||||
discoveryTopic = String(buf);
|
||||
} else {
|
||||
statusTopic = F("homeassistant/status");
|
||||
discoveryTopic = F("homeassistant/sensor/");
|
||||
}
|
||||
// strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
|
||||
setHomeAssistantConfig(config);
|
||||
};
|
||||
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
bool publishFirmware();
|
||||
|
||||
bool postConnect();
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
void setHomeAssistantConfig(HomeAssistantConfig config);
|
||||
private:
|
||||
String topic;
|
||||
uint8_t boardType;
|
||||
|
||||
String pubTopic;
|
||||
String subTopic;
|
||||
|
||||
|
||||
String deviceName;
|
||||
String deviceModel;
|
||||
@@ -79,12 +51,14 @@ private:
|
||||
String deviceUrl;
|
||||
|
||||
String statusTopic;
|
||||
String discoveryTopic;
|
||||
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;
|
||||
bool tInit[32] = {false};
|
||||
bool prInit[38] = {false};
|
||||
uint32_t lastThresholdPublish = 0;
|
||||
|
||||
HwTools* hw;
|
||||
|
||||
@@ -93,7 +67,7 @@ private:
|
||||
bool publishList3(AmsData* data, EnergyAccounting* ea);
|
||||
bool publishList4(AmsData* data, EnergyAccounting* ea);
|
||||
String getMeterModel(AmsData* data);
|
||||
bool publishRealtime(AmsData* data, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishRealtime(AmsData* data, EnergyAccounting* ea, PriceService* ps);
|
||||
void publishSensor(const HomeAssistantSensor sensor);
|
||||
void publishList1Sensors();
|
||||
void publishList1ExportSensors();
|
||||
@@ -103,11 +77,12 @@ private:
|
||||
void publishList3ExportSensors();
|
||||
void publishList4Sensors();
|
||||
void publishList4ExportSensors();
|
||||
void publishRealtimeSensors(EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
void publishRealtimeExportSensors(EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
void publishRealtimeSensors(EnergyAccounting* ea, PriceService* ps);
|
||||
void publishRealtimeExportSensors(EnergyAccounting* ea, PriceService* ps);
|
||||
void publishTemperatureSensor(uint8_t index, String id);
|
||||
void publishPriceSensors(EntsoeApi* eapi);
|
||||
void publishPriceSensors(PriceService* ps);
|
||||
void publishSystemSensors();
|
||||
void publishThresholdSensors();
|
||||
|
||||
String boardTypeToString(uint8_t b) {
|
||||
switch(b) {
|
||||
@@ -155,6 +130,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
|
||||
|
||||
@@ -14,9 +20,10 @@ struct HomeAssistantSensor {
|
||||
};
|
||||
|
||||
|
||||
const uint8_t List1SensorCount PROGMEM = 1;
|
||||
const uint8_t List1SensorCount PROGMEM = 2;
|
||||
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
|
||||
{"Active import", "/power", "P", 30, "W", "power", "measurement"}
|
||||
{"Active import", "/power", "P", 30, "W", "power", "measurement"},
|
||||
{"Data timestamp", "/power", "t", 30, "", "timestamp", ""}
|
||||
};
|
||||
|
||||
const uint8_t List2SensorCount PROGMEM = 8;
|
||||
@@ -36,11 +43,12 @@ const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = {
|
||||
{"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", 4000, "kWh", "energy", "total_increasing"},
|
||||
{"Accumulated reactive import","/energy", "tQI", 4000, "kvarh","", "total_increasing"},
|
||||
{"Accumulated reactive export","/energy", "tQO", 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;
|
||||
@@ -48,28 +56,34 @@ const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = {
|
||||
{"Accumulated active export", "/energy", "tPO", 4000, "kWh", "energy", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t List4SensorCount PROGMEM = 7;
|
||||
const uint8_t List4SensorCount PROGMEM = 10;
|
||||
const HomeAssistantSensor List4Sensors[List4SensorCount] PROGMEM = {
|
||||
{"Power factor", "/power", "PF", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 power factor", "/power", "PF1", 30, "%", "power_factor", "measurement"},
|
||||
{"L2 power factor", "/power", "PF2", 30, "%", "power_factor", "measurement"},
|
||||
{"L3 power factor", "/power", "PF3", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 active import", "/power", "P1", 30, "W", "power", "measurement"},
|
||||
{"L2 active import", "/power", "P2", 30, "W", "power", "measurement"},
|
||||
{"L3 active import", "/power", "P3", 30, "W", "power", "measurement"}
|
||||
{"Power factor", "/power", "PF", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 power factor", "/power", "PF1", 30, "%", "power_factor", "measurement"},
|
||||
{"L2 power factor", "/power", "PF2", 30, "%", "power_factor", "measurement"},
|
||||
{"L3 power factor", "/power", "PF3", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 active import", "/power", "P1", 30, "W", "power", "measurement"},
|
||||
{"L2 active import", "/power", "P2", 30, "W", "power", "measurement"},
|
||||
{"L3 active import", "/power", "P3", 30, "W", "power", "measurement"},
|
||||
{"L1 accumulated active import","/power", "tPI1", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L2 accumulated active import","/power", "tPI2", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L3 accumulated active import","/power", "tPI3", 30, "kWh", "energy", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t List4ExportSensorCount PROGMEM = 3;
|
||||
const uint8_t List4ExportSensorCount PROGMEM = 6;
|
||||
const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
|
||||
{"L1 active export", "/power", "PO1", 30, "W", "power", "measurement"},
|
||||
{"L2 active export", "/power", "PO2", 30, "W", "power", "measurement"},
|
||||
{"L3 active export", "/power", "PO3", 30, "W", "power", "measurement"}
|
||||
{"L1 active export", "/power", "PO1", 30, "W", "power", "measurement"},
|
||||
{"L2 active export", "/power", "PO2", 30, "W", "power", "measurement"},
|
||||
{"L3 active export", "/power", "PO3", 30, "W", "power", "measurement"},
|
||||
{"L1 accumulated active export","/power", "tPO1", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L2 accumulated active export","/power", "tPO2", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L3 accumulated active export","/power", "tPO3", 30, "kWh", "energy", "total_increasing"}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeSensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = {
|
||||
{"Month max", "/realtime","max", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Month max", "/realtime","max", 120, "kWh", "energy", ""},
|
||||
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", ""},
|
||||
{"Current hour used", "/realtime","hour.use", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current hour cost", "/realtime","hour.cost", 120, "", "monetary", ""},
|
||||
{"Current day used", "/realtime","day.use", 120, "kWh", "energy", "total_increasing"},
|
||||
@@ -89,6 +103,7 @@ const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGM
|
||||
};
|
||||
|
||||
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", 4000, "kWh", "energy", ""};
|
||||
const HomeAssistantSensor RealtimeThresholdSensor PROGMEM = {"Tariff threshold %d", "/realtime", "thresholds[%d]", 4000, "kWh", "energy", ""};
|
||||
|
||||
const uint8_t PriceSensorCount PROGMEM = 5;
|
||||
const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
|
||||
@@ -101,10 +116,11 @@ const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
|
||||
|
||||
const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices", "prices['%d']", 4000, "", "monetary", ""};
|
||||
|
||||
const uint8_t SystemSensorCount PROGMEM = 2;
|
||||
const uint8_t SystemSensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
|
||||
{"Status", "/state", "rssi", 180, "dBm", "signal_strength", "measurement"},
|
||||
{"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement"}
|
||||
{"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement"},
|
||||
{"Uptime", "/state", "up", 180, "s", "duration", "measurement"}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", 900, "°C", "temperature", "measurement"};
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,53 +12,151 @@
|
||||
#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) {
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = fInit = false;
|
||||
|
||||
pubTopic = String(mqttConfig.publishTopic);
|
||||
subTopic = String(mqttConfig.subscribeTopic);
|
||||
if(subTopic.isEmpty()) subTopic = pubTopic+"/command";
|
||||
|
||||
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 {
|
||||
deviceName = F("AMS reader");
|
||||
sensorNamePrefix = "";
|
||||
}
|
||||
deviceModel = boardTypeToString(boardType);
|
||||
manufacturer = boardManufacturerToString(boardType);
|
||||
|
||||
char hostname[32];
|
||||
#if defined(ESP8266)
|
||||
strcpy(hostname, WiFi.hostname().c_str());
|
||||
#elif defined(ESP32)
|
||||
strcpy(hostname, WiFi.getHostname());
|
||||
#endif
|
||||
|
||||
stripNonAscii((uint8_t*) hostname, 32, false);
|
||||
deviceUid = String(hostname); // Maybe configurable in the future?
|
||||
|
||||
if(strlen(config.discoveryHostname) > 0) {
|
||||
if(strncmp_P(config.discoveryHostname, PSTR("http"), 4) == 0) {
|
||||
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(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);
|
||||
} else {
|
||||
statusTopic = F("homeassistant/status");
|
||||
sensorTopic = F("homeassistant/sensor");
|
||||
updateTopic = F("homeassistant/update");
|
||||
}
|
||||
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::postConnect() {
|
||||
if(!subTopic.isEmpty() && !mqtt.subscribe(subTopic)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to to [%s]\n"), subTopic.c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(pubTopic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(time(nullptr) < FirmwareVersion::BuildEpoch)
|
||||
return false;
|
||||
|
||||
// if(data->getListType() >= 3) { // publish energy counts
|
||||
// publishList3(data, ea);
|
||||
// loop();
|
||||
// }
|
||||
AmsData data;
|
||||
if(mqttConfig.stateUpdate) {
|
||||
uint64_t now = millis64();
|
||||
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
|
||||
data.apply(*previousState);
|
||||
data.apply(*update);
|
||||
lastStateUpdate = now;
|
||||
} else {
|
||||
data = *update;
|
||||
}
|
||||
|
||||
// if(data->getListType() == 1) { // publish power counts
|
||||
// publishList1(data, ea);
|
||||
// } else if(data->getListType() <= 3) { // publish power counts and volts/amps
|
||||
// publishList2(data, ea);
|
||||
// } else if(data->getListType() == 4) { // publish power counts and volts/amps/phase power and PF
|
||||
// publishList4(data, ea);
|
||||
// }
|
||||
if(data.getListType() >= 3 && !data.isCounterEstimated()) { // publish energy counts
|
||||
publishList3(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if(data.getListType() == 1) { // publish power counts
|
||||
publishList1(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() <= 3) { // publish power counts and volts/amps
|
||||
publishList2(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 4) { // publish power counts and volts/amps/phase power and PF
|
||||
publishList4(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if(ea->isInitialized()) {
|
||||
publishRealtime(&data, ea, ps);
|
||||
mqtt.loop();
|
||||
}
|
||||
loop();
|
||||
|
||||
// if(ea->isInitialized()) {
|
||||
// publishRealtime(data, ea, eapi);
|
||||
// loop();
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
|
||||
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];
|
||||
memset(pt, 0, 24);
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getPackageTimestamp(), tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
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];
|
||||
memset(pt, 0, 24);
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getPackageTimestamp(), tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
snprintf_P(json, BufferSize, HA3_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -66,27 +170,55 @@ 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];
|
||||
memset(mt, 0, 24);
|
||||
if(data->getMeterTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getMeterTimestamp(), tm);
|
||||
sprintf_P(mt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
char pt[24];
|
||||
memset(pt, 0, 24);
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getPackageTimestamp(), tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
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];
|
||||
memset(pt, 0, 24);
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getPackageTimestamp(), tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
snprintf_P(json, BufferSize, HA4_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -107,12 +239,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) {
|
||||
@@ -121,9 +260,10 @@ String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
return meterModel;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
publishRealtimeSensors(ea, eapi);
|
||||
if(ea->getProducedThisHour() > 0.0 || ea->getProducedToday() > 0.0 || ea->getProducedThisMonth() > 0.0) publishRealtimeExportSensors(ea, eapi);
|
||||
bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting* ea, PriceService* ps) {
|
||||
publishRealtimeSensors(ea, ps);
|
||||
if(ea->getProducedThisHour() > 0.0 || ea->getProducedToday() > 0.0 || ea->getProducedThisMonth() > 0.0) publishRealtimeExportSensors(ea, ps);
|
||||
if(lastThresholdPublish == 0) publishThresholdSensors();
|
||||
String peaks = "";
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
@@ -131,7 +271,7 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i).value / 100.0, 2);
|
||||
}
|
||||
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"max\":%.1f,\"peaks\":[%s],\"threshold\":%d,\"hour\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f},\"day\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f},\"month\":{\"use\":%.2f,\"cost\":%.2f,\"produced\":%.2f,\"income\":%.2f}"),
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
@@ -148,25 +288,50 @@ 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];
|
||||
memset(pt, 0, 24);
|
||||
if(now > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
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
|
||||
);
|
||||
@@ -174,20 +339,31 @@ 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];
|
||||
memset(pt, 0, 24);
|
||||
if(now > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
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() || !mqtt.connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
publishPriceSensors(eapi);
|
||||
publishPriceSensors(ps);
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
@@ -195,12 +371,12 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, now, i);
|
||||
values[i] = val;
|
||||
|
||||
if(val == ENTSOE_NO_VALUE) break;
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
|
||||
if(val < min) min = val;
|
||||
if(val > max) max = val;
|
||||
@@ -215,7 +391,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val1 = values[i++];
|
||||
float val2 = values[i++];
|
||||
float val3 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE) continue;
|
||||
float val3hr = val1+val2+val3;
|
||||
if(min3hrIdx == -1 || min3hr > val3hr) {
|
||||
min3hr = val3hr;
|
||||
@@ -231,7 +407,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val4 = values[i++];
|
||||
float val5 = values[i++];
|
||||
float val6 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE || val4 == PRICE_NO_VALUE || val5 == PRICE_NO_VALUE || val6 == PRICE_NO_VALUE) continue;
|
||||
float val6hr = val1+val2+val3+val4+val5+val6;
|
||||
if(min6hrIdx == -1 || min6hr > val6hr) {
|
||||
min6hr = val6hr;
|
||||
@@ -265,74 +441,75 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
snprintf_P(json, BufferSize, JSONPRICES_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
values[0] == ENTSOE_NO_VALUE ? "null" : String(values[0], 4).c_str(),
|
||||
values[1] == ENTSOE_NO_VALUE ? "null" : String(values[1], 4).c_str(),
|
||||
values[2] == ENTSOE_NO_VALUE ? "null" : String(values[2], 4).c_str(),
|
||||
values[3] == ENTSOE_NO_VALUE ? "null" : String(values[3], 4).c_str(),
|
||||
values[4] == ENTSOE_NO_VALUE ? "null" : String(values[4], 4).c_str(),
|
||||
values[5] == ENTSOE_NO_VALUE ? "null" : String(values[5], 4).c_str(),
|
||||
values[6] == ENTSOE_NO_VALUE ? "null" : String(values[6], 4).c_str(),
|
||||
values[7] == ENTSOE_NO_VALUE ? "null" : String(values[7], 4).c_str(),
|
||||
values[8] == ENTSOE_NO_VALUE ? "null" : String(values[8], 4).c_str(),
|
||||
values[9] == ENTSOE_NO_VALUE ? "null" : String(values[9], 4).c_str(),
|
||||
values[10] == ENTSOE_NO_VALUE ? "null" : String(values[10], 4).c_str(),
|
||||
values[11] == ENTSOE_NO_VALUE ? "null" : String(values[11], 4).c_str(),
|
||||
values[12] == ENTSOE_NO_VALUE ? "null" : String(values[12], 4).c_str(),
|
||||
values[13] == ENTSOE_NO_VALUE ? "null" : String(values[13], 4).c_str(),
|
||||
values[14] == ENTSOE_NO_VALUE ? "null" : String(values[14], 4).c_str(),
|
||||
values[15] == ENTSOE_NO_VALUE ? "null" : String(values[15], 4).c_str(),
|
||||
values[16] == ENTSOE_NO_VALUE ? "null" : String(values[16], 4).c_str(),
|
||||
values[17] == ENTSOE_NO_VALUE ? "null" : String(values[17], 4).c_str(),
|
||||
values[18] == ENTSOE_NO_VALUE ? "null" : String(values[18], 4).c_str(),
|
||||
values[19] == ENTSOE_NO_VALUE ? "null" : String(values[19], 4).c_str(),
|
||||
values[20] == ENTSOE_NO_VALUE ? "null" : String(values[20], 4).c_str(),
|
||||
values[21] == ENTSOE_NO_VALUE ? "null" : String(values[21], 4).c_str(),
|
||||
values[22] == ENTSOE_NO_VALUE ? "null" : String(values[22], 4).c_str(),
|
||||
values[23] == ENTSOE_NO_VALUE ? "null" : String(values[23], 4).c_str(),
|
||||
values[24] == ENTSOE_NO_VALUE ? "null" : String(values[24], 4).c_str(),
|
||||
values[25] == ENTSOE_NO_VALUE ? "null" : String(values[25], 4).c_str(),
|
||||
values[26] == ENTSOE_NO_VALUE ? "null" : String(values[26], 4).c_str(),
|
||||
values[27] == ENTSOE_NO_VALUE ? "null" : String(values[27], 4).c_str(),
|
||||
values[28] == ENTSOE_NO_VALUE ? "null" : String(values[28], 4).c_str(),
|
||||
values[29] == ENTSOE_NO_VALUE ? "null" : String(values[29], 4).c_str(),
|
||||
values[30] == ENTSOE_NO_VALUE ? "null" : String(values[30], 4).c_str(),
|
||||
values[31] == ENTSOE_NO_VALUE ? "null" : String(values[31], 4).c_str(),
|
||||
values[32] == ENTSOE_NO_VALUE ? "null" : String(values[32], 4).c_str(),
|
||||
values[33] == ENTSOE_NO_VALUE ? "null" : String(values[33], 4).c_str(),
|
||||
values[34] == ENTSOE_NO_VALUE ? "null" : String(values[34], 4).c_str(),
|
||||
values[35] == ENTSOE_NO_VALUE ? "null" : String(values[35], 4).c_str(),
|
||||
values[36] == ENTSOE_NO_VALUE ? "null" : String(values[36], 4).c_str(),
|
||||
values[37] == ENTSOE_NO_VALUE ? "null" : String(values[37], 4).c_str(),
|
||||
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{"), WiFi.macAddress().c_str());
|
||||
for(uint8_t i = 0;i < 38; i++) {
|
||||
if(values[i] == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%d\":null,"), i);
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%d\":%.4f,"), i, values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"),
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
ts1hr,
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
bool ret = mqtt.publish(topic + "/prices", json, true, 0);
|
||||
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_EXPORT, now, 0);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"exportprices\":{\"0\":null}"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"exportprices\":{\"0\":%.4f}"), val);
|
||||
}
|
||||
|
||||
char pt[24];
|
||||
memset(pt, 0, 24);
|
||||
if(now > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
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() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
|
||||
publishSystemSensors();
|
||||
if(hw->getTemperature() > -50) publishTemperatureSensor(0, "");
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
time_t now = time(nullptr);
|
||||
char pt[24];
|
||||
memset(pt, 0, 24);
|
||||
if(now > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -365,7 +542,7 @@ void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor sensor) {
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : "",
|
||||
strlen_P(sensor.stacl) > 0 ? "\"" : ""
|
||||
);
|
||||
mqtt.publish(discoveryTopic + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
mqtt.publish(sensorTopic + "/" + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
loop();
|
||||
}
|
||||
|
||||
@@ -430,13 +607,13 @@ void HomeAssistantMqttHandler::publishList4ExportSensors() {
|
||||
l4eInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, PriceService* ps) {
|
||||
if(rtInit) return;
|
||||
for(uint8_t i = 0; i < RealtimeSensorCount; i++) {
|
||||
HomeAssistantSensor sensor = RealtimeSensors[i];
|
||||
if(strncmp_P(sensor.devcl, PSTR("monetary"), 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
sensor.uom = eapi->getCurrency();
|
||||
if(ps == NULL) continue;
|
||||
sensor.uom = ps->getCurrency();
|
||||
}
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -461,13 +638,13 @@ void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, Ents
|
||||
rtInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishRealtimeExportSensors(EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
void HomeAssistantMqttHandler::publishRealtimeExportSensors(EnergyAccounting* ea, PriceService* ps) {
|
||||
if(rteInit) return;
|
||||
for(uint8_t i = 0; i < RealtimeExportSensorCount; i++) {
|
||||
HomeAssistantSensor sensor = RealtimeExportSensors[i];
|
||||
if(strncmp_P(sensor.devcl, PSTR("monetary"), 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
sensor.uom = eapi->getCurrency();
|
||||
if(ps == NULL) continue;
|
||||
sensor.uom = ps->getCurrency();
|
||||
}
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -499,9 +676,9 @@ void HomeAssistantMqttHandler::publishTemperatureSensor(uint8_t index, String id
|
||||
tInit[index] = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
if(eapi == NULL) return;
|
||||
String uom = String(eapi->getCurrency()) + "/kWh";
|
||||
void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
|
||||
if(ps == NULL) return;
|
||||
String uom = String(ps->getCurrency()) + "/kWh";
|
||||
|
||||
if(!pInit) {
|
||||
for(uint8_t i = 0; i < PriceSensorCount; i++) {
|
||||
@@ -515,8 +692,8 @@ void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
}
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
if(prInit[i]) continue;
|
||||
float val = eapi->getValueForHour(i);
|
||||
if(val == ENTSOE_NO_VALUE) continue;
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, i);
|
||||
if(val == PRICE_NO_VALUE) continue;
|
||||
|
||||
char name[strlen(PriceSensor.name)+2];
|
||||
snprintf(name, strlen(PriceSensor.name)+2, PriceSensor.name, i, i == 1 ? "hour" : "hours");
|
||||
@@ -535,6 +712,21 @@ void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
prInit[i] = true;
|
||||
}
|
||||
|
||||
float exportPrice = ps->getValueForHour(PRICE_DIRECTION_EXPORT, 0);
|
||||
if(exportPrice != PRICE_NO_VALUE) {
|
||||
char path[20];
|
||||
snprintf(path, 20, "exportprices['%d']", 0);
|
||||
HomeAssistantSensor sensor = {
|
||||
"Export price current hour",
|
||||
PriceSensor.topic,
|
||||
path,
|
||||
PriceSensor.ttl,
|
||||
uom.c_str(),
|
||||
PriceSensor.devcl,
|
||||
"total"
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
@@ -545,6 +737,27 @@ void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
sInit = true;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishThresholdSensors() {
|
||||
if(rInit) return;
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
char name[strlen(RealtimeThresholdSensor.name)+1];
|
||||
snprintf(name, strlen(RealtimeThresholdSensor.name)+2, RealtimeThresholdSensor.name, i+1);
|
||||
char path[strlen(RealtimeThresholdSensor.path)+1];
|
||||
snprintf(path, strlen(RealtimeThresholdSensor.path)+1, RealtimeThresholdSensor.path, i);
|
||||
HomeAssistantSensor sensor = {
|
||||
name,
|
||||
RealtimeThresholdSensor.topic,
|
||||
path,
|
||||
RealtimeThresholdSensor.ttl,
|
||||
RealtimeThresholdSensor.uom,
|
||||
RealtimeThresholdSensor.devcl,
|
||||
RealtimeThresholdSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
rInit = true;
|
||||
}
|
||||
|
||||
uint8_t HomeAssistantMqttHandler::getFormat() {
|
||||
return 4;
|
||||
}
|
||||
@@ -553,13 +766,44 @@ bool HomeAssistantMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishFirmware() {
|
||||
if(!fInit) {
|
||||
snprintf_P(json, BufferSize, PSTR("{\"name\":\"%sFirmware\",\"stat_t\":\"%s/firmware\",\"dev_cla\":\"firmware\",\"cmd_t\":\"%s\",\"pl_inst\":\"fwupgrade\"}"),
|
||||
sensorNamePrefix.c_str(),
|
||||
pubTopic.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(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = false;
|
||||
for(uint8_t i = 0; i < 32; i++) tInit[i] = false;
|
||||
for(uint8_t i = 0; i < 38; i++) prInit[i] = false;
|
||||
}
|
||||
} else if(topic.equals(subTopic)) {
|
||||
if(payload.equals("fwupgrade")) {
|
||||
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
|
||||
updater->setTargetVersion(updater->getNextVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HWTOOLS_H
|
||||
#define _HWTOOLS_H
|
||||
|
||||
@@ -36,7 +42,8 @@ struct AdcConfig {
|
||||
|
||||
class HwTools {
|
||||
public:
|
||||
void setup(GpioConfig*, AmsConfiguration*);
|
||||
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
|
||||
void setup(SystemConfig* sys, GpioConfig* gpio);
|
||||
float getVcc();
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorData* getTempSensorData(uint8_t);
|
||||
@@ -48,22 +55,33 @@ 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 maxVcc = 3.2; // 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
|
||||
|
||||
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,154 @@
|
||||
/**
|
||||
* @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;
|
||||
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 +156,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 +200,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 +382,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 +405,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 +448,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 +528,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 +536,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 +549,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 +604,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 +613,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 +622,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 +631,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 +640,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 +652,22 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HwTools::isVoltageOptimal(float range) {
|
||||
if(boardType >= 5 && boardType <= 7 && maxVcc > 2.8) { // Pow-*
|
||||
float vcc = getVcc();
|
||||
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;
|
||||
}
|
||||
1
lib/JsonMqttHandler/include/.gitignore
vendored
1
lib/JsonMqttHandler/include/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
json/*.h
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _JSONMQTTHANDLER_H
|
||||
#define _JSONMQTTHANDLER_H
|
||||
|
||||
@@ -5,13 +11,19 @@
|
||||
|
||||
class JsonMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
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, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
this->hw = hw;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
#else
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->hw = hw;
|
||||
};
|
||||
#endif
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
@@ -20,7 +32,8 @@ public:
|
||||
|
||||
private:
|
||||
HwTools* hw;
|
||||
|
||||
uint16_t appendJsonHeader(AmsData* data);
|
||||
uint16_t appendJsonFooter(EnergyAccounting* ea, uint16_t pos);
|
||||
bool publishList1(AmsData* data, EnergyAccounting* ea);
|
||||
bool publishList2(AmsData* data, EnergyAccounting* ea);
|
||||
bool publishList3(AmsData* data, EnergyAccounting* ea);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"P" : %d
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"tPI" : %.3f,
|
||||
"tPO" : %.3f,
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : %lu
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %u,
|
||||
"t" : %lu,
|
||||
"vcc" : %.3f,
|
||||
"rssi": %d,
|
||||
"temp": %.2f,
|
||||
"data" : {
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"P1" : %.2f,
|
||||
"P2" : %.2f,
|
||||
"P3" : %.2f,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"PO1" : %.2f,
|
||||
"PO2" : %.2f,
|
||||
"PO3" : %.2f,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"PF" : %.2f,
|
||||
"PF1" : %.2f,
|
||||
"PF2" : %.2f,
|
||||
"PF3" : %.2f,
|
||||
"tPI" : %.3f,
|
||||
"tPO" : %.3f,
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : %lu
|
||||
},
|
||||
"realtime" : {
|
||||
"h" : %.2f,
|
||||
"d" : %.1f,
|
||||
"t" : %d,
|
||||
"x" : %.2f,
|
||||
"he" : %.2f,
|
||||
"de" : %.1f
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"prices" : {
|
||||
"0" : %s,
|
||||
"1" : %s,
|
||||
"2" : %s,
|
||||
"3" : %s,
|
||||
"4" : %s,
|
||||
"5" : %s,
|
||||
"6" : %s,
|
||||
"7" : %s,
|
||||
"8" : %s,
|
||||
"9" : %s,
|
||||
"10" : %s,
|
||||
"11" : %s,
|
||||
"12" : %s,
|
||||
"13" : %s,
|
||||
"14" : %s,
|
||||
"15" : %s,
|
||||
"16" : %s,
|
||||
"17" : %s,
|
||||
"18" : %s,
|
||||
"19" : %s,
|
||||
"20" : %s,
|
||||
"21" : %s,
|
||||
"22" : %s,
|
||||
"23" : %s,
|
||||
"24" : %s,
|
||||
"25" : %s,
|
||||
"26" : %s,
|
||||
"27" : %s,
|
||||
"28" : %s,
|
||||
"29" : %s,
|
||||
"30" : %s,
|
||||
"31" : %s,
|
||||
"32" : %s,
|
||||
"33" : %s,
|
||||
"34" : %s,
|
||||
"35" : %s,
|
||||
"36" : %s,
|
||||
"37" : %s,
|
||||
"min" : %.4f,
|
||||
"max" : %.4f,
|
||||
"cheapest1hr" : "%s",
|
||||
"cheapest3hr" : "%s",
|
||||
"cheapest6hr" : "%s"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"id" : "%s",
|
||||
"name" : "%s",
|
||||
"up" : %d,
|
||||
"vcc" : %.3f,
|
||||
"rssi" : %d,
|
||||
"temp" : %.2f,
|
||||
"version" : "%s"
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
from SCons.Script import (
|
||||
ARGUMENTS,
|
||||
COMMAND_LINE_TARGETS,
|
||||
DefaultEnvironment,
|
||||
)
|
||||
env = DefaultEnvironment()
|
||||
|
||||
env.Execute(
|
||||
env.VerboseAction(
|
||||
'$PYTHONEXE -m pip install "css_html_js_minify" ',
|
||||
"Installing Python dependencies",
|
||||
)
|
||||
)
|
||||
try:
|
||||
from css_html_js_minify import js_minify
|
||||
except:
|
||||
print("WARN: Unable to load minifier")
|
||||
|
||||
|
||||
webroot = "lib/JsonMqttHandler/json"
|
||||
srcroot = "lib/JsonMqttHandler/include/json"
|
||||
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
try:
|
||||
result = subprocess.run(['git','rev-parse','--short','HEAD'], capture_output=True, check=False)
|
||||
if result.returncode == 0:
|
||||
version = result.stdout.decode('utf-8').strip()
|
||||
else:
|
||||
version = "SNAPSHOT"
|
||||
except:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(srcfile, encoding="utf-8") as f:
|
||||
content = f.read().replace("${version}", version)
|
||||
|
||||
try:
|
||||
if (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(";");
|
||||
|
||||
@@ -1,69 +1,115 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "JsonMqttHandler.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "json/json1_json.h"
|
||||
#include "json/json2_json.h"
|
||||
#include "json/json3_json.h"
|
||||
#include "json/json4_json.h"
|
||||
#include "json/jsonsys_json.h"
|
||||
#include "json/jsonprices_json.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
bool JsonMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, no publish topic\n"));
|
||||
return false;
|
||||
}
|
||||
if(!mqtt.connected()) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, not connected\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
memset(json, 0, BufferSize);
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Publishing list ID %d!\n"), data->getListType());
|
||||
if(data->getListType() == 1) {
|
||||
ret = publishList1(data, ea);
|
||||
} else if(data->getListType() == 2) {
|
||||
ret = publishList2(data, ea);
|
||||
} else if(data->getListType() == 3) {
|
||||
ret = publishList3(data, ea);
|
||||
} else if(data->getListType() == 4) {
|
||||
ret = publishList4(data, ea);
|
||||
AmsData data;
|
||||
if(mqttConfig.stateUpdate) {
|
||||
uint64_t now = millis64();
|
||||
if(now-lastStateUpdate < mqttConfig.stateUpdateInterval * 1000) return false;
|
||||
data.apply(*previousState);
|
||||
data.apply(*update);
|
||||
lastStateUpdate = now;
|
||||
} else {
|
||||
data = *update;
|
||||
}
|
||||
|
||||
if(data.getListType() == 1) {
|
||||
ret = publishList1(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 2) {
|
||||
ret = publishList2(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 3) {
|
||||
ret = publishList3(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data.getListType() == 4) {
|
||||
ret = publishList4(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON1_JSON,
|
||||
uint16_t JsonMqttHandler::appendJsonHeader(AmsData* data) {
|
||||
return snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%u,\"t\":%lu,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,"),
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
data->getActiveImportPower(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
hw->getTemperature()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
|
||||
uint16_t JsonMqttHandler::appendJsonFooter(EnergyAccounting* ea, uint16_t pos) {
|
||||
char pf[4];
|
||||
if(mqttConfig.payloadFormat == 6) {
|
||||
strcpy_P(pf, PSTR("rt_"));
|
||||
} else {
|
||||
memset(pf, 0, 4);
|
||||
}
|
||||
|
||||
return snprintf_P(json+pos, BufferSize-pos, PSTR("%s\"%sh\":%.2f,\"%sd\":%.1f,\"%st\":%d,\"%sx\":%.2f,\"%she\":%.2f,\"%sde\":%.1f%s"),
|
||||
strlen(pf) == 0 ? "},\"realtime\":{" : ",",
|
||||
pf,
|
||||
ea->getUseThisHour(),
|
||||
pf,
|
||||
ea->getUseToday(),
|
||||
pf,
|
||||
ea->getCurrentThreshold(),
|
||||
pf,
|
||||
ea->getMonthMax(),
|
||||
pf,
|
||||
ea->getProducedThisHour(),
|
||||
pf,
|
||||
ea->getProducedToday(),
|
||||
strlen(pf) == 0 ? "}" : ""
|
||||
);
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"P\":%d"), data->getActiveImportPower());
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list1"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON2_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"lv\":\"%s\",\"meterId\":\"%s\",\"type\":\"%s\",\"P\":%d,\"Q\":%d,\"PO\":%d,\"QO\":%d,\"I1\":%.2f,\"I2\":%.2f,\"I3\":%.2f,\"U1\":%.2f,\"U2\":%.2f,\"U3\":%.2f"),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
getMeterModel(data).c_str(),
|
||||
@@ -76,26 +122,26 @@ bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
data->getL3Voltage()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list2"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON3_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"lv\":\"%s\",\"meterId\":\"%s\",\"type\":\"%s\",\"P\":%d,\"Q\":%d,\"PO\":%d,\"QO\":%d,\"I1\":%.2f,\"I2\":%.2f,\"I3\":%.2f,\"U1\":%.2f,\"U2\":%.2f,\"U3\":%.2f,\"tPI\":%.3f,\"tPO\":%.3f,\"tQI\":%.3f,\"tQO\":%.3f,\"rtc\":%lu"),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
getMeterModel(data).c_str(),
|
||||
@@ -113,26 +159,26 @@ bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list3"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON4_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
uint16_t pos = appendJsonHeader(data);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"data\":{"));
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"lv\":\"%s\",\"meterId\":\"%s\",\"type\":\"%s\",\"P\":%d,\"P1\":%d,\"P2\":%d,\"P3\":%d,\"Q\":%d,\"PO\":%d,\"PO1\":%d,\"PO2\":%d,\"PO3\":%d,\"QO\":%d,\"I1\":%.2f,\"I2\":%.2f,\"I3\":%.2f,\"U1\":%.2f,\"U2\":%.2f,\"U3\":%.2f,\"PF\":%.2f,\"PF1\":%.2f,\"PF2\":%.2f,\"PF3\":%.2f,\"tPI\":%.3f,\"tPO\":%.3f,\"tQI\":%.3f,\"tQO\":%.3f,\"tPI1\":%.3f,\"tPI2\":%.3f,\"tPI3\":%.3f,\"tPO1\":%.3f,\"tPO2\":%.3f,\"tPO3\":%.3f,\"rtc\":%lu"),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
getMeterModel(data).c_str(),
|
||||
@@ -160,15 +206,24 @@ bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
data->getL1ActiveImportCounter(),
|
||||
data->getL2ActiveImportCounter(),
|
||||
data->getL3ActiveImportCounter(),
|
||||
data->getL1ActiveExportCounter(),
|
||||
data->getL2ActiveExportCounter(),
|
||||
data->getL3ActiveExportCounter(),
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list4"), mqttConfig.publishTopic);
|
||||
return mqtt.publish(topic, json);
|
||||
} else {
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
}
|
||||
|
||||
String JsonMqttHandler::getMeterModel(AmsData* data) {
|
||||
@@ -183,30 +238,43 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf_P(json, 24, PSTR("{\"temperatures\":{"));
|
||||
|
||||
uint16_t pos = 0;
|
||||
if(mqttConfig.payloadFormat == 6) {
|
||||
json[pos++] = '{';
|
||||
} else {
|
||||
pos = snprintf_P(json, 24, PSTR("{\"temperatures\":{"));
|
||||
}
|
||||
for(int i = 0; i < count; i++) {
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
if(data != NULL) {
|
||||
char* pos = json+strlen(json);
|
||||
snprintf_P(pos, 26, PSTR("\"%s\":%.2f,"),
|
||||
pos += snprintf_P(json+pos, 26, PSTR("\"%s\":%.2f,"),
|
||||
toHex(data->address, 8).c_str(),
|
||||
data->lastRead
|
||||
);
|
||||
data->changed = false;
|
||||
}
|
||||
}
|
||||
char* pos = json+strlen(json);
|
||||
snprintf_P(count == 0 ? pos : pos-1, 8, PSTR("}}"));
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
bool ret = false;
|
||||
json[pos-1] = '}';
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
}
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/temperatures"), mqttConfig.publishTopic);
|
||||
ret = mqtt.publish(topic, json);
|
||||
} else {
|
||||
ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
@@ -215,12 +283,12 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, now, i);
|
||||
values[i] = val;
|
||||
|
||||
if(val == ENTSOE_NO_VALUE) break;
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
|
||||
if(val < min) min = val;
|
||||
if(val > max) max = val;
|
||||
@@ -235,7 +303,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val1 = values[i++];
|
||||
float val2 = values[i++];
|
||||
float val3 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE) continue;
|
||||
float val3hr = val1+val2+val3;
|
||||
if(min3hrIdx == -1 || min3hr > val3hr) {
|
||||
min3hr = val3hr;
|
||||
@@ -251,7 +319,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
float val4 = values[i++];
|
||||
float val5 = values[i++];
|
||||
float val6 = val;
|
||||
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
|
||||
if(val1 == PRICE_NO_VALUE || val2 == PRICE_NO_VALUE || val3 == PRICE_NO_VALUE || val4 == PRICE_NO_VALUE || val5 == PRICE_NO_VALUE || val6 == PRICE_NO_VALUE) continue;
|
||||
float val6hr = val1+val2+val3+val4+val5+val6;
|
||||
if(min6hrIdx == -1 || min6hr > val6hr) {
|
||||
min6hr = val6hr;
|
||||
@@ -286,62 +354,56 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
snprintf_P(json, BufferSize, JSONPRICES_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
values[0] == ENTSOE_NO_VALUE ? "null" : String(values[0], 4).c_str(),
|
||||
values[1] == ENTSOE_NO_VALUE ? "null" : String(values[1], 4).c_str(),
|
||||
values[2] == ENTSOE_NO_VALUE ? "null" : String(values[2], 4).c_str(),
|
||||
values[3] == ENTSOE_NO_VALUE ? "null" : String(values[3], 4).c_str(),
|
||||
values[4] == ENTSOE_NO_VALUE ? "null" : String(values[4], 4).c_str(),
|
||||
values[5] == ENTSOE_NO_VALUE ? "null" : String(values[5], 4).c_str(),
|
||||
values[6] == ENTSOE_NO_VALUE ? "null" : String(values[6], 4).c_str(),
|
||||
values[7] == ENTSOE_NO_VALUE ? "null" : String(values[7], 4).c_str(),
|
||||
values[8] == ENTSOE_NO_VALUE ? "null" : String(values[8], 4).c_str(),
|
||||
values[9] == ENTSOE_NO_VALUE ? "null" : String(values[9], 4).c_str(),
|
||||
values[10] == ENTSOE_NO_VALUE ? "null" : String(values[10], 4).c_str(),
|
||||
values[11] == ENTSOE_NO_VALUE ? "null" : String(values[11], 4).c_str(),
|
||||
values[12] == ENTSOE_NO_VALUE ? "null" : String(values[12], 4).c_str(),
|
||||
values[13] == ENTSOE_NO_VALUE ? "null" : String(values[13], 4).c_str(),
|
||||
values[14] == ENTSOE_NO_VALUE ? "null" : String(values[14], 4).c_str(),
|
||||
values[15] == ENTSOE_NO_VALUE ? "null" : String(values[15], 4).c_str(),
|
||||
values[16] == ENTSOE_NO_VALUE ? "null" : String(values[16], 4).c_str(),
|
||||
values[17] == ENTSOE_NO_VALUE ? "null" : String(values[17], 4).c_str(),
|
||||
values[18] == ENTSOE_NO_VALUE ? "null" : String(values[18], 4).c_str(),
|
||||
values[19] == ENTSOE_NO_VALUE ? "null" : String(values[19], 4).c_str(),
|
||||
values[20] == ENTSOE_NO_VALUE ? "null" : String(values[20], 4).c_str(),
|
||||
values[21] == ENTSOE_NO_VALUE ? "null" : String(values[21], 4).c_str(),
|
||||
values[22] == ENTSOE_NO_VALUE ? "null" : String(values[22], 4).c_str(),
|
||||
values[23] == ENTSOE_NO_VALUE ? "null" : String(values[23], 4).c_str(),
|
||||
values[24] == ENTSOE_NO_VALUE ? "null" : String(values[24], 4).c_str(),
|
||||
values[25] == ENTSOE_NO_VALUE ? "null" : String(values[25], 4).c_str(),
|
||||
values[26] == ENTSOE_NO_VALUE ? "null" : String(values[26], 4).c_str(),
|
||||
values[27] == ENTSOE_NO_VALUE ? "null" : String(values[27], 4).c_str(),
|
||||
values[28] == ENTSOE_NO_VALUE ? "null" : String(values[28], 4).c_str(),
|
||||
values[29] == ENTSOE_NO_VALUE ? "null" : String(values[29], 4).c_str(),
|
||||
values[30] == ENTSOE_NO_VALUE ? "null" : String(values[30], 4).c_str(),
|
||||
values[31] == ENTSOE_NO_VALUE ? "null" : String(values[31], 4).c_str(),
|
||||
values[32] == ENTSOE_NO_VALUE ? "null" : String(values[32], 4).c_str(),
|
||||
values[33] == ENTSOE_NO_VALUE ? "null" : String(values[33], 4).c_str(),
|
||||
values[34] == ENTSOE_NO_VALUE ? "null" : String(values[34], 4).c_str(),
|
||||
values[35] == ENTSOE_NO_VALUE ? "null" : String(values[35], 4).c_str(),
|
||||
values[36] == ENTSOE_NO_VALUE ? "null" : String(values[36], 4).c_str(),
|
||||
values[37] == ENTSOE_NO_VALUE ? "null" : String(values[37], 4).c_str(),
|
||||
char pf[4];
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\","), WiFi.macAddress().c_str());
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
memset(pf, 0, 4);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"prices\":{"));
|
||||
} else {
|
||||
strcpy_P(pf, PSTR("pr_"));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0;i < 38; i++) {
|
||||
if(values[i] == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":null,"), pf, i);
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":%.4f,"), pf, i, values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%smin\":%.4f,\"%smax\":%.4f,\"%scheapest1hr\":\"%s\",\"%scheapest3hr\":\"%s\",\"%scheapest6hr\":\"%s\"}"),
|
||||
pf,
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
pf,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
pf,
|
||||
ts1hr,
|
||||
pf,
|
||||
ts3hr,
|
||||
pf,
|
||||
ts6hr
|
||||
);
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
}
|
||||
bool ret = false;
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/prices"), mqttConfig.publishTopic);
|
||||
ret = mqtt.publish(topic, json);
|
||||
} else {
|
||||
ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\"}"),
|
||||
WiFi.macAddress().c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
@@ -350,7 +412,14 @@ bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounti
|
||||
hw->getTemperature(),
|
||||
FirmwareVersion::VersionString
|
||||
);
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
bool ret = false;
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/system"), mqttConfig.publishTopic);
|
||||
ret = mqtt.publish(topic, json);
|
||||
} else {
|
||||
ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IEC62056_21_H
|
||||
#define _IEC62056_21_H
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IEC62056_7_5_H
|
||||
#define _IEC62056_7_5_H
|
||||
|
||||
@@ -25,6 +31,8 @@ private:
|
||||
float getNumber(CosemData*);
|
||||
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
|
||||
|
||||
uint8_t AMS_OBIS_UNKNOWN_1[4] = { 25, 9, 0, 255 };
|
||||
|
||||
uint8_t AMS_OBIS_VERSION[4] = { 0, 2, 129, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL[4] = { 96, 1, 1, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL_2[4] = { 96, 1, 7, 255 };
|
||||
@@ -55,6 +63,12 @@ private:
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L1_COUNT[4] = { 21, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2_COUNT[4] = { 41, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3_COUNT[4] = { 61, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1_COUNT[4] = { 22, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2_COUNT[4] = { 42, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3_COUNT[4] = { 62, 8, 0, 255 };
|
||||
|
||||
};
|
||||
#endif
|
||||
18
lib/MeterCommunicators/include/ImpulseAmsData.h
Normal file
18
lib/MeterCommunicators/include/ImpulseAmsData.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IMPULSEAMSDATA_H
|
||||
#define _IMPULSEAMSDATA_H
|
||||
|
||||
#include "AmsData.h"
|
||||
|
||||
class ImpulseAmsData : public AmsData {
|
||||
public:
|
||||
ImpulseAmsData(AmsData &state, uint16_t pulsePerKwh, uint8_t pulses);
|
||||
ImpulseAmsData(double activeImportCounter);
|
||||
};
|
||||
|
||||
#endif
|
||||
39
lib/MeterCommunicators/include/KmpCommunicator.h
Normal file
39
lib/MeterCommunicators/include/KmpCommunicator.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2024
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "PassiveMeterCommunicator.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "Timezone.h"
|
||||
#include "ImpulseAmsData.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "SoftwareSerial.h"
|
||||
#endif
|
||||
|
||||
#include "KmpTalker.h"
|
||||
|
||||
class KmpCommunicator : public PassiveMeterCommunicator {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
KmpCommunicator(RemoteDebug* debugger) : PassiveMeterCommunicator(debugger) {};
|
||||
#else
|
||||
KmpCommunicator(Stream* debugger) : PassiveMeterCommunicator(debugger) {};
|
||||
#endif
|
||||
void configure(MeterConfig&);
|
||||
bool loop();
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
void getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
private:
|
||||
KmpTalker* talker = NULL;
|
||||
};
|
||||
56
lib/MeterCommunicators/include/KmpTalker.h
Normal file
56
lib/MeterCommunicators/include/KmpTalker.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: All rights reserved
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Stream.h>
|
||||
|
||||
#define DATA_PARSE_OK 0
|
||||
#define DATA_PARSE_FAIL -1
|
||||
#define DATA_PARSE_INCOMPLETE -2
|
||||
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
|
||||
|
||||
struct KmpParserContext {
|
||||
uint8_t type;
|
||||
uint16_t length;
|
||||
};
|
||||
|
||||
struct KmpDataHolder {
|
||||
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||
uint32_t l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
uint32_t l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
double l1activeImportCounter = 0, l2activeImportCounter = 0, l3activeImportCounter = 0;
|
||||
double l1activeExportCounter = 0, l2activeExportCounter = 0, l3activeExportCounter = 0;
|
||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||
uint16_t meterId;
|
||||
};
|
||||
|
||||
class KmpTalker {
|
||||
public:
|
||||
KmpTalker(Stream *hanSerial, uint8_t* hanBuffer, uint16_t hanBufferSize);
|
||||
bool loop();
|
||||
void getData(KmpDataHolder& data);
|
||||
int getLastError();
|
||||
|
||||
private:
|
||||
Stream *hanSerial;
|
||||
uint8_t *hanBuffer = NULL;
|
||||
uint16_t hanBufferSize = 0;
|
||||
|
||||
bool dataAvailable = false;
|
||||
int len = 0;
|
||||
int pos = DATA_PARSE_INCOMPLETE;
|
||||
int lastError = DATA_PARSE_OK;
|
||||
bool serialInit = false;
|
||||
|
||||
uint64_t lastUpdate = 0;
|
||||
uint8_t batch = 0;
|
||||
KmpParserContext ctx;
|
||||
|
||||
KmpDataHolder state;
|
||||
};
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LNG_H
|
||||
#define _LNG_H
|
||||
|
||||
@@ -5,7 +11,6 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct LngHeader {
|
||||
uint8_t tag;
|
||||
@@ -25,7 +30,7 @@ struct LngObisDescriptor {
|
||||
|
||||
class LNG : public AmsData {
|
||||
public:
|
||||
LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
|
||||
LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
|
||||
uint64_t getNumber(CosemData* item);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LNG2_H
|
||||
#define _LNG2_H
|
||||
|
||||
@@ -5,7 +11,6 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct Lng2Data_3p {
|
||||
CosemBasic header;
|
||||
@@ -27,7 +32,7 @@ struct Lng2Data_3p {
|
||||
|
||||
class LNG2 : public AmsData {
|
||||
public:
|
||||
LNG2(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
|
||||
LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
|
||||
|
||||
private:
|
||||
uint8_t getString(CosemData* item, char* target);
|
||||
29
lib/MeterCommunicators/include/MeterCommunicator.h
Normal file
29
lib/MeterCommunicators/include/MeterCommunicator.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _METERCOMMUNICATOR_H
|
||||
#define _METERCOMMUNICATOR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
class MeterCommunicator {
|
||||
public:
|
||||
virtual ~MeterCommunicator() {};
|
||||
virtual void configure(MeterConfig&, Timezone*);
|
||||
virtual bool loop();
|
||||
virtual AmsData* getData(AmsData& meterState);
|
||||
virtual int getLastError();
|
||||
virtual bool isConfigChanged();
|
||||
virtual void ackConfigChanged();
|
||||
virtual void getCurrentConfig(MeterConfig& meterConfig);
|
||||
};
|
||||
|
||||
#endif
|
||||
96
lib/MeterCommunicators/include/PassiveMeterCommunicator.h
Normal file
96
lib/MeterCommunicators/include/PassiveMeterCommunicator.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PASSIVEMETERCOMMUNICATOR_H
|
||||
#define _PASSIVEMETERCOMMUNICATOR_H
|
||||
|
||||
#include "MeterCommunicator.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParsers.h"
|
||||
#include "Timezone.h"
|
||||
#include "PassthroughMqttHandler.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "SoftwareSerial.h"
|
||||
#endif
|
||||
|
||||
const uint32_t AUTO_BAUD_RATES[] = { 2400, 9600, 115200 };
|
||||
|
||||
class PassiveMeterCommunicator : public MeterCommunicator {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PassiveMeterCommunicator(RemoteDebug* debugger);
|
||||
#else
|
||||
PassiveMeterCommunicator(Stream* debugger);
|
||||
#endif
|
||||
void configure(MeterConfig&, Timezone*);
|
||||
bool loop();
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
bool isConfigChanged();
|
||||
void ackConfigChanged();
|
||||
void getCurrentConfig(MeterConfig& meterConfig);
|
||||
void setPassthroughMqttHandler(PassthroughMqttHandler*);
|
||||
|
||||
HardwareSerial* getHwSerial();
|
||||
void rxerr(int err);
|
||||
|
||||
protected:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
MeterConfig meterConfig;
|
||||
bool configChanged = false;
|
||||
Timezone* tz;
|
||||
|
||||
PassthroughMqttHandler* pt = NULL;
|
||||
|
||||
uint8_t *hanBuffer = NULL;
|
||||
uint16_t hanBufferSize = 0;
|
||||
Stream *hanSerial;
|
||||
#if defined(ESP8266)
|
||||
SoftwareSerial *swSerial = NULL;
|
||||
#endif
|
||||
HardwareSerial *hwSerial = NULL;
|
||||
uint8_t rxBufferErrors = 0;
|
||||
|
||||
bool autodetect = false, validDataReceived = false;
|
||||
unsigned long meterAutodetectLastChange = 0;
|
||||
long rate = 10000;
|
||||
uint32_t autodetectBaud = 0;
|
||||
uint8_t autodetectParity = 11;
|
||||
bool autodetectInvert = false;
|
||||
uint8_t autodetectCount = 0;
|
||||
|
||||
bool dataAvailable = false;
|
||||
int len = 0;
|
||||
int pos = DATA_PARSE_INCOMPLETE;
|
||||
int lastError = DATA_PARSE_OK;
|
||||
bool serialInit = false;
|
||||
bool maxDetectPayloadDetectDone = false;
|
||||
uint8_t maxDetectedPayloadSize = 64;
|
||||
DataParserContext ctx = {0,0,0,0};
|
||||
|
||||
HDLCParser *hdlcParser = NULL;
|
||||
MBUSParser *mbusParser = NULL;
|
||||
GBTParser *gbtParser = NULL;
|
||||
GCMParser *gcmParser = NULL;
|
||||
LLCParser *llcParser = NULL;
|
||||
DLMSParser *dlmsParser = NULL;
|
||||
DSMRParser *dsmrParser = NULL;
|
||||
|
||||
void setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive = true);
|
||||
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
|
||||
void printHanReadError(int pos);
|
||||
void handleAutodetect(unsigned long now);
|
||||
};
|
||||
|
||||
#endif
|
||||
52
lib/MeterCommunicators/include/PulseMeterCommunicator.h
Normal file
52
lib/MeterCommunicators/include/PulseMeterCommunicator.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2024
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PULSEMETERCOMMUNICATOR_H
|
||||
#define _PULSEMETERCOMMUNICATOR_H
|
||||
|
||||
#include "MeterCommunicator.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "Timezone.h"
|
||||
#include "ImpulseAmsData.h"
|
||||
|
||||
class PulseMeterCommunicator : public MeterCommunicator {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PulseMeterCommunicator(RemoteDebug* debugger);
|
||||
#else
|
||||
PulseMeterCommunicator(Stream* debugger);
|
||||
#endif
|
||||
void configure(MeterConfig& config, Timezone* tz);
|
||||
bool loop();
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
bool isConfigChanged();
|
||||
void ackConfigChanged();
|
||||
void getCurrentConfig(MeterConfig& meterConfig);
|
||||
|
||||
void onPulse(uint8_t pulses);
|
||||
|
||||
protected:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
MeterConfig meterConfig;
|
||||
bool configChanged = false;
|
||||
Timezone* tz;
|
||||
bool updated = false;
|
||||
bool initialized = false;
|
||||
AmsData state;
|
||||
uint64_t lastUpdate = 0;
|
||||
|
||||
void setupGpio();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "IEC6205621.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "IEC6205675.h"
|
||||
#include "lwip/def.h"
|
||||
#include "Timezone.h"
|
||||
#include "ntohll.h"
|
||||
#include "Uptime.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state) {
|
||||
float val;
|
||||
@@ -12,14 +19,20 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
this->packageTimestamp = ctx.timestamp == 0 ? time(nullptr) : ctx.timestamp;
|
||||
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
|
||||
if(val == NOVALUE) {
|
||||
CosemData* data = getCosemDataAt(1, ((char *) (d)));
|
||||
|
||||
// Kaifa special case...
|
||||
if(data->base.type == CosemTypeOctetString) {
|
||||
if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
listType = 1;
|
||||
meterType = AmsTypeKaifa;
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(data->base.type == CosemTypeOctetString) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
@@ -134,83 +147,471 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
int idx = 0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data->base.length == 0x12) {
|
||||
listType = 2;
|
||||
apply(state);
|
||||
listType = state.getListType() > 2 ? state.getListType() : 2;
|
||||
|
||||
// 42.0.0 COSEM logical device name
|
||||
idx++;
|
||||
|
||||
// 96.1.3 Device ID 4
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
// 1.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 2.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 3.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 4.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 32.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 52.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 72.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 31.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 51.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 71.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 21.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 41.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 61.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 22.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 42.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 62.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(data->base.length == 0x0C) {
|
||||
apply(state);
|
||||
|
||||
listType = 3;
|
||||
idx += 4;
|
||||
CosemData* no3 = getCosemDataAt(3, ((char *) (d)));
|
||||
if(no3->base.type == CosemTypeBoolean) {
|
||||
apply(state);
|
||||
listType = state.getListType() > 3 ? state.getListType() : 3;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
idx += 2;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
idx += 2;
|
||||
// 42.0.0 COSEM logical device name
|
||||
idx++;
|
||||
|
||||
// 96.1.3 Device ID 4
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
// 96.3.10 Disconnect control
|
||||
// 96.14.0 Currently acrive energy tariff
|
||||
idx += 2;
|
||||
|
||||
// 1.8.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 1.8.1
|
||||
// 1.8.2
|
||||
idx += 2;
|
||||
|
||||
// 2.8.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 2.8.1
|
||||
// 2.8.2
|
||||
idx += 2;
|
||||
|
||||
// 3.8.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 4.8.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(no3->base.type == CosemTypeLongUnsigned) {
|
||||
apply(state);
|
||||
listType = state.getListType() > 2 ? state.getListType() : 2;
|
||||
|
||||
// 42.0.0 COSEM logical device name
|
||||
idx++;
|
||||
|
||||
// 96.1.2 Device ID 3
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
// 32.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 52.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 72.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 31.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 51.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 71.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 1.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 2.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 3.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 4.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
} else if(data->base.length == 0x0A) {
|
||||
CosemData* no7 = getCosemDataAt(7, ((char *) (d)));
|
||||
if(no7->base.type == CosemTypeLongUnsigned) {
|
||||
apply(state);
|
||||
listType = state.getListType() > 2 ? state.getListType() : 2;
|
||||
|
||||
// 42.0.0 COSEM logical device name
|
||||
idx++;
|
||||
|
||||
// 96.1.2 Device ID 3
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
// 1.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 2.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 3.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 4.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 32.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 31.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 21.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 22.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(no7->base.type == CosemTypeDLongUnsigned) {
|
||||
apply(state);
|
||||
listType = state.getListType() > 3 ? state.getListType() : 3;
|
||||
|
||||
// 42.0.0 COSEM logical device name
|
||||
idx++;
|
||||
|
||||
// 96.1.3 Device ID 4
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
// 1.8.1
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis181 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 1.8.2
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis182 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
if(activeImportCounter < obis181 + obis182)
|
||||
activeImportCounter = obis181 + obis182;
|
||||
|
||||
// 2.8.1
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis281 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 2.8.2
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis282 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
if(activeExportCounter < obis281 + obis282)
|
||||
activeExportCounter = obis281 + obis282;
|
||||
|
||||
// 3.8.1
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis381 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 3.8.2
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis382 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
if(reactiveImportCounter < obis381 + obis382)
|
||||
reactiveImportCounter = obis381 + obis382;
|
||||
|
||||
// 4.8.1
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis481 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 4.8.2
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis482 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
if(reactiveExportCounter < obis481 + obis482)
|
||||
reactiveExportCounter = obis481 + obis482;
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
} else if(data->base.length == 0x09) {
|
||||
CosemData* no7 = getCosemDataAt(7, ((char *) (d)));
|
||||
if(no7->base.type == CosemTypeLongUnsigned) {
|
||||
apply(state);
|
||||
listType = state.getListType() > 3 ? state.getListType() : 3;
|
||||
|
||||
// 42.0.0 COSEM logical device name
|
||||
idx++;
|
||||
|
||||
// 96.1.2 Device ID 3
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
// 1.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 2.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 3.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 4.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 32.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 2.8.1
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis281 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 2.8.2
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
double obis282 = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
activeExportCounter = obis281 + obis282;
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(no7->base.type == CosemTypeDLongUnsigned) {
|
||||
apply(state);
|
||||
listType = state.getListType() > 3 ? state.getListType() : 3;
|
||||
|
||||
// 42.0.0 COSEM logical device name?
|
||||
idx++;
|
||||
|
||||
// 1.7.0?
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 2.7.0?
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 1.8.0?
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 1.8.1?
|
||||
// 1.8.2?
|
||||
idx += 2;
|
||||
|
||||
// 2.8.0?
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 2.8.1?
|
||||
// 2.8.2?
|
||||
idx += 2;
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
} else if(data->base.length == 0x08) {
|
||||
// 42.0.0 COSEM logical device name
|
||||
idx++;
|
||||
|
||||
// 96.1.2 Device ID 3
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
// 32.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
l1voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
// 31.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
// 1.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 2.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 3.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
|
||||
// 4.7.0
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(data->base.length == 0x04) {
|
||||
// ?
|
||||
idx++;
|
||||
|
||||
// ?
|
||||
idx++;
|
||||
|
||||
// 1.8.0?
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
// 2.8.0?
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
} else if(useMeterType == AmsTypeIskra && data->base.type == CosemTypeOctetString) { // Iskra special case
|
||||
meterType = AmsTypeIskra;
|
||||
uint8_t idx = 5;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data != NULL) {
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
}
|
||||
|
||||
uint8_t str_len = 0;
|
||||
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
meterId = String(str);
|
||||
}
|
||||
|
||||
listType = 3;
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(useMeterType == AmsTypeUnknown) {
|
||||
uint8_t str_len = 0;
|
||||
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
|
||||
if(str_len > 0) {
|
||||
meterType = AmsTypeIskra;
|
||||
meterId = String(str);
|
||||
lastUpdateMillis = millis64();
|
||||
listType = 3;
|
||||
}
|
||||
}
|
||||
} else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
listType = 1;
|
||||
meterType = AmsTypeKaifa;
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
// Kaifa end
|
||||
} else {
|
||||
listType = 1;
|
||||
activeImportPower = val;
|
||||
@@ -233,17 +634,9 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try system title
|
||||
if(meterType == AmsTypeUnknown) {
|
||||
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
|
||||
meterType = AmsTypeSagemcom;
|
||||
} else if(memcmp(ctx.system_title, "KFM", 3) == 0) {
|
||||
meterType = AmsTypeKaifa;
|
||||
}
|
||||
}
|
||||
|
||||
if(this->packageTimestamp > 0) {
|
||||
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
|
||||
if(meterType == AmsTypeKamstrup) {
|
||||
this->packageTimestamp = this->packageTimestamp - 3600;
|
||||
}
|
||||
}
|
||||
@@ -348,8 +741,10 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
if(meterTs != NULL) {
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = decodeCosemDateTime(amst->dt);
|
||||
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
|
||||
meterTimestamp = ts - 3600;
|
||||
if(amst->dt.deviation == 0x8000) { // Deviation not specified, adjust from localtime to UTC
|
||||
meterTimestamp = tz.toUTC(ts);
|
||||
} else if(meterType == AmsTypeAidon) {
|
||||
meterTimestamp = ts - 3600; // 21.09.24, the clock is now correct
|
||||
} else {
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
@@ -407,6 +802,38 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
listType = 4;
|
||||
l3activeExportPower = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L1_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L1_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l1activeImportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L2_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l2activeImportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L3_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L3_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l3activeImportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L1_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L1_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l1activeExportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l2activeExportCounter = val/1000;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l3activeExportCounter = val/1000;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(meterType == AmsTypeKamstrup) {
|
||||
if(listType >= 3) {
|
||||
@@ -414,6 +841,12 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
activeExportCounter *= 10;
|
||||
reactiveImportCounter *= 10;
|
||||
reactiveExportCounter *= 10;
|
||||
l1activeImportCounter *= 10;
|
||||
l2activeImportCounter *= 10;
|
||||
l3activeImportCounter *= 10;
|
||||
l1activeExportCounter *= 10;
|
||||
l2activeExportCounter *= 10;
|
||||
l3activeExportCounter *= 10;
|
||||
}
|
||||
if(l1current != 0)
|
||||
l1current /= 100;
|
||||
@@ -457,6 +890,22 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
|
||||
// Try system title
|
||||
if(meterType == AmsTypeUnknown) {
|
||||
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
|
||||
meterType = AmsTypeSagemcom;
|
||||
} else if(memcmp(ctx.system_title, "KFM", 3) == 0) {
|
||||
meterType = AmsTypeKaifa;
|
||||
} else if(memcmp(ctx.system_title, "ISK", 3) == 0) {
|
||||
meterType = AmsTypeIskra;
|
||||
}
|
||||
|
||||
if(meterId.isEmpty() && meterType != AmsTypeUnknown) {
|
||||
stripNonAscii((uint8_t*) ctx.system_title, 8);
|
||||
meterId = String((const char*)ctx.system_title);
|
||||
}
|
||||
}
|
||||
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
@@ -478,6 +927,12 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
l1activeImportCounter = l1activeImportCounter > 0 ? l1activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
l2activeImportCounter = l2activeImportCounter > 0 ? l2activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
l3activeImportCounter = l3activeImportCounter > 0 ? l3activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
l1activeExportCounter = l1activeExportCounter > 0 ? l1activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
l2activeExportCounter = l2activeExportCounter > 0 ? l2activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
l3activeExportCounter = l3activeExportCounter > 0 ? l3activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
@@ -491,6 +946,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
threePhase = true;
|
||||
}
|
||||
}
|
||||
meterId.trim();
|
||||
}
|
||||
|
||||
CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
|
||||
27
lib/MeterCommunicators/src/ImpulseAmsData.cpp
Normal file
27
lib/MeterCommunicators/src/ImpulseAmsData.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ImpulseAmsData.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
ImpulseAmsData::ImpulseAmsData(AmsData& state, uint16_t pulsePerKwh, uint8_t pulses) {
|
||||
listType = 1;
|
||||
if(pulses > 0) {
|
||||
lastUpdateMillis = millis64();
|
||||
uint64_t lastStateMillis = state.getLastUpdateMillis();
|
||||
if(lastStateMillis > 0) {
|
||||
uint64_t ms = (lastUpdateMillis - lastStateMillis) / pulses;
|
||||
activeImportPower = (1000.0 / pulsePerKwh) / (((float) ms) / 3600000.0);
|
||||
}
|
||||
} else {
|
||||
lastUpdateMillis = state.getLastUpdateMillis();
|
||||
}
|
||||
}
|
||||
|
||||
ImpulseAmsData::ImpulseAmsData(double activeImportCounter) {
|
||||
this->activeImportCounter = activeImportCounter;
|
||||
this->listType = 3;
|
||||
}
|
||||
92
lib/MeterCommunicators/src/KmpCommunicator.cpp
Normal file
92
lib/MeterCommunicators/src/KmpCommunicator.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "KmpCommunicator.h"
|
||||
#include "Uptime.h"
|
||||
#include "crc.h"
|
||||
#include "OBIScodes.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
void KmpCommunicator::configure(MeterConfig& meterConfig) {
|
||||
this->meterConfig = meterConfig;
|
||||
this->configChanged = false;
|
||||
if(meterConfig.baud == 0) {
|
||||
this->configChanged = true;
|
||||
meterConfig.baud = 9600;
|
||||
meterConfig.parity = 7;
|
||||
meterConfig.invert = false;
|
||||
}
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert, false);
|
||||
talker = new KmpTalker(hanSerial, hanBuffer, hanBufferSize);
|
||||
}
|
||||
|
||||
bool KmpCommunicator::loop() {
|
||||
uint64_t now = millis64();
|
||||
|
||||
bool ret = talker->loop();
|
||||
int lastError = getLastError();
|
||||
if(ret) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Successful loop\n"));
|
||||
Serial.flush();
|
||||
} else if(lastError < 0 && lastError != DATA_PARSE_INCOMPLETE) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Error code: %d\n"), getLastError());
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, hanBufferSize, debugger);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int KmpCommunicator::getLastError() {
|
||||
return talker == NULL ? DATA_PARSE_FAIL : talker->getLastError();
|
||||
}
|
||||
|
||||
AmsData* KmpCommunicator::getData(AmsData& meterState) {
|
||||
if(talker == NULL) return NULL;
|
||||
KmpDataHolder kmpData;
|
||||
talker->getData(kmpData);
|
||||
AmsData* data = new AmsData();
|
||||
data->apply(OBIS_ACTIVE_IMPORT_COUNT, kmpData.activeImportCounter);
|
||||
data->apply(OBIS_ACTIVE_EXPORT_COUNT, kmpData.activeExportCounter);
|
||||
data->apply(OBIS_REACTIVE_IMPORT_COUNT, kmpData.reactiveImportCounter);
|
||||
data->apply(OBIS_REACTIVE_EXPORT_COUNT, kmpData.reactiveExportCounter);
|
||||
data->apply(OBIS_ACTIVE_IMPORT, kmpData.activeImportPower);
|
||||
data->apply(OBIS_ACTIVE_EXPORT, kmpData.activeExportPower);
|
||||
data->apply(OBIS_REACTIVE_IMPORT, kmpData.reactiveImportPower);
|
||||
data->apply(OBIS_REACTIVE_EXPORT, kmpData.reactiveExportPower);
|
||||
data->apply(OBIS_VOLTAGE_L1, kmpData.l1voltage);
|
||||
data->apply(OBIS_VOLTAGE_L2, kmpData.l2voltage);
|
||||
data->apply(OBIS_VOLTAGE_L3, kmpData.l3voltage);
|
||||
data->apply(OBIS_CURRENT_L1, kmpData.l1current);
|
||||
data->apply(OBIS_CURRENT_L2, kmpData.l2current);
|
||||
data->apply(OBIS_CURRENT_L3, kmpData.l3current);
|
||||
data->apply(OBIS_POWER_FACTOR_L1, kmpData.l1PowerFactor);
|
||||
data->apply(OBIS_POWER_FACTOR_L2, kmpData.l2PowerFactor);
|
||||
data->apply(OBIS_POWER_FACTOR_L3, kmpData.l3PowerFactor);
|
||||
data->apply(OBIS_POWER_FACTOR, kmpData.powerFactor);
|
||||
data->apply(OBIS_ACTIVE_IMPORT_L1, kmpData.l1activeImportPower);
|
||||
data->apply(OBIS_ACTIVE_IMPORT_L2, kmpData.l2activeImportPower);
|
||||
data->apply(OBIS_ACTIVE_IMPORT_L3, kmpData.l3activeImportPower);
|
||||
data->apply(OBIS_ACTIVE_EXPORT_L1, kmpData.l1activeExportPower);
|
||||
data->apply(OBIS_ACTIVE_EXPORT_L2, kmpData.l2activeExportPower);
|
||||
data->apply(OBIS_ACTIVE_EXPORT_L3, kmpData.l3activeExportPower);
|
||||
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L1, kmpData.l1activeImportCounter);
|
||||
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L2, kmpData.l2activeImportCounter);
|
||||
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L3, kmpData.l3activeImportCounter);
|
||||
data->apply(OBIS_METER_ID, kmpData.meterId);
|
||||
data->apply(OBIS_NULL, AmsTypeKamstrup);
|
||||
return data;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user