Compare commits

...

89 Commits

Author SHA1 Message Date
Gunnar Skjold
13bbc81b7f Clear debug config on setup if not already set 2021-09-23 20:56:24 +02:00
Gunnar Skjold
7412ba2697 Removed temp sensor update if no sensors were found 2021-09-23 20:30:22 +02:00
Gunnar Skjold
dce7b7e64b Support for long MQTT username and password 2021-09-23 19:28:55 +02:00
Gunnar Skjold
7627d6c369 Updated readme 2021-09-23 18:48:58 +02:00
Gunnar Skjold
9fc9adea1c Fixed crash when enabling substitute values in meter config 2021-09-23 18:30:15 +02:00
Gunnar Skjold
46df07fe40 Fixed build error on ESP32 2021-09-23 18:20:54 +02:00
Gunnar Skjold
0aeb5555e7 Fixed long web passwords 2021-09-23 18:10:16 +02:00
Gunnar Skjold
181fe3c909 Removed meter from setup 2021-09-23 17:36:43 +02:00
Gunnar Skjold
867d6a4ef8 Added profiles for Pow board 2021-09-23 17:34:21 +02:00
Gunnar Skjold
d52216a73e Moved hardware page to Wiki 2021-09-23 17:28:51 +02:00
Gunnar Skjold
d70b41a454 Added images for Pow 2021-09-23 17:25:52 +02:00
Gunnar Skjold
332c366561 Renamed kamstrup meters 2021-09-22 11:44:59 +02:00
Gunnar Skjold
b572ad97f8 Added Kamstrup document 2021-09-22 11:42:22 +02:00
Gunnar Skjold
aa307f8690 Fixed incorrect value for accumulated export 2021-09-22 11:40:48 +02:00
Gunnar Skjold
19223312b5 Fixed danish KAmstrup 2021-09-13 10:14:47 +02:00
Gunnar Skjold
74bc5aa7a0 Fixed build 2021-09-13 10:14:28 +02:00
Gunnar Skjold
e66e8a96ff Added some frames as documentation 2021-09-13 08:10:39 +02:00
Gunnar Skjold
ce95360a64 Minor fix 2021-09-13 08:08:39 +02:00
Gunnar Skjold
7802aeaab1 Removed unnecessary config for arduinojson 2021-09-13 08:07:38 +02:00
Gunnar Skjold
37ce3566bf Fixed config clear 2021-09-13 08:07:03 +02:00
Gunnar Skjold
606bac100a Cleanup 2021-03-24 09:37:46 +01:00
Gunnar Skjold
be569d1802 Fixed buffer for JSON generation 2021-03-17 20:36:40 +01:00
Gunnar Skjold
3e21105b2d Fixed prices in JSON handler 2021-03-17 20:33:11 +01:00
Gunnar Skjold
9946827431 Fixed cheapest price calculation 2021-03-17 20:17:36 +01:00
Gunnar Skjold
90d03ca77f Retain flag for prices 2021-03-16 11:44:01 +01:00
Gunnar Skjold
7727b17122 Fixed publishing of prices 2021-03-16 11:02:59 +01:00
Gunnar Skjold
fbc4c8c502 Merge pull request #101 from gskjold/dev-v1.5.0
Dev v1.5.0
2021-03-10 19:22:23 +01:00
Gunnar Skjold
5d6a4ea0a4 Updated readme 2021-03-10 19:15:00 +01:00
Gunnar Skjold
91ce486bde Updated readme 2021-03-10 19:14:22 +01:00
Gunnar Skjold
523139749e Auto install minifier 2021-03-10 19:14:12 +01:00
Gunnar Skjold
5b9f3b7aed Minor changes after testing 2021-03-10 18:33:45 +01:00
Gunnar Skjold
af76243761 Retain flag for accumulated values 2021-03-10 17:45:16 +01:00
Gunnar Skjold
605dc4901c Fixed incorrect check for 32/35 A main fuse 2021-03-10 17:45:02 +01:00
Gunnar Skjold
54fb950513 Changes after testing 2021-01-22 08:01:23 +01:00
Gunnar Skjold
ff84278edf Some changes after testing 2021-01-20 07:41:09 +01:00
Gunnar Skjold
f15cf5d75e Some changes after testing 2021-01-18 20:32:51 +01:00
Gunnar Skjold
33070af111 Refactored MQTT payload handling into separate classes 2021-01-17 20:11:04 +01:00
Gunnar Skjold
53573184f3 Simplified temperature.json 2021-01-17 15:53:56 +01:00
Gunnar Skjold
decc4788a7 Simplified code that generates data.json 2021-01-17 15:08:01 +01:00
Gunnar Skjold
f9597c786e Changed to BearSSL for meter decryption on ESP8266 + some minor changes 2021-01-17 12:34:57 +01:00
Gunnar Skjold
af8f5a7c24 Memory optimization and bugfix 2021-01-16 16:02:39 +01:00
Gunnar Skjold
a830a52863 Stripped HTML to improve stability 2021-01-14 20:03:49 +01:00
Gunnar Skjold
037bac24de Changes in user interface 2021-01-14 16:19:00 +01:00
Gunnar Skjold
837c3cf802 Taking timezone into account when calculating midnight 2021-01-11 20:21:36 +01:00
Gunnar Skjold
75956c087c Closing http connections after receiving data 2021-01-11 09:49:25 +01:00
Gunnar Skjold
88528b4099 Added reboot and cleaned up a bit in UI 2021-01-10 22:30:55 +01:00
Gunnar Skjold
6176d34e84 Link to documentation 2021-01-10 22:03:02 +01:00
Gunnar Skjold
f2dda26bbb Added support for retrieving energy price from ENTSO-E API 2021-01-10 20:54:25 +01:00
Gunnar Skjold
402ecf67d7 Merge branch 'master' into dev-v1.5.0 2021-01-09 11:25:31 +01:00
Gunnar Skjold
a83a6c1c53 Fixed release 2021-01-09 11:21:01 +01:00
Gunnar Skjold
5beb13894c Initial implementation of supporting timezone in timestamp 2021-01-09 11:11:49 +01:00
Gunnar Skjold
c3fa618ab2 Fixed build problem 2021-01-09 09:50:20 +01:00
Gunnar Skjold
7713ae8566 Trying to fix GitHub Actions build issue 2021-01-09 09:36:20 +01:00
Gunnar Skjold
7ae860ec72 Fixed incorrect reading of analog temperature 2021-01-09 09:33:09 +01:00
Gunnar Skjold
376008a735 Merge pull request #96 from kallemooo/fixFromHex
Changed fromHex() to use an supplied buffer
2021-01-04 09:58:32 +01:00
Karl Thorén
feed10184b Changed fromHex() to use an supplied buffer
Solves the problem with returning a pointer to local variable.

Signed-off-by: Karl Thorén <karl.h.thoren@gmail.com>
2020-12-27 16:29:53 +01:00
Gunnar Skjold
59ca29f6a8 Update README.md 2020-09-25 06:46:16 +02:00
Gunnar Skjold
644a3fa40b Fixed factory reset 2020-09-05 20:03:06 +02:00
Gunnar Skjold
a2c1c8fc61 Updated ESP32 flash procedure 2020-09-05 19:27:36 +02:00
Gunnar Skjold
b823f029ea Updated readme 2020-09-05 19:12:59 +02:00
Gunnar Skjold
fd907deec1 Merge pull request #87 from gskjold/dev-v1.4.0
Dev v1.4.0
2020-09-05 19:05:01 +02:00
Gunnar Skjold
c1b56e25ad Minor changes 2020-09-05 18:59:52 +02:00
Gunnar Skjold
5491088cec Cleared out some unnecessary files from mbedtls lib 2020-09-05 11:14:00 +02:00
Gunnar Skjold
04271accc1 Dynamic temperature page 2020-09-05 11:10:36 +02:00
Gunnar Skjold
a332e38b97 Some cleanup 2020-09-05 09:40:07 +02:00
Gunnar Skjold
17bd85ebd0 Added more debugging code. Also some changes after testing 2020-09-04 07:25:11 +02:00
Gunnar Skjold
4207216770 Some changes for mbedtls when building ESP32 2020-08-28 21:20:11 +02:00
Gunnar Skjold
02491e074b Added comment 2020-08-28 20:33:46 +02:00
Gunnar Skjold
e79a0585f0 Some changes during testing 2020-08-28 19:58:33 +02:00
Gunnar Skjold
859220d33f Added missing getter 2020-08-28 17:55:07 +02:00
Gunnar Skjold
5d47105951 Added support for TMP236 analog temp sensor 2020-08-28 17:46:34 +02:00
Gunnar Skjold
e71f937856 Some changes during testing 2020-08-06 13:11:31 +02:00
Gunnar Skjold
d730aac33b Send temperature updates to MQTT 2020-08-05 20:33:49 +02:00
Gunnar Skjold
e121ec75d8 Support multiple temperature sensors 2020-08-05 19:55:16 +02:00
Gunnar Skjold
6479fd6a63 Splitted system config into Web, NTP, GPIO and Debugging 2020-08-05 13:45:10 +02:00
Gunnar Skjold
603f2925ce Removing mbedtls code in HanReader lib 2020-07-31 11:25:45 +02:00
Gunnar Skjold
1284f3f848 Using internal mbedtls lib for encrypted meters. Next up, find out how to modify config.h when using external lib. 2020-07-31 11:19:23 +02:00
Gunnar Skjold
620e355a27 Support encrypted meters and added vcc offset 2020-07-31 10:15:11 +02:00
Gunnar Skjold
47ddf57547 Merge branch 'master' into dev-v1.4.0 2020-07-26 11:24:43 +02:00
Gunnar Skjold
fcbfe4d96f Changes to make substituted I2 correct when exporting power 2020-07-25 09:17:39 +02:00
Gunnar Skjold
d789d6ff3b Reduced number of build targets 2020-07-25 09:15:05 +02:00
Gunnar Skjold
b4f18de030 Made base64 work for both platforms 2020-07-25 08:48:51 +02:00
Gunnar Skjold
00d5d215cd Switched to internal base64 2020-07-25 08:45:31 +02:00
Gunnar Skjold
8de5a58a6b Changed dependency for Base64 2020-07-06 19:17:45 +02:00
Gunnar Skjold
012794e682 Make web content minifier optional 2020-07-06 19:14:37 +02:00
Gunnar Skjold
38eb2d8c19 Merge pull request #79 from gskjold/dev-v1.3.0
Dev v1.3.0
2020-06-06 20:30:47 +02:00
Gunnar Skjold
d95137adbc Support for Kamstrup IT meters 2020-05-28 18:44:38 +02:00
Gunnar Skjold
cde3f80fca Ensure that GPIO is correctly set after setup 2020-05-28 14:19:03 +02:00
Gunnar Skjold
cdc012743f Changes for building on GitHub actions for all branches 2020-05-27 08:38:59 +02:00
83 changed files with 5819 additions and 3016 deletions

View File

@@ -9,7 +9,7 @@ on:
- web/**
- platformio.ini
branches:
- master
- '*'
tags:
- '*'
- '!v*.*.*'
@@ -41,7 +41,7 @@ jobs:
python -m pip install --upgrade pip
pip install -U platformio css_html_js_minify
- name: Configure build targets
run: echo "[platformio]\ndefault_envs = hw1esp12e, esp12e, esp32" > platformio-user.ini
run: echo "[platformio]\ndefault_envs = esp8266, esp32" > platformio-user.ini
- name: PlatformIO lib install
run: pio lib install
- name: PlatformIO run

View File

@@ -22,7 +22,7 @@ jobs:
- name: Get release version for code
env:
GITHUB_REF: ${{ github.ref }}
run: echo ::set-env name=GITHUB_TAG::$(echo ${GITHUB_REF##*/})
run: echo "GITHUB_TAG=$(echo ${GITHUB_REF##*/})" >> $GITHUB_ENV
- name: Cache Python dependencies
uses: actions/cache@v1
with:
@@ -55,32 +55,14 @@ jobs:
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Upload hw1esp12e binary to release
- 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/hw1esp12e/firmware.bin
asset_name: ams2mqtt-hw1esp12e-${{ steps.release_tag.outputs.tag }}.bin
asset_content_type: application/octet-stream
- name: Upload esp12e 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/esp12e/firmware.bin
asset_name: ams2mqtt-esp12e-${{ steps.release_tag.outputs.tag }}.bin
asset_content_type: application/octet-stream
- name: Upload d1mini 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/d1mini/firmware.bin
asset_name: ams2mqtt-d1mini-${{ steps.release_tag.outputs.tag }}.bin
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 esp32 binary to release
uses: actions/upload-release-asset@v1
@@ -91,39 +73,12 @@ jobs:
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 lolind32 binary to release
- name: Upload esp32 partitions 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/lolind32/firmware.bin
asset_name: ams2mqtt-lolind32-${{ steps.release_tag.outputs.tag }}.bin
asset_content_type: application/octet-stream
- name: Upload featheresp32 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/featheresp32/firmware.bin
asset_name: ams2mqtt-featheresp32-${{ steps.release_tag.outputs.tag }}.bin
asset_content_type: application/octet-stream
- name: Upload azdevkit4 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/az-delivery-devkit-v4/firmware.bin
asset_name: ams2mqtt-azdevkit4-${{ steps.release_tag.outputs.tag }}.bin
asset_content_type: application/octet-stream
- name: Upload doitdevkit1 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/esp32doit-devkit-v1/firmware.bin
asset_name: ams2mqtt-doitdevkit1-${{ steps.release_tag.outputs.tag }}.bin
asset_path: .pio/build/esp32/partitions.bin
asset_name: ams2mqtt-esp32-partitions-${{ steps.release_tag.outputs.tag }}.bin
asset_content_type: application/octet-stream

3
.gitignore vendored
View File

@@ -10,3 +10,6 @@ platformio-user.ini
/src/version.h
/src/web/root
/src/AmsToMqttBridge.ino.cpp
/test
/web/test.html
/sdkconfig

View File

