mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-10 20:54:24 +00:00
Compare commits
225 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ed701fc57 | ||
|
|
473932f1bc | ||
|
|
d7e68ce692 | ||
|
|
e11fac3d11 | ||
|
|
031422f783 | ||
|
|
2ff8fddc14 | ||
|
|
e5d260ae3e | ||
|
|
633671851e | ||
|
|
69da9f9d48 | ||
|
|
19f78126d6 | ||
|
|
d3cc92949a | ||
|
|
f1089faab5 | ||
|
|
4a3ad6ab9b | ||
|
|
86449949c5 | ||
|
|
9bd9826835 | ||
|
|
94ff9d6da7 | ||
|
|
9518d1811b | ||
|
|
983426b36c | ||
|
|
bcb3c3b2ec | ||
|
|
9fd383c1ef | ||
|
|
a931f4cef8 | ||
|
|
fddecaab39 | ||
|
|
e5eab82d68 | ||
|
|
8ae1d46b2a | ||
|
|
99ccb03b45 | ||
|
|
b8f2d501a5 | ||
|
|
e042806619 | ||
|
|
16f9ed7ecb | ||
|
|
3eaefefd26 | ||
|
|
03c8c3ddbc | ||
|
|
08371b9078 | ||
|
|
a6ae25f3e7 | ||
|
|
8051db6a9b | ||
|
|
d0bfdae5d8 | ||
|
|
4d681ed2e2 | ||
|
|
8ee3f53714 | ||
|
|
e8cf8a98ed | ||
|
|
9153a98694 | ||
|
|
37aa6ae816 | ||
|
|
8a35346fcf | ||
|
|
792ae4c935 | ||
|
|
a7324d828a | ||
|
|
fe739c51d3 | ||
|
|
795d2d0375 | ||
|
|
5ef36a91f6 | ||
|
|
8491d6c471 | ||
|
|
f95f22058a | ||
|
|
b5c45cebfa | ||
|
|
19a953b269 | ||
|
|
6ae970ff68 | ||
|
|
0f0ee82af9 | ||
|
|
d84b9351e1 | ||
|
|
6668258b66 | ||
|
|
9c42aab04f | ||
|
|
c771870e3e | ||
|
|
fb59ee52c1 | ||
|
|
3483910136 | ||
|
|
13e70f7bd4 | ||
|
|
210001e232 | ||
|
|
094e588ad5 | ||
|
|
f9d0cdfa47 | ||
|
|
0476058958 | ||
|
|
3e337a5639 | ||
|
|
bf0e1d1bf3 | ||
|
|
d3b65b0175 | ||
|
|
2caa7252a0 | ||
|
|
f7596de166 | ||
|
|
f5178459e8 | ||
|
|
a55f7dc66a | ||
|
|
3d9cad8953 | ||
|
|
67535b2792 | ||
|
|
2b5f3f50df | ||
|
|
111807a743 | ||
|
|
348ba8cfc4 | ||
|
|
81c72f0ca3 | ||
|
|
20de294a8c | ||
|
|
e31faaa726 | ||
|
|
4ad2921132 | ||
|
|
1fa62fa97c | ||
|
|
affa66f78f | ||
|
|
84860bc684 | ||
|
|
0cadedf9df | ||
|
|
0c3eea8d37 | ||
|
|
1d4e7e564d | ||
|
|
067aa0c6cb | ||
|
|
3d540e2a65 | ||
|
|
61d4728ffa | ||
|
|
42e0ca963c | ||
|
|
3bc40cb136 | ||
|
|
1453bbbb7e | ||
|
|
9da2d0760e | ||
|
|
18af98511a | ||
|
|
40c84d2429 | ||
|
|
c0d95f918e | ||
|
|
0947d335ce | ||
|
|
43e2b2478b | ||
|
|
a5fc1f0cbe | ||
|
|
bf3059ba04 | ||
|
|
319b534d4f | ||
|
|
b7c2510098 | ||
|
|
07205b8008 | ||
|
|
165a385844 | ||
|
|
fa76233151 | ||
|
|
22b7877ee9 | ||
|
|
bd9a1bb330 | ||
|
|
1b5efaa937 | ||
|
|
05ce4c5c1a | ||
|
|
e022f630b2 | ||
|
|
a73ff87653 | ||
|
|
70d6a8699b | ||
|
|
118c633878 | ||
|
|
fa5985f60b | ||
|
|
c1309e9a13 | ||
|
|
718eef7999 | ||
|
|
afdd282adf | ||
|
|
1302f18fab | ||
|
|
fc89670ea4 | ||
|
|
225320ea7d | ||
|
|
b2cf3bb0c4 | ||
|
|
e36a8ade42 | ||
|
|
2ef67d92c4 | ||
|
|
1c9af6b1c2 | ||
|
|
4f47ffa21e | ||
|
|
3d1026f829 | ||
|
|
af6ecc5d47 | ||
|
|
025c556626 | ||
|
|
0b342d6c49 | ||
|
|
ada57b1052 | ||
|
|
cf773985b1 | ||
|
|
75a14ad9cb | ||
|
|
fbe90cbc5a | ||
|
|
f0bc895952 | ||
|
|
f5c10c1a50 | ||
|
|
c4736c730e | ||
|
|
d2c96c733c | ||
|
|
d0dd9de4d7 | ||
|
|
159244974d | ||
|
|
933926fbb4 | ||
|
|
2c95f036ba | ||
|
|
c5c0b52eb7 | ||
|
|
561b62cafe | ||
|
|
55520cd7f6 | ||
|
|
e27d41839e | ||
|
|
569a0ddfaa | ||
|
|
de63263dbb | ||
|
|
8adb7055c0 | ||
|
|
11e09c5b56 | ||
|
|
ac32f74eba | ||
|
|
d49b8c3839 | ||
|
|
054fd43a0d | ||
|
|
423c5da25e | ||
|
|
9a4a8a10f2 | ||
|
|
d15992142e | ||
|
|
fed3948f85 | ||
|
|
b77342a648 | ||
|
|
04f407aba0 | ||
|
|
b122fae04c | ||
|
|
8a4efd0047 | ||
|
|
4407526d96 | ||
|
|
1d91416348 | ||
|
|
a1bbcc20e3 | ||
|
|
2b779920d7 | ||
|
|
de79fd4c43 | ||
|
|
8dfaa34d03 | ||
|
|
0d0dc07903 | ||
|
|
e38a064928 | ||
|
|
81b3aacc4d | ||
|
|
6277d5880d | ||
|
|
8424d1f75e | ||
|
|
099b23d2d5 | ||
|
|
28d746415c | ||
|
|
fd373a5846 | ||
|
|
cd7315990f | ||
|
|
59bf0ce066 | ||
|
|
93e55f457a | ||
|
|
4d340c5482 | ||
|
|
1db0082103 | ||
|
|
f593e14e68 | ||
|
|
da6328c246 | ||
|
|
5eeab2ba89 | ||
|
|
eeaaf088ac | ||
|
|
32afea2817 | ||
|
|
e7b496280d | ||
|
|
d0621e98cd | ||
|
|
202d57843c | ||
|
|
770d662f2c | ||
|
|
da1d5993f4 | ||
|
|
7ad97daf86 | ||
|
|
3b155d78b1 | ||
|
|
37a1e3b93e | ||
|
|
846cf85331 | ||
|
|
a1a6953521 | ||
|
|
a137316cec | ||
|
|
faa468b287 | ||
|
|
703c68a2cf | ||
|
|
b6168a0082 | ||
|
|
38ec99f2ee | ||
|
|
22f8349f2f | ||
|
|
d98a021f39 | ||
|
|
77d899fe0a | ||
|
|
c03bfcbcd7 | ||
|
|
f3732674b0 | ||
|
|
5926ddfaf9 | ||
|
|
99e341e582 | ||
|
|
05506cdc9f | ||
|
|
3f1861deda | ||
|
|
efacbd4b31 | ||
|
|
beaaa191e8 | ||
|
|
43d49f21a3 | ||
|
|
95c495b773 | ||
|
|
68c680debf | ||
|
|
fec6cc7612 | ||
|
|
dc3cea80b6 | ||
|
|
ce67ef2bea | ||
|
|
3a980fac4c | ||
|
|
77873f4a38 | ||
|
|
d9d384ea02 | ||
|
|
16fb1ea87c | ||
|
|
c74e719327 | ||
|
|
8c8e14f60c | ||
|
|
3b93897a8e | ||
|
|
e080c7d535 | ||
|
|
64ea8c4888 | ||
|
|
e7ae24b26f | ||
|
|
84ff999c4c |
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
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -30,12 +30,12 @@ jobs:
|
||||
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@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
|
||||
- name: Cache PlatformIO dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.pio/libdeps
|
||||
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.x'
|
||||
node-version: '19.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
|
||||
41
.github/workflows/localazy.yml
vendored
Normal file
41
.github/workflows/localazy.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Deploy language files from localazy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.x'
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: eu-north-1
|
||||
|
||||
- name: Generate localazy-keys.json
|
||||
run: |
|
||||
echo '{"writeKey": "", "readKey": "${{secrets.LOCALAZY_READ_KEY}}"}' > localazy/localazy-keys.json
|
||||
|
||||
- name: Create localazy language folder
|
||||
run: mkdir -p localazy/language
|
||||
|
||||
- name: Install Localazy CLI
|
||||
run: npm install -g @localazy/cli
|
||||
|
||||
- name: Download translations
|
||||
working-directory: localazy
|
||||
run: localazy download -k localazy-keys.json
|
||||
|
||||
- name: Upload translations to S3
|
||||
run: aws s3 sync ./localazy/language/ s3://amscloud-private/language/
|
||||
24
.github/workflows/release.yml
vendored
24
.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:
|
||||
@@ -33,17 +33,17 @@ jobs:
|
||||
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
|
||||
@@ -52,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
|
||||
@@ -67,16 +67,12 @@ jobs:
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
|
||||
- name: Create Release
|
||||
- name: Create release with release notes
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
name: Release v${{ steps.release_tag.outputs.tag }}
|
||||
generateReleaseNotes: true
|
||||
|
||||
- name: Build esp8266 firmware
|
||||
run: pio run -e esp8266
|
||||
|
||||
63
.github/workflows/x-test-esp8266.yml
vendored
63
.github/workflows/x-test-esp8266.yml
vendored
@@ -1,63 +0,0 @@
|
||||
name: Test ESP8266
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
esp8266:
|
||||
runs-on: esp8266
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Get commit hash
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Check outputs
|
||||
run: echo ${{ steps.vars.outputs.sha_short }}
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Configure PlatformIO environment
|
||||
run: |
|
||||
echo "[platformio]
|
||||
default_envs = dev8266
|
||||
|
||||
[env:dev8266]
|
||||
platform = espressif8266@4.2.0
|
||||
framework = arduino
|
||||
board = esp12e
|
||||
board_build.ldscript = eagle.flash.4m2m.ld
|
||||
build_flags = \${common.build_flags}
|
||||
lib_ldf_mode = off
|
||||
lib_compat_mode = off
|
||||
lib_deps = ESP8266WiFi, ESP8266mDNS, ESP8266WebServer, ESP8266HTTPClient, ESP8266httpUpdate, ESP8266SSDP, \${common.lib_deps}
|
||||
lib_ignore = \${common.lib_ignore}
|
||||
extra_scripts = \${common.extra_scripts}" > platformio-user.ini
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: true
|
||||
- name: PlatformIO lib install
|
||||
run: pio pkg update
|
||||
- name: PlatformIO run
|
||||
run: pio run -t upload --upload-port /dev/ttyUSB0
|
||||
- name: Wait for device to come online
|
||||
run: waitforhost 10.42.0.11 80
|
||||
- name: Confirm version
|
||||
run: curl -s http://10.42.0.11/sysinfo.json|jq -r .version | grep "${{ steps.vars.outputs.sha_short }}" || exit 1
|
||||
- name: Running amsreader-test
|
||||
run: amsreader-test 10.42.0.11
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.vs/
|
||||
.idea/
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
**/__vm/
|
||||
@@ -18,5 +19,5 @@ platformio-user.ini
|
||||
node_modules
|
||||
/gui/dist
|
||||
/scripts/*dev
|
||||
/src/KmpCommunicator.cpp
|
||||
/src/KmpCommunicatorDefs.h
|
||||
localazy-keys.json
|
||||
localazy/language
|
||||
|
||||
7
custom_partition.csv
Normal file
7
custom_partition.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x1D0000,
|
||||
app1, app, ota_1, 0x1E0000,0x1D0000,
|
||||
spiffs, data, spiffs, 0x3B0000,0x40000,
|
||||
coredump, data, coredump,0x3F0000,0x10000,
|
||||
|
BIN
doc/Austria/Smart.meter.customer.interfaces.Austria.pdf
Normal file
BIN
doc/Austria/Smart.meter.customer.interfaces.Austria.pdf
Normal file
Binary file not shown.
BIN
doc/Poland/Stoen/HAN_Interface_Stoen_Operator_EN.pdf
Normal file
BIN
doc/Poland/Stoen/HAN_Interface_Stoen_Operator_EN.pdf
Normal file
Binary file not shown.
BIN
doc/Poland/Stoen/interfejs-sieci-han-w-stoen-operator-v1-0-1.pdf
Normal file
BIN
doc/Poland/Stoen/interfejs-sieci-han-w-stoen-operator-v1-0-1.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
doc/Poland/Stoen/opis-profili-rejestrowane-kody-obis-v1-0-2.pdf
Normal file
BIN
doc/Poland/Stoen/opis-profili-rejestrowane-kody-obis-v1-0-2.pdf
Normal file
Binary file not shown.
1
doc/Poland/Stoen/placeholder.txt
Normal file
1
doc/Poland/Stoen/placeholder.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -31,3 +31,59 @@ Object with three values Value Object with two values
|
||||
02 03 09 06 01 00 04 08 00 FF 06 00 0A F5 EC 02 02 0F 01 16 20
|
||||
51 D7
|
||||
7E
|
||||
|
||||
|
||||
Received 21.09.24 20:00:10:
|
||||
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
|
||||
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 02 71 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 01 F0 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 00 06 02 02 0F FF 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 05 02 02 0F FF 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 17 02 02
|
||||
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 3F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 09 36 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 22 02 02 0F FF 16 23 02 02 09
|
||||
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 09 15 06 13 00
|
||||
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
|
||||
(V) 00 8D 98 13 02 02 0F 01 16 1E 02 03 09 06 01 00
|
||||
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
|
||||
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 6E 02 02
|
||||
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
|
||||
(V) 13 F2 06 02 02 0F 01 16 20 C8 1D 7E
|
||||
|
||||
|
||||
Received 27.10.24 11:00:13:
|
||||
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
|
||||
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 02 AA 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 01 CC 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 00 02 02 02 0F FF 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 0B 02 02 0F FF 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 16 02 02
|
||||
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 09 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 0F 02 02 0F FF 16 23 02 02 09
|
||||
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 0A 1B 00 0B 00
|
||||
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
|
||||
(V) 00 8F 25 6F 02 02 0F 01 16 1E 02 03 09 06 01 00
|
||||
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
|
||||
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 8D 02 02
|
||||
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
|
||||
(V) 14 44 26 02 02 0F 01 16 20 84 09 7E
|
||||
|
||||
14
frames/EGM5G35.txt
Normal file
14
frames/EGM5G35.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
2F 45 47 4D 35 47 33 35 // DSMR Identifier
|
||||
0D 0A // CRLF
|
||||
0D 0A // CRLF
|
||||
00 // System title, blank
|
||||
82 // Two-byte length follows
|
||||
02 30 // 560 bytes
|
||||
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 1=Authentication, 0000= Security Suite ID
|
||||
00 00 00 00 // Frame counter, blank
|
||||
|
||||
// Encrypted payload follows (560 - security tag - frame counter = 555 bytes)
|
||||
30 10 73 D6 59 85 96 B0 2A 8B CC E7 33 AD 19 A3 A5 4A 4D 0E 3A CE F6 33 DB 57 0A 45 9A C3 5B F0 D4 C0 AA 15 B9 93 B0 E8 67 85 EC 22 CA 40 8C D7 1A B5 62 AE DF 0F 48 EA D9 E6 56 FB 84 B3 7B 47 C6 29 12 F1 EE BB 22 88 26 75 5C F8 A2 20 F4 93 F3 64 CA 7C C6 60 32 62 B0 7F F9 7F 71 0A 9F DF AB 61 89 6A 9A 10 B1 DF 94 2C 74 8F 51 B6 09 5C F2 45 6C 38 54 52 FE B6 CB 1F 42 F2 93 DC 57 BE EE 5A 0D F8 1F 53 55 0E 21 EF 46 0A 72 74 CA AB D8 9C A9 05 9E FD 54 5E CC 7B 71 40 E1 22 B2 25 5C D5 63 80 BD 97 6E 54 4B 24 A6 58 1F 79 1F 45 C7 DF C0 83 3B 7F 1E C7 43 B9 26 F6 EB 6E F2 B1 7C 99 3A 91 EA 39 72 E5 FB 72 B1 E1 24 AF 9D 19 8A 6C 50 69 97 90 D3 3D 68 97 57 C2 EC 49 EF 12 1D 11 44 46 70 46 88 88 98 37 6F B0 93 FC D2 3F 37 66 1C C7 F0 93 E3 AF 4D 3C BE 41 8E B9 48 77 A9 92 5B 42 0A 96 E5 34 4D 30 56 90 5A 08 03 CC 41 78 58 79 FD 89 82 E0 46 3D 66 AA 42 85 F4 A8 46 2D 4A E4 81 12 40 68 D6 F5 F9 11 5A 94 51 21 3C 9F 4F F7 FE B3 B5 BF 1E D2 12 CF B9 FB 28 D0 B4 82 9E 11 D8 1C FC E3 36 EC DE 8A 7C 83 76 F7 EB 3D 2C 4A B7 7F 75 91 F1 F5 18 D4 70 6A C9 3E 3D 3F 9E 0F C6 0F A7 E7 20 11 03 E7 75 4E 40 F1 5E BA 86 30 6D C0 28 C4 14 47 DF 6A EF CC FE E4 B6 23 58 2B 9D 4F BF 6A A8 93 84 F4 CD 4D FF 47 B5 C4 81 D2 DA D3 6B C3 F4 1D C4 6E E1 9B 24 30 91 7B 33 57 67 B6 E4 93 FE 8B 5B AE 8A 0D 81 DE A5 C3 3F 82 9A CE E2 BA 6D 50 39 FC 99 E6 7A 7B B4 E9 A9 84 78 AF 2D 4C D2 15 53 6B 98 2B 1F C8 3D 57 9E 49 68 40 0C D3 4A 9E D6 4F 1F 12 50 84 32 AD 62 C6 D3 42 34 46 66 65 56 49 81 F4 BC 8E C6 75 77 11 1F 29 BB 80 20 23 0A 80 D5 31 50 16 89 6F 07 4A 25 BE F0 4B 60 36 87 AE A6 2A 53 79 22 15 A5 2A C7 1D 15 7B 87 DD B5 07 3D 1C 28 C1 B6 51 47 2F 39 DE D9 B9 29 B6 78
|
||||
21 // End of DSMR (!)
|
||||
46 45 30 43 // FE0C - Checksum
|
||||
0D 0A // CRLF
|
||||
@@ -31,4 +31,183 @@ C9 95 7E // CRC and end tag
|
||||
06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05
|
||||
06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B
|
||||
09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E
|
||||
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
|
||||
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
|
||||
|
||||
|
||||
01.10.24 19:00 ish:
|
||||
(D) READY to update (internal clock 16:59:58 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 78 01 02 01 10 C4 98 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 00 FF 80 00 00 02
|
||||
(V) 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
|
||||
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
|
||||
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
|
||||
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06 00
|
||||
(V) 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00 00
|
||||
(V) 09 69 06 00 00 09 59 18 EB 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 00 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30
|
||||
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
|
||||
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
|
||||
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
|
||||
(V) 06 00 00 4C DF 06 00 00 05 0C 06 00 00 03 9B 06
|
||||
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 00 FF
|
||||
(V) 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10
|
||||
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
|
||||
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
|
||||
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
|
||||
(V) 4C DF 06 00 00 05 0C 06 00 00 03 9B 06 00 00 09
|
||||
(V) 34 06 00 00 09 69 06 00 00 09 59
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
|
||||
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
|
||||
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
|
||||
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06
|
||||
(V) 00 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00
|
||||
(V) 00 09 69 06 00 00 09 59
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:01 UTC, meter clock: 00:00:00, list type 2, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 02 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 C1 56 F5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 02 FF 80 00 00 02 01 06 00 00 13 C1
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 02 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 C1
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 C1
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:02 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 04 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC F9 5A 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 04 FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 04 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:04 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 06 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC 42 58 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 06 FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 06 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:06 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 08 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BB DC 21 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 08 FF 80 00 00 02 01 06 00 00 13 BB
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 08 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BB
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BB
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:08 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0A FF 80 00 00 02
|
||||
(V) 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
|
||||
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
|
||||
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
|
||||
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06 00
|
||||
(V) 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00 00
|
||||
(V) 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13 00
|
||||
(V) 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00 06
|
||||
(V) 00 97 35 DE 06 00 08 97 2F FA E5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30
|
||||
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
|
||||
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
|
||||
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
|
||||
(V) 06 00 00 4C DD 06 00 00 05 0E 06 00 00 03 90 06
|
||||
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59 09 0C
|
||||
(V) 07 E8 0A 01 02 13 00 0A FF 80 00 00 06 02 8E 4B
|
||||
(V) 5E 06 00 00 00 00 06 00 97 35 DE 06 00 08 97 2F
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0A FF
|
||||
(V) 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10
|
||||
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
|
||||
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
|
||||
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
|
||||
(V) 4C DD 06 00 00 05 0E 06 00 00 03 90 06 00 00 09
|
||||
(V) 34 06 00 00 09 69 06 00 00 09 59 09 0C 07 E8 0A
|
||||
(V) 01 02 13 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00
|
||||
(V) 00 00 00 06 00 97 35 DE 06 00 08 97 2F
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
|
||||
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
|
||||
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
|
||||
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06
|
||||
(V) 00 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00
|
||||
(V) 00 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00
|
||||
(V) 06 00 97 35 DE 06 00 08 97 2F
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:12 UTC, meter clock: 17:00:10, list type 3, est: 1, using clock: 0)
|
||||
(D) Updating data storage using actual data
|
||||
(D) Clearing hours from 16 to 17
|
||||
(D) Clearing days from 1 to 1
|
||||
(D) Day is not happy
|
||||
(D) - normal
|
||||
(I) Saving data
|
||||
(I) Saving energy accounting
|
||||
(W) Used 1249ms to read HAN port (true)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0C FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC 15 50 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0C FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0C FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 17:00:12 UTC, meter clock: 00:00:00, list type 1, est: 0)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0E FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 B7 7D EC 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0E FF 80 00 00 02 01 06 00 00 13 B7
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0E FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 B7
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 B7
|
||||
|
||||
1375
frames/KamstrupOmnipower_Bornholm_DK_5digitVoltage_9sep25.log
Normal file
1375
frames/KamstrupOmnipower_Bornholm_DK_5digitVoltage_9sep25.log
Normal file
File diff suppressed because it is too large
Load Diff
682
frames/L&G-E350_Norway.raw
Normal file
682
frames/L&G-E350_Norway.raw
Normal file
@@ -0,0 +1,682 @@
|
||||
*** Remote debug - over telnet - for ESP32 - version 3.0.5
|
||||
* Host name: ams-e603 IP:10.10.10.62 Mac address:D8:3B:DA:C4:03:E6
|
||||
* Free Heap RAM: 87952
|
||||
* ESP SDK version: 4.4.5.230722
|
||||
******************************************************
|
||||
* Commands:
|
||||
? or help -> display these help of commands
|
||||
q -> quit (close this connection)
|
||||
m -> display memory available
|
||||
v -> set debug level to verbose
|
||||
d -> set debug level to debug
|
||||
i -> set debug level to info
|
||||
w -> set debug level to warning
|
||||
e -> set debug level to errors
|
||||
s -> set debug silence on/off
|
||||
l -> show debug level
|
||||
t -> show time (millis)
|
||||
profiler:
|
||||
p -> show time between actual and last message (in millis)
|
||||
p min -> show only if time is this minimal
|
||||
P time -> set debug level to profiler
|
||||
c -> show colors
|
||||
filter:
|
||||
filter <string> -> show only debugs with this
|
||||
nofilter -> disable the filter
|
||||
* Please type the command and press enter to execute.(? or h for this help)
|
||||
***
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 2F AF 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 F4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 68 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 68 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:06 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) F4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:07 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) F4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:10 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) E0 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:12 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 00 00 00 00 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 28
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 AE 9D 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 00 00 00 00 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 E0 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 28 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 68 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 00 00 00 00 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 28 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 68 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 00 00 00 00 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 28 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:16 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) E0 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:17 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:20 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:22 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 38 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 4A DF 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5C 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 38 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5C 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 38 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 38 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:26 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:27 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:29 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:32 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 46
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 3A 49 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 46 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 67 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 46 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 67 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 46 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 06 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:36 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:37 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:39 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) D5 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:42 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 4A 9D 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 D5 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 3C 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 77 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5F 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 3C 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 77 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5F 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 06 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:46 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
qq(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) D5 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:47 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q
|
||||
* Debug: Command received: qqqqq
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:49 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:51 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q
|
||||
* Debug: Command received: q
|
||||
* Closing client connection ...
|
||||
1
frames/dsmr.raw
Normal file
1
frames/dsmr.raw
Normal file
@@ -0,0 +1 @@
|
||||
2F454C4C355C3235333833333633355F410D0A0D0A302D303A312E302E302832343132313232303133343857290D0A312D303A312E382E302830303034373532322E3237382A6B5768290D0A312D303A322E382E302830303030303030302E3030312A6B5768290D0A312D303A332E382E302830303030303035392E3033392A6B76617268290D0A312D303A342E382E302830303031323537302E3937362A6B76617268290D0A312D303A312E372E3028303030312E3233312A6B57290D0A312D303A322E372E3028303030302E3030302A6B57290D0A312D303A332E372E3028303030302E3030302A6B766172290D0A312D303A342E372E3028303030302E3435312A6B766172290D0A312D303A32312E372E3028303030302E3337362A6B57290D0A312D303A34312E372E3028303030302E3832342A6B57290D0A312D303A36312E372E3028303030302E3033302A6B57290D0A312D303A32322E372E3028303030302E3030302A6B57290D0A312D303A34322E372E3028303030302E3030302A6B57290D0A312D303A36322E372E3028303030302E3030302A6B57290D0A312D303A32332E372E3028303030302E3030302A6B766172290D0A312D303A34332E372E3028303030302E3030302A6B766172290D0A312D303A36332E372E3028303030302E3030302A6B766172290D0A312D303A32342E372E3028303030302E3037322A6B766172290D0A312D303A34342E372E3028303030302E3334392A6B766172290D0A312D303A36342E372E3028303030302E3032392A6B766172290D0A312D303A33322E372E30283233322E382A56290D0A312D303A35322E372E30283233332E352A56290D0A312D303A37322E372E30283233382E352A56290D0A312D303A33312E372E30283030312E382A41290D0A312D303A35312E372E30283030342E302A41290D0A312D303A37312E372E30283030302E312A41290D0A21343736300D0A
|
||||
@@ -13,10 +13,8 @@
|
||||
#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_UPGRADE_INFO_START 16
|
||||
#define CONFIG_NETWORK_START 40
|
||||
#define CONFIG_METER_START 296
|
||||
#define CONFIG_GPIO_START 368
|
||||
@@ -30,6 +28,8 @@
|
||||
#define CONFIG_HA_START 1552
|
||||
#define CONFIG_UI_START 1720
|
||||
#define CONFIG_CLOUD_START 1742
|
||||
#define CONFIG_UPGRADE_INFO_START 1934
|
||||
#define CONFIG_ZC_START 2000
|
||||
|
||||
#define CONFIG_METER_START_103 32
|
||||
#define CONFIG_UPGRADE_INFO_START_103 216
|
||||
@@ -63,7 +63,7 @@ struct SystemConfig {
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[3];
|
||||
uint8_t energyspeedometer;
|
||||
}; // 9
|
||||
}; // 8
|
||||
|
||||
struct NetworkConfig {
|
||||
char ssid[32];
|
||||
@@ -92,7 +92,13 @@ struct MqttConfig {
|
||||
char password[256];
|
||||
uint8_t payloadFormat;
|
||||
bool ssl;
|
||||
}; // 676
|
||||
uint8_t magic;
|
||||
bool stateUpdate;
|
||||
uint16_t stateUpdateInterval;
|
||||
uint16_t timeout;
|
||||
uint8_t keepalive;
|
||||
uint8_t rebootMinutes;
|
||||
}; // 684
|
||||
|
||||
struct WebConfig {
|
||||
uint8_t security;
|
||||
@@ -211,11 +217,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;
|
||||
@@ -238,9 +239,12 @@ struct UiConfig {
|
||||
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;
|
||||
@@ -248,7 +252,14 @@ struct CloudConfig {
|
||||
char hostname[64];
|
||||
uint16_t port;
|
||||
uint8_t clientId[16];
|
||||
}; // 69
|
||||
uint8_t proto;
|
||||
}; // 88
|
||||
|
||||
struct ZmartChargeConfig {
|
||||
bool enabled;
|
||||
char token[21];
|
||||
char baseUrl[64];
|
||||
}; // 86
|
||||
|
||||
class AmsConfiguration {
|
||||
public:
|
||||
@@ -279,6 +290,8 @@ public:
|
||||
bool getWebConfig(WebConfig&);
|
||||
bool setWebConfig(WebConfig&);
|
||||
void clearWebConfig(WebConfig&);
|
||||
bool isWebChanged();
|
||||
void ackWebChange();
|
||||
|
||||
bool getMeterConfig(MeterConfig&);
|
||||
bool setMeterConfig(MeterConfig&);
|
||||
@@ -295,7 +308,7 @@ public:
|
||||
|
||||
bool getGpioConfig(GpioConfig&);
|
||||
bool setGpioConfig(GpioConfig&);
|
||||
void clearGpio(GpioConfig&);
|
||||
void clearGpio(GpioConfig& config, bool all=true);
|
||||
|
||||
void print(Print* debugger);
|
||||
|
||||
@@ -333,7 +346,7 @@ public:
|
||||
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&);
|
||||
@@ -341,6 +354,13 @@ public:
|
||||
void clearCloudConfig(CloudConfig&);
|
||||
bool isCloudChanged();
|
||||
void ackCloudConfig();
|
||||
|
||||
bool getZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool setZmartChargeConfig(ZmartChargeConfig&);
|
||||
void clearZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool isZmartChargeConfigChanged();
|
||||
void ackZmartChargeConfig();
|
||||
|
||||
|
||||
void clear();
|
||||
|
||||
@@ -349,10 +369,8 @@ protected:
|
||||
private:
|
||||
uint8_t configVersion = 0;
|
||||
|
||||
bool sysChanged = false, networkChanged, mqttChanged, meterChanged = true, ntpChanged = true, priceChanged = false, energyAccountingChanged = true, cloudChanged = true, uiLanguageChanged = false;
|
||||
bool sysChanged = false, networkChanged = false, mqttChanged = false, webChanged = false, meterChanged = true, ntpChanged = true, priceChanged = false, energyAccountingChanged = true, cloudChanged = true, uiLanguageChanged = false, zcChanged = true;
|
||||
|
||||
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();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#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"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
String toHex(uint8_t* in);
|
||||
String toHex(uint8_t* in, uint16_t size);
|
||||
void fromHex(uint8_t *out, String in, uint16_t size);
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended = false);
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended = false, bool trim = true);
|
||||
void debugPrint(uint8_t *buffer, uint16_t start, uint16_t length, Print* debugger);
|
||||
|
||||
#endif
|
||||
@@ -13,13 +13,18 @@
|
||||
bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
|
||||
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
|
||||
EEPROM.get(CONFIG_SYSTEM_START, config);
|
||||
EEPROM.end();
|
||||
EEPROM.get(CONFIG_SYSTEM_START, config);
|
||||
EEPROM.end();
|
||||
if(configVersion == EEPROM_CHECK_SUM) {
|
||||
return true;
|
||||
} else {
|
||||
config.boardType = 0xFF;
|
||||
config.vendorConfigured = false;
|
||||
if(configVersion == EEPROM_CLEARED_INDICATOR && config.boardType > 0 && config.boardType < 250) {
|
||||
config.vendorConfigured = true;
|
||||
} else {
|
||||
config.vendorConfigured = false;
|
||||
config.boardType = 0xFF;
|
||||
clear();
|
||||
}
|
||||
config.userConfigured = false;
|
||||
config.dataCollectionConsent = 0;
|
||||
config.energyspeedometer = 0;
|
||||
@@ -91,7 +96,7 @@ bool AmsConfiguration::setNetworkConfig(NetworkConfig& config) {
|
||||
}
|
||||
|
||||
stripNonAscii((uint8_t*) config.ssid, 32, true);
|
||||
stripNonAscii((uint8_t*) config.psk, 64);
|
||||
stripNonAscii((uint8_t*) config.psk, 64, true, false);
|
||||
stripNonAscii((uint8_t*) config.ip, 16);
|
||||
stripNonAscii((uint8_t*) config.gateway, 16);
|
||||
stripNonAscii((uint8_t*) config.subnet, 16);
|
||||
@@ -147,6 +152,18 @@ bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_MQTT_START, config);
|
||||
EEPROM.end();
|
||||
if(config.magic != 0xA5) { // New magic for 2.4.11
|
||||
if(config.magic != 0x9C) {
|
||||
if(config.magic != 0x7B) {
|
||||
config.stateUpdate = false;
|
||||
config.stateUpdateInterval = 10;
|
||||
}
|
||||
config.timeout = 1000;
|
||||
config.keepalive = 60;
|
||||
}
|
||||
config.rebootMinutes = config.ssl ? 5 : 0;
|
||||
config.magic = 0xA5;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
clearMqtt(config);
|
||||
@@ -166,6 +183,11 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
|
||||
mqttChanged |= strcmp(config.password, existing.password) != 0;
|
||||
mqttChanged |= config.payloadFormat != existing.payloadFormat;
|
||||
mqttChanged |= config.ssl != existing.ssl;
|
||||
mqttChanged |= config.stateUpdate != existing.stateUpdate;
|
||||
mqttChanged |= config.stateUpdateInterval != existing.stateUpdateInterval;
|
||||
mqttChanged |= config.timeout != existing.timeout;
|
||||
mqttChanged |= config.keepalive != existing.keepalive;
|
||||
mqttChanged |= config.rebootMinutes != existing.rebootMinutes;
|
||||
} else {
|
||||
mqttChanged = true;
|
||||
}
|
||||
@@ -174,8 +196,13 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
|
||||
stripNonAscii((uint8_t*) config.clientId, 32);
|
||||
stripNonAscii((uint8_t*) config.publishTopic, 64);
|
||||
stripNonAscii((uint8_t*) config.subscribeTopic, 64);
|
||||
stripNonAscii((uint8_t*) config.username, 128);
|
||||
stripNonAscii((uint8_t*) config.password, 256);
|
||||
stripNonAscii((uint8_t*) config.username, 128, true);
|
||||
stripNonAscii((uint8_t*) config.password, 256, true, false);
|
||||
if(config.timeout < 500) config.timeout = 1000;
|
||||
if(config.timeout > 10000) config.timeout = 1000;
|
||||
if(config.keepalive < 5) config.keepalive = 60;
|
||||
if(config.keepalive > 240) config.keepalive = 60;
|
||||
if(config.rebootMinutes > 240) config.rebootMinutes = 0;
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_MQTT_START, config);
|
||||
@@ -195,6 +222,12 @@ void AmsConfiguration::clearMqtt(MqttConfig& config) {
|
||||
memset(config.password, 0, 256);
|
||||
config.payloadFormat = 0;
|
||||
config.ssl = false;
|
||||
config.magic = 0x7B;
|
||||
config.stateUpdate = false;
|
||||
config.stateUpdateInterval = 10;
|
||||
config.timeout = 1000;
|
||||
config.keepalive = 60;
|
||||
config.rebootMinutes = 0;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttChanged() {
|
||||
@@ -222,9 +255,17 @@ bool AmsConfiguration::getWebConfig(WebConfig& config) {
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setWebConfig(WebConfig& config) {
|
||||
WebConfig existing;
|
||||
if(getWebConfig(existing)) {
|
||||
webChanged |= strcmp(config.username, existing.username) != 0;
|
||||
webChanged |= strcmp(config.password, existing.password) != 0;
|
||||
webChanged |= strcmp(config.context, existing.context) != 0;
|
||||
} else {
|
||||
webChanged = true;
|
||||
}
|
||||
|
||||
stripNonAscii((uint8_t*) config.username, 37);
|
||||
stripNonAscii((uint8_t*) config.password, 37);
|
||||
stripNonAscii((uint8_t*) config.password, 37, false, false);
|
||||
stripNonAscii((uint8_t*) config.context, 37);
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
@@ -241,9 +282,18 @@ void AmsConfiguration::clearWebConfig(WebConfig& config) {
|
||||
memset(config.context, 0, 37);
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isWebChanged() {
|
||||
return webChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackWebChange() {
|
||||
webChanged = false;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getMeterConfig(MeterConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
|
||||
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
|
||||
EEPROM.get(CONFIG_METER_START, config);
|
||||
EEPROM.end();
|
||||
if(config.bufferSize < 1 || config.bufferSize > 64) {
|
||||
@@ -400,6 +450,7 @@ bool AmsConfiguration::getHomeAssistantConfig(HomeAssistantConfig& config) {
|
||||
EEPROM.end();
|
||||
if(stripNonAscii((uint8_t*) config.discoveryPrefix, 64) || stripNonAscii((uint8_t*) config.discoveryHostname, 64) || stripNonAscii((uint8_t*) config.discoveryNameTag, 16)) {
|
||||
clearHomeAssistantConfig(config);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -518,7 +569,7 @@ bool AmsConfiguration::setGpioConfig(GpioConfig& config) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearGpio(GpioConfig& config) {
|
||||
void AmsConfiguration::clearGpio(GpioConfig& config, bool all) {
|
||||
config.apPin = 0xFF;
|
||||
config.ledPin = 0xFF;
|
||||
config.ledInverted = true;
|
||||
@@ -529,13 +580,16 @@ void AmsConfiguration::clearGpio(GpioConfig& config) {
|
||||
config.tempSensorPin = 0xFF;
|
||||
config.tempAnalogSensorPin = 0xFF;
|
||||
config.vccPin = 0xFF;
|
||||
config.vccOffset = 0;
|
||||
config.vccMultiplier = 1000;
|
||||
config.vccBootLimit = 0;
|
||||
config.vccResistorGnd = 0;
|
||||
config.vccResistorVcc = 0;
|
||||
config.ledDisablePin = 0xFF;
|
||||
config.ledBehaviour = LED_BEHAVIOUR_DEFAULT;
|
||||
|
||||
if(all) {
|
||||
config.vccOffset = 0;
|
||||
config.vccMultiplier = 1000;
|
||||
config.vccBootLimit = 0;
|
||||
config.vccResistorGnd = 0;
|
||||
config.vccResistorVcc = 0;
|
||||
config.ledBehaviour = LED_BEHAVIOUR_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getNtpConfig(NtpConfig& config) {
|
||||
@@ -599,6 +653,7 @@ bool AmsConfiguration::getPriceServiceConfig(PriceServiceConfig& config) {
|
||||
EEPROM.end();
|
||||
if(strlen(config.entsoeToken) != 0 && strlen(config.entsoeToken) != 36) {
|
||||
clearPriceServiceConfig(config);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -654,6 +709,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
EEPROM.end();
|
||||
if(config.thresholds[9] != 0xFFFF) {
|
||||
clearEnergyAccountingConfig(config);
|
||||
return false;
|
||||
}
|
||||
if(config.hours > 5) config.hours = 5;
|
||||
return true;
|
||||
@@ -761,13 +817,7 @@ void AmsConfiguration::ackUiLanguageChange() {
|
||||
uiLanguageChanged = false;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion) {
|
||||
UpgradeInformation upinfo;
|
||||
upinfo.exitCode = exitCode;
|
||||
upinfo.errorCode = errorCode;
|
||||
strcpy(upinfo.fromVersion, currentVersion);
|
||||
strcpy(upinfo.toVersion, nextVersion);
|
||||
|
||||
bool AmsConfiguration::setUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
stripNonAscii((uint8_t*) upinfo.fromVersion, 8);
|
||||
stripNonAscii((uint8_t*) upinfo.toVersion, 8);
|
||||
|
||||
@@ -785,6 +835,7 @@ bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
EEPROM.end();
|
||||
if(stripNonAscii((uint8_t*) upinfo.fromVersion, 8) || stripNonAscii((uint8_t*) upinfo.toVersion, 8)) {
|
||||
clearUpgradeInformation(upinfo);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -794,10 +845,13 @@ bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
upinfo.exitCode = -1;
|
||||
upinfo.errorCode = 0;
|
||||
memset(upinfo.fromVersion, 0, 8);
|
||||
memset(upinfo.toVersion, 0, 8);
|
||||
upinfo.errorCode = 0;
|
||||
upinfo.size = 0;
|
||||
upinfo.block_position = 0;
|
||||
upinfo.retry_count = 0;
|
||||
upinfo.reboot_count = 0;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getCloudConfig(CloudConfig& config) {
|
||||
@@ -805,6 +859,7 @@ bool AmsConfiguration::getCloudConfig(CloudConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_CLOUD_START, config);
|
||||
EEPROM.end();
|
||||
if(config.proto > 2) config.proto = 0;
|
||||
return true;
|
||||
} else {
|
||||
clearCloudConfig(config);
|
||||
@@ -818,6 +873,7 @@ bool AmsConfiguration::setCloudConfig(CloudConfig& config) {
|
||||
cloudChanged |= config.enabled != existing.enabled;
|
||||
cloudChanged |= config.interval!= existing.interval;
|
||||
cloudChanged |= config.port!= existing.port;
|
||||
cloudChanged |= config.proto!= existing.proto;
|
||||
cloudChanged |= strcmp(config.hostname, existing.hostname) != 0;
|
||||
cloudChanged |= memcmp(config.clientId, existing.clientId, 16) != 0;
|
||||
} else {
|
||||
@@ -836,6 +892,7 @@ bool AmsConfiguration::setCloudConfig(CloudConfig& config) {
|
||||
void AmsConfiguration::clearCloudConfig(CloudConfig& config) {
|
||||
config.enabled = false;
|
||||
strcpy_P(config.hostname, PSTR("cloud.amsleser.no"));
|
||||
config.proto = 1;
|
||||
config.port = 7443;
|
||||
config.interval = 10;
|
||||
memset(config.clientId, 0, 16);
|
||||
@@ -849,6 +906,65 @@ void AmsConfiguration::ackCloudConfig() {
|
||||
cloudChanged = false;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getZmartChargeConfig(ZmartChargeConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_ZC_START, config);
|
||||
EEPROM.end();
|
||||
stripNonAscii((uint8_t*) config.token, 21);
|
||||
stripNonAscii((uint8_t*) config.baseUrl, 64);
|
||||
if(strlen(config.token) != 20 || !config.enabled) {
|
||||
config.enabled = false;
|
||||
memset(config.token, 0, 64);
|
||||
memset(config.baseUrl, 0, 64);
|
||||
}
|
||||
if(strlen(config.baseUrl) == 0 || strncmp_P(config.baseUrl, PSTR("https"), 5) != 0) {
|
||||
memset(config.baseUrl, 0, 64);
|
||||
snprintf_P(config.baseUrl, 64, PSTR("https://main.zmartcharge.com/api"));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
clearZmartChargeConfig(config);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setZmartChargeConfig(ZmartChargeConfig& config) {
|
||||
ZmartChargeConfig existing;
|
||||
if(getZmartChargeConfig(existing)) {
|
||||
zcChanged |= config.enabled != existing.enabled;
|
||||
zcChanged |= strcmp(config.token, existing.token) != 0;
|
||||
zcChanged |= strcmp(config.baseUrl, existing.baseUrl) != 0;
|
||||
} else {
|
||||
zcChanged = true;
|
||||
}
|
||||
|
||||
stripNonAscii((uint8_t*) config.token, 21);
|
||||
stripNonAscii((uint8_t*) config.baseUrl, 64);
|
||||
if(strncmp_P(config.baseUrl, PSTR("https"), 5) != 0) {
|
||||
memset(config.baseUrl, 0, 64);
|
||||
}
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_ZC_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearZmartChargeConfig(ZmartChargeConfig& config) {
|
||||
config.enabled = false;
|
||||
memset(config.token, 0, 21);
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isZmartChargeConfigChanged() {
|
||||
return zcChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackZmartChargeConfig() {
|
||||
zcChanged = false;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setUiLanguageChanged() {
|
||||
uiLanguageChanged = true;
|
||||
}
|
||||
@@ -916,6 +1032,10 @@ void AmsConfiguration::clear() {
|
||||
clearCloudConfig(cloud);
|
||||
EEPROM.put(CONFIG_CLOUD_START, cloud);
|
||||
|
||||
ZmartChargeConfig zc;
|
||||
clearZmartChargeConfig(zc);
|
||||
EEPROM.put(CONFIG_ZC_START, zc);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CLEARED_INDICATOR);
|
||||
EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -935,22 +1055,6 @@ bool AmsConfiguration::hasConfig() {
|
||||
}
|
||||
} else {
|
||||
switch(configVersion) {
|
||||
case 101:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig101()) {
|
||||
configVersion = 102;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 102:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig102()) {
|
||||
configVersion = 103;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 103:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig103()) {
|
||||
@@ -973,48 +1077,6 @@ int AmsConfiguration::getConfigVersion() {
|
||||
return configVersion;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig101() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
EnergyAccountingConfig config;
|
||||
EnergyAccountingConfig101 config101;
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START_103, config101);
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
config.thresholds[i] = config101.thresholds[i];
|
||||
}
|
||||
config.thresholds[9] = 0xFFFF;
|
||||
config.hours = config101.hours;
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START_103, config);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 102);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig102() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
GpioConfig103 gpioConfig;
|
||||
EEPROM.get(CONFIG_GPIO_START_103, gpioConfig);
|
||||
gpioConfig.hanPinPullup = true;
|
||||
EEPROM.put(CONFIG_GPIO_START_103, gpioConfig);
|
||||
|
||||
HomeAssistantConfig haconf;
|
||||
clearHomeAssistantConfig(haconf);
|
||||
EEPROM.put(CONFIG_HA_START_103, haconf);
|
||||
|
||||
PriceServiceConfig entsoe;
|
||||
EEPROM.get(CONFIG_ENTSOE_START_103, entsoe);
|
||||
entsoe.unused2 = 0;
|
||||
EEPROM.put(CONFIG_ENTSOE_START_103, entsoe);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 103);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig103() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
@@ -1080,8 +1142,8 @@ bool AmsConfiguration::relocateConfig103() {
|
||||
};
|
||||
|
||||
WebConfig web = {web103.security};
|
||||
strcpy_P(web.username, web103.username);
|
||||
strcpy_P(web.password, web103.password);
|
||||
strcpy(web.username, web103.username);
|
||||
strcpy(web.password, web103.password);
|
||||
memset(web.context, 0, 37);
|
||||
|
||||
strcpy_P(ui.language, PSTR("en"));
|
||||
@@ -1108,6 +1170,10 @@ bool AmsConfiguration::relocateConfig103() {
|
||||
clearCloudConfig(cloud);
|
||||
EEPROM.put(CONFIG_CLOUD_START, cloud);
|
||||
|
||||
ZmartChargeConfig zcc;
|
||||
clearZmartChargeConfig(zcc);
|
||||
EEPROM.put(CONFIG_ZC_START, zcc);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 104);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -1116,6 +1182,7 @@ bool AmsConfiguration::relocateConfig103() {
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
uint8_t configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
bool success = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@@ -1187,6 +1254,9 @@ void AmsConfiguration::print(Print* debugger)
|
||||
}
|
||||
debugger->printf_P(PSTR("Payload format: %i\r\n"), mqtt.payloadFormat);
|
||||
debugger->printf_P(PSTR("SSL: %s\r\n"), mqtt.ssl ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("Timeout: %i\r\n"), mqtt.timeout);
|
||||
debugger->printf_P(PSTR("Keep-alive: %i\r\n"), mqtt.keepalive);
|
||||
debugger->printf_P(PSTR("Auto reboot minutes: %i\r\n"), mqtt.rebootMinutes);
|
||||
} else {
|
||||
debugger->printf_P(PSTR("Enabled: No\r\n"));
|
||||
}
|
||||
@@ -1295,27 +1365,50 @@ void AmsConfiguration::print(Print* debugger)
|
||||
debugger->printf_P(PSTR("Area: %s\r\n"), price.area);
|
||||
debugger->printf_P(PSTR("Currency: %s\r\n"), price.currency);
|
||||
debugger->printf_P(PSTR("ENTSO-E Token: %s\r\n"), price.entsoeToken);
|
||||
debugger->println(F(""));
|
||||
delay(10);
|
||||
debugger->flush();
|
||||
}
|
||||
debugger->println(F(""));
|
||||
delay(10);
|
||||
debugger->flush();
|
||||
}
|
||||
|
||||
UiConfig ui;
|
||||
if(getUiConfig(ui)) {
|
||||
debugger->println(F("--UI configuration--"));
|
||||
debugger->printf_P(PSTR("Language: %s\r\n"), ui.language);
|
||||
debugger->println(F(""));
|
||||
delay(10);
|
||||
debugger->flush();
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
CloudConfig cc;
|
||||
if(getCloudConfig(cc)) {
|
||||
String uuid = ESPRandom::uuidToString(cc.clientId);;
|
||||
debugger->println(F("--UI configuration--"));
|
||||
debugger->println(F("--Cloud configuration--"));
|
||||
debugger->printf_P(PSTR("Enabled: %s\r\n"), cc.enabled ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("Hostname: %s\r\n"), cc.hostname);
|
||||
debugger->printf_P(PSTR("Client ID: %s\r\n"), uuid.c_str());
|
||||
debugger->printf_P(PSTR("Interval: %d\r\n"), cc.interval);
|
||||
if(cc.enabled) {
|
||||
debugger->printf_P(PSTR("Hostname: %s\r\n"), cc.hostname);
|
||||
debugger->printf_P(PSTR("Client ID: %s\r\n"), uuid.c_str());
|
||||
debugger->printf_P(PSTR("Interval: %d\r\n"), cc.interval);
|
||||
}
|
||||
debugger->println(F(""));
|
||||
delay(10);
|
||||
debugger->flush();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ZMART_CHARGE)
|
||||
ZmartChargeConfig zc;
|
||||
if(getZmartChargeConfig(zc)) {
|
||||
debugger->println(F("--ZmartCharge configuration--"));
|
||||
debugger->printf_P(PSTR("Enabled: %s\r\n"), zc.enabled ? "Yes" : "No");
|
||||
if(zc.enabled) {
|
||||
debugger->printf_P(PSTR("Base URL: '%s'\r\n"), zc.baseUrl);
|
||||
debugger->printf_P(PSTR("Token: '%s'\r\n"), zc.token);
|
||||
}
|
||||
debugger->println(F(""));
|
||||
delay(10);
|
||||
debugger->flush();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ void fromHex(uint8_t *out, String in, uint16_t size) {
|
||||
}
|
||||
}
|
||||
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
|
||||
bool stripNonAscii(uint8_t* in, uint16_t size, bool extended, bool trim) {
|
||||
bool ret = false;
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
if(in[i] == 0) { // Clear the rest with null-terminator
|
||||
memset(in+i, 0, size-i);
|
||||
break;
|
||||
}
|
||||
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157)) {
|
||||
if(extended && (in[i] < 32 || in[i] == 127 || in[i] == 129 || in[i] == 141 || in[i] == 143 || in[i] == 144 || in[i] == 157 || in[i] == 160)) {
|
||||
memset(in+i, ' ', 1);
|
||||
ret = true;
|
||||
} else if(!extended && (in[i] < 32 || in[i] > 126)) {
|
||||
@@ -43,6 +43,38 @@ bool stripNonAscii(uint8_t* in, uint16_t size, bool extended) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
if(trim) {
|
||||
// Strip leading spaces
|
||||
while(in[0] == ' ') {
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
in[i] = in[i+1];
|
||||
}
|
||||
}
|
||||
// Strip trailing spaces
|
||||
for(int i = size-1; i > 0; i--) {
|
||||
if(in[i] == ' ' || in[i] == 0) {
|
||||
memset(in+i, 0, 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
memset(in+size-1, 0, 1); // Make sure the last character is null-terminator
|
||||
return ret;
|
||||
}
|
||||
|
||||
void debugPrint(uint8_t *buffer, uint16_t start, uint16_t length, Print* debugger) {
|
||||
for (uint16_t i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
debugger->print(F("0"));
|
||||
debugger->print(buffer[i], HEX);
|
||||
debugger->print(F(" "));
|
||||
if ((i - start + 1) % 16 == 0)
|
||||
debugger->println(F(""));
|
||||
else if ((i - start + 1) % 4 == 0)
|
||||
debugger->print(F(" "));
|
||||
|
||||
yield(); // Let other get some resources too
|
||||
}
|
||||
debugger->println(F(""));
|
||||
}
|
||||
@@ -105,6 +105,7 @@ protected:
|
||||
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;
|
||||
|
||||
@@ -59,6 +59,10 @@ 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 };
|
||||
@@ -66,6 +70,10 @@ 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 };
|
||||
@@ -73,6 +81,10 @@ 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 };
|
||||
|
||||
@@ -69,10 +69,23 @@ void AmsData::apply(AmsData& other) {
|
||||
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();
|
||||
@@ -100,88 +113,156 @@ void AmsData::apply(AmsData& other) {
|
||||
}
|
||||
|
||||
void AmsData::apply(OBIS_code_t obis, double value) {
|
||||
switch(obis.gr) {
|
||||
case 1:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveImportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
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() {
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#define _AMSDATASTORAGE_H
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "Timezone.h"
|
||||
|
||||
struct DayDataPoints5 {
|
||||
@@ -53,9 +55,13 @@ struct MonthDataPoints {
|
||||
|
||||
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);
|
||||
@@ -73,12 +79,17 @@ 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;
|
||||
DayDataPoints day = {
|
||||
@@ -95,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
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
#include "AmsStorage.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsDataStorage::AmsDataStorage(RemoteDebug* debugger) {
|
||||
#else
|
||||
AmsDataStorage::AmsDataStorage(Stream* debugger) {
|
||||
#endif
|
||||
day.version = 6;
|
||||
day.accuracy = 1;
|
||||
month.version = 7;
|
||||
@@ -22,13 +26,20 @@ void AmsDataStorage::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::update(AmsData* data) {
|
||||
if(isHappy()) {
|
||||
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(tz == NULL) {
|
||||
#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) {
|
||||
@@ -39,6 +50,10 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
}
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Before build time, not updating\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,11 +67,31 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
uint64_t exportCounter = data->getActiveExportCounter() * 1000;
|
||||
|
||||
// Clear hours between last update and now
|
||||
if(day.lastMeterReadTime > now) {
|
||||
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((importCounter > 0 && day.activeImport == 0) || now - day.lastMeterReadTime > 86400) {
|
||||
} 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;
|
||||
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;
|
||||
@@ -68,6 +103,10 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
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++) {
|
||||
setHourImport(i, 0);
|
||||
@@ -82,11 +121,31 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
|
||||
// Clear days between last update and now
|
||||
if(month.lastMeterReadTime > now) {
|
||||
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((importCounter > 0 && month.activeImport == 0) || now - month.lastMeterReadTime > 2682000) {
|
||||
} 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;
|
||||
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;
|
||||
@@ -98,6 +157,10 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
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++) {
|
||||
setDayImport(i, 0);
|
||||
@@ -112,20 +175,36 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
|
||||
if(data->getListType() < 3) {
|
||||
#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);
|
||||
@@ -135,6 +214,10 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
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;
|
||||
@@ -165,14 +248,26 @@ 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)
|
||||
#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;
|
||||
|
||||
@@ -182,6 +277,10 @@ 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);
|
||||
@@ -531,22 +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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
if(now < day.lastMeterReadTime) {
|
||||
return false;
|
||||
}
|
||||
if(now-day.lastMeterReadTime > 3600) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -560,26 +660,26 @@ bool AmsDataStorage::isDayHappy() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isMonthHappy() {
|
||||
bool AmsDataStorage::isMonthHappy(time_t now) {
|
||||
if(tz == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < month.lastMeterReadTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
if(tm.Day != last.Day) {
|
||||
// 25 hours, because of DST
|
||||
if(now-month.lastMeterReadTime > 90000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(now-month.lastMeterReadTime > 90100) {
|
||||
tmElements_t tm, last;
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
if(tm.Day != last.Day) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,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
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#define DATA_PARSE_OK 0
|
||||
#define DATA_PARSE_FAIL -1
|
||||
#define DATA_PARSE_INCOMPLETE -2
|
||||
#define DATA_PARSE_BOUNDRY_FLAG_MISSING -3
|
||||
#define DATA_PARSE_BOUNDARY_FLAG_MISSING -3
|
||||
#define DATA_PARSE_HEADER_CHECKSUM_ERROR -4
|
||||
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
|
||||
#define DATA_PARSE_INTERMEDIATE_SEGMENT -6
|
||||
|
||||
@@ -9,15 +9,19 @@
|
||||
|
||||
#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
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
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];
|
||||
|
||||
@@ -12,6 +12,5 @@
|
||||
|
||||
uint16_t crc16(const uint8_t* p, int len);
|
||||
uint16_t crc16_x25(const uint8_t* p, int len);
|
||||
uint16_t crc16_1021(const uint8_t* p, int len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,12 +19,10 @@ time_t decodeCosemDateTime(CosemDateTime timestamp) {
|
||||
tm.Minute = timestamp.minute;
|
||||
tm.Second = timestamp.second;
|
||||
|
||||
//Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status);
|
||||
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(timestamp.deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
time += deviation * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
@@ -9,27 +9,77 @@
|
||||
#include "hexutils.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
|
||||
// verified indicates that this data was encapsulated in something else, so we know this has the correct size etc
|
||||
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified, Print* debugger) {
|
||||
uint16_t lenBefore = ctx.length;
|
||||
uint16_t crcPos = 0;
|
||||
bool reachedEnd = verified;
|
||||
uint8_t lastByte = 0x00;
|
||||
for(int pos = 0; pos < ctx.length; pos++) {
|
||||
for(uint16_t pos = 0; pos < ctx.length; pos++) {
|
||||
uint8_t b = *(buf+pos);
|
||||
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
if(pos > 0 && b == '!' && lastByte == '\n') crcPos = pos+1;
|
||||
if(crcPos > 0 && b == '\n') reachedEnd = true;
|
||||
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
if(pos > 0 && b == '!') crcPos = pos+1;
|
||||
if(crcPos > 0 && b == 0x0A && lastByte == 0x0D) {
|
||||
reachedEnd = true;
|
||||
ctx.length = pos;
|
||||
break;
|
||||
}
|
||||
lastByte = b;
|
||||
}
|
||||
if(!reachedEnd) return DATA_PARSE_INCOMPLETE;
|
||||
buf[ctx.length+1] = '\0';
|
||||
if(crcPos > 0) {
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ int8_t GBTParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
GBTHeader* h = (GBTHeader*) (d);
|
||||
uint16_t sequence = ntohs(h->sequence);
|
||||
|
||||
if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
|
||||
if(sequence == 1) {
|
||||
if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ?
|
||||
|
||||
@@ -17,25 +17,34 @@ GCMParser::GCMParser(uint8_t *encryption_key, uint8_t *authentication_key) {
|
||||
memcpy(this->authentication_key, authentication_key, 16);
|
||||
}
|
||||
|
||||
int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx, bool hastag) {
|
||||
if(ctx.length < 12) return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
uint32_t headersize = 0;
|
||||
uint8_t* ptr = (uint8_t*) d;
|
||||
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
ptr++;
|
||||
if(hastag) {
|
||||
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
ptr++;
|
||||
headersize++;
|
||||
}
|
||||
// Encrypted APDU
|
||||
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
|
||||
|
||||
uint8_t systemTitleLength = *ptr;
|
||||
ptr++;
|
||||
headersize++;
|
||||
|
||||
uint8_t initialization_vector[12];
|
||||
memcpy(ctx.system_title, ptr, systemTitleLength);
|
||||
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
|
||||
memset(ctx.system_title, 0, 8);
|
||||
memset(initialization_vector, 0, 12);
|
||||
if(systemTitleLength > 0) {
|
||||
memcpy(ctx.system_title, ptr, systemTitleLength);
|
||||
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
|
||||
ptr += systemTitleLength;
|
||||
headersize += systemTitleLength;
|
||||
}
|
||||
|
||||
uint32_t len = 0;
|
||||
uint32_t headersize = 2 + systemTitleLength;
|
||||
ptr += systemTitleLength;
|
||||
if(((*ptr) & 0xFF) == 0x81) {
|
||||
// 1-byte payload length
|
||||
ptr++;
|
||||
|
||||
@@ -29,7 +29,7 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
// First and last byte should be HDLC_FLAG
|
||||
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
|
||||
// Verify FCS
|
||||
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
|
||||
@@ -19,7 +19,7 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
MbusHeader* mh = (MbusHeader*) d;
|
||||
if(mh->flag1 != MBUS_START || mh->flag2 != MBUS_START)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
|
||||
// First two bytes is 1-byte length value repeated. Only used for last segment
|
||||
if(mh->len1 != mh->len2)
|
||||
@@ -40,7 +40,7 @@ int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
MbusFooter* mf = (MbusFooter*) (d + len + headersize);
|
||||
if(mf->flag != MBUS_END)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
if(checksum(d + headersize, len) != mf->fcs)
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
|
||||
|
||||
@@ -33,22 +33,3 @@ uint16_t crc16 (const uint8_t *p, int len) {
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t crc16_1021(const uint8_t *p, int len) {
|
||||
uint32_t crc = 0x0000;
|
||||
for(int i = 0; i < len; i++) {
|
||||
int mask = 0x80;
|
||||
while(mask > 0) {
|
||||
crc <<= 1;
|
||||
if (p[i] & mask){
|
||||
crc |= 1;
|
||||
}
|
||||
mask>>=1;
|
||||
if (crc & 0x10000) {
|
||||
crc &= 0xffff;
|
||||
crc ^= 0x1021;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
143
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
143
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <Print.h>
|
||||
#include "HwTools.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include "esp_flash_partitions.h"
|
||||
#include "LittleFS.h"
|
||||
#include "WiFi.h"
|
||||
#include "HTTPClient.h"
|
||||
|
||||
#define AMS_PARTITION_TABLE_OFFSET 0x8000
|
||||
#define AMS_PARTITION_APP0_OFFSET 0x10000
|
||||
#define AMS_PARTITION_APP_SIZE 0x1D0000
|
||||
#define AMS_PARTITION_MIN_SPIFFS_SIZE 0x20000
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
|
||||
#define AMS_FLASH_SKETCH_SIZE 0xFEFF0
|
||||
#define AMS_FLASH_OTA_START AMS_FLASH_OTA_SIZE
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
|
||||
#define AMS_UPDATE_ERR_OK 0
|
||||
#define AMS_UPDATE_ERR_DETAILS 1
|
||||
#define AMS_UPDATE_ERR_FETCH 2
|
||||
#define AMS_UPDATE_ERR_ERASE 3
|
||||
#define AMS_UPDATE_ERR_WRITE 4
|
||||
#define AMS_UPDATE_ERR_READ 5
|
||||
#define AMS_UPDATE_ERR_MD5 6
|
||||
#define AMS_UPDATE_ERR_ACTIVATE 7
|
||||
#define AMS_UPDATE_ERR_REBOOT 64
|
||||
#define AMS_UPDATE_ERR_SUCCESS_SIGNAL 122
|
||||
#define AMS_UPDATE_ERR_SUCCESS_CONFIRMED 123
|
||||
|
||||
#define UPDATE_BUF_SIZE 4096
|
||||
|
||||
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
|
||||
};
|
||||
1267
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
1267
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "PriceService.h"
|
||||
#include "AmsFirmwareUpdater.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
@@ -21,12 +22,21 @@
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) {
|
||||
#else
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) {
|
||||
#endif
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
this->updater = updater;
|
||||
mqtt.dropOverflow(true);
|
||||
|
||||
pubTopic = String(mqttConfig.publishTopic);
|
||||
subTopic = String(mqttConfig.subscribeTopic);
|
||||
if(subTopic.isEmpty()) subTopic = pubTopic+"/command";
|
||||
};
|
||||
|
||||
void setCaVerification(bool);
|
||||
@@ -40,11 +50,13 @@ public:
|
||||
|
||||
virtual uint8_t getFormat() { return 0; };
|
||||
|
||||
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(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() {
|
||||
@@ -55,7 +67,11 @@ public:
|
||||
};
|
||||
|
||||
protected:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
MqttConfig mqttConfig;
|
||||
bool mqttConfigChanged = true;
|
||||
MQTTClient mqtt = MQTTClient(256);
|
||||
@@ -65,6 +81,13 @@ protected:
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
char* json;
|
||||
uint16_t BufferSize = 2048;
|
||||
uint64_t lastStateUpdate = 0;
|
||||
uint64_t lastSuccessfulLoop = 0;
|
||||
|
||||
String pubTopic;
|
||||
String subTopic;
|
||||
|
||||
AmsFirmwareUpdater* updater = NULL;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "FirmwareVersion.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "LittleFS.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
void AmsMqttHandler::setCaVerification(bool caVerification) {
|
||||
this->caVerification = caVerification;
|
||||
@@ -33,15 +34,18 @@ bool AmsMqttHandler::connect() {
|
||||
if(epoch < FirmwareVersion::BuildEpoch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool applySslConfiguration = mqttConfigChanged;
|
||||
if(mqttSecureClient == NULL) {
|
||||
mqttSecureClient = new WiFiClientSecure();
|
||||
#if defined(ESP8266)
|
||||
mqttSecureClient->setBufferSizes(512, 512);
|
||||
return false;
|
||||
#endif
|
||||
applySslConfiguration = true;
|
||||
}
|
||||
|
||||
if(mqttConfigChanged) {
|
||||
if(applySslConfiguration) {
|
||||
if(caVerification && LittleFS.begin()) {
|
||||
File file;
|
||||
|
||||
@@ -100,6 +104,8 @@ bool AmsMqttHandler::connect() {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -114,19 +120,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) {
|
||||
@@ -142,6 +161,10 @@ bool AmsMqttHandler::connect() {
|
||||
void AmsMqttHandler::disconnect() {
|
||||
mqtt.disconnect();
|
||||
mqtt.loop();
|
||||
if(mqttSecureClient != NULL) {
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
delay(10);
|
||||
yield();
|
||||
}
|
||||
@@ -155,7 +178,20 @@ bool AmsMqttHandler::connected() {
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::loop() {
|
||||
bool ret = mqtt.loop();
|
||||
uint64_t now = millis64();
|
||||
bool ret = mqtt.connected() && mqtt.loop();
|
||||
if(ret) {
|
||||
lastSuccessfulLoop = now;
|
||||
} else if(mqttConfig.rebootMinutes > 0) {
|
||||
if(now - lastSuccessfulLoop > (uint64_t) mqttConfig.rebootMinutes * 60000) {
|
||||
// Reboot the device if the MQTT connection is lost for too long
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("MQTT connection lost for over %d minutes, rebooting device\n"), mqttConfig.rebootMinutes);
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
delay(10);
|
||||
yield();
|
||||
#if defined(ESP32)
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#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"
|
||||
@@ -35,14 +37,14 @@
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
#define CC_BUF_SIZE 2048
|
||||
#define CC_BUF_SIZE 4096
|
||||
|
||||
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,\"utcOffset\":%d},\"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\"}";
|
||||
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;
|
||||
@@ -51,26 +53,43 @@ struct CloudData {
|
||||
|
||||
class CloudConnector {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
CloudConnector(RemoteDebug*);
|
||||
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, HwTools* hw, ResetDataContainer* rdc);
|
||||
#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 setTimezone(Timezone* tz);
|
||||
void forcePriceUpdate();
|
||||
void setConnectionHandler(ConnectionHandler* ch);
|
||||
String generateSeed();
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger = NULL;
|
||||
#else
|
||||
Stream* debugger = NULL;
|
||||
#endif
|
||||
HwTools* hw = NULL;
|
||||
ConnectionHandler* ch = NULL;
|
||||
ResetDataContainer* rdc = NULL;
|
||||
Timezone* tz = 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;
|
||||
|
||||
@@ -80,7 +99,10 @@ private:
|
||||
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;
|
||||
@@ -88,7 +110,6 @@ private:
|
||||
char* pers = "amsreader";
|
||||
|
||||
bool init();
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
|
||||
String meterManufacturer(uint8_t type) {
|
||||
switch(type) {
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
#include "esp32s3/rom/rtc.h"
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
CloudConnector::CloudConnector(RemoteDebug* debugger) {
|
||||
#else
|
||||
CloudConnector::CloudConnector(Stream* debugger) {
|
||||
#endif
|
||||
this->debugger = debugger;
|
||||
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
@@ -46,7 +50,7 @@ CloudConnector::CloudConnector(RemoteDebug* debugger) {
|
||||
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, HwTools* hw, ResetDataContainer* rdc) {
|
||||
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)) {
|
||||
@@ -59,8 +63,10 @@ bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig
|
||||
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;
|
||||
@@ -84,7 +90,10 @@ bool CloudConnector::init() {
|
||||
strcpy_P(config.hostname, PSTR("cloud.amsleser.no"));
|
||||
|
||||
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/public.key"), config.hostname);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(CloudConnector) Downloading public key from %s\n"), clearBuffer);
|
||||
#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);
|
||||
@@ -107,14 +116,11 @@ bool CloudConnector::init() {
|
||||
memset(clearBuffer, 0, CC_BUF_SIZE);
|
||||
snprintf(clearBuffer, CC_BUF_SIZE, pub.c_str());
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Cloud public key:\n%s\n"), clearBuffer);
|
||||
|
||||
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){
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("RSA public key OK\n"));
|
||||
rsa = mbedtls_pk_rsa(pk);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
mbedtls_entropy_init(&entropy);
|
||||
@@ -123,18 +129,37 @@ bool CloudConnector::init() {
|
||||
&entropy, (const unsigned char *) pers,
|
||||
strlen(pers));
|
||||
if(ret != 0) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("mbedtls_ctr_drbg_seed return code: %d\n"), ret);
|
||||
#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(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("RSA public key read error: ");
|
||||
#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(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("%s\n", clearBuffer);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf("%s\n", clearBuffer);
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(CloudConnector) Communication error, returned status: %d\n"), status);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(http.errorToString(status).c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http.getString().c_str());
|
||||
#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();
|
||||
}
|
||||
@@ -144,17 +169,44 @@ bool CloudConnector::init() {
|
||||
}
|
||||
|
||||
void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
if(!config.enabled) return;
|
||||
unsigned long now = millis();
|
||||
if(now-lastUpdate < config.interval*1000) return;
|
||||
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(CloudConnector) Client ID is not valid\n"));
|
||||
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(data.getListType() < 2) return;
|
||||
|
||||
if(!initialized && !init()) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
@@ -164,8 +216,13 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
int pos = 0;
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"id\":\"%s\""), uuid.c_str());
|
||||
if(!seed.isEmpty()) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"seed\":\"%s\""), seed.c_str());
|
||||
}
|
||||
|
||||
if(lastUpdate == 0) {
|
||||
bool sendData = true;
|
||||
if(sendFirst) {
|
||||
seed.clear();
|
||||
if(mainFuse > 0 && distributionSystem > 0) {
|
||||
int voltage = distributionSystem == 2 ? 400 : 230;
|
||||
if(data.isThreePhase()) {
|
||||
@@ -204,7 +261,7 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
boardType,
|
||||
rtc_get_reset_reason(0),
|
||||
rdc == NULL ? 0 : rdc->last_cause,
|
||||
tz == NULL ? 0 : (tz->toLocal(now)-now)/3600,
|
||||
timezone,
|
||||
data.getMeterType(),
|
||||
meterManufacturer(data.getMeterType()).c_str(),
|
||||
data.getMeterModel().c_str(),
|
||||
@@ -219,191 +276,355 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
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);
|
||||
|
||||
float vcc = 0.0;
|
||||
int rssi = 0;
|
||||
float temperature = -127;
|
||||
if(hw != NULL) {
|
||||
vcc = hw->getVcc();
|
||||
rssi = hw->getWifiRssi();
|
||||
temperature = hw->getTemperature();
|
||||
}
|
||||
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);
|
||||
|
||||
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
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
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"));
|
||||
}
|
||||
|
||||
uint8_t wifiStatus;
|
||||
if(rssi > -75) {
|
||||
wifiStatus = 1;
|
||||
} else if(rssi > -95) {
|
||||
wifiStatus = 2;
|
||||
} else {
|
||||
wifiStatus = 3;
|
||||
}
|
||||
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"));
|
||||
}
|
||||
|
||||
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());
|
||||
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 ? "" : ","
|
||||
);
|
||||
}
|
||||
}
|
||||
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());
|
||||
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;
|
||||
}
|
||||
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());
|
||||
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;
|
||||
}
|
||||
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("}"));
|
||||
}
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS,
|
||||
espStatus, 0,
|
||||
hanStatus, data.getLastError(),
|
||||
wifiStatus, 0,
|
||||
mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
|
||||
);
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
|
||||
|
||||
uint16_t crc = crc16((uint8_t*) clearBuffer, pos);
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"crc\":\"%04X\"}"), crc);
|
||||
|
||||
if(rsa == nullptr) return;
|
||||
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(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
|
||||
#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(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("%s\n"), clearBuffer);
|
||||
#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 = 100 * (rsa->len/128);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(CloudConnector) Sending %d bytes and maxlen %d\n", pos, maxlen);
|
||||
udp.beginPacket(config.hostname,7443);
|
||||
for(int i = 0; i < pos; i += maxlen) {
|
||||
int size = min(maxlen, pos-i);
|
||||
int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, size, (unsigned char*) (clearBuffer+i), encryptedBuffer);
|
||||
if(ret == 0) {
|
||||
udp.write(encryptedBuffer, rsa->len);
|
||||
delay(1);
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
|
||||
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("%s\n"), clearBuffer);
|
||||
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);
|
||||
}
|
||||
}
|
||||
udp.endPacket();
|
||||
|
||||
lastUpdate = now;
|
||||
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::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
void CloudConnector::forcePriceUpdate() {
|
||||
lastPriceConfig = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::setConnectionHandler(ConnectionHandler* ch) {
|
||||
this->ch = ch;
|
||||
}
|
||||
|
||||
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::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;
|
||||
}
|
||||
|
||||
@@ -9,13 +9,19 @@
|
||||
|
||||
#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);
|
||||
@@ -33,7 +39,11 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
NetworkConfig config;
|
||||
|
||||
bool connected = false;
|
||||
|
||||
@@ -9,12 +9,18 @@
|
||||
|
||||
#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);
|
||||
@@ -32,7 +38,11 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
NetworkConfig config;
|
||||
|
||||
DNSServer dnsServer;
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
|
||||
#include "ConnectionHandler.h"
|
||||
#include <Arduino.h>
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
|
||||
#define CONNECTION_TIMEOUT 30000
|
||||
#define RECONNECT_TIMEOUT 5000
|
||||
@@ -20,7 +22,11 @@ esp_err_t set_esp_interface_ip(esp_interface_t interface, IPAddress local_ip=INA
|
||||
|
||||
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);
|
||||
@@ -38,7 +44,11 @@ public:
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
NetworkConfig config;
|
||||
bool busPowered = false;
|
||||
bool firstConnect = true;
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
#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;
|
||||
}
|
||||
@@ -56,11 +60,21 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
|
||||
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) {
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("LilyGO T-ETH-Lite\n"));
|
||||
return false; // TODO
|
||||
} else {
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Board type %d incompatible with ETH\n"), sys.boardType);
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -69,7 +83,10 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
|
||||
digitalWrite(ethEnablePin, 1);
|
||||
}
|
||||
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Connecting to Ethernet\n"));
|
||||
#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)
|
||||
@@ -98,7 +115,10 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Unable to start Ethernet\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start Ethernet\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -106,7 +126,10 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
|
||||
}
|
||||
|
||||
void EthernetConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
}
|
||||
|
||||
bool EthernetConnectionHandler::isConnected() {
|
||||
@@ -118,22 +141,35 @@ void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
connected = true;
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
#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(debugger->isActive(RemoteDebug::INFO)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("IP: %s\n"), getIP().toString().c_str());
|
||||
debugger->printf_P(PSTR("GW: %s\n"), getGateway().toString().c_str());
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
IPAddress dns4 = getDns(i);
|
||||
if(!dns4.isAny()) debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
|
||||
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(debugger->isActive(RemoteDebug::INFO)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
IPv6Address ipv6 = getIPv6();
|
||||
if(ipv6 == IPv6Address()) {
|
||||
// No IP
|
||||
@@ -154,7 +190,10 @@ void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t
|
||||
}
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
connected = false;
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Ethernet was disconnected!\n"));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -42,22 +46,37 @@ void WiFiAccessPointConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEvent
|
||||
IPAddress stationIP;
|
||||
switch(event) {
|
||||
case ARDUINO_EVENT_WIFI_AP_START:
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("WiFi access point started with SSID %s\n"), config.ssid);
|
||||
#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(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("WiFi access point stopped!\n"));
|
||||
#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(debugger->isActive(RemoteDebug::INFO)) 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]);
|
||||
#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(debugger->isActive(RemoteDebug::INFO)) 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]);
|
||||
#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(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Client was assigned IP %s\n"), stationIP.toString().c_str());
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
#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;
|
||||
}
|
||||
@@ -28,14 +32,20 @@ bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys
|
||||
}
|
||||
|
||||
if(WiFi.getMode() != WIFI_OFF) {
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Not connected to WiFi, closing resources\n"));
|
||||
#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 (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Connecting to WiFi network: %s\n"), config.ssid);
|
||||
#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
|
||||
@@ -90,6 +100,7 @@ bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys
|
||||
}
|
||||
#endif
|
||||
WiFi.setAutoReconnect(true);
|
||||
this->config = config;
|
||||
#if defined(ESP32)
|
||||
if(begin(config.ssid, config.psk)) {
|
||||
#else
|
||||
@@ -110,9 +121,11 @@ bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys
|
||||
}
|
||||
yield();
|
||||
} else {
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Unable to start WiFi\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start WiFi\n"));
|
||||
}
|
||||
this->config = config;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -164,7 +177,7 @@ wl_status_t WiFiClientConnectionHandler::begin(const char* ssid, const char* pas
|
||||
}
|
||||
}
|
||||
|
||||
if(strlen(config.ip) > 0){
|
||||
if(strlen(config.ip) == 0){
|
||||
if(set_esp_interface_ip(ESP_IF_WIFI_STA) != ESP_OK) {
|
||||
return WL_CONNECT_FAILED;
|
||||
}
|
||||
@@ -208,7 +221,10 @@ void WiFiClientConnectionHandler::wifi_sta_config(wifi_config_t * wifi_config, c
|
||||
#endif
|
||||
|
||||
void WiFiClientConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
#if defined(ESP8266)
|
||||
WiFiClient::stopAll();
|
||||
#endif
|
||||
@@ -235,7 +251,10 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to WiFi!\n"));
|
||||
#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"));
|
||||
}
|
||||
@@ -246,7 +265,10 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
|
||||
wifi_phy_mode_t phyMode;
|
||||
if(esp_wifi_sta_get_negotiated_phymode(&phyMode) == ESP_OK) {
|
||||
if(phyMode > WIFI_PHY_MODE_11B) {
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("WiFi supports better rates than 802.11b, disabling\n"));
|
||||
#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;
|
||||
@@ -284,18 +306,28 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
|
||||
#endif
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP: {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("IP: %s\n"), getIP().toString().c_str());
|
||||
debugger->printf_P(PSTR("GW: %s\n"), getGateway().toString().c_str());
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
IPAddress dns4 = getDns(i);
|
||||
if(!dns4.isAny()) debugger->printf_P(PSTR("DNS: %s\n"), dns4.toString().c_str());
|
||||
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(debugger->isActive(RemoteDebug::INFO)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
IPv6Address ipv6 = getIPv6();
|
||||
if(ipv6 == IPv6Address()) {
|
||||
// No IP
|
||||
@@ -322,7 +354,10 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
|
||||
break;
|
||||
default:
|
||||
if(strlen(descr) > 0) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("WiFi disconnected, reason %s\n"), descr);
|
||||
}
|
||||
disconnect(RECONNECT_TIMEOUT);
|
||||
|
||||
@@ -12,9 +12,15 @@
|
||||
|
||||
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;
|
||||
};
|
||||
#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(PriceService*);
|
||||
@@ -25,6 +31,10 @@ public:
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
void setDomoticzConfig(DomoticzConfig config) {
|
||||
this->config = config;
|
||||
}
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
double energy = 0.0;
|
||||
|
||||
@@ -6,16 +6,29 @@
|
||||
|
||||
#include "DomoticzMqttHandler.h"
|
||||
#include "json/domoticz_json.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
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
|
||||
@@ -25,12 +38,12 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -41,7 +54,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
|
||||
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
|
||||
@@ -52,7 +65,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
|
||||
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
|
||||
@@ -63,7 +76,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
|
||||
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
|
||||
|
||||
@@ -80,7 +80,11 @@ 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 setPriceService(PriceService *ps);
|
||||
void setTimezone(Timezone*);
|
||||
@@ -123,7 +127,11 @@ public:
|
||||
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;
|
||||
PriceService *ps = NULL;
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
#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) {
|
||||
@@ -82,8 +86,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
init = true;
|
||||
}
|
||||
|
||||
float price = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
|
||||
if(!initPrice && price != PRICE_NO_VALUE) {
|
||||
float importPrice = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
|
||||
if(!initPrice && importPrice != PRICE_NO_VALUE) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
@@ -155,8 +159,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhi > 0) {
|
||||
this->realtimeData->use += kwhi;
|
||||
if(price != PRICE_NO_VALUE) {
|
||||
float cost = price * kwhi;
|
||||
if(importPrice != PRICE_NO_VALUE) {
|
||||
float cost = importPrice * kwhi;
|
||||
this->realtimeData->costHour += cost;
|
||||
this->realtimeData->costDay += cost;
|
||||
}
|
||||
@@ -169,8 +173,9 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhe > 0) {
|
||||
this->realtimeData->produce += kwhe;
|
||||
if(price != PRICE_NO_VALUE) {
|
||||
float income = price * kwhe;
|
||||
float exportPrice = getPriceForHour(PRICE_DIRECTION_EXPORT, 0);
|
||||
if(exportPrice != PRICE_NO_VALUE) {
|
||||
float income = exportPrice * kwhe;
|
||||
this->realtimeData->incomeHour += income;
|
||||
this->realtimeData->incomeDay += income;
|
||||
}
|
||||
|
||||
@@ -10,72 +10,35 @@
|
||||
#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, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
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 deviceName;
|
||||
String deviceModel;
|
||||
@@ -84,12 +47,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;
|
||||
|
||||
@@ -113,6 +78,7 @@ private:
|
||||
void publishTemperatureSensor(uint8_t index, String id);
|
||||
void publishPriceSensors(PriceService* ps);
|
||||
void publishSystemSensors();
|
||||
void publishThresholdSensors();
|
||||
|
||||
String boardTypeToString(uint8_t b) {
|
||||
switch(b) {
|
||||
|
||||
@@ -20,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;
|
||||
@@ -42,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;
|
||||
@@ -80,8 +82,8 @@ const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
|
||||
|
||||
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"},
|
||||
@@ -101,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 = {
|
||||
|
||||
@@ -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,30 +2,31 @@
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"P1" : %d,
|
||||
"P2" : %d,
|
||||
"P3" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"PO1" : %d,
|
||||
"PO2" : %d,
|
||||
"PO3" : %d,
|
||||
"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
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -13,38 +13,113 @@
|
||||
#include "json/ha3_json.h"
|
||||
#include "json/ha4_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, PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config) {
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = fInit = false;
|
||||
|
||||
if(strlen(config.discoveryNameTag) > 0) {
|
||||
snprintf_P(json, 128, PSTR("AMS reader (%s)"), config.discoveryNameTag);
|
||||
deviceName = String(json);
|
||||
snprintf_P(json, 128, PSTR("[%s] "), config.discoveryNameTag);
|
||||
sensorNamePrefix = String(json);
|
||||
} else {
|
||||
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);
|
||||
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() >= 3 && !data.isCounterEstimated()) { // publish energy counts
|
||||
publishList3(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if(data->getListType() == 1) { // publish power counts
|
||||
publishList1(data, ea);
|
||||
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);
|
||||
} 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);
|
||||
} 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);
|
||||
publishRealtime(&data, ea, ps);
|
||||
mqtt.loop();
|
||||
}
|
||||
loop();
|
||||
@@ -53,13 +128,31 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
|
||||
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(),
|
||||
@@ -73,27 +166,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(),
|
||||
@@ -114,18 +235,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()
|
||||
data->getL3ActiveExportCounter(),
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
@@ -137,6 +259,7 @@ String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
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;
|
||||
@@ -144,7 +267,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(),
|
||||
@@ -161,25 +284,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
|
||||
);
|
||||
@@ -187,15 +335,26 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
|
||||
publishTemperatureSensor(i+1, id);
|
||||
}
|
||||
}
|
||||
char* pos = buf+strlen(buf);
|
||||
snprintf_P(count == 0 ? pos : pos-1, 8, PSTR("}}"));
|
||||
bool ret = mqtt.publish(topic + "/temperatures", buf);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}"));
|
||||
|
||||
time_t now = time(nullptr);
|
||||
char pt[24];
|
||||
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(PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
if(pubTopic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
return false;
|
||||
@@ -288,7 +447,7 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
|
||||
}
|
||||
}
|
||||
|
||||
snprintf_P(json+pos, BufferSize-pos, PSTR("\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}}"),
|
||||
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,
|
||||
@@ -296,28 +455,57 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
|
||||
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, PriceService* ps, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
if(pubTopic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
publishSystemSensors();
|
||||
if(hw->getTemperature() > -50) publishTemperatureSensor(0, "");
|
||||
|
||||
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\"}"),
|
||||
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;
|
||||
}
|
||||
@@ -350,7 +538,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();
|
||||
}
|
||||
|
||||
@@ -520,6 +708,21 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
|
||||
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() {
|
||||
@@ -530,6 +733,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;
|
||||
}
|
||||
@@ -538,13 +762,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ struct AdcConfig {
|
||||
class HwTools {
|
||||
public:
|
||||
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
|
||||
void setup(GpioConfig*);
|
||||
void setup(SystemConfig* sys, GpioConfig* gpio);
|
||||
float getVcc();
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorData* getTempSensorData(uint8_t);
|
||||
@@ -56,15 +56,24 @@ public:
|
||||
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.25; // 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;
|
||||
bool tempSensorInit;
|
||||
OneWire *oneWire = NULL;
|
||||
DallasTemperature *sensorApi = NULL;
|
||||
|
||||
@@ -22,6 +22,9 @@ bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterC
|
||||
gpioConfig.vccResistorGnd = 22;
|
||||
gpioConfig.vccResistorVcc = 33;
|
||||
gpioConfig.ledDisablePin = 6;
|
||||
gpioConfig.vccBootLimit = 0;
|
||||
gpioConfig.vccOffset = 0;
|
||||
gpioConfig.vccMultiplier = 0;
|
||||
return true;
|
||||
case 51: // Wemos S2 mini
|
||||
gpioConfig.ledPin = 15;
|
||||
@@ -75,6 +78,9 @@ bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterC
|
||||
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;
|
||||
@@ -144,8 +150,8 @@ bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterC
|
||||
return false;
|
||||
}
|
||||
|
||||
void HwTools::setup(GpioConfig* config) {
|
||||
this->config = config;
|
||||
void HwTools::setup(SystemConfig* sys, GpioConfig* config) {
|
||||
this->boardType = sys->boardType;
|
||||
this->tempSensorInit = false;
|
||||
if(sensorApi != NULL)
|
||||
delete sensorApi;
|
||||
@@ -153,8 +159,9 @@ void HwTools::setup(GpioConfig* config) {
|
||||
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)
|
||||
@@ -196,47 +203,63 @@ void HwTools::setup(GpioConfig* config) {
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -362,7 +385,7 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
||||
|
||||
float HwTools::getVcc() {
|
||||
float volts = 0.0;
|
||||
if(config->vccPin != 0xFF) {
|
||||
if(vccPin != 0xFF) {
|
||||
#if defined(ESP32)
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
uint32_t x = 0;
|
||||
@@ -385,32 +408,35 @@ float HwTools::getVcc() {
|
||||
} else {
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
x += analogRead(vccPin);
|
||||
}
|
||||
volts = (x * 3.3) / 10.0 / analogRange;
|
||||
}
|
||||
#else
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
x += analogRead(vccPin);
|
||||
}
|
||||
volts = (x * 3.3) / 10.0 / analogRange;
|
||||
#endif
|
||||
} else {
|
||||
}
|
||||
if(volts == 0.0) {
|
||||
#if defined(ESP8266)
|
||||
volts = ESP.getVcc() / 1024.0;
|
||||
#else
|
||||
return 0.0;
|
||||
#endif
|
||||
}
|
||||
if(volts == 0.0) return 0.0;
|
||||
|
||||
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
|
||||
volts *= ((float) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
|
||||
}
|
||||
|
||||
|
||||
float vccOffset = config->vccOffset / 100.0;
|
||||
float vccMultiplier = config->vccMultiplier / 1000.0;
|
||||
return vccOffset + (volts > 0.0 ? volts * vccMultiplier : 0.0);
|
||||
if(vccGnd_r > 0 && vccVcc_r > 0)
|
||||
volts *= ((float) (vccGnd_r + vccVcc_r) / vccGnd_r);
|
||||
if(vccOffset != 0.0)
|
||||
volts += vccOffset;
|
||||
if(vccMultiplier != 0.0)
|
||||
volts *= vccMultiplier;
|
||||
|
||||
return volts;
|
||||
}
|
||||
|
||||
uint8_t HwTools::getTempSensorCount() {
|
||||
@@ -425,9 +451,9 @@ TempSensorData* HwTools::getTempSensorData(uint8_t i) {
|
||||
}
|
||||
|
||||
bool HwTools::updateTemperatures() {
|
||||
if(config->tempSensorPin != 0xFF) {
|
||||
if(tempPin != 0xFF) {
|
||||
if(!tempSensorInit) {
|
||||
oneWire = new OneWire(config->tempSensorPin);
|
||||
oneWire = new OneWire(tempPin);
|
||||
sensorApi = new DallasTemperature(this->oneWire);
|
||||
sensorApi->begin();
|
||||
delay(100);
|
||||
@@ -513,9 +539,9 @@ float HwTools::getTemperature() {
|
||||
return c == 0 ? DEVICE_DISCONNECTED_C : ret/c;
|
||||
}
|
||||
float HwTools::getTemperatureAnalog() {
|
||||
if(config->tempAnalogSensorPin != 0xFF) {
|
||||
if(atempPin != 0xFF) {
|
||||
float adcCalibrationFactor = 1.06587;
|
||||
int volts = ((float) analogRead(config->tempAnalogSensorPin) / analogRange) * 3.3;
|
||||
int volts = ((float) analogRead(atempPin) / analogRange) * 3.3;
|
||||
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
|
||||
}
|
||||
return DEVICE_DISCONNECTED_C;
|
||||
@@ -529,42 +555,42 @@ int HwTools::getWifiRssi() {
|
||||
void HwTools::setBootSuccessful(bool value) {
|
||||
if(bootSuccessful && value) return;
|
||||
bootSuccessful = value;
|
||||
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
|
||||
switch(config->ledBehaviour) {
|
||||
if(ledDisablePin > 0 && ledDisablePin < 40) {
|
||||
switch(ledBehaviour) {
|
||||
case LED_BEHAVIOUR_ERROR_ONLY:
|
||||
case LED_BEHAVIOUR_OFF:
|
||||
digitalWrite(config->ledDisablePin, LOW);
|
||||
digitalWrite(ledDisablePin, LOW);
|
||||
break;
|
||||
case LED_BEHAVIOUR_BOOT:
|
||||
if(bootSuccessful) {
|
||||
digitalWrite(config->ledDisablePin, LOW);
|
||||
digitalWrite(ledDisablePin, LOW);
|
||||
} else {
|
||||
digitalWrite(config->ledDisablePin, HIGH);
|
||||
digitalWrite(ledDisablePin, HIGH);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
digitalWrite(config->ledDisablePin, HIGH);
|
||||
digitalWrite(ledDisablePin, HIGH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HwTools::ledOn(uint8_t color) {
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_OFF) return false;
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
|
||||
if(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,8 +607,8 @@ bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
|
||||
bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
switch(color) {
|
||||
case LED_INTERNAL: {
|
||||
if(config->ledPin != 0xFF) {
|
||||
digitalWrite(config->ledPin, state);
|
||||
if(ledPin != 0xFF) {
|
||||
digitalWrite(ledPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -590,8 +616,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_RED: {
|
||||
if(config->ledPinRed != 0xFF) {
|
||||
digitalWrite(config->ledPinRed, state);
|
||||
if(redPin != 0xFF) {
|
||||
digitalWrite(redPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -599,8 +625,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_GREEN: {
|
||||
if(config->ledPinGreen != 0xFF) {
|
||||
digitalWrite(config->ledPinGreen, state);
|
||||
if(greenPin != 0xFF) {
|
||||
digitalWrite(greenPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -608,8 +634,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_BLUE: {
|
||||
if(config->ledPinBlue != 0xFF) {
|
||||
digitalWrite(config->ledPinBlue, state);
|
||||
if(bluePin != 0xFF) {
|
||||
digitalWrite(bluePin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -617,9 +643,9 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
break;
|
||||
}
|
||||
case LED_YELLOW: {
|
||||
if(config->ledPinRed != 0xFF && config->ledPinGreen != 0xFF) {
|
||||
digitalWrite(config->ledPinRed, state);
|
||||
digitalWrite(config->ledPinGreen, state);
|
||||
if(redPin != 0xFF && greenPin != 0xFF) {
|
||||
digitalWrite(redPin, state);
|
||||
digitalWrite(greenPin, state);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@@ -629,3 +655,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;
|
||||
}
|
||||
@@ -11,14 +11,21 @@
|
||||
|
||||
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;
|
||||
};
|
||||
#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(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
bool publishFirmware();
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
|
||||
@@ -9,31 +9,39 @@
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
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);
|
||||
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);
|
||||
} else if(data.getListType() == 2) {
|
||||
ret = publishList2(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data->getListType() == 3) {
|
||||
ret = publishList3(data, ea);
|
||||
} else if(data.getListType() == 3) {
|
||||
ret = publishList3(&data, ea);
|
||||
mqtt.loop();
|
||||
} else if(data->getListType() == 4) {
|
||||
ret = publishList4(data, ea);
|
||||
} else if(data.getListType() == 4) {
|
||||
ret = publishList4(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
loop();
|
||||
@@ -85,7 +93,8 @@ bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
}
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"P\":%d"), data->getActiveImportPower());
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos] = '}';
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list1"), mqttConfig.publishTopic);
|
||||
@@ -116,7 +125,8 @@ bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getL3Voltage()
|
||||
);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos] = '}';
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list2"), mqttConfig.publishTopic);
|
||||
@@ -152,7 +162,8 @@ bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos] = '}';
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list3"), mqttConfig.publishTopic);
|
||||
@@ -204,7 +215,8 @@ bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
pos += appendJsonFooter(ea, pos);
|
||||
json[pos] = '}';
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/list4"), mqttConfig.publishTopic);
|
||||
@@ -245,7 +257,8 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
bool ret = false;
|
||||
json[pos-1] = '}';
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
json[pos] = '}';
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
}
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
char topic[192];
|
||||
@@ -371,7 +384,8 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
ts6hr
|
||||
);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
json[pos] = '}';
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
}
|
||||
bool ret = false;
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
@@ -418,5 +432,26 @@ bool JsonMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void JsonMqttHandler::onMessage(String &topic, String &payload) {
|
||||
bool JsonMqttHandler::publishFirmware() {
|
||||
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)
|
||||
);
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/firmware"), mqttConfig.publishTopic);
|
||||
bool ret = mqtt.publish(topic, json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void JsonMqttHandler::onMessage(String &topic, String &payload) {
|
||||
if(topic.equals(subTopic)) {
|
||||
if(payload.equals("fwupgrade")) {
|
||||
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
|
||||
updater->setTargetVersion(updater->getNextVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
|
||||
#define NOVALUE 0xFFFFFFFF
|
||||
|
||||
@@ -21,7 +24,11 @@ struct AmsOctetTimestamp {
|
||||
|
||||
class IEC6205675 : public AmsData {
|
||||
public:
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, RemoteDebug* debugger);
|
||||
#else
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, Stream* debugger);
|
||||
#endif
|
||||
|
||||
private:
|
||||
CosemData* getCosemDataAt(uint8_t index, const char* ptr);
|
||||
@@ -31,6 +38,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 };
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IMPULSEAMSDATA_H
|
||||
#define _IMPULSEAMSDATA_H
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct LngHeader {
|
||||
uint8_t tag;
|
||||
@@ -31,7 +30,7 @@ struct LngObisDescriptor {
|
||||
|
||||
class LNG : public AmsData {
|
||||
public:
|
||||
LNG(AmsData& meterState, 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);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct Lng2Data_3p {
|
||||
CosemBasic header;
|
||||
@@ -33,7 +32,7 @@ struct Lng2Data_3p {
|
||||
|
||||
class LNG2 : public AmsData {
|
||||
public:
|
||||
LNG2(AmsData& meterState, 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);
|
||||
@@ -8,7 +8,9 @@
|
||||
#define _METERCOMMUNICATOR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
@@ -20,6 +22,7 @@ public:
|
||||
virtual AmsData* getData(AmsData& meterState);
|
||||
virtual int getLastError();
|
||||
virtual bool isConfigChanged();
|
||||
virtual void ackConfigChanged();
|
||||
virtual void getCurrentConfig(MeterConfig& meterConfig);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#define _PASSIVEMETERCOMMUNICATOR_H
|
||||
|
||||
#include "MeterCommunicator.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParsers.h"
|
||||
#include "Timezone.h"
|
||||
@@ -18,14 +20,21 @@
|
||||
#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*);
|
||||
|
||||
@@ -33,7 +42,11 @@ public:
|
||||
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;
|
||||
@@ -49,12 +62,14 @@ protected:
|
||||
HardwareSerial *hwSerial = NULL;
|
||||
uint8_t rxBufferErrors = 0;
|
||||
|
||||
bool autodetect = false, validDataReceived = false;
|
||||
bool autodetect = false;
|
||||
uint8_t validDataReceived = 0;
|
||||
unsigned long meterAutodetectLastChange = 0;
|
||||
uint8_t meterAutoIndex = 0;
|
||||
uint32_t bauds[6];
|
||||
uint8_t parities[6];
|
||||
bool inverts[6];
|
||||
long rate = 10000;
|
||||
uint32_t autodetectBaud = 0;
|
||||
uint8_t autodetectParity = 11; // 8E1
|
||||
bool autodetectInvert = false;
|
||||
uint8_t autodetectCount = 0;
|
||||
|
||||
bool dataAvailable = false;
|
||||
int len = 0;
|
||||
@@ -73,11 +88,11 @@ protected:
|
||||
DLMSParser *dlmsParser = NULL;
|
||||
DSMRParser *dsmrParser = NULL;
|
||||
|
||||
void setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert);
|
||||
void setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive = true);
|
||||
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
void printHanReadError(int pos);
|
||||
void handleAutodetect(unsigned long now);
|
||||
uint8_t getNextParity(uint8_t parityOrdinal);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -8,25 +8,36 @@
|
||||
#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;
|
||||
@@ -11,6 +11,8 @@ IEC6205621::IEC6205621(const char* p, Timezone* tz, MeterConfig* meterConfig) {
|
||||
if(strlen(p) < 16)
|
||||
return;
|
||||
|
||||
this->packageTimestamp = time(nullptr);
|
||||
|
||||
String payload(p+1);
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
@@ -59,15 +61,44 @@ IEC6205621::IEC6205621(const char* p, Timezone* tz, MeterConfig* meterConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
tmElements_t tm { 0, 0, 0, 0, 0, 0, 0 };
|
||||
String timestamp = extract(payload, F("1.0.0"));
|
||||
if(timestamp.length() > 10) {
|
||||
tmElements_t tm;
|
||||
tm.Year = (timestamp.substring(0,2).toInt() + 2000) - 1970;
|
||||
tm.Month = timestamp.substring(4,6).toInt();
|
||||
tm.Day = timestamp.substring(2,4).toInt();
|
||||
tm.Hour = timestamp.substring(6,8).toInt();
|
||||
tm.Minute = timestamp.substring(8,10).toInt();
|
||||
tm.Second = timestamp.substring(10,12).toInt();
|
||||
if(timestamp.length() == 13) { // yyMMddHHmmssX
|
||||
char x = timestamp.charAt(12);
|
||||
if(x == 'S' || x == 'W') {
|
||||
tm.Year = (timestamp.substring(0,2).toInt() + 2000) - 1970;
|
||||
tm.Month = timestamp.substring(2,4).toInt();
|
||||
tm.Day = timestamp.substring(4,6).toInt();
|
||||
tm.Hour = timestamp.substring(6,8).toInt();
|
||||
tm.Minute = timestamp.substring(8,10).toInt();
|
||||
tm.Second = timestamp.substring(10,12).toInt();
|
||||
}
|
||||
} else if(timestamp.length() == 17) { // yyyyMMdd HH:mm:ss
|
||||
char x = timestamp.charAt(11);
|
||||
char y = timestamp.charAt(14);
|
||||
if(x == ':' && y == ':') {
|
||||
tm.Year = (timestamp.substring(0,4).toInt()) - 1970;
|
||||
tm.Month = timestamp.substring(4,6).toInt();
|
||||
tm.Day = timestamp.substring(6,8).toInt();
|
||||
tm.Hour = timestamp.substring(9,11).toInt();
|
||||
tm.Minute = timestamp.substring(12,14).toInt();
|
||||
tm.Second = timestamp.substring(15,17).toInt();
|
||||
}
|
||||
} else if(timestamp.length() == 19) { // yyyy-MM-dd HH:mm:ss
|
||||
char x = timestamp.charAt(4);
|
||||
char y = timestamp.charAt(13);
|
||||
if(x == '-' && y == ':') {
|
||||
tm.Year = (timestamp.substring(0,4).toInt()) - 1970;
|
||||
tm.Month = timestamp.substring(5,7).toInt();
|
||||
tm.Day = timestamp.substring(8,10).toInt();
|
||||
tm.Hour = timestamp.substring(11,13).toInt();
|
||||
tm.Minute = timestamp.substring(14,16).toInt();
|
||||
tm.Second = timestamp.substring(17,19).toInt();
|
||||
}
|
||||
} else {
|
||||
meterTimestamp = 0;
|
||||
}
|
||||
if(tm.Year > 0) {
|
||||
meterTimestamp = makeTime(tm);
|
||||
if(tz != NULL) meterTimestamp = tz->toUTC(meterTimestamp);
|
||||
}
|
||||
@@ -9,8 +9,13 @@
|
||||
#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) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, RemoteDebug* debugger) {
|
||||
#else
|
||||
IEC6205675::IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, Stream* debugger) {
|
||||
#endif
|
||||
float val;
|
||||
char str[64];
|
||||
|
||||
@@ -18,14 +23,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);
|
||||
@@ -140,83 +151,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() > 4 ? state.getListType() : 4;
|
||||
|
||||
// 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() > 4 ? state.getListType() : 4;
|
||||
|
||||
// 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;
|
||||
@@ -239,20 +638,6 @@ 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) {
|
||||
this->packageTimestamp = this->packageTimestamp - 3600;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t str_len = 0;
|
||||
str_len = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
|
||||
@@ -354,8 +739,14 @@ 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;
|
||||
int16_t deviation = ntohs(amst->dt.deviation);
|
||||
if(deviation < -720 || deviation > 720) { // Deviation not specified, adjust from localtime to UTC
|
||||
meterTimestamp = tz.toUTC(ts);
|
||||
if(ctx.timestamp > 0) {
|
||||
this->packageTimestamp = tz.toUTC(ctx.timestamp);
|
||||
}
|
||||
} else if(meterType == AmsTypeAidon) {
|
||||
meterTimestamp = ts - 3600; // 21.09.24, the clock is now correct
|
||||
} else {
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
@@ -501,6 +892,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;
|
||||
@@ -522,6 +929,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;
|
||||
@@ -535,6 +948,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
threePhase = true;
|
||||
}
|
||||
}
|
||||
meterId.trim();
|
||||
}
|
||||
|
||||
CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ImpulseAmsData.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "ntohll.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
|
||||
LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
|
||||
LngHeader* h = (LngHeader*) payload;
|
||||
if(h->tag == CosemTypeStructure && h->arrayTag == CosemTypeArray) {
|
||||
apply(meterState);
|
||||
@@ -42,33 +42,75 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
|
||||
switch(descriptor->obis[2]) {
|
||||
case 1:
|
||||
o170 = getNumber(item);
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
o170 = o170 > 0 ? o170 * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
o270 = getNumber(item);
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
o270 = o270 > 0 ? o270 * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportPower = getNumber(item);
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportPower = getNumber(item);
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 21:
|
||||
l1activeImportPower = getNumber(item);
|
||||
listType = listType >= 4 ? listType : 4;
|
||||
break;
|
||||
case 41:
|
||||
l2activeImportPower = getNumber(item);
|
||||
listType = listType >= 4 ? listType : 4;
|
||||
break;
|
||||
case 61:
|
||||
l3activeImportPower = getNumber(item);
|
||||
listType = listType >= 4 ? listType : 4;
|
||||
break;
|
||||
case 31:
|
||||
l1current = getNumber(item) / 100.0;
|
||||
if(meterConfig->amperageMultiplier > 0) {
|
||||
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 51:
|
||||
l2current = getNumber(item) / 100.0;
|
||||
if(meterConfig->amperageMultiplier > 0) {
|
||||
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 71:
|
||||
l3current = getNumber(item) / 100.0;
|
||||
if(meterConfig->amperageMultiplier > 0) {
|
||||
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 32:
|
||||
l1voltage = getNumber(item) / 10.0;
|
||||
if(meterConfig->voltageMultiplier > 0) {
|
||||
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 52:
|
||||
l2voltage = getNumber(item) / 10.0;
|
||||
if(meterConfig->voltageMultiplier > 0) {
|
||||
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 72:
|
||||
l3voltage = getNumber(item) / 10.0;
|
||||
if(meterConfig->voltageMultiplier > 0) {
|
||||
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -79,30 +121,54 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
|
||||
case 1:
|
||||
o180 = getNumber(item);
|
||||
activeImportCounter = o180 / 1000.0;
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
o280 = getNumber(item);
|
||||
activeExportCounter = o280 / 1000.0;
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
o380 = getNumber(item);
|
||||
reactiveImportCounter = o380 / 1000.0;
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
o480 = getNumber(item);
|
||||
reactiveExportCounter = o480 / 1000.0;
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
o580 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o580 = o580 > 0 ? o580 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
o680 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o680 = o680 > 0 ? o680 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
o780 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o780 = o780 > 0 ? o780 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
o880 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o880 = o880 > 0 ? o880 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
@@ -110,9 +176,15 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
|
||||
switch(descriptor->obis[2]) {
|
||||
case 1:
|
||||
o181 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o181 = o181 > 0 ? o181 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
o281 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o281 = o281 > 0 ? o281 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
@@ -120,9 +192,15 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
|
||||
switch(descriptor->obis[2]) {
|
||||
case 1:
|
||||
o182 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o182 = o182 > 0 ? o182 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
o282 = getNumber(item);
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
o282 = o282 > 0 ? o282 * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -182,6 +260,11 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
lastUpdateMillis = millis64();
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
if(!threePhase)
|
||||
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "LNG2.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
LNG2::LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
|
||||
LNG2::LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
|
||||
CosemBasic* h = (CosemBasic*) payload;
|
||||
if(h->length == 0x0e) {
|
||||
apply(meterState);
|
||||
@@ -9,52 +9,55 @@
|
||||
#include "IEC6205621.h"
|
||||
#include "LNG.h"
|
||||
#include "LNG2.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <driver/uart.h>
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PassiveMeterCommunicator::PassiveMeterCommunicator(RemoteDebug* debugger) {
|
||||
this->debugger = debugger;
|
||||
bauds[0] = 2400;
|
||||
parities[0] = 11;
|
||||
inverts[0] = false;
|
||||
|
||||
bauds[1] = 2400;
|
||||
parities[1] = 3;
|
||||
inverts[1] = false;
|
||||
|
||||
bauds[2] = 115200;
|
||||
parities[2] = 3;
|
||||
inverts[2] = false;
|
||||
|
||||
bauds[3] = 2400;
|
||||
parities[3] = 11;
|
||||
inverts[3] = true;
|
||||
|
||||
bauds[4] = 2400;
|
||||
parities[4] = 3;
|
||||
inverts[4] = true;
|
||||
|
||||
bauds[5] = 115200;
|
||||
parities[5] = 3;
|
||||
inverts[5] = true;
|
||||
}
|
||||
#else
|
||||
PassiveMeterCommunicator::PassiveMeterCommunicator(Stream* debugger) {
|
||||
this->debugger = debugger;
|
||||
}
|
||||
#endif
|
||||
|
||||
void PassiveMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
|
||||
this->meterConfig = meterConfig;
|
||||
if(meterConfig.baud == 0) {
|
||||
autodetect = true;
|
||||
}
|
||||
this->configChanged = false;
|
||||
this->tz = tz;
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
||||
if(dsmrParser != NULL) {
|
||||
delete dsmrParser;
|
||||
dsmrParser = NULL;
|
||||
}
|
||||
if(gcmParser != NULL) {
|
||||
delete gcmParser;
|
||||
gcmParser = NULL;
|
||||
}
|
||||
bool encen = false;
|
||||
for(uint8_t i = 0; i < 16; i++) {
|
||||
if(meterConfig.encryptionKey[i] > 0) {
|
||||
encen = true;
|
||||
}
|
||||
}
|
||||
if(encen) {
|
||||
gcmParser = new GCMParser(meterConfig.encryptionKey, meterConfig.authenticationKey);
|
||||
}
|
||||
}
|
||||
|
||||
bool PassiveMeterCommunicator::loop() {
|
||||
if(hanBufferSize == 0) return false;
|
||||
|
||||
unsigned long now = millis();
|
||||
if(autodetect) handleAutodetect(now);
|
||||
|
||||
unsigned long start, end;
|
||||
if(!hanSerial->available()) {
|
||||
return false;
|
||||
@@ -67,9 +70,6 @@ bool PassiveMeterCommunicator::loop() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
if(autodetect) handleAutodetect(now);
|
||||
|
||||
dataAvailable = false;
|
||||
ctx = {0,0,0,0};
|
||||
memset(ctx.system_title, 0, 8);
|
||||
@@ -81,7 +81,10 @@ bool PassiveMeterCommunicator::loop() {
|
||||
if(len >= hanBufferSize) {
|
||||
hanSerial->readBytes(hanBuffer, hanBufferSize);
|
||||
len = 0;
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
|
||||
return false;
|
||||
}
|
||||
hanBuffer[len++] = hanSerial->read();
|
||||
@@ -90,26 +93,47 @@ bool PassiveMeterCommunicator::loop() {
|
||||
if(ctx.type > 0 && pos >= 0) {
|
||||
switch(ctx.type) {
|
||||
case DATA_TAG_DLMS:
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid DLMS at %d\n"), pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid DLMS at %d +%d\n"), pos, ctx.length);
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid DSMR at %d\n"), pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid DSMR at %d +%d\n"), pos, ctx.length);
|
||||
break;
|
||||
case DATA_TAG_SNRM:
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid SNMR at %d\n"), pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid SNMR at %d +%d\n"), pos, ctx.length);
|
||||
break;
|
||||
case DATA_TAG_AARE:
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid AARE at %d\n"), pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid AARE at %d +%d\n"), pos, ctx.length);
|
||||
break;
|
||||
case DATA_TAG_RES:
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid Get Response at %d\n"), pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid Get Response at %d +%d\n"), pos, ctx.length);
|
||||
break;
|
||||
case DATA_TAG_HDLC:
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received valid HDLC at %d\n"), pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid HDLC at %d +%d\n"), pos, ctx.length);
|
||||
break;
|
||||
default:
|
||||
// TODO: Move this so that payload is sent to MQTT
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
@@ -118,18 +142,27 @@ bool PassiveMeterCommunicator::loop() {
|
||||
}
|
||||
end = millis();
|
||||
if(end-start > 1000) {
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
|
||||
}
|
||||
|
||||
if(pos == DATA_PARSE_INCOMPLETE) {
|
||||
return false;
|
||||
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unknown data received\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data received\n"));
|
||||
lastError = pos;
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
debugPrint(hanBuffer, 0, len, debugger);
|
||||
}
|
||||
len = 0;
|
||||
return false;
|
||||
@@ -144,9 +177,12 @@ bool PassiveMeterCommunicator::loop() {
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(hanBuffer, len);
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
debugPrint(hanBuffer, 0, len, debugger);
|
||||
}
|
||||
while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above
|
||||
len = 0;
|
||||
@@ -154,12 +190,18 @@ bool PassiveMeterCommunicator::loop() {
|
||||
}
|
||||
|
||||
if(ctx.type == 0) {
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
|
||||
lastError = pos;
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR(" payload:\n"));
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
debugPrint(hanBuffer, 0, len, debugger);
|
||||
}
|
||||
len = 0;
|
||||
return false;
|
||||
@@ -172,26 +214,6 @@ bool PassiveMeterCommunicator::loop() {
|
||||
dataAvailable = true;
|
||||
lastError = DATA_PARSE_OK;
|
||||
|
||||
// After one hour, adjust buffer size to match the largest payload
|
||||
if(!maxDetectPayloadDetectDone && now > 3600000) {
|
||||
if(maxDetectedPayloadSize * 1.25 > meterConfig.bufferSize * 64) {
|
||||
int bufferSize = min((double) 64, ceil((maxDetectedPayloadSize * 1.25) / 64));
|
||||
#if defined(ESP8266)
|
||||
if(meterConfig.rxPin != 3 && meterConfig.rxPin != 113) {
|
||||
bufferSize = min(bufferSize, 2);
|
||||
} else {
|
||||
bufferSize = min(bufferSize, 8);
|
||||
}
|
||||
#endif
|
||||
if(bufferSize != meterConfig.bufferSize) {
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
||||
meterConfig.bufferSize = bufferSize;
|
||||
configChanged = true;
|
||||
}
|
||||
}
|
||||
maxDetectPayloadDetectDone = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -211,13 +233,22 @@ AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
|
||||
pt->publishBytes((uint8_t*) payload, ctx.length);
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("Using application data:\n"));
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Using application data:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugPrint((byte*) payload, 0, ctx.length, debugger);
|
||||
|
||||
// Rudimentary detector for L&G proprietary format, this is terrible code... Fix later
|
||||
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LNG\n"));
|
||||
LNG lngData = LNG(meterState, payload, meterState.getMeterType(), &meterConfig, ctx, debugger);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LNG\n"));
|
||||
LNG lngData = LNG(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
|
||||
if(lngData.getListType() >= 1) {
|
||||
data = new AmsData();
|
||||
data->apply(meterState);
|
||||
@@ -231,17 +262,23 @@ AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
|
||||
payload[14] == CosemTypeLongUnsigned &&
|
||||
payload[17] == CosemTypeLongUnsigned
|
||||
) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LNG2\n"));
|
||||
LNG2 lngData = LNG2(meterState, payload, meterState.getMeterType(), &meterConfig, ctx, debugger);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LNG2\n"));
|
||||
LNG2 lngData = LNG2(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
|
||||
if(lngData.getListType() >= 1) {
|
||||
data = new AmsData();
|
||||
data->apply(meterState);
|
||||
data->apply(lngData);
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DLMS\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DLMS\n"));
|
||||
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
|
||||
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState);
|
||||
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState, debugger);
|
||||
}
|
||||
} else if(ctx.type == DATA_TAG_DSMR) {
|
||||
data = new IEC6205621(payload, tz, &meterConfig);
|
||||
@@ -249,7 +286,7 @@ AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
|
||||
len = 0;
|
||||
if(data != NULL) {
|
||||
if(data->getListType() > 0) {
|
||||
validDataReceived = true;
|
||||
validDataReceived++;
|
||||
if(rxBufferErrors > 0) rxBufferErrors--;
|
||||
}
|
||||
}
|
||||
@@ -261,7 +298,10 @@ int PassiveMeterCommunicator::getLastError() {
|
||||
#if defined ESP8266
|
||||
if(hwSerial != NULL) {
|
||||
if(hwSerial->hasRxError()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial RX error\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial RX error\n"));
|
||||
lastError = 96;
|
||||
}
|
||||
if(hwSerial->hasOverrun()) {
|
||||
@@ -280,11 +320,14 @@ bool PassiveMeterCommunicator::isConfigChanged() {
|
||||
return configChanged;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::ackConfigChanged() {
|
||||
configChanged = false;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
|
||||
|
||||
int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &context) {
|
||||
int16_t ret = 0;
|
||||
bool doRet = false;
|
||||
@@ -322,8 +365,12 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
|
||||
if(res >= 0) doRet = true;
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
if(dsmrParser == NULL) dsmrParser = new DSMRParser();
|
||||
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
|
||||
if(dsmrParser == NULL) dsmrParser = new DSMRParser(gcmParser);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE, debugger->isActive(RemoteDebug::VERBOSE) ? debugger : NULL);
|
||||
#else
|
||||
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE, debugger);
|
||||
#endif
|
||||
if(res >= 0) doRet = true;
|
||||
break;
|
||||
case DATA_TAG_SNRM:
|
||||
@@ -333,7 +380,10 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
|
||||
doRet = true;
|
||||
break;
|
||||
default:
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
lastTag = tag;
|
||||
@@ -341,53 +391,89 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
|
||||
return res;
|
||||
}
|
||||
if(context.length > end) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
|
||||
context.type = 0;
|
||||
context.length = 0;
|
||||
return false;
|
||||
}
|
||||
switch(tag) {
|
||||
case DATA_TAG_HDLC:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("HDLC frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HDLC frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(buf, curLen);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_MBUS:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("MBUS frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("MBUS frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(buf, curLen);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_GBT:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("GBT frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GBT frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_GCM:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("GCM frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GCM frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_LLC:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("LLC frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LLC frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_DLMS:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DLMS frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DLMS frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("DSMR frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DSMR frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishString((char*) buf);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_SNRM:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("SNMR frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("SNMR frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_AARE:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("AARE frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("AARE frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_RES:
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("RES frame:\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("RES frame:\n"));
|
||||
break;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugPrint(buf, 0, curLen);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugPrint(buf, 0, curLen, debugger);
|
||||
if(res == DATA_PARSE_FINAL_SEGMENT) {
|
||||
if(tag == DATA_TAG_MBUS) {
|
||||
res = mbusParser->write(buf, context);
|
||||
@@ -410,80 +496,105 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
|
||||
// Use start byte of new buffer position as tag for next round in loop
|
||||
tag = (*buf);
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
debugger->print(F("0"));
|
||||
debugger->print(buffer[i], HEX);
|
||||
debugger->print(F(" "));
|
||||
if ((i - start + 1) % 16 == 0)
|
||||
debugger->println(F(""));
|
||||
else if ((i - start + 1) % 4 == 0)
|
||||
debugger->print(F(" "));
|
||||
|
||||
yield(); // Let other get some resources too
|
||||
}
|
||||
debugger->println(F(""));
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::printHanReadError(int pos) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
switch(pos) {
|
||||
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Boundry flag missing\n"));
|
||||
case DATA_PARSE_BOUNDARY_FLAG_MISSING:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Boundary flag missing\n"));
|
||||
break;
|
||||
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Header checksum error\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Header checksum error\n"));
|
||||
break;
|
||||
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Frame checksum error\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame checksum error\n"));
|
||||
break;
|
||||
case DATA_PARSE_INCOMPLETE:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Received frame is incomplete\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received frame is incomplete\n"));
|
||||
break;
|
||||
case GCM_AUTH_FAILED:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Decrypt authentication failed\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decrypt authentication failed\n"));
|
||||
break;
|
||||
case GCM_ENCRYPTION_KEY_FAILED:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Setting decryption key failed\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Setting decryption key failed\n"));
|
||||
break;
|
||||
case GCM_DECRYPT_FAILED:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Decryption failed\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decryption failed\n"));
|
||||
break;
|
||||
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Frame length mismatch\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame length mismatch\n"));
|
||||
break;
|
||||
case DATA_PARSE_INTERMEDIATE_SEGMENT:
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Intermediate segment received\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Intermediate segment received\n"));
|
||||
break;
|
||||
case DATA_PARSE_UNKNOWN_DATA:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
|
||||
break;
|
||||
default:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert) {
|
||||
void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive) {
|
||||
int8_t rxpin = meterConfig.rxPin;
|
||||
int8_t txpin = meterConfig.txPin;
|
||||
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d and parity %d\n"), rxpin, txpin, baud, parityOrdinal);
|
||||
int8_t txpin = passive ? -1 : meterConfig.txPin;
|
||||
|
||||
if(baud == 0) {
|
||||
baud = bauds[meterAutoIndex];
|
||||
parityOrdinal = parities[meterAutoIndex];
|
||||
invert = inverts[meterAutoIndex];
|
||||
autodetectBaud = baud = 2400;
|
||||
}
|
||||
|
||||
if(parityOrdinal == 0) {
|
||||
parityOrdinal = 3; // 8N1
|
||||
parityOrdinal = 11; // 8E1
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d and parity %d\n"), rxpin, txpin, baud, parityOrdinal);
|
||||
|
||||
if(rxpin == 3 || rxpin == 113) {
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
hwSerial = &Serial0;
|
||||
@@ -505,7 +616,10 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
|
||||
#endif
|
||||
|
||||
if(rxpin == 0) {
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -513,7 +627,10 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
|
||||
if(meterConfig.bufferSize > 64) meterConfig.bufferSize = 64;
|
||||
|
||||
if(hwSerial != NULL) {
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Hardware serial\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Hardware serial\n"));
|
||||
Serial.flush();
|
||||
#if defined(ESP8266)
|
||||
SerialConfig serialConfig;
|
||||
@@ -549,10 +666,16 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(rxpin == 3) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
|
||||
Serial.pins(1,3);
|
||||
} else if(rxpin == 113) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
|
||||
Serial.pins(15,13);
|
||||
}
|
||||
#endif
|
||||
@@ -584,7 +707,10 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
|
||||
#endif
|
||||
} else {
|
||||
#if defined(ESP8266)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Software serial\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial\n"));
|
||||
Serial.flush();
|
||||
|
||||
if(swSerial == NULL) {
|
||||
@@ -616,15 +742,18 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
|
||||
#if defined(ESP8266)
|
||||
if(bufferSize > 2) bufferSize = 2;
|
||||
#endif
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Using serial buffer size %d\n"), 64 * bufferSize);
|
||||
swSerial->begin(baud, serialConfig, rxpin, txpin, invert, meterConfig.bufferSize * 64);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Using serial buffer size %d\n"), 64 * bufferSize);
|
||||
swSerial->begin(baud, serialConfig, rxpin, txpin, invert, meterConfig.bufferSize * 64, meterConfig.bufferSize * 64);
|
||||
hanSerial = swSerial;
|
||||
|
||||
Serial.end();
|
||||
Serial.begin(115200);
|
||||
hwSerial = NULL;
|
||||
#else
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Software serial not available\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial not available\n"));
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
@@ -632,15 +761,23 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
|
||||
if(hanBuffer != NULL) {
|
||||
free(hanBuffer);
|
||||
}
|
||||
hanBufferSize = max(64 * meterConfig.bufferSize * 2, 512);
|
||||
hanBufferSize = max(64 * meterConfig.bufferSize * 3, 512);
|
||||
hanBuffer = (uint8_t*) malloc(hanBufferSize);
|
||||
|
||||
// The library automatically sets the pullup in Serial.begin()
|
||||
if(!meterConfig.rxPinPullup) {
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
|
||||
pinMode(meterConfig.rxPin, INPUT);
|
||||
}
|
||||
|
||||
if(meterConfig.txPin != 0xFF && passive) {
|
||||
pinMode(meterConfig.txPin, OUTPUT);
|
||||
digitalWrite(meterConfig.txPin, LOW);
|
||||
}
|
||||
|
||||
hanSerial->setTimeout(250);
|
||||
|
||||
// Empty buffer before starting
|
||||
@@ -662,25 +799,48 @@ HardwareSerial* PassiveMeterCommunicator::getHwSerial() {
|
||||
|
||||
void PassiveMeterCommunicator::rxerr(int err) {
|
||||
if(err == 0) return;
|
||||
if(lastError == 90+err) return; // Do not flood with same error
|
||||
switch(err) {
|
||||
case 2:
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial buffer overflow\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial buffer overflow\n"));
|
||||
rxBufferErrors++;
|
||||
if(rxBufferErrors > 3 && meterConfig.bufferSize < 64) {
|
||||
if(rxBufferErrors > 1 && meterConfig.bufferSize < 8) {
|
||||
meterConfig.bufferSize += 2;
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
||||
configChanged = true;
|
||||
rxBufferErrors = 0;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("Serial FIFO overflow\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial FIFO overflow\n"));
|
||||
break;
|
||||
case 4:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Serial frame error\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial frame error\n"));
|
||||
break;
|
||||
case 5:
|
||||
if (debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("Serial parity error\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial parity error\n"));
|
||||
unsigned long now = millis();
|
||||
if(autodetect) {
|
||||
meterAutodetectLastChange = 0;
|
||||
} else if(validDataReceived > 2) {
|
||||
meterConfig.parity = getNextParity(meterConfig.parity);
|
||||
configChanged = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Do not include serial break
|
||||
@@ -689,23 +849,55 @@ void PassiveMeterCommunicator::rxerr(int err) {
|
||||
|
||||
void PassiveMeterCommunicator::handleAutodetect(unsigned long now) {
|
||||
if(!autodetect) return;
|
||||
if(now - meterAutodetectLastChange < 12000) return;
|
||||
|
||||
if(!validDataReceived) {
|
||||
if(now - meterAutodetectLastChange > 20000 && (meterConfig.baud == 0 || meterConfig.parity == 0)) {
|
||||
if(validDataReceived < 2) {
|
||||
if(meterConfig.baud == 0 || meterConfig.parity == 0) {
|
||||
autodetect = true;
|
||||
meterAutoIndex++; // Default is to try the first one in setup()
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
|
||||
if(meterAutoIndex >= 6) meterAutoIndex = 0;
|
||||
setupHanPort(bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex]);
|
||||
if(lastError == 95) { // If parity error, switch parity
|
||||
autodetectParity = getNextParity(autodetectParity);
|
||||
lastError = 0;
|
||||
} else {
|
||||
autodetectCount++;
|
||||
}
|
||||
if(autodetectCount == sizeof(AUTO_BAUD_RATES)/sizeof(AUTO_BAUD_RATES[0])) {
|
||||
autodetectInvert = !autodetectInvert;
|
||||
autodetectCount = 0;
|
||||
}
|
||||
autodetectBaud = AUTO_BAUD_RATES[autodetectCount];
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
|
||||
meterConfig.bufferSize = max((uint32_t) 1, autodetectBaud / 14400);
|
||||
setupHanPort(autodetectBaud, autodetectParity, autodetectInvert);
|
||||
meterAutodetectLastChange = now;
|
||||
}
|
||||
} else if(autodetect) {
|
||||
if (debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), bauds[meterAutoIndex], parities[meterAutoIndex], inverts[meterAutoIndex] ? "true" : "false");
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
|
||||
autodetect = false;
|
||||
meterConfig.baud = bauds[meterAutoIndex];
|
||||
meterConfig.parity = parities[meterAutoIndex];
|
||||
meterConfig.invert = inverts[meterAutoIndex];
|
||||
meterConfig.baud = autodetectBaud;
|
||||
meterConfig.parity = autodetectParity;
|
||||
meterConfig.invert = autodetectInvert;
|
||||
configChanged = true;
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PassiveMeterCommunicator::getNextParity(uint8_t parityOrdinal) {
|
||||
switch(parityOrdinal) {
|
||||
case 10: // 7E1
|
||||
return 2; // 7N1
|
||||
case 14: // 7E2
|
||||
return 6; // 7N2
|
||||
|
||||
case 11: // 8E1
|
||||
return 3; // 8N1
|
||||
case 15: // 8E2
|
||||
return 7; // 8N2
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PulseMeterCommunicator.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PulseMeterCommunicator::PulseMeterCommunicator(RemoteDebug* debugger) {
|
||||
#else
|
||||
PulseMeterCommunicator::PulseMeterCommunicator(Stream* debugger) {
|
||||
#endif
|
||||
this->debugger = debugger;
|
||||
}
|
||||
|
||||
@@ -37,12 +47,19 @@ bool PulseMeterCommunicator::isConfigChanged() {
|
||||
return this->configChanged;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::ackConfigChanged() {
|
||||
configChanged = false;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::setupGpio() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Setting up Pulse Meter GPIO, rx: %d, tx: %d\n"), meterConfig.rxPin, meterConfig.txPin);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Setting up Pulse Meter GPIO, rx: %d, tx: %d\n"), meterConfig.rxPin, meterConfig.txPin);
|
||||
if(meterConfig.rxPin != NOT_A_PIN) {
|
||||
pinMode(meterConfig.rxPin, meterConfig.rxPinPullup ? INPUT_PULLUP : INPUT);
|
||||
}
|
||||
@@ -11,9 +11,15 @@
|
||||
|
||||
class PassthroughMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
PassthroughMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PassthroughMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
this->topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
#else
|
||||
PassthroughMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
#endif
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(PriceService*);
|
||||
@@ -11,7 +11,9 @@
|
||||
|
||||
#include "TimeLib.h"
|
||||
#include "Timezone.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EntsoeA44Parser.h"
|
||||
|
||||
@@ -63,8 +65,13 @@ struct PricePart {
|
||||
|
||||
class PriceService {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PriceService(RemoteDebug*);
|
||||
#else
|
||||
PriceService(Stream*);
|
||||
#endif
|
||||
void setup(PriceServiceConfig&);
|
||||
void setTimezone(Timezone* tz);
|
||||
bool loop();
|
||||
|
||||
char* getToken();
|
||||
@@ -88,7 +95,11 @@ public:
|
||||
bool save();
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Stream* debugger;
|
||||
#endif
|
||||
PriceServiceConfig* config = NULL;
|
||||
HTTPClient* http = NULL;
|
||||
|
||||
@@ -104,6 +115,7 @@ private:
|
||||
std::vector<PriceConfig> priceConfig;
|
||||
|
||||
Timezone* tz = NULL;
|
||||
Timezone* entsoeTz = NULL;
|
||||
|
||||
static const uint16_t BufferSize = 256;
|
||||
char* buf;
|
||||
@@ -119,7 +131,6 @@ private:
|
||||
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);
|
||||
bool timeIsInPeriod(tmElements_t tm, PriceConfig pc);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -72,7 +72,10 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||
} else if(docPos == DOCPOS_POSITION) {
|
||||
if(byte == '<') {
|
||||
buf[pos] = '\0';
|
||||
pointNum = String(buf).toInt() - 1;
|
||||
long pn = String(buf).toInt() - 1;
|
||||
if(pn < 25) {
|
||||
pointNum = pn;
|
||||
}
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
} else {
|
||||
@@ -81,7 +84,10 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||
} else if(docPos == DOCPOS_AMOUNT) {
|
||||
if(byte == '<') {
|
||||
buf[pos] = '\0';
|
||||
points[pointNum] = String(buf).toFloat();
|
||||
float val = String(buf).toFloat();
|
||||
for(uint8_t i = pointNum; i < 25; i++) {
|
||||
points[i] = val;
|
||||
}
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
} else {
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PriceService::PriceService(RemoteDebug* Debug) : priceConfig(std::vector<PriceConfig>()) {
|
||||
#else
|
||||
PriceService::PriceService(Stream* Debug) : priceConfig(std::vector<PriceConfig>()) {
|
||||
#endif
|
||||
this->buf = (char*) malloc(BufferSize);
|
||||
|
||||
debugger = Debug;
|
||||
@@ -28,7 +32,8 @@ PriceService::PriceService(RemoteDebug* Debug) : priceConfig(std::vector<PriceCo
|
||||
// Entso-E uses CET/CEST
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
tz = new Timezone(CEST, CET);
|
||||
entsoeTz = new Timezone(CEST, CET);
|
||||
tz = entsoeTz;
|
||||
|
||||
tomorrowFetchMinute = 15 + random(45); // Random between 13:15 and 14:00
|
||||
}
|
||||
@@ -68,8 +73,12 @@ void PriceService::setup(PriceServiceConfig& config) {
|
||||
load();
|
||||
}
|
||||
|
||||
void PriceService::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
|
||||
char* PriceService::getToken() {
|
||||
return this->config->entsoeToken;
|
||||
return ""; // Currently the implementation is not working, so lets disable it for al. Old code: this->config->entsoeToken;
|
||||
}
|
||||
|
||||
char* PriceService::getCurrency() {
|
||||
@@ -106,30 +115,23 @@ float PriceService::getValueForHour(uint8_t direction, time_t ts, int8_t hour) {
|
||||
return ret;
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(ts + (hour) * SECS_PER_HOUR), tm);
|
||||
uint8_t day = 0x01 << (tm.Wday - 2);
|
||||
uint32_t hrs = 0x01 << tm.Hour;
|
||||
breakTime(tz->toLocal(ts + (hour * SECS_PER_HOUR)), tm);
|
||||
|
||||
for (uint8_t i = 0; i < priceConfig.size(); i++) {
|
||||
PriceConfig pc = priceConfig.at(i);
|
||||
if(pc.type == PRICE_TYPE_FIXED) continue;
|
||||
uint8_t start_month = pc.start_month == 0 || pc.start_month > 12 ? 1 : pc.start_month;
|
||||
uint8_t start_dayofmonth = pc.start_dayofmonth == 0 || pc.start_dayofmonth > 31 ? 1 : pc.start_dayofmonth;
|
||||
uint8_t end_month = pc.end_month == 0 || pc.end_month > 12 ? 12 : pc.end_month;
|
||||
uint8_t end_dayofmonth = pc.end_dayofmonth == 0 || pc.end_dayofmonth > 31 ? 31 : pc.end_dayofmonth;
|
||||
|
||||
if((pc.direction & direction) == direction && (pc.days & day) == day && (pc.hours & hrs) == hrs && tm.Month >= start_month && tm.Day >= start_dayofmonth && tm.Month <= end_month && tm.Day <= end_dayofmonth) {
|
||||
switch(pc.type) {
|
||||
case PRICE_TYPE_ADD:
|
||||
ret += pc.value / 10000.0;
|
||||
break;
|
||||
case PRICE_TYPE_SUBTRACT:
|
||||
ret -= pc.value / 10000.0;
|
||||
break;
|
||||
case PRICE_TYPE_PCT:
|
||||
ret += ((pc.value / 10000.0) * ret) / 100.0;
|
||||
break;
|
||||
}
|
||||
if((pc.direction & direction) != direction) continue;
|
||||
if(!timeIsInPeriod(tm, pc)) continue;
|
||||
switch(pc.type) {
|
||||
case PRICE_TYPE_ADD:
|
||||
ret += pc.value / 10000.0;
|
||||
break;
|
||||
case PRICE_TYPE_SUBTRACT:
|
||||
ret -= pc.value / 10000.0;
|
||||
break;
|
||||
case PRICE_TYPE_PCT:
|
||||
ret += ((pc.value / 10000.0) * ret) / 100.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@@ -137,54 +139,55 @@ float PriceService::getValueForHour(uint8_t direction, time_t ts, int8_t hour) {
|
||||
|
||||
float PriceService::getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t hour) {
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(ts + (hour) * SECS_PER_HOUR), tm);
|
||||
uint8_t day = 0x01 << (tm.Wday - 2);
|
||||
uint32_t hrs = 0x01 << tm.Hour;
|
||||
breakTime(tz->toLocal(ts + (hour * SECS_PER_HOUR)), tm);
|
||||
|
||||
float value = PRICE_NO_VALUE;
|
||||
for (uint8_t i = 0; i < priceConfig.size(); i++) {
|
||||
PriceConfig pc = priceConfig.at(i);
|
||||
if(pc.type != PRICE_TYPE_FIXED) continue;
|
||||
uint8_t start_month = pc.start_month == 0 || pc.start_month > 12 ? 1 : pc.start_month;
|
||||
uint8_t start_dayofmonth = pc.start_dayofmonth == 0 || pc.start_dayofmonth > 31 ? 1 : pc.start_dayofmonth;
|
||||
uint8_t end_month = pc.end_month == 0 || pc.end_month > 12 ? 12 : pc.end_month;
|
||||
uint8_t end_dayofmonth = pc.end_dayofmonth == 0 || pc.end_dayofmonth > 31 ? 31 : pc.end_dayofmonth;
|
||||
if((pc.direction & direction) != direction) continue;
|
||||
if(!timeIsInPeriod(tm, pc)) continue;
|
||||
|
||||
if((pc.direction & direction) == direction && (pc.days & day) == day && (pc.hours & hrs) == hrs && tm.Month >= start_month && tm.Day >= start_dayofmonth && tm.Month <= end_month && tm.Day <= end_dayofmonth) {
|
||||
if(value == PRICE_NO_VALUE) {
|
||||
value = pc.value / 10000.0;
|
||||
} else {
|
||||
value += pc.value / 10000.0;
|
||||
}
|
||||
if(value == PRICE_NO_VALUE) {
|
||||
value = pc.value / 10000.0;
|
||||
} else {
|
||||
value += pc.value / 10000.0;
|
||||
}
|
||||
}
|
||||
if(value != PRICE_NO_VALUE) return value;
|
||||
|
||||
int8_t pos = hour;
|
||||
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
breakTime(entsoeTz->toLocal(ts), tm);
|
||||
while(tm.Hour > 0) {
|
||||
ts -= 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
breakTime(entsoeTz->toLocal(ts), tm);
|
||||
pos++;
|
||||
}
|
||||
uint8_t hoursToday = 0;
|
||||
uint8_t todayDate = tm.Day;
|
||||
while(tm.Day == todayDate) {
|
||||
ts += 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
breakTime(entsoeTz->toLocal(ts), tm);
|
||||
hoursToday++;
|
||||
}
|
||||
if(pos > 49)
|
||||
return PRICE_NO_VALUE;
|
||||
uint8_t hoursTomorrow = 0;
|
||||
uint8_t tomorrowDate = tm.Day;
|
||||
while(tm.Day == tomorrowDate) {
|
||||
ts += 3600;
|
||||
breakTime(entsoeTz->toLocal(ts), tm);
|
||||
hoursTomorrow++;
|
||||
}
|
||||
|
||||
float multiplier = 1.0;
|
||||
if(pos >= hoursToday) {
|
||||
pos = pos - hoursToday;
|
||||
if(pos >= hoursTomorrow) return PRICE_NO_VALUE;
|
||||
if(tomorrow == NULL)
|
||||
return PRICE_NO_VALUE;
|
||||
if(tomorrow->points[pos-hoursToday] == PRICE_NO_VALUE)
|
||||
if(tomorrow->points[pos] == PRICE_NO_VALUE)
|
||||
return PRICE_NO_VALUE;
|
||||
value = tomorrow->points[pos-hoursToday] / 10000.0;
|
||||
value = tomorrow->points[pos] / 10000.0;
|
||||
if(strcmp(tomorrow->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) {
|
||||
@@ -212,7 +215,7 @@ float PriceService::getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t h
|
||||
if(mult == 0) return PRICE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
}
|
||||
return value * multiplier;
|
||||
return value == PRICE_NO_VALUE ? PRICE_NO_VALUE : value * multiplier;
|
||||
}
|
||||
|
||||
bool PriceService::loop() {
|
||||
@@ -227,15 +230,13 @@ bool PriceService::loop() {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if(!config->enabled)
|
||||
return false;
|
||||
if(strlen(config->area) == 0)
|
||||
return false;
|
||||
if(strlen(config->currency) == 0)
|
||||
return false;
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
breakTime(entsoeTz->toLocal(t), tm);
|
||||
|
||||
if(currentDay == 0) {
|
||||
currentDay = tm.Day;
|
||||
@@ -250,12 +251,15 @@ bool PriceService::loop() {
|
||||
}
|
||||
currentDay = tm.Day;
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
|
||||
} else if(currentHour != tm.Hour) {
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
if(!config->enabled)
|
||||
return false;
|
||||
|
||||
bool readyToFetchForTomorrow = tomorrow == NULL && (tm.Hour > 13 || (tm.Hour == 13 && tm.Minute >= tomorrowFetchMinute)) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > (nextFetchDelayMinutes*60000));
|
||||
|
||||
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > (nextFetchDelayMinutes*60000))) {
|
||||
@@ -323,9 +327,18 @@ bool PriceService::retrieve(const char* url, Stream* doc) {
|
||||
} else {
|
||||
nextFetchDelayMinutes = 2;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(http->errorToString(status).c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http->getString().c_str());
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) 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());
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf(http->getString().c_str());
|
||||
|
||||
http->end();
|
||||
return false;
|
||||
@@ -356,12 +369,10 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
|
||||
float currencyMultiplier = 0;
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), from);
|
||||
if(retrieve(buf, &p)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) got exchange rate %.4f\n"), p.getValue());
|
||||
currencyMultiplier = p.getValue();
|
||||
if(strncmp(to, "NOK", 3) != 0) {
|
||||
snprintf_P(buf, BufferSize, PSTR("https://data.norges-bank.no/api/data/EXR/B.%s.NOK.SP?lastNObservations=1"), to);
|
||||
if(retrieve(buf, &p)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) got exchange rate %.4f\n"), p.getValue());
|
||||
if(p.getValue() > 0.0) {
|
||||
currencyMultiplier /= p.getValue();
|
||||
} else {
|
||||
@@ -373,13 +384,19 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
|
||||
}
|
||||
}
|
||||
if(currencyMultiplier != 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier);
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
lastCurrencyFetch = now + (SECS_PER_DAY * 1000) - (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000) + (3600000 * 6) + (tomorrowFetchMinute * 60);
|
||||
this->currencyMultiplier = currencyMultiplier;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n"));
|
||||
lastCurrencyFetch = now + (SECS_PER_HOUR * 1000);
|
||||
if(this->currencyMultiplier == 1) return 0;
|
||||
}
|
||||
@@ -390,7 +407,7 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
|
||||
PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
if(strlen(getToken()) > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
breakTime(entsoeTz->toLocal(t), tm);
|
||||
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // Local midnight
|
||||
time_t e2 = e1 + SECS_PER_DAY;
|
||||
tmElements_t d1, d2;
|
||||
@@ -409,8 +426,14 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
EntsoeA44Parser a44;
|
||||
if(retrieve(buf, &a44) && a44.getPoint(0) != PRICE_NO_VALUE) {
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
@@ -421,7 +444,7 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
}
|
||||
} else if(hub) {
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
breakTime(entsoeTz->toLocal(t), tm);
|
||||
|
||||
String data;
|
||||
snprintf_P(buf, BufferSize, PSTR("http://hub.amsleser.no/hub/price/%s/%d/%d/%d?currency=%s"),
|
||||
@@ -431,8 +454,14 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
tm.Day,
|
||||
config->currency
|
||||
);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Fetching prices for %02d.%02d.%04d\n"), tm.Day, tm.Month, tm.Year+1970);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
@@ -473,7 +502,10 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
} else {
|
||||
lastError = gcmRet;
|
||||
nextFetchDelayMinutes = 60;
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet);
|
||||
}
|
||||
} else {
|
||||
lastError = status;
|
||||
@@ -484,12 +516,21 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
} else {
|
||||
nextFetchDelayMinutes = 5;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) 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(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http->getString().c_str());
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf(http->getString().c_str());
|
||||
|
||||
http->end();
|
||||
}
|
||||
@@ -498,22 +539,6 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void PriceService::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
debugger->print(F("0"));
|
||||
debugger->print(buffer[i], HEX);
|
||||
debugger->print(F(" "));
|
||||
if ((i - start + 1) % 16 == 0)
|
||||
debugger->println(F(""));
|
||||
else if ((i - start + 1) % 4 == 0)
|
||||
debugger->print(F(" "));
|
||||
|
||||
yield(); // Let other get some resources too
|
||||
}
|
||||
debugger->println(F(""));
|
||||
}
|
||||
|
||||
int16_t PriceService::getLastError() {
|
||||
return lastError;
|
||||
}
|
||||
@@ -523,7 +548,7 @@ std::vector<PriceConfig>& PriceService::getPriceConfig() {
|
||||
}
|
||||
|
||||
void PriceService::setPriceConfig(uint8_t index, PriceConfig &priceConfig) {
|
||||
stripNonAscii((uint8_t*) priceConfig.name, 32);
|
||||
stripNonAscii((uint8_t*) priceConfig.name, 32, true);
|
||||
|
||||
if(this->priceConfig.capacity() != index+1)
|
||||
this->priceConfig.resize(index+1);
|
||||
@@ -541,11 +566,17 @@ void PriceService::cropPriceConfig(uint8_t size) {
|
||||
|
||||
bool PriceService::save() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
|
||||
|
||||
PriceConfig pc;
|
||||
File file = LittleFS.open(FILE_PRICE_CONF, "w");
|
||||
@@ -567,14 +598,19 @@ bool PriceService::save() {
|
||||
|
||||
bool PriceService::load() {
|
||||
if(!LittleFS.begin()) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
return false;
|
||||
}
|
||||
if(!LittleFS.exists(FILE_PRICE_CONF)) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(PriceService) No price config file\n"));
|
||||
return false;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
|
||||
|
||||
this->priceConfig.clear();
|
||||
|
||||
@@ -588,4 +624,38 @@ bool PriceService::load() {
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool PriceService::timeIsInPeriod(tmElements_t tm, PriceConfig pc) {
|
||||
uint8_t day = 0x01 << ((tm.Wday+5)%7);
|
||||
uint32_t hrs = 0x01 << tm.Hour;
|
||||
|
||||
if((pc.days & day) != day) return false;
|
||||
if((pc.hours & hrs) != hrs) return false;
|
||||
|
||||
tmElements_t tms;
|
||||
tms.Year = tm.Year;
|
||||
tms.Month = pc.start_month == 0 || pc.start_month > 12 ? 1 : pc.start_month;
|
||||
tms.Day = pc.start_dayofmonth == 0 || pc.start_dayofmonth > 31 ? 1 : pc.start_dayofmonth;
|
||||
tms.Hour = 0;
|
||||
tms.Minute = 0;
|
||||
tms.Second = 0;
|
||||
|
||||
tmElements_t tme;
|
||||
tme.Year = tm.Year;
|
||||
tme.Month = pc.end_month == 0 || pc.end_month > 12 ? 12 : pc.end_month;
|
||||
tme.Day = pc.end_dayofmonth == 0 || pc.end_dayofmonth > 31 ? 31 : pc.end_dayofmonth;
|
||||
tme.Hour = 23;
|
||||
tme.Minute = 59;
|
||||
tme.Second = 59;
|
||||
if(makeTime(tms) > makeTime(tme)) {
|
||||
if(makeTime(tm) <= makeTime(tme)) {
|
||||
tms.Year--;
|
||||
} else {
|
||||
tme.Year++;
|
||||
}
|
||||
}
|
||||
|
||||
return makeTime(tms) <= makeTime(tm) && makeTime(tme) >= makeTime(tm);
|
||||
}
|
||||
@@ -11,10 +11,17 @@
|
||||
|
||||
class RawMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
RawMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RawMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
full = mqttConfig.payloadFormat == 2;
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
#else
|
||||
RawMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
full = mqttConfig.payloadFormat == 2;
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
#endif
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(PriceService*);
|
||||
@@ -28,6 +35,7 @@ public:
|
||||
private:
|
||||
bool full;
|
||||
String topic;
|
||||
uint32_t lastThresholdPublish = 0;
|
||||
|
||||
bool publishList1(AmsData* data, AmsData* meterState);
|
||||
bool publishList2(AmsData* data, AmsData* meterState);
|
||||
|
||||
@@ -7,26 +7,38 @@
|
||||
#include "RawMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccounting* ea, PriceService* ps) {
|
||||
bool RawMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
mqtt.publish(topic + "/meter/dlms/timestamp", String(data->getPackageTimestamp()));
|
||||
|
||||
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;
|
||||
}
|
||||
switch(data->getListType()) {
|
||||
|
||||
if(data.getPackageTimestamp() > 0) {
|
||||
mqtt.publish(topic + "/meter/dlms/timestamp", String(data.getPackageTimestamp()));
|
||||
}
|
||||
switch(data.getListType()) {
|
||||
case 4:
|
||||
publishList4(data, meterState);
|
||||
publishList4(&data, previousState);
|
||||
loop();
|
||||
case 3:
|
||||
publishList3(data, meterState);
|
||||
publishList3(&data, previousState);
|
||||
loop();
|
||||
case 2:
|
||||
publishList2(data, meterState);
|
||||
publishList2(&data, previousState);
|
||||
loop();
|
||||
case 1:
|
||||
publishList1(data, meterState);
|
||||
publishList1(&data, previousState);
|
||||
loop();
|
||||
}
|
||||
if(ea->isInitialized()) {
|
||||
@@ -188,6 +200,15 @@ bool RawMqttHandler::publishRealtime(EnergyAccounting* ea) {
|
||||
mqtt.loop();
|
||||
mqtt.publish(topic + "/realtime/export/month", String(ea->getProducedThisMonth(), 1));
|
||||
mqtt.loop();
|
||||
uint32_t now = millis();
|
||||
if(lastThresholdPublish == 0 || now-lastThresholdPublish > 3600000) {
|
||||
EnergyAccountingConfig* conf = ea->getConfig();
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
mqtt.publish(topic + "/realtime/import/thresholds/" + String(i+1, 10), String(conf->thresholds[i], 10), true, 0);
|
||||
mqtt.loop();
|
||||
}
|
||||
lastThresholdPublish = now;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -317,6 +338,15 @@ bool RawMqttHandler::publishPrices(PriceService* ps) {
|
||||
mqtt.publish(topic + "/price/cheapest/6hr", String(ts6hr), true, 0);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
float exportPrice = ps->getEnergyPriceForHour(PRICE_DIRECTION_EXPORT, now, 0);
|
||||
if(exportPrice == PRICE_NO_VALUE) {
|
||||
mqtt.publish(topic + "/exportprice/0", "", true, 0);
|
||||
mqtt.loop();
|
||||
} else {
|
||||
mqtt.publish(topic + "/exportprice/0", String(exportPrice, 4), true, 0);
|
||||
mqtt.loop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -353,4 +383,11 @@ bool RawMqttHandler::publishRaw(String data) {
|
||||
}
|
||||
|
||||
void RawMqttHandler::onMessage(String &topic, String &payload) {
|
||||
if(topic.equals(subTopic)) {
|
||||
if(payload.equals("fwupgrade")) {
|
||||
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
|
||||
updater->setTargetVersion(updater->getNextVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
lib/SvelteUi/app/dist/index.css
vendored
2
lib/SvelteUi/app/dist/index.css
vendored
File diff suppressed because one or more lines are too long
12
lib/SvelteUi/app/dist/index.js
vendored
12
lib/SvelteUi/app/dist/index.js
vendored
File diff suppressed because one or more lines are too long
3706
lib/SvelteUi/app/package-lock.json
generated
3706
lib/SvelteUi/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user