mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-05-18 12:06:35 +00:00
Compare commits
136 Commits
v2.4.0
...
upgrade/sv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a8e26dcc4 | ||
|
|
4673feaaf3 | ||
|
|
2c96b4d94f | ||
|
|
6011d3169e | ||
|
|
f0c3873635 | ||
|
|
fb4eea8208 | ||
|
|
e628056e56 | ||
|
|
df5611844f | ||
|
|
3cb6e09341 | ||
|
|
f7ccd2a96b | ||
|
|
13aff62aff | ||
|
|
64a0667947 | ||
|
|
4d128700c1 | ||
|
|
6743750d8f | ||
|
|
640e957065 | ||
|
|
d4f11c0412 | ||
|
|
01acc6d6e8 | ||
|
|
e89bb53941 | ||
|
|
009c4686ee | ||
|
|
33dc5fc177 | ||
|
|
faf047e25f | ||
|
|
b4322c5f9c | ||
|
|
0b4884652f | ||
|
|
82aeae8699 | ||
|
|
a7333653b0 | ||
|
|
24e63d5e32 | ||
|
|
eb7c266378 | ||
|
|
cf8c48ab99 | ||
|
|
78a1cd78ea | ||
|
|
fdfa6c1b52 | ||
|
|
4f1790a464 | ||
|
|
ca4cef5233 | ||
|
|
a0d7fd0d95 | ||
|
|
489dbf9254 | ||
|
|
a81aa11558 | ||
|
|
2e4a0fc0a3 | ||
|
|
fc6e9e8085 | ||
|
|
ad73821f1c | ||
|
|
98bf5b958f | ||
|
|
f323c5a4f6 | ||
|
|
ea91248e67 | ||
|
|
271ce2081f | ||
|
|
8438020dbd | ||
|
|
9252a810df | ||
|
|
c0c696a55c | ||
|
|
ef70d39f70 | ||
|
|
1cf890dc26 | ||
|
|
9d307e3192 | ||
|
|
61f0356a10 | ||
|
|
c648546b61 | ||
|
|
ffd8d46f2e | ||
|
|
eefbc08222 | ||
|
|
1a5b9542f4 | ||
|
|
19ff70782f | ||
|
|
0dfd2d9022 | ||
|
|
7a4ab77a83 | ||
|
|
46cd8c6e68 | ||
|
|
c307103605 | ||
|
|
d9ec111458 | ||
|
|
e3a1aa78a9 | ||
|
|
b06aa5f71b | ||
|
|
6a75b0fe71 | ||
|
|
e11fac3d11 | ||
|
|
031422f783 | ||
|
|
2ff8fddc14 | ||
|
|
e5d260ae3e | ||
|
|
633671851e | ||
|
|
69da9f9d48 | ||
|
|
19f78126d6 | ||
|
|
d3cc92949a | ||
|
|
f1089faab5 | ||
|
|
4a3ad6ab9b | ||
|
|
86449949c5 | ||
|
|
9bd9826835 | ||
|
|
94ff9d6da7 | ||
|
|
9518d1811b | ||
|
|
983426b36c | ||
|
|
bcb3c3b2ec | ||
|
|
9fd383c1ef | ||
|
|
a931f4cef8 | ||
|
|
fddecaab39 | ||
|
|
e5eab82d68 | ||
|
|
8ae1d46b2a | ||
|
|
99ccb03b45 | ||
|
|
b8f2d501a5 | ||
|
|
e042806619 | ||
|
|
16f9ed7ecb | ||
|
|
3eaefefd26 | ||
|
|
03c8c3ddbc | ||
|
|
08371b9078 | ||
|
|
a6ae25f3e7 | ||
|
|
8051db6a9b | ||
|
|
d0bfdae5d8 | ||
|
|
4d681ed2e2 | ||
|
|
8ee3f53714 | ||
|
|
e8cf8a98ed | ||
|
|
9153a98694 | ||
|
|
37aa6ae816 | ||
|
|
8a35346fcf | ||
|
|
792ae4c935 | ||
|
|
a7324d828a | ||
|
|
fe739c51d3 | ||
|
|
795d2d0375 | ||
|
|
5ef36a91f6 | ||
|
|
8491d6c471 | ||
|
|
f95f22058a | ||
|
|
b5c45cebfa | ||
|
|
19a953b269 | ||
|
|
6ae970ff68 | ||
|
|
0f0ee82af9 | ||
|
|
d84b9351e1 | ||
|
|
6668258b66 | ||
|
|
9c42aab04f | ||
|
|
c771870e3e | ||
|
|
fb59ee52c1 | ||
|
|
3483910136 | ||
|
|
13e70f7bd4 | ||
|
|
210001e232 | ||
|
|
094e588ad5 | ||
|
|
f9d0cdfa47 | ||
|
|
0476058958 | ||
|
|
3e337a5639 | ||
|
|
bf0e1d1bf3 | ||
|
|
d3b65b0175 | ||
|
|
2caa7252a0 | ||
|
|
f7596de166 | ||
|
|
f5178459e8 | ||
|
|
a55f7dc66a | ||
|
|
3d9cad8953 | ||
|
|
67535b2792 | ||
|
|
2b5f3f50df | ||
|
|
111807a743 | ||
|
|
348ba8cfc4 | ||
|
|
81c72f0ca3 | ||
|
|
3d540e2a65 | ||
|
|
165a385844 |
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: '22.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/
|
||||
82
.github/workflows/pr-build-env.yml
vendored
Normal file
82
.github/workflows/pr-build-env.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: PR build with env
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
env:
|
||||
description: 'The environment to build for'
|
||||
required: true
|
||||
type: string
|
||||
is_esp32:
|
||||
description: 'Whether the build is for ESP32 based firmware'
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code from repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- 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
|
||||
run: pio run -e ${{ inputs.env }}
|
||||
|
||||
- name: Create zip file
|
||||
run: /bin/sh scripts/${{ inputs.env }}/mkzip.sh
|
||||
|
||||
- name: Upload zip as artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ inputs.env }}.zip
|
||||
path: ${{ inputs.env }}.zip
|
||||
archive: false
|
||||
retention-days: 7
|
||||
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
|
||||
110
.github/workflows/pull-request.yml
vendored
Normal file
110
.github/workflows/pull-request.yml
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
name: Pull Request build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
build-esp32s2:
|
||||
uses: ./.github/workflows/pr-build-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s2
|
||||
is_esp32: true
|
||||
|
||||
build-esp32s3:
|
||||
uses: ./.github/workflows/pr-build-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32s3
|
||||
is_esp32: true
|
||||
|
||||
build-esp32c3:
|
||||
uses: ./.github/workflows/pr-build-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32c3
|
||||
is_esp32: true
|
||||
|
||||
build-esp32:
|
||||
uses: ./.github/workflows/pr-build-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32
|
||||
is_esp32: true
|
||||
|
||||
build-esp32solo:
|
||||
uses: ./.github/workflows/pr-build-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp32solo
|
||||
is_esp32: true
|
||||
|
||||
build-esp8266:
|
||||
uses: ./.github/workflows/pr-build-env.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
env: esp8266
|
||||
is_esp32: false
|
||||
|
||||
comment:
|
||||
needs:
|
||||
- build-esp32s2
|
||||
- build-esp32s3
|
||||
- build-esp32c3
|
||||
- build-esp32
|
||||
- build-esp32solo
|
||||
- build-esp8266
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Post PR comment with download links
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const runId = context.runId;
|
||||
const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`;
|
||||
|
||||
// Get the commit SHA (short version)
|
||||
const sha = context.payload.pull_request.head.sha;
|
||||
const shortSha = sha.substring(0, 7);
|
||||
|
||||
// Fetch the list of artifacts for this run via the API
|
||||
const artifactsResp = await github.rest.actions.listWorkflowRunArtifacts({ owner, repo, run_id: runId });
|
||||
const artifacts = artifactsResp.data.artifacts;
|
||||
|
||||
const envs = ['esp32s2', 'esp32s3', 'esp32c3', 'esp32', 'esp32solo', 'esp8266'];
|
||||
const lines = envs.map(env => {
|
||||
const artifact = artifacts.find(a => a.name === `${env}.zip`);
|
||||
if (artifact) {
|
||||
// The artifact download page URL - directly navigable in the browser
|
||||
const artifactUrl = `${runUrl}#artifacts-${env}`;
|
||||
return `- **${env}**: [Download ${env}.zip](https://github.com/${owner}/${repo}/actions/runs/${runId}/artifacts/${artifact.id})`;
|
||||
}
|
||||
return `- **${env}**: ⚠️ artifact not found`;
|
||||
});
|
||||
|
||||
const body = [
|
||||
'## 🔧 PR Build Artifacts',
|
||||
'',
|
||||
`**Version**: \`${shortSha}\``,
|
||||
'',
|
||||
'All environments built successfully. Download the zip files:',
|
||||
'',
|
||||
...lines,
|
||||
'',
|
||||
`> Artifacts expire after 7 days. [View workflow run](${runUrl})`,
|
||||
].join('\n');
|
||||
|
||||
// Find and delete any previous bot comment to keep the PR clean
|
||||
const comments = await github.rest.issues.listComments({ owner, repo, issue_number: prNumber });
|
||||
for (const comment of comments.data) {
|
||||
if (comment.user.type === 'Bot' && comment.body.includes('PR Build Artifacts')) {
|
||||
await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id });
|
||||
}
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
|
||||
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: '22.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
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,3 +19,5 @@ platformio-user.ini
|
||||
node_modules
|
||||
/gui/dist
|
||||
/scripts/*dev
|
||||
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.
|
||||
|
||||
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 @@
|
||||
|
||||
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 ...
|
||||
48
frames/iskra_croatia.txt
Normal file
48
frames/iskra_croatia.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
They actually use multiple frames, so this is a "fake" frame combining the two into one, but without checksum fields.
|
||||
|
||||
7E
|
||||
A0 BD
|
||||
CF 02 23 03 00 00
|
||||
E6 E7 00
|
||||
0F 00 03 46 3B
|
||||
0C 07 E9 0C 13 05 17 37 28 00 FF C4 00
|
||||
|
||||
02 21
|
||||
09 08 39 32 30 32 39 36 39 31
|
||||
09 04 17 37 28 00
|
||||
09 05 07 E9 0C 13 05
|
||||
06 00 6C 28 5A
|
||||
06 00 4B 76 1A
|
||||
06 00 20 B2 40
|
||||
06 00 58 68 AA
|
||||
06 00 57 A1 62
|
||||
06 00 00 C7 48
|
||||
06 00 17 EE D7
|
||||
06 00 12 F5 5C
|
||||
06 00 00 D9 6A
|
||||
06 00 15 36 84
|
||||
06 00 00 01 7E
|
||||
06 00 00 00 00
|
||||
12 03 79
|
||||
06 00 00 00 7F
|
||||
06 00 00 00 BD
|
||||
06 00 00 00 41
|
||||
06 00 00 00 00
|
||||
06 00 00 00 00
|
||||
06 00 00 00 00
|
||||
12 09 54
|
||||
12 09 35
|
||||
12 09 49
|
||||
12 00 37
|
||||
12 00 59
|
||||
12 00 4D
|
||||
06 00 00 43 62
|
||||
01 01
|
||||
12 24 B8
|
||||
01 01
|
||||
12 24 B8
|
||||
01 01
|
||||
12 24 B8
|
||||
03 01
|
||||
|
||||
00 00 7E
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 199 KiB |
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -13,7 +13,6 @@
|
||||
#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_NETWORK_START 40
|
||||
@@ -30,6 +29,7 @@
|
||||
#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,7 +79,8 @@ struct SystemConfig {
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[3];
|
||||
uint8_t energyspeedometer;
|
||||
}; // 8
|
||||
uint8_t firmwareChannel;
|
||||
}; // 9
|
||||
|
||||
struct NetworkConfig {
|
||||
char ssid[32];
|
||||
@@ -97,7 +114,8 @@ struct MqttConfig {
|
||||
uint16_t stateUpdateInterval;
|
||||
uint16_t timeout;
|
||||
uint8_t keepalive;
|
||||
}; // 685
|
||||
uint8_t rebootMinutes;
|
||||
}; // 684
|
||||
|
||||
struct WebConfig {
|
||||
uint8_t security;
|
||||
@@ -157,7 +175,8 @@ struct GpioConfig {
|
||||
uint16_t vccResistorVcc;
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 21
|
||||
uint8_t powersaving;
|
||||
}; // 22
|
||||
|
||||
struct GpioConfig103 {
|
||||
uint8_t hanPin;
|
||||
@@ -206,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];
|
||||
@@ -236,14 +257,14 @@ struct UiConfig {
|
||||
}; // 15
|
||||
|
||||
struct UpgradeInformation {
|
||||
char fromVersion[8];
|
||||
char toVersion[8];
|
||||
char fromVersion[16];
|
||||
char toVersion[16];
|
||||
uint32_t size;
|
||||
uint16_t block_position;
|
||||
uint8_t retry_count;
|
||||
uint8_t reboot_count;
|
||||
int8_t errorCode;
|
||||
}; // 25
|
||||
}; // 41+3
|
||||
|
||||
struct CloudConfig {
|
||||
bool enabled;
|
||||
@@ -254,6 +275,12 @@ struct CloudConfig {
|
||||
uint8_t proto;
|
||||
}; // 88
|
||||
|
||||
struct ZmartChargeConfig {
|
||||
bool enabled;
|
||||
char token[21];
|
||||
char baseUrl[64];
|
||||
}; // 86
|
||||
|
||||
class AmsConfiguration {
|
||||
public:
|
||||
bool hasConfig();
|
||||
@@ -283,6 +310,8 @@ public:
|
||||
bool getWebConfig(WebConfig&);
|
||||
bool setWebConfig(WebConfig&);
|
||||
void clearWebConfig(WebConfig&);
|
||||
bool isWebChanged();
|
||||
void ackWebChange();
|
||||
|
||||
bool getMeterConfig(MeterConfig&);
|
||||
bool setMeterConfig(MeterConfig&);
|
||||
@@ -345,6 +374,15 @@ public:
|
||||
void clearCloudConfig(CloudConfig&);
|
||||
bool isCloudChanged();
|
||||
void ackCloudConfig();
|
||||
|
||||
bool getZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool setZmartChargeConfig(ZmartChargeConfig&);
|
||||
void clearZmartChargeConfig(ZmartChargeConfig&);
|
||||
bool isZmartChargeConfigChanged();
|
||||
void ackZmartChargeConfig();
|
||||
|
||||
uint32_t getChipId();
|
||||
void getUniqueName(char* buffer, size_t length);
|
||||
|
||||
void clear();
|
||||
|
||||
@@ -353,7 +391,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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -13,7 +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
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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, true);
|
||||
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);
|
||||
@@ -110,16 +124,12 @@ void AmsConfiguration::clearNetworkConfig(NetworkConfig& config) {
|
||||
memset(config.ssid, 0, 32);
|
||||
memset(config.psk, 0, 64);
|
||||
clearNetworkConfigIp(config);
|
||||
|
||||
uint16_t chipId;
|
||||
getUniqueName(config.hostname, 32);
|
||||
#if defined(ESP32)
|
||||
chipId = ( ESP.getEfuseMac() >> 32 ) % 0xFFFFFFFF;
|
||||
config.power = 195;
|
||||
#else
|
||||
chipId = ESP.getChipId();
|
||||
config.power = 205;
|
||||
#endif
|
||||
strcpy(config.hostname, (String("ams-") + String(chipId, HEX)).c_str());
|
||||
config.mdns = true;
|
||||
config.sleep = 0xFF;
|
||||
config.use11b = 1;
|
||||
@@ -147,14 +157,17 @@ bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_MQTT_START, config);
|
||||
EEPROM.end();
|
||||
if(config.magic != 0x9C) {
|
||||
if(config.magic != 0x7B) {
|
||||
config.stateUpdate = false;
|
||||
config.stateUpdateInterval = 10;
|
||||
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.timeout = 1000;
|
||||
config.keepalive = 60;
|
||||
config.magic = 0x9C;
|
||||
config.rebootMinutes = config.ssl ? 5 : 0;
|
||||
config.magic = 0xA5;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -177,6 +190,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;
|
||||
}
|
||||
@@ -186,11 +202,12 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
|
||||
stripNonAscii((uint8_t*) config.publishTopic, 64);
|
||||
stripNonAscii((uint8_t*) config.subscribeTopic, 64);
|
||||
stripNonAscii((uint8_t*) config.username, 128, true);
|
||||
stripNonAscii((uint8_t*) config.password, 256, 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);
|
||||
@@ -215,6 +232,7 @@ void AmsConfiguration::clearMqtt(MqttConfig& config) {
|
||||
config.stateUpdateInterval = 10;
|
||||
config.timeout = 1000;
|
||||
config.keepalive = 60;
|
||||
config.rebootMinutes = 0;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttChanged() {
|
||||
@@ -242,9 +260,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);
|
||||
@@ -261,9 +287,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) {
|
||||
@@ -478,6 +513,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);
|
||||
@@ -559,6 +595,7 @@ void AmsConfiguration::clearGpio(GpioConfig& config, bool all) {
|
||||
config.vccResistorGnd = 0;
|
||||
config.vccResistorVcc = 0;
|
||||
config.ledBehaviour = LED_BEHAVIOUR_DEFAULT;
|
||||
config.powersaving = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,6 +662,9 @@ bool AmsConfiguration::getPriceServiceConfig(PriceServiceConfig& config) {
|
||||
clearPriceServiceConfig(config);
|
||||
return false;
|
||||
}
|
||||
if(config.resolutionInMinutes != 15 && config.resolutionInMinutes != 60) {
|
||||
config.resolutionInMinutes = 60;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
clearPriceServiceConfig(config);
|
||||
@@ -639,6 +679,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;
|
||||
}
|
||||
@@ -658,9 +699,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() {
|
||||
@@ -788,8 +828,8 @@ void AmsConfiguration::ackUiLanguageChange() {
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
stripNonAscii((uint8_t*) upinfo.fromVersion, 8);
|
||||
stripNonAscii((uint8_t*) upinfo.toVersion, 8);
|
||||
stripNonAscii((uint8_t*) upinfo.fromVersion, 16);
|
||||
stripNonAscii((uint8_t*) upinfo.toVersion, 16);
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_UPGRADE_INFO_START, upinfo);
|
||||
@@ -803,7 +843,7 @@ 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;
|
||||
}
|
||||
@@ -815,8 +855,8 @@ bool AmsConfiguration::getUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
memset(upinfo.fromVersion, 0, 8);
|
||||
memset(upinfo.toVersion, 0, 8);
|
||||
memset(upinfo.fromVersion, 0, 16);
|
||||
memset(upinfo.toVersion, 0, 16);
|
||||
upinfo.errorCode = 0;
|
||||
upinfo.size = 0;
|
||||
upinfo.block_position = 0;
|
||||
@@ -876,10 +916,86 @@ 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;
|
||||
}
|
||||
|
||||
uint32_t AmsConfiguration::getChipId() {
|
||||
uint32_t chipId;
|
||||
#if defined(ESP32)
|
||||
for(int i=0; i<17; i=i+8) {
|
||||
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
|
||||
}
|
||||
#else
|
||||
chipId = ESP.getChipId();
|
||||
#endif
|
||||
return chipId;
|
||||
}
|
||||
|
||||
void AmsConfiguration::getUniqueName(char* buffer, size_t length) {
|
||||
uint32_t chipId = getChipId();
|
||||
snprintf(buffer, length, "ams-%06x", chipId);
|
||||
}
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
@@ -887,6 +1003,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);
|
||||
@@ -943,6 +1060,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();
|
||||
@@ -1045,7 +1166,8 @@ bool AmsConfiguration::relocateConfig103() {
|
||||
gpio103.vccResistorGnd,
|
||||
gpio103.vccResistorVcc,
|
||||
gpio103.ledDisablePin,
|
||||
gpio103.ledBehaviour
|
||||
gpio103.ledBehaviour,
|
||||
0
|
||||
};
|
||||
|
||||
WebConfig web = {web103.security};
|
||||
@@ -1077,6 +1199,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();
|
||||
@@ -1085,6 +1211,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();
|
||||
@@ -1156,6 +1283,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"));
|
||||
}
|
||||
@@ -1209,6 +1339,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);
|
||||
}
|
||||
@@ -1264,10 +1395,10 @@ 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;
|
||||
@@ -1285,9 +1416,29 @@ void AmsConfiguration::print(Print* debugger)
|
||||
String uuid = ESPRandom::uuidToString(cc.clientId);;
|
||||
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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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,22 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -7,8 +7,7 @@
|
||||
#ifndef _AMSDATA_H
|
||||
#define _AMSDATA_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Timezone.h>
|
||||
#include <WString.h>
|
||||
#include "OBIScodes.h"
|
||||
|
||||
enum AmsType {
|
||||
@@ -28,7 +27,7 @@ public:
|
||||
AmsData();
|
||||
|
||||
void apply(AmsData& other);
|
||||
void apply(const OBIS_code_t obis, double value);
|
||||
void apply(const OBIS_code_t obis, double value, uint64_t millis64);
|
||||
|
||||
uint64_t getLastUpdateMillis();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsData.h"
|
||||
#include <algorithm>
|
||||
|
||||
AmsData::AmsData() {}
|
||||
|
||||
@@ -17,7 +18,6 @@ void AmsData::apply(AmsData& other) {
|
||||
uint32_t power = (activeImportPower + other.getActiveImportPower()) / 2;
|
||||
float add = power * (((float) ms) / 3600000.0);
|
||||
activeImportCounter += add / 1000.0;
|
||||
//Serial.printf("%dW, %dms, %.6fkWh added\n", other.getActiveImportPower(), ms, add);
|
||||
}
|
||||
|
||||
if(other.getListType() > 1) {
|
||||
@@ -112,7 +112,7 @@ void AmsData::apply(AmsData& other) {
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
}
|
||||
|
||||
void AmsData::apply(OBIS_code_t obis, double value) {
|
||||
void AmsData::apply(OBIS_code_t obis, double value, uint64_t millis64) {
|
||||
if(obis.sensor == 0 && obis.gr == 0 && obis.tariff == 0) {
|
||||
meterType = value;
|
||||
}
|
||||
@@ -127,138 +127,137 @@ void AmsData::apply(OBIS_code_t obis, double value) {
|
||||
}
|
||||
}
|
||||
if(obis.tariff != 0) {
|
||||
Serial.println("Tariff not implemented");
|
||||
return;
|
||||
}
|
||||
if(obis.gr == 7) { // Instant values
|
||||
switch(obis.sensor) {
|
||||
case 1:
|
||||
activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 2:
|
||||
activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 13:
|
||||
powerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 21:
|
||||
l1activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 22:
|
||||
l1activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 31:
|
||||
l1current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 32:
|
||||
l1voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 33:
|
||||
l1PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 41:
|
||||
l2activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 42:
|
||||
l2activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 51:
|
||||
l2current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 52:
|
||||
l2voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 53:
|
||||
l2PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 61:
|
||||
l3activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 62:
|
||||
l3activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 71:
|
||||
l3current = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 72:
|
||||
l3voltage = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
listType = std::max(listType, (uint8_t) 2);
|
||||
break;
|
||||
case 73:
|
||||
l3PowerFactor = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
}
|
||||
} else if(obis.gr == 8) { // Accumulated values
|
||||
switch(obis.sensor) {
|
||||
case 1:
|
||||
activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
listType = std::max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 2:
|
||||
activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
listType = std::max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 3:
|
||||
reactiveImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
listType = std::max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 4:
|
||||
reactiveExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
listType = std::max(listType, (uint8_t) 3);
|
||||
break;
|
||||
case 21:
|
||||
l1activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 22:
|
||||
l1activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 41:
|
||||
l2activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 42:
|
||||
l2activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 61:
|
||||
l3activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
case 62:
|
||||
l3activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 4);
|
||||
listType = std::max(listType, (uint8_t) 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(listType > 0)
|
||||
lastUpdateMillis = millis();
|
||||
lastUpdateMillis = millis64;
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
if(!threePhase)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -639,25 +639,22 @@ bool AmsDataStorage::isDayHappy(time_t now) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
if(now < day.lastMeterReadTime) {
|
||||
// If the timestamp is before the firware was built, there is something seriously wrong..
|
||||
if(now < FirmwareVersion::BuildEpoch) {
|
||||
return false;
|
||||
}
|
||||
// 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) {
|
||||
|
||||
// If the timestamp is before the last time we updated, there is also something wrong..
|
||||
if(now < day.lastMeterReadTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tmElements_t tm, last;
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(day.lastMeterReadTime), last);
|
||||
if(tm.Hour != last.Hour) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// If the timestamp is at the same day and hour as last update, we are happy
|
||||
return tm.Day == last.Day && tm.Hour == last.Hour;
|
||||
}
|
||||
|
||||
bool AmsDataStorage::isMonthHappy(time_t now) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -19,8 +19,6 @@ 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) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -17,7 +17,7 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified, Pr
|
||||
uint8_t lastByte = 0x00;
|
||||
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 != '/') return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
if(pos > 0 && b == '!') crcPos = pos+1;
|
||||
if(crcPos > 0 && b == 0x0A && lastByte == 0x0D) {
|
||||
reachedEnd = true;
|
||||
@@ -74,7 +74,7 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified, Pr
|
||||
fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2);
|
||||
crc = ntohs(crc);
|
||||
|
||||
if(crc != crc_calc) {
|
||||
if(crc > 0 && crc != crc_calc) {
|
||||
if(debugger != NULL) {
|
||||
debugger->printf_P(PSTR("CRC incorrrect, %04X != %04X at position %lu\n"), crc, crc_calc, crcPos);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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 ?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -23,7 +23,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx, bool hastag) {
|
||||
uint32_t headersize = 0;
|
||||
uint8_t* ptr = (uint8_t*) d;
|
||||
if(hastag) {
|
||||
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
ptr++;
|
||||
headersize++;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx, bool hastag) {
|
||||
footersize += authkeylen;
|
||||
memcpy(additional_authenticated_data + 1, authentication_key, 16);
|
||||
memcpy(authentication_tag, ptr + len - footersize - 5, authkeylen);
|
||||
for(uint8_t i; i < 16; i++) authenticate |= authentication_key[i] > 0;
|
||||
for(uint8_t i = 0; i < 16; i++) authenticate |= authentication_key[i] > 0;
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -29,10 +29,10 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
// First and last byte should be HDLC_FLAG
|
||||
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
|
||||
|
||||
// Verify FCS
|
||||
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
if(f->fcs > 0 && ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
|
||||
// Skip destination address, LSB marks last byte
|
||||
@@ -50,7 +50,7 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
||||
|
||||
// Verify HCS
|
||||
if(ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
||||
if(t3->hcs > 0 && ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
||||
return DATA_PARSE_HEADER_CHECKSUM_ERROR;
|
||||
ptr += 3;
|
||||
|
||||
@@ -69,7 +69,12 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
|
||||
if((*ptr) == DATA_TAG_LLC) {
|
||||
ptr += 3; // Skip LLC
|
||||
ctx.length -= 3;
|
||||
}
|
||||
|
||||
memcpy(buf + pos, ptr, ctx.length);
|
||||
pos += ctx.length;
|
||||
|
||||
lastSequenceNumber++;
|
||||
@@ -78,7 +83,12 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
lastSequenceNumber = 0;
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
|
||||
if((*ptr) == DATA_TAG_LLC) {
|
||||
ptr += 3; // Skip LLC
|
||||
ctx.length -= 3;
|
||||
}
|
||||
|
||||
memcpy(buf + pos, ptr, ctx.length);
|
||||
pos += ctx.length;
|
||||
|
||||
memcpy((uint8_t *) d, buf, pos);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <Print.h>
|
||||
@@ -17,6 +22,9 @@
|
||||
#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)
|
||||
@@ -36,6 +44,8 @@
|
||||
#define AMS_UPDATE_ERR_SUCCESS_CONFIRMED 123
|
||||
|
||||
#define UPDATE_BUF_SIZE 4096
|
||||
#define UPDATE_MAX_BLOCK_RETRY 25
|
||||
#define UPDATE_MAX_REBOOT_RETRY 12
|
||||
|
||||
class AmsFirmwareUpdater {
|
||||
public:
|
||||
@@ -57,6 +67,13 @@ public:
|
||||
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);
|
||||
@@ -92,10 +109,11 @@ private:
|
||||
String md5;
|
||||
|
||||
uint32_t lastVersionCheck = 0;
|
||||
uint8_t firmwareVariant;
|
||||
uint8_t firmwareChannel;
|
||||
bool autoUpgrade;
|
||||
char nextVersion[10];
|
||||
char nextVersion[17];
|
||||
|
||||
void getChannelName(char * buffer);
|
||||
|
||||
bool fetchNextVersion();
|
||||
bool fetchVersionDetails();
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
#include "AmsFirmwareUpdater.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "FirmwareVersion.h"
|
||||
@@ -22,7 +27,7 @@ this->debugger = debugger;
|
||||
this->hw = hw;
|
||||
this->meterState = meterState;
|
||||
memset(nextVersion, 0, sizeof(nextVersion));
|
||||
firmwareVariant = 0;
|
||||
firmwareChannel = 0;
|
||||
autoUpgrade = false;
|
||||
}
|
||||
|
||||
@@ -74,7 +79,7 @@ void AmsFirmwareUpdater::setUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Resuming uprade to %s\n"), updateStatus.toVersion);
|
||||
|
||||
if(updateStatus.reboot_count++ < 8) {
|
||||
if(updateStatus.reboot_count++ < UPDATE_MAX_REBOOT_RETRY) {
|
||||
updateStatus.errorCode = AMS_UPDATE_ERR_OK;
|
||||
} else {
|
||||
updateStatus.errorCode = AMS_UPDATE_ERR_REBOOT;
|
||||
@@ -92,11 +97,16 @@ void AmsFirmwareUpdater::ackUpgradeInformationChanged() {
|
||||
}
|
||||
|
||||
float AmsFirmwareUpdater::getProgress() {
|
||||
if(strlen(updateStatus.toVersion) == 0 || updateStatus.size == 0) return -1.0;
|
||||
if(strlen(updateStatus.toVersion) == 0 || updateStatus.size == 0 || updateStatus.errorCode >= AMS_UPDATE_ERR_SUCCESS_SIGNAL) return -1.0;
|
||||
return min((float) 100.0, ((((float) updateStatus.block_position) * UPDATE_BUF_SIZE) / updateStatus.size) * 100);
|
||||
}
|
||||
|
||||
void AmsFirmwareUpdater::loop() {
|
||||
if(millis() < 30000) {
|
||||
// Wait 30 seconds before starting upgrade. This allows the device to deal with other tasks first
|
||||
// It will also allow BUS powered devices to reach a stable voltage so that hw->isVoltageOptimal will behave properly
|
||||
return;
|
||||
}
|
||||
if(strlen(updateStatus.toVersion) > 0 && updateStatus.errorCode == AMS_UPDATE_ERR_OK) {
|
||||
if(!hw->isVoltageOptimal(0.1)) {
|
||||
writeUpdateStatus();
|
||||
@@ -124,7 +134,7 @@ void AmsFirmwareUpdater::loop() {
|
||||
HTTPClient http;
|
||||
start = millis();
|
||||
if(!fetchFirmwareChunk(http)) {
|
||||
if(updateStatus.retry_count++ == 3) {
|
||||
if(updateStatus.retry_count++ > UPDATE_MAX_BLOCK_RETRY) {
|
||||
updateStatus.errorCode = AMS_UPDATE_ERR_FETCH;
|
||||
updateStatusChanged = true;
|
||||
}
|
||||
@@ -203,15 +213,33 @@ void AmsFirmwareUpdater::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void AmsFirmwareUpdater::getChannelName(char * buffer) {
|
||||
switch(firmwareChannel) {
|
||||
case FIRMWARE_CHANNEL_EARLY:
|
||||
strcpy(buffer, PSTR("early"));
|
||||
break;
|
||||
case FIRMWARE_CHANNEL_RC:
|
||||
strcpy(buffer, PSTR("rc"));
|
||||
break;
|
||||
case FIRMWARE_CHANNEL_SNAPSHOT:
|
||||
strcpy(buffer, PSTR("snapshot"));
|
||||
break;
|
||||
default:
|
||||
strcpy(buffer, PSTR("stable"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsFirmwareUpdater::fetchNextVersion() {
|
||||
HTTPClient http;
|
||||
const char * headerkeys[] = { "x-version" };
|
||||
http.collectHeaders(headerkeys, 1);
|
||||
|
||||
char firmwareVariant[10] = "stable";
|
||||
char channel[10] = "";
|
||||
getChannelName(channel);
|
||||
|
||||
char url[128];
|
||||
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/next"), chipType, firmwareVariant);
|
||||
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/next"), chipType, channel);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
@@ -225,6 +253,16 @@ bool AmsFirmwareUpdater::fetchNextVersion() {
|
||||
http.setUserAgent("AMS-Firmware-Updater");
|
||||
http.addHeader(F("Cache-Control"), "no-cache");
|
||||
http.addHeader(F("x-AMS-version"), FirmwareVersion::VersionString);
|
||||
http.addHeader(F("x-AMS-STA-MAC"), WiFi.macAddress());
|
||||
http.addHeader(F("x-AMS-AP-MAC"), WiFi.softAPmacAddress());
|
||||
http.addHeader(F("x-AMS-chip-size"), String(ESP.getFlashChipSize()));
|
||||
http.addHeader(F("x-AMS-board-type"), String(hw->getBoardType(), 10));
|
||||
if(meterState->getMeterType() != AmsTypeAutodetect) {
|
||||
http.addHeader(F("x-AMS-meter-mfg"), String(meterState->getMeterType(), 10));
|
||||
}
|
||||
if(!meterState->getMeterModel().isEmpty()) {
|
||||
http.addHeader(F("x-AMS-meter-model"), meterState->getMeterModel());
|
||||
}
|
||||
int status = http.GET();
|
||||
if(status == 204) {
|
||||
String nextVersion = http.header("x-version");
|
||||
@@ -248,10 +286,11 @@ bool AmsFirmwareUpdater::fetchVersionDetails() {
|
||||
const char * headerkeys[] = { "x-size" };
|
||||
http.collectHeaders(headerkeys, 1);
|
||||
|
||||
char firmwareVariant[10] = "stable";
|
||||
char channel[10] = "";
|
||||
getChannelName(channel);
|
||||
|
||||
char url[128];
|
||||
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/%s/details"), chipType, firmwareVariant, updateStatus.toVersion);
|
||||
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/%s/details"), chipType, channel, updateStatus.toVersion);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
@@ -304,10 +343,11 @@ bool AmsFirmwareUpdater::fetchFirmwareChunk(HTTPClient& http) {
|
||||
char range[24];
|
||||
snprintf_P(range, 24, PSTR("bytes=%lu-%lu"), start, end);
|
||||
|
||||
char firmwareVariant[10] = "stable";
|
||||
char channel[10] = "";
|
||||
getChannelName(channel);
|
||||
|
||||
char url[128];
|
||||
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/%s/chunk"), chipType, firmwareVariant, updateStatus.toVersion);
|
||||
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/%s/chunk"), chipType, channel, updateStatus.toVersion);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
@@ -1128,7 +1168,7 @@ bool AmsFirmwareUpdater::moveLittleFsFromApp1ToNew() {
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
uintptr_t AmsFirmwareUpdater::getFirmwareUpdateStart() {
|
||||
return FS_start - 0x40200000;
|
||||
return (AMS_FLASH_SKETCH_SIZE + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||
}
|
||||
|
||||
bool AmsFirmwareUpdater::isFlashReadyForNextUpdateVersion(uint32_t size) {
|
||||
@@ -1137,6 +1177,14 @@ bool AmsFirmwareUpdater::isFlashReadyForNextUpdateVersion(uint32_t size) {
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Checking if we can upgrade\n"));
|
||||
|
||||
if(FS_PHYS_ADDR < (getFirmwareUpdateStart() + AMS_FLASH_SKETCH_SIZE)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("No room for OTA update\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!ESP.checkFlashConfig(false)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
@@ -1145,24 +1193,12 @@ bool AmsFirmwareUpdater::isFlashReadyForNextUpdateVersion(uint32_t size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//size of current sketch rounded to a sector
|
||||
size_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||
|
||||
//size of the update rounded to a sector
|
||||
size_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||
|
||||
//address of the end of the space available for sketch and update
|
||||
uintptr_t updateEndAddress = FS_start - 0x40200000;
|
||||
|
||||
uintptr_t updateStartAddress = (updateEndAddress > roundedSize) ? (updateEndAddress - roundedSize) : 0;
|
||||
|
||||
//make sure that the size of both sketches is less than the total space (updateEndAddress)
|
||||
if(updateStartAddress < currentSketchSize) {
|
||||
if(size > AMS_FLASH_SKETCH_SIZE) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("New firmware does not fit flash\n"));
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::INFO))
|
||||
@@ -1180,14 +1216,28 @@ bool AmsFirmwareUpdater::writeBufferToFlash() {
|
||||
uint32_t offset = updateStatus.block_position * UPDATE_BUF_SIZE;
|
||||
uintptr_t currentAddress = getFirmwareUpdateStart() + offset;
|
||||
uint32_t sector = currentAddress/FLASH_SECTOR_SIZE;
|
||||
if(!ESP.flashEraseSector(sector)) {
|
||||
|
||||
if (currentAddress % FLASH_SECTOR_SIZE == 0) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("flashEraseSector(%lu) failed\n"), sector);
|
||||
updateStatus.errorCode = AMS_UPDATE_ERR_ERASE;
|
||||
return false;
|
||||
debugger->printf_P(PSTR("flashEraseSector(%lu)\n"), sector);
|
||||
yield();
|
||||
if(!ESP.flashEraseSector(sector)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("flashEraseSector(%lu) failed\n"), sector);
|
||||
updateStatus.errorCode = AMS_UPDATE_ERR_ERASE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("flashWrite(%lu)\n"), sector);
|
||||
yield();
|
||||
if(!ESP.flashWrite(currentAddress, buf, UPDATE_BUF_SIZE)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
|
||||
14
lib/AmsJsonGenerator/include/AmsJsonGenerator.h
Normal file
14
lib/AmsJsonGenerator/include/AmsJsonGenerator.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
#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);
|
||||
};
|
||||
22
lib/AmsJsonGenerator/src/AmsJsonGenerator.cpp
Normal file
22
lib/AmsJsonGenerator/src/AmsJsonGenerator.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
#include "AmsJsonGenerator.h"
|
||||
|
||||
void AmsJsonGenerator::generateDayPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize) {
|
||||
uint16_t pos = snprintf_P(buf, bufSize, PSTR("{\"unit\":\"kwh\""));
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
pos += snprintf_P(buf+pos, bufSize-pos, PSTR(",\"i%02d\":%.3f,\"e%02d\":%.3f"), i, ds->getHourImport(i) / 1000.0, i, ds->getHourExport(i) / 1000.0);
|
||||
}
|
||||
snprintf_P(buf+pos, bufSize-pos, PSTR("}"));
|
||||
}
|
||||
|
||||
void AmsJsonGenerator::generateMonthPlotJson(AmsDataStorage* ds, char* buf, size_t bufSize) {
|
||||
uint16_t pos = snprintf_P(buf, bufSize, PSTR("{\"unit\":\"kwh\""));
|
||||
for(uint8_t i = 1; i < 32; i++) {
|
||||
pos += snprintf_P(buf+pos, bufSize-pos, PSTR(",\"i%02d\":%.3f,\"e%02d\":%.3f"), i, ds->getDayImport(i) / 1000.0, i, ds->getDayExport(i) / 1000.0);
|
||||
}
|
||||
snprintf_P(buf+pos, bufSize-pos, PSTR("}"));
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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) {
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, AmsFirmwareUpdater* updater) {
|
||||
#endif
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->mqttConfigChanged = true;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
this->updater = updater;
|
||||
mqtt.dropOverflow(true);
|
||||
|
||||
pubTopic = String(mqttConfig.publishTopic);
|
||||
subTopic = String(mqttConfig.subscribeTopic);
|
||||
if(subTopic.isEmpty()) subTopic = pubTopic+"/command";
|
||||
};
|
||||
#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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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,6 +103,17 @@ 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);
|
||||
@@ -117,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) {
|
||||
@@ -153,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();
|
||||
}
|
||||
@@ -165,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();
|
||||
@@ -178,4 +216,8 @@ bool AmsMqttHandler::loop() {
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::isRebootSuggested() {
|
||||
return rebootSuggested;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -169,9 +169,24 @@ bool CloudConnector::init() {
|
||||
}
|
||||
|
||||
void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
if(!config.enabled) return;
|
||||
unsigned long now = millis();
|
||||
if(now-lastUpdate < config.interval*1000) return;
|
||||
if(now-lastUpdate < ((unsigned long)config.interval)*1000) {
|
||||
return;
|
||||
};
|
||||
bool sendFirst = lastUpdate == 0;
|
||||
lastUpdate = now;
|
||||
if(config.enabled) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Enabled\n"));
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) Not enabled\n"));
|
||||
return;
|
||||
}
|
||||
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
@@ -179,15 +194,19 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
debugger->printf_P(PSTR("(CloudConnector) Client ID is not valid\n"));
|
||||
return;
|
||||
}
|
||||
if(data.getListType() < 2) return;
|
||||
if(data.getListType() < 2) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::VERBOSE))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("(CloudConnector) List type not enough data\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!initialized && !init()) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::WARNING))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("Unable to initialize cloud connector\n"));
|
||||
lastUpdate = now;
|
||||
config.enabled = false;
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
@@ -202,7 +221,7 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
}
|
||||
|
||||
bool sendData = true;
|
||||
if(lastUpdate == 0) {
|
||||
if(sendFirst) {
|
||||
seed.clear();
|
||||
if(mainFuse > 0 && distributionSystem > 0) {
|
||||
int voltage = distributionSystem == 2 ? 400 : 230;
|
||||
@@ -460,7 +479,13 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
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)
|
||||
@@ -481,7 +506,7 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
Stream *stream = NULL;
|
||||
|
||||
if(config.proto == 0) {
|
||||
udp.beginPacket(config.hostname,7443);
|
||||
udp.beginPacket(config.hostname, config.port);
|
||||
stream = &udp;
|
||||
} else if(config.proto == 1) {
|
||||
if(!tcp.connected()) {
|
||||
@@ -493,7 +518,7 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
debugger->printf_P(PSTR("tcp.connect(%s, %d) return code: %d\n"), config.hostname, config.port, ret);
|
||||
return;
|
||||
}
|
||||
tcp.setTimeout(config.interval * 2);
|
||||
tcp.setTimeout((config.interval * 1000) / 2);
|
||||
}
|
||||
while(tcp.available()) tcp.read(); // Empty incoming buffer
|
||||
stream = &tcp;
|
||||
@@ -521,6 +546,7 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
if(ret == 0) {
|
||||
if(stream != NULL) {
|
||||
stream->write(encryptedBuffer, rsa->len);
|
||||
stream->flush();
|
||||
} else {
|
||||
memcpy(httpBuffer + sendBytes, encryptedBuffer, rsa->len);
|
||||
}
|
||||
@@ -565,12 +591,11 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
http.end();
|
||||
}
|
||||
}
|
||||
lastUpdate = now;
|
||||
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::DEBUG))
|
||||
#endif
|
||||
debugger->printf_P(PSTR("%d bytes sent to %s:%d from %s\n"), sendBytes, config.hostname, config.proto == 2 ? 80 : config.port, uuid.c_str());
|
||||
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() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -114,6 +114,7 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
|
||||
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))
|
||||
@@ -147,6 +148,9 @@ void EthernetConnectionHandler::eventHandler(WiFiEvent_t event, WiFiEventInfo_t
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -13,11 +13,11 @@
|
||||
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
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, DomoticzConfig config, AmsFirmwareUpdater* updater) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
this->config = config;
|
||||
};
|
||||
#endif
|
||||
@@ -25,7 +25,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);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -7,17 +7,39 @@
|
||||
#ifndef _ENERGYACCOUNTING_H
|
||||
#define _ENERGYACCOUNTING_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
#include "AmsDataStorage.h"
|
||||
#include "PriceService.h"
|
||||
|
||||
struct EnergyAccountingPeak {
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
struct EnergyAccountingPeak6 {
|
||||
uint8_t day;
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
struct EnergyAccountingData {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
int32_t costToday;
|
||||
int32_t costYesterday;
|
||||
int32_t costThisMonth;
|
||||
int32_t costLastMonth;
|
||||
int32_t incomeToday;
|
||||
int32_t incomeYesterday;
|
||||
int32_t incomeThisMonth;
|
||||
int32_t incomeLastMonth;
|
||||
uint32_t lastMonthImport;
|
||||
uint32_t lastMonthExport;
|
||||
uint8_t lastMonthAccuracy;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
time_t lastUpdated;
|
||||
};
|
||||
|
||||
struct EnergyAccountingData6 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
int32_t costYesterday;
|
||||
@@ -29,37 +51,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 {
|
||||
@@ -89,7 +81,7 @@ public:
|
||||
void setPriceService(PriceService *ps);
|
||||
void setTimezone(Timezone*);
|
||||
EnergyAccountingConfig* getConfig();
|
||||
bool update(AmsData* amsData);
|
||||
bool update(time_t now, uint64_t lastUpdatedMillis, uint8_t listType, uint32_t activeImportPower, uint32_t activeExportPower);
|
||||
bool load();
|
||||
bool save();
|
||||
bool isInitialized();
|
||||
@@ -124,7 +116,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 +128,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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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) {
|
||||
@@ -54,9 +54,8 @@ bool EnergyAccounting::isInitialized() {
|
||||
return this->init;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::update(AmsData* amsData) {
|
||||
bool EnergyAccounting::update(time_t now, uint64_t lastUpdatedMillis, uint8_t listType, uint32_t activeImportPower, uint32_t activeExportPower) {
|
||||
if(config == NULL) return false;
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
if(tz == NULL) {
|
||||
return false;
|
||||
@@ -67,59 +66,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 && (listType >= 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 +144,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;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(realtimeData->lastImportUpdateMillis < lastUpdatedMillis) {
|
||||
unsigned long ms = lastUpdatedMillis - realtimeData->lastImportUpdateMillis;
|
||||
float kwhi = (activeImportPower * (((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 = lastUpdatedMillis;
|
||||
}
|
||||
|
||||
if(amsData->getListType() > 1 && this->realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastExportUpdateMillis;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(listType > 1 && realtimeData->lastExportUpdateMillis < lastUpdatedMillis) {
|
||||
unsigned long ms = lastUpdatedMillis - realtimeData->lastExportUpdateMillis;
|
||||
float kwhe = (activeExportPower * (((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 = lastUpdatedMillis;
|
||||
}
|
||||
|
||||
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 +194,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 +231,7 @@ void EnergyAccounting::calcDayCost() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseThisHour() {
|
||||
return this->realtimeData->use;
|
||||
return realtimeData->use;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseToday() {
|
||||
@@ -231,7 +241,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,18 +252,20 @@ float EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseLastMonth() {
|
||||
return (data.lastMonthImport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
float ret = (data.lastMonthImport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
if(std::isnan(ret)) return 0.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedThisHour() {
|
||||
return this->realtimeData->produce;
|
||||
return realtimeData->produce;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedToday() {
|
||||
@@ -263,7 +275,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,22 +286,24 @@ float EnergyAccounting::getProducedThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
for(uint8_t i = 1; i < realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayExport(i) / 1000.0;
|
||||
}
|
||||
return ret + getProducedToday();
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedLastMonth() {
|
||||
return (data.lastMonthExport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
float ret = (data.lastMonthExport * pow(10, data.lastMonthAccuracy)) / 1000;
|
||||
if(std::isnan(ret)) return 0.0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostThisHour() {
|
||||
return this->realtimeData->costHour;
|
||||
return realtimeData->costHour;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostToday() {
|
||||
return this->realtimeData->costDay;
|
||||
return realtimeData->costDay;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostYesterday() {
|
||||
@@ -305,11 +319,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 +341,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 +421,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 +478,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 +511,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);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -15,30 +15,31 @@
|
||||
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) {
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, Stream* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw, AmsFirmwareUpdater* updater, char* hostname) : AmsMqttHandler(mqttConfig, debugger, buf, updater) {
|
||||
#endif
|
||||
this->boardType = boardType;
|
||||
this->hw = hw;
|
||||
setHomeAssistantConfig(config);
|
||||
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);
|
||||
void setHomeAssistantConfig(HomeAssistantConfig config, char* hostname);
|
||||
private:
|
||||
uint8_t boardType;
|
||||
|
||||
String topic;
|
||||
|
||||
String deviceName;
|
||||
String deviceModel;
|
||||
String deviceUid;
|
||||
@@ -46,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;
|
||||
@@ -77,6 +79,7 @@ private:
|
||||
void publishPriceSensors(PriceService* ps);
|
||||
void publishSystemSensors();
|
||||
void publishThresholdSensors();
|
||||
void toJsonIsoTimestamp(time_t t, char* buf, size_t buflen);
|
||||
|
||||
String boardTypeToString(uint8_t b) {
|
||||
switch(b) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -17,113 +17,113 @@ struct HomeAssistantSensor {
|
||||
const char* uom;
|
||||
const char* devcl;
|
||||
const char* stacl;
|
||||
const char* uid;
|
||||
};
|
||||
|
||||
|
||||
const uint8_t List1SensorCount PROGMEM = 2;
|
||||
const HomeAssistantSensor List1Sensors[List1SensorCount] PROGMEM = {
|
||||
{"Active import", "/power", "P", 30, "W", "power", "measurement"},
|
||||
{"Data timestamp", "/power", "t", 30, "", "timestamp", ""}
|
||||
{"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 = 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"},
|
||||
{"Meter timestamp", "/energy", "rtc", 4000, "", "timestamp", ""}
|
||||
{"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,4 +1,4 @@
|
||||
{
|
||||
"P" : %lu,
|
||||
"t" : "%s"
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"tPO" : %.3f,
|
||||
"tQI" : %.3f,
|
||||
"tQO" : %.3f,
|
||||
"rtc" : "%s",
|
||||
"t" : "%s"
|
||||
"rtc" : %s,
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"t" : "%s"
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"tPO1" : %.3f,
|
||||
"tPO2" : %.3f,
|
||||
"tPO3" : %.3f,
|
||||
"t" : "%s"
|
||||
"t" : %s
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
"name" : "%s%s",
|
||||
"stat_t" : "%s%s",
|
||||
"uniq_id" : "%s_%s",
|
||||
"obj_id" : "%s_%s",
|
||||
"unit_of_meas" : "%s",
|
||||
"default_entity_id" : "sensor.%s_%s",
|
||||
"val_tpl" : "{{ value_json.%s | is_defined }}",
|
||||
"expire_after" : %d,
|
||||
"dev" : {
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -19,10 +19,8 @@
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config) {
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = rInit = false;
|
||||
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
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);
|
||||
@@ -30,21 +28,18 @@ void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config
|
||||
snprintf_P(json, 128, PSTR("[%s] "), config.discoveryNameTag);
|
||||
sensorNamePrefix = String(json);
|
||||
} else {
|
||||
deviceName = F("AMS reader");
|
||||
snprintf_P(json, 128, PSTR("AMS reader"));
|
||||
deviceName = String(json);
|
||||
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 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) {
|
||||
@@ -58,21 +53,42 @@ void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config
|
||||
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);
|
||||
discoveryTopic = String(json);
|
||||
} else {
|
||||
statusTopic = F("homeassistant/status");
|
||||
discoveryTopic = F("homeassistant/sensor/");
|
||||
if(strlen(config.discoveryPrefix) == 0) {
|
||||
snprintf_P(config.discoveryPrefix, 64, PSTR("homeassistant"));
|
||||
}
|
||||
|
||||
snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix);
|
||||
statusTopic = String(json);
|
||||
|
||||
snprintf_P(json, 128, PSTR("%s/sensor"), config.discoveryPrefix);
|
||||
sensorTopic = String(json);
|
||||
|
||||
snprintf_P(json, 128, PSTR("%s/update"), config.discoveryPrefix);
|
||||
updateTopic = String(json);
|
||||
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::postConnect() {
|
||||
bool ret = true;
|
||||
if(!statusTopic.isEmpty()) {
|
||||
if(mqtt.subscribe(statusTopic)) {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Subscribed to [%s]\n"), statusTopic.c_str());
|
||||
} else {
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
if (debugger->isActive(RemoteDebug::ERROR))
|
||||
#endif
|
||||
debugger->printf_P(PSTR(" Unable to subscribe to [%s]\n"), statusTopic.c_str());
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* update, AmsData* previousState, EnergyAccounting* ea, PriceService* ps) {
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
if(pubTopic.isEmpty() || !connected())
|
||||
return false;
|
||||
|
||||
if(time(nullptr) < FirmwareVersion::BuildEpoch)
|
||||
@@ -117,15 +133,10 @@ bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea)
|
||||
publishList1Sensors();
|
||||
|
||||
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);
|
||||
}
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower(), pt);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
@@ -133,12 +144,7 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea)
|
||||
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);
|
||||
}
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA3_JSON,
|
||||
data->getListId().c_str(),
|
||||
@@ -156,7 +162,7 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea)
|
||||
data->getL3Voltage(),
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
@@ -164,20 +170,11 @@ bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea)
|
||||
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);
|
||||
}
|
||||
toJsonIsoTimestamp(data->getMeterTimestamp(), mt, sizeof(mt));
|
||||
|
||||
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);
|
||||
}
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA2_JSON,
|
||||
data->getActiveImportCounter(),
|
||||
@@ -187,7 +184,7 @@ bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea)
|
||||
mt,
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/energy", json);
|
||||
return mqtt.publish(pubTopic + "/energy", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
@@ -195,12 +192,7 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
|
||||
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);
|
||||
}
|
||||
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, HA4_JSON,
|
||||
data->getListId().c_str(),
|
||||
@@ -234,7 +226,7 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
|
||||
data->getL3ActiveExportCounter(),
|
||||
pt
|
||||
);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
return mqtt.publish(pubTopic + "/power", json);
|
||||
}
|
||||
|
||||
String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
@@ -290,18 +282,13 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
|
||||
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);
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
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) {
|
||||
@@ -326,24 +313,19 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
|
||||
|
||||
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);
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}"));
|
||||
|
||||
bool ret = mqtt.publish(topic + "/temperatures", json);
|
||||
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);
|
||||
@@ -356,7 +338,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;
|
||||
@@ -404,70 +386,84 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
|
||||
memset(ts1hr, 0, 24);
|
||||
if(min1hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts1hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts1hr, sizeof(ts1hr));
|
||||
}
|
||||
char ts3hr[24];
|
||||
memset(ts3hr, 0, 24);
|
||||
if(min3hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts3hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts3hr, sizeof(ts3hr));
|
||||
}
|
||||
char ts6hr[24];
|
||||
memset(ts6hr, 0, 24);
|
||||
if(min6hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts6hr, sizeof(ts6hr));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
char pt[24];
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
|
||||
|
||||
json[pos++] = '}';
|
||||
json[pos] = '\0';
|
||||
|
||||
bool ret = mqtt.publish(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();
|
||||
@@ -475,14 +471,9 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, PriceService* ps, Ener
|
||||
|
||||
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);
|
||||
}
|
||||
toJsonIsoTimestamp(now, pt, sizeof(pt));
|
||||
|
||||
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\",\"t\":\"%s\"}"),
|
||||
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),
|
||||
@@ -492,24 +483,28 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, PriceService* ps, Ener
|
||||
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(),
|
||||
@@ -518,14 +513,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();
|
||||
}
|
||||
|
||||
@@ -614,7 +616,8 @@ void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, Pric
|
||||
RealtimePeakSensor.ttl,
|
||||
RealtimePeakSensor.uom,
|
||||
RealtimePeakSensor.devcl,
|
||||
RealtimePeakSensor.stacl
|
||||
RealtimePeakSensor.stacl,
|
||||
RealtimePeakSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -653,7 +656,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;
|
||||
@@ -673,45 +677,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++) {
|
||||
@@ -734,7 +789,8 @@ void HomeAssistantMqttHandler::publishThresholdSensors() {
|
||||
RealtimeThresholdSensor.ttl,
|
||||
RealtimeThresholdSensor.uom,
|
||||
RealtimeThresholdSensor.devcl,
|
||||
RealtimeThresholdSensor.stacl
|
||||
RealtimeThresholdSensor.stacl,
|
||||
RealtimeThresholdSensor.uid
|
||||
};
|
||||
publishSensor(sensor);
|
||||
}
|
||||
@@ -745,8 +801,49 @@ 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) {
|
||||
@@ -756,9 +853,27 @@ void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
|
||||
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;
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HomeAssistantMqttHandler::toJsonIsoTimestamp(time_t t, char* buf, size_t buflen) {
|
||||
memset(buf, 0, buflen);
|
||||
if(t > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
snprintf_P(buf, buflen, PSTR("\"%04d-%02d-%02dT%02d:%02d:%02dZ\""), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
} else {
|
||||
snprintf_P(buf, buflen, PSTR("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -45,6 +45,7 @@ public:
|
||||
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
|
||||
void setup(SystemConfig* sys, GpioConfig* gpio);
|
||||
float getVcc();
|
||||
void setMaxVcc(float maxVcc);
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorData* getTempSensorData(uint8_t);
|
||||
bool updateTemperatures();
|
||||
@@ -67,7 +68,9 @@ private:
|
||||
bool ledInvert, rgbInvert;
|
||||
uint8_t vccPin, vccGnd_r, vccVcc_r;
|
||||
float vccOffset, vccMultiplier;
|
||||
float maxVcc = 3.2; // Best to have this close to max as a start, in case Pow-U reboots and starts off with a low voltage, we dont want that to be perceived as max
|
||||
float vcc = 3.3; // Last known Vcc
|
||||
float maxVcc = 3.28; // Best to have this close to max as a start, in case Pow-U reboots and starts off with a low voltage, we dont want that to be perceived as max
|
||||
unsigned long lastVccRead = 0;
|
||||
|
||||
uint16_t analogRange = 1024;
|
||||
AdcConfig voltAdc, tempAdc;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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;
|
||||
@@ -201,15 +204,15 @@ void HwTools::setup(SystemConfig* sys, GpioConfig* config) {
|
||||
pinMode(config->vccPin, INPUT);
|
||||
#endif
|
||||
vccPin = config->vccPin;
|
||||
vccOffset = config->vccOffset / 100.0;
|
||||
vccMultiplier = config->vccMultiplier / 1000.0;
|
||||
vccGnd_r = config->vccResistorGnd;
|
||||
vccVcc_r = config->vccResistorVcc;
|
||||
} else {
|
||||
voltAdc.unit = 0xFF;
|
||||
voltAdc.channel = 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);
|
||||
@@ -417,17 +420,23 @@ float HwTools::getVcc() {
|
||||
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(vccGnd_r > 0 && vccVcc_r > 0) {
|
||||
if(vccGnd_r > 0 && vccVcc_r > 0)
|
||||
volts *= ((float) (vccGnd_r + vccVcc_r) / vccGnd_r);
|
||||
}
|
||||
|
||||
return vccOffset + (volts > 0.0 ? volts * vccMultiplier : 0.0);
|
||||
if(vccOffset != 0.0)
|
||||
volts += vccOffset;
|
||||
if(vccMultiplier != 0.0)
|
||||
volts *= vccMultiplier;
|
||||
|
||||
return volts;
|
||||
}
|
||||
|
||||
uint8_t HwTools::getTempSensorCount() {
|
||||
@@ -648,10 +657,15 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
|
||||
}
|
||||
|
||||
bool HwTools::isVoltageOptimal(float range) {
|
||||
if(boardType >= 5 && boardType <= 7 && maxVcc > 2.8) { // Pow-*
|
||||
float vcc = getVcc();
|
||||
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
|
||||
maxVcc = 0;
|
||||
return true; // Voltage is outside the operating range, we have to assume voltage is OK
|
||||
} else if(vcc > maxVcc) {
|
||||
maxVcc = vcc;
|
||||
} else {
|
||||
@@ -664,4 +678,8 @@ bool HwTools::isVoltageOptimal(float range) {
|
||||
|
||||
uint8_t HwTools::getBoardType() {
|
||||
return boardType;
|
||||
}
|
||||
|
||||
void HwTools::setMaxVcc(float vcc) {
|
||||
this->maxVcc = min(3.3f, vcc);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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);
|
||||
@@ -39,5 +42,6 @@ private:
|
||||
bool publishList3(AmsData* data, EnergyAccounting* ea);
|
||||
bool publishList4(AmsData* data, EnergyAccounting* ea);
|
||||
String getMeterModel(AmsData* data);
|
||||
void toJsonIsoTimestamp(time_t t, char* buf, size_t buflen);
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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;
|
||||
@@ -333,59 +356,89 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
|
||||
memset(ts1hr, 0, 24);
|
||||
if(min1hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts1hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts1hr, sizeof(ts1hr));
|
||||
}
|
||||
char ts3hr[24];
|
||||
memset(ts3hr, 0, 24);
|
||||
if(min3hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts3hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts3hr, sizeof(ts3hr));
|
||||
}
|
||||
char ts6hr[24];
|
||||
memset(ts6hr, 0, 24);
|
||||
if(min6hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
|
||||
tmElements_t tm;
|
||||
tmElements_t tm;
|
||||
breakTime(ts, tm);
|
||||
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
ts = makeTime(tm);
|
||||
toJsonIsoTimestamp(ts, ts6hr, sizeof(ts6hr));
|
||||
}
|
||||
|
||||
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 +453,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 +481,77 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JsonMqttHandler::toJsonIsoTimestamp(time_t t, char* buf, size_t buflen) {
|
||||
memset(buf, 0, buflen);
|
||||
if(t > 0) {
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
snprintf_P(buf, buflen, PSTR("\"%04d-%02d-%02dT%02d:%02d:%02dZ\""), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
|
||||
} else {
|
||||
snprintf_P(buf, buflen, PSTR("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParser.h"
|
||||
#include "Cosem.h"
|
||||
#include "Timezone.h"
|
||||
#if defined(AMS_REMOTE_DEBUG)
|
||||
#include "RemoteDebug.h"
|
||||
#endif
|
||||
|
||||
#define NOVALUE 0xFFFFFFFF
|
||||
|
||||
@@ -21,7 +25,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 +38,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,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2024
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: All rights reserved
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -13,6 +13,7 @@
|
||||
#endif
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "AmsMqttHandler.h"
|
||||
|
||||
class MeterCommunicator {
|
||||
public:
|
||||
@@ -24,6 +25,13 @@ public:
|
||||
virtual bool isConfigChanged();
|
||||
virtual void ackConfigChanged();
|
||||
virtual void getCurrentConfig(MeterConfig& meterConfig);
|
||||
virtual void setMqttHandlerForDebugging(AmsMqttHandler* mqttHandler) {
|
||||
this->mqttDebug = mqttHandler;
|
||||
};
|
||||
|
||||
protected:
|
||||
AmsMqttHandler* mqttDebug = NULL;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
@@ -14,7 +14,7 @@
|
||||
#include "AmsConfiguration.h"
|
||||
#include "DataParsers.h"
|
||||
#include "Timezone.h"
|
||||
#include "PassthroughMqttHandler.h"
|
||||
#include "AmsMqttHandler.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "SoftwareSerial.h"
|
||||
@@ -36,7 +36,9 @@ public:
|
||||
bool isConfigChanged();
|
||||
void ackConfigChanged();
|
||||
void getCurrentConfig(MeterConfig& meterConfig);
|
||||
void setPassthroughMqttHandler(PassthroughMqttHandler*);
|
||||
void setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
};
|
||||
|
||||
HardwareSerial* getHwSerial();
|
||||
void rxerr(int err);
|
||||
@@ -51,8 +53,6 @@ protected:
|
||||
bool configChanged = false;
|
||||
Timezone* tz;
|
||||
|
||||
PassthroughMqttHandler* pt = NULL;
|
||||
|
||||
uint8_t *hanBuffer = NULL;
|
||||
uint16_t hanBufferSize = 0;
|
||||
Stream *hanSerial;
|
||||
@@ -62,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;
|
||||
|
||||
@@ -91,6 +92,7 @@ protected:
|
||||
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
|
||||
void printHanReadError(int pos);
|
||||
void handleAutodetect(unsigned long now);
|
||||
uint8_t getNextParity(uint8_t parityOrdinal);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2024
|
||||
* @copyright Utilitech AS 2023-2026
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user