@@ -3,32 +3,12 @@ Orignally designed and coded by [@roarfred](https://github.com/roarfred), see th
This repository contains the code and schematics necessary to build a device to receive and convert data from AMS electrical meters installed in Norway. The code can be used on both ESP8266 and ESP32, both as custom build devices or built from readily available development modules. It reads data from the HAN port of the meter and sends this to a configured MQTT bus.
There is also a web interface available on runtime, showing meter data in real time.
There is a web interface available on runtime, showing meter data in real time.
<img src="webui.jpg" width="480">
## Hardware options
Look in [hardware section](/hardware) for more details about supported hardware
## Release binaries
In the [Release section](https://github.com/gskjold/AmsToMqttBridge/releases) of this repository, you will find precompiled binaries for some common boards.
- _hw1esp12e_ :: First version custom hardware with ESP 12E of 12F chip
- _esp12e_ :: General ESP8266 board with 12E or 12F chip
- _d1mini_ :: Wemos D1 mini
- _esp32_ :: General ESP32 board
- _lolind32_ :: Wemos D32
- _featheresp32_ :: Adafruit ESP32 feather
### Flashing binaries with [esptool.py](https://github.com/espressif/esptool)
Linux:
```esptool.py --port /dev/ttyUSB0 write_flash 0x0 binary-file.bin```
Windows:
```esptool.py --port COM1 write_flash 0x0 binary-file.bin```
<img src="webui.jpg">
## Setting up your device
Go to the [WiKi](https://github.com/gskjold/AmsToMqttBridge/wiki) for information on how to get your own device!
## Building this project with PlatformIO
To build this project, you need [PlatformIO](https://platformio.org/) installed.
@@ -39,4 +19,4 @@ It is recommended to use Visual Studio Code with the PlatformIO plugin for devel
[PlatformIO vscode plugin](https://platformio.org/install/ide?install=vscode)
Copy the ```platformio-user.ini-example``` to ```platformio-user.ini``` and customize to your preference. The code will adapt to the platform and board set in your profile. If you are using the original board design by [@roarfred](https://github.com/roarfred) use build flag -D HW_ROARFRED=1
Copy the ```platformio-user.ini-example``` to ```platformio-user.ini``` and customize to your preference. The code will adapt to the platform and board set in your profile.

4
frames/Aidon-TN-3p.raw Normal file
View File

@@ -0,0 +1,4 @@
T FF FF DA SA SA C HC HC LD LS LQ AT AI AI AI AI AD
7E A0 2A 41 08 83 13 04 13 E6 E7 00 0F 40 00 00 00 00 01 01 02 03 09 06 01 00 01 07 00 FF 06 00 00 08 64 02 02 0F 00 16 1B E1
7E A1 1E 41 08 83 13 EE EE E6 E7 00 0F 40 00 00 00 00 01 0D 02 02 09 06 01 01 00 02 81 FF 0A 0B 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07 FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00 FF 06 00 00 08 6C 02 02 0F 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00 00 02 09 02 02 0F 00 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 00 41 02 02 0F FF 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 13 02 02 0F FF 16 21 02 03 09 06 01 00 47 07 00 FF 10 00 0E 02 02 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 08 F2 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12 08 D1 02 02 0F FF 16 23 02 03 09 06 01 00 48 07 00 FF 12 08 E8 02 02 0F FF 16 23 8B
7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07 FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00 FF 06 00 00 03 9A 02 02 0F 00 16 1B 02 03 09 06 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF 06 00 00 02 0E 02 02 0F 00 16 1D 02 03 09 06 01 00 1F 07 00 FF 10 00 11 02 02 0F FF 16 21 02 03 09 06 01 00 33 07 00 FF 10 00 10 02 02 0F FF 16 21 02 03 09 06 01 00 47 07 00 FF 10 00 0E 02 02 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 08 F4 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00 FF 12 08 CD 02 02 0F FF 16 23 02 03 09 06 01 00 48 07 00 FF 12 08 DC 02 02 0F FF 16 23 02 02 09 06 00 00 01 00 00 FF 09 0C 07 E5 03 18 03 08 00 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06 00 47 F0 34 02 02 0F 01 16 1E 02 03 09 06 01 00 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02 03 09 06 01 00 03 08 00 FF 06 00 00 21 9E 02 02 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00 08 E0 21 02 02 0F 01 16 20 57

26
frames/Kaifa-TN-3p.raw Normal file
View File

@@ -0,0 +1,26 @@
T FF FF DA SA SA C HC HC LD LS LQ AT AI AI AI AI AD
7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00 00 09 0C 07 E5 03 17 02 13 1A 3A FF 80 00 00
02 01 // Frame type and size
06 00 00 0B F3 // Active power
5B 05 7E // CRC and end tag
T FF FF DA SA SA C HC HC LD LS LQ AT AI AI AI AI AD
7E A0 78 01 02 01 10 C4 98 E6 E7 00 0F 40 00 00 00 09 0C 07 E5 03 17 02 13 1B 00 FF 80 00 00
02 0D // Frame type and size
09 07 4B 46 4D 5F 30 30 31 // List version
09 10 XX XX XX XX XX XX XX XX XX XX 35 33 34 34 39 33 // Meter ID
09 07 4D 41 33 30 34 48 34 // Meter type
06 00 00 0C 21 // Active import
06 00 00 00 00 // Active export
06 00 00 00 00 // Reactive import
06 00 00 01 9F // Reactive export
06 00 00 0B F3 // I1
06 00 00 05 0B // I2
06 00 00 25 11 // I3
06 00 00 09 44 // U1
06 00 00 09 49 // U2
06 00 00 09 39 // U3
C9 95 7E // CRC and end tag
7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00 00 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 XX XX XX XX XX XX XX XX XX XX 35 33 34 34 39 33 09 07 4D 41 33 30 34 48 34 06 00 00 09 99 06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05 06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E

32
frames/decoding.txt Normal file
View File

@@ -0,0 +1,32 @@
T = Tag
FF = Frame format (4 bit type, 1 bit segmentation, 11 bit frame length)
DA = Destination Address (1-4 bytes, LSB=1 terminates)
SA = Source address (1-4 bytes, LSB=1 terminates)
C = Control (1 byte)
HC = HCS (2 bytes)
LD = LLC Destination
LS = LLC Remote
LQ = LLC Quality
AT = Tag (0x0F = unencrypted, 0xDB = encrypted) Not really documented that well...
AI = Invoke ID and priority (4 bytes)
AD = Date and time
AS = System title
CT = Cipher frame tag ? Undocumented
CL = Lenght of Cipher frame. Length of payload will be this number - 5 (control and counter in start) - 12 (GCM tag appended after payload)
CC = Security control
CO = Invocation counter
Security control bits
01234567
00110000 (0x30)
0 = read acces
1 = write access
2 = Authenticated req
3 = Encrypted req
4 = Digitally signed req
5 = Authenticated res
6 = Encrypted res
7 = Digitally signed res

View File

@@ -1,39 +1 @@
# Hardware options
There are currently two possible hardware options for this project, both in need of external power supply. A self powered board is under development by a member of the community and is currently being tested.
## Hardware v1 by [@roarfred](https://github.com/roarfred)
Composed from a ESP12E (or F) chip, this ESP8266 based board is designed specifically for this project with an on board M-bus chip.
Building this project will require some skills in ordering and assembling electronic circuits as well as programming. No detailed instructions are available.
![The HAN Reader Hardware](v1/img/HanReaderInEnclosure.PNG)
*The completed board mounted in a [3D printed enclosure](/Enclosure)*
## Assembly of readily available modules
You can also use a ESP based development board and combine this with a M-Bus module. Here are a few boards that have been tested, each one has a dedicated firmware file in the releases section.
### ESP8266 based boards
[Wemos D1 mini](https://docs.wemos.cc/en/latest/d1/d1_mini.html)
- M-Bus connected to GPIO5 (D1)
- Jump GPIO4 (D2) to GND to force AP mode during boot
- Dallas temp sensor connected to GPIO14 (D5)
### ESP32 based boards
[Wemos D32](https://docs.wemos.cc/en/latest/d32/d32.html)
- M-Bus connected to GPIO16
- Jump GPIO4 to GND to force AP mode during boot
- Dallas temp sensor connected to GPIO14
[Adafruit HUZZAH32](https://www.adafruit.com/product/3405)
- M-Bus connected to GPIO16
Combine one of above board with an M-Bus module. Connect 3.3v and GND together between the boards and connect the TX pin from the M-Bus board to the dedicated M-Bus pin on the ESP board.
[TSS721 M-BUS module board](https://www.aliexpress.com/item/TSS721/32751482255.html)
![FeatherMbus](img/feather_3010-00_mbus_slave.jpg)
[See Hardware page in Wiki](https://github.com/gskjold/AmsToMqttBridge/wiki)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -1,4 +1,4 @@
name=HANreader
name=HanReader
version=1.0.1
author=roarfred
maintainer=roarfred <not@important.com>

View File

@@ -1,36 +1,13 @@
#include "Crc16.h"
Crc16Class::Crc16Class()
{
unsigned short value;
unsigned short temp;
for (unsigned short i = 0; i < 256; ++i)
{
value = 0;
temp = i;
for (byte j = 0; j < 8; ++j)
{
if (((value ^ temp) & 0x0001) != 0)
{
value = (ushort)((value >> 1) ^ polynomial);
}
else
{
value >>= 1;
}
temp >>= 1;
}
table[i] = value;
}
}
Crc16Class::Crc16Class() { }
unsigned short Crc16Class::ComputeChecksum(byte *data, int start, int length)
{
unsigned short Crc16Class::ComputeChecksum(byte *data, int start, int length) {
ushort fcs = 0xffff;
for (int i = start; i < (start + length); i++)
{
byte index = (fcs ^ data[i]) & 0xff;
fcs = (ushort)((fcs >> 8) ^ table[index]);
fcs = (ushort)((fcs >> 8) ^ crc16_ccitt_table_reverse[index]);
}
fcs ^= 0xffff;
return fcs;

View File

@@ -1,6 +1,44 @@
#ifndef _CRC16_h
#define _CRC16_h
#include "Arduino.h"
const int crc16_ccitt_table_reverse [256] PROGMEM = {
0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF,
0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7,
0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E,
0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876,
0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD,
0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5,
0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C,
0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974,
0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3,
0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A,
0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72,
0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9,
0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1,
0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738,
0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70,
0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7,
0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036,
0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E,
0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5,
0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD,
0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134,
0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C,
0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3,
0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB,
0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A,
0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1,
0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9,
0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330,
0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78
};
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
@@ -12,10 +50,6 @@ class Crc16Class
public:
Crc16Class();
unsigned short ComputeChecksum(byte *data, int start, int length);
protected:
private:
const unsigned short polynomial = 0x8408;
unsigned short table[256];
};
#endif

View File

@@ -14,7 +14,7 @@ void DlmsReader::Clear()
this->frameFormatType = 0;
}
bool DlmsReader::Read(byte data)
bool DlmsReader::Read(byte data, Print* debugger)
{
if (position == 0 && data != 0x7E)
{
@@ -24,12 +24,21 @@ bool DlmsReader::Read(byte data)
else
{
// We have completed reading of one package, so clear and be ready for the next
if (dataLength > 0 && position >= dataLength + 2)
if (dataLength > 0 && position >= dataLength + 2) {
if(debugger != NULL) {
debugger->printf("Preparing for next frame\n");
}
Clear();
}
// Check if we're about to run into a buffer overflow
if (position >= DLMS_READER_BUFFER_SIZE)
if (position >= DLMS_READER_BUFFER_SIZE) {
if(debugger != NULL) {
debugger->printf("Buffer overflow\n");
debugPrint(buffer, 0, position, debugger);
}
Clear();
}
// Check if this is a second start flag, which indicates the previous one was a stop from the last package
if (position == 1 && data == 0x7E)
@@ -50,8 +59,13 @@ bool DlmsReader::Read(byte data)
{
// Capture the Frame Format Type
frameFormatType = (byte)(data & 0xF0);
if (!IsValidFrameFormat(frameFormatType))
if (!IsValidFrameFormat(frameFormatType)) {
if(debugger != NULL) {
debugger->printf("Incorrect frame format %02X\n", frameFormatType);
debugPrint(buffer, 0, position, debugger);
}
Clear();
}
return false;
}
else if (position == 3)
@@ -64,32 +78,52 @@ bool DlmsReader::Read(byte data)
{
// Capture the destination address
destinationAddressLength = GetAddress(3, destinationAddress, 0, DLMS_READER_MAX_ADDRESS_SIZE);
if (destinationAddressLength > 3)
if (destinationAddressLength > 3) {
if(debugger != NULL) {
debugger->printf("Destination address length incorrect\n");
debugPrint(buffer, 0, position, debugger);
}
Clear();
}
return false;
}
else if (sourceAddressLength == 0)
{
// Capture the source address
sourceAddressLength = GetAddress(3 + destinationAddressLength, sourceAddress, 0, DLMS_READER_MAX_ADDRESS_SIZE);
if (sourceAddressLength > 3)
if (sourceAddressLength > 3) {
if(debugger != NULL) {
debugger->printf("Source address length incorrect\n");
debugPrint(buffer, 0, position, debugger);
}
Clear();
}
return false;
}
else if (position == 4 + destinationAddressLength + sourceAddressLength + 2)
{
// Verify the header checksum
ushort headerChecksum = GetChecksum(position - 3);
if (headerChecksum != Crc16.ComputeChecksum(buffer, 1, position - 3))
if (headerChecksum != Crc16.ComputeChecksum(buffer, 1, position - 3)) {
if(debugger != NULL) {
debugger->printf("Header checksum is incorrect %02X\n", headerChecksum);
debugPrint(buffer, 0, position, debugger);
}
Clear();
}
return false;
}
else if (position == dataLength + 1)
{
// Verify the data package checksum
ushort checksum = this->GetChecksum(position - 3);
if (checksum != Crc16.ComputeChecksum(buffer, 1, position - 3))
if (checksum != Crc16.ComputeChecksum(buffer, 1, position - 3)) {
if(debugger != NULL) {
debugger->printf("Frame checksum is incorrect %02X\n", checksum);
debugPrint(buffer, 0, position, debugger);
}
Clear();
}
return false;
}
else if (position == dataLength + 2)
@@ -99,6 +133,10 @@ bool DlmsReader::Read(byte data)
return true;
else
{
if(debugger != NULL) {
debugger->printf("Received incorrect end marker %02X\n", data);
debugPrint(buffer, 0, position, debugger);
}
Clear();
return false;
}
@@ -129,6 +167,22 @@ int DlmsReader::GetRawData(byte *dataBuffer, int start, int length)
return 0;
}
int DlmsReader::getBytesRead() {
return dataLength - (destinationAddressLength + sourceAddressLength + 7);
}
byte* DlmsReader::getBuffer() {
return buffer + (3 + destinationAddressLength + sourceAddressLength + 2 + 1);
}
byte* DlmsReader::getFullBuffer() {
return buffer;
}
int DlmsReader::getFullBufferLength() {
return dataLength;
}
int DlmsReader::GetAddress(int addressPosition, byte* addressBuffer, int start, int length)
{
int addressBufferPos = start;
@@ -152,3 +206,19 @@ ushort DlmsReader::GetChecksum(int checksumPosition)
return (ushort)(buffer[checksumPosition + 2] << 8 |
buffer[checksumPosition + 1]);
}
void DlmsReader::debugPrint(byte *buffer, int start, int length, Print* debugger) {
for (int i = start; i < start + length; i++) {
if (buffer[i] < 0x10)
debugger->print("0");
debugger->print(buffer[i], HEX);
debugger->print(" ");
if ((i - start + 1) % 16 == 0)
debugger->println("");
else if ((i - start + 1) % 4 == 0)
debugger->print(" ");
yield(); // Let other get some resources too
}
debugger->println("");
}

View File

@@ -16,8 +16,12 @@ class DlmsReader
{
public:
DlmsReader();
bool Read(byte data);
bool Read(byte data, Print* Debug);
int GetRawData(byte *buffer, int start, int length);
int getBytesRead();
byte* getBuffer();
byte* getFullBuffer();
int getFullBufferLength();
protected:
Crc16Class Crc16;
@@ -37,6 +41,7 @@ class DlmsReader
unsigned short GetChecksum(int checksumPosition);
bool IsValidFrameFormat(byte frameFormatType);
void WriteBuffer();
void debugPrint(byte *buffer, int start, int length, Print* debugger);
};
#endif

View File

@@ -1,5 +1,9 @@
#include "HanReader.h"
#if defined(ESP32)
#include "mbedtls/gcm.h"
#endif
HanReader::HanReader() {
// Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
@@ -16,18 +20,30 @@ void HanReader::setup(Stream *hanPort, RemoteDebug *debug)
if (debug) debug->println("MBUS serial setup complete");
}
void HanReader::setup(Stream *hanPort)
{
void HanReader::setup(Stream *hanPort){
setup(hanPort, NULL);
}
void HanReader::setEncryptionKey(uint8_t* encryption_key) {
this->encryption_key = encryption_key;
}
void HanReader::setAuthenticationKey(uint8_t* authentication_key) {
this->authentication_key = authentication_key;
}
bool HanReader::read(byte data) {
if (reader.Read(data)) {
bytesRead = reader.GetRawData(buffer, 0, 512);
if (reader.Read(data, debugger->isActive(RemoteDebug::DEBUG) ? debugger : NULL)) {
bytesRead = reader.getBytesRead();
buffer = reader.getBuffer();
if (debugger->isActive(RemoteDebug::INFO)) {
printI("Got valid DLMS data (%d bytes)", bytesRead);
if (debugger->isActive(RemoteDebug::DEBUG)) {
debugPrint(buffer, 0, bytesRead);
byte* full = reader.getFullBuffer();
int size = reader.getFullBufferLength();
printI("Full DLMS frame (%d bytes)", size);
debugPrint(full, 0, size);
}
}
@@ -40,16 +56,32 @@ bool HanReader::read(byte data) {
return false;
}
else if (
buffer[0] != 0xE6 ||
buffer[1] != 0xE7 ||
buffer[2] != 0x00 ||
buffer[3] != 0x0F
buffer[0] != 0xE6
|| buffer[1] != 0xE7
|| buffer[2] != 0x00
)
{
printW("Invalid HAN data: Start should be E6 E7 00 0F");
printW("Invalid HAN data: Start should be E6 E7 00");
return false;
}
// Have not found any documentation supporting this, but 0x0F for all norwegian meters.
// Danish meters with encryption has 0xDB, so lets assume this has something to do with that.
switch(buffer[3]) {
case 0x0F:
dataHeader = 8;
break;
case 0xDB:
printI("Decrypting frame");
if(!decryptFrame()) return false;
if (debugger->isActive(RemoteDebug::DEBUG)) {
printD("Data after decryption:");
debugPrint(buffer, 0, bytesRead);
}
dataHeader = 26;
break;
}
listSize = getInt(0, buffer, 0, bytesRead);
printI("HAN data is valid, listSize: %d", listSize);
return true;
@@ -58,6 +90,81 @@ bool HanReader::read(byte data) {
return false;
}
const size_t headersize = 3;
const size_t footersize = 0;
bool HanReader::decryptFrame() {
uint8_t system_title[8];
memcpy(system_title, buffer + headersize + 2, 8);
if (debugger->isActive(RemoteDebug::DEBUG)) {
printD("System title:");
debugPrint(system_title, 0, 8);
}
uint8_t initialization_vector[12];
memcpy(initialization_vector, system_title, 8);
memcpy(initialization_vector + 8, buffer + headersize + 14, 4);
if (debugger->isActive(RemoteDebug::DEBUG)) {
printD("Initialization vector:");
debugPrint(initialization_vector, 0, 12);
}
uint8_t additional_authenticated_data[17];
memcpy(additional_authenticated_data, buffer + headersize + 13, 1);
memcpy(additional_authenticated_data + 1, authentication_key, 16);
if (debugger->isActive(RemoteDebug::DEBUG)) {
printD("Additional authenticated data:");
debugPrint(additional_authenticated_data, 0, 17);
}
uint8_t authentication_tag[12];
memcpy(authentication_tag, buffer + headersize + bytesRead - headersize - footersize - 12, 12);
if (debugger->isActive(RemoteDebug::DEBUG)) {
printD("Authentication tag:");
debugPrint(authentication_tag, 0, 12);
}
if (debugger->isActive(RemoteDebug::DEBUG)) {
printD("Encryption key:");
debugPrint(encryption_key, 0, 16);
}
#if defined(ESP8266)
br_gcm_context gcmCtx;
br_aes_ct_ctr_keys bc;
br_aes_ct_ctr_init(&bc, encryption_key, 16);
br_gcm_init(&gcmCtx, &bc.vtable, br_ghash_ctmul32);
br_gcm_reset(&gcmCtx, initialization_vector, sizeof(initialization_vector));
br_gcm_aad_inject(&gcmCtx, additional_authenticated_data, sizeof(additional_authenticated_data));
br_gcm_flip(&gcmCtx);
br_gcm_run(&gcmCtx, 0, buffer + headersize + 18, bytesRead - headersize - footersize - 18 - 12);
if(br_gcm_check_tag_trunc(&gcmCtx, authentication_tag, 12) != 1) {
printE("authdecrypt failed");
return false;
}
#elif defined(ESP32)
uint8_t cipher_text[bytesRead - headersize - footersize - 18 - 12];
memcpy(cipher_text, buffer + headersize + 18, bytesRead - headersize - footersize - 12 - 18);
mbedtls_gcm_context m_ctx;
mbedtls_gcm_init(&m_ctx);
int success = mbedtls_gcm_setkey(&m_ctx, MBEDTLS_CIPHER_ID_AES, encryption_key, 128);
if (0 != success ) {
printE("Setkey failed: " + String(success));
return false;
}
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), initialization_vector, sizeof(initialization_vector),
additional_authenticated_data, sizeof(additional_authenticated_data), authentication_tag, sizeof(authentication_tag),
cipher_text, buffer + headersize + 18);
if (0 != success) {
printE("authdecrypt failed: " + String(success));
return false;
}
mbedtls_gcm_free(&m_ctx);
#endif
return true;
}
void HanReader::debugPrint(byte *buffer, int start, int length) {
for (int i = start; i < start + length; i++) {
if (buffer[i] < 0x10)
@@ -87,26 +194,26 @@ int HanReader::getListSize() {
return listSize;
}
time_t HanReader::getPackageTime() {
time_t HanReader::getPackageTime(bool respectTimezone, bool respectDsc) {
int packageTimePosition = dataHeader
+ (compensateFor09HeaderBug ? 1 : 0);
return getTime(buffer, packageTimePosition, bytesRead);
return getTime(buffer, packageTimePosition, bytesRead, respectTimezone, respectDsc);
}
time_t HanReader::getTime(int objectId) {
return getTime(objectId, buffer, 0, bytesRead);
time_t HanReader::getTime(uint8_t objectId, bool respectTimezone, bool respectDsc) {
return getTime(objectId, respectTimezone, respectDsc, buffer, 0, bytesRead);
}
int32_t HanReader::getInt(int objectId) {
int32_t HanReader::getInt(uint8_t objectId) {
return getInt(objectId, buffer, 0, bytesRead);
}
uint32_t HanReader::getUint(int objectId) {
uint32_t HanReader::getUint(uint8_t objectId) {
return getUint32(objectId, buffer, 0, bytesRead);
}
String HanReader::getString(int objectId) {
String HanReader::getString(uint8_t objectId) {
return getString(objectId, buffer, 0, bytesRead);
}
@@ -117,7 +224,7 @@ int HanReader::getBuffer(byte* buf) {
return bytesRead;
}
int HanReader::findValuePosition(int dataPosition, byte *buffer, int start, int length) {
int HanReader::findValuePosition(uint8_t dataPosition, byte *buffer, int start, int length) {
// The first byte after the header gives the length
// of the extended header information (variable)
int headerSize = dataHeader + (compensateFor09HeaderBug ? 1 : 0);
@@ -158,14 +265,14 @@ int HanReader::findValuePosition(int dataPosition, byte *buffer, int start, int
}
time_t HanReader::getTime(int dataPosition, byte *buffer, int start, int length) {
time_t HanReader::getTime(uint8_t dataPosition, bool respectTimezone, bool respectDsc, byte *buffer, int start, int length) {
// TODO: check if the time is represented always as a 12 byte string (0x09 0x0C)
int timeStart = findValuePosition(dataPosition, buffer, start, length);
timeStart += 1;
return getTime(buffer, start + timeStart, length - timeStart);
return getTime(buffer, start + timeStart, length - timeStart, respectTimezone, respectDsc);
}
time_t HanReader::getTime(byte *buffer, int start, int length) {
time_t HanReader::getTime(byte *buffer, int start, int length, bool respectTimezone, bool respectDsc) {
int pos = start;
int dataLength = buffer[pos++];
@@ -175,9 +282,13 @@ time_t HanReader::getTime(byte *buffer, int start, int length) {
int month = buffer[pos + 2];
int day = buffer[pos + 3];
// 4: Day of week
int hour = buffer[pos + 5];
int minute = buffer[pos + 6];
int second = buffer[pos + 7];
// 8: Hundredths
int16_t tzMinutes = buffer[pos + 9] << 8 | buffer[pos + 10];
bool dsc = (buffer[pos + 11] & 0x80) == 0x80;
tmElements_t tm;
tm.Year = year - 1970;
@@ -186,7 +297,18 @@ time_t HanReader::getTime(byte *buffer, int start, int length) {
tm.Hour = hour;
tm.Minute = minute;
tm.Second = second;
return localZone->toUTC(makeTime(tm));
time_t time = makeTime(tm);
if(respectTimezone && tzMinutes != 0x8000) {
time -= tzMinutes * 60;
if(respectDsc && dsc)
time -= 3600;
} else {
if(respectDsc && dsc)
time += 3600;
time = localZone->toUTC(time);
}
return time;
} else if(dataLength == 0) {
return (time_t)0L;
} else {
@@ -196,7 +318,7 @@ time_t HanReader::getTime(byte *buffer, int start, int length) {
}
}
int HanReader::getInt(int dataPosition, byte *buffer, int start, int length) {
int HanReader::getInt(uint8_t dataPosition, byte *buffer, int start, int length) {
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0) {
@@ -218,7 +340,7 @@ int HanReader::getInt(int dataPosition, byte *buffer, int start, int length) {
return 0;
}
int8_t HanReader::getInt8(int dataPosition, byte *buffer, int start, int length) {
int8_t HanReader::getInt8(uint8_t dataPosition, byte *buffer, int start, int length) {
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0 && buffer[valuePosition++] == 0x0F) {
return buffer[valuePosition];
@@ -226,7 +348,7 @@ int8_t HanReader::getInt8(int dataPosition, byte *buffer, int start, int length)
return 0;
}
int16_t HanReader::getInt16(int dataPosition, byte *buffer, int start, int length) {
int16_t HanReader::getInt16(uint8_t dataPosition, byte *buffer, int start, int length) {
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0 && buffer[valuePosition++] == 0x10) {
return buffer[valuePosition] << 8 | buffer[valuePosition+1];
@@ -234,7 +356,7 @@ int16_t HanReader::getInt16(int dataPosition, byte *buffer, int start, int lengt
return 0;
}
uint8_t HanReader::getUint8(int dataPosition, byte *buffer, int start, int length) {
uint8_t HanReader::getUint8(uint8_t dataPosition, byte *buffer, int start, int length) {
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0) {
switch(buffer[valuePosition++]) {
@@ -247,7 +369,7 @@ uint8_t HanReader::getUint8(int dataPosition, byte *buffer, int start, int lengt
return 0;
}
uint16_t HanReader::getUint16(int dataPosition, byte *buffer, int start, int length) {
uint16_t HanReader::getUint16(uint8_t dataPosition, byte *buffer, int start, int length) {
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0 && buffer[valuePosition++] == 0x12) {
return buffer[valuePosition] << 8 | buffer[valuePosition+1];
@@ -255,7 +377,7 @@ uint16_t HanReader::getUint16(int dataPosition, byte *buffer, int start, int len
return 0;
}
uint32_t HanReader::getUint32(int dataPosition, byte *buffer, int start, int length) {
uint32_t HanReader::getUint32(uint8_t dataPosition, byte *buffer, int start, int length) {
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0) {
if(buffer[valuePosition++] != 0x06)
@@ -269,7 +391,7 @@ uint32_t HanReader::getUint32(int dataPosition, byte *buffer, int start, int len
return 0;
}
String HanReader::getString(int dataPosition, byte *buffer, int start, int length) {
String HanReader::getString(uint8_t dataPosition, byte *buffer, int start, int length) {
int valuePosition = findValuePosition(dataPosition, buffer, start, length);
if (valuePosition > 0) {
String value = String("");

View File

@@ -15,7 +15,7 @@
class HanReader
{
public:
const uint dataHeader = 8;
uint dataHeader = 8;
bool compensateFor09HeaderBug = false;
HanReader();
@@ -24,35 +24,42 @@ public:
bool read();
bool read(byte data);
int getListSize();
time_t getPackageTime();
int32_t getInt(int objectId); // Use this for uint8, int8, uint16, int16
uint32_t getUint(int objectId); // Only for uint32
String getString(int objectId);
time_t getTime(int objectId);
time_t getPackageTime(bool respectTimezone, bool respectDsc);
int32_t getInt(uint8_t objectId); // Use this for uint8, int8, uint16, int16
uint32_t getUint(uint8_t objectId); // Only for uint32
String getString(uint8_t objectId);
time_t getTime(uint8_t objectId, bool respectTimezone, bool respectDsc);
int getBuffer(byte* buf);
void setEncryptionKey(uint8_t* encryption_key);
void setAuthenticationKey(uint8_t* authentication_key);
private:
RemoteDebug* debugger;
Stream *han;
byte buffer[512];
byte* buffer;
int bytesRead;
DlmsReader reader;
int listSize;
Timezone *localZone;
uint8_t* encryption_key;
uint8_t* authentication_key;
int findValuePosition(int dataPosition, byte *buffer, int start, int length);
int findValuePosition(uint8_t dataPosition, byte *buffer, int start, int length);
time_t getTime(int dataPosition, byte *buffer, int start, int length);
time_t getTime(byte *buffer, int start, int length);
int getInt(int dataPosition, byte *buffer, int start, int length);
int8_t getInt8(int dataPosition, byte *buffer, int start, int length);
uint8_t getUint8(int dataPosition, byte *buffer, int start, int length);
int16_t getInt16(int dataPosition, byte *buffer, int start, int length);
uint16_t getUint16(int dataPosition, byte *buffer, int start, int length);
uint32_t getUint32(int dataPosition, byte *buffer, int start, int length);
String getString(int dataPosition, byte *buffer, int start, int length);
time_t getTime(uint8_t dataPosition, bool respectTimezone, bool respectDsc, byte *buffer, int start, int length);
time_t getTime(byte *buffer, int start, int length, bool respectTimezone, bool respectDsc);
int getInt(uint8_t dataPosition, byte *buffer, int start, int length);
int8_t getInt8(uint8_t dataPosition, byte *buffer, int start, int length);
uint8_t getUint8(uint8_t dataPosition, byte *buffer, int start, int length);
int16_t getInt16(uint8_t dataPosition, byte *buffer, int start, int length);
uint16_t getUint16(uint8_t dataPosition, byte *buffer, int start, int length);
uint32_t getUint32(uint8_t dataPosition, byte *buffer, int start, int length);
String getString(uint8_t dataPosition, byte *buffer, int start, int length);
time_t toUnixTime(int year, int month, int day, int hour, int minute, int second);
time_t toUnixTime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second);
bool decryptFrame();
void debugPrint(byte *buffer, int start, int length);

View File

@@ -1,7 +1,7 @@
#ifndef _KAIFA_h
#define _KAIFA_h
enum class Kaifa : byte {
enum class Kaifa {
List1 = 0x01,
List1PhaseShort = 0x09,
List3PhaseShort = 0x0D,

View File

@@ -5,10 +5,12 @@
enum class Kamstrup
{
List1PhaseShort = 0x11,
List1PhaseLong = 0x1B,
List3PhaseShort = 0x19,
List3PhaseLong = 0x23,
List1PhaseShort = 0x11,
List1PhaseLong = 0x1B
List3PhaseITShort = 0x17,
List3PhaseITLong = 0x21
};
enum class Kamstrup_List3Phase
@@ -83,5 +85,43 @@ enum class Kamstrup_List1Phase
CumulativeReactiveExportEnergy
};
enum class Kamstrup_List3PhaseIT
{
ListSize,
ListVersionIdentifier,
MeterID_OBIS,
MeterID,
MeterType_OBIS,
MeterType,
ActiveImportPower_OBIS,
ActiveImportPower,
ActiveExportPower_OBIS,
ActiveExportPower,
ReactiveImportPower_OBIS,
ReactiveImportPower,
ReactiveExportPower_OBIS,
ReactiveExportPower,
CurrentL1_OBIS,
CurrentL1,
CurrentL3_OBIS,
CurrentL3,
VoltageL1_OBIS,
VoltageL1,
VoltageL2_OBIS,
VoltageL2,
VoltageL3_OBIS,
VoltageL3,
MeterClock_OBIS,
MeterClock,
CumulativeActiveImportEnergy_OBIS,
CumulativeActiveImportEnergy,
CumulativeActiveExportEnergy_OBIS,
CumulativeActiveExportEnergy,
CumulativeReactiveImportEnergy_OBIS,
CumulativeReactiveImportEnergy,
CumulativeReactiveExportEnergy_OBIS,
CumulativeReactiveExportEnergy
};
#endif

View File

@@ -0,0 +1,77 @@
#ifndef _OMNIPOWER_h
#define _OMNIPOWER_h
enum class Omnipower {
DLMS = 0x41
};
enum class Omnipower_DLMS {
ListSize,
ListVersionIdentifier,
CumulativeActiveImportEnergy_OBIS,
CumulativeActiveImportEnergy,
CumulativeActiveExportEnergy_OBIS,
CumulativeActiveExportEnergy,
CumulativeReactiveImportEnergy_OBIS,
CumulativeReactiveImportEnergy,
CumulativeReactiveExportEnergy_OBIS,
CumulativeReactiveExportEnergy,
MeterNumber_OBIS,
MeterNumber,
ActiveImportPower_OBIS,
ActiveImportPower,
ActiveExportPower_OBIS,
ActiveExportPower,
ReactiveImportPower_OBIS,
ReactiveImportPower,
ReactiveExportPower_OBIS,
ReactiveExportPower,
MeterClock_OBIS,
MeterClock,
VoltageL1_OBIS,
VoltageL1,
VoltageL2_OBIS,
VoltageL2,
VoltageL3_OBIS,
VoltageL3,
CurrentL1_OBIS,
CurrentL1,
CurrentL2_OBIS,
CurrentL2,
CurrentL3_OBIS,
CurrentL3,
ActiveImportPowerL1_OBIS,
ActiveImportPowerL1,
ActiveImportPowerL2_OBIS,
ActiveImportPowerL2,
ActiveImportPowerL3_OBIS,
ActiveImportPowerL3,
PowerFactorL1_OBIS,
PowerFactorL1,
PowerFactorL2_OBIS,
PowerFactorL2,
PowerFactorL3_OBIS,
PowerFactorL3,
PowerFactor_OBIS,
PowerFactor,
ActiveExportPowerL1_OBIS,
ActiveExportPowerL1,
ActiveExportPowerL2_OBIS,
ActiveExportPowerL2,
ActiveExportPowerL3_OBIS,
ActiveExportPowerL3,
CumulativeActiveExportEnergyL1_OBIS,
CumulativeActiveExportEnergyL1,
CumulativeActiveExportEnergyL2_OBIS,
CumulativeActiveExportEnergyL2,
CumulativeActiveExportEnergyL3_OBIS,
CumulativeActiveExportEnergyL3,
CumulativeActiveImportEnergyL1_OBIS,
CumulativeActiveImportEnergyL1,
CumulativeActiveImportEnergyL2_OBIS,
CumulativeActiveImportEnergyL2,
CumulativeActiveImportEnergyL3_OBIS,
CumulativeActiveImportEnergyL3
};
#endif

View File

@@ -7,4 +7,4 @@ paragraph=The primary aim of the Timezone library is to convert Universal Coordi
category=Timing
url=https://github.com/JChristensen/Timezone
architectures=*
depends=Time
depends=Time (=1.6.0)

View File

@@ -7,12 +7,11 @@ board = esp12e
framework = ${common.framework}
lib_deps = ${common.lib_deps}
build_flags =
-D HW_ROARFRED=1
-D DEBUG_MODE=1
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
monitor_speed = 2400
monitor_speed = 115200 ; If serial port is shared with HAN, use 2400 and parity E (or N for Norwegian Kamstrup)
monitor_flags =
--parity
E
N

View File

@@ -1,80 +1,24 @@
[platformio]
extra_configs = platformio-user.ini
[common]
lib_deps = file://lib/HanReader, file://lib/Timezone, MQTT@2.5.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.9.0, RemoteDebug@3.0.5, Time@1.6.0
[env:esp8266]
platform = espressif8266@2.6.2
board = esp12e
board_build.ldscript = eagle.flash.4m2m.ld
framework = arduino
lib_deps = HanReader@1.0.1, ArduinoJson@6.14.1, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, Base64@1.0.0, RemoteDebug@3.0.5, Time@1.6
[env:hw1esp12e]
platform = espressif8266@2.3.3
board = esp12e
framework = ${common.framework}
lib_deps = ${common.lib_deps}
build_flags =
-D HW_ROARFRED=1
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
[env:esp12e]
platform = espressif8266@2.3.3
board = esp12e
framework = ${common.framework}
lib_deps = ${common.lib_deps}
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
[env:d1mini]
platform = espressif8266@2.3.3
board = d1_mini
framework = ${common.framework}
lib_deps = ${common.lib_deps}
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
[env:esp32]
platform = espressif32@1.11.2
platform = espressif32@2.1.0
board = esp32dev
framework = ${common.framework}
lib_deps = ${common.lib_deps}
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
[env:lolind32]
platform = espressif32@1.11.2
board = lolin_d32
framework = ${common.framework}
lib_deps = ${common.lib_deps}
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
[env:featheresp32]
platform = espressif32@1.11.2
board = featheresp32
framework = ${common.framework}
lib_deps = ${common.lib_deps}
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
[env:az-delivery-devkit-v4]
platform = espressif32@1.11.2
board = az-delivery-devkit-v4
framework = ${common.framework}
lib_deps = ${common.lib_deps}
extra_scripts =
pre:scripts/addversion.py
scripts/makeweb.py
[env:esp32doit-devkit-v1]
platform = espressif32@1.11.2
board = esp32doit-devkit-v1
framework = ${common.framework}
board_build.partitions = no_ota.csv
framework = arduino
lib_deps = ${common.lib_deps}
extra_scripts =
pre:scripts/addversion.py

View File

@@ -1,7 +1,28 @@
import os
import re
import shutil
from css_html_js_minify import html_minify, js_minify, css_minify
try:
from css_html_js_minify import html_minify, js_minify, css_minify
except:
from SCons.Script import (
ARGUMENTS,
COMMAND_LINE_TARGETS,
DefaultEnvironment,
)
env = DefaultEnvironment()
env.Execute(
env.VerboseAction(
'$PYTHONEXE -m pip install "css_html_js_minify" ',
"Installing Python dependencies",
)
)
try:
from css_html_js_minify import html_minify, js_minify, css_minify
except:
print("WARN: Unable to load minifier")
webroot = "web"
srcroot = "src/web/root"
@@ -26,13 +47,16 @@ for filename in os.listdir(webroot):
with open(srcfile, encoding="utf-8") as f:
content = f.read().replace("${version}", version)
if filename.endswith(".html"):
content = html_minify(content)
elif filename.endswith(".css"):
content = css_minify(content)
elif filename.endswith(".js") and filename != 'gaugemeter.js':
content = js_minify(content)
try:
if filename.endswith(".html"):
content = html_minify(content)
elif filename.endswith(".css"):
content = css_minify(content)
elif (filename.endswith(".js") and filename != 'gaugemeter.js') or filename.endswith(".json"):
content = js_minify(content)
except:
print("WARN: Unable to minify")
with open(dstfile, "w") as dst:
dst.write("const char ")
@@ -42,7 +66,7 @@ for filename in os.listdir(webroot):
dst.write(")==\"==\";\n")
dst.write("const int ");
dst.write(varname)
dst.write("_LEN = ");
dst.write("_LEN PROGMEM = ");
dst.write(str(len(content)))
dst.write(";");

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,128 @@
#include <EEPROM.h>
#include "Arduino.h"
struct ConfigObject {
#define EEPROM_SIZE 1024 * 3
#define EEPROM_CHECK_SUM 87 // Used to check if config is stored. Change if structure changes
#define EEPROM_CONFIG_ADDRESS 0
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
#define CONFIG_SYSTEM_START 8
#define CONFIG_WIFI_START 16
#define CONFIG_WEB_START 648
#define CONFIG_METER_START 784
#define CONFIG_DEBUG_START 824
#define CONFIG_GPIO_START 832
#define CONFIG_DOMOTICZ_START 856
#define CONFIG_NTP_START 872
#define CONFIG_ENTSOE_START 944
#define CONFIG_MQTT_START 1004
#define CONFIG_MQTT_START_86 224
struct SystemConfig {
uint8_t boardType;
}; // 1
struct WiFiConfig {
char ssid[32];
char psk[64];
char ip[15];
char gateway[15];
char subnet[15];
char dns1[15];
char dns2[15];
char hostname[32];
bool mdns;
}; // 204
struct MqttConfig86 {
char host[128];
uint16_t port;
char clientId[32];
char publishTopic[64];
char subscribeTopic[64];
char username[64];
char password[64];
uint8_t payloadFormat;
bool ssl;
}; // 420
struct MqttConfig {
char host[128];
uint16_t port;
char clientId[32];
char publishTopic[64];
char subscribeTopic[64];
char username[128];
char password[256];
uint8_t payloadFormat;
bool ssl;
}; // 676
struct WebConfig {
uint8_t security;
char username[64];
char password[64];
}; // 129
struct MeterConfig {
uint8_t type;
uint8_t distributionSystem;
uint8_t mainFuse;
uint8_t productionCapacity;
uint8_t encryptionKey[16];
uint8_t authenticationKey[16];
bool substituteMissing;
}; // 37
struct DebugConfig {
bool telnet;
bool serial;
uint8_t level;
}; // 3
struct GpioConfig {
uint8_t hanPin;
uint8_t apPin;
uint8_t ledPin;
bool ledInverted;
uint8_t ledPinRed;
uint8_t ledPinGreen;
uint8_t ledPinBlue;
bool ledRgbInverted;
uint8_t tempSensorPin;
uint8_t tempAnalogSensorPin;
uint8_t vccPin;
int16_t vccOffset;
uint16_t vccMultiplier;
uint8_t vccBootLimit;
}; // 16
struct DomoticzConfig {
uint16_t elidx;
uint16_t vl1idx;
uint16_t vl2idx;
uint16_t vl3idx;
uint16_t cl1idx;
}; // 10
struct NtpConfig {
bool enable;
bool dhcp;
int16_t offset;
int16_t summerOffset;
char server[64];
}; // 70
struct EntsoeConfig {
char token[37];
char area[17];
char currency[4];
uint16_t multiplier;
}; // 60
struct ConfigObject83 {
uint8_t boardType;
char wifiSsid[32];
char wifiPassword[64];
@@ -25,6 +146,73 @@ struct ConfigObject {
uint8_t authSecurity;
char authUser[64];
char authPassword[64];
uint8_t meterType;
uint8_t distributionSystem;
uint8_t mainFuse;
uint8_t productionCapacity;
uint8_t meterEncryptionKey[16];
uint8_t meterAuthenticationKey[16];
bool substituteMissing;
bool sendUnknown;
bool debugTelnet;
bool debugSerial;
uint8_t debugLevel;
uint8_t hanPin;
uint8_t apPin;
uint8_t ledPin;
bool ledInverted;
uint8_t ledPinRed;
uint8_t ledPinGreen;
uint8_t ledPinBlue;
bool ledRgbInverted;
uint8_t tempSensorPin;
uint8_t vccPin;
int16_t vccOffset;
uint16_t vccMultiplier;
uint8_t vccBootLimit;
uint16_t domoELIDX;
uint16_t domoVL1IDX;
uint16_t domoVL2IDX;
uint16_t domoVL3IDX;
uint16_t domoCL1IDX;
bool mDnsEnable;
bool ntpEnable;
bool ntpDhcp;
int16_t ntpOffset;
int16_t ntpSummerOffset;
char ntpServer[64];
uint8_t tempAnalogSensorPin;
};
struct ConfigObject82 {
uint8_t boardType;
char wifiSsid[32];
char wifiPassword[64];
char wifiIp[15];
char wifiGw[15];
char wifiSubnet[15];
char wifiDns1[15];
char wifiDns2[15];
char wifiHostname[32];
char mqttHost[128];
uint16_t mqttPort;
char mqttClientId[32];
char mqttPublishTopic[64];
char mqttSubscribeTopic[64];
char mqttUser[64];
char mqttPassword[64];
uint8_t mqttPayloadFormat;
bool mqttSsl;
uint8_t authSecurity;
char authUser[64];
char authPassword[64];
uint8_t meterType;
uint8_t distributionSystem;
uint8_t mainFuse;
@@ -56,203 +244,99 @@ struct ConfigObject {
uint16_t domoCL1IDX;
};
struct TempSensorConfig {
uint8_t address[8];
char name[16];
bool common;
};
class AmsConfiguration {
public:
bool hasConfig();
int getConfigVersion();
bool load();
bool save();
uint8_t getBoardType();
void setBoardType(uint8_t boardType);
char* getWifiSsid();
void setWifiSsid(const char* wifiSsid);
char* getWifiPassword();
void setWifiPassword(const char* wifiPassword);
char* getWifiIp();
void setWifiIp(const char* wifiIp);
char* getWifiGw();
void setWifiGw(const char* wifiGw);
char* getWifiSubnet();
void setWifiSubnet(const char* wifiSubnet);
char* getWifiDns1();
void setWifiDns1(const char* wifiDns1);
char* getWifiDns2();
void setWifiDns2(const char* wifiDns1);
char* getWifiHostname();
void setWifiHostname(const char* wifiHostname);
void clearWifi();
void clearWifiIp();
bool getSystemConfig(SystemConfig&);
bool setSystemConfig(SystemConfig&);
bool getWiFiConfig(WiFiConfig&);
bool setWiFiConfig(WiFiConfig&);
void clearWifi(WiFiConfig&);
void clearWifiIp(WiFiConfig&);
bool isWifiChanged();
void ackWifiChange();
char* getMqttHost();
void setMqttHost(const char* mqttHost);
uint16_t getMqttPort();
void setMqttPort(uint16_t mqttPort);
char* getMqttClientId();
void setMqttClientId(const char* mqttClientId);
char* getMqttPublishTopic();
void setMqttPublishTopic(const char* mqttPublishTopic);
char* getMqttSubscribeTopic();
void setMqttSubscribeTopic(const char* mqttSubscribeTopic);
char* getMqttUser();
void setMqttUser(const char* mqttUser);
char* getMqttPassword();
void setMqttPassword(const char* mqttPassword);
uint8_t getMqttPayloadFormat();
void setMqttPayloadFormat(uint8_t mqttPayloadFormat);
bool isMqttSsl();
void setMqttSsl(bool mqttSsl);
void clearMqtt();
bool getMqttConfig(MqttConfig&);
bool setMqttConfig(MqttConfig&);
void clearMqtt(MqttConfig&);
void setMqttChanged();
bool isMqttChanged();
void ackMqttChange();
byte getAuthSecurity();
void setAuthSecurity(byte authSecurity);
char* getAuthUser();
void setAuthUser(const char* authUser);
char* getAuthPassword();
void setAuthPassword(const char* authPassword);
void clearAuth();
bool getWebConfig(WebConfig&);
bool setWebConfig(WebConfig&);
void clearAuth(WebConfig&);
uint8_t getMeterType();
void setMeterType(uint8_t meterType);
uint8_t getDistributionSystem();
void setDistributionSystem(uint8_t distributionSystem);
uint8_t getMainFuse();
void setMainFuse(uint8_t mainFuse);
uint8_t getProductionCapacity();
void setProductionCapacity(uint8_t productionCapacity);
bool isSubstituteMissing();
void setSubstituteMissing(bool substituteMissing);
bool isSendUnknown();
void setSendUnknown(bool sendUnknown);
void clearMeter();
bool getMeterConfig(MeterConfig&);
bool setMeterConfig(MeterConfig&);
void clearMeter(MeterConfig&);
bool isMeterChanged();
void ackMeterChanged();
bool isDebugTelnet();
void setDebugTelnet(bool debugTelnet);
bool isDebugSerial();
void setDebugSerial(bool debugSerial);
uint8_t getDebugLevel();
void setDebugLevel(uint8_t debugLevel);
bool getDebugConfig(DebugConfig&);
bool setDebugConfig(DebugConfig&);
void clearDebug(DebugConfig&);
bool pinUsed(uint8_t pin);
bool pinUsed(uint8_t, GpioConfig&);
uint8_t getHanPin();
void setHanPin(uint8_t hanPin);
uint8_t getApPin();
void setApPin(uint8_t apPin);
uint8_t getLedPin();
void setLedPin(uint8_t ledPin);
bool isLedInverted();
void setLedInverted(bool ledInverted);
uint8_t getLedPinRed();
void setLedPinRed(uint8_t ledPinRed);
uint8_t getLedPinGreen();
void setLedPinGreen(uint8_t ledPinGreen);
uint8_t getLedPinBlue();
void setLedPinBlue(uint8_t ledPinBlue);
bool isLedRgbInverted();
void setLedRgbInverted(bool ledRgbInverted);
uint8_t getTempSensorPin();
void setTempSensorPin(uint8_t tempSensorPin);
uint8_t getVccPin();
void setVccPin(uint8_t vccPin);
double getVccMultiplier();
void setVccMultiplier(double vccMultiplier);
double getVccBootLimit();
void setVccBootLimit(double vccBootLimit);
bool getGpioConfig(GpioConfig&);
bool setGpioConfig(GpioConfig&);
void clearGpio(GpioConfig&);
void print(Print* debugger);
uint16_t getDomoELIDX();
uint16_t getDomoVL1IDX();
uint16_t getDomoVL2IDX();
uint16_t getDomoVL3IDX();
uint16_t getDomoCL1IDX();
void setDomoELIDX(uint16_t domoELIDX);
void setDomoVL1IDX(uint16_t domoVL1IDX);
void setDomoVL2IDX(uint16_t domoVL2IDX);
void setDomoVL3IDX(uint16_t domoVL3IDX);
void setDomoCL1IDX(uint16_t domoCL1IDX);
void clearDomo();
bool getDomoticzConfig(DomoticzConfig&);
bool setDomoticzConfig(DomoticzConfig&);
void clearDomo(DomoticzConfig&);
bool isDomoChanged();
void ackDomoChange();
bool getNtpConfig(NtpConfig&);
bool setNtpConfig(NtpConfig&);
void clearNtp(NtpConfig&);
bool isNtpChanged();
void ackNtpChange();
bool getEntsoeConfig(EntsoeConfig&);
bool setEntsoeConfig(EntsoeConfig&);
void clearEntsoe(EntsoeConfig&);
bool isEntsoeChanged();
void ackEntsoeChange();
void loadTempSensors();
void saveTempSensors();
uint8_t getTempSensorCount();
TempSensorConfig* getTempSensorConfig(uint8_t address[8]);
void updateTempSensorConfig(uint8_t address[8], const char name[32], bool common);
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
void clear();
protected:
private:
int configVersion = 0;
ConfigObject config {
0, // Board type
"", // SSID
"", // PSK
"", // IP
"", // GW
"", // Subnet
"", // DNS 1
"", // DNS 2
"", // Hostname
"", // MQTT host
1883, // Port
"", // Client ID
"", // Publish topic
"", // Subscribe topic
"", // Username
"", // Password
0, // Format
false, // SSL
0, // Web security
"", // Username
"", // Password
0, // Meter type
0, // Distribution system
0, // Main fuse
0, // Production capacity
false, // Substitute
false, // Send unknown
false, // Debug telnet
false, // Debug serial
5, // Debug level
0x03, // HAN pin
0xFF, // AP pin
0x02, // LED pin
true, // Inverted
0xFF, // Red
0xFF, // Green
0xFF, // Blue
true, // Inverted
0xFF, // Temp sensor
0xFF, // Vcc
100, // Multiplier
0, // Boot limit
//Domoticz
0, // ELIDX
0, // VL1IDX
0, // VL2IDX
0, // VL3IDX
0 // CL1IDX
// 786 bytes
};
bool wifiChanged, mqttChanged, domoChanged;
uint8_t configVersion = 0;
const int EEPROM_SIZE = 790; // Config size + 4 bytes for config version
const int EEPROM_CHECK_SUM = 82; // Used to check if config is stored. Change if structure changes
const int EEPROM_CONFIG_ADDRESS = 0;
bool wifiChanged, mqttChanged, meterChanged = true, domoChanged, ntpChanged = true, entsoeChanged = false;
bool loadConfig80(int address);
bool loadConfig81(int address);
uint8_t tempSensorCount = 0;
TempSensorConfig** tempSensors;
bool loadConfig82(int address);
bool loadConfig83(int address);
bool relocateConfig86();
int readString(int pAddress, char* pString[]);
int readInt(int pAddress, int *pValue);

View File

@@ -2,12 +2,13 @@
#include "Kaifa.h"
#include "Aidon.h"
#include "Kamstrup.h"
#include "Omnipower.h"
AmsData::AmsData() {}
AmsData::AmsData(int meterType, bool substituteMissing, HanReader& hanReader) {
AmsData::AmsData(uint8_t meterType, bool substituteMissing, HanReader& hanReader) {
lastUpdateMillis = millis();
packageTimestamp = hanReader.getPackageTime();
packageTimestamp = hanReader.getPackageTime(true, true);
int listSize = hanReader.getListSize();
switch(meterType) {
@@ -18,224 +19,305 @@ AmsData::AmsData(int meterType, bool substituteMissing, HanReader& hanReader) {
extractFromAidon(hanReader, listSize, substituteMissing);
break;
case METER_TYPE_KAMSTRUP:
extractFromKamstrup(hanReader, listSize);
extractFromKamstrup(hanReader, listSize, substituteMissing);
break;
case METER_TYPE_OMNIPOWER:
extractFromOmnipower(hanReader, listSize, substituteMissing);
break;
}
}
void AmsData::extractFromKaifa(HanReader& hanReader, int listSize) {
void AmsData::extractFromKaifa(HanReader& hanReader, uint8_t listSize) {
switch(listSize) {
case (int)Kaifa::List1:
case (uint8_t)Kaifa::List1:
listType = 1;
break;
case (int)Kaifa::List3PhaseShort:
case (uint8_t)Kaifa::List3PhaseShort:
threePhase = true;
case (int)Kaifa::List1PhaseShort:
case (uint8_t)Kaifa::List1PhaseShort:
listType = 2;
break;
case (int)Kaifa::List3PhaseLong:
case (uint8_t)Kaifa::List3PhaseLong:
threePhase = true;
case (int)Kaifa::List1PhaseLong:
case (uint8_t)Kaifa::List1PhaseLong:
listType = 3;
break;
}
if(listSize == (int)Kaifa::List1) {
if(listSize == (uint8_t)Kaifa::List1) {
activeImportPower = hanReader.getInt((int)Kaifa_List1::ActivePowerImported);
} else {
switch(listSize) {
case (int)Kaifa::List3PhaseLong:
meterTimestamp = hanReader.getTime( (int)Kaifa_List3Phase::MeterClock);
activeImportCounter = ((double) hanReader.getUint((int)Kaifa_List3Phase::CumulativeActiveImportEnergy)) / 1000;
activeExportCounter = ((double) hanReader.getUint((int)Kaifa_List3Phase::CumulativeActiveExportEnergy)) / 1000;
reactiveImportCounter = ((double) hanReader.getUint((int)Kaifa_List3Phase::CumulativeReactiveImportEnergy)) / 1000;
reactiveExportCounter = ((double) hanReader.getUint((int)Kaifa_List3Phase::CumulativeReactiveExportEnergy)) / 1000;
case (int)Kaifa::List3PhaseShort:
case (uint8_t)Kaifa::List3PhaseLong:
meterTimestamp = hanReader.getTime( (int)Kaifa_List3Phase::MeterClock, false, false);
activeImportCounter = ((float) hanReader.getUint((int)Kaifa_List3Phase::CumulativeActiveImportEnergy)) / 1000;
activeExportCounter = ((float) hanReader.getUint((int)Kaifa_List3Phase::CumulativeActiveExportEnergy)) / 1000;
reactiveImportCounter = ((float) hanReader.getUint((int)Kaifa_List3Phase::CumulativeReactiveImportEnergy)) / 1000;
reactiveExportCounter = ((float) hanReader.getUint((int)Kaifa_List3Phase::CumulativeReactiveExportEnergy)) / 1000;
case (uint8_t)Kaifa::List3PhaseShort:
listId = hanReader.getString( (int)Kaifa_List3Phase::ListVersionIdentifier);
meterId = hanReader.getString( (int)Kaifa_List3Phase::MeterID);
meterType = hanReader.getString( (int)Kaifa_List3Phase::MeterType);
meterModel = hanReader.getString( (int)Kaifa_List3Phase::MeterType);
activeImportPower = hanReader.getUint( (int)Kaifa_List3Phase::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (int)Kaifa_List3Phase::ReactiveImportPower);
activeExportPower = hanReader.getUint( (int)Kaifa_List3Phase::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (int)Kaifa_List3Phase::ReactiveExportPower);
l1current = ((double) hanReader.getInt( (int)Kaifa_List3Phase::CurrentL1)) / 1000;
l2current = ((double) hanReader.getInt( (int)Kaifa_List3Phase::CurrentL2)) / 1000;
l3current = ((double) hanReader.getInt( (int)Kaifa_List3Phase::CurrentL3)) / 1000;
l1voltage = ((double) hanReader.getInt( (int)Kaifa_List3Phase::VoltageL1)) / 10;
l2voltage = ((double) hanReader.getInt( (int)Kaifa_List3Phase::VoltageL2)) / 10;
l3voltage = ((double) hanReader.getInt( (int)Kaifa_List3Phase::VoltageL3)) / 10;
l1current = ((float) hanReader.getInt( (int)Kaifa_List3Phase::CurrentL1)) / 1000;
l2current = ((float) hanReader.getInt( (int)Kaifa_List3Phase::CurrentL2)) / 1000;
l3current = ((float) hanReader.getInt( (int)Kaifa_List3Phase::CurrentL3)) / 1000;
l1voltage = ((float) hanReader.getInt( (int)Kaifa_List3Phase::VoltageL1)) / 10;
l2voltage = ((float) hanReader.getInt( (int)Kaifa_List3Phase::VoltageL2)) / 10;
l3voltage = ((float) hanReader.getInt( (int)Kaifa_List3Phase::VoltageL3)) / 10;
break;
case (int)Kaifa::List1PhaseLong:
meterTimestamp = hanReader.getTime( (int)Kaifa_List1Phase::MeterClock);
activeImportCounter = ((double) hanReader.getUint((int)Kaifa_List1Phase::CumulativeActiveImportEnergy));
activeExportCounter = ((double) hanReader.getUint((int)Kaifa_List1Phase::CumulativeActiveExportEnergy));
reactiveImportCounter = ((double) hanReader.getUint((int)Kaifa_List1Phase::CumulativeReactiveImportEnergy));
reactiveExportCounter = ((double) hanReader.getUint((int)Kaifa_List1Phase::CumulativeReactiveExportEnergy));
case (int)Kaifa::List1PhaseShort:
case (uint8_t)Kaifa::List1PhaseLong:
meterTimestamp = hanReader.getTime( (int)Kaifa_List1Phase::MeterClock, false, false);
activeImportCounter = ((float) hanReader.getUint((int)Kaifa_List1Phase::CumulativeActiveImportEnergy));
activeExportCounter = ((float) hanReader.getUint((int)Kaifa_List1Phase::CumulativeActiveExportEnergy));
reactiveImportCounter = ((float) hanReader.getUint((int)Kaifa_List1Phase::CumulativeReactiveImportEnergy));
reactiveExportCounter = ((float) hanReader.getUint((int)Kaifa_List1Phase::CumulativeReactiveExportEnergy));
case (uint8_t)Kaifa::List1PhaseShort:
listId = hanReader.getString( (int)Kaifa_List1Phase::ListVersionIdentifier);
meterId = hanReader.getString( (int)Kaifa_List1Phase::MeterID);
meterType = hanReader.getString( (int)Kaifa_List1Phase::MeterType);
meterModel = hanReader.getString( (int)Kaifa_List1Phase::MeterType);
activeImportPower = hanReader.getUint( (int)Kaifa_List1Phase::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (int)Kaifa_List1Phase::ReactiveImportPower);
activeExportPower = hanReader.getUint( (int)Kaifa_List1Phase::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (int)Kaifa_List1Phase::ReactiveExportPower);
l1current = ((double) hanReader.getInt( (int)Kaifa_List1Phase::CurrentL1)) / 1000;
l1voltage = ((double) hanReader.getInt( (int)Kaifa_List1Phase::VoltageL1)) / 10;
l1current = ((float) hanReader.getInt( (int)Kaifa_List1Phase::CurrentL1)) / 1000;
l1voltage = ((float) hanReader.getInt( (int)Kaifa_List1Phase::VoltageL1)) / 10;
break;
}
}
}
void AmsData::extractFromAidon(HanReader& hanReader, int listSize, bool substituteMissing) {
void AmsData::extractFromAidon(HanReader& hanReader, uint8_t listSize, bool substituteMissing) {
switch(listSize) {
case (int)Aidon::List1:
case (uint8_t)Aidon::List1:
listType = 1;
break;
case (int)Aidon::List3PhaseITShort:
case (int)Aidon::List3PhaseShort:
case (uint8_t)Aidon::List3PhaseITShort:
case (uint8_t)Aidon::List3PhaseShort:
threePhase = true;
case (int)Aidon::List1PhaseShort:
case (uint8_t)Aidon::List1PhaseShort:
listType = 2;
break;
case (int)Aidon::List3PhaseITLong:
case (int)Aidon::List3PhaseLong:
case (uint8_t)Aidon::List3PhaseITLong:
case (uint8_t)Aidon::List3PhaseLong:
threePhase = true;
case (int)Aidon::List1PhaseLong:
case (uint8_t)Aidon::List1PhaseLong:
listType = 3;
break;
}
if(listSize == (int)Aidon::List1) {
activeImportPower = hanReader.getUint((int)Aidon_List1::ActiveImportPower);
if(listSize == (uint8_t)Aidon::List1) {
activeImportPower = hanReader.getUint((uint8_t)Aidon_List1::ActiveImportPower);
} else {
switch(listSize) {
case (int)Aidon::List3PhaseLong:
meterTimestamp = hanReader.getTime( (int)Aidon_List3Phase::Timestamp);
activeImportCounter = ((double) hanReader.getUint( (int)Aidon_List3Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((double) hanReader.getUint( (int)Aidon_List3Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((double) hanReader.getUint( (int)Aidon_List3Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((double) hanReader.getUint( (int)Aidon_List3Phase::CumulativeReactiveExportEnergy)) / 100;
case (int)Aidon::List3PhaseShort:
listId = hanReader.getString( (int)Aidon_List3Phase::ListVersionIdentifier);
meterId = hanReader.getString( (int)Aidon_List3Phase::MeterID);
meterType = hanReader.getString( (int)Aidon_List3Phase::MeterType);
activeImportPower = hanReader.getUint( (int)Aidon_List3Phase::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (int)Aidon_List3Phase::ReactiveImportPower);
activeExportPower = hanReader.getUint( (int)Aidon_List3Phase::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (int)Aidon_List3Phase::ReactiveExportPower);
l1current = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL1)) / 10;
l2current = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL2)) / 10;
l3current = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL3)) / 10;
l1voltage = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL1)) / 10;
l2voltage = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL2)) / 10;
l3voltage = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL3)) / 10;
case (uint8_t)Aidon::List3PhaseLong:
meterTimestamp = hanReader.getTime( (uint8_t)Aidon_List3Phase::Timestamp, false, false);
activeImportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3Phase::CumulativeReactiveExportEnergy)) / 100;
case (uint8_t)Aidon::List3PhaseShort:
listId = hanReader.getString( (uint8_t)Aidon_List3Phase::ListVersionIdentifier);
meterId = hanReader.getString( (uint8_t)Aidon_List3Phase::MeterID);
meterModel = hanReader.getString( (uint8_t)Aidon_List3Phase::MeterType);
activeImportPower = hanReader.getUint( (uint8_t)Aidon_List3Phase::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (uint8_t)Aidon_List3Phase::ReactiveImportPower);
activeExportPower = hanReader.getUint( (uint8_t)Aidon_List3Phase::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (uint8_t)Aidon_List3Phase::ReactiveExportPower);
l1current = ((float) hanReader.getInt( (uint8_t)Aidon_List3Phase::CurrentL1)) / 10;
l2current = ((float) hanReader.getInt( (uint8_t)Aidon_List3Phase::CurrentL2)) / 10;
l3current = ((float) hanReader.getInt( (uint8_t)Aidon_List3Phase::CurrentL3)) / 10;
l1voltage = ((float) hanReader.getInt( (uint8_t)Aidon_List3Phase::VoltageL1)) / 10;
l2voltage = ((float) hanReader.getInt( (uint8_t)Aidon_List3Phase::VoltageL2)) / 10;
l3voltage = ((float) hanReader.getInt( (uint8_t)Aidon_List3Phase::VoltageL3)) / 10;
break;
case (int)Aidon::List1PhaseLong:
meterTimestamp = hanReader.getTime( (int)Aidon_List1Phase::Timestamp);
activeImportCounter = ((double) hanReader.getUint( (int)Aidon_List1Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((double) hanReader.getUint( (int)Aidon_List1Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((double) hanReader.getUint( (int)Aidon_List1Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((double) hanReader.getUint( (int)Aidon_List1Phase::CumulativeReactiveExportEnergy)) / 100;
case (int)Aidon::List1PhaseShort:
listId = hanReader.getString( (int)Aidon_List1Phase::ListVersionIdentifier);
meterId = hanReader.getString( (int)Aidon_List1Phase::MeterID);
meterType = hanReader.getString( (int)Aidon_List1Phase::MeterType);
activeImportPower = hanReader.getUint( (int)Aidon_List1Phase::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (int)Aidon_List1Phase::ReactiveImportPower);
activeExportPower = hanReader.getUint( (int)Aidon_List1Phase::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (int)Aidon_List1Phase::ReactiveExportPower);
l1current = ((double) hanReader.getInt( (int)Aidon_List1Phase::CurrentL1)) / 10;
l1voltage = ((double) hanReader.getInt( (int)Aidon_List1Phase::VoltageL1)) / 10;
case (uint8_t)Aidon::List1PhaseLong:
meterTimestamp = hanReader.getTime( (uint8_t)Aidon_List1Phase::Timestamp, false, false);
activeImportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List1Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List1Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List1Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List1Phase::CumulativeReactiveExportEnergy)) / 100;
case (uint8_t)Aidon::List1PhaseShort:
listId = hanReader.getString( (uint8_t)Aidon_List1Phase::ListVersionIdentifier);
meterId = hanReader.getString( (uint8_t)Aidon_List1Phase::MeterID);
meterModel = hanReader.getString( (uint8_t)Aidon_List1Phase::MeterType);
activeImportPower = hanReader.getUint( (uint8_t)Aidon_List1Phase::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (uint8_t)Aidon_List1Phase::ReactiveImportPower);
activeExportPower = hanReader.getUint( (uint8_t)Aidon_List1Phase::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (uint8_t)Aidon_List1Phase::ReactiveExportPower);
l1current = ((float) hanReader.getInt( (uint8_t)Aidon_List1Phase::CurrentL1)) / 10;
l1voltage = ((float) hanReader.getInt( (uint8_t)Aidon_List1Phase::VoltageL1)) / 10;
break;
case (int)Aidon::List3PhaseITLong:
meterTimestamp = hanReader.getTime( (int)Aidon_List3PhaseIT::Timestamp);
activeImportCounter = ((double) hanReader.getUint( (int)Aidon_List3PhaseIT::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((double) hanReader.getUint( (int)Aidon_List3PhaseIT::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((double) hanReader.getUint( (int)Aidon_List3PhaseIT::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((double) hanReader.getUint( (int)Aidon_List3PhaseIT::CumulativeReactiveExportEnergy)) / 100;
case (int)Aidon::List3PhaseITShort:
listId = hanReader.getString( (int)Aidon_List3PhaseIT::ListVersionIdentifier);
meterId = hanReader.getString( (int)Aidon_List3PhaseIT::MeterID);
meterType = hanReader.getString( (int)Aidon_List3PhaseIT::MeterType);
activeImportPower = hanReader.getUint( (int)Aidon_List3PhaseIT::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (int)Aidon_List3PhaseIT::ReactiveImportPower);
activeExportPower = hanReader.getUint( (int)Aidon_List3PhaseIT::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (int)Aidon_List3PhaseIT::ReactiveExportPower);
l1current = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::CurrentL1)) / 10;
case (uint8_t)Aidon::List3PhaseITLong:
meterTimestamp = hanReader.getTime( (uint8_t)Aidon_List3PhaseIT::Timestamp, false, false);
activeImportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((float) hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::CumulativeReactiveExportEnergy)) / 100;
case (uint8_t)Aidon::List3PhaseITShort:
listId = hanReader.getString( (uint8_t)Aidon_List3PhaseIT::ListVersionIdentifier);
meterId = hanReader.getString( (uint8_t)Aidon_List3PhaseIT::MeterID);
meterModel = hanReader.getString( (uint8_t)Aidon_List3PhaseIT::MeterType);
activeImportPower = hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::ActiveImportPower);
reactiveImportPower = hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::ReactiveImportPower);
activeExportPower = hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::ActiveExportPower);
reactiveExportPower = hanReader.getUint( (uint8_t)Aidon_List3PhaseIT::ReactiveExportPower);
l1current = ((float) hanReader.getInt( (uint8_t)Aidon_List3PhaseIT::CurrentL1)) / 10;
l2current = 0;
l3current = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::CurrentL3)) / 10;
l1voltage = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::VoltageL1)) / 10;
l2voltage = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::VoltageL2)) / 10;
l3voltage = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::VoltageL3)) / 10;
l3current = ((float) hanReader.getInt( (uint8_t)Aidon_List3PhaseIT::CurrentL3)) / 10;
l1voltage = ((float) hanReader.getInt( (uint8_t)Aidon_List3PhaseIT::VoltageL1)) / 10;
l2voltage = ((float) hanReader.getInt( (uint8_t)Aidon_List3PhaseIT::VoltageL2)) / 10;
l3voltage = ((float) hanReader.getInt( (uint8_t)Aidon_List3PhaseIT::VoltageL3)) / 10;
if(substituteMissing) {
l2current = ((activeImportPower * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
}
break;
}
}
}
void AmsData::extractFromKamstrup(HanReader& hanReader, int listSize) {
void AmsData::extractFromKamstrup(HanReader& hanReader, uint8_t listSize, bool substituteMissing) {
switch(listSize) {
case (int)Kamstrup::List3PhaseShort:
case (uint8_t)Kamstrup::List3PhaseITShort:
case (uint8_t)Kamstrup::List3PhaseShort:
threePhase = true;
case (int)Kamstrup::List1PhaseShort:
case (uint8_t)Kamstrup::List1PhaseShort:
listType = 2;
break;
case (int)Kamstrup::List3PhaseLong:
case (uint8_t)Kamstrup::List3PhaseITLong:
case (uint8_t)Kamstrup::List3PhaseLong:
threePhase = true;
case (int)Kamstrup::List1PhaseLong:
case (uint8_t)Kamstrup::List1PhaseLong:
listType = 3;
break;
}
switch(listSize) {
case (int)Kamstrup::List3PhaseLong:
meterTimestamp = hanReader.getTime( (int)Kamstrup_List3Phase::MeterClock);
activeImportCounter = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CumulativeReactiveExportEnergy)) / 100;
case (int)Kamstrup::List3PhaseShort:
listId = hanReader.getString( (int)Kamstrup_List3Phase::ListVersionIdentifier);
meterId = hanReader.getString( (int)Kamstrup_List3Phase::MeterID);
meterType = hanReader.getString( (int)Kamstrup_List3Phase::MeterType);
activeImportPower = hanReader.getInt( (int)Kamstrup_List3Phase::ActiveImportPower);
reactiveImportPower = hanReader.getInt( (int)Kamstrup_List3Phase::ReactiveImportPower);
activeExportPower = hanReader.getInt( (int)Kamstrup_List3Phase::ActiveExportPower);
reactiveExportPower = hanReader.getInt( (int)Kamstrup_List3Phase::ReactiveExportPower);
l1current = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CurrentL1)) / 100;
l2current = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CurrentL2)) / 100;
l3current = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CurrentL3)) / 100;
l1voltage = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL1);
l2voltage = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL2);
l3voltage = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL3);
case (uint8_t)Kamstrup::List1PhaseLong:
meterTimestamp = hanReader.getTime( (uint8_t)Kamstrup_List1Phase::MeterClock, true, true);
activeImportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List1Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List1Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List1Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List1Phase::CumulativeReactiveExportEnergy)) / 100;
case (uint8_t)Kamstrup::List1PhaseShort:
listId = hanReader.getString( (uint8_t)Kamstrup_List1Phase::ListVersionIdentifier);
meterId = hanReader.getString( (uint8_t)Kamstrup_List1Phase::MeterID);
meterModel = hanReader.getString( (uint8_t)Kamstrup_List1Phase::MeterType);
activeImportPower = hanReader.getInt( (uint8_t)Kamstrup_List1Phase::ActiveImportPower);
reactiveImportPower = hanReader.getInt( (uint8_t)Kamstrup_List1Phase::ReactiveImportPower);
activeExportPower = hanReader.getInt( (uint8_t)Kamstrup_List1Phase::ActiveExportPower);
reactiveExportPower = hanReader.getInt( (uint8_t)Kamstrup_List1Phase::ReactiveExportPower);
l1current = ((float) hanReader.getInt((uint8_t)Kamstrup_List1Phase::CurrentL1)) / 100;
l1voltage = hanReader.getInt( (uint8_t)Kamstrup_List1Phase::VoltageL1);
break;
case (int)Kamstrup::List1PhaseLong:
meterTimestamp = hanReader.getTime( (int)Kamstrup_List1Phase::MeterClock);
activeImportCounter = ((double) hanReader.getInt((int)Kamstrup_List1Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((double) hanReader.getInt((int)Kamstrup_List1Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((double) hanReader.getInt((int)Kamstrup_List1Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((double) hanReader.getInt((int)Kamstrup_List1Phase::CumulativeReactiveExportEnergy)) / 100;
case (int)Kamstrup::List1PhaseShort:
listId = hanReader.getString( (int)Kamstrup_List1Phase::ListVersionIdentifier);
meterId = hanReader.getString( (int)Kamstrup_List1Phase::MeterID);
meterType = hanReader.getString( (int)Kamstrup_List1Phase::MeterType);
activeImportPower = hanReader.getInt( (int)Kamstrup_List1Phase::ActiveImportPower);
reactiveImportPower = hanReader.getInt( (int)Kamstrup_List1Phase::ReactiveImportPower);
activeExportPower = hanReader.getInt( (int)Kamstrup_List1Phase::ActiveExportPower);
reactiveExportPower = hanReader.getInt( (int)Kamstrup_List1Phase::ReactiveExportPower);
l1current = ((double) hanReader.getInt((int)Kamstrup_List1Phase::CurrentL1)) / 100;
l1voltage = hanReader.getInt( (int)Kamstrup_List1Phase::VoltageL1);
case (uint8_t)Kamstrup::List3PhaseLong:
meterTimestamp = hanReader.getTime( (uint8_t)Kamstrup_List3Phase::MeterClock, true, true);
activeImportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeReactiveExportEnergy)) / 100;
case (uint8_t)Kamstrup::List3PhaseShort:
listId = hanReader.getString( (uint8_t)Kamstrup_List3Phase::ListVersionIdentifier);
meterId = hanReader.getString( (uint8_t)Kamstrup_List3Phase::MeterID);
meterModel = hanReader.getString( (uint8_t)Kamstrup_List3Phase::MeterType);
activeImportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ActiveImportPower);
reactiveImportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ReactiveImportPower);
activeExportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ActiveExportPower);
reactiveExportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ReactiveExportPower);
l1current = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CurrentL1)) / 100;
l2current = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CurrentL2)) / 100;
l3current = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CurrentL3)) / 100;
l1voltage = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::VoltageL1);
l2voltage = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::VoltageL2);
l3voltage = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::VoltageL3);
break;
case (uint8_t)Kamstrup::List3PhaseITLong:
meterTimestamp = hanReader.getTime( (uint8_t)Kamstrup_List3Phase::MeterClock, true, true);
activeImportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CumulativeReactiveExportEnergy)) / 100;
case (uint8_t)Kamstrup::List3PhaseITShort:
listId = hanReader.getString( (uint8_t)Kamstrup_List3Phase::ListVersionIdentifier);
meterId = hanReader.getString( (uint8_t)Kamstrup_List3Phase::MeterID);
meterModel = hanReader.getString( (uint8_t)Kamstrup_List3Phase::MeterType);
activeImportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ActiveImportPower);
reactiveImportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ReactiveImportPower);
activeExportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ActiveExportPower);
reactiveExportPower = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::ReactiveExportPower);
l1current = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CurrentL1)) / 100;
l2current = 0;
l3current = ((float) hanReader.getInt((uint8_t)Kamstrup_List3Phase::CurrentL3)) / 100;
l1voltage = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::VoltageL1);
l2voltage = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::VoltageL2);
l3voltage = hanReader.getInt( (uint8_t)Kamstrup_List3Phase::VoltageL3);
if(substituteMissing) {
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
}
break;
}
}
void AmsData::extractFromOmnipower(HanReader& hanReader, uint8_t listSize, bool substituteMissing) {
switch(listSize) {
case (uint8_t)Kamstrup::List3PhaseITShort:
case (uint8_t)Kamstrup::List3PhaseShort:
case (uint8_t)Kamstrup::List1PhaseShort:
case (uint8_t)Kamstrup::List3PhaseITLong:
case (uint8_t)Kamstrup::List3PhaseLong:
case (uint8_t)Kamstrup::List1PhaseLong:
extractFromKamstrup(hanReader, listSize, substituteMissing);
break;
case (uint8_t)Omnipower::DLMS:
meterTimestamp = hanReader.getTime( (uint8_t)Omnipower_DLMS::MeterClock, true, true);
activeImportCounter = ((float) hanReader.getInt((uint8_t)Omnipower_DLMS::CumulativeActiveImportEnergy)) / 100;
activeExportCounter = ((float) hanReader.getInt((uint8_t)Omnipower_DLMS::CumulativeActiveExportEnergy)) / 100;
reactiveImportCounter = ((float) hanReader.getInt((uint8_t)Omnipower_DLMS::CumulativeReactiveImportEnergy)) / 100;
reactiveExportCounter = ((float) hanReader.getInt((uint8_t)Omnipower_DLMS::CumulativeReactiveExportEnergy)) / 100;
listId = hanReader.getString( (uint8_t)Omnipower_DLMS::ListVersionIdentifier);
activeImportPower = hanReader.getInt( (uint8_t)Omnipower_DLMS::ActiveImportPower);
reactiveImportPower = hanReader.getInt( (uint8_t)Omnipower_DLMS::ReactiveImportPower);
activeExportPower = hanReader.getInt( (uint8_t)Omnipower_DLMS::ActiveExportPower);
reactiveExportPower = hanReader.getInt( (uint8_t)Omnipower_DLMS::ReactiveExportPower);
l1current = ((float) hanReader.getInt((uint8_t)Omnipower_DLMS::CurrentL1)) / 100;
l2current = ((float) hanReader.getInt((uint8_t)Omnipower_DLMS::CurrentL2)) / 100;
l3current = ((float) hanReader.getInt((uint8_t)Omnipower_DLMS::CurrentL3)) / 100;
l1voltage = hanReader.getInt( (uint8_t)Omnipower_DLMS::VoltageL1);
l2voltage = hanReader.getInt( (uint8_t)Omnipower_DLMS::VoltageL2);
l3voltage = hanReader.getInt( (uint8_t)Omnipower_DLMS::VoltageL3);
listType = 3;
break;
}
threePhase = l3voltage != 0;
}
void AmsData::apply(AmsData& other) {
if(other.getListType() < 3) {
unsigned long ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastUpdateMillis;
if(ms > 0) {
if(other.getActiveImportPower() > 0)
activeImportCounter += (((float) ms) * other.getActiveImportPower()) / 3600000000;
if(other.getListType() > 1) {
if(other.getActiveExportPower() > 0)
activeExportCounter += (((float) ms*2) * other.getActiveExportPower()) / 3600000000;
if(other.getReactiveImportPower() > 0)
reactiveImportCounter += (((float) ms*2) * other.getReactiveImportPower()) / 3600000000;
if(other.getReactiveExportPower() > 0)
reactiveExportCounter += (((float) ms*2) * other.getReactiveExportPower()) / 3600000000;
}
counterEstimated = true;
}
}
this->lastUpdateMillis = other.getLastUpdateMillis();
this->packageTimestamp = other.getPackageTimestamp();
this->listType = max(this->listType, other.getListType());
if(other.getListType() > this->listType)
this->listType = other.getListType();
switch(other.getListType()) {
case 3:
this->meterTimestamp = other.getMeterTimestamp();
@@ -243,10 +325,11 @@ void AmsData::apply(AmsData& other) {
this->activeExportCounter = other.getActiveExportCounter();
this->reactiveImportCounter = other.getReactiveImportCounter();
this->reactiveExportCounter = other.getReactiveExportCounter();
this->counterEstimated = false;
case 2:
this->listId = other.getListId();
this->meterId = other.getMeterId();
this->meterType = other.getMeterType();
this->meterModel = other.getMeterModel();
this->reactiveImportPower = other.getReactiveImportPower();
this->activeExportPower = other.getActiveExportPower();
this->reactiveExportPower = other.getReactiveExportPower();
@@ -266,11 +349,11 @@ unsigned long AmsData::getLastUpdateMillis() {
return this->lastUpdateMillis;
}
unsigned long AmsData::getPackageTimestamp() {
time_t AmsData::getPackageTimestamp() {
return this->packageTimestamp;
}
int AmsData::getListType() {
uint8_t AmsData::getListType() {
return this->listType;
}
@@ -282,67 +365,67 @@ String AmsData::getMeterId() {
return this->meterId;
}
String AmsData::getMeterType() {
return this->meterType;
String AmsData::getMeterModel() {
return this->meterModel;
}
unsigned long AmsData::getMeterTimestamp() {
time_t AmsData::getMeterTimestamp() {
return this->meterTimestamp;
}
int AmsData::getActiveImportPower() {
uint16_t AmsData::getActiveImportPower() {
return this->activeImportPower;
}
int AmsData::getReactiveImportPower() {
uint16_t AmsData::getReactiveImportPower() {
return this->reactiveImportPower;
}
int AmsData::getActiveExportPower() {
uint16_t AmsData::getActiveExportPower() {
return this->activeExportPower;
}
int AmsData::getReactiveExportPower() {
uint16_t AmsData::getReactiveExportPower() {
return this->reactiveExportPower;
}
double AmsData::getL1Voltage() {
float AmsData::getL1Voltage() {
return this->l1voltage;
}
double AmsData::getL2Voltage() {
float AmsData::getL2Voltage() {
return this->l2voltage;
}
double AmsData::getL3Voltage() {
float AmsData::getL3Voltage() {
return this->l3voltage;
}
double AmsData::getL1Current() {
float AmsData::getL1Current() {
return this->l1current;
}
double AmsData::getL2Current() {
float AmsData::getL2Current() {
return this->l2current;
}
double AmsData::getL3Current() {
float AmsData::getL3Current() {
return this->l3current;
}
double AmsData::getActiveImportCounter() {
float AmsData::getActiveImportCounter() {
return this->activeImportCounter;
}
double AmsData::getReactiveImportCounter() {
float AmsData::getReactiveImportCounter() {
return this->reactiveImportCounter;
}
double AmsData::getActiveExportCounter() {
float AmsData::getActiveExportCounter() {
return this->activeExportCounter;
}
double AmsData::getReactiveExportCounter() {
float AmsData::getReactiveExportCounter() {
return this->reactiveExportCounter;
}

View File

@@ -8,60 +8,62 @@
#define METER_TYPE_KAIFA 1
#define METER_TYPE_AIDON 2
#define METER_TYPE_KAMSTRUP 3
#define METER_TYPE_OMNIPOWER 4
class AmsData {
public:
AmsData();
AmsData(int meterType, bool substituteMissing, HanReader& hanReader);
AmsData(uint8_t meterType, bool substituteMissing, HanReader& hanReader);
void apply(AmsData& other);
unsigned long getLastUpdateMillis();
unsigned long getPackageTimestamp();
time_t getPackageTimestamp();
int getListType();
uint8_t getListType();
String getListId();
String getMeterId();
String getMeterType();
String getMeterModel();
unsigned long getMeterTimestamp();
time_t getMeterTimestamp();
int getActiveImportPower();
int getReactiveImportPower();
int getActiveExportPower();
int getReactiveExportPower();
uint16_t getActiveImportPower();
uint16_t getReactiveImportPower();
uint16_t getActiveExportPower();
uint16_t getReactiveExportPower();
double getL1Voltage();
double getL2Voltage();
double getL3Voltage();
float getL1Voltage();
float getL2Voltage();
float getL3Voltage();
double getL1Current();
double getL2Current();
double getL3Current();
float getL1Current();
float getL2Current();
float getL3Current();
double getActiveImportCounter();
double getReactiveImportCounter();
double getActiveExportCounter();
double getReactiveExportCounter();
float getActiveImportCounter();
float getReactiveImportCounter();
float getActiveExportCounter();
float getReactiveExportCounter();
bool isThreePhase();
private:
unsigned long lastUpdateMillis = 0;
int listType = 0;
unsigned long packageTimestamp = 0;
String listId, meterId, meterType;
unsigned long meterTimestamp = 0;
int activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
double l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
bool threePhase = false;
uint8_t listType = 0;
time_t packageTimestamp = 0;
String listId, meterId, meterModel;
time_t meterTimestamp = 0;
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
float activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
bool threePhase = false, counterEstimated = false;
void extractFromKaifa(HanReader& hanReader, int listSize);
void extractFromAidon(HanReader& hanReader, int listSize, bool substituteMissing);
void extractFromKamstrup(HanReader& hanReader, int listSize);
void extractFromKaifa(HanReader& hanReader, uint8_t listSize);
void extractFromAidon(HanReader& hanReader, uint8_t listSize, bool substituteMissing);
void extractFromKamstrup(HanReader& hanReader, uint8_t listSize, bool substituteMissing);
void extractFromOmnipower(HanReader& hanReader, uint8_t listSize, bool substituteMissing);
};
#endif

View File

@@ -5,6 +5,8 @@
#define INVALID_BUTTON_PIN 0xFFFFFFFF
#define EPOCH_2021_01_01 1609459200
#include <SoftwareSerial.h>
#if defined(ESP8266)

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +0,0 @@
#include "HanToJson.h"
void hanToJson(JsonDocument& json, AmsData& data, HwTools& hw, double temperature, String name) {
json["id"] = WiFi.macAddress();
json["name"] = name;
json["up"] = millis();
json["t"] = data.getPackageTimestamp();
double vcc = hw.getVcc();
if(vcc > 0) {
json["vcc"] = serialized(String(vcc, 3));
}
json["rssi"] = hw.getWifiRssi();
if(temperature != DEVICE_DISCONNECTED_C) {
json["temp"] = serialized(String(temperature, 2));
}
// Add a sub-structure to the json object,
// to keep the data from the meter itself
JsonObject jd = json.createNestedObject("data");
switch(data.getListType()) {
case 3:
jd["rtc"] = data.getMeterTimestamp();
jd["tPI"] = data.getActiveImportCounter();
jd["tPO"] = data.getActiveExportCounter();
jd["tQI"] = data.getReactiveImportCounter();
jd["tQO"] = data.getReactiveExportCounter();
case 2:
jd["lv"] = data.getListId();
jd["id"] = data.getMeterId();
jd["type"] = data.getMeterType();
jd["Q"] = data.getReactiveImportPower();
jd["PO"] = data.getActiveExportPower();
jd["QO"] = data.getReactiveExportPower();
jd["I1"] = data.getL1Current();
jd["I2"] = data.getL2Current();
jd["I3"] = data.getL3Current();
jd["U1"] = data.getL1Voltage();
jd["U2"] = data.getL2Voltage();
jd["U3"] = data.getL3Voltage();
case 1:
jd["P"] = data.getActiveImportPower();
}
}

View File

@@ -1,16 +0,0 @@
#ifndef _HANTOJSON_h
#define _HANTOJSON_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <ArduinoJson.h>
#include "AmsData.h"
#include "HwTools.h"
void hanToJson(JsonDocument& json, AmsData& data, HwTools& hw, double temperature, String name);
#endif

View File

@@ -1,41 +1,67 @@
#include "HwTools.h"
void HwTools::setTempSensorPin(int tempSensorPin) {
if(tempSensorPin != this->tempSensorPin) {
this->tempSensorInit = false;
if(tempSensor)
delete tempSensor;
if(oneWire)
delete oneWire;
if(tempSensorPin > 0 && tempSensorPin < 40) {
this->tempSensorPin = tempSensorPin;
pinMode(tempSensorPin, INPUT);
} else {
this->tempSensorPin = 0xFF;
}
}
}
void HwTools::setVccPin(int vccPin) {
if(vccPin > 0 && vccPin < 40) {
pinMode(vccPin, INPUT);
this->vccPin = vccPin;
void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
this->config = config;
this->amsConf = amsConf;
this->tempSensorInit = false;
if(sensorApi != NULL)
delete sensorApi;
if(oneWire != NULL)
delete oneWire;
if(config->tempSensorPin > 0 && config->tempSensorPin < 40) {
pinMode(config->tempSensorPin, INPUT);
} else {
this->vccPin = 0xFF;
config->tempSensorPin = 0xFF;
}
}
void HwTools::setVccMultiplier(double vccMultiplier) {
this->vccMultiplier = vccMultiplier;
if(config->tempAnalogSensorPin > 0 && config->tempAnalogSensorPin < 40) {
pinMode(config->tempAnalogSensorPin, INPUT);
} else {
config->tempAnalogSensorPin = 0xFF;
}
if(config->vccPin > 0 && config->vccPin < 40) {
pinMode(config->vccPin, INPUT);
} else {
config->vccPin = 0xFF;
}
if(config->ledPin > 0 && config->ledPin < 40) {
pinMode(config->ledPin, OUTPUT);
ledOff(LED_INTERNAL);
} else {
config->ledPin = 0xFF;
}
if(config->ledPinRed > 0 && config->ledPinRed < 40) {
pinMode(config->ledPinRed, OUTPUT);
ledOff(LED_RED);
} else {
config->ledPinRed = 0xFF;
}
if(config->ledPinGreen > 0 && config->ledPinGreen < 40) {
pinMode(config->ledPinGreen, OUTPUT);
ledOff(LED_GREEN);
} else {
config->ledPinGreen = 0xFF;
}
if(config->ledPinBlue > 0 && config->ledPinBlue < 40) {
pinMode(config->ledPinBlue, OUTPUT);
ledOff(LED_BLUE);
} else {
config->ledPinBlue = 0xFF;
}
}
double HwTools::getVcc() {
double volts = 0.0;
if(vccPin != 0xFF) {
if(config->vccPin != 0xFF) {
#if defined(ESP8266)
volts = (analogRead(vccPin) / 1024.0) * 3.3;
volts = (analogRead(config->vccPin) / 1024.0) * 3.3;
#elif defined(ESP32)
volts = (analogRead(vccPin) / 4095.0) * 3.3;
volts = (analogRead(config->vccPin) / 4095.0) * 3.3;
#endif
} else {
#if defined(ESP8266)
@@ -43,29 +69,122 @@ double HwTools::getVcc() {
#endif
}
return volts > 0.0 ? volts * vccMultiplier : 0.0;
float vccOffset = config->vccOffset / 100.0;
float vccMultiplier = config->vccMultiplier / 1000.0;
return vccOffset + (volts > 0.0 ? volts * vccMultiplier : 0.0);
}
uint8_t HwTools::getTempSensorCount() {
return sensorCount;
}
TempSensorData* HwTools::getTempSensorData(uint8_t i) {
if(i < sensorCount) {
return tempSensors[i];
}
return NULL;
}
bool HwTools::updateTemperatures() {
if(config->tempSensorPin != 0xFF) {
if(!tempSensorInit) {
oneWire = new OneWire(config->tempSensorPin);
sensorApi = new DallasTemperature(this->oneWire);
sensorApi->begin();
delay(100);
tempSensorInit = true;
DeviceAddress addr;
sensorApi->requestTemperatures();
int c = sensorApi->getDeviceCount();
if(this->tempSensors != NULL) {
delete this->tempSensors;
}
this->tempSensors = new TempSensorData*[c];
for(int i = 0; i < c; i++) {
bool found = false;
sensorApi->getAddress(addr, i);
float t = sensorApi->getTempC(addr);
for(int x = 0; x < sensorCount; x++) {
TempSensorData *data = tempSensors[x];
if(isSensorAddressEqual(data->address, addr)) {
found = true;
data->lastRead = t;
if(t > -85) {
data->changed = data->lastValidRead != t;
data->lastValidRead = t;
}
}
}
if(!found) {
TempSensorData *data = new TempSensorData();
memcpy(data->address, addr, 8);
data->lastRead = t;
if(t > -85) {
data->changed = data->lastValidRead != t;
data->lastValidRead = t;
}
tempSensors[sensorCount++] = data;
}
delay(10);
}
} else {
if(sensorCount > 0) {
sensorApi->requestTemperatures();
for(int x = 0; x < sensorCount; x++) {
TempSensorData *data = tempSensors[x];
float t = sensorApi->getTempC(data->address);
data->lastRead = t;
if(t > -85) {
data->changed = data->lastValidRead != t;
data->lastValidRead = t;
}
}
}
}
return true;
}
return false;
}
bool HwTools::isSensorAddressEqual(uint8_t a[8], uint8_t b[8]) {
for(int i = 0; i < 8; i++) {
if(a[i] != b[i]) return false;
}
return true;
}
double HwTools::getTemperature() {
if(tempSensorPin != 0xFF) {
if(!tempSensorInit) {
oneWire = new OneWire(tempSensorPin);
tempSensor = new DallasTemperature(this->oneWire);
tempSensor->begin();
delay(50);
tempSensor->requestTemperatures();
hasTempSensor = tempSensor->getTempCByIndex(0) != DEVICE_DISCONNECTED_C;
tempSensorInit = true;
}
if(hasTempSensor) {
tempSensor->requestTemperatures();
return tempSensor->getTempCByIndex(0);
} else {
return DEVICE_DISCONNECTED_C;
uint8_t c = 0;
double ret = 0;
double analogTemp = getTemperatureAnalog();
if(analogTemp != DEVICE_DISCONNECTED_C) {
ret += analogTemp;
c++;
}
for(int x = 0; x < sensorCount; x++) {
TempSensorData data = *tempSensors[x];
TempSensorConfig* conf = amsConf->getTempSensorConfig(data.address);
if((conf == NULL || conf->common) && data.lastValidRead > -85) {
ret += data.lastValidRead;
c++;
}
}
return c == 0 ? DEVICE_DISCONNECTED_C : ret/c;
}
double HwTools::getTemperatureAnalog() {
if(config->tempAnalogSensorPin != 0xFF) {
float adcCalibrationFactor = 1.06587;
int volts;
#if defined(ESP8266)
volts = (analogRead(config->tempAnalogSensorPin) / 1024.0) * 3.3;
#elif defined(ESP32)
volts = (analogRead(config->tempAnalogSensorPin) / 4095.0) * 3.3;
#endif
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
}
return DEVICE_DISCONNECTED_C;
}
@@ -74,55 +193,19 @@ int HwTools::getWifiRssi() {
return isnan(rssi) ? -100.0 : rssi;
}
void HwTools::setLed(uint8_t ledPin, bool ledInverted) {
if(ledPin > 0 && ledPin < 40) {
this->ledPin = ledPin;
this->ledInverted = ledInverted;
pinMode(ledPin, OUTPUT);
ledOff(LED_INTERNAL);
} else {
this->ledPin = 0xFF;
}
}
void HwTools::setLedRgb(uint8_t ledPinRed, uint8_t ledPinGreen, uint8_t ledPinBlue, bool ledRgbInverted) {
this->ledRgbInverted = ledRgbInverted;
if(ledPinRed > 0 && ledPinRed < 40) {
this->ledPinRed = ledPinRed;
pinMode(ledPinRed, OUTPUT);
ledOff(LED_RED);
} else {
this->ledPinRed = 0xFF;
}
if(ledPinGreen > 0 && ledPinGreen < 40) {
this->ledPinGreen = ledPinGreen;
pinMode(ledPinGreen, OUTPUT);
ledOff(LED_GREEN);
} else {
this->ledPinGreen = 0xFF;
}
if(ledPinBlue > 0 && ledPinBlue < 40) {
this->ledPinBlue = ledPinBlue;
pinMode(ledPinBlue, OUTPUT);
ledOff(LED_BLUE);
} else {
this->ledPinBlue = 0xFF;
}
}
bool HwTools::ledOn(uint8_t color) {
if(color == LED_INTERNAL) {
return writeLedPin(color, ledInverted ? LOW : HIGH);
return writeLedPin(color, config->ledInverted ? LOW : HIGH);
} else {
return writeLedPin(color, ledRgbInverted ? LOW : HIGH);
return writeLedPin(color, config->ledRgbInverted ? LOW : HIGH);
}
}
bool HwTools::ledOff(uint8_t color) {
if(color == LED_INTERNAL) {
return writeLedPin(color, ledInverted ? HIGH : LOW);
return writeLedPin(color, config->ledInverted ? HIGH : LOW);
} else {
return writeLedPin(color, ledRgbInverted ? HIGH : LOW);
return writeLedPin(color, config->ledRgbInverted ? HIGH : LOW);
}
}
@@ -138,47 +221,52 @@ bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
switch(color) {
case LED_INTERNAL:
if(ledPin != 0xFF) {
digitalWrite(ledPin, state);
case LED_INTERNAL: {
if(config->ledPin != 0xFF) {
digitalWrite(config->ledPin, state);
return true;
} else {
return false;
}
break;
case LED_RED:
if(ledPinRed != 0xFF) {
digitalWrite(ledPinRed, state);
}
case LED_RED: {
if(config->ledPinRed != 0xFF) {
digitalWrite(config->ledPinRed, state);
return true;
} else {
return false;
}
break;
case LED_GREEN:
if(ledPinGreen != 0xFF) {
digitalWrite(ledPinGreen, state);
}
case LED_GREEN: {
if(config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinGreen, state);
return true;
} else {
return false;
}
break;
case LED_BLUE:
if(ledPinBlue != 0xFF) {
digitalWrite(ledPinBlue, state);
}
case LED_BLUE: {
if(config->ledPinBlue != 0xFF) {
digitalWrite(config->ledPinBlue, state);
return true;
} else {
return false;
}
break;
case LED_YELLOW:
if(ledPinRed != 0xFF && ledPinGreen != 0xFF) {
digitalWrite(ledPinRed, state);
digitalWrite(ledPinGreen, state);
}
case LED_YELLOW: {
if(config->ledPinRed != 0xFF && config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinRed, state);
digitalWrite(config->ledPinGreen, state);
return true;
} else {
return false;
}
break;
}
}
return false;
}

View File

@@ -11,6 +11,7 @@
#include <DallasTemperature.h>
#include <OneWire.h>
#include "AmsConfiguration.h"
#define LED_INTERNAL 0
#define LED_RED 1
@@ -18,32 +19,40 @@
#define LED_BLUE 3
#define LED_YELLOW 4
struct TempSensorData {
uint8_t address[8];
float lastRead;
float lastValidRead;
bool changed;
};
class HwTools {
public:
void setTempSensorPin(int tempSensorPin);
void setVccPin(int vccPin);
void setVccMultiplier(double vccMultiplier);
void setup(GpioConfig*, AmsConfiguration*);
double getVcc();
uint8_t getTempSensorCount();
TempSensorData* getTempSensorData(uint8_t);
bool updateTemperatures();
double getTemperature();
double getTemperatureAnalog();
double getTemperature(uint8_t address[8]);
int getWifiRssi();
void setLed(uint8_t ledPin, bool ledInverted);
void setLedRgb(uint8_t ledPinRed, uint8_t ledPinGreen, uint8_t ledPinBlue, bool ledRgbInverted);
bool ledOn(uint8_t color);
bool ledOff(uint8_t color);
bool ledBlink(uint8_t color, uint8_t blink);
HwTools() {};
private:
uint8_t tempSensorPin = -1;
uint8_t vccPin = -1;
uint8_t ledPin = -1, ledPinRed = -1, ledPinGreen = -1, ledPinBlue = -1;
bool ledInverted, ledRgbInverted;
double vccMultiplier = 1.0;
bool tempSensorInit, hasTempSensor;
OneWire *oneWire;
DallasTemperature *tempSensor;
GpioConfig* config;
AmsConfiguration* amsConf;
bool tempSensorInit;
OneWire *oneWire = NULL;
DallasTemperature *sensorApi = NULL;
uint8_t sensorCount = 0;
TempSensorData** tempSensors = NULL;
bool writeLedPin(uint8_t color, uint8_t state);
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
};
#endif

View File

@@ -0,0 +1,60 @@
#include "DnbCurrParser.h"
#include "HardwareSerial.h"
float DnbCurrParser::getValue() {
return value;
}
int DnbCurrParser::available() {
return 0;
}
int DnbCurrParser::read() {
return 0;
}
int DnbCurrParser::peek() {
return 0;
}
void DnbCurrParser::flush() {
}
size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
for(int i = 0; i < size; i++) {
write(buffer[i]);
}
return size;
}
size_t DnbCurrParser::write(uint8_t byte) {
if(pos == 0) {
if(byte == '<') {
buf[pos++] = byte;
}
} else if(byte == '>') {
buf[pos++] = byte;
if(strncmp(buf, "<Obs", 4) == 0) {
for(int i = 0; i < pos; i++) {
if(strncmp(buf+i, "OBS_VALUE=\"", 11) == 0) {
pos = i + 11;
break;
}
}
for(int i = 0; i < 16; i++) {
uint8_t b = buf[pos+i];
if(b == '"') {
buf[pos+i] = '\0';
break;
}
}
value = String(buf+pos).toFloat();
}
pos = 0;
} else {
buf[pos++] = byte;
}
return 1;
}

View File

@@ -0,0 +1,25 @@
#ifndef _DNBCURRPARSER_H
#define _DNBCURRPARSER_H
#include "Stream.h"
class DnbCurrParser: public Stream {
public:
float getValue();
int available();
int read();
int peek();
void flush();
size_t write(const uint8_t *buffer, size_t size);
size_t write(uint8_t);
private:
float value = 1.0;
char buf[64];
uint8_t pos = 0;
uint8_t mode = 0;
};
#endif

View File

@@ -0,0 +1,102 @@
#include "EntsoeA44Parser.h"
#include "HardwareSerial.h"
EntsoeA44Parser::EntsoeA44Parser() {
for(int i = 0; i < 24; i++) points[i] = 0.0;
}
char* EntsoeA44Parser::getCurrency() {
return currency;
}
char* EntsoeA44Parser::getMeasurementUnit() {
return measurementUnit;
}
float EntsoeA44Parser::getPoint(uint8_t position) {
return points[position];
}
int EntsoeA44Parser::available() {
return 0;
}
int EntsoeA44Parser::read() {
return 0;
}
int EntsoeA44Parser::peek() {
return 0;
}
void EntsoeA44Parser::flush() {
}
size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
for(int i = 0; i < size; i++) {
write(buffer[i]);
}
return size;
}
size_t EntsoeA44Parser::write(uint8_t byte) {
if(docPos == DOCPOS_CURRENCY) {
buf[pos++] = byte;
if(pos == 3) {
buf[pos++] = '\0';
memcpy(currency, buf, pos);
docPos = DOCPOS_SEEK;
pos = 0;
}
} else if(docPos == DOCPOS_MEASUREMENTUNIT) {
buf[pos++] = byte;
if(pos == 3) {
buf[pos++] = '\0';
memcpy(measurementUnit, buf, pos);
docPos = DOCPOS_SEEK;
pos = 0;
}
} else if(docPos == DOCPOS_POSITION) {
if(byte == '<') {
buf[pos] = '\0';
pointNum = String(buf).toInt() - 1;
docPos = DOCPOS_SEEK;
pos = 0;
} else {
buf[pos++] = byte;
}
} else if(docPos == DOCPOS_AMOUNT) {
if(byte == '<') {
buf[pos] = '\0';
points[pointNum] = String(buf).toFloat();
docPos = DOCPOS_SEEK;
pos = 0;
} else {
buf[pos++] = byte;
}
} else {
if(pos == 0) {
if(byte == '<') {
buf[pos++] = byte;
}
} else if(byte == '>') {
buf[pos++] = byte;
buf[pos] = '\0';
if(strcmp(buf, "<currency_Unit.name>") == 0) {
docPos = DOCPOS_CURRENCY;
} else if(strcmp(buf, "<price_Measure_Unit.name>") == 0) {
docPos = DOCPOS_MEASUREMENTUNIT;
} else if(strcmp(buf, "<position>") == 0) {
docPos = DOCPOS_POSITION;
pointNum = 0xFF;
} else if(strcmp(buf, "<price.amount>") == 0) {
docPos = DOCPOS_AMOUNT;
}
pos = 0;
} else {
buf[pos++] = byte;
}
}
return 1;
}

View File

@@ -0,0 +1,38 @@
#ifndef _ENTSOEA44PARSER_H
#define _ENTSOEA44PARSER_H
#include "Stream.h"
#define DOCPOS_SEEK 0
#define DOCPOS_CURRENCY 1
#define DOCPOS_MEASUREMENTUNIT 2
#define DOCPOS_POSITION 3
#define DOCPOS_AMOUNT 4
class EntsoeA44Parser: public Stream {
public:
EntsoeA44Parser();
char* getCurrency();
char* getMeasurementUnit();
float getPoint(uint8_t position);
int available();
int read();
int peek();
void flush();
size_t write(const uint8_t *buffer, size_t size);
size_t write(uint8_t);
private:
char currency[4];
char measurementUnit[4];
float points[24];
char buf[64];
uint8_t pos = 0;
uint8_t docPos = 0;
uint8_t pointNum = 0;
};
#endif

282
src/entsoe/EntsoeApi.cpp Normal file
View File

@@ -0,0 +1,282 @@
#include "EntsoeApi.h"
#include <EEPROM.h>
#include "Uptime.h"
#include "Time.h"
#include "DnbCurrParser.h"
#if defined(ESP8266)
#include <ESP8266HTTPClient.h>
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
#include <HTTPClient.h>
#else
#warning "Unsupported board type"
#endif
EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
debugger = Debug;
// Entso-E uses CET/CEST
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
tz = new Timezone(CEST, CET);
}
void EntsoeApi::setup(EntsoeConfig& config) {
if(this->config == NULL) {
this->config = new EntsoeConfig();
}
memcpy(this->config, &config, sizeof(config));
}
char* EntsoeApi::getToken() {
return this->config->token;
}
float EntsoeApi::getValueForHour(uint8_t hour) {
time_t cur = time(nullptr);
return getValueForHour(cur, hour);
}
float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
tmElements_t tm;
if(tz != NULL)
cur = tz->toLocal(cur);
breakTime(cur, tm);
int pos = tm.Hour + hour;
if(pos >= 48)
return ENTSOE_NO_VALUE;
double value = ENTSOE_NO_VALUE;
double multiplier = config->multiplier / 1000.0;
if(pos > 23) {
if(tomorrow == NULL)
return ENTSOE_NO_VALUE;
value = tomorrow->getPoint(pos-24);
if(strcmp(tomorrow->getMeasurementUnit(), "MWH") == 0) {
multiplier *= 0.001;
} else {
return ENTSOE_NO_VALUE;
}
multiplier *= getCurrencyMultiplier(tomorrow->getCurrency(), config->currency);
} else {
if(today == NULL)
return ENTSOE_NO_VALUE;
value = today->getPoint(pos);
if(strcmp(today->getMeasurementUnit(), "MWH") == 0) {
multiplier *= 0.001;
} else {
return ENTSOE_NO_VALUE;
}
multiplier *= getCurrencyMultiplier(today->getCurrency(), config->currency);
}
return value * multiplier;
}
bool EntsoeApi::loop() {
if(strlen(config->token) == 0)
return false;
bool ret = false;
uint64_t now = millis64();
if(now < 10000) return false; // Grace period
if(midnightMillis == 0) {
time_t t = time(nullptr);
if(t <= 0) return false; // NTP not ready
time_t epoch = tz->toLocal(t);
tmElements_t tm;
breakTime(epoch, tm);
if(tm.Year > 50) { // Make sure we are in 2021 or later (years after 1970)
uint64_t curDeviceMillis = millis64();
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
midnightMillis = curDeviceMillis + (SECS_PER_DAY * 1000) - curDayMillis;
printI("Setting midnight millis " + String((uint32_t) midnightMillis));
}
} else if(now > midnightMillis) {
printI("Rotating price objects");
delete today;
today = tomorrow;
tomorrow = NULL;
midnightMillis = 0; // Force new midnight millis calculation
} else {
if(today == NULL && (lastTodayFetch == 0 || now - lastTodayFetch > 60000)) {
lastTodayFetch = now;
time_t e1 = time(nullptr) - (SECS_PER_DAY * 1);
time_t e2 = e1 + SECS_PER_DAY;
tmElements_t d1, d2;
breakTime(e1, d1);
breakTime(e2, d2);
char url[256];
snprintf(url, sizeof(url), "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
"https://transparency.entsoe.eu/api", config->token,
d1.Year+1970, d1.Month, d1.Day, 23, 00,
d2.Year+1970, d2.Month, d2.Day, 23, 00,
config->area, config->area);
printI("Fetching prices for today");
printD(url);
EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(url, a44)) {
today = a44;
ret = true;
} else {
delete a44;
today = NULL;
}
}
if(tomorrow == NULL
&& midnightMillis - now < 39600000
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 60000)
) {
lastTomorrowFetch = now;
time_t e1 = time(nullptr);
time_t e2 = e1 + SECS_PER_DAY;
tmElements_t d1, d2;
breakTime(e1, d1);
breakTime(e2, d2);
char url[256];
snprintf(url, sizeof(url), "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
"https://transparency.entsoe.eu/api", config->token,
d1.Year+1970, d1.Month, d1.Day, 23, 00,
d2.Year+1970, d2.Month, d2.Day, 23, 00,
config->area, config->area);
printI("Fetching prices for tomorrow");
printD(url);
EntsoeA44Parser* a44 = new EntsoeA44Parser();
if(retrieve(url, a44)) {
tomorrow = a44;
ret = true;
} else {
delete a44;
tomorrow = NULL;
}
}
}
return ret;
}
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
WiFiClientSecure client;
#if defined(ESP8266)
// https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/bearssl-client-secure-class.html#mfln-or-maximum-fragment-length-negotiation-saving-ram
/* Rumor has it that a client cannot request a lower max_fragment_length, so I guess thats why the following does not work.
And there is currently not enough heap space to go around in this project to do a full HTTPS request on ESP8266
int bufSize = 512;
while(!client.probeMaxFragmentLength("transparency.entsoe.eu", 443, bufSize) && bufSize <= 4096) {
bufSize += 512;
}
if(client.probeMaxFragmentLength("transparency.entsoe.eu", 443, bufSize)) {
printD("Negotiated MFLN size");
printD(String(bufSize));
client.setBufferSizes(bufSize, bufSize);
}
*/
client.setInsecure();
#endif
HTTPClient https;
#if defined(ESP8266)
https.setFollowRedirects(true);
#endif
if(https.begin(client, url)) {
printD("Connection established");
/*
#if defined(ESP8266)
if(!client.getMFLNStatus()) {
printE("Negotiated MFLN was not respected");
https.end();
client.stop();
return false;
}
#endif
*/
int status = https.GET();
if(status == HTTP_CODE_OK) {
printD("Receiving data");
https.writeToStream(doc);
https.end();
return true;
} else {
printE("Communication error: ");
printE(https.errorToString(status));
printD(https.getString());
#if defined(ESP8266)
char buf[64];
client.getLastSSLError(buf,64);
printE(buf);
#endif
https.end();
return false;
}
} else {
#if defined(ESP8266)
char buf[64];
client.getLastSSLError(buf,64);
printE(buf);
#endif
return false;
}
client.stop();
}
float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
if(strcmp(from, to) == 0)
return 1.00;
uint64_t now = millis64();
if(lastCurrencyFetch == 0 || now - lastCurrencyFetch > (SECS_PER_HOUR * 1000)) {
char url[256];
snprintf(url, sizeof(url), "https://data.norges-bank.no/api/data/EXR/M.%s.%s.SP?lastNObservations=1",
from,
to
);
DnbCurrParser p;
if(retrieve(url, &p)) {
currencyMultiplier = p.getValue();
}
lastCurrencyFetch = now;
}
return currencyMultiplier;
}
void EntsoeApi::printD(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}
void EntsoeApi::printI(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}
void EntsoeApi::printW(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}
void EntsoeApi::printE(String fmt, ...) {
va_list args;
va_start(args, fmt);
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
va_end(args);
}

47
src/entsoe/EntsoeApi.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef _ENTSOEAPI_H
#define _ENTSOEAPI_H
#include "time.h"
#include "Timezone.h"
#include "RemoteDebug.h"
#include "EntsoeA44Parser.h"
#include "AmsConfiguration.h"
#define ENTSOE_NO_VALUE -127
#define ENTSOE_DEFAULT_MULTIPLIER 1.00
#define SSL_BUF_SIZE 512
class EntsoeApi {
public:
EntsoeApi(RemoteDebug*);
void setup(EntsoeConfig&);
bool loop();
char* getToken();
float getValueForHour(uint8_t);
float getValueForHour(time_t, uint8_t);
private:
RemoteDebug* debugger;
EntsoeConfig* config = NULL;
uint64_t midnightMillis = 0;
uint64_t lastTodayFetch = 0;
uint64_t lastTomorrowFetch = 0;
uint64_t lastCurrencyFetch = 0;
EntsoeA44Parser* today = NULL;
EntsoeA44Parser* tomorrow = NULL;
Timezone* tz = NULL;
float currencyMultiplier = ENTSOE_DEFAULT_MULTIPLIER;
bool retrieve(const char* url, Stream* doc);
float getCurrencyMultiplier(const char* from, const char* to);
void printD(String fmt, ...);
void printI(String fmt, ...);
void printW(String fmt, ...);
void printE(String fmt, ...);
};
#endif

23
src/hexutils.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "hexutils.h";
String toHex(uint8_t* in) {
return toHex(in, sizeof(in)*2);
}
String toHex(uint8_t* in, uint8_t size) {
String hex;
for(int i = 0; i < size; i++) {
if(in[i] < 0x10) {
hex += '0';
}
hex += String(in[i], HEX);
}
hex.toUpperCase();
return hex;
}
void fromHex(uint8_t *out, String in, uint8_t size) {
for(int i = 0; i < size*2; i += 2) {
out[i/2] = strtol(in.substring(i, i+2).c_str(), 0, 16);
}
}

11
src/hexutils.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef _HEXUTILS_H
#define _HEXUTILS_H
#include <stdint.h>
#include "Arduino.h"
String toHex(uint8_t* in);
String toHex(uint8_t* in, uint8_t size);
void fromHex(uint8_t *out, String in, uint8_t size);
#endif

26
src/mqtt/AmsMqttHandler.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef _AMSMQTTHANDLER_H
#define _AMSMQTTHANDLER_H
#include "Arduino.h"
#include <MQTT.h>
#include "AmsData.h"
#include "AmsConfiguration.h"
#include "HwTools.h"
#include "entsoe/EntsoeApi.h"
class AmsMqttHandler {
public:
AmsMqttHandler(MQTTClient* mqtt) {
this->mqtt = mqtt;
};
virtual bool publish(AmsData* data, AmsData* previousState);
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
virtual bool publishPrices(EntsoeApi* eapi);
virtual bool publishSystem(HwTools*);
protected:
MQTTClient* mqtt;
};
#endif

View File

@@ -0,0 +1,89 @@
#include "DomoticzMqttHandler.h"
#include "web/root/domoticz_json.h"
bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState) {
bool ret = false;
if (config.elidx > 0) {
if(data->getActiveImportCounter() > 1.0) {
energy = data->getActiveImportCounter();
}
if(energy > 0.0) {
char val[16];
snprintf(val, 16, "%.1f;%.1f", (data->getActiveImportPower()/1.0), energy*1000.0);
char json[192];
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.elidx,
val
);
ret = mqtt->publish("domoticz/in", json);
}
}
if(data->getListType() == 1)
return ret;
if (config.vl1idx > 0){
if (data->getL1Voltage() > 0.1){
char val[16];
snprintf(val, 16, "%.2f", data->getL1Voltage());
char json[192];
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.vl1idx,
val
);
ret |= mqtt->publish("domoticz/in", json);
}
}
if (config.vl2idx > 0){
if (data->getL2Voltage() > 0.1){
char val[16];
snprintf(val, 16, "%.2f", data->getL2Voltage());
char json[192];
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.vl2idx,
val
);
ret |= mqtt->publish("domoticz/in", json);
}
}
if (config.vl3idx > 0){
if (data->getL3Voltage() > 0.1){
char val[16];
snprintf(val, 16, "%.2f", data->getL3Voltage());
char json[192];
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.vl3idx,
val
);
ret |= mqtt->publish("domoticz/in", json);
}
}
if (config.cl1idx > 0){
if(data->getL1Current() > 0.0) {
char val[16];
snprintf(val, 16, "%.1f;%.1f;%.1f", data->getL1Current(), data->getL2Current(), data->getL3Current());
char json[192];
snprintf_P(json, sizeof(json), DOMOTICZ_JSON,
config.cl1idx,
val
);
ret |= mqtt->publish("domoticz/in", json);
}
}
return ret;
}
bool DomoticzMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
return false;
}
bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
return false;
}
bool DomoticzMqttHandler::publishSystem(HwTools* hw) {
return false;
}

View File

@@ -0,0 +1,21 @@
#ifndef _DOMOTICZMQTTHANDLER_H
#define _DOMOTICZMQTTHANDLER_H
#include "AmsMqttHandler.h"
#include "AmsConfiguration.h"
class DomoticzMqttHandler : public AmsMqttHandler {
public:
DomoticzMqttHandler(MQTTClient* mqtt, DomoticzConfig config) : AmsMqttHandler(mqtt) {
this->config = config;
};
bool publish(AmsData* data, AmsData* previousState);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
private:
DomoticzConfig config;
int energy = 0.0;
};
#endif

View File

@@ -0,0 +1,233 @@
#include "JsonMqttHandler.h"
#include "hexutils.h"
#include "Uptime.h"
#include "web/root/json1_json.h"
#include "web/root/json2_json.h"
#include "web/root/json3_json.h"
#include "web/root/jsonsys_json.h"
#include "web/root/jsonprices_json.h"
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState) {
if(topic.isEmpty() || !mqtt->connected())
return false;
if(data->getListType() == 1) {
char json[192];
snprintf_P(json, sizeof(json), JSON1_JSON,
WiFi.macAddress().c_str(),
clientId.c_str(),
(uint32_t) (millis64()/1000),
data->getPackageTimestamp(),
hw->getVcc(),
hw->getWifiRssi(),
hw->getTemperature(),
data->getActiveImportPower()
);
return mqtt->publish(topic, json);
} else if(data->getListType() == 2) {
char json[384];
snprintf_P(json, sizeof(json), JSON2_JSON,
WiFi.macAddress().c_str(),
clientId.c_str(),
(uint32_t) (millis64()/1000),
data->getPackageTimestamp(),
hw->getVcc(),
hw->getWifiRssi(),
hw->getTemperature(),
data->getListId().c_str(),
data->getMeterId().c_str(),
data->getMeterModel().c_str(),
data->getActiveImportPower(),
data->getReactiveImportPower(),
data->getActiveExportPower(),
data->getReactiveExportPower(),
data->getL1Current(),
data->getL2Current(),
data->getL3Current(),
data->getL1Voltage(),
data->getL2Voltage(),
data->getL3Voltage()
);
return mqtt->publish(topic, json);
} else if(data->getListType() == 3) {
char json[512];
snprintf_P(json, sizeof(json), JSON3_JSON,
WiFi.macAddress().c_str(),
clientId.c_str(),
(uint32_t) (millis64()/1000),
data->getPackageTimestamp(),
hw->getVcc(),
hw->getWifiRssi(),
hw->getTemperature(),
data->getListId().c_str(),
data->getMeterId().c_str(),
data->getMeterModel().c_str(),
data->getActiveImportPower(),
data->getReactiveImportPower(),
data->getActiveExportPower(),
data->getReactiveExportPower(),
data->getL1Current(),
data->getL2Current(),
data->getL3Current(),
data->getL1Voltage(),
data->getL2Voltage(),
data->getL3Voltage(),
data->getActiveImportCounter(),
data->getActiveExportCounter(),
data->getReactiveImportCounter(),
data->getReactiveExportCounter(),
data->getMeterTimestamp()
);
return mqtt->publish(topic, json);
}
}
bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
int count = hw->getTempSensorCount();
if(count == 0)
return false;
int size = 32 + (count * 26);
char buf[size];
snprintf(buf, 24, "{\"temperatures\":{");
for(int i = 0; i < count; i++) {
TempSensorData* data = hw->getTempSensorData(i);
if(data != NULL) {
char* pos = buf+strlen(buf);
snprintf(pos, 26, "\"%s\":%.2f,",
toHex(data->address, 8).c_str(),
data->lastRead
);
data->changed = false;
delay(1);
}
}
char* pos = buf+strlen(buf);
snprintf(count == 0 ? pos : pos-1, 8, "}}");
return mqtt->publish(topic, buf);
}
bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
if(topic.isEmpty() || !mqtt->connected())
return false;
if(strcmp(eapi->getToken(), "") == 0)
return false;
time_t now = time(nullptr);
float min1hr, min3hr, min6hr;
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
float min = INT16_MAX, max = INT16_MIN;
float values[24] = {0};
for(uint8_t i = 0; i < 24; i++) {
float val = eapi->getValueForHour(now, i);
values[i] = val;
if(val == ENTSOE_NO_VALUE) break;
if(val < min) min = val;
if(val > max) max = val;
if(min1hrIdx == -1 || min1hr > val) {
min1hr = val;
min1hrIdx = i;
}
if(i >= 2) {
i -= 2;
float val1 = values[i++];
float val2 = values[i++];
float val3 = val;
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
float val3hr = val1+val2+val3;
if(min3hrIdx == -1 || min3hr > val3hr) {
min3hr = val3hr;
min3hrIdx = i-2;
}
}
if(i >= 5) {
i -= 5;
float val1 = values[i++];
float val2 = values[i++];
float val3 = values[i++];
float val4 = values[i++];
float val5 = values[i++];
float val6 = val;
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
float val6hr = val1+val2+val3+val4+val5+val6;
if(min6hrIdx == -1 || min6hr > val6hr) {
min6hr = val6hr;
min6hrIdx = i-5;
}
}
}
char ts1hr[21];
if(min1hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
tmElements_t tm;
breakTime(ts, tm);
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts3hr[21];
if(min3hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
tmElements_t tm;
breakTime(ts, tm);
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts6hr[21];
if(min6hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
tmElements_t tm;
breakTime(ts, tm);
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char json[384];
snprintf_P(json, sizeof(json), JSONPRICES_JSON,
WiFi.macAddress().c_str(),
values[0],
values[1],
values[2],
values[3],
values[4],
values[5],
values[6],
values[7],
values[8],
values[9],
values[10],
values[11],
min == INT16_MAX ? 0.0 : min,
max == INT16_MIN ? 0.0 : max,
ts1hr,
ts3hr,
ts6hr
);
return mqtt->publish(topic, json);
}
bool JsonMqttHandler::publishSystem(HwTools* hw) {
if(init || topic.isEmpty() || !mqtt->connected())
return false;
char json[192];
snprintf_P(json, sizeof(json), JSONSYS_JSON,
WiFi.macAddress().c_str(),
clientId.c_str(),
(uint32_t) (millis64()/1000),
hw->getVcc(),
hw->getWifiRssi(),
hw->getTemperature()
);
init = mqtt->publish(topic, json);
return init;
}

View File

@@ -0,0 +1,24 @@
#ifndef _JSONMQTTHANDLER_H
#define _JSONMQTTHANDLER_H
#include "AmsMqttHandler.h"
class JsonMqttHandler : public AmsMqttHandler {
public:
JsonMqttHandler(MQTTClient* mqtt, const char* clientId, const char* topic, HwTools* hw) : AmsMqttHandler(mqtt) {
this->clientId = clientId;
this->topic = String(topic);
this->hw = hw;
};
bool publish(AmsData* data, AmsData* previousState);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
private:
String clientId;
String topic;
HwTools* hw;
bool init = false;
};
#endif

207
src/mqtt/RawMqttHandler.cpp Normal file
View File

@@ -0,0 +1,207 @@
#include "RawMqttHandler.h"
#include "hexutils.h"
#include "Uptime.h"
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState) {
if(topic.isEmpty() || !mqtt->connected())
return false;
if(data->getPackageTimestamp() > 0) {
mqtt->publish(topic + "/meter/dlms/timestamp", String(data->getPackageTimestamp()));
}
switch(data->getListType()) {
case 3:
// ID and type belongs to List 2, but I see no need to send that every 10s
mqtt->publish(topic + "/meter/id", data->getMeterId(), true, 0);
mqtt->publish(topic + "/meter/type", data->getMeterModel(), true, 0);
mqtt->publish(topic + "/meter/clock", String(data->getMeterTimestamp()));
mqtt->publish(topic + "/meter/import/reactive/accumulated", String(data->getReactiveImportCounter(), 2), true, 0);
mqtt->publish(topic + "/meter/import/active/accumulated", String(data->getActiveImportCounter(), 2), true, 0);
mqtt->publish(topic + "/meter/export/reactive/accumulated", String(data->getReactiveExportCounter(), 2), true, 0);
mqtt->publish(topic + "/meter/export/active/accumulated", String(data->getActiveExportCounter(), 2), true, 0);
case 2:
// Only send data if changed. ID and Type is sent on the 10s interval only if changed
if(full || meterState->getMeterId() != data->getMeterId()) {
mqtt->publish(topic + "/meter/id", data->getMeterId());
}
if(full || meterState->getMeterModel() != data->getMeterModel()) {
mqtt->publish(topic + "/meter/type", data->getMeterModel());
}
if(full || meterState->getL1Current() != data->getL1Current()) {
mqtt->publish(topic + "/meter/l1/current", String(data->getL1Current(), 2));
}
if(full || meterState->getL1Voltage() != data->getL1Voltage()) {
mqtt->publish(topic + "/meter/l1/voltage", String(data->getL1Voltage(), 2));
}
if(full || meterState->getL2Current() != data->getL2Current()) {
mqtt->publish(topic + "/meter/l2/current", String(data->getL2Current(), 2));
}
if(full || meterState->getL2Voltage() != data->getL2Voltage()) {
mqtt->publish(topic + "/meter/l2/voltage", String(data->getL2Voltage(), 2));
}
if(full || meterState->getL3Current() != data->getL3Current()) {
mqtt->publish(topic + "/meter/l3/current", String(data->getL3Current(), 2));
}
if(full || meterState->getL3Voltage() != data->getL3Voltage()) {
mqtt->publish(topic + "/meter/l3/voltage", String(data->getL3Voltage(), 2));
}
if(full || meterState->getReactiveExportPower() != data->getReactiveExportPower()) {
mqtt->publish(topic + "/meter/export/reactive", String(data->getReactiveExportPower()));
}
if(full || meterState->getActiveExportPower() != data->getActiveExportPower()) {
mqtt->publish(topic + "/meter/export/active", String(data->getActiveExportPower()));
}
if(full || meterState->getReactiveImportPower() != data->getReactiveImportPower()) {
mqtt->publish(topic + "/meter/import/reactive", String(data->getReactiveImportPower()));
}
case 1:
if(full || meterState->getActiveImportPower() != data->getActiveImportPower()) {
mqtt->publish(topic + "/meter/import/active", String(data->getActiveImportPower()));
}
}
return true;
}
bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
uint8_t c = hw->getTempSensorCount();
for(int i = 0; i < c; i++) {
TempSensorData* data = hw->getTempSensorData(i);
if(data != NULL && data->lastValidRead > -85) {
if(data->changed || full) {
mqtt->publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2));
data->changed = false;
}
}
}
return c > 0;
}
bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
if(topic.isEmpty() || !mqtt->connected())
return false;
if(strcmp(eapi->getToken(), "") == 0)
return false;
time_t now = time(nullptr);
float min1hr, min3hr, min6hr;
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
float min = INT16_MAX, max = INT16_MIN;
float values[34] = {0};
for(uint8_t i = 0; i < 34; i++) {
float val = eapi->getValueForHour(now, i);
values[i] = val;
if(i > 23) continue;
if(val == ENTSOE_NO_VALUE) break;
if(val < min) min = val;
if(val > max) max = val;
if(min1hrIdx == -1 || min1hr > val) {
min1hr = val;
min1hrIdx = i;
}
if(i >= 2) {
i -= 2;
float val1 = values[i++];
float val2 = values[i++];
float val3 = val;
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
float val3hr = val1+val2+val3;
if(min3hrIdx == -1 || min3hr > val3hr) {
min3hr = val3hr;
min3hrIdx = i-2;
}
}
if(i >= 5) {
i -= 5;
float val1 = values[i++];
float val2 = values[i++];
float val3 = values[i++];
float val4 = values[i++];
float val5 = values[i++];
float val6 = val;
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
float val6hr = val1+val2+val3+val4+val5+val6;
if(min6hrIdx == -1 || min6hr > val6hr) {
min6hr = val6hr;
min6hrIdx = i-5;
}
}
}
char ts1hr[21];
if(min1hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
tmElements_t tm;
breakTime(ts, tm);
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts3hr[21];
if(min3hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
tmElements_t tm;
breakTime(ts, tm);
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts6hr[21];
if(min6hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
tmElements_t tm;
breakTime(ts, tm);
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
for(int i = 0; i < 34; i++) {
float val = values[i];
if(val == ENTSOE_NO_VALUE) {
mqtt->publish(topic + "/price/" + String(i), "", true, 0);
break;
} else {
mqtt->publish(topic + "/price/" + String(i), String(val, 4), true, 0);
}
mqtt->loop();
delay(10);
}
if(min != INT16_MAX) {
mqtt->publish(topic + "/price/min", String(min, 4), true, 0);
}
if(max != INT16_MIN) {
mqtt->publish(topic + "/price/max", String(max, 4), true, 0);
}
if(min1hrIdx != -1) {
mqtt->publish(topic + "/price/cheapest/1hr", String(ts1hr), true, 0);
}
if(min3hrIdx != -1) {
mqtt->publish(topic + "/price/cheapest/3hr", String(ts3hr), true, 0);
}
if(min6hrIdx != -1) {
mqtt->publish(topic + "/price/cheapest/6hr", String(ts6hr), true, 0);
}
return true;
}
bool RawMqttHandler::publishSystem(HwTools* hw) {
if(topic.isEmpty() || !mqtt->connected())
return false;
mqtt->publish(topic + "/id", WiFi.macAddress(), true, 0);
mqtt->publish(topic + "/uptime", String((unsigned long) millis64()/1000));
float vcc = hw->getVcc();
if(vcc > 0) {
mqtt->publish(topic + "/vcc", String(vcc, 2));
}
mqtt->publish(topic + "/rssi", String(hw->getWifiRssi()));
if(hw->getTemperature() > -85) {
mqtt->publish(topic + "/temperature", String(hw->getTemperature(), 2));
}
return true;
}

21
src/mqtt/RawMqttHandler.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef _RAWMQTTHANDLER_H
#define _RAWMQTTHANDLER_H
#include "AmsMqttHandler.h"
class RawMqttHandler : public AmsMqttHandler {
public:
RawMqttHandler(MQTTClient* mqtt, const char* topic, bool full) : AmsMqttHandler(mqtt) {
this->topic = String(topic);
this->full = full;
};
bool publish(AmsData* data, AmsData* previousState);
bool publishTemperatures(AmsConfiguration*, HwTools*);
bool publishPrices(EntsoeApi*);
bool publishSystem(HwTools*);
private:
String topic;
bool full;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -3,19 +3,14 @@
#define BOOTSTRAP_URL "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css"
#include "Arduino.h"
#include <MQTT.h>
#include <ArduinoJson.h>
#include "AmsConfiguration.h"
#include "HwTools.h"
#include "AmsData.h"
#include "Uptime.h"
#include "RemoteDebug.h"
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include "entsoe/EntsoeApi.h"
#if defined(ESP8266)
#include <ESP8266WiFi.h>
@@ -26,7 +21,6 @@
#include <WebServer.h>
#include <HTTPClient.h>
#include "SPIFFS.h"
#include "Update.h"
#else
#warning "Unsupported board type"
#endif
@@ -34,17 +28,24 @@
class AmsWebServer {
public:
AmsWebServer(RemoteDebug* Debug, HwTools* hw);
void setup(AmsConfiguration* config, MQTTClient* mqtt);
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, MQTTClient*);
void loop();
void setData(AmsData& data);
void setTimezone(Timezone* tz);
void setMqttEnabled(bool);
void setEntsoeApi(EntsoeApi* eapi);
private:
RemoteDebug* debugger;
bool mqttEnabled = false;
int maxPwr = 0;
HwTools* hw;
AmsConfiguration* config;
AmsData data;
Timezone* tz;
EntsoeApi* eapi = NULL;
AmsConfiguration* config;
GpioConfig* gpioConfig;
MeterConfig* meterConfig;
WebConfig webConfig;
AmsData* meterState;
MQTTClient* mqtt;
bool uploading = false;
File file;
@@ -60,11 +61,19 @@ private:
void indexHtml();
void applicationJs();
void temperature();
void temperaturePost();
void temperatureJson();
void price();
void configMeterHtml();
void configWifiHtml();
void configMqttHtml();
void configWebHtml();
void configDomoticzHtml();
void configEntsoeHtml();
void configNtpHtml();
void configGpioHtml();
void configDebugHtml();
void bootCss();
void gaugemeterJs();
void githubSvg();
@@ -73,11 +82,12 @@ private:
void handleSetup();
void handleSave();
void configSystemHtml();
String getSerialSelectOptions(int selected);
void firmwareHtml();
void firmwareUpload();
void firmwareDownload();
void restartHtml();
void restartPost();
void restartWaitHtml();
void isAliveCheck();

View File

@@ -1,7 +1,7 @@
var nextVersion;
var im, em;
var im, em, vm, am;
$(function() {
im = $("#importMeter");
im = $("#im");
if(im && im.gaugeMeter) {
im.gaugeMeter({
percent: 0,
@@ -10,7 +10,7 @@ $(function() {
});
}
em = $("#exportMeter");
em = $("#em");
if(em && em.gaugeMeter) {
em.gaugeMeter({
percent: 0,
@@ -18,6 +18,24 @@ $(function() {
append: "W"
});
}
vm = $("#vm");
if(vm && vm.gaugeMeter) {
vm.gaugeMeter({
percent: 0,
text: "-",
append: "V"
});
}
am = $("#am");
if(am && am.gaugeMeter) {
am.gaugeMeter({
percent: 0,
text: "-",
append: "A"
});
}
var meters = $('.SimpleMeter');
@@ -25,23 +43,23 @@ $(function() {
fetch();
}
// For config-mqtt
$('#mqttEnable').on('change', function() {
var inputs = $('.mqtt-config');
// For mqtt
$('#m').on('change', function() {
var inputs = $('.mc');
inputs.prop('disabled', !$(this).is(':checked'));
});
$('#mqttPayloadFormat').on('change', function() {
$('#f').on('change', function() {
var val = parseInt($(this).val());
if(val == 3) {
$('.format-type-domoticz').show();
$('.f3-s').show();
} else {
$('.format-type-domoticz').hide();
$('.f3-s').hide();
}
});
$('#mqttSsl').on('change', function() {
var port = $('#mqttPort');
$('#s').on('change', function() {
var port = $('#p');
if($(this).is(':checked')) {
if(port.val() == 1883) {
port.val(8883);
@@ -53,37 +71,45 @@ $(function() {
}
});
$('#mqttEnable').trigger('change');
$('#mqttPayloadFormat').trigger('change');
$('#m').trigger('change');
$('#f').trigger('change');
// For config-meter
$('.subtitute-dependent').on('change', function() {
console.log("test");
if($('#meterType').val() == 2 && $('#distributionSystem').val() == 1) {
$('#substitute').show();
// For meter
$('.sd').on('change', function() {
if(($('#mt').val() == 2 || $('#mt').val() == 3) && $('#d').val() == 1) {
$('#ss').show();
} else {
$('#substitute').hide();
$('#ss').hide();
}
});
$('#meterType').trigger('change');
// For config-wifi
$('#wifiIpType').on('change', function() {
$('#mt').on('change', function() {
if($('#mt').val() == 4) {
$('.enc').show();
} else {
$('.enc').hide();
}
});
$('#mt').trigger('change');
// For wifi
$('#st').on('change', function() {
if($(this).is(':checked')) {
$('#staticIp').show();
$('#i').show();
} else {
$('#staticIp').hide();
$('#i').hide();
}
});
$('#wifiIpType').trigger('change');
$('#st').trigger('change');
// For config-web
$('#authSecurity').on('change', function() {
var inputs = $('.auth-config');
// For web
$('#as').on('change', function() {
var inputs = $('.ac');
inputs.prop('disabled', $(this).val() == 0);
});
$('#authSecurity').trigger('change');
$('#as').trigger('change');
// For file upload
$('#fileUploadField').on('change',function(){
@@ -91,30 +117,42 @@ $(function() {
$(this).next('.custom-file-label').html(fileName);
})
// For NTP
$('#n').on('change', function() {
var inputs = $('.nc');
inputs.prop('disabled', !$(this).is(':checked'));
});
$('#n').trigger('change');
// Navbar
switch(window.location.pathname) {
case '/config-meter':
$('#config-meter-link').addClass('active');
case '/temperature':
$('#temp-link').addClass('active');
break;
case '/config-wifi':
$('#config-wifi-link').addClass('active');
case '/price':
$('#price-link').addClass('active');
break;
case '/config-mqtt':
case '/meter':
case '/wifi':
case '/mqtt':
case '/mqtt-ca':
case '/mqtt-cert':
case '/mqtt-key':
case '/config-domoticz':
$('#config-mqtt-link').addClass('active');
case '/domoticz':
case '/web':
case '/ntp':
case '/entsoe':
$('#config-link').addClass('active');
break;
case '/config-web':
$('#config-web-link').addClass('active');
break;
case '/config-system':
case '/gpio':
case '/debugging':
case '/firmware':
case '/reset':
$('#config-system-link').addClass('active');
$('#system-link').addClass('active');
break;
}
// Check for software upgrade
var swv = $('#swVersion')
if(meters.length > 0 && swv.length == 1 && swv.text() != "SNAPSHOT") {
var v = swv.text().substring(1).split('.');
@@ -173,14 +211,30 @@ $(function() {
}
});
}
// Temperature
var tt = $('#temp-template');
if(tt.length > 0) {
setTimeout(loadTempSensors, 500);
}
});
var setStatus = function(id, status) {
var setStatus = function(id, sid) {
var item = $('#'+id);
item.removeClass('d-none');
item.removeClass (function (index, className) {
return (className.match (/(^|\s)badge-\S+/g) || []).join(' ');
});
var status;
if(sid == 0) {
status = "secondary";
} else if(sid == 1) {
status = "success";
} else if(sid == 2) {
status = "warning";
} else {
status = "danger";
}
item.addClass('badge badge-' + status);
};
@@ -191,142 +245,116 @@ var fetch = function() {
timeout: 10000,
dataType: 'json',
}).done(function(json) {
if(im && em) {
if(im) {
$(".SimpleMeter").hide();
im.show();
em.show();
vm.show();
am.show();
}
for(var id in json) {
var str = json[id];
if(typeof str === "object")
if(typeof str === "object") {
continue;
}
if(isNaN(str)) {
$('#'+id).html(str);
$('.j'+id).html(str);
} else {
var num = parseFloat(str);
$('#'+id).html(num.toFixed(num < 0 ? 0 : num < 10 ? 2 : 1));
$('.j'+id).html(num.toFixed(num < 0 ? 0 : num < 10 ? 2 : 1));
}
$('.r'+id).show();
}
if(window.moment) {
$('#currentMillis').html(moment.duration(parseInt(json.uptime_seconds), 'seconds').humanize());
$('#currentMillis').closest('.row').show();
$('.ju').html(moment.duration(parseInt(json.u), 'seconds').humanize());
}
if(json.status) {
for(var id in json.status) {
setStatus(id, json.status[id]);
}
}
setStatus("esp", json.em);
setStatus("han", json.hm);
setStatus("wifi", json.wm);
setStatus("mqtt", json.mm);
if(json.mqtt) {
$('.mqtt-error').addClass('d-none');
$('.mqtt-error'+json.mqtt.lastError).removeClass('d-none');
$('#mqtt-lastError').html(json.mqtt.lastError);
}
if(json.wifi) {
for(var id in json.wifi) {
var str = json.wifi[id];
dst = $('#'+id);
if(isNaN(str)) {
dst.html(str);
} else {
var num = parseFloat(str);
dst.html(num.toFixed(0));
$('#'+id+'-row').show();
}
}
}
if(json.data) {
var p = 0;
var p_pct = parseInt(json.p_pct);
var p_append = "W";
if(json.data.P) {
p = parseFloat(json.data.P);
if(p > 1000) {
p = (p/1000).toFixed(1);
p_append = "kW";
}
}
if(im && im.gaugeMeter) {
im.gaugeMeter({
percent: p_pct,
text: p,
append: p_append
});
}
var po = 0;
var po_pct = parseInt(json.po_pct);
var po_append = "W";
if(json.data.PO) {
po = parseFloat(json.data.PO);
if(po > 1000) {
po = (po/1000).toFixed(1);
po_append = "kW";
}
}
if(em && em.gaugeMeter) {
em.gaugeMeter({
percent: po_pct,
text: po,
append: po_append
});
}
for(var id in json.data) {
var str = json.data[id];
if(isNaN(str)) {
$('#'+id).html(str);
} else {
var num = parseFloat(str);
$('#'+id).html(num.toFixed(1));
$('#'+id+'-row').show();
}
}
} else {
if(im && im.gaugeMeter) {
im.gaugeMeter({
percent: 0,
text: "-",
append: "W"
});
}
if(em && em.gaugeMeter) {
em.gaugeMeter({
percent: 0,
text: "-",
append: "W"
});
}
}
setTimeout(fetch, interval);
}).fail(function() {
setTimeout(fetch, interval*4);
if(im && im.gaugeMeter) {
var v = parseInt(json.i);
var pct = (v*100)/parseInt(json.im);
var append = "W";
if(v > 1000) {
v = (v/1000).toFixed(1);
append = "kW";
}
im.gaugeMeter({
percent: 0,
text: "-",
append: "W"
percent: pct,
text: v,
append: append
});
}
if(em && em.gaugeMeter) {
if(em && em.gaugeMeter && json.om) {
var v = parseInt(json.e);
var pct = (v*100)/(parseInt(json.om)*1000);
var append = "W";
if(v > 1000) {
v = (v/1000).toFixed(1);
append = "kW";
}
em.gaugeMeter({
percent: 0,
text: "-",
append: "W"
percent: pct,
text: v,
append: append
});
}
setStatus("mqtt", "secondary");
setStatus("wifi", "secondary");
setStatus("han", "secondary");
setStatus("esp", "danger");
if(vm && vm.gaugeMeter && json.u1) {
var v = parseFloat(json.u1);
if(json.u2) {
v = (v+parseFloat(json.u2)+parseFloat(json.u3)) / 3;
}
var pct = (Math.max(v-207, 1)*100/46);
vm.gaugeMeter({
percent: pct,
text: v.toFixed(1)
});
}
if(am && am.gaugeMeter && json.i1 && json.mf) {
var v = parseFloat(json.i1);
if(json.i2) {
v = Math.max(v, parseFloat(json.i2));
}
if(json.i3) {
v = Math.max(v, parseFloat(json.i3));
}
var pct = (v*100)/parseInt(json.mf);
am.gaugeMeter({
percent: pct,
text: v.toFixed(1)
});
}
if(json.me) {
$('.me').addClass('d-none');
$('.me'+json.me).removeClass('d-none');
$('#ml').html(json.me);
}
var temp = parseInt(json.t);
if(temp == -127) {
$('.jt').html("N/A");
}
setTimeout(fetch, interval);
}).fail(function(x, text, error) {
console.log("Failed request");
console.log(text);
console.log(error);
setTimeout(fetch, interval*4);
setStatus("mqtt", 0);
setStatus("wifi", 0);
setStatus("han", 0);
setStatus("esp", 3);
});
}
@@ -337,3 +365,40 @@ var upgrade = function() {
}
}
}
var loadTempSensors = function() {
$.ajax({
url: '/temperature.json',
timeout: 10000,
dataType: 'json',
}).done(function(json) {
if($('#loading').length > 0) {
$('#loading').hide();
var list = $('#sensors');
if(json.c > 0) {
list.empty();
var temp = $.trim($('#temp-template').html());
$.each(json.s, function(i, o) {
var item = temp.replace(/{{index}}/ig, o.i);
var item = item.replace(/{{address}}/ig, o.a);
var item = item.replace(/{{name}}/ig, o.n);
var item = item.replace(/{{value}}/ig, o.v > -50 && o.v < 127 ? o.v : "N/A");
var item = item.replace(/{{common}}/ig, o.c ? "checked" : "");
list.append(item);
});
} else {
$('#notemp').show();
}
} else {
$.each(json.s, function(i, o) {
$('#temp-'+o.i).html(o.v > -50 && o.v < 127 ? o.v : "N/A");
});
}
setTimeout(loadTempSensors, 10000);
}).fail(function() {
setTimeout(loadTempSensors, 60000);
$('#loading').hide();
$('#error').show();
});
}

View File

@@ -1,78 +0,0 @@
<form method="post" action="/save">
<input type="hidden" name="meterConfig" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<div class="row">
<div class="col-lg-3 col-md-4 col-sm-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Meter type</span>
</div>
<select id="meterType" class="form-control subtitute-dependent" name="meterType">
<option value="0" ${config.meterType0}>Autodetect</option>
<option value="1" ${config.meterType1}>Kaifa</option>
<option value="2" ${config.meterType2}>Aidon</option>
<option value="3" ${config.meterType3}>Kamstrup</option>
</select>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Distribution system</span>
</div>
<select id="distributionSystem" class="form-control subtitute-dependent" name="distributionSystem">
<option value="0" ${config.distributionSystem0}></option>
<option value="1" ${config.distributionSystem1}>IT (230V)</option>
<option value="2" ${config.distributionSystem2}>TN (400V)</option>
</select>
</div>
</div>
<div class="col-xl-2 col-md-3 col-sm-5">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Main fuse</span>
</div>
<select class="form-control" name="mainFuse">
<option value="0" ${config.mainFuse0}></option>
<option value="25" ${config.mainFuse25}>25A</option>
<option value="32" ${config.mainFuse32}>32A</option>
<option value="35" ${config.mainFuse32}>35A</option>
<option value="40" ${config.mainFuse40}>40A</option>
<option value="50" ${config.mainFuse50}>50A</option>
<option value="63" ${config.mainFuse63}>63A</option>
</select>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Production capacity</span>
</div>
<input class="form-control" name="productionCapacity" type="number" min="0" max="50" value="${config.productionCapacity}"/>
<div class="input-group-append">
<span class="input-group-text">kWp</span>
</div>
</div>
</div>
<div id="substitute" class="col-lg-3 col-md-4 col-sm-5">
<div class="m-2">
<label class="small"><input id="substituteMissing" type="checkbox" name="substituteMissing" value="true" ${config.substituteMissing}/> Substitute missing values</label>
</div>
</div>
<div class="col-lg-3 col-md-4 col-sm-5">
<div class="m-2">
<label class="small"><input type="checkbox" name="sendUnknown" value="true" ${config.sendUnknown}/> Send unknown packets to MQTT</label>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

View File

@@ -1,134 +0,0 @@
<div class="alert alert-warning">!!WARNING!!<br/>Do not change anything here unless you know exactly what you are doing! Changing things here could cause the device to stop responding</div>
<form method="post" action="/save">
<input type="hidden" name="sysConfig" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>GPIO settings</h6>
<div class="row">
<div class="col-xl-2 col-md-3 col-sm-6 col-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">HAN</span>
</div>
<select name="hanPin" class="form-control">
${options.han}
</select>
</div>
</div>
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-5 col-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">LED</span>
</div>
<input name="ledPin" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPin}"/>
<div class="input-group-append" title="Inverted">
<label class="input-group-text">
<input type="checkbox" name="ledInverted" value="true" ${config.ledInverted}/> inv
</label>
</div>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">RGB</span>
</div>
<input name="ledPinRed" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinRed}"/>
<input name="ledPinGreen" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinGreen}"/>
<input name="ledPinBlue" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinBlue}"/>
<div class="input-group-append" title="Inverted">
<label class="input-group-text">
<input type="checkbox" name="ledRgbInverted" value="true" ${config.ledRgbInverted}/> inv
</label>
</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-6 col-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">AP button</span>
</div>
<input name="apPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.apPin}"/>
</div>
</div>
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Temperature</span>
</div>
<input name="tempSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempSensorPin}"/>
</div>
</div>
<div class="col-xl-6 col-lg-8">
<div class="row p-2">
<div class="col-sm-3 col-5">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Vcc</span>
</div>
<input name="vccPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.vccPin}"/>
</div>
</div>
<div class="col-sm-4 col-7">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Multiplier</span>
</div>
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="vccMultiplier" value="${config.vccMultiplier}" />
</div>
</div>
<div class="col-sm-4 col-7">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Boot limit</span>
</div>
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="vccBootLimit" value="${config.vccBootLimit}" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>Debugger</h6>
<div class="row">
<div class="col-xl-2 col-md-3">
<label><input type="checkbox" name="debugTelnet" value="true" ${config.debugTelnet}/> Telnet debugger</label>
</div>
<div class="col-xl-2 col-md-3">
<label><input type="checkbox" name="debugSerial" value="true" ${config.debugSerial}/> Serial debugger</label>
</div>
<div class="col-xl-3 col-md-4">
<div class="row form-group">
<label class="col-6">Debug level</label>
<div class="col-6">
<select class="form-control form-control-sm" name="debugLevel">
<option value="2" ${config.debugLevel2}>Debug</option>
<option value="3" ${config.debugLevel3}>Info</option>
<option value="4" ${config.debugLevel4}>Warning</option>
<option value="5" ${config.debugLevel5}>Error</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="my-3 p-3 bg-white rounded shadow">
<div class="row">
<div class="col-6">
<a href="/firmware" class="btn btn-sm btn-outline-secondary">Upload firmware</a>
</div>
<div class="col-6 text-right">
<a href="/reset" class="btn btn-sm btn-danger">Factory reset</a>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

29
web/data.json Normal file
View File

@@ -0,0 +1,29 @@
{
"im" : %d,
"om" : %d,
"mf" : %d,
"i" : %d,
"e" : %d,
"ri" : %d,
"re" : %d,
"ic" : %.1f,
"ec" : %.1f,
"ric" : %.1f,
"rec" : %.1f,
"u1" : %.1f,
"u2" : %.1f,
"u3" : %.1f,
"i1" : %.2f,
"i2" : %.2f,
"i3" : %.2f,
"v" : %.2f,
"r" : %d,
"t" : %.1f,
"u" : %lu,
"m" : %lu,
"em" : %d,
"hm" : %d,
"wm" : %d,
"mm" : %d,
"me" : %d
}

37
web/debugging.html Normal file
View File

@@ -0,0 +1,37 @@
<div class="alert alert-warning">!!NOTE!!<br/>Telnet debugging is not considered safe and should be switched off when not in use</div>
<form method="post" action="/save">
<input type="hidden" name="debugConfig" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>Debugging</h6>
<div class="row">
<div class="col-xl-2 col-md-3">
<label><input type="checkbox" name="debugTelnet" value="true" ${config.debugTelnet}/> Telnet debugger</label>
</div>
<div class="col-xl-2 col-md-3">
<label><input type="checkbox" name="debugSerial" value="true" ${config.debugSerial}/> Serial debugger</label>
</div>
<div class="col-xl-3 col-md-4">
<div class="row form-group">
<label class="col-6">Debug level</label>
<div class="col-6">
<select class="form-control form-control-sm" name="debugLevel">
<option value="2" ${config.debugLevel2}>Debug</option>
<option value="3" ${config.debugLevel3}>Info</option>
<option value="4" ${config.debugLevel4}>Warning</option>
<option value="5" ${config.debugLevel5}>Error</option>
</select>
</div>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

View File

@@ -1,30 +1,18 @@
<form method="post" action="/save">
<input type="hidden" name="domoConfig" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<p>Domoticz Configuration. Requires that a Domoticz MQTT-message-broker is setup. HOWTO: https://www.domoticz.com/wiki/MQTT.</p>
<p>The following virtual sensors can currently be used:</p>
<ul>
<li>Electricity (instant and counter)</li>
<li>Electricity Current/Ampere 3 Phase</li>
<li>Voltage</li>
</ul>
<p>see: <a href="https://www.domoticz.com/wiki/Domoticz_API/JSON_URL's">https://www.domoticz.com/wiki/Domoticz_API/JSON_URL's</a></p>
<p>Create the sensors in Domoticz under Hardware > Dummy > Create virtual sensor, and use the IDX assigned to the sensor as input here.</p>
<p>"Electricity (instant and counter)" relies on Total energy import "tPI" and will not start before the first value is read (once an hour).</p>
</div>
<input type="hidden" name="dc" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<div class="d-flex flex-row flex-wrap">
<div class="m-2 input-group input-group-sm" style="width: 200px;">
<div class="input-group-prepend">
<span class="input-group-text">Electricity IDX</span>
</div>
<input type="number" class="form-control" name="domoELIDX" value="${config.domoELIDX}" min="0" max="65535"/>
<input type="number" class="form-control" name="elidx" value="{elidx}" min="0" max="65535"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 240px;">
<div class="input-group-prepend">
<span class="input-group-text">Current (3 Phase) IDX</span>
</div>
<input type="number" class="form-control" name="domoCL1IDX" value="${config.domoCL1IDX}" min="0" max="65535"/>
<input type="number" class="form-control" name="cl1idx" value="{cl1idx}" min="0" max="65535"/>
</div>
</div>
<div class="d-flex flex-row flex-wrap">
@@ -32,19 +20,19 @@
<div class="input-group-prepend">
<span class="input-group-text">Voltage L1 IDX</span>
</div>
<input type="number" class="form-control" name="domoVL1IDX" value="${config.domoVL1IDX}" min="0" max="65535"/>
<input type="number" class="form-control" name="vl1idx" value="{vl1idx}" min="0" max="65535"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 200px;">
<div class="input-group-prepend">
<span class="input-group-text">Voltage L1 IDX</span>
</div>
<input type="number" class="form-control" name="domoVL2IDX" value="${config.domoVL2IDX}" min="0" max="65535"/>
<input type="number" class="form-control" name="vl2idx" value="{vl2idx}" min="0" max="65535"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 200px;">
<div class="input-group-prepend">
<span class="input-group-text">Voltage L1 IDX</span>
</div>
<input type="number" class="form-control" name="domoVL3IDX" value="${config.domoVL3IDX}" min="0" max="65535"/>
<input type="number" class="form-control" name="vl3idx" value="{vl3idx}" min="0" max="65535"/>
</div>
</div>
</div>

6
web/domoticz.json Normal file
View File

@@ -0,0 +1,6 @@
{
"command" : "udevice",
"idx" : %d,
"nvalue" : 0,
"svalue" : "%s"
}

72
web/entsoe.html Normal file
View File

@@ -0,0 +1,72 @@
<form method="post" action="/save">
<input type="hidden" name="ec" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>ENTSO-E API</h6>
<div class="row">
<div class="col-xl-4 col-lg-6 col-md-8">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Token</span>
</div>
<input type="text" name="et" class="form-control" value="{et}"/>
</div>
</div>
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Region</span>
</div>
<select name="ea" class="form-control">
<optgroup label="Norway">
<option value="10YNO-1--------2" {eaNo1}>NO1</option>
<option value="10YNO-2--------T" {eaNo1}>NO2</option>
<option value="10YNO-3--------J" {eaNo3}>NO3</option>
<option value="10YNO-4--------9" {eaNo4}>NO4</option>
<option value="10Y1001A1001A48H" {eaNo5}>NO5</option>
</optgroup>
<optgroup label="Sweden">
<option value="10Y1001A1001A44P" {eaSe1}>SE1</option>
<option value="10Y1001A1001A45N" {eaSe2}>SE2</option>
<option value="10Y1001A1001A46L" {eaSe3}>SE3</option>
<option value="10Y1001A1001A47J" {eaSe4}>SE4</option>
</optgroup>
<optgroup label="Denmark">
<option value="10YDK-1--------W" {eaDk1}>DK1</option>
<option value="10YDK-2--------M" {eaDk2}>DK2</option>
</optgroup>
</select>
</div>
</div>
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Currency</span>
</div>
<select name="ecu" class="form-control">
<option value="NOK" {ecNOK}>NOK</option>
<option value="SEK" {ecSEK}>SEK</option>
<option value="DKK" {ecDKK}>DKK</option>
<option value="EUR" {ecEUR}>EUR</option>
</select>
</div>
</div>
<div class="col-xl-2 col-lg-3 col-md-4 col-sm-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Multiplier</span>
</div>
<input name="em" type="number" min="0.001" max="1000" step="0.001" class="form-control" value="{em}"/>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

View File

@@ -7,7 +7,7 @@
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<script src="gaugemeter.js"></script>
<script src="application.js"></script>
<script src="application-${version}.js"></script>
</body>
</html>

View File

@@ -55,6 +55,7 @@
"LightGreen-DarkGreen" === option.theme && (e > 0 && (t = "#3afc00"), e > 10 && (t = "#39f900"), e > 20 && (t = "#38f600"), e > 30 && (t = "#38f100"), e > 40 && (t = "#37ec00"), e > 50 && (t = "#36e700"), e > 60 && (t = "#34e200"), e > 70 && (t = "#34df00"), e > 80 && (t = "#33db00"), e > 90 && (t = "#32d900")),
"DarkGold-LightGold" === option.theme && (e > 0 && (t = "#ffb800"), e > 10 && (t = "#ffba00"), e > 20 && (t = "#ffbd00"), e > 30 && (t = "#ffc200"), e > 40 && (t = "#ffc600"), e > 50 && (t = "#ffcb00"), e > 60 && (t = "#ffcf00"), e > 70 && (t = "#ffd400"), e > 80 && (t = "#ffd600"), e > 90 && (t = "#ffd900")),
"LightGold-DarkGold" === option.theme && (e > 0 && (t = "#ffd900"), e > 10 && (t = "#ffd600"), e > 20 && (t = "#ffd400"), e > 30 && (t = "#ffcf00"), e > 40 && (t = "#ffcb00"), e > 50 && (t = "#ffc600"), e > 60 && (t = "#ffc200"), e > 70 && (t = "#ffbd00"), e > 80 && (t = "#ffba00"), e > 90 && (t = "#ffb800")),
"Voltage" === option.theme && (e <= 0 && (t = "#d90000"), e > 0 && (t = "#e32100"), e > 10 && (t = "#ffb800"), e > 20 && (t = "#dcd800"), e > 30 && (t = "#32d900"), e > 40 && (t = "#32d900"), e > 50 && (t = "#32d900"), e > 60 && (t = "#32d900"), e > 70 && (t = "#dcd800"), e > 80 && (t = "#ffb800"), e > 90 && (t = "#e32100"), e >= 100 && (t = "#d90000")),
"White" === option.theme && (t = "#fff"),
"Black" === option.theme && (t = "#000"),
t;

92
web/gpio.html Normal file
View File

@@ -0,0 +1,92 @@
<div class="alert alert-warning">!!WARNING!!<br/>Do not change anything here unless you know exactly what you are doing! Changing things here could cause the device to stop responding</div>
<form method="post" action="/save">
<input type="hidden" name="gpioConfig" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>GPIO settings</h6>
<div class="d-flex flex-row flex-wrap">
<div class="m-2 input-group input-group-sm" style="width: 150px;">
<div class="input-group-prepend">
<span class="input-group-text">HAN</span>
</div>
<select name="hanPin" class="form-control">
${options.han}
</select>
</div>
<div class="m-2 input-group input-group-sm" style="width: 150px;">
<div class="input-group-prepend">
<span class="input-group-text">LED</span>
</div>
<input name="ledPin" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPin}"/>
<div class="input-group-append" title="Inverted">
<label class="input-group-text">
<input type="checkbox" name="ledInverted" value="true" ${config.ledInverted}/> inv
</label>
</div>
</div>
<div class="m-2 input-group input-group-sm" style="width: 250px;">
<div class="input-group-prepend">
<span class="input-group-text">RGB</span>
</div>
<input name="ledPinRed" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinRed}"/>
<input name="ledPinGreen" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinGreen}"/>
<input name="ledPinBlue" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinBlue}"/>
<div class="input-group-append" title="Inverted">
<label class="input-group-text">
<input type="checkbox" name="ledRgbInverted" value="true" ${config.ledRgbInverted}/> inv
</label>
</div>
</div>
<div class="m-2 input-group input-group-sm" style="width: 130px;">
<div class="input-group-prepend">
<span class="input-group-text">AP button</span>
</div>
<input name="apPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.apPin}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 150px;">
<div class="input-group-prepend">
<span class="input-group-text">Temperature</span>
</div>
<input name="tempSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempSensorPin}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 150px;">
<div class="input-group-prepend">
<span class="input-group-text">Analog temp</span>
</div>
<input name="tempAnalogSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempAnalogSensorPin}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 100px;">
<div class="input-group-prepend">
<span class="input-group-text">Vcc</span>
</div>
<input name="vccPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.vccPin}"/>
</div>
<div class="m-2 input-group input-group-sm" style="width: 140px;">
<div class="input-group-prepend">
<span class="input-group-text">Multiplier</span>
</div>
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="vccMultiplier" value="${config.vccMultiplier}" />
</div>
<div class="m-2 input-group input-group-sm" style="width: 120px;">
<div class="input-group-prepend">
<span class="input-group-text">Offset</span>
</div>
<input type="number" min="0.1" max="3.5" step="0.01" class="form-control" name="vccOffset" value="${config.vccOffset}" />
</div>
<div class="m-2 input-group input-group-sm" style="width: 130px;">
<div class="input-group-prepend">
<span class="input-group-text">Boot limit</span>
</div>
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="vccBootLimit" value="${config.vccBootLimit}" />
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

View File

@@ -45,28 +45,60 @@
font-weight: 200;
opacity: .8;
}
.navbar-expand .navbar-nav .nav-link,.navbar-brand {
padding-top: 0px;
padding-bottom: 0px;
margin-top: 0px;
margin-bottom: 0px;
}
</style>
</head>
<body class="bg-light">
<main role="main" class="container">
<header class="navbar navbar-expand navbar-dark flex-column flex-lg-row rounded mt-2 mb-4" style="background-color: var(--purple);">
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small id="swVersion" data-url="https://api.github.com/repos/gskjold/AmsToMqttBridge/releases">${version}</small></h6></a>
<header class="navbar navbar-expand navbar-dark flex-column flex-lg-row rounded shadow mt-2 mb-3" style="background-color: var(--purple);">
<a href="/" class="">
<h6 class="navbar-brand">AMS reader <small id="swVersion" data-url="https://api.github.com/repos/gskjold/AmsToMqttBridge/releases">${version}</small></h6>
</a>
<div class="navbar-nav-scroll">
<ul class="navbar-nav bd-navbar-nav flex-row">
<li class="nav-item">
<a id="config-meter-link" class="nav-link" href="/config-meter">Meter</a>
<a id="temp-link" class="nav-link" href="/temperature">Temp<span class="d-none d-sm-inline">erature</span></a>
</li>
<li class="nav-item">
<a id="config-wifi-link" class="nav-link" href="/config-wifi">WiFi</a>
<a id="price-link" class="nav-link" href="/price"><span class="d-none d-sm-inline">Energy</span> Price</a>
</li>
<li class="nav-item">
<a id="config-mqtt-link" class="nav-link" href="/config-mqtt">MQTT</a>
<div class="dropdown">
<a class="dropdown-toggle nav-link" href="#" role="button" id="config-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Config<span class="d-none d-md-inline d-lg-none d-xl-inline">uration</span>
</a>
<div class="dropdown-menu" aria-labelledby="config-link">
<a class="dropdown-item" href="/meter">Meter</a>
<a class="dropdown-item" href="/wifi">WiFi</a>
<a class="dropdown-item" href="/mqtt">MQTT</a>
<a class="dropdown-item" href="/web">Web</a>
<a class="dropdown-item" href="/ntp">NTP</a>
<a class="dropdown-item" href="/entsoe">ENTSO-E API</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="https://github.com/gskjold/AmsToMqttBridge/wiki" target="_blank">Documentation</a>
</div>
</div>
</li>
<li class="nav-item">
<a id="config-web-link" class="nav-link" href="/config-web">Web</a>
</li>
<li class="nav-item">
<a id="config-system-link" class="nav-link" href="/config-system">System</a>
<div class="dropdown">
<a class="dropdown-toggle nav-link" href="#" role="button" id="system-link" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Sys<span class="d-none d-sm-inline">tem</span>
</a>
<div class="dropdown-menu" aria-labelledby="system-link">
<a class="dropdown-item" href="/gpio">GPIO</a>
<a class="dropdown-item" href="/debugging">Debugging</a>
<a class="dropdown-item" href="/firmware">Firmware</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/restart">Restart</a>
<a class="dropdown-item text-danger" href="/reset">Factory reset</a>
</div>
</div>
</li>
</ul>
</div>

View File

@@ -1,107 +1,138 @@
<div class="my-3 p-3 bg-white rounded shadow">
<div class="bg-white rounded shadow p-1">
<div class="row">
<div class="col-sm-6 col-lg-3">
<div class="text-center">
<div id="P" class="SimpleMeter" style="display: inline;">
${data.P} W
<div class="col-md-3 col-6">
<div class="text-center">Up <span class="ju">{cs}</span></div>
</div>
<div class="col-md-3 col-6">
<div class="text-center">Temperature: <span class="jt">{temp}</span>&deg;C</div>
</div>
<div class="col-md-3 col-6">
<div class="text-center">ESP volt: <span class="jv">{vcc}</span>V</div>
</div>
<div class="col-md-3 col-6">
<div class="text-center">WiFi RSSI: <span class="jr">{rssi}</span>dBm</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="row">
<div class="col-sm-6 mt-3">
<div class="bg-white rounded shadow p-3">
<div class="text-center">
<div class="SimpleMeter ji" style="display: inline;">
{P} W
</div>
<div id="im" class="GaugeMeter rounded"
style="display: none;"
data-size="180px"
data-text_size="0.15"
data-width="25"
data-style="Arch"
data-theme="Green-Gold-Red"
data-animationstep="0"
data-label="{ti}"
></div>
</div>
<div class="row ric" style="display: {da};">
<div class="col-12 text-right"><span class="jic">{tPI}</span> kWh</div>
</div>
</div>
<div id="importMeter" class="GaugeMeter rounded"
</div>
<div class="col-sm-6 mt-3" style="display: {de};">
<div class="bg-white rounded shadow p-3">
<div class="text-center">
<div class="SimpleMeter je" style="display: inline;">
{PO} W
</div>
<div id="em" class="GaugeMeter rounded"
style="display: none;"
data-size="180px"
data-text_size="0.15"
data-width="25"
data-style="Arch"
data-theme="DarkGreen-LightGreen"
data-animationstep="0"
data-label="Export"
></div>
</div>
<div class="row rec" style="display: {da};">
<div class="col-12 text-right"><span class="jec">{tPO}</span> kWh</div>
</div>
</div>
</div>
<div class="col-sm-6 mt-3" style="display: {dn};">
<div class="bg-white rounded shadow p-3" style="display: {da};">
<h5 class="text-center">Reactive</h5>
<div class="row rric">
<div class="col-4">In</div>
<div class="col-8 text-right"><span class="jric">{tQI}</span> kvarh</div>
<div class="col-4">Out</div>
<div class="col-8 text-right"><span class="jrec">{tQO}</span> kvarh</div>
</div>
</div>
</div>
<div class="col-sm-12 mt-3" style="display: {de};">
<div class="bg-white rounded shadow p-3" style="display: {da};">
<div class="row rrec">
<div class="col-4 col-sm-2">In</div>
<div class="col-8 col-sm-4 text-right"><span class="jric">{tQI}</span> kvarh</div>
<div class="col-4 col-sm-2">Out</div>
<div class="col-8 col-sm-4 text-right"><span class="jrec">{tQO}</span> kvarh</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6 mt-3">
<div class="bg-white rounded shadow p-3">
<div class="text-center">
<div id="vm" class="GaugeMeter rounded"
style="display: none;"
data-size="200px"
data-text_size="0.11"
data-size="180px"
data-text_size="0.15"
data-width="25"
data-style="Arch"
data-theme="Voltage"
data-animationstep="0"
data-label="Volt"
></div>
</div>
<div class="row ru2" style="display: {3p};">
<div class="col-4"><span class="ju1">{U1}</span>V</div>
<div class="col-4 text-center"><span class="ju2">{U2}</span>V</div>
<div class="col-4 text-right"><span class="ju3">{U3}</span>V</div>
</div>
</div>
</div>
<div class="col-lg-3 col-sm-6 mb-3 mt-3">
<div class="bg-white rounded shadow p-3">
<div class="text-center">
<div id="am" class="GaugeMeter rounded"
style="display: none;"
data-size="180px"
data-text_size="0.15"
data-width="25"
data-style="Arch"
data-theme="Green-Gold-Red"
data-animationstep="0"
data-label="${text.import}"
data-label="Ampere"
></div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div id="U1-row" class="row" style="display: ${display.P1};">
<div class="col-2">L1</div>
<div class="col-5 text-right"><span id="U1">${data.U1}</span> V</div>
<div class="col-5 text-right"><span id="I1">${data.I1}</span> A</div>
</div>
<div id="U2-row" class="row" style="display: ${display.P2};">
<div class="col-2">L2</div>
<div class="col-5 text-right"><span id="U2">${data.U2}</span> V</div>
<div class="col-5 text-right"><span id="I2">${data.I2}</span> A</div>
</div>
<div id="U3-row" class="row" style="display: ${display.P3};">
<div class="col-2">L3</div>
<div class="col-5 text-right"><span id="U3">${data.U3}</span> V</div>
<div class="col-5 text-right"><span id="I3">${data.I3}</span> A</div>
</div>
<hr/>
<div id="tPI-row" class="row" style="display: ${display.accumulative};">
<div class="col-6">Active in</div>
<div class="col-6 text-right"><span id="tPI">${data.tPI}</span> kWh</div>
</div>
<div id="tPO-row" class="row" style="display: ${display.accumulative};">
<div class="col-6">Active out</div>
<div class="col-6 text-right"><span id="tPO">${data.tPO}</span> kWh</div>
</div>
<div id="tQI-row" class="row" style="display: ${display.accumulative};">
<div class="col-6">Reactive in</div>
<div class="col-6 text-right"><span id="tQI">${data.tQI}</span> kvarh</div>
</div>
<div id="tQO-row" class="row" style="display: ${display.accumulative};">
<div class="col-6">Reactive out</div>
<div class="col-6 text-right"><span id="tQO">${data.tQO}</span> kvarh</div>
<div class="row ru2" style="display: {3p};">
<div class="col-4"><span class="ji1">{I1}</span>A</div>
<div class="col-4 text-center"><span class="ji2">{I2}</span>A</div>
<div class="col-4 text-right"><span class="ji3">{I3}</span>A</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="text-center" style="display: ${display.export};">
<div id="P" class="SimpleMeter" style="display: inline;">
${data.PO} W
</div>
<div id="exportMeter" class="GaugeMeter rounded"
style="display: none;"
data-size="200px"
data-text_size="0.11"
data-width="25"
data-style="Arch"
data-theme="DarkGreen-LightGreen"
data-animationstep="0"
data-label="Export"
></div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<hr class="d-md-inline"/>
<div class="row">
<div class="col-6">Vcc</div>
<div class="col-6 text-right"><span id="vcc">${vcc}</span> V</div>
</div>
<div class="row" style="display: ${display.temp};">
<div class="col-6">Temperature</div>
<div class="col-6 text-right"><span id="temp">${temp}</span> °C</div>
</div>
<div class="row" style="display: none;">
<div class="col-6">Uptime</div>
<div class="col-6 text-right"><span id="currentMillis">${currentMillis}</span></div>
</div>
<hr/>
<div class="row">
<div class="col-6">SSID</div>
<div class="col-6 text-right"><span id="ssid">${wifi.ssid}</span></div>
</div>
<div class="row">
<div class="col-6">Channel</div>
<div class="col-6 text-right"><span id="channel">${wifi.channel}</span></div>
</div>
<div class="row">
<div class="col-6">RSSI</div>
<div class="col-6 text-right"><span id="rssi">${wifi.rssi}</span> dBm</div>
</div>
<hr class="d-none mqtt-error mqtt-error-1 mqtt-error-2 mqtt-error-3 mqtt-error-4 mqtt-error-5 mqtt-error-6 mqtt-error-7 mqtt-error-8 mqtt-error-9 mqtt-error-10 mqtt-error-11 mqtt-error-12 mqtt-error-13"/>
<div class="d-none badge badge-danger mqtt-error mqtt-error-1 mqtt-error-2 mqtt-error-5 mqtt-error-6 mqtt-error-7 mqtt-error-8 mqtt-error-9 mqtt-error-12">MQTT communication error (<span id="mqtt-lastError">-</span>)</div>
<div class="d-none badge badge-danger mqtt-error mqtt-error-3">MQTT failed to connect</div>
<div class="d-none badge badge-danger mqtt-error mqtt-error-4">MQTT network timeout</div>
<div class="d-none badge badge-danger mqtt-error mqtt-error-10">MQTT connection denied</div>
<div class="d-none badge badge-danger mqtt-error mqtt-error-11">MQTT failed to subscribe</div>
<div class="d-none badge badge-danger mqtt-error mqtt-error-13">MQTT lost connection</div>
</div>
<div class="col-lg-3 col-sm-6 mb-3 d-none me me-1 me-2 me-3 me-4 me-5 me-6 me-7 me-8 me-9 me-10 me-11 me-12 me-13">
<div class="d-none badge badge-danger me me-1 me-2 me-5 me-6 me-7 me-8 me-9 me-12">MQTT communication error (<span id="ml">-</span>)</div>
<div class="d-none badge badge-danger me me-3">MQTT failed to connect</div>
<div class="d-none badge badge-danger me me-4">MQTT network timeout</div>
<div class="d-none badge badge-danger me me-10">MQTT connection denied</div>
<div class="d-none badge badge-danger me me-11">MQTT failed to subscribe</div>
<div class="d-none badge badge-danger me me-13">MQTT lost connection</div>
</div>
</div>

12
web/json1.json Normal file
View File

@@ -0,0 +1,12 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,
"data" : {
"P" : %d
}
}

24
web/json2.json Normal file
View File

@@ -0,0 +1,24 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,
"data" : {
"lv" : "%s",
"id" : "%s",
"type" : "%s",
"P" : %d,
"Q" : %d,
"PO" : %d,
"QO" : %d,
"I1" : %.2f,
"I2" : %.2f,
"I3" : %.2f,
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f
}
}

29
web/json3.json Normal file
View File

@@ -0,0 +1,29 @@
{
"id" : "%s",
"name" : "%s",
"up" : %lu,
"t" : %lu,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f,
"data" : {
"lv" : "%s",
"id" : "%s",
"type" : "%s",
"P" : %d,
"Q" : %d,
"PO" : %d,
"QO" : %d,
"I1" : %.2f,
"I2" : %.2f,
"I3" : %.2f,
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f,
"tPI" : %.1f,
"tPO" : %.1f,
"tQI" : %.1f,
"tQO" : %.1f,
"rtc" : %lu
}
}

22
web/jsonprices.json Normal file
View File

@@ -0,0 +1,22 @@
{
"id" : "%s",
"prices" : {
"0" : %.4f,
"1" : %.4f,
"2" : %.4f,
"3" : %.4f,
"4" : %.4f,
"5" : %.4f,
"6" : %.4f,
"7" : %.4f,
"8" : %.4f,
"9" : %.4f,
"10" : %.4f,
"11" : %.4f,
"min" : %.4f,
"max" : %.4f,
"cheapest1hr" : "%s",
"cheapest3hr" : "%s",
"cheapest6hr" : "%s"
}
}

8
web/jsonsys.json Normal file
View File

@@ -0,0 +1,8 @@
{
"id" : "%s",
"name" : "%s",
"up" : %d,
"vcc" : %.3f,
"rssi": %d,
"temp": %.2f
}

1
web/lowmem.html Normal file
View File

@@ -0,0 +1 @@
<div class="alert alert-info">There is currently not enough available heap space on your device to support this feature. If you are using ESP8266, you can consider switching to ESP32 to get enable this feature</div>

91
web/meter.html Normal file
View File

@@ -0,0 +1,91 @@
<form method="post" action="/save">
<input type="hidden" name="mc" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>Meter</h6>
<div class="row">
<div class="col-lg-3 col-md-4 col-sm-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Meter type</span>
</div>
<select id="mt" class="form-control sd" name="m">
<option value="0" {m0}>Autodetect</option>
<option value="1" {m1}>Kaifa</option>
<option value="2" {m2}>Aidon</option>
<option value="3" {m3}>Kamstrup non-encrypted</option>
<option value="4" {m4}>Kamstrup encrypted</option>
</select>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Distribution system</span>
</div>
<select id="d" class="form-control sd" name="d">
<option value="0" {d0}></option>
<option value="1" {d1}>IT (230V)</option>
<option value="2" {d2}>TN (400V)</option>
</select>
</div>
</div>
<div class="col-xl-2 col-md-3 col-sm-5">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Main fuse</span>
</div>
<select class="form-control" name="f">
<option value="0" {f0}></option>
<option value="25" {f25}>25A</option>
<option value="32" {f32}>32A</option>
<option value="35" {f35}>35A</option>
<option value="40" {f40}>40A</option>
<option value="50" {f50}>50A</option>
<option value="63" {f63}>63A</option>
</select>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Production capacity</span>
</div>
<input class="form-control" name="p" type="number" min="0" max="50" value="{p}"/>
<div class="input-group-append">
<span class="input-group-text">kWp</span>
</div>
</div>
</div>
<div class="col-lg-6 enc">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Encryption key</span>
</div>
<input class="form-control" name="e" type="text" value="{e}"/>
</div>
</div>
<div class="col-lg-6 enc">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Authentication key</span>
</div>
<input class="form-control" name="a" type="text" value="{a}"/>
</div>
</div>
<div id="ss" class="col-lg-3 col-md-4 col-sm-5">
<div class="m-2">
<label class="small"><input type="checkbox" name="s" value="true" {s}/> Substitute missing values</label>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

View File

@@ -1,14 +1,15 @@
<form method="post" action="/save">
<input type="hidden" name="mqttConfig" value="true"/>
<input type="hidden" name="mqc" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<label class="m-2"><input id="mqttEnable" type="checkbox" name="mqtt" value="true" ${config.mqtt}/> Enable</label>
<h6>MQTT</h6>
<label class="m-2"><input id="m" type="checkbox" name="m" value="true" {m}/> Enable</label>
<div class="row">
<div class="col-xl-4 col-lg-5 col-md-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Host</span>
</div>
<input type="text" class="form-control mqtt-config" name="mqttHost" value="${config.mqttHost}" maxlength="128"/>
<input type="text" class="form-control mc" name="h" value="{h}" maxlength="127"/>
</div>
</div>
<div class="col-xl-2 col-lg-3 col-md-3 col-sm-4">
@@ -16,7 +17,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Port</span>
</div>
<input id="mqttPort" type="number" class="form-control mqtt-config" name="mqttPort" value="${config.mqttPort}" min="1024" max="65535" placeholder="1883"/>
<input id="p" type="number" class="form-control mc" name="p" value="{p}" min="1024" max="65535" placeholder="1883"/>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-6 col-sm-8">
@@ -24,7 +25,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Client ID</span>
</div>
<input type="text" class="form-control mqtt-config" name="mqttClientId" value="${config.mqttClientId}" maxlength="32"/>
<input type="text" class="form-control mc" name="i" value="{i}" maxlength="31"/>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-6">
@@ -32,7 +33,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Publish topic</span>
</div>
<input type="text" class="form-control mqtt-config" name="mqttPublishTopic" value="${config.mqttPublishTopic}" maxlength="64"/>
<input type="text" class="form-control mc" name="t" value="{t}" maxlength="63"/>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-6 col-sm-6">
@@ -40,7 +41,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Username</span>
</div>
<input type="text" class="form-control mqtt-config" name="mqttUser" value="${config.mqttUser}" maxlength="64"/>
<input type="text" class="form-control mc" name="u" value="{u}" maxlength="127"/>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-6 col-sm-6">
@@ -48,7 +49,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Password</span>
</div>
<input type="password" class="form-control mqtt-config" name="mqttPassword" value="${config.mqttPassword}" maxlength="64"/>
<input type="password" class="form-control mc" name="pw" value="{pw}" maxlength="255"/>
</div>
</div>
<div class="col-lg-3 col-md-4 col-sm-6">
@@ -56,17 +57,17 @@
<div class="input-group-prepend">
<span class="input-group-text">Payload</span>
</div>
<select id="mqttPayloadFormat" class="form-control mqtt-config" name="mqttPayloadFormat">
<option value="0" ${config.mqttPayloadFormat0}>JSON</option>
<option value="1" ${config.mqttPayloadFormat1}>Raw (minimal)</option>
<option value="2" ${config.mqttPayloadFormat2}>Raw (full)</option>
<option value="3" ${config.mqttPayloadFormat3}>Domoticz</option>
<select id="f" class="form-control mc" name="f">
<option value="0" {f0}>JSON</option>
<option value="1" {f1}>Raw (minimal)</option>
<option value="2" {f2}>Raw (full)</option>
<option value="3" {f3}>Domoticz</option>
</select>
</div>
</div>
<div class="col-md-3 col-sm-6 format-type-domoticz">
<div class="col-md-3 col-sm-6 f3-s">
<div class="m-2">
<a href="/config-domoticz" class="btn btn-sm btn-outline-secondary">Configuration</a>
<a href="/domoticz" class="btn btn-sm btn-outline-secondary">Configuration</a>
</div>
</div>
</div>
@@ -74,7 +75,7 @@
<div class="my-3 p-3 bg-white rounded shadow">
<div class="row">
<div class="col-md-2">
<label class="m-2"><input id="mqttSsl" type="checkbox" name="mqttSsl" value="true" ${config.mqttSsl}/> SSL</label>
<label class="m-2"><input id="s" type="checkbox" name="s" value="true" {s}/> SSL</label>
</div>
<div class="col-lg-2 col-md-3">
<div class="m-2 input-group input-group-sm">
@@ -82,10 +83,10 @@
<span class="input-group-text">CA</span>
</div>
<div class="input-group-append">
<span style="display: ${display.ca.upload};">
<span style="display: {dcu};">
<a href="/mqtt-ca" class="btn btn-sm btn-outline-secondary">Upload</a>
</span>
<span style="display: ${display.ca.file};">
<span style="display: {dcf};">
<a href="/mqtt-ca" class="btn btn-sm btn-danger">Delete</a>
</span>
</div>
@@ -97,10 +98,10 @@
<span class="input-group-text">Certificate</span>
</div>
<div class="input-group-append">
<span style="display: ${display.cert.upload};">
<span style="display: {deu};">
<a href="/mqtt-cert" class="btn btn-sm btn-outline-secondary">Upload</a>
</span>
<span style="display: ${display.cert.file};">
<span style="display: {def};">
<a href="/mqtt-cert" class="btn btn-sm btn-danger">Delete</a>
</span>
</div>
@@ -112,10 +113,10 @@
<span class="input-group-text">Private key</span>
</div>
<div class="input-group-append">
<span style="display: ${display.key.upload};">
<span style="display: {dku};">
<a href="/mqtt-key" class="btn btn-sm btn-outline-secondary">Upload</a>
</span>
<span style="display: ${display.key.file};">
<span style="display: {dkf};">
<a href="/mqtt-key" class="btn btn-sm btn-danger">Delete</a>
</span>
</div>

1
web/notfound.html Normal file
View File

@@ -0,0 +1 @@
<div class="alert alert-danger">Page not found</div>

54
web/ntp.html Normal file
View File

@@ -0,0 +1,54 @@
<form method="post" action="/save">
<input type="hidden" name="nc" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>NTP</h6>
<label class="m-2"><input id="n" type="checkbox" name="n" value="true" {n}/> Enable</label>
<div class="row">
<div class="col-lg-3 col-md-4 col-sm-5">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Timezone</span>
</div>
<select id="o" class="form-control nc" name="o">
<option value="0" {o0}>UTC</option>
<option value="3600" {o3600}>UTC+1</option>
<option value="7200" {o7200}>UTC+2</option>
</select>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-5 col-sm-7">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Summertime offset</span>
</div>
<select id="so" class="form-control nc" name="so">
<option value="0" {so0}>Disabled</option>
<option value="3600" {so3600}>+1hr</option>
</select>
</div>
</div>
<div class="col-xl-4 col-lg-5 col-md-6">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Server</span>
</div>
<input type="text" class="form-control nc" name="ns" value="{ns}" maxlength="64"/>
</div>
</div>
<div class="col-lg-4 col-md-5 col-sm-6">
<div class="m-2">
<label class="small"><input type="checkbox" name="nd" value="true" {nd} class="nc"/> Obtain NTP server from DHCP</label>
</div>
</div>
</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

149
web/price.html Normal file
View File

@@ -0,0 +1,149 @@
<div id="sensors" class="my-3 p-3 bg-white rounded shadow">
<p>Price retrieval requires ENTSO-E API to be configured and valid timestamp available. The timestamp can either come from a working NTP configuration or valid timestamp from the meter.</p>
<div class="row">
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time0}</div>
<div class="col-6">${price0}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time1}</div>
<div class="col-6">${price1}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time2}</div>
<div class="col-6">${price2}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time3}</div>
<div class="col-6">${price3}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time4}</div>
<div class="col-6">${price4}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time5}</div>
<div class="col-6">${price5}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time6}</div>
<div class="col-6">${price6}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time7}</div>
<div class="col-6">${price7}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time8}</div>
<div class="col-6">${price8}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time9}</div>
<div class="col-6">${price9}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time10}</div>
<div class="col-6">${price10}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time11}</div>
<div class="col-6">${price11}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time12}</div>
<div class="col-6">${price12}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time13}</div>
<div class="col-6">${price13}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time14}</div>
<div class="col-6">${price14}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time15}</div>
<div class="col-6">${price15}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time16}</div>
<div class="col-6">${price16}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time17}</div>
<div class="col-6">${price17}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time18}</div>
<div class="col-6">${price18}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time19}</div>
<div class="col-6">${price19}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time20}</div>
<div class="col-6">${price20}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time21}</div>
<div class="col-6">${price21}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time22}</div>
<div class="col-6">${price22}</div>
</div>
</div>
<div class="col-lg-2 col-md-3 col-sm-4">
<div class="row">
<div class="col-6 text-right">${time23}</div>
<div class="col-6">${price23}</div>
</div>
</div>
</div>
</div>

14
web/restart.html Normal file
View File

@@ -0,0 +1,14 @@
<form method="post">
<div class="my-3 p-3 bg-white rounded shadow">
<div class="alert alert-warning">Are you sure you want restart?</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-success" name="perform" value="true">Restart</button>
</div>
</div>
</form>

View File

@@ -25,9 +25,11 @@
<h5>Hardware</h5>
<select name="board" class="form-control">
<optgroup label="Custom hardware">
<option value="0" ${config.boardType0}>Custom hardware by Roar Fredriksen</option>
<option value="1" ${config.boardType1}>Kamstrup module by Egil Opsahl</option>
<option value="4" ${config.boardType2}>Pow-U or Pow-K from amsleser.no (GPIO12)</option>
<option value="3" ${config.boardType2}>Pow-U or Pow-K from amsleser.no (UART0)</option>
<option value="2" ${config.boardType2}>HAN Reader 2.0 by Max Spencer</option>
<option value="1" ${config.boardType1}>Kamstrup module by Egil Opsahl</option>
<option value="0" ${config.boardType0}>Custom hardware by Roar Fredriksen</option>
</optgroup>
<optgroup label="ESP8266">
<option value="101" ${config.boardType101}>Wemos D1</option>
@@ -41,15 +43,6 @@
</optgroup>
</select>
</div>
<div class="col-xl-2 col-md-3">
<h5>Meter</h5>
<select name="meterType" class="form-control">
<option value="0" ${config.meterType0}>Autodetect</option>
<option value="1" ${config.meterType1}>Kaifa</option>
<option value="2" ${config.meterType2}>Aidon</option>
<option value="3" ${config.meterType3}>Kamstrup</option>
</select>
</div>
</div>
</div>
<div class="my-3 p-3 bg-white rounded shadow">

48
web/temperature.html Normal file
View File

@@ -0,0 +1,48 @@
<script id="temp-template" type="template">
<div class="row mb-3">
<input type="hidden" name="sensor{{index}}" value="{{address}}"/>
<div class="col-xl-3 col-lg-4 col-sm-6">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Address {{index}}</span>
</div>
<input name="sensor{{index}}address" type="text" class="form-control" value="{{address}}" maxlength="16" disabled/>
</div>
</div>
<div class="col-xl-4 col-lg-3 col-sm-6">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Name</span>
</div>
<input name="sensor{{index}}name" type="text" class="form-control" value="{{name}}" maxlength="16"/>
</div>
</div>
<div class="col-xl-3 col-lg-3 col-sm-6">
<div class="form-check">
<input name="sensor{{index}}common" class="form-check-input" type="checkbox" value="true" id="sensor{{index}}common" {{common}}>
<label class="form-check-label" for="sensor{{index}}common">Include in average</label>
</div>
</div>
<div class="col-xl-2 col-lg-2 col-sm-6">
<span id="temp-{{index}}">{{value}}</span> &deg;C
</div>
</div>
</script>
<form method="post">
<input type="hidden" name="tempConfig" value="true"/>
<div id="sensors" class="my-3 p-3 bg-white rounded shadow">
<div id="loading" class="alert alert-info">Loading temperature sensors</div>
<div id="notemp" class="alert alert-info" style="display: none;">No temperature sensors are configured or found</div>
<div id="error" class="alert alert-danger" style="display: none;">Error loading data</div>
</div>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="/" class="btn btn-outline-secondary">Back</a>
</div>
<div class="col-6 text-right">
<button class="btn btn-primary">Save</button>
</div>
</div>
</form>

7
web/tempsensor.json Normal file
View File

@@ -0,0 +1,7 @@
{
"i" : %d,
"a" : "%s",
"n" : "%s",
"c" : %d,
"v" : %.1f
},

View File

@@ -1,16 +1,17 @@
<form method="post" action="/save">
<input type="hidden" name="authConfig" value="true"/>
<input type="hidden" name="ac" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>Web</h6>
<div class="row">
<div class="col-xl-3 col-lg-4 col-md-7">
<div class="m-2 input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Security</span>
</div>
<select id="authSecurity" class="form-control" name="authSecurity">
<option value="0" ${config.authSecurity0}>None</option>
<option value="1" ${config.authSecurity1}>Only configuration</option>
<option value="2" ${config.authSecurity2}>Everything</option>
<select id="as" class="form-control" name="as">
<option value="0" {as0}>None</option>
<option value="1" {as1}>Only configuration</option>
<option value="2" {as2}>Everything</option>
</select>
</div>
</div>
@@ -19,7 +20,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Username</span>
</div>
<input type="text" class="form-control auth-config" name="authUser" value="${config.authUser}" maxlength="64"/>
<input type="text" class="form-control ac" name="au" value="{au}" maxlength="64"/>
</div>
</div>
<div class="col-xl-3 col-lg-4 col-md-6">
@@ -27,7 +28,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Password</span>
</div>
<input type="password" class="form-control auth-config" name="authPassword" value="${config.authPassword}" maxlength="64"/>
<input type="password" class="form-control ac" name="ap" value="{ap}" maxlength="64"/>
</div>
</div>
</div>

View File

@@ -1,13 +1,14 @@
<form method="post" action="/save">
<input type="hidden" name="wifiConfig" value="true"/>
<input type="hidden" name="wc" value="true"/>
<div class="my-3 p-3 bg-white rounded shadow">
<h6>WiFi</h6>
<div class="row">
<div class="col-xl-3 col-md-6 form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">SSID</span>
</div>
<input type="text" name="wifiSsid" class="form-control" maxlength="32" placeholder="Name of your WiFi" value="${config.wifiSsid}" required/>
<input type="text" name="s" class="form-control" maxlength="32" value="{s}" required/>
</div>
</div>
<div class="col-xl-3 col-md-6 form-group">
@@ -15,7 +16,7 @@
<div class="input-group-prepend">
<span class="input-group-text">PSK</span>
</div>
<input type="password" name="wifiPassword" class="form-control" maxlength="63" placeholder="Password for WiFi" value="${config.wifiPassword}" required/>
<input type="password" name="p" class="form-control" maxlength="63" value="{p}" required/>
</div>
</div>
<div class="col-xl-4 col-md-6 form-group">
@@ -23,20 +24,25 @@
<div class="input-group-prepend">
<span class="input-group-text">Hostname</span>
</div>
<input type="text" name="wifiHostname" class="form-control" maxlength="32" pattern="[a-z0-9_-]+" placeholder="Optional, ex.: ams-reader" value="${config.wifiHostname}"/>
<input type="text" name="h" class="form-control" maxlength="32" pattern="[a-z0-9_-]+" placeholder="Optional, ex.: ams-reader" value="{h}"/>
<div class="input-group-append">
<label class="input-group-text">
<input type="checkbox" name="m" value="true" {m}/> mDNS
</label>
</div>
</div>
</div>
<div class="col-xl-2 col-md-6 form-group">
<label><input id="wifiIpType" type="checkbox" name="wifiIpType" value="1" ${config.wifiStaticIp}/> Static IP</label>
<label><input id="st" type="checkbox" name="st" value="1" {st}/> Static IP</label>
</div>
</div>
<div class="row" id="staticIp">
<div class="row" id="i">
<div class="col-xl-3 col-lg-4 form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">IP</span>
</div>
<input type="text" name="wifiIp" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex: 192.168.1.200" value="${config.wifiIp}"/>
<input type="text" name="i" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{i}"/>
</div>
</div>
<div class="col-xl-3 col-lg-4 form-group">
@@ -44,7 +50,7 @@
<div class="input-group-prepend">
<span class="input-group-text">Subnet</span>
</div>
<input type="text" name="wifiSubnet" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex.: 255.255.255.0" value="${config.wifiSubnet}"/>
<input type="text" name="sn" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{sn}"/>
</div>
</div>
<div class="col-xl-3 col-lg-4 form-group">
@@ -52,23 +58,23 @@
<div class="input-group-prepend">
<span class="input-group-text">Gateway</span>
</div>
<input type="text" name="wifiGw" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex.: 192.168.1.1" value="${config.wifiGw}"/>
<input type="text" name="g" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{g}"/>
</div>
</div>
<div class="col-xl-4 col-lg-5 form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Primary DNS</span>
<span class="input-group-text">DNS 1</span>
</div>
<input type="text" name="wifiDns1" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex.: 192.168.1.1" value="${config.wifiDns1}"/>
<input type="text" name="d1" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d1}"/>
</div>
</div>
<div class="col-xl-4 col-lg-5 form-group">
<div class="input-group input-group-sm">
<div class="input-group-prepend">
<span class="input-group-text">Secondary DNS</span>
<span class="input-group-text">DNS 2</span>
</div>
<input type="text" name="wifiDns2" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" placeholder="Ex.: 8.8.8.8" value="${config.wifiDns2}"/>
<input type="text" name="d2" class="form-control" pattern="\d?\d?\d.\d?\d?\d.\d?\d?\d.\d?\d?\d" value="{d2}"/>
</div>
</div>
</div>

BIN
webui.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 333 KiB

After

Width:  |  Height:  |  Size: 73 KiB