mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-10 20:54:24 +00:00
Compare commits
185 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea91248e67 | ||
|
|
271ce2081f | ||
|
|
8438020dbd | ||
|
|
9252a810df | ||
|
|
c0c696a55c | ||
|
|
ef70d39f70 | ||
|
|
1cf890dc26 | ||
|
|
9d307e3192 | ||
|
|
61f0356a10 | ||
|
|
c648546b61 | ||
|
|
ffd8d46f2e | ||
|
|
eefbc08222 | ||
|
|
1a5b9542f4 | ||
|
|
19ff70782f | ||
|
|
0dfd2d9022 | ||
|
|
7a4ab77a83 | ||
|
|
46cd8c6e68 | ||
|
|
c307103605 | ||
|
|
d9ec111458 | ||
|
|
e3a1aa78a9 | ||
|
|
b06aa5f71b | ||
|
|
6a75b0fe71 | ||
|
|
e11fac3d11 | ||
|
|
031422f783 | ||
|
|
2ff8fddc14 | ||
|
|
e5d260ae3e | ||
|
|
633671851e | ||
|
|
69da9f9d48 | ||
|
|
19f78126d6 | ||
|
|
d3cc92949a | ||
|
|
f1089faab5 | ||
|
|
4a3ad6ab9b | ||
|
|
86449949c5 | ||
|
|
9bd9826835 | ||
|
|
94ff9d6da7 | ||
|
|
9518d1811b | ||
|
|
983426b36c | ||
|
|
bcb3c3b2ec | ||
|
|
9fd383c1ef | ||
|
|
a931f4cef8 | ||
|
|
fddecaab39 | ||
|
|
e5eab82d68 | ||
|
|
8ae1d46b2a | ||
|
|
99ccb03b45 | ||
|
|
b8f2d501a5 | ||
|
|
e042806619 | ||
|
|
16f9ed7ecb | ||
|
|
3eaefefd26 | ||
|
|
03c8c3ddbc | ||
|
|
08371b9078 | ||
|
|
a6ae25f3e7 | ||
|
|
8051db6a9b | ||
|
|
d0bfdae5d8 | ||
|
|
4d681ed2e2 | ||
|
|
8ee3f53714 | ||
|
|
e8cf8a98ed | ||
|
|
9153a98694 | ||
|
|
37aa6ae816 | ||
|
|
8a35346fcf | ||
|
|
792ae4c935 | ||
|
|
a7324d828a | ||
|
|
fe739c51d3 | ||
|
|
795d2d0375 | ||
|
|
5ef36a91f6 | ||
|
|
8491d6c471 | ||
|
|
f95f22058a | ||
|
|
b5c45cebfa | ||
|
|
19a953b269 | ||
|
|
6ae970ff68 | ||
|
|
0f0ee82af9 | ||
|
|
d84b9351e1 | ||
|
|
6668258b66 | ||
|
|
9c42aab04f | ||
|
|
c771870e3e | ||
|
|
fb59ee52c1 | ||
|
|
3483910136 | ||
|
|
13e70f7bd4 | ||
|
|
210001e232 | ||
|
|
094e588ad5 | ||
|
|
f9d0cdfa47 | ||
|
|
0476058958 | ||
|
|
3e337a5639 | ||
|
|
bf0e1d1bf3 | ||
|
|
d3b65b0175 | ||
|
|
2caa7252a0 | ||
|
|
f7596de166 | ||
|
|
f5178459e8 | ||
|
|
a55f7dc66a | ||
|
|
3d9cad8953 | ||
|
|
67535b2792 | ||
|
|
2b5f3f50df | ||
|
|
111807a743 | ||
|
|
348ba8cfc4 | ||
|
|
81c72f0ca3 | ||
|
|
20de294a8c | ||
|
|
e31faaa726 | ||
|
|
4ad2921132 | ||
|
|
1fa62fa97c | ||
|
|
affa66f78f | ||
|
|
84860bc684 | ||
|
|
0cadedf9df | ||
|
|
0c3eea8d37 | ||
|
|
1d4e7e564d | ||
|
|
067aa0c6cb | ||
|
|
3d540e2a65 | ||
|
|
61d4728ffa | ||
|
|
42e0ca963c | ||
|
|
3bc40cb136 | ||
|
|
1453bbbb7e | ||
|
|
9da2d0760e | ||
|
|
18af98511a | ||
|
|
40c84d2429 | ||
|
|
c0d95f918e | ||
|
|
0947d335ce | ||
|
|
43e2b2478b | ||
|
|
a5fc1f0cbe | ||
|
|
bf3059ba04 | ||
|
|
319b534d4f | ||
|
|
b7c2510098 | ||
|
|
07205b8008 | ||
|
|
165a385844 | ||
|
|
fa76233151 | ||
|
|
22b7877ee9 | ||
|
|
bd9a1bb330 | ||
|
|
1b5efaa937 | ||
|
|
05ce4c5c1a | ||
|
|
e022f630b2 | ||
|
|
a73ff87653 | ||
|
|
70d6a8699b | ||
|
|
118c633878 | ||
|
|
fa5985f60b | ||
|
|
c1309e9a13 | ||
|
|
718eef7999 | ||
|
|
afdd282adf | ||
|
|
1302f18fab | ||
|
|
fc89670ea4 | ||
|
|
225320ea7d | ||
|
|
b2cf3bb0c4 | ||
|
|
e36a8ade42 | ||
|
|
2ef67d92c4 | ||
|
|
1c9af6b1c2 | ||
|
|
4f47ffa21e | ||
|
|
3d1026f829 | ||
|
|
af6ecc5d47 | ||
|
|
025c556626 | ||
|
|
0b342d6c49 | ||
|
|
ada57b1052 | ||
|
|
cf773985b1 | ||
|
|
75a14ad9cb | ||
|
|
fbe90cbc5a | ||
|
|
f0bc895952 | ||
|
|
f5c10c1a50 | ||
|
|
c4736c730e | ||
|
|
d2c96c733c | ||
|
|
d0dd9de4d7 | ||
|
|
159244974d | ||
|
|
933926fbb4 | ||
|
|
2c95f036ba | ||
|
|
c5c0b52eb7 | ||
|
|
561b62cafe | ||
|
|
55520cd7f6 | ||
|
|
e27d41839e | ||
|
|
569a0ddfaa | ||
|
|
de63263dbb | ||
|
|
8adb7055c0 | ||
|
|
11e09c5b56 | ||
|
|
ac32f74eba | ||
|
|
d49b8c3839 | ||
|
|
054fd43a0d | ||
|
|
423c5da25e | ||
|
|
9a4a8a10f2 | ||
|
|
d15992142e | ||
|
|
fed3948f85 | ||
|
|
b77342a648 | ||
|
|
04f407aba0 | ||
|
|
b122fae04c | ||
|
|
8a4efd0047 | ||
|
|
4407526d96 | ||
|
|
1d91416348 | ||
|
|
a1bbcc20e3 | ||
|
|
2b779920d7 | ||
|
|
de79fd4c43 | ||
|
|
8dfaa34d03 | ||
|
|
0d0dc07903 | ||
|
|
e38a064928 |
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
|
||||
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -27,15 +27,16 @@ jobs:
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_SC_KEY/AMS2MQTT_SC_KEY=\\"${{secrets.AMS2MQTT_SC_KEY}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@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 +51,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://${{ secrets.AWS_S3_BUCKET }}/language/
|
||||
85
.github/workflows/prerelease.yml
vendored
Normal file
85
.github/workflows/prerelease.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Release candidate build and upload
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+'
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Get release version for filenames
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
|
||||
|
||||
- name: Create release with release notes
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: Release candidate v${{ steps.release_tag.outputs.tag }}
|
||||
prerelease: true
|
||||
|
||||
outputs:
|
||||
version: ${{ steps.release_tag.outputs.tag }}
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
esp32s2:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s2
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32s3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32c3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32c3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp32solo:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32solo
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
esp8266:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp8266
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
subfolder: /rc
|
||||
is_esp32: false
|
||||
136
.github/workflows/release-deploy-env.yml
vendored
Normal file
136
.github/workflows/release-deploy-env.yml
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
name: Build with env and deploy
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
env:
|
||||
description: 'The environment to build for'
|
||||
required: true
|
||||
type: string
|
||||
upload_url:
|
||||
description: 'The upload URL for the release assets'
|
||||
required: true
|
||||
type: string
|
||||
version:
|
||||
description: 'The version tag for the release assets'
|
||||
required: true
|
||||
type: string
|
||||
subfolder:
|
||||
description: 'The subfolder in S3 to upload the binary to'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
is_esp32:
|
||||
description: 'Whether the build is for ESP32 based firmware'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: eu-north-1
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Cache PlatformIO dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.pio/libdeps
|
||||
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
|
||||
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_SC_KEY/AMS2MQTT_SC_KEY=\\"${{secrets.AMS2MQTT_SC_KEY}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '19.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: false
|
||||
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
|
||||
- name: Build firmware
|
||||
env:
|
||||
GITHUB_TAG: v${{ inputs.version }}
|
||||
run: pio run -e ${{ inputs.env }}
|
||||
|
||||
- name: Create zip file
|
||||
run: /bin/sh scripts/${{ inputs.env }}/mkzip.sh
|
||||
|
||||
- name: Upload binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ inputs.upload_url }}
|
||||
asset_path: .pio/build/${{ inputs.env }}/firmware.bin
|
||||
asset_name: ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ inputs.upload_url }}
|
||||
asset_path: ${{ inputs.env }}.zip
|
||||
asset_name: ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Create MD5 checksum file
|
||||
run: md5sum .pio/build/${{ inputs.env }}/firmware.bin | cut -z -d ' ' -f 1 > firmware.md5
|
||||
|
||||
- name: Upload binary to S3
|
||||
run: aws s3 cp .pio/build/${{ inputs.env }}/firmware.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.bin
|
||||
|
||||
- name: Upload MD5 checksum to S3
|
||||
run: aws s3 cp firmware.md5 s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}.md5
|
||||
|
||||
- name: Upload bootloader to S3
|
||||
if: ${{ inputs.is_esp32 }}
|
||||
run: aws s3 cp .pio/build/${{ inputs.env }}/bootloader.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}-bootloader.bin
|
||||
|
||||
- name: Upload partition table to S3
|
||||
if: ${{ inputs.is_esp32 }}
|
||||
run: aws s3 cp .pio/build/${{ inputs.env }}/partitions.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}-partitions.bin
|
||||
|
||||
- name: Upload app0 to S3
|
||||
if: ${{ inputs.is_esp32 }}
|
||||
run: aws s3 cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin s3://${{ secrets.AWS_S3_BUCKET }}/firmware${{ inputs.subfolder }}/ams2mqtt-${{ inputs.env }}-${{ inputs.version }}-app0.bin
|
||||
274
.github/workflows/release.yml
vendored
274
.github/workflows/release.yml
vendored
@@ -1,217 +1,79 @@
|
||||
name: Release
|
||||
name: Release build and upload
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v1
|
||||
- name: Get release version for filenames
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
|
||||
- name: Get release version for code
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo "GITHUB_TAG=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
- name: Get release version for filenames
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
|
||||
|
||||
- name: Inject secrets into ini file
|
||||
run: |
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_KEY/AMS2MQTT_PRICE_KEY="${{secrets.AMS2MQTT_PRICE_KEY}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_PRICE_AUTHENTICATION/AMS2MQTT_PRICE_AUTHENTICATION="${{secrets.AMS2MQTT_PRICE_AUTHENTICATION}}"/g' platformio.ini
|
||||
sed -i 's/NO_AMS2MQTT_SC_KEY/AMS2MQTT_SC_KEY=\\"${{secrets.AMS2MQTT_SC_KEY}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_USER/ENERGY_SPEEDOMETER_USER=\\"${{secrets.ENERGY_SPEEDOMETER_USER}}\\"/g' platformio.ini
|
||||
sed -i 's/NO_ENERGY_SPEEDOMETER_PASS/ENERGY_SPEEDOMETER_PASS=\\"${{secrets.ENERGY_SPEEDOMETER_PASS}}\\"/g' platformio.ini
|
||||
- name: Create release with release notes
|
||||
id: create_release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: Release v${{ steps.release_tag.outputs.tag }}
|
||||
generateReleaseNotes: true
|
||||
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('platformio.ini') }}
|
||||
- name: Cache PlatformIO dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.pio/libdeps
|
||||
key: ${{ runner.os }}-pio-${{ hashFiles('platformio.ini') }}
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio css_html_js_minify
|
||||
outputs:
|
||||
version: ${{ steps.release_tag.outputs.tag }}
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
- name: Set up node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
- name: Build with node
|
||||
run: |
|
||||
cd lib/SvelteUi/app
|
||||
npm ci
|
||||
npm run build
|
||||
cd -
|
||||
env:
|
||||
CI: false
|
||||
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1.0.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
- name: Build esp8266 firmware
|
||||
run: pio run -e esp8266
|
||||
- name: Create esp8266 zip file
|
||||
run: /bin/sh scripts/esp8266/mkzip.sh
|
||||
- name: Upload esp8266 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp8266/firmware.bin
|
||||
asset_name: ams2mqtt-esp8266-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp8266 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp8266.zip
|
||||
asset_name: ams2mqtt-esp8266-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32 firmware
|
||||
run: pio run -e esp32
|
||||
- name: Create esp32 zip file
|
||||
run: /bin/sh scripts/esp32/mkzip.sh
|
||||
- name: Upload esp32 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32/firmware.bin
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32.zip
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32s2 firmware
|
||||
run: pio run -e esp32s2
|
||||
- name: Create esp32s2 zip file
|
||||
run: /bin/sh scripts/esp32s2/mkzip.sh
|
||||
- name: Upload esp32s2 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32s2/firmware.bin
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32s2 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32s2.zip
|
||||
asset_name: ams2mqtt-esp32s2-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32s3 firmware
|
||||
run: pio run -e esp32s3
|
||||
- name: Create esp32s3 zip file
|
||||
run: /bin/sh scripts/esp32s3/mkzip.sh
|
||||
- name: Upload esp32s3 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32s3/firmware.bin
|
||||
asset_name: ams2mqtt-esp32s3-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32s3 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32s3.zip
|
||||
asset_name: ams2mqtt-esp32s3-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32solo firmware
|
||||
run: pio run -e esp32solo
|
||||
- name: Create esp32solo zip file
|
||||
run: /bin/sh scripts/esp32solo/mkzip.sh
|
||||
- name: Upload esp32solo binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32solo/firmware.bin
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32solo zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32solo.zip
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
|
||||
- name: Build esp32c3 firmware
|
||||
run: pio run -e esp32c3
|
||||
- name: Create esp32c3 zip file
|
||||
run: /bin/sh scripts/esp32c3/mkzip.sh
|
||||
- name: Upload esp32c3 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32c3/firmware.bin
|
||||
asset_name: ams2mqtt-esp32c3-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32c3 zip to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: esp32c3.zip
|
||||
asset_name: ams2mqtt-esp32c3-${{ steps.release_tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
esp32s2:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s2
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32s3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32c3:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32c3
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp32solo:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32solo
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
esp8266:
|
||||
needs: prepare
|
||||
uses: ./.github/workflows/release-deploy-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp8266
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
upload_url: ${{ needs.prepare.outputs.upload_url }}
|
||||
is_esp32: false
|
||||
|
||||
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
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
# AMS Reader
|
||||
This code is designed to decode data from electric smart meters installed in many countries in Europe these days. The data is presented in a graphical web interface and can also send the data to a MQTT broker which makes it suitable for home automation project. Originally it was only designed to work with Norwegian meters, but has since been adapter to read any IEC-62056-7-5 or IEC-62056-21 compliant meters.
|
||||
|
||||
Later development have added Energy usage graph for both day and month, as well as future energy price. The code can run on any ESP8266 or ESP32 hardware which you can read more about in the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki). If you don't have the knowledge to set up a ESP device yourself, or you would like to support our work, please have a look at our shop at [amsleser.no](https://amsleser.no/).
|
||||
Later development have added Energy usage graph for both day and month, as well as future energy price. The code can run on any ESP8266 or ESP32 hardware which you can read more about in the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki). If you don't have the knowledge to set up a ESP device yourself, or you would like to support our work, please have a look at our shop at [amsleser.no](https://www.amsleser.no/).
|
||||
|
||||
|
||||
<img src="images/dashboard.png">
|
||||
|
||||
Go to the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki) for information on how to get your own device! And find the latest prebuilt firmware file at the [release section](https://github.com/UtilitechAS/amsreader-firmware/releases).
|
||||
## Installing pre-built firmware
|
||||
If you have a device already running this firmware and you for some reason need to upgrade via USB port, you can use a [this web-based tool](https://www.amsleser.cloud/flasher)
|
||||
|
||||
If you are using a development board and want to flash a pre-built firmware manually, get the necessary files from the [release](https://github.com/UtilitechAS/amsreader-firmware/releases) section and visit the [WiKi](https://github.com/UtilitechAS/amsreader-firmware/wiki) and have a look at the [Flashing](https://github.com/UtilitechAS/amsreader-firmware/wiki/flashinghttps://github.com/UtilitechAS/amsreader-firmware/wiki/flashing) section
|
||||
|
||||
## Building this project with PlatformIO
|
||||
To build this project, you need [PlatformIO](https://platformio.org/) installed.
|
||||
|
||||
7
custom_partition.csv
Normal file
7
custom_partition.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x1D0000,
|
||||
app1, app, ota_1, 0x1E0000,0x1D0000,
|
||||
spiffs, data, spiffs, 0x3B0000,0x40000,
|
||||
coredump, data, coredump,0x3F0000,0x10000,
|
||||
|
BIN
doc/Austria/Smart.meter.customer.interfaces.Austria.pdf
Normal file
BIN
doc/Austria/Smart.meter.customer.interfaces.Austria.pdf
Normal file
Binary file not shown.
BIN
doc/Norway/Norwegian_payload_breakdown.docx
Normal file
BIN
doc/Norway/Norwegian_payload_breakdown.docx
Normal file
Binary file not shown.
BIN
doc/Poland/Stoen/HAN_Interface_Stoen_Operator_EN.pdf
Normal file
BIN
doc/Poland/Stoen/HAN_Interface_Stoen_Operator_EN.pdf
Normal file
Binary file not shown.
BIN
doc/Poland/Stoen/interfejs-sieci-han-w-stoen-operator-v1-0-1.pdf
Normal file
BIN
doc/Poland/Stoen/interfejs-sieci-han-w-stoen-operator-v1-0-1.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
doc/Poland/Stoen/opis-profili-rejestrowane-kody-obis-v1-0-2.pdf
Normal file
BIN
doc/Poland/Stoen/opis-profili-rejestrowane-kody-obis-v1-0-2.pdf
Normal file
Binary file not shown.
1
doc/Poland/Stoen/placeholder.txt
Normal file
1
doc/Poland/Stoen/placeholder.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -31,3 +31,59 @@ Object with three values Value Object with two values
|
||||
02 03 09 06 01 00 04 08 00 FF 06 00 0A F5 EC 02 02 0F 01 16 20
|
||||
51 D7
|
||||
7E
|
||||
|
||||
|
||||
Received 21.09.24 20:00:10:
|
||||
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
|
||||
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 02 71 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 01 F0 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 00 06 02 02 0F FF 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 05 02 02 0F FF 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 17 02 02
|
||||
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 3F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 09 36 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 22 02 02 0F FF 16 23 02 02 09
|
||||
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 09 15 06 13 00
|
||||
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
|
||||
(V) 00 8D 98 13 02 02 0F 01 16 1E 02 03 09 06 01 00
|
||||
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
|
||||
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 6E 02 02
|
||||
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
|
||||
(V) 13 F2 06 02 02 0F 01 16 20 C8 1D 7E
|
||||
|
||||
|
||||
Received 27.10.24 11:00:13:
|
||||
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
|
||||
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 02 AA 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 01 CC 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 00 02 02 02 0F FF 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 0B 02 02 0F FF 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 16 02 02
|
||||
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 09 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 0F 02 02 0F FF 16 23 02 02 09
|
||||
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 0A 1B 00 0B 00
|
||||
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
|
||||
(V) 00 8F 25 6F 02 02 0F 01 16 1E 02 03 09 06 01 00
|
||||
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
|
||||
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 8D 02 02
|
||||
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
|
||||
(V) 14 44 26 02 02 0F 01 16 20 84 09 7E
|
||||
|
||||
14
frames/EGM5G35.txt
Normal file
14
frames/EGM5G35.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
2F 45 47 4D 35 47 33 35 // DSMR Identifier
|
||||
0D 0A // CRLF
|
||||
0D 0A // CRLF
|
||||
00 // System title, blank
|
||||
82 // Two-byte length follows
|
||||
02 30 // 560 bytes
|
||||
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 1=Authentication, 0000= Security Suite ID
|
||||
00 00 00 00 // Frame counter, blank
|
||||
|
||||
// Encrypted payload follows (560 - security tag - frame counter = 555 bytes)
|
||||
30 10 73 D6 59 85 96 B0 2A 8B CC E7 33 AD 19 A3 A5 4A 4D 0E 3A CE F6 33 DB 57 0A 45 9A C3 5B F0 D4 C0 AA 15 B9 93 B0 E8 67 85 EC 22 CA 40 8C D7 1A B5 62 AE DF 0F 48 EA D9 E6 56 FB 84 B3 7B 47 C6 29 12 F1 EE BB 22 88 26 75 5C F8 A2 20 F4 93 F3 64 CA 7C C6 60 32 62 B0 7F F9 7F 71 0A 9F DF AB 61 89 6A 9A 10 B1 DF 94 2C 74 8F 51 B6 09 5C F2 45 6C 38 54 52 FE B6 CB 1F 42 F2 93 DC 57 BE EE 5A 0D F8 1F 53 55 0E 21 EF 46 0A 72 74 CA AB D8 9C A9 05 9E FD 54 5E CC 7B 71 40 E1 22 B2 25 5C D5 63 80 BD 97 6E 54 4B 24 A6 58 1F 79 1F 45 C7 DF C0 83 3B 7F 1E C7 43 B9 26 F6 EB 6E F2 B1 7C 99 3A 91 EA 39 72 E5 FB 72 B1 E1 24 AF 9D 19 8A 6C 50 69 97 90 D3 3D 68 97 57 C2 EC 49 EF 12 1D 11 44 46 70 46 88 88 98 37 6F B0 93 FC D2 3F 37 66 1C C7 F0 93 E3 AF 4D 3C BE 41 8E B9 48 77 A9 92 5B 42 0A 96 E5 34 4D 30 56 90 5A 08 03 CC 41 78 58 79 FD 89 82 E0 46 3D 66 AA 42 85 F4 A8 46 2D 4A E4 81 12 40 68 D6 F5 F9 11 5A 94 51 21 3C 9F 4F F7 FE B3 B5 BF 1E D2 12 CF B9 FB 28 D0 B4 82 9E 11 D8 1C FC E3 36 EC DE 8A 7C 83 76 F7 EB 3D 2C 4A B7 7F 75 91 F1 F5 18 D4 70 6A C9 3E 3D 3F 9E 0F C6 0F A7 E7 20 11 03 E7 75 4E 40 F1 5E BA 86 30 6D C0 28 C4 14 47 DF 6A EF CC FE E4 B6 23 58 2B 9D 4F BF 6A A8 93 84 F4 CD 4D FF 47 B5 C4 81 D2 DA D3 6B C3 F4 1D C4 6E E1 9B 24 30 91 7B 33 57 67 B6 E4 93 FE 8B 5B AE 8A 0D 81 DE A5 C3 3F 82 9A CE E2 BA 6D 50 39 FC 99 E6 7A 7B B4 E9 A9 84 78 AF 2D 4C D2 15 53 6B 98 2B 1F C8 3D 57 9E 49 68 40 0C D3 4A 9E D6 4F 1F 12 50 84 32 AD 62 C6 D3 42 34 46 66 65 56 49 81 F4 BC 8E C6 75 77 11 1F 29 BB 80 20 23 0A 80 D5 31 50 16 89 6F 07 4A 25 BE F0 4B 60 36 87 AE A6 2A 53 79 22 15 A5 2A C7 1D 15 7B 87 DD B5 07 3D 1C 28 C1 B6 51 47 2F 39 DE D9 B9 29 B6 78
|
||||
21 // End of DSMR (!)
|
||||
46 45 30 43 // FE0C - Checksum
|
||||
0D 0A // CRLF
|
||||
@@ -31,4 +31,183 @@ C9 95 7E // CRC and end tag
|
||||
06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05
|
||||
06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B
|
||||
09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E
|
||||
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
|
||||
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
|
||||
|
||||
|
||||
01.10.24 19:00 ish:
|
||||
(D) READY to update (internal clock 16:59:58 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 78 01 02 01 10 C4 98 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 00 FF 80 00 00 02
|
||||
(V) 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
|
||||
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
|
||||
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
|
||||
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06 00
|
||||
(V) 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00 00
|
||||
(V) 09 69 06 00 00 09 59 18 EB 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 00 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30
|
||||
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
|
||||
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
|
||||
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
|
||||
(V) 06 00 00 4C DF 06 00 00 05 0C 06 00 00 03 9B 06
|
||||
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 00 FF
|
||||
(V) 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10
|
||||
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
|
||||
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
|
||||
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
|
||||
(V) 4C DF 06 00 00 05 0C 06 00 00 03 9B 06 00 00 09
|
||||
(V) 34 06 00 00 09 69 06 00 00 09 59
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
|
||||
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
|
||||
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
|
||||
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06
|
||||
(V) 00 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00
|
||||
(V) 00 09 69 06 00 00 09 59
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:01 UTC, meter clock: 00:00:00, list type 2, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 02 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 C1 56 F5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 02 FF 80 00 00 02 01 06 00 00 13 C1
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 02 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 C1
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 C1
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:02 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 04 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC F9 5A 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 04 FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 04 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:04 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 06 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC 42 58 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 06 FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 06 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:06 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 08 FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BB DC 21 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 08 FF 80 00 00 02 01 06 00 00 13 BB
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 08 FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BB
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BB
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:08 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0A FF 80 00 00 02
|
||||
(V) 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
|
||||
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
|
||||
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
|
||||
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06 00
|
||||
(V) 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00 00
|
||||
(V) 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13 00
|
||||
(V) 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00 06
|
||||
(V) 00 97 35 DE 06 00 08 97 2F FA E5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30
|
||||
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
|
||||
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
|
||||
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
|
||||
(V) 06 00 00 4C DD 06 00 00 05 0E 06 00 00 03 90 06
|
||||
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59 09 0C
|
||||
(V) 07 E8 0A 01 02 13 00 0A FF 80 00 00 06 02 8E 4B
|
||||
(V) 5E 06 00 00 00 00 06 00 97 35 DE 06 00 08 97 2F
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0A FF
|
||||
(V) 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10
|
||||
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
|
||||
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
|
||||
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
|
||||
(V) 4C DD 06 00 00 05 0E 06 00 00 03 90 06 00 00 09
|
||||
(V) 34 06 00 00 09 69 06 00 00 09 59 09 0C 07 E8 0A
|
||||
(V) 01 02 13 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00
|
||||
(V) 00 00 00 06 00 97 35 DE 06 00 08 97 2F
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
|
||||
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
|
||||
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
|
||||
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06
|
||||
(V) 00 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00
|
||||
(V) 00 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00
|
||||
(V) 06 00 97 35 DE 06 00 08 97 2F
|
||||
(V) DLMS
|
||||
(D) READY to update (internal clock 17:00:12 UTC, meter clock: 17:00:10, list type 3, est: 1, using clock: 0)
|
||||
(D) Updating data storage using actual data
|
||||
(D) Clearing hours from 16 to 17
|
||||
(D) Clearing days from 1 to 1
|
||||
(D) Day is not happy
|
||||
(D) - normal
|
||||
(I) Saving data
|
||||
(I) Saving energy accounting
|
||||
(W) Used 1249ms to read HAN port (true)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0C FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 BC 15 50 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0C FF 80 00 00 02 01 06 00 00 13 BC
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0C FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 BC
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 BC
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 17:00:12 UTC, meter clock: 00:00:00, list type 1, est: 0)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
|
||||
(V) 00 09 0C 07 E8 0A 01 02 13 00 0E FF 80 00 00 02
|
||||
(V) 01 06 00 00 13 B7 7D EC 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
|
||||
(V) 00 0E FF 80 00 00 02 01 06 00 00 13 B7
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0E FF
|
||||
(V) 80 00 00 02 01 06 00 00 13 B7
|
||||
(D) Received valid DLMS at 31
|
||||
(V) Using application data:
|
||||
(V) 02 01 06 00 00 13 B7
|
||||
|
||||
1375
frames/KamstrupOmnipower_Bornholm_DK_5digitVoltage_9sep25.log
Normal file
1375
frames/KamstrupOmnipower_Bornholm_DK_5digitVoltage_9sep25.log
Normal file
File diff suppressed because it is too large
Load Diff
682
frames/L&G-E350_Norway.raw
Normal file
682
frames/L&G-E350_Norway.raw
Normal file
@@ -0,0 +1,682 @@
|
||||
*** Remote debug - over telnet - for ESP32 - version 3.0.5
|
||||
* Host name: ams-e603 IP:10.10.10.62 Mac address:D8:3B:DA:C4:03:E6
|
||||
* Free Heap RAM: 87952
|
||||
* ESP SDK version: 4.4.5.230722
|
||||
******************************************************
|
||||
* Commands:
|
||||
? or help -> display these help of commands
|
||||
q -> quit (close this connection)
|
||||
m -> display memory available
|
||||
v -> set debug level to verbose
|
||||
d -> set debug level to debug
|
||||
i -> set debug level to info
|
||||
w -> set debug level to warning
|
||||
e -> set debug level to errors
|
||||
s -> set debug silence on/off
|
||||
l -> show debug level
|
||||
t -> show time (millis)
|
||||
profiler:
|
||||
p -> show time between actual and last message (in millis)
|
||||
p min -> show only if time is this minimal
|
||||
P time -> set debug level to profiler
|
||||
c -> show colors
|
||||
filter:
|
||||
filter <string> -> show only debugs with this
|
||||
nofilter -> disable the filter
|
||||
* Please type the command and press enter to execute.(? or h for this help)
|
||||
***
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 2F AF 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 F4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 68 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6C 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 68 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 F4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 6C 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 68 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:06 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) F4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:07 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 F4 02 02 0F 00 16 1B A7 7F 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 F4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) F4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:10 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) E0 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:12 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 00 00 00 00 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 28
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 AE 9D 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 00 00 00 00 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 E0 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 28 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 68 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 00 00 00 00 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 28 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 68 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 00 00 00 00 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 E0 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 28 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 68 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:16 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 E0 02 02 0F 00 16 1B 18 A5 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 E0 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) E0 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:17 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:20 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:22 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 31
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 38 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 10 02 02 0F FF 16 23 4A DF 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 31 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5C 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 38 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 10 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 31 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 6E 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5C 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 38 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 10 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 31 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 6E 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5C 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 38 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 10 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:26 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:27 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:29 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:32 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 46
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 3A 49 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 A4 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 46 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 67 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5D 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 46 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 67 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5D 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 A4 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 46 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 67 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5D 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 06 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:36 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:37 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 A4 02 02 0F 00 16 1B 68 0D 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 A4 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) A4 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:39 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) D5 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:42 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B
|
||||
(V) 45 4D 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00
|
||||
(V) 00 60 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00
|
||||
(V) 00 00 00 00 00 00 00 02 02 09 06 00 00 60 01 07
|
||||
(V) FF 0A 04 5A 46 46 31 02 03 09 06 01 00 01 07 00
|
||||
(V) FF 06 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06
|
||||
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
|
||||
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C
|
||||
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
|
||||
(V) 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01
|
||||
(V) 00 1F 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03
|
||||
(V) 09 06 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16
|
||||
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02
|
||||
(V) 0F FE 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
|
||||
(V) 2E 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
|
||||
(V) FF 12 00 00 02 02 0F FF 16 23 02 03 09 06 01 00
|
||||
(V) 48 07 00 FF 12 09 06 02 02 0F FF 16 23 4A 9D 7E
|
||||
(V)
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01
|
||||
(V) 01 00 02 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31
|
||||
(V) 5F 31 02 02 09 06 00 00 60 01 00 FF 0A 10 31 33
|
||||
(V) 39 34 33 35 39 37 00 00 00 00 00 00 00 00 02 02
|
||||
(V) 09 06 00 00 60 01 07 FF 0A 04 5A 46 46 31 02 03
|
||||
(V) 09 06 01 00 01 07 00 FF 06 00 00 24 D5 02 02 0F
|
||||
(V) 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00
|
||||
(V) 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07
|
||||
(V) 00 FF 06 00 00 00 3C 02 02 0F 00 16 1D 02 03 09
|
||||
(V) 06 01 00 04 07 00 FF 06 00 00 00 00 02 02 0F 00
|
||||
(V) 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 0C 77 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 33 07 00 FF 10
|
||||
(V) 00 00 02 02 0F FE 16 21 02 03 09 06 01 00 47 07
|
||||
(V) 00 FF 10 0D 5F 02 02 0F FE 16 21 02 03 09 06 01
|
||||
(V) 00 20 07 00 FF 12 09 2E 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 34 07 00 FF 12 00 00 02 02 0F FF 16
|
||||
(V) 23 02 03 09 06 01 00 48 07 00 FF 12 09 06 02 02
|
||||
(V) 0F FF 16 23
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02
|
||||
(V) 81 FF 0A 0B 45 4D 42 52 49 51 5F 56 31 5F 31 02
|
||||
(V) 02 09 06 00 00 60 01 00 FF 0A 10 31 33 39 34 33
|
||||
(V) 35 39 37 00 00 00 00 00 00 00 00 02 02 09 06 00
|
||||
(V) 00 60 01 07 FF 0A 04 5A 46 46 31 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V) 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02
|
||||
(V) 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06
|
||||
(V) 00 00 00 3C 02 02 0F 00 16 1D 02 03 09 06 01 00
|
||||
(V) 04 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02
|
||||
(V) 03 09 06 01 00 1F 07 00 FF 10 0C 77 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 00 02
|
||||
(V) 02 0F FE 16 21 02 03 09 06 01 00 47 07 00 FF 10
|
||||
(V) 0D 5F 02 02 0F FE 16 21 02 03 09 06 01 00 20 07
|
||||
(V) 00 FF 12 09 2E 02 02 0F FF 16 23 02 03 09 06 01
|
||||
(V) 00 34 07 00 FF 12 00 00 02 02 0F FF 16 23 02 03
|
||||
(V) 09 06 01 00 48 07 00 FF 12 09 06 02 02 0F FF 16
|
||||
(V) 23
|
||||
(D) Received valid DLMS at 18 +267
|
||||
(V) Using application data:
|
||||
(V) 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 45 4D
|
||||
(V) 42 52 49 51 5F 56 31 5F 31 02 02 09 06 00 00 60
|
||||
(V) 01 00 FF 0A 10 31 33 39 34 33 35 39 37 00 00 00
|
||||
(V) 00 00 00 00 00 02 02 09 06 00 00 60 01 07 FF 0A
|
||||
(V) 04 5A 46 46 31 02 03 09 06 01 00 01 07 00 FF 06
|
||||
(V) 00 00 24 D5 02 02 0F 00 16 1B 02 03 09 06 01 00
|
||||
(V) 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02
|
||||
(V) 03 09 06 01 00 03 07 00 FF 06 00 00 00 3C 02 02
|
||||
(V) 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00
|
||||
(V) 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 1F
|
||||
(V) 07 00 FF 10 0C 77 02 02 0F FE 16 21 02 03 09 06
|
||||
(V) 01 00 33 07 00 FF 10 00 00 02 02 0F FE 16 21 02
|
||||
(V) 03 09 06 01 00 47 07 00 FF 10 0D 5F 02 02 0F FE
|
||||
(V) 16 21 02 03 09 06 01 00 20 07 00 FF 12 09 2E 02
|
||||
(V) 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12
|
||||
(V) 00 00 02 02 0F FF 16 23 02 03 09 06 01 00 48 07
|
||||
(V) 00 FF 12 09 06 02 02 0F FF 16 23
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:46 UTC, meter clock: 00:00:00, list type 2, est: 1)
|
||||
qq(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 D5 02 02 0F 00 16 1B F1 83 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 D5 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) D5 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:47 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q
|
||||
* Debug: Command received: qqqqq
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:49 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
(V) HDLC frame:
|
||||
(V) 7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00
|
||||
(V) 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00
|
||||
(V) 00 24 B8 02 02 0F 00 16 1B 3B 09 7E
|
||||
(V) LLC frame:
|
||||
(V) E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01
|
||||
(V) 00 01 07 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(V)
|
||||
(V) DLMS frame:
|
||||
(V) 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07
|
||||
(V) 00 FF 06 00 00 24 B8 02 02 0F 00 16 1B
|
||||
(D) Received valid DLMS at 18 +23
|
||||
(V) Using application data:
|
||||
(V) 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 24
|
||||
(V) B8 02 02 0F 00 16 1B
|
||||
(V) DLMS
|
||||
(D) NOT Ready to update (internal clock 12:32:51 UTC, meter clock: 00:00:00, list type 1, est: 1)
|
||||
q
|
||||
* Debug: Command received: q
|
||||
* Closing client connection ...
|
||||
1
frames/dsmr.raw
Normal file
1
frames/dsmr.raw
Normal file
@@ -0,0 +1 @@
|
||||
2F454C4C355C3235333833333633355F410D0A0D0A302D303A312E302E302832343132313232303133343857290D0A312D303A312E382E302830303034373532322E3237382A6B5768290D0A312D303A322E382E302830303030303030302E3030312A6B5768290D0A312D303A332E382E302830303030303035392E3033392A6B76617268290D0A312D303A342E382E302830303031323537302E3937362A6B76617268290D0A312D303A312E372E3028303030312E3233312A6B57290D0A312D303A322E372E3028303030302E3030302A6B57290D0A312D303A332E372E3028303030302E3030302A6B766172290D0A312D303A342E372E3028303030302E3435312A6B766172290D0A312D303A32312E372E3028303030302E3337362A6B57290D0A312D303A34312E372E3028303030302E3832342A6B57290D0A312D303A36312E372E3028303030302E3033302A6B57290D0A312D303A32322E372E3028303030302E3030302A6B57290D0A312D303A34322E372E3028303030302E3030302A6B57290D0A312D303A36322E372E3028303030302E3030302A6B57290D0A312D303A32332E372E3028303030302E3030302A6B766172290D0A312D303A34332E372E3028303030302E3030302A6B766172290D0A312D303A36332E372E3028303030302E3030302A6B766172290D0A312D303A32342E372E3028303030302E3037322A6B766172290D0A312D303A34342E372E3028303030302E3334392A6B766172290D0A312D303A36342E372E3028303030302E3032392A6B766172290D0A312D303A33322E372E30283233322E382A56290D0A312D303A35322E372E30283233332E352A56290D0A312D303A37322E372E30283233382E352A56290D0A312D303A33312E372E30283030312E382A41290D0A312D303A35312E372E30283030342E302A41290D0A312D303A37312E372E30283030302E312A41290D0A21343736300D0A
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 199 KiB |
@@ -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
|
||||
@@ -50,6 +50,22 @@
|
||||
#define LED_BEHAVIOUR_ERROR_ONLY 3
|
||||
#define LED_BEHAVIOUR_OFF 9
|
||||
|
||||
#define FIRMWARE_CHANNEL_STABLE 0
|
||||
#define FIRMWARE_CHANNEL_EARLY 1
|
||||
#define FIRMWARE_CHANNEL_RC 2
|
||||
#define FIRMWARE_CHANNEL_SNAPSHOT 3
|
||||
|
||||
#define REBOOT_CAUSE_WEB_SYSINFO_JSON 1
|
||||
#define REBOOT_CAUSE_WEB_SAVE 2
|
||||
#define REBOOT_CAUSE_WEB_REBOOT 3
|
||||
#define REBOOT_CAUSE_WEB_FACTORY_RESET 4
|
||||
#define REBOOT_CAUSE_BTN_FACTORY_RESET 5
|
||||
#define REBOOT_CAUSE_REPARTITION 6
|
||||
#define REBOOT_CAUSE_CONFIG_FILE_UPDATE 7
|
||||
#define REBOOT_CAUSE_FIRMWARE_UPDATE 8
|
||||
#define REBOOT_CAUSE_MQTT_DISCONNECTED 9
|
||||
#define REBOOT_CAUSE_SMART_CONFIG 10
|
||||
|
||||
struct ResetDataContainer {
|
||||
uint8_t cause;
|
||||
uint8_t last_cause;
|
||||
@@ -63,6 +79,7 @@ struct SystemConfig {
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[3];
|
||||
uint8_t energyspeedometer;
|
||||
uint8_t firmwareChannel;
|
||||
}; // 9
|
||||
|
||||
struct NetworkConfig {
|
||||
@@ -95,7 +112,10 @@ struct MqttConfig {
|
||||
uint8_t magic;
|
||||
bool stateUpdate;
|
||||
uint16_t stateUpdateInterval;
|
||||
}; // 680
|
||||
uint16_t timeout;
|
||||
uint8_t keepalive;
|
||||
uint8_t rebootMinutes;
|
||||
}; // 684
|
||||
|
||||
struct WebConfig {
|
||||
uint8_t security;
|
||||
@@ -155,7 +175,8 @@ struct GpioConfig {
|
||||
uint16_t vccResistorVcc;
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 21
|
||||
uint8_t powersaving;
|
||||
}; // 22
|
||||
|
||||
struct GpioConfig103 {
|
||||
uint8_t hanPin;
|
||||
@@ -204,10 +225,12 @@ struct PriceServiceConfig {
|
||||
char entsoeToken[37];
|
||||
char area[17];
|
||||
char currency[4];
|
||||
uint32_t unused1;
|
||||
bool enabled;
|
||||
uint8_t resolutionInMinutes;
|
||||
uint16_t unused2;
|
||||
}; // 64
|
||||
uint16_t unused3;
|
||||
bool enabled;
|
||||
uint16_t unused6;
|
||||
};
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
uint16_t thresholds[10];
|
||||
@@ -234,11 +257,14 @@ struct UiConfig {
|
||||
}; // 15
|
||||
|
||||
struct UpgradeInformation {
|
||||
char fromVersion[8];
|
||||
char toVersion[8];
|
||||
int16_t exitCode;
|
||||
int16_t errorCode;
|
||||
}; // 20
|
||||
char fromVersion[16];
|
||||
char toVersion[16];
|
||||
uint32_t size;
|
||||
uint16_t block_position;
|
||||
uint8_t retry_count;
|
||||
uint8_t reboot_count;
|
||||
int8_t errorCode;
|
||||
}; // 41+3
|
||||
|
||||
struct CloudConfig {
|
||||
bool enabled;
|
||||
@@ -246,7 +272,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:
|
||||
@@ -277,6 +310,8 @@ public:
|
||||
bool getWebConfig(WebConfig&);
|
||||
bool setWebConfig(WebConfig&);
|
||||
void clearWebConfig(WebConfig&);
|
||||
bool isWebChanged();
|
||||
void ackWebChange();
|
||||
|
||||
bool getMeterConfig(MeterConfig&);
|
||||
bool setMeterConfig(MeterConfig&);
|
||||
@@ -331,7 +366,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&);
|
||||
@@ -339,6 +374,13 @@ public:
|
||||
void clearCloudConfig(CloudConfig&);
|
||||
bool isCloudChanged();
|
||||
void ackCloudConfig();
|
||||
|
||||
bool getZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool setZmartChargeConfig(ZmartChargeConfig&);
|
||||
void clearZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool isZmartChargeConfigChanged();
|
||||
void ackZmartChargeConfig();
|
||||
|
||||
|
||||
void clear();
|
||||
|
||||
@@ -347,7 +389,7 @@ 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 relocateConfig103(); // 2.2.12, until, but not including 2.3
|
||||
|
||||
|
||||
@@ -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,15 +13,26 @@
|
||||
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(config.firmwareChannel > 3) {
|
||||
config.firmwareChannel = 0;
|
||||
}
|
||||
|
||||
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.firmwareChannel = 0;
|
||||
config.energyspeedometer = 0;
|
||||
memset(config.country, 0, 3);
|
||||
return false;
|
||||
@@ -37,6 +48,9 @@ bool AmsConfiguration::setSystemConfig(SystemConfig& config) {
|
||||
sysChanged |= config.dataCollectionConsent != existing.dataCollectionConsent;
|
||||
sysChanged |= strcmp(config.country, existing.country) != 0;
|
||||
sysChanged |= config.energyspeedometer != existing.energyspeedometer;
|
||||
sysChanged |= config.firmwareChannel != existing.firmwareChannel;
|
||||
} else {
|
||||
sysChanged = true;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
stripNonAscii((uint8_t*) config.country, 2);
|
||||
@@ -91,7 +105,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,10 +161,17 @@ bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_MQTT_START, config);
|
||||
EEPROM.end();
|
||||
if(config.magic != 0x7B) {
|
||||
config.stateUpdate = false;
|
||||
config.stateUpdateInterval = 10;
|
||||
config.magic = 0x7B;
|
||||
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 {
|
||||
@@ -173,6 +194,9 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
|
||||
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;
|
||||
}
|
||||
@@ -181,8 +205,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);
|
||||
@@ -205,6 +234,9 @@ void AmsConfiguration::clearMqtt(MqttConfig& config) {
|
||||
config.magic = 0x7B;
|
||||
config.stateUpdate = false;
|
||||
config.stateUpdateInterval = 10;
|
||||
config.timeout = 1000;
|
||||
config.keepalive = 60;
|
||||
config.rebootMinutes = 0;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttChanged() {
|
||||
@@ -232,9 +264,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);
|
||||
@@ -251,9 +291,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) {
|
||||
@@ -410,6 +459,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 {
|
||||
@@ -467,6 +517,7 @@ bool AmsConfiguration::getGpioConfig(GpioConfig& config) {
|
||||
if(configVersion == EEPROM_CHECK_SUM || configVersion == EEPROM_CLEARED_INDICATOR) {
|
||||
EEPROM.get(CONFIG_GPIO_START, config);
|
||||
EEPROM.end();
|
||||
if(config.powersaving > 4) config.powersaving = 0;
|
||||
return true;
|
||||
} else {
|
||||
clearGpio(config);
|
||||
@@ -540,6 +591,7 @@ void AmsConfiguration::clearGpio(GpioConfig& config, bool all) {
|
||||
config.tempAnalogSensorPin = 0xFF;
|
||||
config.vccPin = 0xFF;
|
||||
config.ledDisablePin = 0xFF;
|
||||
config.powersaving = 0;
|
||||
|
||||
if(all) {
|
||||
config.vccOffset = 0;
|
||||
@@ -612,6 +664,10 @@ bool AmsConfiguration::getPriceServiceConfig(PriceServiceConfig& config) {
|
||||
EEPROM.end();
|
||||
if(strlen(config.entsoeToken) != 0 && strlen(config.entsoeToken) != 36) {
|
||||
clearPriceServiceConfig(config);
|
||||
return false;
|
||||
}
|
||||
if(config.resolutionInMinutes != 15 && config.resolutionInMinutes != 60) {
|
||||
config.resolutionInMinutes = 60;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -627,6 +683,7 @@ bool AmsConfiguration::setPriceServiceConfig(PriceServiceConfig& config) {
|
||||
priceChanged |= strcmp(config.area, existing.area) != 0;
|
||||
priceChanged |= strcmp(config.currency, existing.currency) != 0;
|
||||
priceChanged |= config.enabled != existing.enabled;
|
||||
priceChanged |= config.resolutionInMinutes != existing.resolutionInMinutes;
|
||||
} else {
|
||||
priceChanged = true;
|
||||
}
|
||||
@@ -646,9 +703,8 @@ void AmsConfiguration::clearPriceServiceConfig(PriceServiceConfig& config) {
|
||||
memset(config.entsoeToken, 0, 37);
|
||||
memset(config.area, 0, 17);
|
||||
memset(config.currency, 0, 4);
|
||||
config.unused1 = 1000;
|
||||
config.enabled = false;
|
||||
config.unused2 = 0;
|
||||
config.resolutionInMinutes = 60;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isPriceServiceChanged() {
|
||||
@@ -667,6 +723,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;
|
||||
@@ -774,15 +831,9 @@ 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);
|
||||
|
||||
stripNonAscii((uint8_t*) upinfo.fromVersion, 8);
|
||||
stripNonAscii((uint8_t*) upinfo.toVersion, 8);
|
||||
bool AmsConfiguration::setUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
stripNonAscii((uint8_t*) upinfo.fromVersion, 16);
|
||||
stripNonAscii((uint8_t*) upinfo.toVersion, 16);
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_UPGRADE_INFO_START, upinfo);
|
||||
@@ -796,8 +847,9 @@ bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_UPGRADE_INFO_START, upinfo);
|
||||
EEPROM.end();
|
||||
if(stripNonAscii((uint8_t*) upinfo.fromVersion, 8) || stripNonAscii((uint8_t*) upinfo.toVersion, 8)) {
|
||||
if(stripNonAscii((uint8_t*) upinfo.fromVersion, 16) || stripNonAscii((uint8_t*) upinfo.toVersion, 16)) {
|
||||
clearUpgradeInformation(upinfo);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -807,10 +859,13 @@ bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
upinfo.exitCode = -1;
|
||||
memset(upinfo.fromVersion, 0, 16);
|
||||
memset(upinfo.toVersion, 0, 16);
|
||||
upinfo.errorCode = 0;
|
||||
memset(upinfo.fromVersion, 0, 8);
|
||||
memset(upinfo.toVersion, 0, 8);
|
||||
upinfo.size = 0;
|
||||
upinfo.block_position = 0;
|
||||
upinfo.retry_count = 0;
|
||||
upinfo.reboot_count = 0;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getCloudConfig(CloudConfig& config) {
|
||||
@@ -818,6 +873,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);
|
||||
@@ -831,6 +887,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 {
|
||||
@@ -849,6 +906,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);
|
||||
@@ -862,6 +920,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;
|
||||
}
|
||||
@@ -873,6 +990,7 @@ void AmsConfiguration::clear() {
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
sys.userConfigured = false;
|
||||
sys.dataCollectionConsent = 0;
|
||||
sys.firmwareChannel = 0;
|
||||
sys.energyspeedometer = 0;
|
||||
memset(sys.country, 0, 3);
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
@@ -929,6 +1047,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();
|
||||
@@ -1031,7 +1153,8 @@ bool AmsConfiguration::relocateConfig103() {
|
||||
gpio103.vccResistorGnd,
|
||||
gpio103.vccResistorVcc,
|
||||
gpio103.ledDisablePin,
|
||||
gpio103.ledBehaviour
|
||||
gpio103.ledBehaviour,
|
||||
0
|
||||
};
|
||||
|
||||
WebConfig web = {web103.security};
|
||||
@@ -1063,6 +1186,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();
|
||||
@@ -1071,6 +1198,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();
|
||||
@@ -1142,6 +1270,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"));
|
||||
}
|
||||
@@ -1195,6 +1326,7 @@ void AmsConfiguration::print(Print* debugger)
|
||||
debugger->printf_P(PSTR("Vcc pin: %i\r\n"), gpio.vccPin);
|
||||
debugger->printf_P(PSTR("LED disable pin: %i\r\n"), gpio.ledDisablePin);
|
||||
debugger->printf_P(PSTR("LED behaviour: %i\r\n"), gpio.ledBehaviour);
|
||||
debugger->printf_P(PSTR("Power saving: %i\r\n"), gpio.powersaving);
|
||||
if(gpio.vccMultiplier > 0) {
|
||||
debugger->printf_P(PSTR("Vcc multiplier: %f\r\n"), gpio.vccMultiplier / 1000.0);
|
||||
}
|
||||
@@ -1250,27 +1382,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(""));
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
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);
|
||||
@@ -79,9 +79,9 @@ 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();
|
||||
|
||||
|
||||
@@ -26,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) {
|
||||
@@ -43,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;
|
||||
}
|
||||
|
||||
@@ -56,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;
|
||||
@@ -72,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);
|
||||
@@ -86,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;
|
||||
@@ -102,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);
|
||||
@@ -116,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);
|
||||
@@ -139,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;
|
||||
@@ -169,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;
|
||||
|
||||
@@ -186,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);
|
||||
@@ -535,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;
|
||||
}
|
||||
|
||||
@@ -564,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;
|
||||
}
|
||||
|
||||
151
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
151
lib/AmsFirmwareUpdater/include/AmsFirmwareUpdater.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#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();
|
||||
|
||||
void setFirmwareChannel(uint8_t channel) {
|
||||
if(firmwareChannel != channel) {
|
||||
firmwareChannel = channel;
|
||||
lastVersionCheck = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool startFirmwareUpload(uint32_t size, const char* version);
|
||||
bool addFirmwareUploadChunk(uint8_t* buf, size_t length);
|
||||
bool completeFirmwareUpload(uint32_t size);
|
||||
|
||||
private:
|
||||
#if defined(ESP8266)
|
||||
char chipType[10] = "esp8266";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
char chipType[10] = "esp32s2";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||
char chipType[10] = "esp32s3";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
char chipType[10] = "esp32c3";
|
||||
#elif defined(ESP32)
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
char chipType[10] = "esp32solo";
|
||||
#else
|
||||
char chipType[10] = "esp32";
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RemoteDebug* debugger;
|
||||
#else
|
||||
Print* debugger;
|
||||
#endif
|
||||
HwTools* hw;
|
||||
AmsData* meterState;
|
||||
|
||||
bool updateStatusChanged = false;
|
||||
UpgradeInformation updateStatus = {"","",0,0,0,0,0};
|
||||
uint16_t lastSaveBlocksWritten = 0;
|
||||
String md5;
|
||||
|
||||
uint32_t lastVersionCheck = 0;
|
||||
uint8_t firmwareChannel;
|
||||
bool autoUpgrade;
|
||||
char nextVersion[17];
|
||||
|
||||
void getChannelName(char * buffer);
|
||||
|
||||
bool fetchNextVersion();
|
||||
bool fetchVersionDetails();
|
||||
bool fetchFirmwareChunk(HTTPClient& http);
|
||||
bool writeBufferToFlash();
|
||||
bool verifyChecksum();
|
||||
bool activateNewFirmware();
|
||||
bool writeUpdateStatus();
|
||||
bool isFlashReadyForNextUpdateVersion(uint32_t size);
|
||||
|
||||
uint8_t* buf = NULL;
|
||||
uint16_t bufPos = 0;
|
||||
|
||||
#if defined(ESP32)
|
||||
bool readPartition(uint8_t num, const esp_partition_info_t* info);
|
||||
bool writePartition(uint8_t num, const esp_partition_info_t* info);
|
||||
bool copyData(const esp_partition_info_t* src, esp_partition_info_t* dst, bool eraseFirst=true);
|
||||
bool copyFile(fs::LittleFSFS* src, fs::LittleFSFS* dst, const char* filename);
|
||||
uint8_t* extractFileData(const char* filename, size_t& size);
|
||||
void saveFileData(const char* filename, uint8_t* data, size_t size);
|
||||
|
||||
bool relocateAppToFirst(const esp_partition_t* active);
|
||||
bool findPartition(const char* label, const esp_partition_info_t* info);
|
||||
|
||||
bool hasLargeEnoughAppPartitions();
|
||||
bool canMigratePartitionTable();
|
||||
bool hasTwoSpiffs();
|
||||
bool spiffsOnCorrectLocation();
|
||||
bool hasFiles();
|
||||
|
||||
bool clearPartitionTable();
|
||||
bool writeNewPartitionChecksum(uint8_t num);
|
||||
bool writePartitionTableWithSpiffsAtOldAndApp1();
|
||||
bool writePartitionTableWithSpiffsAtApp1AndNew();
|
||||
bool writePartitionTableFinal();
|
||||
|
||||
bool moveLittleFsFromOldToApp1();
|
||||
bool moveLittleFsFromApp1ToNew();
|
||||
#elif defined(ESP8266)
|
||||
uintptr_t getFirmwareUpdateStart();
|
||||
#endif
|
||||
};
|
||||
1297
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
1297
lib/AmsFirmwareUpdater/src/AmsFirmwareUpdater.cpp
Normal file
File diff suppressed because it is too large
Load Diff
9
lib/AmsJsonGenerator/include/AmsJsonGenerator.h
Normal file
9
lib/AmsJsonGenerator/include/AmsJsonGenerator.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "AmsDataStorage.h"
|
||||
|
||||
class AmsJsonGenerator {
|
||||
public:
|
||||
static void generateDayPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize);
|
||||
static void generateMonthPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize);
|
||||
};
|
||||
17
lib/AmsJsonGenerator/src/AmsJsonGenerator.cpp
Normal file
17
lib/AmsJsonGenerator/src/AmsJsonGenerator.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "AmsJsonGenerator.h"
|
||||
|
||||
void AmsJsonGenerator::generateDayPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize) {
|
||||
uint16_t pos = snprintf_P(buf, bufSize, PSTR("{\"unit\":\"kwh\""));
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
pos += snprintf_P(buf+pos, bufSize-pos, PSTR(",\"i%02d\":%.3f,\"e%02d\":%.3f"), i, ds->getHourImport(i) / 1000.0, i, ds->getHourExport(i) / 1000.0);
|
||||
}
|
||||
snprintf_P(buf+pos, bufSize-pos, PSTR("}"));
|
||||
}
|
||||
|
||||
void AmsJsonGenerator::generateMonthPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize) {
|
||||
uint16_t pos = snprintf_P(buf, bufSize, PSTR("{\"unit\":\"kwh\""));
|
||||
for(uint8_t i = 1; i < 32; i++) {
|
||||
pos += snprintf_P(buf+pos, bufSize-pos, PSTR(",\"i%02d\":%.3f,\"e%02d\":%.3f"), i, ds->getDayImport(i) / 1000.0, i, ds->getDayExport(i) / 1000.0);
|
||||
}
|
||||
snprintf_P(buf+pos, bufSize-pos, PSTR("}"));
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "PriceService.h"
|
||||
#include "AmsFirmwareUpdater.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
@@ -22,42 +23,50 @@
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
mqtt.dropOverflow(true);
|
||||
};
|
||||
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";
|
||||
};
|
||||
#endif
|
||||
|
||||
void setCaVerification(bool);
|
||||
void setConfig(MqttConfig& mqttConfig);
|
||||
|
||||
bool connect();
|
||||
bool defaultSubscribe();
|
||||
void disconnect();
|
||||
lwmqtt_err_t lastError();
|
||||
bool connected();
|
||||
bool loop();
|
||||
bool isRebootSuggested();
|
||||
|
||||
virtual uint8_t getFormat() { return 0; };
|
||||
|
||||
virtual bool 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 publishRaw(uint8_t* raw, size_t length) { return false; };
|
||||
virtual bool publishFirmware() { return false; };
|
||||
virtual void onMessage(String &topic, String &payload) {};
|
||||
|
||||
virtual ~AmsMqttHandler() {
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient->stop();
|
||||
delete mqttSecureClient;
|
||||
}
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
@@ -77,9 +86,17 @@ protected:
|
||||
bool caVerification = true;
|
||||
WiFiClient *mqttClient = NULL;
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
boolean _connected = false;
|
||||
char* json;
|
||||
uint16_t BufferSize = 2048;
|
||||
uint64_t lastStateUpdate = 0;
|
||||
uint64_t lastSuccessfulLoop = 0;
|
||||
|
||||
String pubTopic;
|
||||
String subTopic;
|
||||
|
||||
AmsFirmwareUpdater* updater = NULL;
|
||||
bool rebootSuggested = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -99,7 +103,20 @@ bool AmsMqttHandler::connect() {
|
||||
actualClient = mqttClient;
|
||||
}
|
||||
|
||||
// This section helps with power saving on ESP32 devices by reducing timeouts
|
||||
// The timeout is multiplied by 10 because WiFiClient is retrying 10 times internally
|
||||
// Power drain for this timeout is too great when using the default 3s timeout
|
||||
// On ESP8266 the timeout is used differently and the following code causes MQTT instability
|
||||
#if defined(ESP32)
|
||||
int clientTimeout = mqttConfig.timeout / 1000;
|
||||
if(clientTimeout > 3) clientTimeout = 3; // 3000ms is default, see WiFiClient.cpp WIFI_CLIENT_DEF_CONN_TIMEOUT_MS
|
||||
actualClient->setTimeout(clientTimeout);
|
||||
// Why can't we set number of retries for write here? WiFiClient defaults to 10 (10*3s == 30s)
|
||||
#endif
|
||||
|
||||
mqttConfigChanged = false;
|
||||
mqtt.setTimeout(mqttConfig.timeout);
|
||||
mqtt.setKeepAlive(mqttConfig.keepalive);
|
||||
mqtt.begin(mqttConfig.host, mqttConfig.port, *actualClient);
|
||||
String statusTopic = String(mqttConfig.publishTopic) + "/status";
|
||||
mqtt.setWill(statusTopic.c_str(), "offline", true, 0);
|
||||
@@ -115,30 +132,20 @@ bool AmsMqttHandler::connect() {
|
||||
if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) ||
|
||||
(strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Successfully connected to MQTT\n"));
|
||||
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 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 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);
|
||||
_connected = mqtt.publish(statusTopic, "online", true, 0);
|
||||
mqtt.loop();
|
||||
defaultSubscribe();
|
||||
postConnect();
|
||||
return true;
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
{
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Failed to connect to MQTT: %d\n"), mqtt.lastError());
|
||||
#if defined(ESP8266)
|
||||
if(mqttSecureClient) {
|
||||
@@ -151,9 +158,29 @@ if (debugger->isActive(RemoteDebug::ERROR))
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::defaultSubscribe() {
|
||||
bool ret = true;
|
||||
if(!subTopic.isEmpty()) {
|
||||
if(mqtt.subscribe(subTopic)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Subscribed to [%s]\n"), subTopic.c_str());
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to [%s]\n"), subTopic.c_str());
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsMqttHandler::disconnect() {
|
||||
mqtt.disconnect();
|
||||
mqtt.loop();
|
||||
_connected = false;
|
||||
delay(10);
|
||||
yield();
|
||||
}
|
||||
@@ -163,12 +190,25 @@ lwmqtt_err_t AmsMqttHandler::lastError() {
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::connected() {
|
||||
return mqtt.connected();
|
||||
return _connected && mqtt.connected();
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::loop() {
|
||||
bool ret = mqtt.loop();
|
||||
delay(10);
|
||||
uint64_t now = millis64();
|
||||
bool ret = connected() && mqtt.loop();
|
||||
if(ret) {
|
||||
lastSuccessfulLoop = now;
|
||||
} else if(mqttConfig.rebootMinutes > 0) {
|
||||
if(now - lastSuccessfulLoop > (uint64_t) mqttConfig.rebootMinutes * 60000) {
|
||||
// Reboot the device if the MQTT connection is lost for too long
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("MQTT connection lost for over %d minutes, rebooting device\n"), mqttConfig.rebootMinutes);
|
||||
rebootSuggested = true;
|
||||
}
|
||||
}
|
||||
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
|
||||
yield();
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
@@ -176,4 +216,8 @@ bool AmsMqttHandler::loop() {
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::isRebootSuggested() {
|
||||
return rebootSuggested;
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
#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}";
|
||||
@@ -58,12 +58,13 @@ public:
|
||||
#else
|
||||
CloudConnector(Stream*);
|
||||
#endif
|
||||
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc);
|
||||
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc, PriceService* ps);
|
||||
void setMqttHandler(AmsMqttHandler* mqttHandler);
|
||||
void update(AmsData& data, EnergyAccounting& ea);
|
||||
void setPriceConfig(PriceServiceConfig&);
|
||||
void setEnergyAccountingConfig(EnergyAccountingConfig&);
|
||||
void forceUpdate();
|
||||
void forcePriceUpdate();
|
||||
void setConnectionHandler(ConnectionHandler* ch);
|
||||
String generateSeed();
|
||||
|
||||
@@ -76,6 +77,7 @@ private:
|
||||
HwTools* hw = NULL;
|
||||
ConnectionHandler* ch = NULL;
|
||||
ResetDataContainer* rdc = NULL;
|
||||
PriceService* ps = NULL;
|
||||
AmsMqttHandler* mqttHandler = NULL;
|
||||
CloudConfig config;
|
||||
PriceServiceConfig priceConfig;
|
||||
@@ -84,6 +86,7 @@ private:
|
||||
unsigned long lastEac = 0;
|
||||
HTTPClient http;
|
||||
WiFiUDP udp;
|
||||
WiFiClient tcp;
|
||||
int maxPwr = 0;
|
||||
uint8_t boardType = 0;
|
||||
char timezone[32];
|
||||
@@ -99,6 +102,7 @@ private:
|
||||
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;
|
||||
@@ -106,7 +110,6 @@ private:
|
||||
char* pers = "amsreader";
|
||||
|
||||
bool init();
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
|
||||
String meterManufacturer(uint8_t type) {
|
||||
switch(type) {
|
||||
|
||||
@@ -50,7 +50,7 @@ CloudConnector::CloudConnector(Stream* 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, NtpConfig& ntp, 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)) {
|
||||
@@ -63,6 +63,7 @@ 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);
|
||||
@@ -90,9 +91,9 @@ bool CloudConnector::init() {
|
||||
|
||||
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/public.key"), config.hostname);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Downloading public key from %s\n"), clearBuffer);
|
||||
if (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);
|
||||
@@ -129,35 +130,36 @@ debugger->printf_P(PSTR("(CloudConnector) Downloading public key from %s\n"), cl
|
||||
strlen(pers));
|
||||
if(ret != 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("mbedtls_ctr_drbg_seed return code: %d\n"), ret);
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("mbedtls_ctr_drbg_seed return code: %d\n"), ret);
|
||||
}
|
||||
return ret == 0;
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf("RSA public key read error: ");
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf("RSA public key read error: ");
|
||||
mbedtls_strerror(error_code, clearBuffer, CC_BUF_SIZE);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf("%s\n", clearBuffer);
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf("%s\n", clearBuffer);
|
||||
}
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Communication error, returned status: %d\n"), status);
|
||||
if (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());
|
||||
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());
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf(http.getString().c_str());
|
||||
|
||||
http.end();
|
||||
}
|
||||
@@ -167,23 +169,44 @@ debugger->printf(http.getString().c_str());
|
||||
}
|
||||
|
||||
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(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::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Client ID is not valid\n"));
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
@@ -197,7 +220,8 @@ debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
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;
|
||||
@@ -253,8 +277,62 @@ debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
dns2.toString().c_str()
|
||||
);
|
||||
} else if(lastPriceConfig == 0) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"price\":{\"area\":\"%s\",\"currency\":\"%s\"}"), priceConfig.area, priceConfig.currency);
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"price\":{\"area\":\"%s\",\"currency\":\"%s\",\"modifiers\":["), priceConfig.area, priceConfig.currency);
|
||||
if(ps != NULL) {
|
||||
std::vector<PriceConfig> pc = ps->getPriceConfig();
|
||||
if(pc.size() > 0) {
|
||||
for(uint8_t i = 0; i < pc.size(); i++) {
|
||||
PriceConfig& p = pc.at(i);
|
||||
|
||||
String days;
|
||||
for(uint8_t d = 0; d < 7; d++) {
|
||||
if((p.days >> d) & 0x1 == 0x1) {
|
||||
days += String(d, 10) + ",";
|
||||
}
|
||||
}
|
||||
days = days.substring(0, days.length()-1);
|
||||
|
||||
String hours;
|
||||
for(uint8_t h = 0; h < 24; h++) {
|
||||
if((p.hours >> h) & 0x1 == 0x1) {
|
||||
hours += String(h, 10) + ",";
|
||||
}
|
||||
}
|
||||
hours = hours.substring(0, hours.length()-1);
|
||||
|
||||
char start[8];
|
||||
memset(start, 0, 8);
|
||||
if(p.start_dayofmonth > 0 && p.start_month > 0) {
|
||||
snprintf_P(start, 8, PSTR("[%d,%d]"), p.start_month, p.start_dayofmonth);
|
||||
} else {
|
||||
strcpy_P(start, PSTR("null"));
|
||||
}
|
||||
|
||||
char end[8];
|
||||
memset(end, 0, 8);
|
||||
if(p.end_dayofmonth > 0 && p.end_month > 0) {
|
||||
snprintf_P(end, 8, PSTR("[%d,%d]"), p.end_month, p.end_dayofmonth);
|
||||
} else {
|
||||
strcpy_P(end, PSTR("null"));
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"type\":%d,\"name\":\"%s\",\"dir\":%d,\"days\":[%s],\"hours\":[%s],\"value\":%.4f,\"start\":%s,\"end\":%s}%s"),
|
||||
p.type,
|
||||
p.name,
|
||||
p.direction,
|
||||
days.c_str(),
|
||||
hours.c_str(),
|
||||
p.value / 10000.0,
|
||||
start,
|
||||
end,
|
||||
i == pc.size()-1 ? "" : ","
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("]}"));
|
||||
lastPriceConfig = now;
|
||||
sendData = false;
|
||||
} else if(lastEac == 0) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"accounting\":{\"hours\":%d,\"thresholds\":[%d,%d,%d,%d,%d,%d,%d,%d,%d]}"),
|
||||
eac.hours,
|
||||
@@ -271,174 +349,253 @@ debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
lastEac = now;
|
||||
}
|
||||
|
||||
float vcc = 0.0;
|
||||
int rssi = 0;
|
||||
float temperature = -127;
|
||||
if(hw != NULL) {
|
||||
vcc = hw->getVcc();
|
||||
rssi = hw->getWifiRssi();
|
||||
temperature = hw->getTemperature();
|
||||
}
|
||||
|
||||
uint8_t espStatus;
|
||||
#if defined(ESP8266)
|
||||
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
|
||||
espStatus = 1;
|
||||
} else if(vcc > 2.8 && vcc < 3.5) {
|
||||
espStatus = 1;
|
||||
} else if(vcc > 2.7 && vcc < 3.6) {
|
||||
espStatus = 2;
|
||||
} else {
|
||||
espStatus = 3;
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.1 && vcc < 3.5) {
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.0 && vcc < 3.6) {
|
||||
espStatus = 2;
|
||||
} else {
|
||||
espStatus = 3;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t hanStatus;
|
||||
if(data.getLastError() != 0) {
|
||||
hanStatus = 3;
|
||||
} else if(data.getLastUpdateMillis() == 0 && now < 30000) {
|
||||
hanStatus = 0;
|
||||
} else if(now - data.getLastUpdateMillis() < 15000) {
|
||||
hanStatus = 1;
|
||||
} else if(now - data.getLastUpdateMillis() < 30000) {
|
||||
hanStatus = 2;
|
||||
} else {
|
||||
hanStatus = 3;
|
||||
}
|
||||
|
||||
uint8_t wifiStatus;
|
||||
if(rssi > -75) {
|
||||
wifiStatus = 1;
|
||||
} else if(rssi > -95) {
|
||||
wifiStatus = 2;
|
||||
} else {
|
||||
wifiStatus = 3;
|
||||
}
|
||||
|
||||
uint8_t mqttStatus;
|
||||
if(mqttHandler == NULL) {
|
||||
mqttStatus = 0;
|
||||
} else if(mqttHandler->connected()) {
|
||||
mqttStatus = 1;
|
||||
} else if(mqttHandler->lastError() == 0) {
|
||||
mqttStatus = 2;
|
||||
} else {
|
||||
mqttStatus = 3;
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
|
||||
(uint32_t) time(nullptr),
|
||||
(uint32_t) (millis64()/1000),
|
||||
(uint32_t) (data.getLastUpdateMillis()/1000),
|
||||
data.isCounterEstimated() ? "true" : "false"
|
||||
);
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
|
||||
}
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
|
||||
}
|
||||
|
||||
if(data.getListType() > 1) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
|
||||
bool first = true;
|
||||
if(data.getL1Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
|
||||
}
|
||||
first = false;
|
||||
if(sendData) {
|
||||
float vcc = 0.0;
|
||||
int rssi = 0;
|
||||
float temperature = -127;
|
||||
if(hw != NULL) {
|
||||
vcc = hw->getVcc();
|
||||
rssi = hw->getWifiRssi();
|
||||
temperature = hw->getTemperature();
|
||||
}
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
#elif defined(ESP32)
|
||||
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.1 && vcc < 3.5) {
|
||||
espStatus = 1;
|
||||
} else if(vcc > 3.0 && vcc < 3.6) {
|
||||
espStatus = 2;
|
||||
} else {
|
||||
espStatus = 3;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t hanStatus;
|
||||
if(data.getLastError() != 0) {
|
||||
hanStatus = 3;
|
||||
} else if(data.getLastUpdateMillis() == 0 && now < 30000) {
|
||||
hanStatus = 0;
|
||||
} else if(now - data.getLastUpdateMillis() < 15000) {
|
||||
hanStatus = 1;
|
||||
} else if(now - data.getLastUpdateMillis() < 30000) {
|
||||
hanStatus = 2;
|
||||
} else {
|
||||
hanStatus = 3;
|
||||
}
|
||||
|
||||
uint8_t wifiStatus;
|
||||
if(rssi > -75) {
|
||||
wifiStatus = 1;
|
||||
} else if(rssi > -95) {
|
||||
wifiStatus = 2;
|
||||
} else {
|
||||
wifiStatus = 3;
|
||||
}
|
||||
|
||||
uint8_t mqttStatus;
|
||||
if(mqttHandler == NULL) {
|
||||
mqttStatus = 0;
|
||||
} else if(mqttHandler->connected()) {
|
||||
mqttStatus = 1;
|
||||
} else if(mqttHandler->lastError() == 0) {
|
||||
mqttStatus = 2;
|
||||
} else {
|
||||
mqttStatus = 3;
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
|
||||
(uint32_t) time(nullptr),
|
||||
(uint32_t) (millis64()/1000),
|
||||
(uint32_t) (data.getLastUpdateMillis()/1000),
|
||||
data.isCounterEstimated() ? "true" : "false"
|
||||
);
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
|
||||
}
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
|
||||
}
|
||||
|
||||
if(data.getListType() > 1) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
|
||||
bool first = true;
|
||||
if(data.getL1Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if(data.getL2Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), String(data.getL2Current(), 2).c_str(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.isL2currentMissing() ? "null" : String(data.getL2Current(), 2).c_str());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if(data.getL3Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
|
||||
}
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS,
|
||||
espStatus, 0,
|
||||
hanStatus, data.getLastError(),
|
||||
wifiStatus, 0,
|
||||
mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
|
||||
);
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
|
||||
}
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("mbedtls_rsa_pkcs1_encrypt return code: %d\n"), ret);
|
||||
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 (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("%s\n"), clearBuffer);
|
||||
return;
|
||||
}
|
||||
memset(encryptedBuffer, 0, rsa->len);
|
||||
|
||||
int maxlen = 100 * (rsa->len/128);
|
||||
udp.beginPacket(config.hostname,7443);
|
||||
int maxlen = rsa->len - 11; // 11 should be the correct padding size for PKCS1
|
||||
|
||||
Stream *stream = NULL;
|
||||
|
||||
if(config.proto == 0) {
|
||||
udp.beginPacket(config.hostname, config.port);
|
||||
stream = &udp;
|
||||
} else if(config.proto == 1) {
|
||||
if(!tcp.connected()) {
|
||||
int ret = tcp.connect(config.hostname, config.port, 1000);
|
||||
if(ret != 1) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("tcp.connect(%s, %d) return code: %d\n"), config.hostname, config.port, ret);
|
||||
return;
|
||||
}
|
||||
tcp.setTimeout((config.interval * 1000) / 2);
|
||||
}
|
||||
while(tcp.available()) tcp.read(); // Empty incoming buffer
|
||||
stream = &tcp;
|
||||
} else if(config.proto == 2) {
|
||||
if(!http.connected()) {
|
||||
http.setReuse(true);
|
||||
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/data"), config.hostname);
|
||||
if(!http.begin(clearBuffer)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Unable to start HTTP connector\n"));
|
||||
http.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(httpBuffer == NULL) {
|
||||
httpBuffer = (uint8_t*) malloc(CC_BUF_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
int sendBytes = 0;
|
||||
for(int i = 0; i < pos; i += maxlen) {
|
||||
int 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);
|
||||
int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, maxlen, (unsigned char*) (clearBuffer+i), encryptedBuffer);
|
||||
if(ret == 0) {
|
||||
udp.write(encryptedBuffer, rsa->len);
|
||||
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);
|
||||
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 (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("%s\n"), clearBuffer);
|
||||
}
|
||||
}
|
||||
udp.endPacket();
|
||||
|
||||
lastUpdate = now;
|
||||
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() {
|
||||
@@ -447,6 +604,10 @@ void CloudConnector::forceUpdate() {
|
||||
lastEac = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::forcePriceUpdate() {
|
||||
lastPriceConfig = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::setConnectionHandler(ConnectionHandler* ch) {
|
||||
this->ch = ch;
|
||||
}
|
||||
@@ -461,22 +622,6 @@ void CloudConnector::setEnergyAccountingConfig(EnergyAccountingConfig& eac) {
|
||||
this->lastEac = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
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(""));
|
||||
}
|
||||
|
||||
String CloudConnector::generateSeed() {
|
||||
uint8_t key[16];
|
||||
ESPRandom::uuid4(key);
|
||||
|
||||
@@ -60,13 +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) {
|
||||
return false; // TODO
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Board type %d incompatible with ETH\n"), sys.boardType);
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Board type %d incompatible with ETH\n"), sys.boardType);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -76,9 +84,9 @@ debugger->printf_P(PSTR("Board type %d incompatible with ETH\n"), sys.boardType)
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Connecting to Ethernet\n"));
|
||||
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)
|
||||
@@ -106,11 +114,12 @@ debugger->printf_P(PSTR("Connecting to Ethernet\n"));
|
||||
debugger->printf_P(PSTR("Static IP configuration is invalid, not using\n"));
|
||||
}
|
||||
}
|
||||
this->config = config;
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start Ethernet\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start Ethernet\n"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -119,9 +128,9 @@ debugger->printf_P(PSTR("Unable to start Ethernet\n"));
|
||||
|
||||
void EthernetConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
}
|
||||
|
||||
bool EthernetConnectionHandler::isConnected() {
|
||||
@@ -134,30 +143,37 @@ void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
connected = true;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Successfully connected to Ethernet!\n"));
|
||||
}
|
||||
if(config.ipv6 && !ETH.enableIpV6()) {
|
||||
debugger->printf_P(PSTR("Unable to enable IPv6\n"));
|
||||
}
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
IPv6Address ipv6 = getIPv6();
|
||||
if(ipv6 == IPv6Address()) {
|
||||
// No IP
|
||||
@@ -179,9 +195,9 @@ if (debugger->isActive(RemoteDebug::INFO))
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
connected = false;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("Ethernet was disconnected!\n"));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -33,9 +33,9 @@ bool WiFiClientConnectionHandler::connect(NetworkConfig config, SystemConfig sys
|
||||
|
||||
if(WiFi.getMode() != WIFI_OFF) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Not connected to WiFi, closing resources\n"));
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Not connected to WiFi, closing resources\n"));
|
||||
|
||||
disconnect(RECONNECT_TIMEOUT);
|
||||
return false;
|
||||
@@ -43,9 +43,9 @@ debugger->printf_P(PSTR("Not connected to WiFi, closing resources\n"));
|
||||
timeout = CONNECTION_TIMEOUT;
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Connecting to WiFi network: %s\n"), config.ssid);
|
||||
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
|
||||
@@ -122,9 +122,9 @@ debugger->printf_P(PSTR("Connecting to WiFi network: %s\n"), config.ssid);
|
||||
yield();
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start WiFi\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to start WiFi\n"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -222,9 +222,9 @@ void WiFiClientConnectionHandler::wifi_sta_config(wifi_config_t * wifi_config, c
|
||||
|
||||
void WiFiClientConnectionHandler::disconnect(unsigned long reconnectDelay) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Disconnecting!\n"));
|
||||
#if defined(ESP8266)
|
||||
WiFiClient::stopAll();
|
||||
#endif
|
||||
@@ -252,9 +252,9 @@ void WiFiClientConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Successfully connected to WiFi!\n"));
|
||||
if (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"));
|
||||
}
|
||||
@@ -266,9 +266,9 @@ debugger->printf_P(PSTR("Successfully connected to WiFi!\n"));
|
||||
if(esp_wifi_sta_get_negotiated_phymode(&phyMode) == ESP_OK) {
|
||||
if(phyMode > WIFI_PHY_MODE_11B) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("WiFi supports better rates than 802.11b, disabling\n"));
|
||||
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;
|
||||
@@ -307,23 +307,27 @@ debugger->printf_P(PSTR("WiFi supports better rates than 802.11b, disabling\n"))
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP: {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
{
|
||||
IPv6Address ipv6 = getIPv6();
|
||||
if(ipv6 == IPv6Address()) {
|
||||
// No IP
|
||||
@@ -351,9 +355,9 @@ if (debugger->isActive(RemoteDebug::INFO))
|
||||
default:
|
||||
if(strlen(descr) > 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
debugger->printf_P(PSTR("WiFi disconnected, reason %s\n"), descr);
|
||||
}
|
||||
disconnect(RECONNECT_TIMEOUT);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
class DomoticzMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
this->config = config;
|
||||
};
|
||||
#else
|
||||
@@ -25,12 +25,16 @@ public:
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
bool publishRaw(uint8_t* raw, size_t length);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
void setDomoticzConfig(DomoticzConfig config) {
|
||||
this->config = config;
|
||||
}
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
double energy = 0.0;
|
||||
|
||||
@@ -103,7 +103,7 @@ uint8_t DomoticzMqttHandler::getFormat() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishRaw(String data) {
|
||||
bool DomoticzMqttHandler::publishRaw(uint8_t* raw, size_t length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,35 @@
|
||||
#include "PriceService.h"
|
||||
|
||||
struct EnergyAccountingPeak {
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
struct EnergyAccountingPeak6 {
|
||||
uint8_t day;
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
struct EnergyAccountingData {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
int32_t costToday;
|
||||
int32_t costYesterday;
|
||||
int32_t costThisMonth;
|
||||
int32_t costLastMonth;
|
||||
int32_t incomeToday;
|
||||
int32_t incomeYesterday;
|
||||
int32_t incomeThisMonth;
|
||||
int32_t incomeLastMonth;
|
||||
uint32_t lastMonthImport;
|
||||
uint32_t lastMonthExport;
|
||||
uint8_t lastMonthAccuracy;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
time_t lastUpdated;
|
||||
};
|
||||
|
||||
struct EnergyAccountingData6 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
int32_t costYesterday;
|
||||
@@ -29,37 +53,7 @@ struct EnergyAccountingData {
|
||||
uint32_t lastMonthImport;
|
||||
uint32_t lastMonthExport;
|
||||
uint8_t lastMonthAccuracy;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData5 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
uint16_t incomeYesterday;
|
||||
uint16_t incomeThisMonth;
|
||||
uint16_t incomeLastMonth;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData4 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData2 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t maxHour;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
EnergyAccountingPeak6 peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingRealtimeData {
|
||||
@@ -124,7 +118,6 @@ public:
|
||||
void setData(EnergyAccountingData&);
|
||||
|
||||
void setCurrency(String currency);
|
||||
float getPriceForHour(uint8_t d, uint8_t h);
|
||||
|
||||
private:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
@@ -137,12 +130,12 @@ private:
|
||||
PriceService *ps = NULL;
|
||||
EnergyAccountingConfig *config = NULL;
|
||||
Timezone *tz = NULL;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
EnergyAccountingRealtimeData* realtimeData = NULL;
|
||||
String currency = "";
|
||||
|
||||
void calcDayCost();
|
||||
bool updateMax(uint16_t val, uint8_t day);
|
||||
bool updateMax(uint16_t val, uint8_t day, uint8_t hour);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ EnergyAccounting::EnergyAccounting(Stream* Stream, EnergyAccountingRealtimeData*
|
||||
rtd->lastImportUpdateMillis = 0;
|
||||
rtd->lastExportUpdateMillis = 0;
|
||||
}
|
||||
this->realtimeData = rtd;
|
||||
realtimeData = rtd;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config) {
|
||||
@@ -67,59 +67,55 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(!init) {
|
||||
this->realtimeData->lastImportUpdateMillis = 0;
|
||||
this->realtimeData->lastExportUpdateMillis = 0;
|
||||
this->realtimeData->currentHour = local.Hour;
|
||||
this->realtimeData->currentDay = local.Day;
|
||||
realtimeData->lastImportUpdateMillis = 0;
|
||||
realtimeData->lastExportUpdateMillis = 0;
|
||||
realtimeData->currentHour = local.Hour;
|
||||
realtimeData->currentDay = local.Day;
|
||||
if(!load()) {
|
||||
data = { 6, local.Month,
|
||||
0, 0, 0, // Cost
|
||||
0, 0, 0, // Income
|
||||
data = { 7, local.Month,
|
||||
0, 0, 0, 0, // Cost
|
||||
0, 0, 0, 0, // Income
|
||||
0, 0, 0, // Last month import, export and accuracy
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
0, 0, 0, // Peak 1
|
||||
0, 0, 0, // Peak 2
|
||||
0, 0, 0, // Peak 3
|
||||
0, 0, 0, // Peak 4
|
||||
0, 0, 0 // Peak 5
|
||||
};
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
float importPrice = getPriceForHour(PRICE_DIRECTION_IMPORT, 0);
|
||||
if(!initPrice && importPrice != PRICE_NO_VALUE) {
|
||||
if(!initPrice && ps != NULL && ps->hasPrice()) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
if(local.Hour != this->realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
if(local.Hour != realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
tmElements_t oneHrAgo, oneHrAgoLocal;
|
||||
breakTime(now-3600, oneHrAgo);
|
||||
uint16_t val = round(ds->getHourImport(oneHrAgo.Hour) / 10.0);
|
||||
|
||||
breakTime(tz->toLocal(now-3600), oneHrAgoLocal);
|
||||
ret |= updateMax(val, oneHrAgoLocal.Day);
|
||||
ret |= updateMax(val, oneHrAgoLocal.Day, oneHrAgoLocal.Hour);
|
||||
|
||||
this->realtimeData->currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
|
||||
if(local.Hour > 0) {
|
||||
calcDayCost();
|
||||
}
|
||||
realtimeData->currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
|
||||
|
||||
this->realtimeData->use = 0;
|
||||
this->realtimeData->produce = 0;
|
||||
this->realtimeData->costHour = 0;
|
||||
this->realtimeData->incomeHour = 0;
|
||||
realtimeData->use = 0;
|
||||
realtimeData->produce = 0;
|
||||
realtimeData->costHour = 0;
|
||||
realtimeData->incomeHour = 0;
|
||||
|
||||
uint8_t prevDay = this->realtimeData->currentDay;
|
||||
if(local.Day != this->realtimeData->currentDay) {
|
||||
data.costYesterday = this->realtimeData->costDay * 100;
|
||||
data.costThisMonth += this->realtimeData->costDay * 100;
|
||||
this->realtimeData->costDay = 0;
|
||||
uint8_t prevDay = realtimeData->currentDay;
|
||||
if(local.Day != realtimeData->currentDay) {
|
||||
data.costYesterday = realtimeData->costDay * 100;
|
||||
data.costThisMonth += realtimeData->costDay * 100;
|
||||
realtimeData->costDay = 0;
|
||||
|
||||
data.incomeYesterday = this->realtimeData->incomeDay * 100;
|
||||
data.incomeThisMonth += this->realtimeData->incomeDay * 100;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
data.incomeYesterday = realtimeData->incomeDay * 100;
|
||||
data.incomeThisMonth += realtimeData->incomeDay * 100;
|
||||
realtimeData->incomeDay = 0;
|
||||
|
||||
this->realtimeData->currentDay = local.Day;
|
||||
realtimeData->currentDay = local.Day;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
@@ -149,42 +145,49 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
data.lastMonthAccuracy = accuracy;
|
||||
|
||||
data.month = local.Month;
|
||||
this->realtimeData->currentThresholdIdx = 0;
|
||||
realtimeData->currentThresholdIdx = 0;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if(ret) {
|
||||
data.costToday = realtimeData->costDay * 100;
|
||||
data.incomeToday = realtimeData->incomeDay * 100;
|
||||
data.lastUpdated = now;
|
||||
}
|
||||
}
|
||||
|
||||
if(this->realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastImportUpdateMillis;
|
||||
if(realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - realtimeData->lastImportUpdateMillis;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhi > 0) {
|
||||
this->realtimeData->use += kwhi;
|
||||
realtimeData->use += kwhi;
|
||||
float importPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_IMPORT);
|
||||
if(importPrice != PRICE_NO_VALUE) {
|
||||
float cost = importPrice * kwhi;
|
||||
this->realtimeData->costHour += cost;
|
||||
this->realtimeData->costDay += cost;
|
||||
realtimeData->costHour += cost;
|
||||
realtimeData->costDay += cost;
|
||||
}
|
||||
}
|
||||
this->realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
}
|
||||
|
||||
if(amsData->getListType() > 1 && this->realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastExportUpdateMillis;
|
||||
if(amsData->getListType() > 1 && realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - realtimeData->lastExportUpdateMillis;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhe > 0) {
|
||||
this->realtimeData->produce += kwhe;
|
||||
float exportPrice = getPriceForHour(PRICE_DIRECTION_EXPORT, 0);
|
||||
realtimeData->produce += kwhe;
|
||||
float exportPrice = ps == NULL ? PRICE_NO_VALUE : ps->getCurrentPrice(PRICE_DIRECTION_EXPORT);
|
||||
if(exportPrice != PRICE_NO_VALUE) {
|
||||
float income = exportPrice * kwhe;
|
||||
this->realtimeData->incomeHour += income;
|
||||
this->realtimeData->incomeDay += income;
|
||||
realtimeData->incomeHour += income;
|
||||
realtimeData->incomeDay += income;
|
||||
}
|
||||
}
|
||||
this->realtimeData->lastExportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
realtimeData->lastExportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
}
|
||||
|
||||
if(config != NULL) {
|
||||
while(getMonthMax() > config->thresholds[this->realtimeData->currentThresholdIdx] && this->realtimeData->currentThresholdIdx < 10) this->realtimeData->currentThresholdIdx++;
|
||||
while(getMonthMax() > config->thresholds[realtimeData->currentThresholdIdx] && realtimeData->currentThresholdIdx < 10) realtimeData->currentThresholdIdx++;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -192,28 +195,36 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
|
||||
void EnergyAccounting::calcDayCost() {
|
||||
time_t now = time(nullptr);
|
||||
tmElements_t local, utc;
|
||||
tmElements_t local, utc, lastUpdateUtc;
|
||||
if(tz == NULL) return;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
if(ps == NULL) return;
|
||||
|
||||
if(getPriceForHour(PRICE_DIRECTION_IMPORT, 0) != PRICE_NO_VALUE) {
|
||||
if(initPrice) {
|
||||
this->realtimeData->costDay = 0;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
if(ps->hasPrice()) {
|
||||
breakTime(data.lastUpdated, lastUpdateUtc);
|
||||
uint8_t calcFromHour = 0;
|
||||
if(lastUpdateUtc.Day != local.Day || lastUpdateUtc.Month != local.Month || lastUpdateUtc.Year != local.Year) {
|
||||
realtimeData->costDay = 0;
|
||||
realtimeData->incomeDay = 0;
|
||||
calcFromHour = 0;
|
||||
} else {
|
||||
realtimeData->costDay = data.costToday / 100.0;
|
||||
realtimeData->incomeDay = data.incomeToday / 100.0;
|
||||
calcFromHour = lastUpdateUtc.Hour;
|
||||
}
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
for(uint8_t i = calcFromHour; i < realtimeData->currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
|
||||
float priceIn = getPriceForHour(PRICE_DIRECTION_IMPORT, i - local.Hour);
|
||||
float priceIn = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i - local.Hour);
|
||||
if(priceIn != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
this->realtimeData->costDay += priceIn * (wh / 1000.0);
|
||||
realtimeData->costDay += priceIn * (wh / 1000.0);
|
||||
}
|
||||
|
||||
float priceOut = getPriceForHour(PRICE_DIRECTION_EXPORT, i - local.Hour);
|
||||
float priceOut = ps->getPriceForRelativeHour(PRICE_DIRECTION_EXPORT, i - local.Hour);
|
||||
if(priceOut != PRICE_NO_VALUE) {
|
||||
int16_t wh = ds->getHourExport(utc.Hour);
|
||||
this->realtimeData->incomeDay += priceOut * (wh / 1000.0);
|
||||
realtimeData->incomeDay += priceOut * (wh / 1000.0);
|
||||
}
|
||||
}
|
||||
initPrice = true;
|
||||
@@ -221,7 +232,7 @@ void EnergyAccounting::calcDayCost() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseThisHour() {
|
||||
return this->realtimeData->use;
|
||||
return realtimeData->use;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseToday() {
|
||||
@@ -231,7 +242,7 @@ float EnergyAccounting::getUseToday() {
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
for(uint8_t i = 0; i < realtimeData->currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourImport(utc.Hour) / 1000.0;
|
||||
}
|
||||
@@ -242,7 +253,7 @@ float EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
@@ -253,7 +264,7 @@ float EnergyAccounting::getUseLastMonth() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedThisHour() {
|
||||
return this->realtimeData->produce;
|
||||
return realtimeData->produce;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedToday() {
|
||||
@@ -263,7 +274,7 @@ float EnergyAccounting::getProducedToday() {
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
for(uint8_t i = 0; i < realtimeData->currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourExport(utc.Hour) / 1000.0;
|
||||
}
|
||||
@@ -274,7 +285,7 @@ float EnergyAccounting::getProducedThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayExport(i) / 1000.0;
|
||||
}
|
||||
return ret + getProducedToday();
|
||||
@@ -285,11 +296,11 @@ float EnergyAccounting::getProducedLastMonth() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostThisHour() {
|
||||
return this->realtimeData->costHour;
|
||||
return realtimeData->costHour;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostToday() {
|
||||
return this->realtimeData->costDay;
|
||||
return realtimeData->costDay;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostYesterday() {
|
||||
@@ -305,11 +316,11 @@ float EnergyAccounting::getCostLastMonth() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeThisHour() {
|
||||
return this->realtimeData->incomeHour;
|
||||
return realtimeData->incomeHour;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeToday() {
|
||||
return this->realtimeData->incomeDay;
|
||||
return realtimeData->incomeDay;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeYesterday() {
|
||||
@@ -327,7 +338,7 @@ float EnergyAccounting::getIncomeLastMonth() {
|
||||
uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
if(config == NULL)
|
||||
return 0;
|
||||
return config->thresholds[this->realtimeData->currentThresholdIdx];
|
||||
return config->thresholds[realtimeData->currentThresholdIdx];
|
||||
}
|
||||
|
||||
float EnergyAccounting::getMonthMax() {
|
||||
@@ -407,85 +418,31 @@ bool EnergyAccounting::load() {
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
|
||||
if(buf[0] == 6) {
|
||||
if(buf[0] == 7) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
ret = true;
|
||||
} else if(buf[0] == 5) {
|
||||
EnergyAccountingData5* data = (EnergyAccountingData5*) buf;
|
||||
this->data = { 6, data->month,
|
||||
((uint32_t) data->costYesterday) * 10,
|
||||
((uint32_t) data->costThisMonth) * 100,
|
||||
((uint32_t) data->costLastMonth) * 100,
|
||||
((uint32_t) data->incomeYesterday) * 10,
|
||||
((uint32_t) data->incomeThisMonth) * 100,
|
||||
((uint32_t) data->incomeLastMonth) * 100,
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else if(buf[0] == 4) {
|
||||
EnergyAccountingData4* data = (EnergyAccountingData4*) buf;
|
||||
this->data = { 5, data->month,
|
||||
((uint32_t) data->costYesterday) * 10,
|
||||
((uint32_t) data->costThisMonth) * 100,
|
||||
((uint32_t) data->costLastMonth) * 100,
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else if(buf[0] == 3) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
this->data = { 5, data->month,
|
||||
data->costYesterday * 10,
|
||||
} else if(buf[0] == 6) {
|
||||
EnergyAccountingData6* data = (EnergyAccountingData6*) buf;
|
||||
this->data = { 7, data->month,
|
||||
0, // Cost today
|
||||
data->costYesterday,
|
||||
data->costThisMonth,
|
||||
data->costLastMonth,
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
0, // Income today
|
||||
data->incomeYesterday,
|
||||
data->incomeThisMonth,
|
||||
data->incomeLastMonth,
|
||||
data->lastMonthImport,
|
||||
data->lastMonthExport,
|
||||
data->lastMonthAccuracy,
|
||||
data->peaks[0].day, 0, data->peaks[0].value,
|
||||
data->peaks[1].day, 0, data->peaks[1].value,
|
||||
data->peaks[2].day, 0, data->peaks[2].value,
|
||||
data->peaks[3].day, 0, data->peaks[3].value,
|
||||
data->peaks[4].day, 0, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else {
|
||||
data = { 5, 0,
|
||||
0, 0, 0, // Cost
|
||||
0,0,0, // Income from production
|
||||
0,0,0, // Last month import, export and accuracy
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
if(buf[0] == 2) {
|
||||
EnergyAccountingData2* data = (EnergyAccountingData2*) buf;
|
||||
this->data.month = data->month;
|
||||
this->data.costYesterday = data->costYesterday * 10;
|
||||
this->data.costThisMonth = data->costThisMonth;
|
||||
this->data.costLastMonth = data->costLastMonth;
|
||||
uint8_t b = 0;
|
||||
for(uint8_t i = sizeof(this->data); i < file.size(); i+=2) {
|
||||
this->data.peaks[b].day = b;
|
||||
memcpy(&this->data.peaks[b].value, buf+i, 2);
|
||||
b++;
|
||||
if(b >= config->hours || b >= 5) break;
|
||||
}
|
||||
ret = true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
@@ -518,11 +475,12 @@ void EnergyAccounting::setData(EnergyAccountingData& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
bool EnergyAccounting::updateMax(uint16_t val, uint8_t day, uint8_t hour) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(data.peaks[i].day == day || data.peaks[i].day == 0) {
|
||||
if(val > data.peaks[i].value) {
|
||||
data.peaks[i].day = day;
|
||||
data.peaks[i].hour = hour;
|
||||
data.peaks[i].value = val;
|
||||
return true;
|
||||
}
|
||||
@@ -550,8 +508,3 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
void EnergyAccounting::setCurrency(String currency) {
|
||||
this->currency = currency;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getPriceForHour(uint8_t d, uint8_t h) {
|
||||
if(ps == NULL) return PRICE_NO_VALUE;
|
||||
return ps->getValueForHour(d, h);
|
||||
}
|
||||
@@ -15,74 +15,30 @@
|
||||
class HomeAssistantMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw, AmsFirmwareUpdater* updater, char* hostname) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#else
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
#endif
|
||||
this->boardType = boardType;
|
||||
this->hw = hw;
|
||||
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = 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);
|
||||
|
||||
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(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, hostname);
|
||||
};
|
||||
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 publishRaw(uint8_t* raw, size_t length);
|
||||
bool publishFirmware();
|
||||
|
||||
bool postConnect();
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
void setHomeAssistantConfig(HomeAssistantConfig config, char* hostname);
|
||||
private:
|
||||
String topic;
|
||||
uint8_t boardType;
|
||||
|
||||
String deviceName;
|
||||
String deviceModel;
|
||||
@@ -91,12 +47,13 @@ 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, rInit;
|
||||
bool l1Init, l2Init, l2eInit, l3Init, l3eInit, l4Init, l4eInit, rtInit, rteInit, pInit, sInit, rInit, fInit, dInit;
|
||||
bool tInit[32] = {false};
|
||||
bool prInit[38] = {false};
|
||||
uint8_t priceImportInit = 0, priceExportInit = 0;
|
||||
uint32_t lastThresholdPublish = 0;
|
||||
|
||||
HwTools* hw;
|
||||
|
||||
@@ -17,111 +17,113 @@ struct HomeAssistantSensor {
|
||||
const char* uom;
|
||||
const char* devcl;
|
||||
const char* stacl;
|
||||
const char* uid;
|
||||
};
|
||||
|
||||
|
||||
const uint8_t List1SensorCount PROGMEM = 1;
|
||||
const uint8_t List1SensorCount PROGMEM = 2;
|
||||
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
|
||||
{"Active import", "/power", "P", 30, "W", "power", "measurement"}
|
||||
{"Active import", "/power", "P", 30, "W", "power", "measurement", ""},
|
||||
{"Data timestamp", "/power", "t", 30, "", "timestamp", "", ""}
|
||||
};
|
||||
|
||||
const uint8_t List2SensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor List2Sensors[List2SensorCount] PROGMEM = {
|
||||
{"Reactive import", "/power", "Q", 30, "var", "reactive_power", "measurement"},
|
||||
{"Reactive export", "/power", "QO", 30, "var", "reactive_power", "measurement"},
|
||||
{"L1 current", "/power", "I1", 30, "A", "current", "measurement"},
|
||||
{"L2 current", "/power", "I2", 30, "A", "current", "measurement"},
|
||||
{"L3 current", "/power", "I3", 30, "A", "current", "measurement"},
|
||||
{"L1 voltage", "/power", "U1", 30, "V", "voltage", "measurement"},
|
||||
{"L2 voltage", "/power", "U2", 30, "V", "voltage", "measurement"},
|
||||
{"L3 voltage", "/power", "U3", 30, "V", "voltage", "measurement"}
|
||||
{"Reactive import", "/power", "Q", 30, "var", "reactive_power", "measurement", ""},
|
||||
{"Reactive export", "/power", "QO", 30, "var", "reactive_power", "measurement", ""},
|
||||
{"L1 current", "/power", "I1", 30, "A", "current", "measurement", ""},
|
||||
{"L2 current", "/power", "I2", 30, "A", "current", "measurement", ""},
|
||||
{"L3 current", "/power", "I3", 30, "A", "current", "measurement", ""},
|
||||
{"L1 voltage", "/power", "U1", 30, "V", "voltage", "measurement", ""},
|
||||
{"L2 voltage", "/power", "U2", 30, "V", "voltage", "measurement", ""},
|
||||
{"L3 voltage", "/power", "U3", 30, "V", "voltage", "measurement", ""}
|
||||
};
|
||||
|
||||
const uint8_t List2ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List2ExportSensors[List2ExportSensorCount] PROGMEM = {
|
||||
{"Active export", "/power", "PO", 30, "W", "power", "measurement"}
|
||||
{"Active export", "/power", "PO", 30, "W", "power", "measurement", ""}
|
||||
};
|
||||
|
||||
const uint8_t List3SensorCount PROGMEM = 3;
|
||||
const uint8_t List3SensorCount PROGMEM = 4;
|
||||
const HomeAssistantSensor List3Sensors[List3SensorCount] PROGMEM = {
|
||||
{"Accumulated active import", "/energy", "tPI", 4000, "kWh", "energy", "total_increasing"},
|
||||
{"Accumulated reactive import","/energy", "tQI", 4000, "kvarh","", "total_increasing"},
|
||||
{"Accumulated reactive export","/energy", "tQO", 4000, "kvarh","", "total_increasing"}
|
||||
{"Accumulated active import", "/energy", "tPI", 4000, "kWh", "energy", "total_increasing", ""},
|
||||
{"Accumulated reactive import","/energy", "tQI", 4000, "kvarh","", "total_increasing", ""},
|
||||
{"Accumulated reactive export","/energy", "tQO", 4000, "kvarh","", "total_increasing", ""},
|
||||
{"Meter timestamp", "/energy", "rtc", 4000, "", "timestamp", "", ""}
|
||||
};
|
||||
|
||||
const uint8_t List3ExportSensorCount PROGMEM = 1;
|
||||
const HomeAssistantSensor List3ExportSensors[List3ExportSensorCount] PROGMEM = {
|
||||
{"Accumulated active export", "/energy", "tPO", 4000, "kWh", "energy", "total_increasing"}
|
||||
{"Accumulated active export", "/energy", "tPO", 4000, "kWh", "energy", "total_increasing", ""}
|
||||
};
|
||||
|
||||
const uint8_t List4SensorCount PROGMEM = 10;
|
||||
const HomeAssistantSensor List4Sensors[List4SensorCount] PROGMEM = {
|
||||
{"Power factor", "/power", "PF", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 power factor", "/power", "PF1", 30, "%", "power_factor", "measurement"},
|
||||
{"L2 power factor", "/power", "PF2", 30, "%", "power_factor", "measurement"},
|
||||
{"L3 power factor", "/power", "PF3", 30, "%", "power_factor", "measurement"},
|
||||
{"L1 active import", "/power", "P1", 30, "W", "power", "measurement"},
|
||||
{"L2 active import", "/power", "P2", 30, "W", "power", "measurement"},
|
||||
{"L3 active import", "/power", "P3", 30, "W", "power", "measurement"},
|
||||
{"L1 accumulated active import","/power", "tPI1", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L2 accumulated active import","/power", "tPI2", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L3 accumulated active import","/power", "tPI3", 30, "kWh", "energy", "total_increasing"}
|
||||
{"Power factor", "/power", "PF", 30, "%", "power_factor", "measurement", ""},
|
||||
{"L1 power factor", "/power", "PF1", 30, "%", "power_factor", "measurement", ""},
|
||||
{"L2 power factor", "/power", "PF2", 30, "%", "power_factor", "measurement", ""},
|
||||
{"L3 power factor", "/power", "PF3", 30, "%", "power_factor", "measurement", ""},
|
||||
{"L1 active import", "/power", "P1", 30, "W", "power", "measurement", ""},
|
||||
{"L2 active import", "/power", "P2", 30, "W", "power", "measurement", ""},
|
||||
{"L3 active import", "/power", "P3", 30, "W", "power", "measurement", ""},
|
||||
{"L1 accumulated active import","/power", "tPI1", 30, "kWh", "energy", "total_increasing", ""},
|
||||
{"L2 accumulated active import","/power", "tPI2", 30, "kWh", "energy", "total_increasing", ""},
|
||||
{"L3 accumulated active import","/power", "tPI3", 30, "kWh", "energy", "total_increasing", ""}
|
||||
};
|
||||
|
||||
const uint8_t List4ExportSensorCount PROGMEM = 6;
|
||||
const HomeAssistantSensor List4ExportSensors[List4ExportSensorCount] PROGMEM = {
|
||||
{"L1 active export", "/power", "PO1", 30, "W", "power", "measurement"},
|
||||
{"L2 active export", "/power", "PO2", 30, "W", "power", "measurement"},
|
||||
{"L3 active export", "/power", "PO3", 30, "W", "power", "measurement"},
|
||||
{"L1 accumulated active export","/power", "tPO1", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L2 accumulated active export","/power", "tPO2", 30, "kWh", "energy", "total_increasing"},
|
||||
{"L3 accumulated active export","/power", "tPO3", 30, "kWh", "energy", "total_increasing"}
|
||||
{"L1 active export", "/power", "PO1", 30, "W", "power", "measurement", ""},
|
||||
{"L2 active export", "/power", "PO2", 30, "W", "power", "measurement", ""},
|
||||
{"L3 active export", "/power", "PO3", 30, "W", "power", "measurement", ""},
|
||||
{"L1 accumulated active export","/power", "tPO1", 30, "kWh", "energy", "total_increasing", ""},
|
||||
{"L2 accumulated active export","/power", "tPO2", 30, "kWh", "energy", "total_increasing", ""},
|
||||
{"L3 accumulated active export","/power", "tPO3", 30, "kWh", "energy", "total_increasing", ""}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeSensorCount PROGMEM = 8;
|
||||
const HomeAssistantSensor RealtimeSensors[RealtimeSensorCount] PROGMEM = {
|
||||
{"Month max", "/realtime","max", 120, "kWh", "energy", ""},
|
||||
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", ""},
|
||||
{"Current hour used", "/realtime","hour.use", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current hour cost", "/realtime","hour.cost", 120, "", "monetary", ""},
|
||||
{"Current day used", "/realtime","day.use", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current day cost", "/realtime","day.cost", 120, "", "monetary", ""},
|
||||
{"Current month used", "/realtime","month.use", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current month cost", "/realtime","month.cost", 120, "", "monetary", ""}
|
||||
{"Month max", "/realtime","max", 120, "kWh", "energy", "", ""},
|
||||
{"Tariff threshold", "/realtime","threshold", 120, "kWh", "energy", "", ""},
|
||||
{"Current hour used", "/realtime","hour.use", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current hour cost", "/realtime","hour.cost", 120, "", "monetary", "", ""},
|
||||
{"Current day used", "/realtime","day.use", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current day cost", "/realtime","day.cost", 120, "", "monetary", "", ""},
|
||||
{"Current month used", "/realtime","month.use", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current month cost", "/realtime","month.cost", 120, "", "monetary", "", ""}
|
||||
};
|
||||
|
||||
const uint8_t RealtimeExportSensorCount PROGMEM = 6;
|
||||
const HomeAssistantSensor RealtimeExportSensors[RealtimeExportSensorCount] PROGMEM = {
|
||||
{"Current hour produced", "/realtime","hour.produced", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current hour income", "/realtime","hour.income", 120, "", "monetary", ""},
|
||||
{"Current day produced", "/realtime","day.produced", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current day income", "/realtime","day.income", 120, "", "monetary", ""},
|
||||
{"Current month produced", "/realtime","month.produced", 120, "kWh", "energy", "total_increasing"},
|
||||
{"Current month income", "/realtime","month.income", 120, "", "monetary", ""}
|
||||
{"Current hour produced", "/realtime","hour.produced", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current hour income", "/realtime","hour.income", 120, "", "monetary", "", ""},
|
||||
{"Current day produced", "/realtime","day.produced", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current day income", "/realtime","day.income", 120, "", "monetary", "", ""},
|
||||
{"Current month produced", "/realtime","month.produced", 120, "kWh", "energy", "total_increasing", ""},
|
||||
{"Current month income", "/realtime","month.income", 120, "", "monetary", "", ""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", 4000, "kWh", "energy", ""};
|
||||
const HomeAssistantSensor RealtimeThresholdSensor PROGMEM = {"Tariff threshold %d", "/realtime", "thresholds[%d]", 4000, "kWh", "energy", ""};
|
||||
const HomeAssistantSensor RealtimePeakSensor PROGMEM = {"Current month peak %d", "/realtime", "peaks[%d]", 4000, "kWh", "energy", "", ""};
|
||||
const HomeAssistantSensor RealtimeThresholdSensor PROGMEM = {"Tariff threshold %d", "/realtime", "thresholds[%d]", 4000, "kWh", "energy", "", ""};
|
||||
|
||||
const uint8_t PriceSensorCount PROGMEM = 5;
|
||||
const HomeAssistantSensor PriceSensors[PriceSensorCount] PROGMEM = {
|
||||
{"Minimum price ahead", "/prices", "prices.min", 4000, "", "monetary", ""},
|
||||
{"Maximum price ahead", "/prices", "prices.max", 4000, "", "monetary", ""},
|
||||
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr",4000, "", "timestamp", ""},
|
||||
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr",4000, "", "timestamp", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr",4000, "", "timestamp", ""}
|
||||
{"Minimum price ahead", "/prices", "prices.min", 4000, "", "monetary", "", ""},
|
||||
{"Maximum price ahead", "/prices", "prices.max", 4000, "", "monetary", "", ""},
|
||||
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr", 4000, "", "timestamp", "", ""},
|
||||
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr", 4000, "", "timestamp", "", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr", 4000, "", "timestamp", "", ""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor PriceSensor PROGMEM = {"Price in %02d %s", "/prices", "prices['%d']", 4000, "", "monetary", ""};
|
||||
|
||||
const uint8_t SystemSensorCount PROGMEM = 3;
|
||||
const HomeAssistantSensor SystemSensors[SystemSensorCount] PROGMEM = {
|
||||
{"Status", "/state", "rssi", 180, "dBm", "signal_strength", "measurement"},
|
||||
{"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement"},
|
||||
{"Uptime", "/state", "up", 180, "s", "duration", "measurement"}
|
||||
{"Status", "/state", "rssi", 180, "dBm", "signal_strength", "measurement", ""},
|
||||
{"Supply volt", "/state", "vcc", 180, "V", "voltage", "measurement", ""},
|
||||
{"Uptime", "/state", "up", 180, "s", "duration", "measurement", ""}
|
||||
};
|
||||
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", 900, "°C", "temperature", "measurement"};
|
||||
const HomeAssistantSensor TemperatureSensor PROGMEM = {"Temperature sensor %s", "/temperatures", "temperatures['%s']", 900, "°C", "temperature", "measurement", ""};
|
||||
|
||||
const HomeAssistantSensor DataSensor PROGMEM = {"Data", "/data", "data", 900, "", "", "", ""};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"P" : %d
|
||||
"P" : %lu,
|
||||
"t" : "%s"
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"tPO" : %.3f,
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : %lu
|
||||
"rtc" : "%s",
|
||||
"t" : "%s"
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
"lv" : "%s",
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"QO" : %d,
|
||||
"P" : %lu,
|
||||
"Q" : %lu,
|
||||
"PO" : %lu,
|
||||
"QO" : %lu,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f
|
||||
"U3" : %.2f,
|
||||
"t" : "%s"
|
||||
}
|
||||
|
||||
@@ -2,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"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"stat_t" : "%s%s",
|
||||
"uniq_id" : "%s_%s",
|
||||
"obj_id" : "%s_%s",
|
||||
"unit_of_meas" : "%s",
|
||||
"val_tpl" : "{{ value_json.%s | is_defined }}",
|
||||
"expire_after" : %d,
|
||||
"dev" : {
|
||||
@@ -13,5 +12,8 @@
|
||||
"sw" : "%s",
|
||||
"mf" : "%s",
|
||||
"cu" : "%s"
|
||||
}%s%s%s%s%s%s
|
||||
}
|
||||
%s%s%s
|
||||
%s%s%s
|
||||
%s%s%s
|
||||
}
|
||||
@@ -19,8 +19,77 @@
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config, char* hostname) {
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = fInit = dInit = false;
|
||||
|
||||
if(strlen(config.discoveryNameTag) > 0) {
|
||||
snprintf_P(json, 128, PSTR("AMS reader (%s)"), config.discoveryNameTag);
|
||||
deviceName = String(json);
|
||||
snprintf_P(json, 128, PSTR("[%s] "), config.discoveryNameTag);
|
||||
sensorNamePrefix = String(json);
|
||||
} else {
|
||||
deviceName = F("AMS reader");
|
||||
sensorNamePrefix = "";
|
||||
}
|
||||
deviceModel = boardTypeToString(boardType);
|
||||
manufacturer = boardManufacturerToString(boardType);
|
||||
|
||||
deviceUid = String(hostname); // Maybe configurable in the future?
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Hostname is [%s]\n"), hostname);
|
||||
|
||||
if(strlen(config.discoveryHostname) > 0) {
|
||||
if(strncmp_P(config.discoveryHostname, PSTR("http"), 4) == 0) {
|
||||
deviceUrl = String(config.discoveryHostname);
|
||||
} else {
|
||||
snprintf_P(json, 128, PSTR("http://%s/"), config.discoveryHostname);
|
||||
deviceUrl = String(json);
|
||||
}
|
||||
} else {
|
||||
snprintf_P(json, 128, PSTR("http://%s.local/"), hostname);
|
||||
deviceUrl = String(json);
|
||||
}
|
||||
|
||||
if(strlen(config.discoveryPrefix) > 0) {
|
||||
snprintf_P(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() {
|
||||
bool ret = true;
|
||||
if(!statusTopic.isEmpty()) {
|
||||
if(mqtt.subscribe(statusTopic)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Subscribed to [%s]\n"), statusTopic.c_str());
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to [%s]\n"), statusTopic.c_str());
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
if(pubTopic.isEmpty() || !connected())
|
||||
return false;
|
||||
|
||||
if(time(nullptr) < FirmwareVersion::BuildEpoch)
|
||||
@@ -63,13 +132,31 @@ bool HomeAssistantMqttHandler::publish(AmsData* update, AmsData* previousState,
|
||||
|
||||
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(),
|
||||
@@ -83,27 +170,55 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea)
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage()
|
||||
data->getL3Voltage(),
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
publishList3Sensors();
|
||||
if(data->getActiveExportCounter() > 0.0) publishList3ExportSensors();
|
||||
|
||||
char mt[24];
|
||||
memset(mt, 0, 24);
|
||||
if(data->getMeterTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getMeterTimestamp(), tm);
|
||||
sprintf_P(mt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
char pt[24];
|
||||
memset(pt, 0, 24);
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getPackageTimestamp(), tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
snprintf_P(json, BufferSize, HA2_JSON,
|
||||
data->getActiveImportCounter(),
|
||||
data->getActiveExportCounter(),
|
||||
data->getReactiveImportCounter(),
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp()
|
||||
mt,
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/energy", json);
|
||||
return mqtt.publish(pubTopic + "/energy", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
publishList4Sensors();
|
||||
if(data->getL1ActiveExportPower() > 0 || data->getL2ActiveExportPower() > 0 || data->getL3ActiveExportPower() > 0) publishList4ExportSensors();
|
||||
|
||||
char pt[24];
|
||||
memset(pt, 0, 24);
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(data->getPackageTimestamp(), tm);
|
||||
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
}
|
||||
|
||||
snprintf_P(json, BufferSize, HA4_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
@@ -124,18 +239,19 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getPowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL1PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 100 : (int) (data->getPowerFactor() * 100),
|
||||
data->getPowerFactor() == 0 ? 100 : (int) (data->getL1PowerFactor() * 100),
|
||||
data->getPowerFactor() == 0 ? 100 : (int) (data->getL2PowerFactor() * 100),
|
||||
data->getPowerFactor() == 0 ? 100 : (int) (data->getL3PowerFactor() * 100),
|
||||
data->getL1ActiveImportCounter(),
|
||||
data->getL2ActiveImportCounter(),
|
||||
data->getL3ActiveImportCounter(),
|
||||
data->getL1ActiveExportCounter(),
|
||||
data->getL2ActiveExportCounter(),
|
||||
data->getL3ActiveExportCounter()
|
||||
data->getL3ActiveExportCounter(),
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
@@ -172,8 +288,8 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
ea->getProducedThisMonth(),
|
||||
ea->getIncomeThisMonth()
|
||||
);
|
||||
uint32_t now = millis();
|
||||
if(lastThresholdPublish == 0 || now-lastThresholdPublish > 3600000) {
|
||||
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],
|
||||
@@ -186,30 +302,36 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
conf->thresholds[7],
|
||||
conf->thresholds[8]
|
||||
);
|
||||
lastThresholdPublish = now;
|
||||
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(topic + "/realtime", json);
|
||||
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
|
||||
);
|
||||
@@ -217,17 +339,28 @@ 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() || !connected())
|
||||
return false;
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
if(!ps->hasPrice())
|
||||
return false;
|
||||
|
||||
publishPriceSensors(ps);
|
||||
@@ -240,7 +373,7 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, now, i);
|
||||
float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i);
|
||||
values[i] = val;
|
||||
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
@@ -309,72 +442,105 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{"), WiFi.macAddress().c_str());
|
||||
for(uint8_t i = 0;i < 38; i++) {
|
||||
if(values[i] == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%d\":null,"), i);
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{\"import\":["), WiFi.macAddress().c_str());
|
||||
|
||||
uint8_t currentPricePointIndex = ps->getCurrentPricePointIndex();
|
||||
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
|
||||
for(int i = currentPricePointIndex; i < numberOfPoints; i++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("null,"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%d\":%.4f,"), i, values[i]);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("%.4f,"), val);
|
||||
}
|
||||
}
|
||||
if(rteInit && ps->isExportPricesDifferentFromImport()) {
|
||||
pos--;
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"export\":["));
|
||||
for(int i = currentPricePointIndex; i < numberOfPoints; i++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("null,"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("%.4f,"), val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"),
|
||||
pos--;
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"),
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
ts1hr,
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
|
||||
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(topic + "/prices", json, true, 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() || !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;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor sensor) {
|
||||
String uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
String uid;
|
||||
if(strlen(sensor.uid) > 0) {
|
||||
uid = String(sensor.uid);
|
||||
} else {
|
||||
uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
}
|
||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||
sensorNamePrefix.c_str(),
|
||||
sensor.name,
|
||||
mqttConfig.publishTopic, sensor.topic,
|
||||
deviceUid.c_str(), uid.c_str(),
|
||||
deviceUid.c_str(), uid.c_str(),
|
||||
sensor.uom,
|
||||
sensor.path,
|
||||
sensor.ttl,
|
||||
deviceUid.c_str(),
|
||||
@@ -383,14 +549,21 @@ void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor sensor) {
|
||||
FirmwareVersion::VersionString,
|
||||
manufacturer.c_str(),
|
||||
deviceUrl.c_str(),
|
||||
|
||||
strlen_P(sensor.devcl) > 0 ? ",\"dev_cla\":\"" : "",
|
||||
strlen_P(sensor.devcl) > 0 ? (char *) FPSTR(sensor.devcl) : "",
|
||||
strlen_P(sensor.devcl) > 0 ? "\"" : "",
|
||||
|
||||
strlen_P(sensor.stacl) > 0 ? ",\"stat_cla\":\"" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : "",
|
||||
strlen_P(sensor.stacl) > 0 ? "\"" : ""
|
||||
strlen_P(sensor.stacl) > 0 ? "\"" : "",
|
||||
|
||||
strlen_P(sensor.uom) > 0 ? ",\"unit_of_meas\":\"" : "",
|
||||
strlen_P(sensor.uom) > 0 ? (char *) FPSTR(sensor.uom) : "",
|
||||
strlen_P(sensor.uom) > 0 ? "\"" : ""
|
||||
);
|
||||
mqtt.publish(discoveryTopic + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
|
||||
mqtt.publish(sensorTopic + "/" + deviceUid + "_" + uid + "/config", json, true, 0);
|
||||
loop();
|
||||
}
|
||||
|
||||
@@ -479,7 +652,8 @@ void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, Pric
|
||||
RealtimePeakSensor.ttl,
|
||||
RealtimePeakSensor.uom,
|
||||
RealtimePeakSensor.devcl,
|
||||
RealtimePeakSensor.stacl
|
||||
RealtimePeakSensor.stacl,
|
||||
RealtimePeakSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -518,7 +692,8 @@ void HomeAssistantMqttHandler::publishTemperatureSensor(uint8_t index, String id
|
||||
TemperatureSensor.ttl,
|
||||
TemperatureSensor.uom,
|
||||
TemperatureSensor.devcl,
|
||||
TemperatureSensor.stacl
|
||||
TemperatureSensor.stacl,
|
||||
TemperatureSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
tInit[index] = true;
|
||||
@@ -538,45 +713,96 @@ void HomeAssistantMqttHandler::publishPriceSensors(PriceService* ps) {
|
||||
}
|
||||
pInit = true;
|
||||
}
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
if(prInit[i]) continue;
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, i);
|
||||
if(val == PRICE_NO_VALUE) continue;
|
||||
|
||||
char name[strlen(PriceSensor.name)+2];
|
||||
snprintf(name, strlen(PriceSensor.name)+2, PriceSensor.name, i, i == 1 ? "hour" : "hours");
|
||||
char path[strlen(PriceSensor.path)+1];
|
||||
snprintf(path, strlen(PriceSensor.path)+1, PriceSensor.path, i);
|
||||
HomeAssistantSensor sensor = {
|
||||
i == 0 ? "Price current hour" : name,
|
||||
PriceSensor.topic,
|
||||
path,
|
||||
PriceSensor.ttl,
|
||||
uom.c_str(),
|
||||
PriceSensor.devcl,
|
||||
i == 0 ? "total" : PriceSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
prInit[i] = true;
|
||||
|
||||
uint8_t currentPricePointIndex = ps->getCurrentPricePointIndex();
|
||||
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
|
||||
|
||||
if(priceImportInit < numberOfPoints-currentPricePointIndex) {
|
||||
uint8_t importPriceSensorNo = 0;
|
||||
for(int pricePointIndex = currentPricePointIndex; pricePointIndex < numberOfPoints; pricePointIndex++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, pricePointIndex);
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
if(importPriceSensorNo < priceImportInit) {
|
||||
importPriceSensorNo++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t resolution = ps->getResolutionInMinutes();
|
||||
|
||||
char path[64];
|
||||
memset(path, 0, 64);
|
||||
snprintf_P(path, 64, PSTR("prices.import[%d]"), importPriceSensorNo);
|
||||
|
||||
char uid[32];
|
||||
memset(uid, 0, 32);
|
||||
snprintf_P(uid, 32, PSTR("prices%d"), importPriceSensorNo);
|
||||
|
||||
char name[64];
|
||||
if(resolution == 60)
|
||||
snprintf_P(name, 64, PSTR("Import price in %02d hour%s"), importPriceSensorNo, importPriceSensorNo == 1 ? "" : "s");
|
||||
else
|
||||
snprintf_P(name, 64, PSTR("Import price in %03d minutes"), importPriceSensorNo * resolution);
|
||||
|
||||
HomeAssistantSensor sensor = {
|
||||
importPriceSensorNo == 0 ? "Current import price" : name,
|
||||
"/prices",
|
||||
path,
|
||||
resolution * 60 + 300,
|
||||
uom.c_str(),
|
||||
"monetary",
|
||||
importPriceSensorNo == 0 ? "total" : "",
|
||||
uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
|
||||
priceImportInit = importPriceSensorNo++;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
if(priceExportInit < numberOfPoints-currentPricePointIndex) {
|
||||
uint8_t exportPriceSensorNo = 0;
|
||||
for(int pricePointIndex = currentPricePointIndex; pricePointIndex < numberOfPoints; pricePointIndex++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, pricePointIndex);
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
if(exportPriceSensorNo < priceExportInit) {
|
||||
exportPriceSensorNo++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t resolution = ps->getResolutionInMinutes();
|
||||
|
||||
char path[64];
|
||||
memset(path, 0, 64);
|
||||
snprintf_P(path, 64, PSTR("prices.export[%d]"), exportPriceSensorNo);
|
||||
|
||||
char uid[32];
|
||||
memset(uid, 0, 32);
|
||||
snprintf_P(uid, 32, PSTR("exportprices%d"), exportPriceSensorNo);
|
||||
|
||||
char name[64];
|
||||
if(resolution == 60)
|
||||
snprintf_P(name, 64, PSTR("Export price in %02d hour%s"), exportPriceSensorNo, exportPriceSensorNo == 1 ? "" : "s");
|
||||
else
|
||||
snprintf_P(name, 64, PSTR("Export price in %03d minutes"), exportPriceSensorNo * resolution);
|
||||
|
||||
HomeAssistantSensor sensor = {
|
||||
exportPriceSensorNo == 0 ? "Current export price" : name,
|
||||
"/prices",
|
||||
path,
|
||||
resolution * 60 + 300,
|
||||
uom.c_str(),
|
||||
"monetary",
|
||||
exportPriceSensorNo == 0 ? "total" : "",
|
||||
uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
|
||||
priceExportInit = exportPriceSensorNo++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
if(sInit) return;
|
||||
for(uint8_t i = 0; i < SystemSensorCount; i++) {
|
||||
@@ -599,7 +825,8 @@ void HomeAssistantMqttHandler::publishThresholdSensors() {
|
||||
RealtimeThresholdSensor.ttl,
|
||||
RealtimeThresholdSensor.uom,
|
||||
RealtimeThresholdSensor.devcl,
|
||||
RealtimeThresholdSensor.stacl
|
||||
RealtimeThresholdSensor.stacl,
|
||||
RealtimeThresholdSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -610,20 +837,68 @@ uint8_t HomeAssistantMqttHandler::getFormat() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
bool HomeAssistantMqttHandler::publishRaw(uint8_t* raw, size_t length) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(length <= 0 || length > BufferSize) return false;
|
||||
|
||||
if(!dInit) {
|
||||
// Not sure how this sensor should be defined in HA, so skipping for now
|
||||
//publishSensor(DataSensor);
|
||||
dInit = true;
|
||||
}
|
||||
|
||||
String str = toHex(raw, length);
|
||||
|
||||
snprintf_P(json, BufferSize, PSTR("{\"data\":\"%s\"}"), str.c_str());
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/data"), mqttConfig.publishTopic);
|
||||
bool ret = mqtt.publish(topic, json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishFirmware() {
|
||||
if(!fInit) {
|
||||
snprintf_P(json, BufferSize, PSTR("{\"name\":\"%sFirmware\",\"stat_t\":\"%s/firmware\",\"uniq_id\":\"%s_fwupgrade\",\"dev_cla\":\"firmware\",\"cmd_t\":\"%s\",\"pl_inst\":\"fwupgrade\"}"),
|
||||
sensorNamePrefix.c_str(),
|
||||
pubTopic.c_str(),
|
||||
deviceUid.c_str(),
|
||||
subTopic.c_str()
|
||||
);
|
||||
fInit = mqtt.publish(updateTopic + "/" + deviceUid + "/config", json, true, 0);
|
||||
loop();
|
||||
return fInit;
|
||||
}
|
||||
snprintf_P(json, BufferSize, PSTR("{\"installed_version\":\"%s\",\"latest_version\":\"%s\",\"title\":\"amsreader firmware\",\"release_url\":\"https://github.com/UtilitechAS/amsreader-firmware/releases\",\"release_summary\":\"New version %s is available\",\"update_percentage\":%s}"),
|
||||
FirmwareVersion::VersionString,
|
||||
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
|
||||
strlen(updater->getNextVersion()) == 0 ? FirmwareVersion::VersionString : updater->getNextVersion(),
|
||||
updater->getProgress() < 0 ? "null" : String(updater->getProgress(), 0)
|
||||
);
|
||||
bool ret = mqtt.publish(pubTopic + "/firmware", json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
|
||||
if(topic.equals(statusTopic)) {
|
||||
if(payload.equals("online")) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = false;
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received online status from HA, resetting sensor status\n"));
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = fInit = dInit = false;
|
||||
for(uint8_t i = 0; i < 32; i++) tInit[i] = false;
|
||||
for(uint8_t i = 0; i < 38; i++) prInit[i] = false;
|
||||
priceImportInit = 0;
|
||||
priceExportInit = 0;
|
||||
}
|
||||
} else if(topic.equals(subTopic)) {
|
||||
if(payload.equals("fwupgrade")) {
|
||||
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
|
||||
updater->setTargetVersion(updater->getNextVersion());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,26 @@ 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 vcc = 3.3; // Last known Vcc
|
||||
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
|
||||
unsigned long lastVccRead = 0;
|
||||
|
||||
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,26 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HwTools::isVoltageOptimal(float range) {
|
||||
if(boardType >= 1 && boardType <= 8 && maxVcc > 2.8) { // BUS-Power boards
|
||||
unsigned long now = millis();
|
||||
if(now - lastVccRead > 250) {
|
||||
vcc = getVcc();
|
||||
lastVccRead = now;
|
||||
}
|
||||
if(vcc > 3.4 || vcc < 2.8) {
|
||||
maxVcc = 0; // Voltage is outside the operating range, we have to assume voltage is OK
|
||||
} else if(vcc > maxVcc) {
|
||||
maxVcc = vcc;
|
||||
} else {
|
||||
float diff = min(maxVcc, (float) 3.3) - vcc;
|
||||
return diff < range;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t HwTools::getBoardType() {
|
||||
return boardType;
|
||||
}
|
||||
@@ -12,19 +12,19 @@
|
||||
class JsonMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->hw = hw;
|
||||
};
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw, AmsDataStorage* ds, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#else
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->hw = hw;
|
||||
};
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, HwTools* hw, AmsDataStorage* ds, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#endif
|
||||
this->hw = hw;
|
||||
this->ds = ds;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, PriceService* ps);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
bool publishRaw(uint8_t* raw, size_t length);
|
||||
bool publishFirmware();
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
@@ -32,6 +32,9 @@ public:
|
||||
|
||||
private:
|
||||
HwTools* hw;
|
||||
bool hasExport = false;
|
||||
AmsDataStorage* ds;
|
||||
|
||||
uint16_t appendJsonHeader(AmsData* data);
|
||||
uint16_t appendJsonFooter(EnergyAccounting* ea, uint16_t pos);
|
||||
bool publishList1(AmsData* data, EnergyAccounting* ea);
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
#include "FirmwareVersion.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "AmsJsonGenerator.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0) {
|
||||
return false;
|
||||
}
|
||||
if(!mqtt.connected()) {
|
||||
if(!connected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -44,6 +45,15 @@ bool JsonMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAcc
|
||||
ret = publishList4(&data, ea);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if(data.getListType() >= 2 && data.getActiveExportPower() > 0.0) {
|
||||
hasExport = true;
|
||||
}
|
||||
|
||||
if(data.getListType() >= 3 && data.getActiveExportCounter() > 0.0) {
|
||||
hasExport = true;
|
||||
}
|
||||
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
@@ -67,14 +77,24 @@ uint16_t JsonMqttHandler::appendJsonFooter(EnergyAccounting* ea, uint16_t pos) {
|
||||
} else {
|
||||
memset(pf, 0, 4);
|
||||
}
|
||||
|
||||
String peaks = "";
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i).value / 100.0, 2);
|
||||
}
|
||||
|
||||
return snprintf_P(json+pos, BufferSize-pos, PSTR("%s\"%sh\":%.2f,\"%sd\":%.1f,\"%st\":%d,\"%sx\":%.2f,\"%she\":%.2f,\"%sde\":%.1f%s"),
|
||||
return snprintf_P(json+pos, BufferSize-pos, PSTR("%s\"%sh\":%.3f,\"%sd\":%.2f,\"%sm\":%.1f,\"%st\":%d,\"%sx\":%.2f,\"%she\":%.3f,\"%sde\":%.2f,\"%sme\":%.1f,\"peaks\":[%s]%s"),
|
||||
strlen(pf) == 0 ? "},\"realtime\":{" : ",",
|
||||
pf,
|
||||
ea->getUseThisHour(),
|
||||
pf,
|
||||
ea->getUseToday(),
|
||||
pf,
|
||||
ea->getUseThisMonth(),
|
||||
pf,
|
||||
ea->getCurrentThreshold(),
|
||||
pf,
|
||||
ea->getMonthMax(),
|
||||
@@ -82,6 +102,9 @@ uint16_t JsonMqttHandler::appendJsonFooter(EnergyAccounting* ea, uint16_t pos) {
|
||||
ea->getProducedThisHour(),
|
||||
pf,
|
||||
ea->getProducedToday(),
|
||||
pf,
|
||||
ea->getProducedThisMonth(),
|
||||
peaks.c_str(),
|
||||
strlen(pf) == 0 ? "}" : ""
|
||||
);
|
||||
}
|
||||
@@ -272,9 +295,9 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !connected())
|
||||
return false;
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
if(!ps->hasPrice())
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
@@ -285,7 +308,7 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
float values[38];
|
||||
for(int i = 0;i < 38; i++) values[i] = PRICE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 38; i++) {
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, now, i);
|
||||
float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i);
|
||||
values[i] = val;
|
||||
|
||||
if(val == PRICE_NO_VALUE) break;
|
||||
@@ -354,38 +377,59 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
char pf[4];
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\","), WiFi.macAddress().c_str());
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
memset(pf, 0, 4);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"prices\":{"));
|
||||
} else {
|
||||
strcpy_P(pf, PSTR("pr_"));
|
||||
}
|
||||
if(mqttConfig.payloadFormat == 6) {
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\","), WiFi.macAddress().c_str());
|
||||
|
||||
for(uint8_t i = 0;i < 38; i++) {
|
||||
if(values[i] == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":null,"), pf, i);
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%s%d\":%.4f,"), pf, i, values[i]);
|
||||
for(uint8_t i = 0;i < 38; i++) {
|
||||
if(values[i] == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"pr_%d\":null,"), i);
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"pr_%d\":%.4f,"), i, values[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"%smin\":%.4f,\"%smax\":%.4f,\"%scheapest1hr\":\"%s\",\"%scheapest3hr\":\"%s\",\"%scheapest6hr\":\"%s\"}"),
|
||||
pf,
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
pf,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
pf,
|
||||
ts1hr,
|
||||
pf,
|
||||
ts3hr,
|
||||
pf,
|
||||
ts6hr
|
||||
);
|
||||
if(mqttConfig.payloadFormat != 6) {
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"pr_min\":%.4f,\"pr_max\":%.4f,\"pr_cheapest1hr\":\"%s\",\"pr_cheapest3hr\":\"%s\",\"pr_cheapest6hr\":\"%s\"}"),
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
ts1hr,
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
} else {
|
||||
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{\"import\":["), WiFi.macAddress().c_str());
|
||||
|
||||
uint8_t currentPricePointIndex = ps->getCurrentPricePointIndex();
|
||||
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
|
||||
for(int i = currentPricePointIndex; i < numberOfPoints; i++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("null,"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("%.4f,"), val);
|
||||
}
|
||||
}
|
||||
if(hasExport && ps->isExportPricesDifferentFromImport()) {
|
||||
pos--;
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"export\":["));
|
||||
for(int i = currentPricePointIndex; i < numberOfPoints; i++) {
|
||||
float val = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i);
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("null,"));
|
||||
} else {
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("%.4f,"), val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos--;
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}}"),
|
||||
min == INT16_MAX ? 0.0 : min,
|
||||
max == INT16_MIN ? 0.0 : max,
|
||||
ts1hr,
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
|
||||
}
|
||||
bool ret = false;
|
||||
if(mqttConfig.payloadFormat == 5) {
|
||||
@@ -400,7 +444,7 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !connected())
|
||||
return false;
|
||||
|
||||
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\"}"),
|
||||
@@ -428,9 +472,66 @@ uint8_t JsonMqttHandler::getFormat() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
bool JsonMqttHandler::publishRaw(uint8_t* raw, size_t length) {
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(length <= 0 || length > BufferSize) return false;
|
||||
|
||||
String str = toHex(raw, length);
|
||||
|
||||
snprintf_P(json, BufferSize, PSTR("{\"data\":\"%s\"}"), str.c_str());
|
||||
char topic[192];
|
||||
snprintf_P(topic, 192, PSTR("%s/data"), mqttConfig.publishTopic);
|
||||
bool ret = mqtt.publish(topic, json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool 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(strlen(mqttConfig.publishTopic) == 0 || !connected())
|
||||
return;
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received command [%s] to [%s]\n"), payload.c_str(), topic.c_str());
|
||||
|
||||
if(topic.equals(subTopic)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" - this is our subscribed topic\n"));
|
||||
if(payload.equals("fwupgrade")) {
|
||||
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
|
||||
updater->setTargetVersion(updater->getNextVersion());
|
||||
}
|
||||
} else if(payload.equals("dayplot")) {
|
||||
char pubTopic[192];
|
||||
snprintf_P(pubTopic, 192, PSTR("%s/dayplot"), mqttConfig.publishTopic);
|
||||
AmsJsonGenerator::generateDayPlotJson(ds, json, BufferSize);
|
||||
bool ret = mqtt.publish(pubTopic, json);
|
||||
loop();
|
||||
} else if(payload.equals("monthplot")) {
|
||||
char pubTopic[192];
|
||||
snprintf_P(pubTopic, 192, PSTR("%s/monthplot"), mqttConfig.publishTopic);
|
||||
AmsJsonGenerator::generateMonthPlotJson(ds, json, BufferSize);
|
||||
bool ret = mqtt.publish(pubTopic, json);
|
||||
loop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, Timezone* tz, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, RemoteDebug* debugger);
|
||||
#else
|
||||
IEC6205675(const char* payload, Timezone* tz, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, Stream* debugger);
|
||||
#endif
|
||||
|
||||
private:
|
||||
CosemData* getCosemDataAt(uint8_t index, const char* ptr);
|
||||
@@ -30,8 +37,9 @@ private:
|
||||
float getNumber(uint8_t* obis, int matchlength, const char* ptr);
|
||||
float getNumber(CosemData*);
|
||||
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
|
||||
time_t adjustForKnownIssues(CosemDateTime dt, Timezone* tz, uint8_t meterType);
|
||||
|
||||
uint8_t AMS_OBIS_UNKNOWN_1[4] = { 25, 9, 0, 255 };
|
||||
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 };
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _IMPULSEAMSDATA_H
|
||||
#define _IMPULSEAMSDATA_H
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include "SoftwareSerial.h"
|
||||
#endif
|
||||
|
||||
#include "KmpTalker.h"
|
||||
|
||||
class KmpCommunicator : public PassiveMeterCommunicator {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
@@ -25,19 +27,13 @@ public:
|
||||
#else
|
||||
KmpCommunicator(Stream* debugger) : PassiveMeterCommunicator(debugger) {};
|
||||
#endif
|
||||
void configure(MeterConfig&, Timezone*);
|
||||
void configure(MeterConfig&);
|
||||
bool loop();
|
||||
AmsData* getData(AmsData& meterState);
|
||||
|
||||
int getLastError();
|
||||
void getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
private:
|
||||
uint64_t lastUpdate = 0;
|
||||
uint8_t batch = 0;
|
||||
AmsData state;
|
||||
|
||||
bool readPacket();
|
||||
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
|
||||
uint8_t stuff(uint8_t* buf, uint8_t len);
|
||||
uint8_t unstuff(uint8_t* buf, uint8_t len);
|
||||
void send(uint8_t* buf, uint8_t len);
|
||||
double convertvalue(uint32_t val, uint8_t unit, uint8_t siex);
|
||||
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;
|
||||
};
|
||||
@@ -12,7 +12,22 @@
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
|
||||
struct Lng2Data_3p {
|
||||
struct Lng2Data_3p_0b {
|
||||
CosemBasic header;
|
||||
CosemLongUnsigned u1;
|
||||
CosemLongUnsigned u2;
|
||||
CosemLongUnsigned u3;
|
||||
CosemLongUnsigned i1;
|
||||
CosemLongUnsigned i2;
|
||||
CosemLongUnsigned i3;
|
||||
CosemDLongUnsigned activeImport;
|
||||
CosemDLongUnsigned activeExport;
|
||||
CosemDLongUnsigned acumulatedImport;
|
||||
CosemDLongUnsigned accumulatedExport;
|
||||
CosemString meterId;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct Lng2Data_3p_0e {
|
||||
CosemBasic header;
|
||||
CosemLongUnsigned u1;
|
||||
CosemLongUnsigned u2;
|
||||
@@ -13,6 +13,7 @@
|
||||
#endif
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "AmsMqttHandler.h"
|
||||
|
||||
class MeterCommunicator {
|
||||
public:
|
||||
@@ -22,7 +23,15 @@ public:
|
||||
virtual AmsData* getData(AmsData& meterState);
|
||||
virtual int getLastError();
|
||||
virtual bool isConfigChanged();
|
||||
virtual void ackConfigChanged();
|
||||
virtual void getCurrentConfig(MeterConfig& meterConfig);
|
||||
virtual void setMqttHandlerForDebugging(AmsMqttHandler* mqttHandler) {
|
||||
this->mqttDebug = mqttHandler;
|
||||
};
|
||||
|
||||
protected:
|
||||
AmsMqttHandler* mqttDebug = NULL;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -14,13 +14,13 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParsers.h"
|
||||
#include "Timezone.h"
|
||||
#include "PassthroughMqttHandler.h"
|
||||
#include "AmsMqttHandler.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "SoftwareSerial.h"
|
||||
#endif
|
||||
|
||||
const uint32_t AUTO_BAUD_RATES[] = { 2400, 115200 };
|
||||
const uint32_t AUTO_BAUD_RATES[] = { 2400, 9600, 115200 };
|
||||
|
||||
class PassiveMeterCommunicator : public MeterCommunicator {
|
||||
public:
|
||||
@@ -34,8 +34,11 @@ public:
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
bool isConfigChanged();
|
||||
void ackConfigChanged();
|
||||
void getCurrentConfig(MeterConfig& meterConfig);
|
||||
void setPassthroughMqttHandler(PassthroughMqttHandler*);
|
||||
void setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
};
|
||||
|
||||
HardwareSerial* getHwSerial();
|
||||
void rxerr(int err);
|
||||
@@ -50,8 +53,6 @@ protected:
|
||||
bool configChanged = false;
|
||||
Timezone* tz;
|
||||
|
||||
PassthroughMqttHandler* pt = NULL;
|
||||
|
||||
uint8_t *hanBuffer = NULL;
|
||||
uint16_t hanBufferSize = 0;
|
||||
Stream *hanSerial;
|
||||
@@ -61,11 +62,12 @@ protected:
|
||||
HardwareSerial *hwSerial = NULL;
|
||||
uint8_t rxBufferErrors = 0;
|
||||
|
||||
bool autodetect = false, validDataReceived = false;
|
||||
bool autodetect = false;
|
||||
uint8_t validDataReceived = 0;
|
||||
unsigned long meterAutodetectLastChange = 0;
|
||||
long rate = 10000;
|
||||
uint32_t autodetectBaud = 0;
|
||||
uint8_t autodetectParity = 11;
|
||||
uint8_t autodetectParity = 11; // 8E1
|
||||
bool autodetectInvert = false;
|
||||
uint8_t autodetectCount = 0;
|
||||
|
||||
@@ -88,9 +90,9 @@ protected:
|
||||
|
||||
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
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
AmsData* getData(AmsData& meterState);
|
||||
int getLastError();
|
||||
bool isConfigChanged();
|
||||
void ackConfigChanged();
|
||||
void getCurrentConfig(MeterConfig& meterConfig);
|
||||
|
||||
void onPulse(uint8_t pulses);
|
||||
@@ -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,24 +9,31 @@
|
||||
#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, Timezone* tz, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, RemoteDebug* debugger) {
|
||||
#else
|
||||
IEC6205675::IEC6205675(const char* d, Timezone* tz, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state, Stream* debugger) {
|
||||
#endif
|
||||
float val;
|
||||
char str[64];
|
||||
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
this->packageTimestamp = time(nullptr); // ctx.timestamp is mostly garbage, so we use current time as package 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) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 && tz != NULL ? 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 != NULL ? tz->toUTC(this->packageTimestamp) : 0;
|
||||
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
@@ -116,7 +123,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
if(data->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) data;
|
||||
time_t ts = decodeCosemDateTime(amst->dt);
|
||||
meterTimestamp = tz.toUTC(ts);
|
||||
meterTimestamp = tz != NULL ? tz->toUTC(ts) : ts;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,73 +147,417 @@ 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;
|
||||
@@ -260,12 +611,6 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
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();
|
||||
}
|
||||
} else {
|
||||
listType = 1;
|
||||
@@ -289,20 +634,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);
|
||||
@@ -403,12 +734,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
CosemData* meterTs = findObis(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
|
||||
if(meterTs != NULL) {
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = decodeCosemDateTime(amst->dt);
|
||||
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
|
||||
meterTimestamp = ts - 3600;
|
||||
} else {
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
this->meterTimestamp = adjustForKnownIssues(amst->dt, tz, meterType == AmsTypeUnknown ? useMeterType : meterType);
|
||||
}
|
||||
|
||||
val = getNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d)));
|
||||
@@ -551,6 +877,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;
|
||||
@@ -572,6 +914,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;
|
||||
@@ -585,6 +933,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
threePhase = true;
|
||||
}
|
||||
}
|
||||
meterId.trim();
|
||||
}
|
||||
|
||||
CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
|
||||
@@ -761,3 +1110,24 @@ time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
time_t IEC6205675::adjustForKnownIssues(CosemDateTime dt, Timezone* tz, uint8_t meterType) {
|
||||
time_t ts = decodeCosemDateTime(dt);
|
||||
int16_t deviation = ntohs(dt.deviation);
|
||||
if(deviation < -720 || deviation > 720) {
|
||||
// Time zone not specified
|
||||
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
|
||||
// Special known case
|
||||
// 21.09.24, the clock is now correct for Aidon
|
||||
// 23.10.25, the clock is now correct for Kamstrup
|
||||
ts -= 3600;
|
||||
} else if(tz != NULL) {
|
||||
// Adjust from localtime to UTC
|
||||
ts = tz->toUTC(ts);
|
||||
}
|
||||
} else if(meterType == AmsTypeAidon) {
|
||||
// 21.09.24, the clock is now correct for Aidon
|
||||
ts -= 3600;
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -183,28 +261,6 @@ LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterCo
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
lastUpdateMillis = millis64();
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
if(meterConfig->voltageMultiplier > 0) {
|
||||
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
if(meterConfig->amperageMultiplier > 0) {
|
||||
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
|
||||
}
|
||||
if(meterConfig->accumulatedMultiplier > 0) {
|
||||
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
|
||||
}
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
if(!threePhase)
|
||||
@@ -14,7 +14,33 @@ LNG2::LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, Meter
|
||||
meterType = AmsTypeLandisGyr;
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
|
||||
Lng2Data_3p* d = (Lng2Data_3p*) payload;
|
||||
Lng2Data_3p_0e* d = (Lng2Data_3p_0e*) payload;
|
||||
this->l1voltage = ntohs(d->u1.data);
|
||||
this->l2voltage = ntohs(d->u2.data);
|
||||
this->l3voltage = ntohs(d->u3.data);
|
||||
|
||||
this->l1current = ntohs(d->i1.data) / 100.0;
|
||||
this->l2current = ntohs(d->i2.data) / 100.0;
|
||||
this->l3current = ntohs(d->i3.data) / 100.0;
|
||||
|
||||
this->activeImportPower = ntohl(d->activeImport.data);
|
||||
this->activeExportPower = ntohl(d->activeExport.data);
|
||||
this->activeImportCounter = ntohl(d->acumulatedImport.data) / 1000.0;
|
||||
this->activeExportCounter = ntohl(d->accumulatedExport.data) / 1000.0;
|
||||
|
||||
char str[64];
|
||||
uint8_t str_len = getString((CosemData*) &d->meterId, str);
|
||||
if(str_len > 0) {
|
||||
this->meterId = String(str);
|
||||
}
|
||||
listType = 3;
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(h->length == 0x0b) {
|
||||
apply(meterState);
|
||||
meterType = AmsTypeLandisGyr;
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
|
||||
Lng2Data_3p_0b* d = (Lng2Data_3p_0b*) payload;
|
||||
this->l1voltage = ntohs(d->u1.data);
|
||||
this->l2voltage = ntohs(d->u2.data);
|
||||
this->l3voltage = ntohs(d->u3.data);
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "IEC6205621.h"
|
||||
#include "LNG.h"
|
||||
#include "LNG2.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <driver/uart.h>
|
||||
@@ -32,10 +33,23 @@ void PassiveMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz)
|
||||
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() {
|
||||
@@ -68,9 +82,9 @@ bool PassiveMeterCommunicator::loop() {
|
||||
hanSerial->readBytes(hanBuffer, hanBufferSize);
|
||||
len = 0;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
|
||||
return false;
|
||||
}
|
||||
hanBuffer[len++] = hanSerial->read();
|
||||
@@ -80,46 +94,46 @@ debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
|
||||
switch(ctx.type) {
|
||||
case DATA_TAG_DLMS:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid DLMS at %d\n"), pos);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid DSMR at %d\n"), pos);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid SNMR at %d\n"), pos);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid AARE at %d\n"), pos);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid Get Response at %d\n"), pos);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received valid HDLC at %d\n"), pos);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
@@ -129,26 +143,26 @@ debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
|
||||
end = millis();
|
||||
if(end-start > 1000) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
|
||||
}
|
||||
|
||||
if(pos == DATA_PARSE_INCOMPLETE) {
|
||||
return false;
|
||||
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data received\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data received\n"));
|
||||
lastError = pos;
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
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;
|
||||
@@ -160,15 +174,15 @@ if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
lastError = pos;
|
||||
printHanReadError(pos);
|
||||
len += hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(hanBuffer, len);
|
||||
if(mqttDebug != NULL) {
|
||||
mqttDebug->publishRaw(hanBuffer, len);
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
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;
|
||||
@@ -177,17 +191,17 @@ if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
|
||||
if(ctx.type == 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
|
||||
lastError = pos;
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
{
|
||||
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;
|
||||
@@ -215,25 +229,25 @@ AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
|
||||
char* payload = ((char *) (hanBuffer)) + pos;
|
||||
if(maxDetectedPayloadSize < pos) maxDetectedPayloadSize = pos;
|
||||
if(ctx.type == DATA_TAG_DLMS) {
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes((uint8_t*) payload, ctx.length);
|
||||
if(mqttDebug != NULL) {
|
||||
mqttDebug->publishRaw((uint8_t*) payload, ctx.length);
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Using application data:\n"));
|
||||
if (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);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LNG\n"));
|
||||
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();
|
||||
@@ -249,9 +263,9 @@ debugger->printf_P(PSTR("LNG\n"));
|
||||
payload[17] == CosemTypeLongUnsigned
|
||||
) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LNG2\n"));
|
||||
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();
|
||||
@@ -260,11 +274,11 @@ debugger->printf_P(PSTR("LNG2\n"));
|
||||
}
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DLMS\n"));
|
||||
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, tz, meterState.getMeterType(), &meterConfig, ctx, meterState, debugger);
|
||||
}
|
||||
} else if(ctx.type == DATA_TAG_DSMR) {
|
||||
data = new IEC6205621(payload, tz, &meterConfig);
|
||||
@@ -272,7 +286,7 @@ debugger->printf_P(PSTR("DLMS\n"));
|
||||
len = 0;
|
||||
if(data != NULL) {
|
||||
if(data->getListType() > 0) {
|
||||
validDataReceived = true;
|
||||
validDataReceived++;
|
||||
if(rxBufferErrors > 0) rxBufferErrors--;
|
||||
}
|
||||
}
|
||||
@@ -285,9 +299,9 @@ int PassiveMeterCommunicator::getLastError() {
|
||||
if(hwSerial != NULL) {
|
||||
if(hwSerial->hasRxError()) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial RX error\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial RX error\n"));
|
||||
lastError = 96;
|
||||
}
|
||||
if(hwSerial->hasOverrun()) {
|
||||
@@ -306,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;
|
||||
@@ -348,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:
|
||||
@@ -360,9 +381,9 @@ int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &co
|
||||
break;
|
||||
default:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
|
||||
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;
|
||||
@@ -371,9 +392,9 @@ debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\
|
||||
}
|
||||
if(context.length > end) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
|
||||
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;
|
||||
@@ -381,78 +402,78 @@ debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
|
||||
switch(tag) {
|
||||
case DATA_TAG_HDLC:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HDLC frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(buf, curLen);
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HDLC frame:\n"));
|
||||
if(mqttDebug != NULL) {
|
||||
mqttDebug->publishRaw(buf, curLen);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_MBUS:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("MBUS frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishBytes(buf, curLen);
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("MBUS frame:\n"));
|
||||
if(mqttDebug != NULL) {
|
||||
mqttDebug->publishRaw(buf, curLen);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_GBT:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GBT frame:\n"));
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GBT frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_GCM:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GCM frame:\n"));
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("GCM frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_LLC:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LLC frame:\n"));
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("LLC frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_DLMS:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DLMS frame:\n"));
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DLMS frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DSMR frame:\n"));
|
||||
if(pt != NULL) {
|
||||
pt->publishString((char*) buf);
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("DSMR frame:\n"));
|
||||
if(mqttDebug != NULL) {
|
||||
mqttDebug->publishRaw(buf, curLen);
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_SNRM:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("SNMR frame:\n"));
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("SNMR frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_AARE:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("AARE frame:\n"));
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("AARE frame:\n"));
|
||||
break;
|
||||
case DATA_TAG_RES:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("RES frame:\n"));
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("RES frame:\n"));
|
||||
break;
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugPrint(buf, 0, curLen);
|
||||
if (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);
|
||||
@@ -476,99 +497,83 @@ debugPrint(buf, 0, curLen);
|
||||
tag = (*buf);
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
debugger->print(F("0"));
|
||||
debugger->print(buffer[i], HEX);
|
||||
debugger->print(F(" "));
|
||||
if ((i - start + 1) % 16 == 0)
|
||||
debugger->println(F(""));
|
||||
else if ((i - start + 1) % 4 == 0)
|
||||
debugger->print(F(" "));
|
||||
|
||||
yield(); // Let other get some resources too
|
||||
}
|
||||
debugger->println(F(""));
|
||||
}
|
||||
|
||||
void PassiveMeterCommunicator::printHanReadError(int pos) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
{
|
||||
switch(pos) {
|
||||
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
|
||||
case DATA_PARSE_BOUNDARY_FLAG_MISSING:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Boundry flag missing\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Boundary flag missing\n"));
|
||||
break;
|
||||
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Header checksum error\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Header checksum error\n"));
|
||||
break;
|
||||
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame checksum error\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame checksum error\n"));
|
||||
break;
|
||||
case DATA_PARSE_INCOMPLETE:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received frame is incomplete\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Received frame is incomplete\n"));
|
||||
break;
|
||||
case GCM_AUTH_FAILED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decrypt authentication failed\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decrypt authentication failed\n"));
|
||||
break;
|
||||
case GCM_ENCRYPTION_KEY_FAILED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Setting decryption key failed\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Setting decryption key failed\n"));
|
||||
break;
|
||||
case GCM_DECRYPT_FAILED:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decryption failed\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Decryption failed\n"));
|
||||
break;
|
||||
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame length mismatch\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Frame length mismatch\n"));
|
||||
break;
|
||||
case DATA_PARSE_INTERMEDIATE_SEGMENT:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Intermediate segment received\n"));
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Intermediate segment received\n"));
|
||||
break;
|
||||
case DATA_PARSE_UNKNOWN_DATA:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
|
||||
break;
|
||||
default:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,17 +583,17 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
|
||||
int8_t txpin = passive ? -1 : meterConfig.txPin;
|
||||
|
||||
if(baud == 0) {
|
||||
baud = 2400;
|
||||
autodetectBaud = baud = 2400;
|
||||
}
|
||||
|
||||
if(parityOrdinal == 0) {
|
||||
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(parityOrdinal == 0) {
|
||||
parityOrdinal = 3; // 8N1
|
||||
}
|
||||
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
|
||||
@@ -612,9 +617,9 @@ debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d
|
||||
|
||||
if(rxpin == 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -623,9 +628,9 @@ debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
|
||||
|
||||
if(hwSerial != NULL) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Hardware serial\n"));
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Hardware serial\n"));
|
||||
Serial.flush();
|
||||
#if defined(ESP8266)
|
||||
SerialConfig serialConfig;
|
||||
@@ -662,15 +667,15 @@ debugger->printf_P(PSTR("Hardware serial\n"));
|
||||
#if defined(ESP8266)
|
||||
if(rxpin == 3) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
|
||||
Serial.pins(1,3);
|
||||
} else if(rxpin == 113) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
|
||||
Serial.pins(15,13);
|
||||
}
|
||||
#endif
|
||||
@@ -703,9 +708,9 @@ debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
|
||||
} else {
|
||||
#if defined(ESP8266)
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial\n"));
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial\n"));
|
||||
Serial.flush();
|
||||
|
||||
if(swSerial == NULL) {
|
||||
@@ -738,20 +743,17 @@ debugger->printf_P(PSTR("Software serial\n"));
|
||||
if(bufferSize > 2) bufferSize = 2;
|
||||
#endif
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Using serial buffer size %d\n"), 64 * bufferSize);
|
||||
swSerial->begin(baud, serialConfig, rxpin, txpin, invert, meterConfig.bufferSize * 64);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial not available\n"));
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Software serial not available\n"));
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
@@ -759,15 +761,15 @@ debugger->printf_P(PSTR("Software serial not available\n"));
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
|
||||
pinMode(meterConfig.rxPin, INPUT);
|
||||
}
|
||||
|
||||
@@ -797,78 +799,47 @@ 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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial buffer overflow\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial buffer overflow\n"));
|
||||
rxBufferErrors++;
|
||||
if(rxBufferErrors > 1 && meterConfig.bufferSize < 8) {
|
||||
meterConfig.bufferSize += 2;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
|
||||
configChanged = true;
|
||||
rxBufferErrors = 0;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial FIFO overflow\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial FIFO overflow\n"));
|
||||
break;
|
||||
case 4:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial frame error\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial frame error\n"));
|
||||
break;
|
||||
case 5:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial parity error\n"));
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Serial parity error\n"));
|
||||
unsigned long now = millis();
|
||||
if(now - meterAutodetectLastChange < 120000) {
|
||||
switch(autodetectParity) {
|
||||
case 2: // 7N1
|
||||
autodetectParity = 10;
|
||||
break;
|
||||
case 10: // 7E1
|
||||
autodetectParity = 6;
|
||||
break;
|
||||
case 6: // 7N2
|
||||
autodetectParity = 14;
|
||||
break;
|
||||
case 14: // 7E2
|
||||
autodetectParity = 2;
|
||||
break;
|
||||
|
||||
case 3: // 8N1
|
||||
autodetectParity = 11;
|
||||
break;
|
||||
case 11: // 8E1
|
||||
autodetectParity = 7;
|
||||
break;
|
||||
case 7: // 8N2
|
||||
autodetectParity = 15;
|
||||
break;
|
||||
case 15: // 8E2
|
||||
autodetectParity = 3;
|
||||
break;
|
||||
|
||||
default:
|
||||
autodetectParity = 3;
|
||||
break;
|
||||
}
|
||||
if(validDataReceived) {
|
||||
meterConfig.parity = autodetectParity;
|
||||
configChanged = true;
|
||||
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
||||
}
|
||||
if(autodetect) {
|
||||
meterAutodetectLastChange = 0;
|
||||
} else if(validDataReceived > 2) {
|
||||
meterConfig.parity = getNextParity(meterConfig.parity);
|
||||
configChanged = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -878,28 +849,35 @@ debugger->printf_P(PSTR("Serial parity error\n"));
|
||||
|
||||
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;
|
||||
if(autodetectCount == 2) {
|
||||
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++];
|
||||
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");
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
|
||||
meterConfig.bufferSize = max((uint32_t) 1, autodetectBaud / 14400);
|
||||
setupHanPort(autodetectBaud, autodetectParity, autodetectInvert);
|
||||
meterAutodetectLastChange = now;
|
||||
}
|
||||
} else if(autodetect) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
|
||||
autodetect = false;
|
||||
meterConfig.baud = autodetectBaud;
|
||||
meterConfig.parity = autodetectParity;
|
||||
@@ -908,3 +886,18 @@ debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), auto
|
||||
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,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "PulseMeterCommunicator.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
@@ -41,6 +47,10 @@ bool PulseMeterCommunicator::isConfigChanged() {
|
||||
return this->configChanged;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::ackConfigChanged() {
|
||||
configChanged = false;
|
||||
}
|
||||
|
||||
void PulseMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
|
||||
meterConfig = this->meterConfig;
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
class PassthroughMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
PassthroughMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
PassthroughMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
this->topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
#else
|
||||
@@ -15,28 +15,23 @@
|
||||
#define DOCPOS_MEASUREMENTUNIT 2
|
||||
#define DOCPOS_POSITION 3
|
||||
#define DOCPOS_AMOUNT 4
|
||||
#define DOCPOS_RESOLUTION 5
|
||||
|
||||
class EntsoeA44Parser: public Stream {
|
||||
public:
|
||||
EntsoeA44Parser();
|
||||
EntsoeA44Parser(PricesContainer *container);
|
||||
virtual ~EntsoeA44Parser();
|
||||
|
||||
char* getCurrency();
|
||||
char* getMeasurementUnit();
|
||||
float getPoint(uint8_t position);
|
||||
|
||||
int available();
|
||||
int read();
|
||||
int peek();
|
||||
void flush();
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
size_t write(uint8_t);
|
||||
void get(PricesContainer*);
|
||||
|
||||
private:
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
float points[25];
|
||||
PricesContainer *container;
|
||||
float multiplier = 1.0;
|
||||
|
||||
char buf[64];
|
||||
uint8_t pos = 0;
|
||||
|
||||
@@ -27,10 +27,6 @@
|
||||
|
||||
#define SSL_BUF_SIZE 512
|
||||
|
||||
#define PRICE_DIRECTION_IMPORT 0x01
|
||||
#define PRICE_DIRECTION_EXPORT 0x02
|
||||
#define PRICE_DIRECTION_BOTH 0x03
|
||||
|
||||
#define PRICE_DAY_MO 0x01
|
||||
#define PRICE_DAY_TU 0x02
|
||||
#define PRICE_DAY_WE 0x04
|
||||
@@ -57,10 +53,13 @@ struct PriceConfig {
|
||||
uint8_t end_dayofmonth;
|
||||
};
|
||||
|
||||
struct PricePart {
|
||||
char name[32];
|
||||
char description[32];
|
||||
uint32_t value;
|
||||
struct AmsPriceV2Header {
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
char source[4];
|
||||
uint8_t resolutionInMinutes;
|
||||
bool differentExportPrices;
|
||||
uint8_t numberOfPoints;
|
||||
};
|
||||
|
||||
class PriceService {
|
||||
@@ -71,23 +70,32 @@ public:
|
||||
PriceService(Stream*);
|
||||
#endif
|
||||
void setup(PriceServiceConfig&);
|
||||
void setTimezone(Timezone* tz);
|
||||
bool loop();
|
||||
|
||||
char* getToken();
|
||||
char* getCurrency();
|
||||
char* getArea();
|
||||
char* getSource();
|
||||
float getValueForHour(uint8_t direction, int8_t hour);
|
||||
float getValueForHour(uint8_t direction, time_t ts, int8_t hour);
|
||||
|
||||
float getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t hour);
|
||||
uint8_t getResolutionInMinutes();
|
||||
uint8_t getNumberOfPointsAvailable();
|
||||
uint8_t getCurrentPricePointIndex();
|
||||
|
||||
bool isExportPricesDifferentFromImport();
|
||||
|
||||
bool hasPrice() { return hasPrice(PRICE_DIRECTION_IMPORT); }
|
||||
bool hasPrice(uint8_t direction) { return getCurrentPrice(direction) != PRICE_NO_VALUE; }
|
||||
bool hasPricePoint(uint8_t direction, int8_t point) { return getPricePoint(direction, point) != PRICE_NO_VALUE; }
|
||||
|
||||
float getCurrentPrice(uint8_t direction);
|
||||
float getPricePoint(uint8_t direction, uint8_t point);
|
||||
float getPriceForRelativeHour(uint8_t direction, int8_t hour); // If not 60min interval, average
|
||||
|
||||
std::vector<PriceConfig>& getPriceConfig();
|
||||
void setPriceConfig(uint8_t index, PriceConfig &priceConfig);
|
||||
void cropPriceConfig(uint8_t size);
|
||||
|
||||
PricePart getPricePart(uint8_t index);
|
||||
|
||||
int16_t getLastError();
|
||||
|
||||
bool load();
|
||||
@@ -102,7 +110,7 @@ private:
|
||||
PriceServiceConfig* config = NULL;
|
||||
HTTPClient* http = NULL;
|
||||
|
||||
uint8_t currentDay = 0, currentHour = 0;
|
||||
uint8_t currentDay = 0, currentPricePoint = 0;
|
||||
uint8_t tomorrowFetchMinute = 15; // How many minutes over 13:00 should it fetch prices
|
||||
uint8_t nextFetchDelayMinutes = 15;
|
||||
uint64_t lastTodayFetch = 0;
|
||||
@@ -114,6 +122,7 @@ private:
|
||||
std::vector<PriceConfig> priceConfig;
|
||||
|
||||
Timezone* tz = NULL;
|
||||
Timezone* entsoeTz = NULL;
|
||||
|
||||
static const uint16_t BufferSize = 256;
|
||||
char* buf;
|
||||
@@ -129,7 +138,8 @@ 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);
|
||||
float getFixedPrice(uint8_t direction, int8_t hour);
|
||||
float getEnergyPricePoint(uint8_t direction, uint8_t point);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -4,15 +4,43 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef _PRICESCONTAINER_H
|
||||
#define _PRICESCONTAINER_H
|
||||
|
||||
#define PRICE_NO_VALUE -127
|
||||
#define PRICE_DIRECTION_IMPORT 0x01
|
||||
#define PRICE_DIRECTION_EXPORT 0x02
|
||||
#define PRICE_DIRECTION_BOTH 0x03
|
||||
|
||||
struct PricesContainer {
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
int32_t points[25];
|
||||
class PricesContainer {
|
||||
public:
|
||||
PricesContainer(char* source);
|
||||
|
||||
void setup(uint8_t resolutionInMinutes, uint8_t numberOfPoints, bool differentExportPrices);
|
||||
|
||||
char* getSource();
|
||||
void setCurrency(char* currency);
|
||||
char* getCurrency();
|
||||
|
||||
bool isExportPricesDifferentFromImport() {
|
||||
return differentExportPrices;
|
||||
}
|
||||
|
||||
uint8_t getResolutionInMinutes();
|
||||
uint8_t getNumberOfPoints();
|
||||
|
||||
void setPrice(uint8_t point, float value, uint8_t direction);
|
||||
bool hasPrice(uint8_t point, uint8_t direction);
|
||||
float getPrice(uint8_t point, uint8_t direction); // int32_t / 10_000
|
||||
|
||||
private:
|
||||
char source[4];
|
||||
char currency[4];
|
||||
uint8_t resolutionInMinutes;
|
||||
bool differentExportPrices;
|
||||
uint8_t numberOfPoints;
|
||||
int32_t *points;
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -7,27 +7,14 @@
|
||||
#include "EntsoeA44Parser.h"
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
EntsoeA44Parser::EntsoeA44Parser() {
|
||||
for(int i = 0; i < 25; i++) points[i] = PRICE_NO_VALUE;
|
||||
EntsoeA44Parser::EntsoeA44Parser(PricesContainer *container) {
|
||||
this->container = container;
|
||||
}
|
||||
|
||||
EntsoeA44Parser::~EntsoeA44Parser() {
|
||||
|
||||
}
|
||||
|
||||
char* EntsoeA44Parser::getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
char* EntsoeA44Parser::getMeasurementUnit() {
|
||||
return measurementUnit;
|
||||
}
|
||||
|
||||
float EntsoeA44Parser::getPoint(uint8_t position) {
|
||||
if(position >= 25) return PRICE_NO_VALUE;
|
||||
return points[position];
|
||||
}
|
||||
|
||||
int EntsoeA44Parser::available() {
|
||||
return 0;
|
||||
}
|
||||
@@ -57,7 +44,7 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||
buf[pos++] = byte;
|
||||
if(pos == 3) {
|
||||
buf[pos++] = '\0';
|
||||
memcpy(currency, buf, pos);
|
||||
container->setCurrency(buf);
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
}
|
||||
@@ -65,14 +52,17 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||
buf[pos++] = byte;
|
||||
if(pos == 3) {
|
||||
buf[pos++] = '\0';
|
||||
memcpy(measurementUnit, buf, pos);
|
||||
if(strcmp_P(buf, PSTR("MWH"))) multiplier = 0.001;
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
}
|
||||
} else if(docPos == DOCPOS_POSITION) {
|
||||
if(byte == '<') {
|
||||
buf[pos] = '\0';
|
||||
pointNum = String(buf).toInt() - 1;
|
||||
long pn = String(buf).toInt() - 1;
|
||||
if(pn < container->getNumberOfPoints()) {
|
||||
pointNum = pn;
|
||||
}
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
} else {
|
||||
@@ -81,7 +71,27 @@ 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 < container->getNumberOfPoints(); i++) {
|
||||
container->setPrice(i, val * multiplier, PRICE_DIRECTION_IMPORT);
|
||||
}
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
} else {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
} else if(docPos == DOCPOS_RESOLUTION) {
|
||||
if(byte == '<') {
|
||||
buf[pos] = '\0';
|
||||
|
||||
// This happens if there are two time series in the XML. We are only interrested in the first one, so we ignore the rest of the document
|
||||
if(container->hasPrice(0, PRICE_DIRECTION_IMPORT)) return 1;
|
||||
|
||||
if(strcmp_P(buf, PSTR("PT15M"))) {
|
||||
container->setup(15, 100, false);
|
||||
} else if(strcmp_P(buf, PSTR("PT60M"))) {
|
||||
container->setup(60, 25, false);
|
||||
}
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
} else {
|
||||
@@ -95,15 +105,17 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||
} else if(byte == '>') {
|
||||
buf[pos++] = byte;
|
||||
buf[pos] = '\0';
|
||||
if(strcmp(buf, "<currency_Unit.name>") == 0) {
|
||||
if(strcmp_P(buf, PSTR("<currency_Unit.name>")) == 0) {
|
||||
docPos = DOCPOS_CURRENCY;
|
||||
} else if(strcmp(buf, "<price_Measure_Unit.name>") == 0) {
|
||||
} else if(strcmp(buf, PSTR("<price_Measure_Unit.name>")) == 0) {
|
||||
docPos = DOCPOS_MEASUREMENTUNIT;
|
||||
} else if(strcmp(buf, "<position>") == 0) {
|
||||
} else if(strcmp(buf, PSTR("<position>")) == 0) {
|
||||
docPos = DOCPOS_POSITION;
|
||||
pointNum = 0xFF;
|
||||
} else if(strcmp(buf, "<price.amount>") == 0) {
|
||||
} else if(strcmp(buf, PSTR("<price.amount>")) == 0) {
|
||||
docPos = DOCPOS_AMOUNT;
|
||||
} else if(strcmp(buf, PSTR("<resolution>")) == 0) {
|
||||
docPos = DOCPOS_RESOLUTION;
|
||||
}
|
||||
pos = 0;
|
||||
} else {
|
||||
@@ -112,15 +124,3 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void EntsoeA44Parser::get(PricesContainer* container) {
|
||||
memset(container, 0, sizeof(*container));
|
||||
|
||||
strcpy(container->currency, currency);
|
||||
strcpy(container->measurementUnit, measurementUnit);
|
||||
strcpy(container->source, "EOE");
|
||||
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
container->points[i] = points[i] == PRICE_NO_VALUE ? PRICE_NO_VALUE : points[i] * 10000;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,8 @@ PriceService::PriceService(Stream* Debug) : priceConfig(std::vector<PriceConfig>
|
||||
// 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
|
||||
}
|
||||
@@ -42,6 +43,10 @@ void PriceService::setup(PriceServiceConfig& config) {
|
||||
this->config = new PriceServiceConfig();
|
||||
}
|
||||
memcpy(this->config, &config, sizeof(config));
|
||||
if(this->config->resolutionInMinutes != 15 && this->config->resolutionInMinutes != 60) {
|
||||
this->config->resolutionInMinutes = 60;
|
||||
}
|
||||
|
||||
lastTodayFetch = lastTomorrowFetch = lastCurrencyFetch = 0;
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) delete tomorrow;
|
||||
@@ -72,8 +77,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() {
|
||||
@@ -86,137 +95,172 @@ char* PriceService::getArea() {
|
||||
|
||||
char* PriceService::getSource() {
|
||||
if(this->today != NULL && this->tomorrow != NULL) {
|
||||
if(strcmp(this->today->source, this->tomorrow->source) == 0) {
|
||||
return this->today->source;
|
||||
if(strcmp(this->today->getSource(), this->tomorrow->getSource()) == 0) {
|
||||
return this->today->getSource();
|
||||
} else {
|
||||
return "MIX";
|
||||
}
|
||||
} else if(today != NULL) {
|
||||
return this->today->source;
|
||||
return this->today->getSource();
|
||||
} else if(tomorrow != NULL) {
|
||||
return this->tomorrow->source;
|
||||
return this->tomorrow->getSource();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
float PriceService::getValueForHour(uint8_t direction, int8_t hour) {
|
||||
time_t cur = time(nullptr);
|
||||
return getValueForHour(direction, cur, hour);
|
||||
uint8_t PriceService::getResolutionInMinutes() {
|
||||
return today != NULL ? today->getResolutionInMinutes() : 60;
|
||||
}
|
||||
|
||||
float PriceService::getValueForHour(uint8_t direction, time_t ts, int8_t hour) {
|
||||
float ret = getEnergyPriceForHour(direction, ts, hour);
|
||||
if(ret == PRICE_NO_VALUE)
|
||||
return ret;
|
||||
uint8_t PriceService::getNumberOfPointsAvailable() {
|
||||
if(today == NULL) return getResolutionInMinutes() == 15 ? 192 : 48;
|
||||
if(tomorrow != NULL) return today->getNumberOfPoints() + tomorrow->getNumberOfPoints();
|
||||
return today->getNumberOfPoints();
|
||||
}
|
||||
|
||||
bool PriceService::isExportPricesDifferentFromImport() {
|
||||
for (uint8_t i = 0; i < priceConfig.size(); i++) {
|
||||
PriceConfig pc = priceConfig.at(i);
|
||||
if(pc.direction != PRICE_DIRECTION_BOTH) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return today != NULL && today->isExportPricesDifferentFromImport();
|
||||
}
|
||||
|
||||
float PriceService::getPricePoint(uint8_t direction, uint8_t point) {
|
||||
float value = getFixedPrice(direction, point * getResolutionInMinutes() / 60);
|
||||
if(value == PRICE_NO_VALUE) value = getEnergyPricePoint(direction, point);
|
||||
if(value == PRICE_NO_VALUE) return PRICE_NO_VALUE;
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(ts + (hour * SECS_PER_HOUR)), tm);
|
||||
uint8_t day = 0x01 << ((tm.Wday+5)%7);
|
||||
uint32_t hrs = 0x01 << tm.Hour;
|
||||
time_t ts = time(nullptr);
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
tm.Hour = tm.Minute = tm.Second = 0;
|
||||
breakTime(makeTime(tm) + (point * SECS_PER_MIN * getResolutionInMinutes()), 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) continue;
|
||||
if(!timeIsInPeriod(tm, pc)) continue;
|
||||
float pcVal = pc.value / 10000.0;
|
||||
|
||||
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;
|
||||
}
|
||||
switch(pc.type) {
|
||||
case PRICE_TYPE_ADD:
|
||||
value += pcVal;
|
||||
break;
|
||||
case PRICE_TYPE_SUBTRACT:
|
||||
value -= pcVal;
|
||||
break;
|
||||
case PRICE_TYPE_PCT:
|
||||
value += (pcVal * value) / 100.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
float PriceService::getEnergyPriceForHour(uint8_t direction, time_t ts, int8_t hour) {
|
||||
float PriceService::getCurrentPrice(uint8_t direction) {
|
||||
time_t ts = time(nullptr);
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(ts + (hour * SECS_PER_HOUR)), tm);
|
||||
uint8_t day = 0x01 << ((tm.Wday+5)%7);
|
||||
uint32_t hrs = 0x01 << tm.Hour;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
uint8_t pos = getCurrentPricePointIndex();
|
||||
|
||||
return getPricePoint(direction, pos);
|
||||
}
|
||||
|
||||
float PriceService::getEnergyPricePoint(uint8_t direction, uint8_t point) {
|
||||
uint8_t pos = point;
|
||||
float multiplier = 1.0;
|
||||
uint8_t numberOfPointsToday = 24;
|
||||
if(today != NULL) {
|
||||
numberOfPointsToday = today->getNumberOfPoints();
|
||||
}
|
||||
|
||||
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 && (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) return value;
|
||||
|
||||
int8_t pos = hour;
|
||||
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
while(tm.Hour > 0) {
|
||||
ts -= 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
pos++;
|
||||
}
|
||||
uint8_t hoursToday = 0;
|
||||
uint8_t todayDate = tm.Day;
|
||||
while(tm.Day == todayDate) {
|
||||
ts += 3600;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
hoursToday++;
|
||||
}
|
||||
if(pos > 49)
|
||||
return PRICE_NO_VALUE;
|
||||
|
||||
float multiplier = 1.0;
|
||||
if(pos >= hoursToday) {
|
||||
if(pos >= numberOfPointsToday) {
|
||||
pos = pos - numberOfPointsToday;
|
||||
if(tomorrow == NULL)
|
||||
return PRICE_NO_VALUE;
|
||||
if(tomorrow->points[pos-hoursToday] == PRICE_NO_VALUE)
|
||||
if(pos >= tomorrow->getNumberOfPoints()) return PRICE_NO_VALUE;
|
||||
if(!tomorrow->hasPrice(pos, direction))
|
||||
return PRICE_NO_VALUE;
|
||||
value = tomorrow->points[pos-hoursToday] / 10000.0;
|
||||
if(strcmp(tomorrow->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(tomorrow->measurementUnit, "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return PRICE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(tomorrow->currency, config->currency, time(nullptr));
|
||||
value = tomorrow->getPrice(pos, direction);
|
||||
float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency, time(nullptr));
|
||||
if(mult == 0) return PRICE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
} else if(pos >= 0) {
|
||||
if(today == NULL)
|
||||
return PRICE_NO_VALUE;
|
||||
if(today->points[pos] == PRICE_NO_VALUE)
|
||||
if(!today->hasPrice(pos, direction))
|
||||
return PRICE_NO_VALUE;
|
||||
value = today->points[pos] / 10000.0;
|
||||
if(strcmp(today->measurementUnit, "KWH") == 0) {
|
||||
// Multiplier is 1
|
||||
} else if(strcmp(today->measurementUnit, "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return PRICE_NO_VALUE;
|
||||
}
|
||||
float mult = getCurrencyMultiplier(today->currency, config->currency, time(nullptr));
|
||||
value = today->getPrice(pos, direction);
|
||||
float mult = getCurrencyMultiplier(today->getCurrency(), config->currency, time(nullptr));
|
||||
if(mult == 0) return PRICE_NO_VALUE;
|
||||
multiplier *= mult;
|
||||
}
|
||||
return value * multiplier;
|
||||
return value == PRICE_NO_VALUE ? PRICE_NO_VALUE : value * multiplier;
|
||||
}
|
||||
|
||||
float PriceService::getPriceForRelativeHour(uint8_t direction, int8_t hour) {
|
||||
time_t ts = time(nullptr);
|
||||
tmElements_t tm;
|
||||
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
int8_t targetHour = tm.Hour + hour;
|
||||
tm.Hour = tm.Minute = tm.Second = 0;
|
||||
time_t startOfDay = tz->toUTC(makeTime(tm));
|
||||
|
||||
if((ts + (hour * SECS_PER_HOUR)) < startOfDay) {
|
||||
return PRICE_NO_VALUE;
|
||||
}
|
||||
|
||||
if(getResolutionInMinutes() == 60) {
|
||||
return getPricePoint(direction, targetHour);
|
||||
}
|
||||
|
||||
float valueSum = 0.0f;
|
||||
uint8_t valueCount = 0;
|
||||
float indexIncrements = 60.0 / today->getResolutionInMinutes();
|
||||
uint8_t priceMapIndexStart = (uint8_t) floor(indexIncrements * targetHour);
|
||||
uint8_t priceMapIndexEnd = (uint8_t) ceil(indexIncrements * (targetHour+1));
|
||||
|
||||
for(uint8_t mi = priceMapIndexStart; mi < priceMapIndexEnd; mi++) {
|
||||
float val = getPricePoint(direction, mi);
|
||||
if(val == PRICE_NO_VALUE) continue;
|
||||
valueSum += val;
|
||||
valueCount++;
|
||||
}
|
||||
if(valueCount == 0) return PRICE_NO_VALUE;
|
||||
return valueSum / valueCount;
|
||||
}
|
||||
|
||||
float PriceService::getFixedPrice(uint8_t direction, int8_t hour) {
|
||||
time_t ts = time(nullptr);
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
tm.Hour = hour;
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
breakTime(makeTime(tm), 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;
|
||||
if((pc.direction & direction) != direction) continue;
|
||||
if(!timeIsInPeriod(tm, pc)) continue;
|
||||
|
||||
if(value == PRICE_NO_VALUE) {
|
||||
value = pc.value / 10000.0;
|
||||
} else {
|
||||
value += pc.value / 10000.0;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
bool PriceService::loop() {
|
||||
@@ -224,40 +268,57 @@ bool PriceService::loop() {
|
||||
if(now < 10000) return false; // Grace period
|
||||
|
||||
time_t t = time(nullptr);
|
||||
if(t < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
#ifndef AMS2MQTT_PRICE_KEY
|
||||
if(strlen(getToken()) == 0) {
|
||||
if(t < FirmwareVersion::BuildEpoch) {
|
||||
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) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Day init\n"));
|
||||
currentDay = tm.Day;
|
||||
currentHour = tm.Hour;
|
||||
currentPricePoint = getCurrentPricePointIndex();
|
||||
}
|
||||
|
||||
if(currentDay != tm.Day) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Day reset\n"));
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) {
|
||||
today = tomorrow;
|
||||
tomorrow = NULL;
|
||||
}
|
||||
currentDay = tm.Day;
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
} else if(currentHour != tm.Hour) {
|
||||
currentHour = tm.Hour;
|
||||
return today != NULL; // Only trigger MQTT publish if we have todays prices.
|
||||
currentPricePoint = getCurrentPricePointIndex();
|
||||
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
|
||||
} else if(currentPricePoint != getCurrentPricePointIndex()) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Price point reset\n"));
|
||||
currentPricePoint = getCurrentPricePointIndex();
|
||||
return today != NULL || (!config->enabled && priceConfig.capacity() != 0); // Only trigger MQTT publish if we have todays prices.
|
||||
}
|
||||
|
||||
if(!config->enabled)
|
||||
return false;
|
||||
|
||||
#ifndef AMS2MQTT_PRICE_KEY
|
||||
if(strlen(getToken()) == 0) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if(strlen(config->area) == 0){
|
||||
return false;
|
||||
}
|
||||
if(strlen(config->currency) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool readyToFetchForTomorrow = tomorrow == NULL && (tm.Hour > 13 || (tm.Hour == 13 && tm.Minute >= tomorrowFetchMinute)) && (lastTomorrowFetch == 0 || now - lastTomorrowFetch > (nextFetchDelayMinutes*60000));
|
||||
@@ -273,6 +334,7 @@ bool PriceService::loop() {
|
||||
}
|
||||
today = NULL;
|
||||
}
|
||||
currentPricePoint = getCurrentPricePointIndex();
|
||||
return today != NULL && !readyToFetchForTomorrow; // Only trigger MQTT publish if we have todays prices and we are not immediately ready to fetch price for tomorrow.
|
||||
}
|
||||
|
||||
@@ -289,6 +351,7 @@ bool PriceService::loop() {
|
||||
}
|
||||
tomorrow = NULL;
|
||||
}
|
||||
currentPricePoint = getCurrentPricePointIndex();
|
||||
return tomorrow != NULL;
|
||||
}
|
||||
|
||||
@@ -328,17 +391,17 @@ bool PriceService::retrieve(const char* url, Stream* doc) {
|
||||
nextFetchDelayMinutes = 2;
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
|
||||
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 (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());
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf(http->getString().c_str());
|
||||
|
||||
http->end();
|
||||
return false;
|
||||
@@ -385,18 +448,18 @@ float PriceService::getCurrencyMultiplier(const char* from, const char* to, time
|
||||
}
|
||||
if(currencyMultiplier != 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Resulting currency multiplier: %.4f\n"), currencyMultiplier);
|
||||
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 defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without value\n"));
|
||||
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;
|
||||
}
|
||||
@@ -407,7 +470,7 @@ debugger->printf_P(PSTR("(PriceService) Multiplier ended in success, but without
|
||||
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;
|
||||
@@ -427,41 +490,47 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
|
||||
#endif
|
||||
|
||||
#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 (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();
|
||||
a44.get(ret);
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
PricesContainer* ret = new PricesContainer("EOE");
|
||||
EntsoeA44Parser a44(ret);
|
||||
if(retrieve(buf, &a44) && ret->hasPrice(0, PRICE_DIRECTION_IMPORT)) {
|
||||
return ret;
|
||||
} else {
|
||||
delete ret;
|
||||
return NULL;
|
||||
}
|
||||
} else if(hub) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Going to fetch prices from hub\n"));
|
||||
|
||||
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"),
|
||||
snprintf_P(buf, BufferSize, PSTR("http://hub.amsleser.no/hub/price/%s/%d/%d/%d/pt%dm?currency=%s"),
|
||||
config->area,
|
||||
tm.Year+1970,
|
||||
tm.Month,
|
||||
tm.Day,
|
||||
config->resolutionInMinutes,
|
||||
config->currency
|
||||
);
|
||||
#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 (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 (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
@@ -488,13 +557,37 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
GCMParser gcm(key, auth);
|
||||
int8_t gcmRet = gcm.parse(content, ctx);
|
||||
if(gcmRet > 0) {
|
||||
PricesContainer* ret = new PricesContainer();
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
ret->points[i] = PRICE_NO_VALUE;
|
||||
AmsPriceV2Header* header = (AmsPriceV2Header*) (content+gcmRet);
|
||||
|
||||
PricesContainer* ret = new PricesContainer(header->source);
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Setting up price container with pt%dm, %dpts, edi: %d\n"), header->resolutionInMinutes, header->numberOfPoints, header->differentExportPrices);
|
||||
|
||||
ret->setup(header->resolutionInMinutes, header->numberOfPoints, header->differentExportPrices);
|
||||
ret->setCurrency(header->currency);
|
||||
int32_t* points = (int32_t*) &header[1];
|
||||
|
||||
for(uint8_t i = 0; i < header->numberOfPoints; i++) {
|
||||
int32_t intval = ntohl(points[i]);
|
||||
float value = intval / 10000.0;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Import price position and value received: %d :: %.2f\n"), i, value);
|
||||
ret->setPrice(i, value, PRICE_DIRECTION_IMPORT);
|
||||
}
|
||||
memcpy(ret, content+gcmRet, sizeof(*ret));
|
||||
for(uint8_t i = 0; i < 25; i++) {
|
||||
ret->points[i] = ntohl(ret->points[i]);
|
||||
if(header->differentExportPrices) {
|
||||
for(uint8_t i = 0; i < header->numberOfPoints; i++) {
|
||||
int32_t intval = ntohl(points[i]);
|
||||
float value = intval / 10000.0;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Export price position and value received: %d :: %.2f\n"), i, value);
|
||||
ret->setPrice(i, value, PRICE_DIRECTION_EXPORT);
|
||||
}
|
||||
}
|
||||
lastError = 0;
|
||||
nextFetchDelayMinutes = 1;
|
||||
@@ -503,9 +596,9 @@ debugger->printf_P(PSTR("(PriceService) url: %s\n"), buf);
|
||||
lastError = gcmRet;
|
||||
nextFetchDelayMinutes = 60;
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet);
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n"), gcmRet);
|
||||
}
|
||||
} else {
|
||||
lastError = status;
|
||||
@@ -517,20 +610,20 @@ debugger->printf_P(PSTR("(PriceService) Error code while decrypting prices: %d\n
|
||||
nextFetchDelayMinutes = 5;
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Communication error, returned status: %d\n"), status);
|
||||
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
|
||||
{
|
||||
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());
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf(http->getString().c_str());
|
||||
|
||||
http->end();
|
||||
}
|
||||
@@ -539,22 +632,6 @@ debugger->printf(http->getString().c_str());
|
||||
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;
|
||||
}
|
||||
@@ -583,16 +660,16 @@ void PriceService::cropPriceConfig(uint8_t size) {
|
||||
bool PriceService::save() {
|
||||
if(!LittleFS.begin()) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
|
||||
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");
|
||||
@@ -615,18 +692,18 @@ debugger->printf_P(PSTR("(PriceService) Saving price config\n"));
|
||||
bool PriceService::load() {
|
||||
if(!LittleFS.begin()) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Unable to load LittleFS\n"));
|
||||
return false;
|
||||
}
|
||||
if(!LittleFS.exists(FILE_PRICE_CONF)) {
|
||||
return false;
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
|
||||
|
||||
this->priceConfig.clear();
|
||||
|
||||
@@ -640,4 +717,45 @@ debugger->printf_P(PSTR("(PriceService) Loading price config\n"));
|
||||
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);
|
||||
}
|
||||
|
||||
uint8_t PriceService::getCurrentPricePointIndex() {
|
||||
time_t ts = time(nullptr);
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(ts), tm);
|
||||
return ((tm.Hour * 60) + tm.Minute) / getResolutionInMinutes();
|
||||
}
|
||||
67
lib/PriceService/src/PricesContainer.cpp
Normal file
67
lib/PriceService/src/PricesContainer.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "PricesContainer.h"
|
||||
#include <cstring>
|
||||
|
||||
PricesContainer::PricesContainer(char* source) {
|
||||
strncpy(this->source, source, 4);
|
||||
}
|
||||
|
||||
void PricesContainer::setup(uint8_t resolutionInMinutes, uint8_t numberOfPoints, bool differentExportPrices) {
|
||||
this->resolutionInMinutes = resolutionInMinutes;
|
||||
this->differentExportPrices = differentExportPrices;
|
||||
this->numberOfPoints = numberOfPoints;
|
||||
this->points = new int32_t[numberOfPoints * (differentExportPrices ? 2 : 1)];
|
||||
memset(this->points, PRICE_NO_VALUE * 10000, numberOfPoints * (differentExportPrices ? 2 : 1) * sizeof(int32_t));
|
||||
}
|
||||
|
||||
char* PricesContainer::getSource() {
|
||||
return this->source;
|
||||
}
|
||||
|
||||
void PricesContainer::setCurrency(char* currency) {
|
||||
strncpy(this->currency, currency, 4);
|
||||
}
|
||||
|
||||
char* PricesContainer::getCurrency() {
|
||||
return this->currency;
|
||||
}
|
||||
|
||||
uint8_t PricesContainer::getResolutionInMinutes() {
|
||||
return this->resolutionInMinutes;
|
||||
}
|
||||
|
||||
uint8_t PricesContainer::getNumberOfPoints() {
|
||||
return this->numberOfPoints;
|
||||
}
|
||||
|
||||
void PricesContainer::setPrice(uint8_t point, float value, uint8_t direction) {
|
||||
if(direction == PRICE_DIRECTION_EXPORT && !differentExportPrices) {
|
||||
return; // Export prices not supported
|
||||
}
|
||||
if(direction != PRICE_DIRECTION_EXPORT) {
|
||||
points[point] = static_cast<int32_t>(value * 10000);
|
||||
}
|
||||
if(differentExportPrices && direction != PRICE_DIRECTION_IMPORT) {
|
||||
points[point + numberOfPoints] = static_cast<int32_t>(value * 10000);
|
||||
}
|
||||
}
|
||||
|
||||
bool PricesContainer::hasPrice(uint8_t point, uint8_t direction) {
|
||||
float val = getPrice(point, direction);
|
||||
return val != PRICE_NO_VALUE;
|
||||
}
|
||||
|
||||
float PricesContainer::getPrice(uint8_t point, uint8_t direction) {
|
||||
if(differentExportPrices && direction == PRICE_DIRECTION_EXPORT) {
|
||||
if(point < numberOfPoints) {
|
||||
return static_cast<float>(points[point + numberOfPoints]) / 10000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if(differentExportPrices && direction == PRICE_DIRECTION_BOTH) return PRICE_NO_VALUE; // Can't get a price for both directions if the export prices are different
|
||||
|
||||
if(point < numberOfPoints) {
|
||||
return static_cast<float>(points[point]) / 10000.0f;
|
||||
}
|
||||
|
||||
return PRICE_NO_VALUE; // Invalid point
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
class RawMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
RawMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
RawMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
full = mqttConfig.payloadFormat == 2;
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(PriceService*);
|
||||
bool publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
bool publishRaw(uint8_t* raw, size_t length);
|
||||
|
||||
void onMessage(String &topic, String &payload);
|
||||
|
||||
@@ -36,6 +36,7 @@ private:
|
||||
bool full;
|
||||
String topic;
|
||||
uint32_t lastThresholdPublish = 0;
|
||||
bool hasExport = false;
|
||||
|
||||
bool publishList1(AmsData* data, AmsData* meterState);
|
||||
bool publishList2(AmsData* data, AmsData* meterState);
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
#include "RawMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
bool RawMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
if(topic.isEmpty() || !connected())
|
||||
return false;
|
||||
|
||||
AmsData data;
|
||||
@@ -40,6 +41,15 @@ bool RawMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAcco
|
||||
publishList1(&data, previousState);
|
||||
loop();
|
||||
}
|
||||
|
||||
if(data.getListType() >= 2 && data.getActiveExportPower() > 0.0) {
|
||||
hasExport = true;
|
||||
}
|
||||
|
||||
if(data.getListType() >= 3 && data.getActiveExportCounter() > 0.0) {
|
||||
hasExport = true;
|
||||
}
|
||||
|
||||
if(ea->isInitialized()) {
|
||||
publishRealtime(ea);
|
||||
loop();
|
||||
@@ -227,9 +237,9 @@ bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishPrices(PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
if(topic.isEmpty() || !connected())
|
||||
return false;
|
||||
if(ps->getValueForHour(PRICE_DIRECTION_IMPORT, 0) == PRICE_NO_VALUE)
|
||||
if(!ps->hasPrice())
|
||||
return false;
|
||||
|
||||
time_t now = time(nullptr);
|
||||
@@ -240,7 +250,7 @@ bool RawMqttHandler::publishPrices(PriceService* ps) {
|
||||
float values[34];
|
||||
for(int i = 0;i < 34; i++) values[i] = PRICE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 34; i++) {
|
||||
float val = ps->getValueForHour(PRICE_DIRECTION_IMPORT, now, i);
|
||||
float val = ps->getPriceForRelativeHour(PRICE_DIRECTION_IMPORT, i);
|
||||
values[i] = val;
|
||||
|
||||
if(i > 23) continue;
|
||||
@@ -307,15 +317,33 @@ bool RawMqttHandler::publishPrices(PriceService* ps) {
|
||||
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 34; i++) {
|
||||
float val = values[i];
|
||||
if(val == PRICE_NO_VALUE) {
|
||||
mqtt.publish(topic + "/price/" + String(i), "", true, 0);
|
||||
mqtt.publish(topic + "/price/resolution", String(ps->getResolutionInMinutes()), true, 0);
|
||||
mqtt.loop();
|
||||
|
||||
uint8_t relativeIndex = 0;
|
||||
uint8_t startIndex = ps->getCurrentPricePointIndex();
|
||||
uint8_t numberOfPoints = ps->getNumberOfPointsAvailable();
|
||||
for(int i = startIndex; i < numberOfPoints; i++) {
|
||||
float importVal = ps->getPricePoint(PRICE_DIRECTION_IMPORT, i);
|
||||
if(importVal == PRICE_NO_VALUE) {
|
||||
mqtt.publish(topic + "/price/import/" + String(relativeIndex), "", true, 0);
|
||||
mqtt.loop();
|
||||
} else {
|
||||
mqtt.publish(topic + "/price/" + String(i), String(val, 4), true, 0);
|
||||
mqtt.publish(topic + "/price/import/" + String(relativeIndex), String(importVal, 4), true, 0);
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
if(hasExport && ps->isExportPricesDifferentFromImport()) {
|
||||
float exportVal = ps->getPricePoint(PRICE_DIRECTION_EXPORT, i);
|
||||
if(exportVal == PRICE_NO_VALUE) {
|
||||
mqtt.publish(topic + "/price/export/" + String(relativeIndex), "", true, 0);
|
||||
mqtt.loop();
|
||||
} else {
|
||||
mqtt.publish(topic + "/price/export/" + String(relativeIndex), String(exportVal, 4), true, 0);
|
||||
mqtt.loop();
|
||||
}
|
||||
}
|
||||
relativeIndex++;
|
||||
}
|
||||
if(min != INT16_MAX) {
|
||||
mqtt.publish(topic + "/price/min", String(min, 4), true, 0);
|
||||
@@ -337,20 +365,11 @@ 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;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishSystem(HwTools* hw, PriceService* ps, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
if(topic.isEmpty() || !connected())
|
||||
return false;
|
||||
|
||||
mqtt.publish(topic + "/id", WiFi.macAddress(), true, 0);
|
||||
@@ -377,9 +396,24 @@ uint8_t RawMqttHandler::getFormat() {
|
||||
return full ? 3 : 2;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
bool RawMqttHandler::publishRaw(uint8_t* raw, size_t length) {
|
||||
if(topic.isEmpty() || !connected())
|
||||
return false;
|
||||
|
||||
if(length <= 0 || length > BufferSize) return false;
|
||||
|
||||
String str = toHex(raw, length);
|
||||
bool ret = mqtt.publish(topic + "/data", str);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user