mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-10 20:54:24 +00:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c936f605d6 | ||
|
|
9d0ceb9ca8 | ||
|
|
b33273e3cc | ||
|
|
ab016fff93 | ||
|
|
a215aa7766 | ||
|
|
30d73c3a6e | ||
|
|
bd905c3595 | ||
|
|
1a2e70b1fb | ||
|
|
d6da7b2715 | ||
|
|
059a430f9a | ||
|
|
8ade50c2a6 | ||
|
|
5b94d8ff61 | ||
|
|
232b9c279d | ||
|
|
9b6a6af6ec | ||
|
|
9671e1eba3 | ||
|
|
78e531a7e9 | ||
|
|
478d63505d | ||
|
|
d8dfaaa730 | ||
|
|
f311b70b37 | ||
|
|
52bc2b14c9 | ||
|
|
2ddfc16d6a | ||
|
|
51ab4a7f07 | ||
|
|
e90a30bfde | ||
|
|
a63e6962d3 | ||
|
|
951ccf7de5 | ||
|
|
001e3376df | ||
|
|
9e4bf93f0a | ||
|
|
e5ac823f69 | ||
|
|
73fd228e2f | ||
|
|
fdac8f88af | ||
|
|
9c8552869c | ||
|
|
be75b013c0 | ||
|
|
79274e490d | ||
|
|
47293eebe4 | ||
|
|
90b62638ed | ||
|
|
78e766156d | ||
|
|
4b32af69a7 | ||
|
|
916b5550d9 | ||
|
|
48016240f9 | ||
|
|
518b408205 | ||
|
|
73a0359597 | ||
|
|
bbe82f9cdc | ||
|
|
4786735d4c | ||
|
|
227eb7b6ff | ||
|
|
00edd94033 | ||
|
|
ccd818e1ab | ||
|
|
fc42fb22d8 | ||
|
|
f6066fbbf3 | ||
|
|
9c09740a41 | ||
|
|
4e24e2949a | ||
|
|
eff00f1fe0 | ||
|
|
f484f3eb0e | ||
|
|
b2174dd521 | ||
|
|
fe79a96827 | ||
|
|
517a40b0a6 | ||
|
|
75f3c8c592 | ||
|
|
554cc7c023 | ||
|
|
6d0c722c98 | ||
|
|
d2f93c07bd | ||
|
|
d63516fd49 | ||
|
|
c114b777c7 | ||
|
|
a06729b535 | ||
|
|
0ea21991ea | ||
|
|
1994f16b82 | ||
|
|
b6efae656f | ||
|
|
fa0f93cefe | ||
|
|
058dc658c1 | ||
|
|
f6df84bf9a | ||
|
|
4b9cb27e58 | ||
|
|
5f14517258 | ||
|
|
ad820fff01 | ||
|
|
6d3d4adc7e | ||
|
|
99a23b3bf7 | ||
|
|
15e861ce70 | ||
|
|
fdfadb9bf9 | ||
|
|
388d35c390 | ||
|
|
e466a45b5c | ||
|
|
2e7b9e8e43 | ||
|
|
deb2148e9a | ||
|
|
27cb5dd833 | ||
|
|
7e6ac860fd | ||
|
|
3a7f6078f4 | ||
|
|
dbc551a41d | ||
|
|
9bb596aab1 | ||
|
|
4a3d22e75c | ||
|
|
d68ffc827d | ||
|
|
20c62a63cf | ||
|
|
8f85b43fc3 | ||
|
|
57d8603790 | ||
|
|
fc1f1554d8 | ||
|
|
70ed0b538a | ||
|
|
ced6f125fd | ||
|
|
f76cacf835 | ||
|
|
a8205edae9 | ||
|
|
0a317450f6 | ||
|
|
e9ed6d825a | ||
|
|
a6751c787c | ||
|
|
378dbbcc99 | ||
|
|
aed56b0b56 | ||
|
|
36b37eb2a6 | ||
|
|
550b216ba6 | ||
|
|
7e4047096b | ||
|
|
7fdedd8bcf | ||
|
|
f693869bd4 | ||
|
|
3312f88804 | ||
|
|
50faaca559 | ||
|
|
bf44849ecf | ||
|
|
4bfd9dee9a | ||
|
|
32d58fdd71 | ||
|
|
aef78962fb | ||
|
|
8ee713b616 | ||
|
|
0fa3e3585a | ||
|
|
a88291c0f0 | ||
|
|
ac1609bfac | ||
|
|
22d5839d60 | ||
|
|
8f0932f1f1 | ||
|
|
aeb161455e | ||
|
|
568180d7b2 | ||
|
|
0d99da6c9a |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -2,8 +2,14 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- src/**
|
||||
- lib/**
|
||||
- scripts/**
|
||||
- web/**
|
||||
- platformio.ini
|
||||
branches:
|
||||
- '*'
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
- '!v*.*.*'
|
||||
@@ -34,6 +40,8 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
- name: Configure build targets
|
||||
run: echo "[platformio]\ndefault_envs = hw1esp12e, esp12e, esp32" > platformio-user.ini
|
||||
- name: PlatformIO lib install
|
||||
run: pio lib install
|
||||
- name: PlatformIO run
|
||||
|
||||
58
.github/workflows/release.yml
vendored
58
.github/workflows/release.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
id: release_tag
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: echo ::set-output name=tag::$(echo ${GITHUB_REF:11})
|
||||
run: echo ::set-env name=GITHUB_TAG::$(echo ${GITHUB_REF:11})
|
||||
- name: Cache Python dependencies
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
@@ -51,17 +51,8 @@ jobs:
|
||||
release_name: Release ${{ github.ref }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
- name: Upload esp12e binary to release
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
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 hw1esp12e binary to release
|
||||
uses: actions/upload-release-asset@v1.0.1
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -69,3 +60,48 @@ jobs:
|
||||
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_content_type: application/octet-stream
|
||||
- name: Upload esp32 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32/firmware.bin
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload lolind32 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/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
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,4 +6,7 @@
|
||||
*.sw[op]
|
||||
.vscode
|
||||
.pio
|
||||
platformio-user.ini
|
||||
platformio-user.ini
|
||||
/src/version.h
|
||||
/src/web/root
|
||||
/src/AmsToMqttBridge.ino.cpp
|
||||
|
||||
28
README.md
28
README.md
@@ -1,14 +1,24 @@
|
||||
# AMS <-> MQTT Bridge
|
||||
Orignally designed and coded by [@roarfred](https://github.com/roarfred), see the original repo at [roarfred/AmsToMqttBridge](https://github.com/roarfred/AmsToMqttBridge)
|
||||
|
||||
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 modules. It reads data from the HAN port of the meter and sends this to a configured MQTT bus.
|
||||
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.
|
||||
|
||||
<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.
|
||||
|
||||
- _esp12e_ :: General ESP8266 board with 12E or 12F chip, ex NodeMCU board.
|
||||
- _hw1esp12e_ :: First version hardware with ESP 12E of 12F chip.
|
||||
- _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)
|
||||
@@ -18,3 +28,15 @@ Linux:
|
||||
|
||||
Windows:
|
||||
```esptool.py --port COM1 write_flash 0x0 binary-file.bin```
|
||||
|
||||
|
||||
## Building this project with PlatformIO
|
||||
To build this project, you need [PlatformIO](https://platformio.org/) installed.
|
||||
|
||||
It is recommended to use Visual Studio Code with the PlatformIO plugin for development.
|
||||
|
||||
[Visual Studio Code](https://code.visualstudio.com/download)
|
||||
|
||||
[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
|
||||
BIN
doc/Aidon-HAN-Interface-Description-v11A-ID-34331.pdf
Normal file
BIN
doc/Aidon-HAN-Interface-Description-v11A-ID-34331.pdf
Normal file
Binary file not shown.
@@ -12,9 +12,27 @@ Building this project will require some skills in ordering and assembling electr
|
||||
*The completed board mounted in a [3D printed enclosure](/Enclosure)*
|
||||
|
||||
## Assembly of readily available modules
|
||||
Use a ESP based developmentboard and a M-bus module.
|
||||
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.
|
||||
|
||||
[Adafruit Feather M0 WiFi w/ATWINC1500](https://www.adafruit.com/product/3010)
|
||||
### 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)
|
||||
|
||||
|
||||
BIN
hardware/img/wemos_d1_mini.jpg
Normal file
BIN
hardware/img/wemos_d1_mini.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
3
hardware/v1/kicad/HAN_ESP_TSS721-rescue.dcm
Normal file
3
hardware/v1/kicad/HAN_ESP_TSS721-rescue.dcm
Normal file
@@ -0,0 +1,3 @@
|
||||
EESchema-DOCLIB Version 2.0
|
||||
#
|
||||
#End Doc Library
|
||||
356
hardware/v1/kicad/HAN_ESP_TSS721-rescue.lib
Normal file
356
hardware/v1/kicad/HAN_ESP_TSS721-rescue.lib
Normal file
@@ -0,0 +1,356 @@
|
||||
EESchema-LIBRARY Version 2.4
|
||||
#encoding utf-8
|
||||
#
|
||||
# BSS84-transistors
|
||||
#
|
||||
DEF BSS84-transistors Q 0 0 Y N 1 F N
|
||||
F0 "Q" 200 75 50 H V L CNN
|
||||
F1 "BSS84-transistors" 200 0 50 H V L CNN
|
||||
F2 "TO_SOT_Packages_SMD:SOT-23" 200 -75 50 H I L CIN
|
||||
F3 "" 0 0 50 H I L CNN
|
||||
$FPLIST
|
||||
SOT?23*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
C 65 0 111 0 1 10 N
|
||||
C 100 -70 11 0 1 0 F
|
||||
C 100 70 11 0 1 0 F
|
||||
P 2 0 1 0 0 0 10 0 N
|
||||
P 2 0 1 0 30 -70 100 -70 N
|
||||
P 2 0 1 10 30 -50 30 -90 N
|
||||
P 2 0 1 0 30 0 100 0 N
|
||||
P 2 0 1 10 30 20 30 -20 N
|
||||
P 2 0 1 0 30 70 100 70 N
|
||||
P 2 0 1 10 30 90 30 50 N
|
||||
P 2 0 1 0 100 -70 100 -100 N
|
||||
P 2 0 1 0 100 -70 100 0 N
|
||||
P 2 0 1 0 100 100 100 70 N
|
||||
P 3 0 1 10 10 75 10 -75 10 -75 N
|
||||
P 4 0 1 0 90 0 50 -15 50 15 90 0 F
|
||||
P 4 0 1 0 100 -70 130 -70 130 70 100 70 N
|
||||
P 4 0 1 0 110 -20 115 -15 145 -15 150 -10 N
|
||||
P 4 0 1 0 130 -15 115 10 145 10 130 -15 N
|
||||
X G 1 -200 0 200 R 50 50 1 1 I
|
||||
X S 2 100 -200 100 U 50 50 1 1 P
|
||||
X D 3 100 200 100 D 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# CP-device
|
||||
#
|
||||
DEF CP-device C 0 10 N Y 1 F N
|
||||
F0 "C" 25 100 50 H V L CNN
|
||||
F1 "CP-device" 25 -100 50 H V L CNN
|
||||
F2 "" 38 -150 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
CP_*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -90 20 -90 40 0 1 0 N
|
||||
S -90 20 90 20 0 1 0 N
|
||||
S 90 -20 -90 -40 0 1 0 F
|
||||
S 90 40 -90 40 0 1 0 N
|
||||
S 90 40 90 20 0 1 0 N
|
||||
P 2 0 1 0 -70 90 -30 90 N
|
||||
P 2 0 1 0 -50 110 -50 70 N
|
||||
X ~ 1 0 150 110 D 50 50 1 1 P
|
||||
X ~ 2 0 -150 110 U 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# CP_Small-device
|
||||
#
|
||||
DEF CP_Small-device C 0 10 N N 1 F N
|
||||
F0 "C" 10 70 50 H V L CNN
|
||||
F1 "CP_Small-device" 10 -80 50 H V L CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
CP_*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -60 -12 60 -27 0 1 0 F
|
||||
S -60 27 60 12 0 1 0 N
|
||||
P 2 0 1 0 -50 60 -30 60 N
|
||||
P 2 0 1 0 -40 50 -40 70 N
|
||||
X ~ 1 0 100 73 D 50 50 1 1 P
|
||||
X ~ 2 0 -100 73 U 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# C_Small-device
|
||||
#
|
||||
DEF C_Small-device C 0 10 N N 1 F N
|
||||
F0 "C" 10 70 50 H V L CNN
|
||||
F1 "C_Small-device" 10 -80 50 H V L CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
C_*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
P 2 0 1 13 -60 -20 60 -20 N
|
||||
P 2 0 1 12 -60 20 60 20 N
|
||||
X ~ 1 0 100 80 D 50 50 1 1 P
|
||||
X ~ 2 0 -100 80 U 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# DS18B20-maxim
|
||||
#
|
||||
DEF DS18B20-maxim U 0 40 Y Y 1 F N
|
||||
F0 "U" -150 250 50 H V C CNN
|
||||
F1 "DS18B20-maxim" 0 -250 50 H V C CNN
|
||||
F2 "" -150 250 50 H I C CNN
|
||||
F3 "" -150 250 50 H I C CNN
|
||||
$FPLIST
|
||||
TO-92_*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -200 200 200 -200 0 1 0 N
|
||||
X GND 1 -300 -100 100 R 50 50 1 1 W
|
||||
X DQ 2 -300 0 100 R 50 50 1 1 B
|
||||
X VDD 3 -300 100 100 R 50 50 1 1 W
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# ESP-12E-ESP8266
|
||||
#
|
||||
DEF ESP-12E-ESP8266 U 0 40 Y Y 1 F N
|
||||
F0 "U" 0 -100 50 H V C CNN
|
||||
F1 "ESP-12E-ESP8266" 0 100 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
ESP-12E
|
||||
ESP-12E_SMD
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -600 -600 600 600 1 0 0 N
|
||||
X REST 1 -900 300 300 R 50 50 1 1 I
|
||||
X GPIO15 10 900 -300 300 L 50 50 1 1 B
|
||||
X GPIO2 11 900 -200 300 L 50 50 1 1 B
|
||||
X GPIO0 12 900 -100 300 L 50 50 1 1 B
|
||||
X GPIO4 13 900 0 300 L 50 50 1 1 B
|
||||
X GPIO5 14 900 100 300 L 50 50 1 1 B
|
||||
X RXD 15 900 200 300 L 50 50 1 1 I
|
||||
X TXD 16 900 300 300 L 50 50 1 1 O
|
||||
X CS0 17 -250 -900 300 U 50 50 1 1 B
|
||||
X MISO 18 -150 -900 300 U 50 50 1 1 B
|
||||
X GPIO9 19 -50 -900 300 U 50 50 1 1 B
|
||||
X ADC 2 -900 200 300 R 50 50 1 1 P
|
||||
X GPIO10 20 50 -900 300 U 50 50 1 1 B
|
||||
X MOSI 21 150 -900 300 U 50 50 1 1 B
|
||||
X SCLK 22 250 -900 300 U 50 50 1 1 B
|
||||
X CH_PD 3 -900 100 300 R 50 50 1 1 I
|
||||
X GPIO16 4 -900 0 300 R 50 50 1 1 B
|
||||
X GPIO14 5 -900 -100 300 R 50 50 1 1 B
|
||||
X GPIO12 6 -900 -200 300 R 50 50 1 1 B
|
||||
X GPIO13 7 -900 -300 300 R 50 50 1 1 B
|
||||
X VCC 8 -900 -400 300 R 50 50 1 1 W
|
||||
X GND 9 900 -400 300 L 50 50 1 1 W
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# FTDI_PROG_HDR-ESPProgHeader
|
||||
#
|
||||
DEF FTDI_PROG_HDR-ESPProgHeader J 0 40 Y N 1 F N
|
||||
F0 "J" 0 300 50 H V C CNN
|
||||
F1 "FTDI_PROG_HDR-ESPProgHeader" 0 -400 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
Connector*:*_??x*mm*
|
||||
Connector*:*1x??x*mm*
|
||||
Pin?Header?Straight?1X*
|
||||
Pin?Header?Angled?1X*
|
||||
Socket?Strip?Straight?1X*
|
||||
Socket?Strip?Angled?1X*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -50 -295 0 -305 1 1 6 N
|
||||
S -50 -195 0 -205 1 1 6 N
|
||||
S -50 -95 0 -105 1 1 6 N
|
||||
S -50 5 0 -5 1 1 6 N
|
||||
S -50 105 0 95 1 1 6 N
|
||||
S -50 205 0 195 1 1 6 N
|
||||
S -50 250 50 -350 1 1 10 f
|
||||
X Pin_1 1 -200 200 150 R 50 50 1 1 N
|
||||
X Pin_2 2 -200 100 150 R 50 50 1 1 P
|
||||
X Pin_3 3 -200 0 150 R 50 50 1 1 P
|
||||
X Pin_4 4 -200 -100 150 R 50 50 1 1 N
|
||||
X Pin_5 5 -200 -200 150 R 50 50 1 1 N
|
||||
X Pin_6 6 -200 -300 150 R 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# Jumper-device
|
||||
#
|
||||
DEF Jumper-device JP 0 30 Y N 1 F N
|
||||
F0 "JP" 0 150 50 H V C CNN
|
||||
F1 "Jumper-device" 0 -80 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
DRAW
|
||||
A 0 -26 125 1426 373 0 1 0 N -98 50 99 50
|
||||
C -100 0 35 0 1 0 N
|
||||
C 100 0 35 0 1 0 N
|
||||
X 1 1 -300 0 165 R 50 50 0 1 P
|
||||
X 2 2 300 0 165 L 50 50 0 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# LM1117-3.3-regul
|
||||
#
|
||||
DEF LM1117-3.3-regul U 0 10 Y Y 1 F N
|
||||
F0 "U" -150 125 50 H V C CNN
|
||||
F1 "LM1117-3.3-regul" 0 125 50 H V L CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
SOT?223*
|
||||
TO?263*
|
||||
TO?252*
|
||||
TO?220*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -200 -200 200 75 0 1 10 f
|
||||
X GND 1 0 -300 100 U 50 50 1 1 W
|
||||
X VO 2 300 0 100 L 50 50 1 1 w
|
||||
X VI 3 -300 0 100 R 50 50 1 1 W
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# RJ45-conn
|
||||
#
|
||||
DEF RJ45-conn J 0 40 Y Y 1 F N
|
||||
F0 "J" 200 500 50 H V C CNN
|
||||
F1 "RJ45-conn" -150 500 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
DRAW
|
||||
S -400 -300 400 450 0 1 10 f
|
||||
P 3 0 1 0 -175 200 -175 250 -175 250 N
|
||||
P 3 0 1 0 -125 250 -125 200 -125 200 N
|
||||
P 3 0 1 0 -75 250 -75 200 -75 200 N
|
||||
P 3 0 1 0 -25 250 -25 200 -25 200 N
|
||||
P 3 0 1 0 25 250 25 200 25 200 N
|
||||
P 3 0 1 0 75 250 75 200 75 200 N
|
||||
P 3 0 1 0 125 200 125 250 125 250 N
|
||||
P 3 0 1 0 175 200 175 250 175 250 N
|
||||
P 14 0 1 0 -225 250 225 250 225 -150 125 -150 125 -200 75 -200 75 -250 -75 -250 -75 -200 -125 -200 -125 -150 -225 -150 -225 250 -225 250 N
|
||||
X ~ 1 -350 -450 150 U 50 50 1 1 P
|
||||
X ~ 2 -250 -450 150 U 50 50 1 1 P
|
||||
X ~ 3 -150 -450 150 U 50 50 1 1 P
|
||||
X ~ 4 -50 -450 150 U 50 50 1 1 P
|
||||
X ~ 5 50 -450 150 U 50 50 1 1 P
|
||||
X ~ 6 150 -450 150 U 50 50 1 1 P
|
||||
X ~ 7 250 -450 150 U 50 50 1 1 P
|
||||
X ~ 8 350 -450 150 U 50 50 1 1 P
|
||||
X SHIELD 9 550 350 150 L 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# R_Small-device
|
||||
#
|
||||
DEF R_Small-device R 0 10 N N 1 F N
|
||||
F0 "R" 30 20 50 H V L CNN
|
||||
F1 "R_Small-device" 30 -40 50 H V L CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
R_*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -30 70 30 -70 0 1 8 N
|
||||
X ~ 1 0 100 30 D 50 50 1 1 P
|
||||
X ~ 2 0 -100 30 U 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# SW_Push-switches
|
||||
#
|
||||
DEF SW_Push-switches SW 0 40 N N 1 F N
|
||||
F0 "SW" 50 100 50 H V L CNN
|
||||
F1 "SW_Push-switches" 0 -60 50 H V C CNN
|
||||
F2 "" 0 200 50 H I C CNN
|
||||
F3 "" 0 200 50 H I C CNN
|
||||
DRAW
|
||||
C -80 0 20 0 1 0 N
|
||||
C 80 0 20 0 1 0 N
|
||||
P 2 0 1 0 0 50 0 120 N
|
||||
P 2 0 1 0 100 50 -100 50 N
|
||||
X 1 1 -200 0 100 R 50 50 0 1 P
|
||||
X 2 2 200 0 100 L 50 50 0 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# TSS721-tss721
|
||||
#
|
||||
DEF TSS721-tss721 U 0 40 Y Y 1 F N
|
||||
F0 "U" 200 850 50 H V L CNN
|
||||
F1 "TSS721-tss721" 200 750 50 H V L CNN
|
||||
F2 "" 0 -850 50 H V C CIN
|
||||
F3 "" -200 -800 50 H V C CNN
|
||||
$FPLIST
|
||||
SOIC*3.9x9.9mm*Pitch1.27mm*
|
||||
TSSOP*4.4x5mm*Pitch0.65mm*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -500 -700 500 700 0 1 10 f
|
||||
X BUSL2 1 -600 400 100 R 50 50 1 1 I
|
||||
X VS 10 -600 0 100 R 50 50 1 1 P
|
||||
X VDD 11 0 800 100 D 50 50 1 1 W
|
||||
X RX 12 600 500 100 L 50 50 1 1 I
|
||||
X RXI 13 600 400 100 L 50 50 1 1 I I
|
||||
X RIS 14 300 -800 100 U 50 50 1 1 I
|
||||
X GND 15 0 -800 100 U 50 50 1 1 W
|
||||
X BUSL1 16 -600 500 100 R 50 50 1 1 I
|
||||
X VB 2 -600 -150 100 R 50 50 1 1 P
|
||||
X STC 3 -600 -500 100 R 50 50 1 1 P
|
||||
X RIDD 4 200 -800 100 U 50 50 1 1 O
|
||||
X PF 5 -600 100 100 R 50 50 1 1 I
|
||||
X SC 6 400 -800 100 U 50 50 1 1 P
|
||||
X TXI 7 600 100 100 L 50 50 1 1 O I
|
||||
X TX 8 600 200 100 L 50 50 1 1 O
|
||||
X BAT 9 -100 800 100 D 50 50 1 1 I
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# USB_OTG-conn
|
||||
#
|
||||
DEF USB_OTG-conn J 0 40 Y Y 1 F N
|
||||
F0 "J" -200 450 50 H V L CNN
|
||||
F1 "USB_OTG-conn" -200 350 50 H V L CNN
|
||||
F2 "" 150 -50 50 H I C CNN
|
||||
F3 "" 150 -50 50 H I C CNN
|
||||
$FPLIST
|
||||
USB*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
C -150 85 25 0 1 10 F
|
||||
C -25 135 15 0 1 10 F
|
||||
S -200 -300 200 300 0 1 10 f
|
||||
S -5 -300 5 -270 0 1 0 N
|
||||
S 10 50 -20 20 0 1 10 F
|
||||
S 200 -205 170 -195 0 1 0 N
|
||||
S 200 -105 170 -95 0 1 0 N
|
||||
S 200 -5 170 5 0 1 0 N
|
||||
S 200 195 170 205 0 1 0 N
|
||||
P 2 0 1 10 -75 85 25 85 N
|
||||
P 4 0 1 10 -125 85 -100 85 -50 135 -25 135 N
|
||||
P 4 0 1 10 -100 85 -75 85 -50 35 0 35 N
|
||||
P 4 0 1 10 25 110 25 60 75 85 25 110 F
|
||||
P 5 0 1 0 -170 220 -70 220 -80 190 -160 190 -170 220 F
|
||||
P 9 0 1 0 -185 230 -185 220 -175 190 -175 180 -65 180 -65 190 -55 220 -55 230 -185 230 N
|
||||
X VBUS 1 300 200 100 L 50 50 1 1 W
|
||||
X D- 2 300 -100 100 L 50 50 1 1 P
|
||||
X D+ 3 300 0 100 L 50 50 1 1 P
|
||||
X ID 4 300 -200 100 L 50 50 1 1 P
|
||||
X GND 5 0 -400 100 U 50 50 1 1 W
|
||||
X Shield 6 -100 -400 100 U 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
#End Library
|
||||
@@ -1,2 +1,3 @@
|
||||
(sym_lib_table
|
||||
(lib (name HAN_ESP_TSS721-rescue)(type Legacy)(uri ${KIPRJMOD}/HAN_ESP_TSS721-rescue.lib)(options "")(descr ""))
|
||||
)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
name=HanConfigAp
|
||||
version=1.0.0
|
||||
author=roarfred
|
||||
maintainer=roarfred <not@important.com>
|
||||
sentence=HAN Configuraiton accesspoint
|
||||
paragraph=HAN Configuraiton accesspoint
|
||||
category=Sensors
|
||||
url=https://github.com/roarfred/AmsToMqttBridge
|
||||
architectures=*
|
||||
File diff suppressed because one or more lines are too long
@@ -1,60 +0,0 @@
|
||||
// ap.h
|
||||
|
||||
#ifndef _ACCESSPOINT_h
|
||||
#define _ACCESSPOINT_h
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
#include <DNSServer.h>
|
||||
#include "configuration.h"
|
||||
|
||||
#define INVALID_BUTTON_PIN 0xFFFFFFFF
|
||||
|
||||
class HanConfigAp {
|
||||
public:
|
||||
void setup(int accessPointButtonPin, Stream* debugger);
|
||||
bool loop();
|
||||
bool hasConfig();
|
||||
configuration config;
|
||||
bool isActivated = false;
|
||||
|
||||
private:
|
||||
const char* AP_SSID = "AMS2MQTT";
|
||||
|
||||
// DNS server
|
||||
const byte DNS_PORT = 53;
|
||||
DNSServer dnsServer;
|
||||
|
||||
static size_t print(const char* text);
|
||||
static size_t println(const char* text);
|
||||
static size_t print(const Printable& data);
|
||||
static size_t println(const Printable& data);
|
||||
|
||||
// Web server
|
||||
static void handleRoot();
|
||||
static void handleSave();
|
||||
#if defined(ESP8266)
|
||||
static ESP8266WebServer server;
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
static WebServer server;
|
||||
#endif
|
||||
|
||||
static Stream* debugger;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
bool configuration::hasConfig()
|
||||
{
|
||||
bool hasConfig = false;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
hasConfig = EEPROM.read(EEPROM_CONFIG_ADDRESS) == EEPROM_CHECK_SUM;
|
||||
EEPROM.end();
|
||||
return hasConfig;
|
||||
}
|
||||
|
||||
bool configuration::save()
|
||||
{
|
||||
int address = EEPROM_CONFIG_ADDRESS;
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(address, EEPROM_CHECK_SUM);
|
||||
address++;
|
||||
|
||||
address += saveString(address, ssid);
|
||||
address += saveString(address, ssidPassword);
|
||||
address += saveByte(address, meterType);
|
||||
address += saveString(address, mqtt);
|
||||
address += saveInt(address, mqttPort);
|
||||
address += saveString(address, mqttClientID);
|
||||
address += saveString(address, mqttPublishTopic);
|
||||
address += saveString(address, mqttSubscribeTopic);
|
||||
|
||||
if (isSecure()) {
|
||||
address += saveBool(address, true);
|
||||
address += saveString(address, mqttUser);
|
||||
address += saveString(address, mqttPass);
|
||||
}
|
||||
else
|
||||
address += saveBool(address, false);
|
||||
|
||||
bool success = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
bool configuration::load()
|
||||
{
|
||||
int address = EEPROM_CONFIG_ADDRESS;
|
||||
bool success = false;
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
if (EEPROM.read(address) == EEPROM_CHECK_SUM)
|
||||
{
|
||||
address++;
|
||||
|
||||
address += readString(address, &ssid);
|
||||
address += readString(address, &ssidPassword);
|
||||
address += readByte(address, &meterType);
|
||||
address += readString(address, &mqtt);
|
||||
address += readInt(address, &mqttPort);
|
||||
address += readString(address, &mqttClientID);
|
||||
address += readString(address, &mqttPublishTopic);
|
||||
address += readString(address, &mqttSubscribeTopic);
|
||||
|
||||
bool secure = false;
|
||||
address += readBool(address, &secure);
|
||||
|
||||
if (secure)
|
||||
{
|
||||
address += readString(address, &mqttUser);
|
||||
address += readString(address, &mqttPass);
|
||||
}
|
||||
else
|
||||
{
|
||||
mqttUser = 0;
|
||||
mqttPass = 0;
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ssid = (char*)String("").c_str();
|
||||
ssidPassword = (char*)String("").c_str();
|
||||
meterType = (byte)0;
|
||||
mqtt = (char*)String("").c_str();
|
||||
mqttClientID = (char*)String("").c_str();
|
||||
mqttPublishTopic = (char*)String("").c_str();
|
||||
mqttSubscribeTopic = (char*)String("").c_str();
|
||||
mqttUser = 0;
|
||||
mqttPass = 0;
|
||||
mqttPort = 1883;
|
||||
}
|
||||
EEPROM.end();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool configuration::isSecure()
|
||||
{
|
||||
return (mqttUser != 0) && (String(mqttUser).length() > 0);
|
||||
}
|
||||
|
||||
int configuration::readInt(int address, int *value)
|
||||
{
|
||||
int lower = EEPROM.read(address);
|
||||
int higher = EEPROM.read(address + 1);
|
||||
*value = lower + (higher << 8);
|
||||
return 2;
|
||||
}
|
||||
int configuration::saveInt(int address, int value)
|
||||
{
|
||||
byte lowByte = value & 0xFF;
|
||||
byte highByte = ((value >> 8) & 0xFF);
|
||||
|
||||
EEPROM.write(address, lowByte);
|
||||
EEPROM.write(address + 1, highByte);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int configuration::readBool(int address, bool *value)
|
||||
{
|
||||
byte y = EEPROM.read(address);
|
||||
*value = (bool)y;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int configuration::saveBool(int address, bool value)
|
||||
{
|
||||
byte y = (byte)value;
|
||||
EEPROM.write(address, y);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int configuration::readByte(int address, byte *value)
|
||||
{
|
||||
*value = EEPROM.read(address);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int configuration::saveByte(int address, byte value)
|
||||
{
|
||||
EEPROM.write(address, value);
|
||||
return 1;
|
||||
}
|
||||
void configuration::print(Stream* debugger)
|
||||
{
|
||||
/*
|
||||
char* ssid;
|
||||
char* ssidPassword;
|
||||
byte meterType;
|
||||
char* mqtt;
|
||||
int mqttPort;
|
||||
char* mqttClientID;
|
||||
char* mqttPublishTopic;
|
||||
char* mqttSubscribeTopic;
|
||||
bool secure;
|
||||
char* mqttUser;
|
||||
char* mqttPass;
|
||||
*/
|
||||
|
||||
debugger->println("Configuration:");
|
||||
debugger->println("-----------------------------------------------");
|
||||
debugger->printf("ssid: %s\r\n", this->ssid);
|
||||
debugger->printf("ssidPassword: %s\r\n", this->ssidPassword);
|
||||
debugger->printf("meterType: %i\r\n", this->meterType);
|
||||
debugger->printf("mqtt: %s\r\n", this->mqtt);
|
||||
debugger->printf("mqttPort: %i\r\n", this->mqttPort);
|
||||
debugger->printf("mqttClientID: %s\r\n", this->mqttClientID);
|
||||
debugger->printf("mqttPublishTopic: %s\r\n", this->mqttPublishTopic);
|
||||
debugger->printf("mqttSubscribeTopic: %s\r\n", this->mqttSubscribeTopic);
|
||||
|
||||
if (this->isSecure())
|
||||
{
|
||||
debugger->printf("SECURE MQTT CONNECTION:\r\n");
|
||||
debugger->printf("mqttUser: %s\r\n", this->mqttUser);
|
||||
debugger->printf("mqttPass: %s\r\n", this->mqttPass);
|
||||
}
|
||||
debugger->println("-----------------------------------------------");
|
||||
}
|
||||
|
||||
template <class T> int configuration::writeAnything(int ee, const T& value)
|
||||
{
|
||||
const byte* p = (const byte*)(const void*)&value;
|
||||
unsigned int i;
|
||||
for (i = 0; i < sizeof(value); i++)
|
||||
EEPROM.write(ee++, *p++);
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class T> int configuration::readAnything(int ee, T& value)
|
||||
{
|
||||
byte* p = (byte*)(void*)&value;
|
||||
unsigned int i;
|
||||
for (i = 0; i < sizeof(value); i++)
|
||||
*p++ = EEPROM.read(ee++);
|
||||
return i;
|
||||
}
|
||||
|
||||
int configuration::readString(int pAddress, char* pString[])
|
||||
{
|
||||
int address = 0;
|
||||
byte length = EEPROM.read(pAddress + address);
|
||||
address++;
|
||||
|
||||
char* buffer = new char[length];
|
||||
for (int i = 0; i<length; i++)
|
||||
{
|
||||
buffer[i] = EEPROM.read(pAddress + address++);
|
||||
}
|
||||
*pString = buffer;
|
||||
return address;
|
||||
}
|
||||
int configuration::saveString(int pAddress, char* pString)
|
||||
{
|
||||
int address = 0;
|
||||
int length = strlen(pString) + 1;
|
||||
EEPROM.put(pAddress + address, length);
|
||||
address++;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
EEPROM.put(pAddress + address, pString[i]);
|
||||
address++;
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// config.h
|
||||
|
||||
#ifndef _CONFIGURATION_h
|
||||
#define _CONFIGURATION_h
|
||||
|
||||
#include <EEPROM.h>
|
||||
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
|
||||
class configuration {
|
||||
public:
|
||||
char* ssid;
|
||||
char* ssidPassword;
|
||||
char* mqtt;
|
||||
int mqttPort;
|
||||
char* mqttClientID;
|
||||
char* mqttPublishTopic;
|
||||
char* mqttSubscribeTopic;
|
||||
char* mqttUser;
|
||||
char* mqttPass;
|
||||
byte meterType;
|
||||
|
||||
bool hasConfig();
|
||||
bool isSecure();
|
||||
bool save();
|
||||
bool load();
|
||||
|
||||
void print(Stream* debugger);
|
||||
protected:
|
||||
|
||||
private:
|
||||
const int EEPROM_SIZE = 512;
|
||||
const byte EEPROM_CHECK_SUM = 71; // Used to check if config is stored. Change if structure changes
|
||||
const int EEPROM_CONFIG_ADDRESS = 0;
|
||||
|
||||
int saveString(int pAddress, char* pString);
|
||||
int readString(int pAddress, char* pString[]);
|
||||
int saveInt(int pAddress, int pValue);
|
||||
int readInt(int pAddress, int *pValue);
|
||||
int saveBool(int pAddress, bool pValue);
|
||||
int readBool(int pAddress, bool *pValue);
|
||||
int saveByte(int pAddress, byte pValue);
|
||||
int readByte(int pAddress, byte *pValue);
|
||||
|
||||
|
||||
template <class T> int writeAnything(int ee, const T& value);
|
||||
template <class T> int readAnything(int ee, T& value);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,14 +3,15 @@
|
||||
#ifndef _AIDON_h
|
||||
#define _AIDON_h
|
||||
|
||||
|
||||
enum class Aidon
|
||||
{
|
||||
List1 = 0x01,
|
||||
List1PhaseShort = 0x09,
|
||||
List1PhaseLong = 0xff, // TODO: Need sample
|
||||
List1PhaseLong = 0x0E,
|
||||
List3PhaseShort = 0x0D,
|
||||
List3PhaseLong = 0x12
|
||||
List3PhaseLong = 0x12,
|
||||
List3PhaseITShort = 0x0C,
|
||||
List3PhaseITLong = 0x11,
|
||||
};
|
||||
|
||||
enum class Aidon_List1
|
||||
@@ -73,6 +74,33 @@ enum class Aidon_List1Phase
|
||||
IGN_14,
|
||||
VoltageL1Int8,
|
||||
VoltageL1Enum,
|
||||
IGN_15,
|
||||
Timestamp_OBIS,
|
||||
Timestamp,
|
||||
IGN_16,
|
||||
CumulativeActiveImportEnergy_OBIS,
|
||||
CumulativeActiveImportEnergy,
|
||||
IGN_17,
|
||||
CumulativeActiveImportEnergyInt8,
|
||||
CumulativeActiveImportEnergyEnum,
|
||||
IGN_18,
|
||||
CumulativeActiveExportEnergy_OBIS,
|
||||
CumulativeActiveExportEnergy,
|
||||
IGN_19,
|
||||
CumulativeActiveExportEnergyInt8,
|
||||
CumulativeActiveExportEnergyEnum,
|
||||
IGN_20,
|
||||
CumulativeReactiveImportEnergy_OBIS,
|
||||
CumulativeReactiveImportEnergy,
|
||||
IGN_21,
|
||||
CumulativeReactiveImportEnergyInt8,
|
||||
CumulativeReactiveImportEnergyEnum,
|
||||
IGN_22,
|
||||
CumulativeReactiveExportEnergy_OBIS,
|
||||
CumulativeReactiveExportEnergy,
|
||||
IGN_23,
|
||||
CumulativeReactiveExportEnergyInt8,
|
||||
CumulativeReactiveExportEnergyEnum
|
||||
};
|
||||
|
||||
enum class Aidon_List3Phase
|
||||
@@ -176,6 +204,101 @@ enum class Aidon_List3Phase
|
||||
CumulativeReactiveExportEnergyEnum
|
||||
};
|
||||
|
||||
enum class Aidon_List3PhaseIT
|
||||
{
|
||||
ListSize,
|
||||
IGN_0,
|
||||
ListVersionIdentifier_OBIS,
|
||||
ListVersionIdentifier,
|
||||
IGN_1,
|
||||
MeterID_OBIS,
|
||||
MeterID,
|
||||
IGN_2,
|
||||
MeterType_OBIS,
|
||||
MeterType,
|
||||
IGN_3,
|
||||
ActiveImportPower_OBIS,
|
||||
ActiveImportPower,
|
||||
IGN_4,
|
||||
ActiveImportPowerInt8,
|
||||
ActiveImportPowerEnum,
|
||||
IGN_5,
|
||||
ActiveExportPower_OBIS,
|
||||
ActiveExportPower,
|
||||
IGN_6,
|
||||
ActiveExportPowerInt8,
|
||||
ActiveExportPowerEnum,
|
||||
IGN_7,
|
||||
ReactiveImportPower_OBIS,
|
||||
ReactiveImportPower,
|
||||
IGN_8,
|
||||
ReactiveImportPowerInt8,
|
||||
ReactiveImportPowerEnum,
|
||||
IGN_9,
|
||||
ReactiveExportPower_OBIS,
|
||||
ReactiveExportPower,
|
||||
IGN_10,
|
||||
ReactiveExportPowerInt8,
|
||||
ReactiveExportPowerEnum,
|
||||
IGN_11,
|
||||
CurrentL1_OBIS,
|
||||
CurrentL1,
|
||||
IGN_12,
|
||||
CurrentL1Int8,
|
||||
CurrentL1Enum,
|
||||
IGN_13,
|
||||
CurrentL3_OBIS,
|
||||
CurrentL3,
|
||||
IGN_14,
|
||||
CurrentL3Int8,
|
||||
CurrentL3Enum,
|
||||
IGN_15,
|
||||
VoltageL1_OBIS,
|
||||
VoltageL1,
|
||||
IGN_16,
|
||||
VoltageL1Int8,
|
||||
VoltageL1Enum,
|
||||
IGN_17,
|
||||
VoltageL2_OBIS,
|
||||
VoltageL2,
|
||||
IGN_18,
|
||||
VoltageL2Int8,
|
||||
VoltageL2Enum,
|
||||
IGN_19,
|
||||
VoltageL3_OBIS,
|
||||
VoltageL3,
|
||||
IGN_20,
|
||||
VoltageL3Int8,
|
||||
VoltageL3Enum,
|
||||
IGN_21,
|
||||
Timestamp_OBIS,
|
||||
Timestamp,
|
||||
IGN_22,
|
||||
CumulativeActiveImportEnergy_OBIS,
|
||||
CumulativeActiveImportEnergy,
|
||||
IGN_23,
|
||||
CumulativeActiveImportEnergyInt8,
|
||||
CumulativeActiveImportEnergyEnum,
|
||||
IGN_24,
|
||||
CumulativeActiveExportEnergy_OBIS,
|
||||
CumulativeActiveExportEnergy,
|
||||
IGN_25,
|
||||
CumulativeActiveExportEnergyInt8,
|
||||
CumulativeActiveExportEnergyEnum,
|
||||
IGN_26,
|
||||
CumulativeReactiveImportEnergy_OBIS,
|
||||
CumulativeReactiveImportEnergy,
|
||||
IGN_27,
|
||||
CumulativeReactiveImportEnergyInt8,
|
||||
CumulativeReactiveImportEnergyEnum,
|
||||
IGN_28,
|
||||
CumulativeReactiveExportEnergy_OBIS,
|
||||
CumulativeReactiveExportEnergy,
|
||||
IGN_29,
|
||||
CumulativeReactiveExportEnergyInt8,
|
||||
CumulativeReactiveExportEnergyEnum
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ HanReader::HanReader()
|
||||
|
||||
}
|
||||
|
||||
void HanReader::setup(HardwareSerial *hanPort, Stream *debugPort)
|
||||
void HanReader::setup(Stream *hanPort, Stream *debugPort)
|
||||
{
|
||||
han = hanPort;
|
||||
bytesRead = 0;
|
||||
@@ -13,7 +13,7 @@ void HanReader::setup(HardwareSerial *hanPort, Stream *debugPort)
|
||||
if (debug) debug->println("MBUS serial setup complete");
|
||||
}
|
||||
|
||||
void HanReader::setup(HardwareSerial *hanPort)
|
||||
void HanReader::setup(Stream *hanPort)
|
||||
{
|
||||
setup(hanPort, NULL);
|
||||
}
|
||||
@@ -80,10 +80,10 @@ void HanReader::debugPrint(byte *buffer, int start, int length)
|
||||
|
||||
bool HanReader::read()
|
||||
{
|
||||
if (han->available())
|
||||
{
|
||||
byte newByte = han->read();
|
||||
return read(newByte);
|
||||
while(han->available()) {
|
||||
if(read(han->read())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ public:
|
||||
bool compensateFor09HeaderBug = false;
|
||||
|
||||
HanReader();
|
||||
void setup(HardwareSerial *hanPort);
|
||||
void setup(HardwareSerial *hanPort, Stream *debugPort);
|
||||
void setup(Stream *hanPort);
|
||||
void setup(Stream *hanPort, Stream *debugPort);
|
||||
bool read();
|
||||
bool read(byte data);
|
||||
int getListSize();
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
|
||||
private:
|
||||
Stream *debug;
|
||||
HardwareSerial *han;
|
||||
Stream *han;
|
||||
byte buffer[512];
|
||||
int bytesRead;
|
||||
DlmsReader reader;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#ifndef _KAMSTRUP_h
|
||||
#define _KAMSTRUP_h
|
||||
|
||||
|
||||
enum class Kamstrup
|
||||
{
|
||||
List3PhaseShort = 0x19,
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
name=HanToJson
|
||||
version=1.0.0
|
||||
author=roarfred
|
||||
maintainer=roarfred <not@important.com>
|
||||
sentence=HAN reader data to Json
|
||||
paragraph=HAN reader data to Json
|
||||
category=Sensors
|
||||
url=https://github.com/roarfred/AmsToMqttBridge
|
||||
architectures=*
|
||||
@@ -1,240 +0,0 @@
|
||||
#include "HanToJson.h"
|
||||
#include "Aidon.h"
|
||||
#include "Kaifa.h"
|
||||
#include "Kamstrup.h"
|
||||
|
||||
|
||||
static void hanToJsonKaifa3phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
if (listSize >= (int)Kaifa::List3PhaseShort)
|
||||
{
|
||||
data["lv"] = hanReader.getString( (int)Kaifa_List3Phase::ListVersionIdentifier);
|
||||
data["id"] = hanReader.getString( (int)Kaifa_List3Phase::MeterID);
|
||||
data["type"] = hanReader.getString( (int)Kaifa_List3Phase::MeterType);
|
||||
data["P"] = hanReader.getInt( (int)Kaifa_List3Phase::ActiveImportPower);
|
||||
data["Q"] = hanReader.getInt( (int)Kaifa_List3Phase::ReactiveImportPower);
|
||||
data["I1"] = ((double) hanReader.getInt((int)Kaifa_List3Phase::CurrentL1)) / 1000;
|
||||
data["I2"] = ((double) hanReader.getInt((int)Kaifa_List3Phase::CurrentL2)) / 1000;
|
||||
data["I3"] = ((double) hanReader.getInt((int)Kaifa_List3Phase::CurrentL3)) / 1000;
|
||||
data["U1"] = ((double) hanReader.getInt((int)Kaifa_List3Phase::VoltageL1)) / 10;
|
||||
data["U2"] = ((double) hanReader.getInt((int)Kaifa_List3Phase::VoltageL2)) / 10;
|
||||
data["U3"] = ((double) hanReader.getInt((int)Kaifa_List3Phase::VoltageL3)) / 10;
|
||||
}
|
||||
|
||||
if (listSize >= (int)Kaifa::List3PhaseLong)
|
||||
{
|
||||
data["tPI"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeActiveImportEnergy);
|
||||
data["tPO"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeActiveExportEnergy);
|
||||
data["tQI"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeReactiveImportEnergy);
|
||||
data["tQO"] = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeReactiveExportEnergy);
|
||||
}
|
||||
}
|
||||
|
||||
static void hanToJsonKaifa1phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
if (listSize >= (int)Kaifa::List1PhaseShort)
|
||||
{
|
||||
data["lv"] = hanReader.getString( (int)Kaifa_List1Phase::ListVersionIdentifier);
|
||||
data["id"] = hanReader.getString( (int)Kaifa_List1Phase::MeterID);
|
||||
data["type"] = hanReader.getString( (int)Kaifa_List1Phase::MeterType);
|
||||
data["P"] = hanReader.getInt( (int)Kaifa_List1Phase::ActiveImportPower);
|
||||
data["Q"] = hanReader.getInt( (int)Kaifa_List1Phase::ReactiveImportPower);
|
||||
data["I1"] = ((double) hanReader.getInt((int)Kaifa_List1Phase::CurrentL1)) / 1000;
|
||||
data["U1"] = ((double) hanReader.getInt((int)Kaifa_List1Phase::VoltageL1)) / 10;
|
||||
}
|
||||
|
||||
if (listSize >= (int)Kaifa::List1PhaseLong)
|
||||
{
|
||||
data["tPI"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeActiveImportEnergy);
|
||||
data["tPO"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeActiveExportEnergy);
|
||||
data["tQI"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeReactiveImportEnergy);
|
||||
data["tQO"] = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeReactiveExportEnergy);
|
||||
}
|
||||
}
|
||||
|
||||
static void hanToJsonKaifa(JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
int listSize = hanReader.getListSize();
|
||||
|
||||
if (listSize == (int)Kaifa::List1)
|
||||
{
|
||||
// Handle listSize == 1 specially
|
||||
data["P"] = hanReader.getInt( (int)Kaifa_List1::ActivePowerImported);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (listSize) {
|
||||
case (int)Kaifa::List3PhaseShort:
|
||||
case (int)Kaifa::List3PhaseLong:
|
||||
return hanToJsonKaifa3phase(listSize, data, hanReader, debugger);
|
||||
case (int)Kaifa::List1PhaseShort:
|
||||
case (int)Kaifa::List1PhaseLong:
|
||||
return hanToJsonKaifa1phase(listSize, data, hanReader, debugger);
|
||||
default:
|
||||
if (debugger) debugger->printf("Warning: Unknown listSize %d\n", listSize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void hanToJsonAidon3phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
if (listSize >= (int)Aidon::List3PhaseShort)
|
||||
{
|
||||
data["lv"] = hanReader.getString( (int)Aidon_List3Phase::ListVersionIdentifier);
|
||||
data["id"] = hanReader.getString( (int)Aidon_List3Phase::MeterID);
|
||||
data["type"] = hanReader.getString( (int)Aidon_List3Phase::MeterType);
|
||||
data["P"] = hanReader.getInt( (int)Aidon_List3Phase::ActiveImportPower);
|
||||
data["Q"] = hanReader.getInt( (int)Aidon_List3Phase::ReactiveExportPower);
|
||||
data["I1"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL1)) / 10;
|
||||
data["I2"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL2)) / 10;
|
||||
data["I3"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::CurrentL3)) / 10;
|
||||
data["U1"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL1)) / 10;
|
||||
data["U2"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL2)) / 10;
|
||||
data["U3"] = ((double) hanReader.getInt( (int)Aidon_List3Phase::VoltageL3)) / 10;
|
||||
}
|
||||
|
||||
if (listSize >= (int)Aidon::List3PhaseLong)
|
||||
{
|
||||
data["tPI"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeActiveImportEnergy);
|
||||
data["tPO"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeActiveExportEnergy);
|
||||
data["tQI"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeReactiveImportEnergy);
|
||||
data["tQO"] = hanReader.getInt( (int)Aidon_List3Phase::CumulativeReactiveExportEnergy);
|
||||
}
|
||||
|
||||
// TODO: Do not divide Aidon values by 10!?
|
||||
}
|
||||
|
||||
static void hanToJsonAidon1phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
if (listSize >= (int)Aidon::List1PhaseShort)
|
||||
{
|
||||
data["lv"] = hanReader.getString( (int)Aidon_List1Phase::ListVersionIdentifier);
|
||||
data["id"] = hanReader.getString( (int)Aidon_List1Phase::MeterID);
|
||||
data["type"] = hanReader.getString( (int)Aidon_List1Phase::MeterType);
|
||||
data["P"] = hanReader.getInt( (int)Aidon_List1Phase::ActiveImportPower);
|
||||
data["Q"] = hanReader.getInt( (int)Aidon_List1Phase::ReactiveExportPower);
|
||||
data["I1"] = ((double) hanReader.getInt( (int)Aidon_List1Phase::CurrentL1)) / 10;
|
||||
data["U1"] = ((double) hanReader.getInt( (int)Aidon_List1Phase::VoltageL1)) / 10;
|
||||
}
|
||||
|
||||
// TODO Aidon::List1PhaseLong
|
||||
}
|
||||
|
||||
static void hanToJsonAidon(JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
int listSize = hanReader.getListSize();
|
||||
|
||||
// Based on the list number, get all details
|
||||
// according to OBIS specifications for the meter
|
||||
if (listSize == (int)Aidon::List1)
|
||||
{
|
||||
// Handle listSize == 1 specially
|
||||
data["P"] = hanReader.getInt((int)Aidon_List1::ActiveImportPower);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (listSize) {
|
||||
case (int)Aidon::List3PhaseShort:
|
||||
case (int)Aidon::List3PhaseLong:
|
||||
return hanToJsonAidon3phase(listSize, data, hanReader, debugger);
|
||||
case (int)Aidon::List1PhaseShort:
|
||||
case (int)Aidon::List1PhaseLong:
|
||||
return hanToJsonAidon1phase(listSize, data, hanReader, debugger);
|
||||
default:
|
||||
if (debugger) debugger->printf("Warning: Unknown listSize %d\n", listSize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void hanToJsonKamstrup3phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
if (listSize >= (int)Kamstrup::List3PhaseShort)
|
||||
{
|
||||
data["lv"] = hanReader.getString( (int)Kamstrup_List3Phase::ListVersionIdentifier);
|
||||
data["id"] = hanReader.getString( (int)Kamstrup_List3Phase::MeterID);
|
||||
data["type"] = hanReader.getString( (int)Kamstrup_List3Phase::MeterType);
|
||||
data["P"] = hanReader.getInt( (int)Kamstrup_List3Phase::ActiveImportPower);
|
||||
data["Q"] = hanReader.getInt( (int)Kamstrup_List3Phase::ReactiveImportPower);
|
||||
data["I1"] = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CurrentL1)) / 100;
|
||||
data["I2"] = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CurrentL2)) / 100;
|
||||
data["I3"] = ((double) hanReader.getInt((int)Kamstrup_List3Phase::CurrentL3)) / 100;
|
||||
data["U1"] = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL1);
|
||||
data["U2"] = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL2);
|
||||
data["U3"] = hanReader.getInt( (int)Kamstrup_List3Phase::VoltageL3);
|
||||
}
|
||||
|
||||
if (listSize >= (int)Kamstrup::List3PhaseLong)
|
||||
{
|
||||
data["tPI"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeActiveImportEnergy);
|
||||
data["tPO"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeActiveExportEnergy);
|
||||
data["tQI"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeReactiveImportEnergy);
|
||||
data["tQO"] = hanReader.getInt( (int)Kamstrup_List3Phase::CumulativeReactiveExportEnergy);
|
||||
}
|
||||
}
|
||||
|
||||
static void hanToJsonKamstrup1phase(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
if (listSize >= (int)Kamstrup::List1PhaseShort)
|
||||
{
|
||||
data["lv"] = hanReader.getString( (int)Kamstrup_List1Phase::ListVersionIdentifier);
|
||||
data["id"] = hanReader.getString( (int)Kamstrup_List1Phase::MeterID);
|
||||
data["type"] = hanReader.getString( (int)Kamstrup_List1Phase::MeterType);
|
||||
data["P"] = hanReader.getInt( (int)Kamstrup_List1Phase::ActiveImportPower);
|
||||
data["Q"] = hanReader.getInt( (int)Kamstrup_List1Phase::ReactiveImportPower);
|
||||
data["I1"] = ((double) hanReader.getInt((int)Kamstrup_List1Phase::CurrentL1)) / 100;
|
||||
data["U1"] = hanReader.getInt( (int)Kamstrup_List1Phase::VoltageL1);
|
||||
}
|
||||
|
||||
if (listSize >= (int)Kamstrup::List1PhaseLong)
|
||||
{
|
||||
data["tPI"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeActiveImportEnergy);
|
||||
data["tPO"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeActiveExportEnergy);
|
||||
data["tQI"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeReactiveImportEnergy);
|
||||
data["tQO"] = hanReader.getInt( (int)Kamstrup_List1Phase::CumulativeReactiveExportEnergy);
|
||||
}
|
||||
}
|
||||
|
||||
static void hanToJsonKamstrup(JsonObject& data, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
int listSize = hanReader.getListSize();
|
||||
|
||||
switch (listSize) {
|
||||
case (int)Kamstrup::List3PhaseShort:
|
||||
case (int)Kamstrup::List3PhaseLong:
|
||||
return hanToJsonKamstrup3phase(listSize, data, hanReader, debugger);
|
||||
case (int)Kamstrup::List1PhaseShort:
|
||||
case (int)Kamstrup::List1PhaseLong:
|
||||
return hanToJsonKamstrup1phase(listSize, data, hanReader, debugger);
|
||||
default:
|
||||
if (debugger) debugger->printf("Warning: Unknown listSize %d\n", listSize);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void hanToJson(JsonObject& data, byte meterType, HanReader& hanReader, Stream *debugger)
|
||||
{
|
||||
// Based on the list number, get all details
|
||||
// according to OBIS specifications for the meter
|
||||
switch (meterType)
|
||||
{
|
||||
case 1: // Kaifa
|
||||
return hanToJsonKaifa(data, hanReader, debugger);
|
||||
case 2: // Aidon
|
||||
return hanToJsonAidon(data, hanReader, debugger);
|
||||
case 3: // Kamstrup
|
||||
return hanToJsonKamstrup(data, hanReader, debugger);
|
||||
default:
|
||||
if (debugger) {
|
||||
debugger->print("Meter type ");
|
||||
debugger->print(meterType, HEX);
|
||||
debugger->println(" is unknown");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void hanToJson(JsonObject& data, byte meterType, HanReader& hanReader)
|
||||
{
|
||||
return hanToJson(data, meterType, hanReader, NULL);
|
||||
}
|
||||
@@ -1,17 +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 "HanReader.h"
|
||||
|
||||
void hanToJson(JsonObject& data, byte meterType, HanReader& hanReader);
|
||||
void hanToJson(JsonObject& root, byte meterType, HanReader& hanReader, Stream *debugPort);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -7,7 +7,9 @@ board = esp12e
|
||||
framework = ${common.framework}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags =
|
||||
-D HAS_DALLAS_TEMP_SENSOR=0
|
||||
-D IS_CUSTOM_AMS_BOARD=0
|
||||
-D HW_ROARFRED=1
|
||||
-D DEBUG_MODE=1
|
||||
monitor_speed = 2400
|
||||
monitor_flags = --parity E
|
||||
monitor_flags =
|
||||
--parity
|
||||
E
|
||||
|
||||
@@ -1,33 +1,63 @@
|
||||
[platformio]
|
||||
extra_configs = platformio-user.ini
|
||||
|
||||
|
||||
[common]
|
||||
framework = arduino
|
||||
lib_deps = HanConfigAp@1.0.0, HanReader@1.0.0, HanToJson@1.0.0, ArduinoJson@^6.0.0, MQTT@^2.4.0, DallasTemperature@^3.8.0
|
||||
|
||||
[env:esp12e]
|
||||
platform = espressif8266
|
||||
board = esp12e
|
||||
framework = ${common.framework}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags =
|
||||
-D HAS_DALLAS_TEMP_SENSOR=0
|
||||
-D IS_CUSTOM_AMS_BOARD=0
|
||||
lib_deps = HanReader@1.0.0, ArduinoJson@6.14.1, MQTT@2.4.7, DallasTemperature@3.8.1, EspSoftwareSerial@6.7.1, Base64@1.0.0
|
||||
|
||||
[env:hw1esp12e]
|
||||
platform = espressif8266
|
||||
platform = espressif8266@2.3.3
|
||||
board = esp12e
|
||||
framework = ${common.framework}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags =
|
||||
-D HAS_DALLAS_TEMP_SENSOR=1
|
||||
-D IS_CUSTOM_AMS_BOARD=1
|
||||
-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
|
||||
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
|
||||
platform = espressif32@1.11.2
|
||||
board = featheresp32
|
||||
framework = ${common.framework}
|
||||
lib_deps = ${common.lib_deps}
|
||||
build_flags =
|
||||
-D HAS_DALLAS_TEMP_SENSOR=0
|
||||
-D IS_CUSTOM_AMS_BOARD=0
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
16
scripts/addversion.py
Normal file
16
scripts/addversion.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import os
|
||||
|
||||
FILENAME_VERSION_H = 'src/version.h'
|
||||
version = os.environ.get('GITHUB_TAG')
|
||||
if version == None:
|
||||
version = "SNAPSHOT"
|
||||
|
||||
import datetime
|
||||
|
||||
hf = """
|
||||
#ifndef VERSION
|
||||
#define VERSION "{}"
|
||||
#endif
|
||||
""".format(version)
|
||||
with open(FILENAME_VERSION_H, 'w+') as f:
|
||||
f.write(hf)
|
||||
30
scripts/makeweb.py
Normal file
30
scripts/makeweb.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
webroot = "web"
|
||||
srcroot = "src/web/root"
|
||||
|
||||
|
||||
if os.path.exists(srcroot):
|
||||
shutil.rmtree(srcroot)
|
||||
os.mkdir(srcroot)
|
||||
else:
|
||||
os.mkdir(srcroot)
|
||||
|
||||
for filename in os.listdir(webroot):
|
||||
basename = re.sub("[^0-9a-zA-Z]+", "_", filename)
|
||||
|
||||
srcfile = webroot + "/" + filename
|
||||
dstfile = srcroot + "/" + basename + ".h"
|
||||
|
||||
varname = basename.upper()
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(\n")
|
||||
with open(srcfile, "r") as src:
|
||||
for line in src.readlines():
|
||||
dst.write(line)
|
||||
dst.write("\n)==\"==\";\n")
|
||||
602
src/AmsConfiguration.cpp
Normal file
602
src/AmsConfiguration.cpp
Normal file
@@ -0,0 +1,602 @@
|
||||
#include "AmsConfiguration.h"
|
||||
|
||||
String AmsConfiguration::getWifiSsid() {
|
||||
return wifiSsid;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setWifiSsid(String wifiSsid) {
|
||||
wifiChanged |= this->wifiSsid != wifiSsid;
|
||||
this->wifiSsid = String(wifiSsid);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getWifiPassword() {
|
||||
return wifiPassword;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setWifiPassword(String wifiPassword) {
|
||||
wifiChanged |= this->wifiPassword != wifiPassword;
|
||||
this->wifiPassword = String(wifiPassword);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getWifiIp() {
|
||||
return wifiIp;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setWifiIp(String wifiIp) {
|
||||
wifiChanged |= this->wifiIp != wifiIp;
|
||||
this->wifiIp = String(wifiIp);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getWifiGw() {
|
||||
return wifiGw;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setWifiGw(String wifiGw) {
|
||||
wifiChanged |= this->wifiGw != wifiGw;
|
||||
this->wifiGw = String(wifiGw);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getWifiSubnet() {
|
||||
return wifiSubnet;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setWifiSubnet(String wifiSubnet) {
|
||||
wifiChanged |= this->wifiSubnet != wifiSubnet;
|
||||
this->wifiSubnet = String(wifiSubnet);
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearWifiIp() {
|
||||
setWifiIp("");
|
||||
setWifiGw("");
|
||||
setWifiSubnet("");
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isWifiChanged() {
|
||||
return wifiChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackWifiChange() {
|
||||
wifiChanged = false;
|
||||
}
|
||||
|
||||
|
||||
String AmsConfiguration::getMqttHost() {
|
||||
return mqttHost;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttHost(String mqttHost) {
|
||||
mqttChanged |= this->mqttHost != mqttHost;
|
||||
this->mqttHost = String(mqttHost);
|
||||
}
|
||||
|
||||
int AmsConfiguration::getMqttPort() {
|
||||
return mqttPort;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttPort(int mqttPort) {
|
||||
mqttChanged |= this->mqttPort != mqttPort;
|
||||
this->mqttPort = mqttPort;
|
||||
}
|
||||
|
||||
String AmsConfiguration::getMqttClientId() {
|
||||
return mqttClientId;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttClientId(String mqttClientId) {
|
||||
mqttChanged |= this->mqttClientId != mqttClientId;
|
||||
this->mqttClientId = String(mqttClientId);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getMqttPublishTopic() {
|
||||
return mqttPublishTopic;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttPublishTopic(String mqttPublishTopic) {
|
||||
mqttChanged |= this->mqttPublishTopic != mqttPublishTopic;
|
||||
this->mqttPublishTopic = String(mqttPublishTopic);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getMqttSubscribeTopic() {
|
||||
return mqttSubscribeTopic;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttSubscribeTopic(String mqttSubscribeTopic) {
|
||||
mqttChanged |= this->mqttSubscribeTopic != mqttSubscribeTopic;
|
||||
this->mqttSubscribeTopic = String(mqttSubscribeTopic);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getMqttUser() {
|
||||
return mqttUser;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttUser(String mqttUser) {
|
||||
mqttChanged |= this->mqttUser != mqttUser;
|
||||
this->mqttUser = String(mqttUser);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getMqttPassword() {
|
||||
return mqttPassword;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMqttPassword(String mqttPassword) {
|
||||
mqttChanged |= this->mqttPassword != mqttPassword;
|
||||
this->mqttPassword = String(mqttPassword);
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearMqtt() {
|
||||
setMqttHost("");
|
||||
setMqttPort(1883);
|
||||
setMqttClientId("");
|
||||
setMqttPublishTopic("");
|
||||
setMqttSubscribeTopic("");
|
||||
setMqttUser("");
|
||||
setMqttPassword("");
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isMqttChanged() {
|
||||
return mqttChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackMqttChange() {
|
||||
mqttChanged = false;
|
||||
}
|
||||
|
||||
|
||||
byte AmsConfiguration::getAuthSecurity() {
|
||||
return authSecurity;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setAuthSecurity(byte authSecurity) {
|
||||
this->authSecurity = authSecurity;
|
||||
}
|
||||
|
||||
String AmsConfiguration::getAuthUser() {
|
||||
return authUser;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setAuthUser(String authUser) {
|
||||
this->authUser = String(authUser);
|
||||
}
|
||||
|
||||
String AmsConfiguration::getAuthPassword() {
|
||||
return authPassword;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setAuthPassword(String authPassword) {
|
||||
this->authPassword = String(authPassword);
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearAuth() {
|
||||
setAuthSecurity(0);
|
||||
setAuthUser("");
|
||||
setAuthPassword("");
|
||||
}
|
||||
|
||||
int AmsConfiguration::getMeterType() {
|
||||
return this->meterType;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMeterType(int meterType) {
|
||||
this->meterType = meterType;
|
||||
}
|
||||
|
||||
int AmsConfiguration::getDistributionSystem() {
|
||||
return this->distributionSystem;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setDistributionSystem(int distributionSystem) {
|
||||
this->distributionSystem = distributionSystem;
|
||||
}
|
||||
|
||||
int AmsConfiguration::getMainFuse() {
|
||||
return this->mainFuse;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setMainFuse(int mainFuse) {
|
||||
this->mainFuse = mainFuse;
|
||||
}
|
||||
|
||||
int AmsConfiguration::getProductionCapacity() {
|
||||
return this->productionCapacity;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setProductionCapacity(int productionCapacity) {
|
||||
this->productionCapacity = productionCapacity;
|
||||
}
|
||||
|
||||
|
||||
bool AmsConfiguration::hasConfig() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
int configVersion = EEPROM.read(EEPROM_CONFIG_ADDRESS);
|
||||
EEPROM.end();
|
||||
switch(configVersion) {
|
||||
case 71:
|
||||
case 72:
|
||||
case 75:
|
||||
case 80:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::load() {
|
||||
int address = EEPROM_CONFIG_ADDRESS;
|
||||
bool success = false;
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
int cs = EEPROM.read(address);
|
||||
address++;
|
||||
switch(cs) {
|
||||
case 71: // Same as 72
|
||||
case 72:
|
||||
success = loadConfig72(address);
|
||||
break;
|
||||
case 75:
|
||||
success = loadConfig75(address);
|
||||
break;
|
||||
case 80:
|
||||
success = loadConfig80(address);
|
||||
break;
|
||||
}
|
||||
EEPROM.end();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::loadConfig72(int address) {
|
||||
char* temp;
|
||||
|
||||
address += readString(address, &temp);
|
||||
setWifiSsid(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiPassword(temp);
|
||||
|
||||
byte b;
|
||||
address += readByte(address, &b);
|
||||
setMeterType(b);
|
||||
|
||||
address += readString(address, &temp);
|
||||
setMqttHost(temp);
|
||||
int port;
|
||||
address += readInt(address, &port);
|
||||
setMqttPort(port);
|
||||
address += readString(address, &temp);
|
||||
setMqttClientId(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPublishTopic(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttSubscribeTopic(temp);
|
||||
|
||||
bool secure = false;
|
||||
address += readBool(address, &secure);
|
||||
if (secure) {
|
||||
address += readString(address, &temp);
|
||||
setMqttUser(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPassword(temp);
|
||||
} else {
|
||||
setMqttUser("");
|
||||
setMqttPassword("");
|
||||
}
|
||||
|
||||
clearAuth();
|
||||
|
||||
setWifiIp("");
|
||||
setWifiGw("");
|
||||
setWifiSubnet("");
|
||||
setMainFuse(0);
|
||||
setProductionCapacity(0);
|
||||
setDistributionSystem(0);
|
||||
|
||||
ackWifiChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::loadConfig75(int address) {
|
||||
char* temp;
|
||||
|
||||
address += readString(address, &temp);
|
||||
setWifiSsid(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiPassword(temp);
|
||||
|
||||
byte b;
|
||||
address += readByte(address, &b);
|
||||
setMeterType(b);
|
||||
|
||||
bool mqtt = false;
|
||||
address += readBool(address, &mqtt);
|
||||
if(mqtt) {
|
||||
address += readString(address, &temp);
|
||||
setMqttHost(temp);
|
||||
int port;
|
||||
address += readInt(address, &port);
|
||||
setMqttPort(port);
|
||||
address += readString(address, &temp);
|
||||
setMqttClientId(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPublishTopic(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttSubscribeTopic(temp);
|
||||
}
|
||||
|
||||
bool secure = false;
|
||||
address += readBool(address, &secure);
|
||||
if (secure) {
|
||||
address += readString(address, &temp);
|
||||
setMqttUser(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPassword(temp);
|
||||
} else {
|
||||
setMqttUser("");
|
||||
setMqttPassword("");
|
||||
}
|
||||
|
||||
address += readByte(address, &authSecurity);
|
||||
if (authSecurity > 0) {
|
||||
address += readString(address, &temp);
|
||||
setAuthUser(temp);
|
||||
address += readString(address, &temp);
|
||||
setAuthPassword(temp);
|
||||
} else {
|
||||
clearAuth();
|
||||
}
|
||||
|
||||
int i;
|
||||
address += readInt(address, &i);
|
||||
setMainFuse(i);
|
||||
address += readByte(address, &b);
|
||||
setDistributionSystem(b);
|
||||
|
||||
setWifiIp("");
|
||||
setWifiGw("");
|
||||
setWifiSubnet("");
|
||||
setProductionCapacity(0);
|
||||
|
||||
ackWifiChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::loadConfig80(int address) {
|
||||
char* temp;
|
||||
|
||||
address += readString(address, &temp);
|
||||
setWifiSsid(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiPassword(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiIp(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiGw(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiSubnet(temp);
|
||||
|
||||
bool mqtt = false;
|
||||
address += readBool(address, &mqtt);
|
||||
if(mqtt) {
|
||||
address += readString(address, &temp);
|
||||
setMqttHost(temp);
|
||||
int port;
|
||||
address += readInt(address, &port);
|
||||
setMqttPort(port);
|
||||
address += readString(address, &temp);
|
||||
setMqttClientId(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPublishTopic(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttSubscribeTopic(temp);
|
||||
|
||||
bool secure = false;
|
||||
address += readBool(address, &secure);
|
||||
if (secure)
|
||||
{
|
||||
address += readString(address, &temp);
|
||||
setMqttUser(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPassword(temp);
|
||||
} else {
|
||||
setMqttUser("");
|
||||
setMqttPassword("");
|
||||
}
|
||||
} else {
|
||||
clearMqtt();
|
||||
}
|
||||
|
||||
address += readByte(address, &authSecurity);
|
||||
if (authSecurity > 0) {
|
||||
address += readString(address, &temp);
|
||||
setAuthUser(temp);
|
||||
address += readString(address, &temp);
|
||||
setAuthPassword(temp);
|
||||
} else {
|
||||
clearAuth();
|
||||
}
|
||||
|
||||
int i;
|
||||
address += readInt(address, &i);
|
||||
setMeterType(i);
|
||||
address += readInt(address, &i);
|
||||
setDistributionSystem(i);
|
||||
address += readInt(address, &i);
|
||||
setMainFuse(i);
|
||||
address += readInt(address, &i);
|
||||
setProductionCapacity(i);
|
||||
|
||||
ackWifiChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
int address = EEPROM_CONFIG_ADDRESS;
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(address, EEPROM_CHECK_SUM);
|
||||
address++;
|
||||
|
||||
address += saveString(address, wifiSsid.c_str());
|
||||
address += saveString(address, wifiPassword.c_str());
|
||||
address += saveString(address, wifiIp.c_str());
|
||||
address += saveString(address, wifiGw.c_str());
|
||||
address += saveString(address, wifiSubnet.c_str());
|
||||
if(mqttHost) {
|
||||
address += saveBool(address, true);
|
||||
address += saveString(address, mqttHost.c_str());
|
||||
address += saveInt(address, mqttPort);
|
||||
address += saveString(address, mqttClientId.c_str());
|
||||
address += saveString(address, mqttPublishTopic.c_str());
|
||||
address += saveString(address, mqttSubscribeTopic.c_str());
|
||||
if (mqttUser) {
|
||||
address += saveBool(address, true);
|
||||
address += saveString(address, mqttUser.c_str());
|
||||
address += saveString(address, mqttPassword.c_str());
|
||||
} else {
|
||||
address += saveBool(address, false);
|
||||
}
|
||||
} else {
|
||||
address += saveBool(address, false);
|
||||
}
|
||||
|
||||
address += saveByte(address, authSecurity);
|
||||
if (authSecurity > 0) {
|
||||
address += saveString(address, authUser.c_str());
|
||||
address += saveString(address, authPassword.c_str());
|
||||
}
|
||||
|
||||
address += saveInt(address, meterType);
|
||||
address += saveInt(address, distributionSystem);
|
||||
address += saveInt(address, mainFuse);
|
||||
address += saveInt(address, productionCapacity);
|
||||
|
||||
bool success = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
int AmsConfiguration::readString(int pAddress, char* pString[]) {
|
||||
int address = 0;
|
||||
byte length = EEPROM.read(pAddress + address);
|
||||
address++;
|
||||
|
||||
char* buffer = new char[length];
|
||||
for (int i = 0; i<length; i++)
|
||||
{
|
||||
buffer[i] = EEPROM.read(pAddress + address++);
|
||||
}
|
||||
*pString = buffer;
|
||||
return address;
|
||||
}
|
||||
|
||||
int AmsConfiguration::saveString(int pAddress, const char* pString) {
|
||||
int address = 0;
|
||||
int length = pString ? strlen(pString) + 1 : 0;
|
||||
EEPROM.put(pAddress + address, length);
|
||||
address++;
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
EEPROM.put(pAddress + address, pString[i]);
|
||||
address++;
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
int AmsConfiguration::readInt(int address, int *value) {
|
||||
int lower = EEPROM.read(address);
|
||||
int higher = EEPROM.read(address + 1);
|
||||
*value = lower + (higher << 8);
|
||||
return 2;
|
||||
}
|
||||
|
||||
int AmsConfiguration::saveInt(int address, int value) {
|
||||
byte lowByte = value & 0xFF;
|
||||
byte highByte = ((value >> 8) & 0xFF);
|
||||
|
||||
EEPROM.write(address, lowByte);
|
||||
EEPROM.write(address + 1, highByte);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int AmsConfiguration::readBool(int address, bool *value) {
|
||||
byte y = EEPROM.read(address);
|
||||
*value = (bool)y;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AmsConfiguration::saveBool(int address, bool value) {
|
||||
byte y = (byte)value;
|
||||
EEPROM.write(address, y);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AmsConfiguration::readByte(int address, byte *value) {
|
||||
*value = EEPROM.read(address);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AmsConfiguration::saveByte(int address, byte value) {
|
||||
EEPROM.write(address, value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <class T> int AmsConfiguration::writeAnything(int ee, const T& value) {
|
||||
const byte* p = (const byte*)(const void*)&value;
|
||||
unsigned int i;
|
||||
for (i = 0; i < sizeof(value); i++)
|
||||
EEPROM.write(ee++, *p++);
|
||||
return i;
|
||||
}
|
||||
|
||||
template <class T> int AmsConfiguration::readAnything(int ee, T& value) {
|
||||
byte* p = (byte*)(void*)&value;
|
||||
unsigned int i;
|
||||
for (i = 0; i < sizeof(value); i++)
|
||||
*p++ = EEPROM.read(ee++);
|
||||
return i;
|
||||
}
|
||||
|
||||
void AmsConfiguration::print(Stream* debugger)
|
||||
{
|
||||
debugger->println("Configuration:");
|
||||
debugger->println("-----------------------------------------------");
|
||||
debugger->printf("WiFi SSID: %s\r\n", this->getWifiSsid().c_str());
|
||||
debugger->printf("WiFi Psk: %s\r\n", this->getWifiPassword().c_str());
|
||||
|
||||
if(getWifiIp()) {
|
||||
debugger->printf("IP: %s\r\n", this->getWifiIp().c_str());
|
||||
debugger->printf("Gateway: %s\r\n", this->getWifiGw().c_str());
|
||||
debugger->printf("Subnet: %s\r\n", this->getWifiSubnet().c_str());
|
||||
}
|
||||
|
||||
if(getMqttHost()) {
|
||||
debugger->printf("mqttHost: %s\r\n", this->getMqttHost().c_str());
|
||||
debugger->printf("mqttPort: %i\r\n", this->getMqttPort());
|
||||
debugger->printf("mqttClientID: %s\r\n", this->getMqttClientId().c_str());
|
||||
debugger->printf("mqttPublishTopic: %s\r\n", this->getMqttPublishTopic().c_str());
|
||||
debugger->printf("mqttSubscribeTopic: %s\r\n", this->getMqttSubscribeTopic().c_str());
|
||||
if (this->getMqttUser()) {
|
||||
debugger->printf("SECURE MQTT CONNECTION:\r\n");
|
||||
debugger->printf("mqttUser: %s\r\n", this->getMqttUser().c_str());
|
||||
debugger->printf("mqttPass: %s\r\n", this->getMqttPassword().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (this->getAuthSecurity()) {
|
||||
debugger->printf("WEB AUTH:\r\n");
|
||||
debugger->printf("authSecurity: %i\r\n", this->getAuthSecurity());
|
||||
debugger->printf("authUser: %s\r\n", this->getAuthUser().c_str());
|
||||
debugger->printf("authPass: %s\r\n", this->getAuthPassword().c_str());
|
||||
}
|
||||
|
||||
debugger->printf("meterType: %i\r\n", this->getMeterType());
|
||||
debugger->printf("distSys: %i\r\n", this->getDistributionSystem());
|
||||
debugger->printf("fuseSize: %i\r\n", this->getMainFuse());
|
||||
debugger->printf("productionCapacity: %i\r\n", this->getProductionCapacity());
|
||||
|
||||
debugger->println("-----------------------------------------------");
|
||||
}
|
||||
110
src/AmsConfiguration.h
Normal file
110
src/AmsConfiguration.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#ifndef _AMSCONFIGURATION_h
|
||||
#define _AMSCONFIGURATION_h
|
||||
#include <EEPROM.h>
|
||||
#include "Arduino.h"
|
||||
|
||||
class AmsConfiguration {
|
||||
public:
|
||||
bool hasConfig();
|
||||
bool load();
|
||||
bool save();
|
||||
|
||||
String getWifiSsid();
|
||||
void setWifiSsid(String wifiSsid);
|
||||
String getWifiPassword();
|
||||
void setWifiPassword(String wifiPassword);
|
||||
String getWifiIp();
|
||||
void setWifiIp(String wifiIp);
|
||||
String getWifiGw();
|
||||
void setWifiGw(String wifiGw);
|
||||
String getWifiSubnet();
|
||||
void setWifiSubnet(String wifiSubnet);
|
||||
void clearWifiIp();
|
||||
|
||||
bool isWifiChanged();
|
||||
void ackWifiChange();
|
||||
|
||||
String getMqttHost();
|
||||
void setMqttHost(String mqttHost);
|
||||
int getMqttPort();
|
||||
void setMqttPort(int mqttPort);
|
||||
String getMqttClientId();
|
||||
void setMqttClientId(String mqttClientId);
|
||||
String getMqttPublishTopic();
|
||||
void setMqttPublishTopic(String mqttPublishTopic);
|
||||
String getMqttSubscribeTopic();
|
||||
void setMqttSubscribeTopic(String mqttSubscribeTopic);
|
||||
String getMqttUser();
|
||||
void setMqttUser(String mqttUser);
|
||||
String getMqttPassword();
|
||||
void setMqttPassword(String mqttPassword);
|
||||
void clearMqtt();
|
||||
|
||||
bool isMqttChanged();
|
||||
void ackMqttChange();
|
||||
|
||||
byte getAuthSecurity();
|
||||
void setAuthSecurity(byte authSecurity);
|
||||
String getAuthUser();
|
||||
void setAuthUser(String authUser);
|
||||
String getAuthPassword();
|
||||
void setAuthPassword(String authPassword);
|
||||
void clearAuth();
|
||||
|
||||
int getMeterType();
|
||||
void setMeterType(int meterType);
|
||||
int getDistributionSystem();
|
||||
void setDistributionSystem(int distributionSystem);
|
||||
int getMainFuse();
|
||||
void setMainFuse(int mainFuse);
|
||||
int getProductionCapacity();
|
||||
void setProductionCapacity(int productionCapacity);
|
||||
|
||||
void print(Stream* debugger);
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
String wifiSsid;
|
||||
String wifiPassword;
|
||||
String wifiIp;
|
||||
String wifiGw;
|
||||
String wifiSubnet;
|
||||
bool wifiChanged;
|
||||
|
||||
String mqttHost;
|
||||
int mqttPort;
|
||||
String mqttClientId;
|
||||
String mqttPublishTopic;
|
||||
String mqttSubscribeTopic;
|
||||
String mqttUser;
|
||||
String mqttPassword;
|
||||
bool mqttChanged;
|
||||
|
||||
byte authSecurity;
|
||||
String authUser;
|
||||
String authPassword;
|
||||
|
||||
int meterType, distributionSystem, mainFuse, productionCapacity;
|
||||
|
||||
const int EEPROM_SIZE = 512;
|
||||
const int EEPROM_CHECK_SUM = 80; // Used to check if config is stored. Change if structure changes
|
||||
const int EEPROM_CONFIG_ADDRESS = 0;
|
||||
|
||||
bool loadConfig72(int address);
|
||||
bool loadConfig75(int address);
|
||||
bool loadConfig80(int address);
|
||||
|
||||
int saveString(int pAddress, const char* pString);
|
||||
int readString(int pAddress, char* pString[]);
|
||||
int saveInt(int pAddress, int pValue);
|
||||
int readInt(int pAddress, int *pValue);
|
||||
int saveBool(int pAddress, bool pValue);
|
||||
int readBool(int pAddress, bool *pValue);
|
||||
int saveByte(int pAddress, byte pValue);
|
||||
int readByte(int pAddress, byte *pValue);
|
||||
|
||||
template <class T> int writeAnything(int ee, const T& value);
|
||||
template <class T> int readAnything(int ee, T& value);
|
||||
};
|
||||
#endif
|
||||
350
src/AmsData.cpp
Normal file
350
src/AmsData.cpp
Normal file
@@ -0,0 +1,350 @@
|
||||
#include "AmsData.h"
|
||||
#include "Kaifa.h"
|
||||
#include "Aidon.h"
|
||||
#include "Kamstrup.h"
|
||||
|
||||
AmsData::AmsData() {}
|
||||
|
||||
AmsData::AmsData(int meterType, HanReader& hanReader) {
|
||||
lastUpdateMillis = millis();
|
||||
packageTimestamp = hanReader.getPackageTime();
|
||||
|
||||
int listSize = hanReader.getListSize();
|
||||
switch(meterType) {
|
||||
case METER_TYPE_KAIFA:
|
||||
extractFromKaifa(hanReader, listSize);
|
||||
break;
|
||||
case METER_TYPE_AIDON:
|
||||
extractFromAidon(hanReader, listSize);
|
||||
break;
|
||||
case METER_TYPE_KAMSTRUP:
|
||||
extractFromKamstrup(hanReader, listSize);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void AmsData::extractFromKaifa(HanReader& hanReader, int listSize) {
|
||||
switch(listSize) {
|
||||
case (int)Kaifa::List1:
|
||||
listType = 1;
|
||||
break;
|
||||
case (int)Kaifa::List3PhaseShort:
|
||||
threePhase = true;
|
||||
case (int)Kaifa::List1PhaseShort:
|
||||
listType = 2;
|
||||
break;
|
||||
case (int)Kaifa::List3PhaseLong:
|
||||
threePhase = true;
|
||||
case (int)Kaifa::List1PhaseLong:
|
||||
listType = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
if(listSize == (int)Kaifa::List1) {
|
||||
activeImportPower = hanReader.getInt((int)Kaifa_List1::ActivePowerImported);
|
||||
} else {
|
||||
switch(listSize) {
|
||||
case (int)Kaifa::List3PhaseLong:
|
||||
meterTimestamp = hanReader.getTime( (int)Kaifa_List3Phase::MeterClock);
|
||||
activeImportCounter = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeActiveImportEnergy);
|
||||
activeExportCounter = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeActiveExportEnergy);
|
||||
reactiveImportCounter = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeReactiveImportEnergy);
|
||||
reactiveExportCounter = hanReader.getInt( (int)Kaifa_List3Phase::CumulativeReactiveExportEnergy);
|
||||
case (int)Kaifa::List3PhaseShort:
|
||||
listId = hanReader.getString( (int)Kaifa_List3Phase::ListVersionIdentifier);
|
||||
meterId = hanReader.getString( (int)Kaifa_List3Phase::MeterID);
|
||||
meterType = hanReader.getString( (int)Kaifa_List3Phase::MeterType);
|
||||
activeImportPower = hanReader.getInt( (int)Kaifa_List3Phase::ActiveImportPower);
|
||||
reactiveImportPower = hanReader.getInt( (int)Kaifa_List3Phase::ReactiveImportPower);
|
||||
activeExportPower = hanReader.getInt( (int)Kaifa_List3Phase::ActiveExportPower);
|
||||
reactiveExportPower = hanReader.getInt( (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;
|
||||
break;
|
||||
case (int)Kaifa::List1PhaseLong:
|
||||
meterTimestamp = hanReader.getTime( (int)Kaifa_List1Phase::MeterClock);
|
||||
activeImportCounter = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeActiveImportEnergy);
|
||||
activeExportCounter = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeActiveExportEnergy);
|
||||
reactiveImportCounter = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeReactiveImportEnergy);
|
||||
reactiveExportCounter = hanReader.getInt( (int)Kaifa_List1Phase::CumulativeReactiveExportEnergy);
|
||||
case (int)Kaifa::List1PhaseShort:
|
||||
listId = hanReader.getString( (int)Kaifa_List1Phase::ListVersionIdentifier);
|
||||
meterId = hanReader.getString( (int)Kaifa_List1Phase::MeterID);
|
||||
meterType = hanReader.getString( (int)Kaifa_List1Phase::MeterType);
|
||||
activeImportPower = hanReader.getInt( (int)Kaifa_List1Phase::ActiveImportPower);
|
||||
reactiveImportPower = hanReader.getInt( (int)Kaifa_List1Phase::ReactiveImportPower);
|
||||
activeExportPower = hanReader.getInt( (int)Kaifa_List1Phase::ActiveExportPower);
|
||||
reactiveExportPower = hanReader.getInt( (int)Kaifa_List1Phase::ReactiveExportPower);
|
||||
l1current = ((double) hanReader.getInt((int)Kaifa_List1Phase::CurrentL1)) / 1000;
|
||||
l1voltage = ((double) hanReader.getInt((int)Kaifa_List1Phase::VoltageL1)) / 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AmsData::extractFromAidon(HanReader& hanReader, int listSize) {
|
||||
switch(listSize) {
|
||||
case (int)Aidon::List1:
|
||||
listType = 1;
|
||||
break;
|
||||
case (int)Aidon::List3PhaseITShort:
|
||||
case (int)Aidon::List3PhaseShort:
|
||||
threePhase = true;
|
||||
case (int)Aidon::List1PhaseShort:
|
||||
listType = 2;
|
||||
break;
|
||||
case (int)Aidon::List3PhaseITLong:
|
||||
case (int)Aidon::List3PhaseLong:
|
||||
threePhase = true;
|
||||
case (int)Aidon::List1PhaseLong:
|
||||
listType = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
if(listSize == (int)Aidon::List1) {
|
||||
activeImportPower = hanReader.getInt((int)Aidon_List1::ActiveImportPower);
|
||||
} else {
|
||||
switch(listSize) {
|
||||
case (int)Aidon::List3PhaseLong:
|
||||
meterTimestamp = hanReader.getTime( (int)Aidon_List3Phase::Timestamp);
|
||||
activeImportCounter = ((double) hanReader.getInt( (int)Aidon_List3Phase::CumulativeActiveImportEnergy)) / 100;
|
||||
activeExportCounter = ((double) hanReader.getInt( (int)Aidon_List3Phase::CumulativeActiveExportEnergy)) / 100;
|
||||
reactiveImportCounter = ((double) hanReader.getInt( (int)Aidon_List3Phase::CumulativeReactiveImportEnergy)) / 100;
|
||||
reactiveExportCounter = ((double) hanReader.getInt( (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.getInt( (int)Aidon_List3Phase::ActiveImportPower);
|
||||
reactiveImportPower = hanReader.getInt( (int)Aidon_List3Phase::ReactiveImportPower);
|
||||
activeExportPower = hanReader.getInt( (int)Aidon_List3Phase::ActiveExportPower);
|
||||
reactiveExportPower = hanReader.getInt( (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;
|
||||
break;
|
||||
case (int)Aidon::List1PhaseLong:
|
||||
meterTimestamp = hanReader.getTime( (int)Aidon_List1Phase::Timestamp);
|
||||
activeImportCounter = ((double) hanReader.getInt( (int)Aidon_List1Phase::CumulativeActiveImportEnergy)) / 100;
|
||||
activeExportCounter = ((double) hanReader.getInt( (int)Aidon_List1Phase::CumulativeActiveExportEnergy)) / 100;
|
||||
reactiveImportCounter = ((double) hanReader.getInt( (int)Aidon_List1Phase::CumulativeReactiveImportEnergy)) / 100;
|
||||
reactiveExportCounter = ((double) hanReader.getInt( (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.getInt( (int)Aidon_List1Phase::ActiveImportPower);
|
||||
reactiveImportPower = hanReader.getInt( (int)Aidon_List1Phase::ReactiveImportPower);
|
||||
activeExportPower = hanReader.getInt( (int)Aidon_List1Phase::ActiveExportPower);
|
||||
reactiveExportPower = hanReader.getInt( (int)Aidon_List1Phase::ReactiveExportPower);
|
||||
l1current = ((double) hanReader.getInt( (int)Aidon_List1Phase::CurrentL1)) / 10;
|
||||
l1voltage = ((double) hanReader.getInt( (int)Aidon_List1Phase::VoltageL1)) / 10;
|
||||
break;
|
||||
case (int)Aidon::List3PhaseITLong:
|
||||
meterTimestamp = hanReader.getTime( (int)Aidon_List3PhaseIT::Timestamp);
|
||||
activeImportCounter = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::CumulativeActiveImportEnergy)) / 100;
|
||||
activeExportCounter = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::CumulativeActiveExportEnergy)) / 100;
|
||||
reactiveImportCounter = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::CumulativeReactiveImportEnergy)) / 100;
|
||||
reactiveExportCounter = ((double) hanReader.getInt( (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.getInt( (int)Aidon_List3PhaseIT::ActiveImportPower);
|
||||
reactiveImportPower = hanReader.getInt( (int)Aidon_List3PhaseIT::ReactiveImportPower);
|
||||
activeExportPower = hanReader.getInt( (int)Aidon_List3PhaseIT::ActiveExportPower);
|
||||
reactiveExportPower = hanReader.getInt( (int)Aidon_List3PhaseIT::ReactiveExportPower);
|
||||
l1current = ((double) hanReader.getInt( (int)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;
|
||||
//l2current = ((activeImportPower * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
|
||||
threePhase = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AmsData::extractFromKamstrup(HanReader& hanReader, int listSize) {
|
||||
switch(listSize) {
|
||||
case (int)Kamstrup::List3PhaseShort:
|
||||
threePhase = true;
|
||||
case (int)Kamstrup::List1PhaseShort:
|
||||
listType = 2;
|
||||
break;
|
||||
case (int)Kamstrup::List3PhaseLong:
|
||||
threePhase = true;
|
||||
case (int)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);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AmsData::apply(AmsData& other) {
|
||||
this->lastUpdateMillis = other.getLastUpdateMillis();
|
||||
this->packageTimestamp = other.getPackageTimestamp();
|
||||
this->listType = max(this->listType, other.getListType());
|
||||
switch(other.getListType()) {
|
||||
case 3:
|
||||
this->meterTimestamp = other.getMeterTimestamp();
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
this->activeExportCounter = other.getActiveExportCounter();
|
||||
this->reactiveImportCounter = other.getReactiveImportCounter();
|
||||
this->reactiveExportCounter = other.getReactiveExportCounter();
|
||||
case 2:
|
||||
this->listId = other.getListId();
|
||||
this->meterId = other.getMeterId();
|
||||
this->meterType = other.getMeterType();
|
||||
this->reactiveImportPower = other.getReactiveImportPower();
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
this->reactiveExportPower = other.getReactiveExportPower();
|
||||
this->l1current = other.getL1Current();
|
||||
this->l2current = other.getL2Current();
|
||||
this->l3current = other.getL3Current();
|
||||
this->l1voltage = other.getL1Voltage();
|
||||
this->l2voltage = other.getL2Voltage();
|
||||
this->l3voltage = other.getL3Voltage();
|
||||
this->threePhase = other.isThreePhase();
|
||||
case 1:
|
||||
this->activeImportPower = other.getActiveImportPower();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long AmsData::getLastUpdateMillis() {
|
||||
return this->lastUpdateMillis;
|
||||
}
|
||||
|
||||
unsigned long AmsData::getPackageTimestamp() {
|
||||
return this->packageTimestamp;
|
||||
}
|
||||
|
||||
int AmsData::getListType() {
|
||||
return this->listType;
|
||||
}
|
||||
|
||||
String AmsData::getListId() {
|
||||
return this->listId;
|
||||
}
|
||||
|
||||
String AmsData::getMeterId() {
|
||||
return this->meterId;
|
||||
}
|
||||
|
||||
String AmsData::getMeterType() {
|
||||
return this->meterType;
|
||||
}
|
||||
|
||||
unsigned long AmsData::getMeterTimestamp() {
|
||||
return this->meterTimestamp;
|
||||
}
|
||||
|
||||
int AmsData::getActiveImportPower() {
|
||||
return this->activeImportPower;
|
||||
}
|
||||
|
||||
int AmsData::getReactiveImportPower() {
|
||||
return this->reactiveImportPower;
|
||||
}
|
||||
|
||||
int AmsData::getActiveExportPower() {
|
||||
return this->activeExportPower;
|
||||
}
|
||||
|
||||
int AmsData::getReactiveExportPower() {
|
||||
return this->reactiveExportPower;
|
||||
}
|
||||
|
||||
double AmsData::getL1Voltage() {
|
||||
return this->l1voltage;
|
||||
}
|
||||
|
||||
double AmsData::getL2Voltage() {
|
||||
return this->l2voltage;
|
||||
}
|
||||
|
||||
double AmsData::getL3Voltage() {
|
||||
return this->l3voltage;
|
||||
}
|
||||
|
||||
double AmsData::getL1Current() {
|
||||
return this->l1current;
|
||||
}
|
||||
|
||||
double AmsData::getL2Current() {
|
||||
return this->l2current;
|
||||
}
|
||||
|
||||
double AmsData::getL3Current() {
|
||||
return this->l3current;
|
||||
}
|
||||
|
||||
double AmsData::getActiveImportCounter() {
|
||||
return this->activeImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getReactiveImportCounter() {
|
||||
return this->reactiveImportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getActiveExportCounter() {
|
||||
return this->activeExportCounter;
|
||||
}
|
||||
|
||||
double AmsData::getReactiveExportCounter() {
|
||||
return this->reactiveExportCounter;
|
||||
}
|
||||
|
||||
bool AmsData::isThreePhase() {
|
||||
return this->threePhase;
|
||||
}
|
||||
66
src/AmsData.h
Normal file
66
src/AmsData.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#ifndef _AMSDATA_H
|
||||
#define _AMSDATA_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "HanReader.h"
|
||||
|
||||
#define METER_TYPE_KAIFA 1
|
||||
#define METER_TYPE_AIDON 2
|
||||
#define METER_TYPE_KAMSTRUP 3
|
||||
|
||||
class AmsData {
|
||||
public:
|
||||
AmsData();
|
||||
AmsData(int meterType, HanReader& hanReader);
|
||||
|
||||
void apply(AmsData& other);
|
||||
|
||||
unsigned long getLastUpdateMillis();
|
||||
|
||||
unsigned long getPackageTimestamp();
|
||||
|
||||
int getListType();
|
||||
|
||||
String getListId();
|
||||
String getMeterId();
|
||||
String getMeterType();
|
||||
|
||||
unsigned long getMeterTimestamp();
|
||||
|
||||
int getActiveImportPower();
|
||||
int getReactiveImportPower();
|
||||
int getActiveExportPower();
|
||||
int getReactiveExportPower();
|
||||
|
||||
double getL1Voltage();
|
||||
double getL2Voltage();
|
||||
double getL3Voltage();
|
||||
|
||||
double getL1Current();
|
||||
double getL2Current();
|
||||
double getL3Current();
|
||||
|
||||
double getActiveImportCounter();
|
||||
double getReactiveImportCounter();
|
||||
double getActiveExportCounter();
|
||||
double 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;
|
||||
|
||||
void extractFromKaifa(HanReader& hanReader, int listSize);
|
||||
void extractFromAidon(HanReader& hanReader, int listSize);
|
||||
void extractFromKamstrup(HanReader& hanReader, int listSize);
|
||||
};
|
||||
|
||||
#endif
|
||||
68
src/AmsToMqttBridge.h
Normal file
68
src/AmsToMqttBridge.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef _AMSTOMQTTBRIDGE_H
|
||||
#define _AMSTOMQTTBRIDGE_H
|
||||
|
||||
#define WIFI_CONNECTION_TIMEOUT 30000;
|
||||
|
||||
#define INVALID_BUTTON_PIN 0xFFFFFFFF
|
||||
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
// Build settings for custom hardware by Roar Fredriksen
|
||||
#if HW_ROARFRED
|
||||
#define LED_PIN 2 // The blue on-board LED of the ESP8266 custom AMS board
|
||||
#define LED_ACTIVE_HIGH 0
|
||||
#define AP_BUTTON_PIN 0
|
||||
|
||||
#if DEBUG_MODE
|
||||
#if SOFTWARE_SERIAL
|
||||
#include <SoftwareSerial.h>
|
||||
SoftwareSerial *hanSerial = new SoftwareSerial(3);
|
||||
#else
|
||||
HardwareSerial *hanSerial = &Serial;
|
||||
#endif
|
||||
#else
|
||||
HardwareSerial *hanSerial = &Serial;
|
||||
#endif
|
||||
|
||||
// Build settings for Wemos Lolin D32
|
||||
#elif defined(ARDUINO_LOLIN_D32)
|
||||
#define LED_PIN 5
|
||||
#define LED_ACTIVE_HIGH 0
|
||||
#define AP_BUTTON_PIN 4
|
||||
|
||||
HardwareSerial *hanSerial = &Serial2;
|
||||
|
||||
// Build settings for Wemos D1 mini
|
||||
#elif defined(ARDUINO_ESP8266_WEMOS_D1MINI)
|
||||
#define LED_PIN D4
|
||||
#define LED_ACTIVE_HIGH 0
|
||||
#define AP_BUTTON_PIN D2
|
||||
|
||||
#define SOFTWARE_SERIAL 1
|
||||
#include <SoftwareSerial.h>
|
||||
SoftwareSerial *hanSerial = new SoftwareSerial(D1);
|
||||
|
||||
// Build settings for Adafruit Feather ESP32
|
||||
#elif defined(ARDUINO_FEATHER_ESP32)
|
||||
#define LED_PIN LED_BUILTIN
|
||||
#define LED_ACTIVE_HIGH 1
|
||||
#define AP_BUTTON_PIN INVALID_BUTTON_PIN
|
||||
|
||||
HardwareSerial *hanSerial = &Serial2;
|
||||
|
||||
// Default build settings
|
||||
#else
|
||||
#define LED_PIN 2
|
||||
#define LED_ACTIVE_HIGH 0
|
||||
#define AP_BUTTON_PIN INVALID_BUTTON_PIN
|
||||
#define SOFTWARE_SERIAL 1
|
||||
#include <SoftwareSerial.h>
|
||||
SoftwareSerial *hanSerial = new SoftwareSerial(5);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,136 +1,260 @@
|
||||
/*
|
||||
Name: AmsToMqttBridge.ino
|
||||
Created: 3/13/2018 7:40:28 PM
|
||||
Author: roarf
|
||||
*/
|
||||
|
||||
|
||||
//#define HAS_DALLAS_TEMP_SENSOR 1 // Set to zero if Dallas one wire temp sensor is not present
|
||||
//#define IS_CUSTOM_AMS_BOARD 1 // Set to zero if using NodeMCU or board not designed by Roar Fredriksen
|
||||
/**
|
||||
* @brief ESP8266 based program to receive data from AMS electric meters and send to MQTT
|
||||
*
|
||||
* @details Originally developed by Roar Fredriksen, this program was created to receive data from
|
||||
* AMS electric meters via M-Bus, decode and send to a MQTT broker. The data packet structure
|
||||
* supported by this software is specific to Norwegian meters, but may also support data from
|
||||
* electricity providers in other countries. It was originally based on ESP8266, but have also been
|
||||
* adapted to work with ESP32.
|
||||
*
|
||||
* @author Roar Fredriksen (@roarfred)
|
||||
* The original developer for this project
|
||||
* https://github.com/roarfred/AmsToMqttBridge
|
||||
*
|
||||
* @author Gunnar Skjold (@gskjold)
|
||||
* Maintainer of current code
|
||||
* https://github.com/gskjold/AmsToMqttBridge
|
||||
*/
|
||||
|
||||
#include "AmsToMqttBridge.h"
|
||||
#include <ArduinoJson.h>
|
||||
#include <MQTT.h>
|
||||
|
||||
#if HAS_DALLAS_TEMP_SENSOR
|
||||
#include <DallasTemperature.h>
|
||||
#include <OneWire.h>
|
||||
#endif
|
||||
#include <DNSServer.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
ADC_MODE(ADC_VCC);
|
||||
#endif
|
||||
|
||||
#include "HanConfigAp.h"
|
||||
#include "HwTools.h"
|
||||
|
||||
#include "web/AmsWebServer.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HanReader.h"
|
||||
#include "HanToJson.h"
|
||||
|
||||
#define WIFI_CONNECTION_TIMEOUT 30000;
|
||||
#include "Aidon.h"
|
||||
#include "Kaifa.h"
|
||||
#include "Kamstrup.h"
|
||||
|
||||
#if IS_CUSTOM_AMS_BOARD
|
||||
#define LED_PIN 2 // The blue on-board LED of the ESP8266 custom AMS board
|
||||
#define LED_ACTIVE_HIGH 0
|
||||
#define AP_BUTTON_PIN 0
|
||||
#else
|
||||
#define LED_PIN LED_BUILTIN
|
||||
#define LED_ACTIVE_HIGH 1
|
||||
#define AP_BUTTON_PIN INVALID_BUTTON_PIN
|
||||
#endif
|
||||
HwTools hw;
|
||||
|
||||
#if HAS_DALLAS_TEMP_SENSOR
|
||||
#define TEMP_SENSOR_PIN 5 // Temperature sensor connected to GPIO5
|
||||
DNSServer dnsServer;
|
||||
|
||||
OneWire oneWire(TEMP_SENSOR_PIN);
|
||||
DallasTemperature tempSensor(&oneWire);
|
||||
#endif
|
||||
AmsConfiguration config;
|
||||
|
||||
// Object used to boot as Access Point
|
||||
HanConfigAp ap;
|
||||
AmsWebServer ws;
|
||||
|
||||
// WiFi client and MQTT client
|
||||
WiFiClient *client;
|
||||
MQTTClient mqtt(384);
|
||||
MQTTClient mqtt(512);
|
||||
|
||||
// Object used for debugging
|
||||
HardwareSerial* debugger = NULL;
|
||||
Stream* debugger = NULL;
|
||||
|
||||
// The HAN Port reader, used to read serial data and decode DLMS
|
||||
HanReader hanReader;
|
||||
|
||||
// the setup function runs once when you press reset or power the board
|
||||
void setup()
|
||||
{
|
||||
// Uncomment to debug over the same port as used for HAN communication
|
||||
//debugger = &Serial;
|
||||
|
||||
if (debugger) {
|
||||
// Setup serial port for debugging
|
||||
debugger->begin(2400, SERIAL_8E1);
|
||||
//debugger->begin(115200);
|
||||
while (!debugger);
|
||||
debugger->println("");
|
||||
debugger->println("Started...");
|
||||
void setup() {
|
||||
if(config.hasConfig()) {
|
||||
config.load();
|
||||
}
|
||||
|
||||
// Flash the LED, to indicate we can boot as AP now
|
||||
#if DEBUG_MODE
|
||||
#if HW_ROARFRED
|
||||
#if SOFTWARE_SERIAL
|
||||
SoftwareSerial *ser = new SoftwareSerial(-1, 1);
|
||||
ser->begin(115200, SWSERIAL_8N1);
|
||||
debugger = ser;
|
||||
#else
|
||||
HardwareSerial *ser = &Serial;
|
||||
if(config.getMeterType() == 3) {
|
||||
ser->begin(2400, SERIAL_8N1);
|
||||
} else {
|
||||
ser->begin(2400, SERIAL_8E1);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
HardwareSerial *ser = &Serial;
|
||||
ser->begin(115200, SERIAL_8N1);
|
||||
#endif
|
||||
debugger = ser;
|
||||
#endif
|
||||
|
||||
double vcc = hw.getVcc();
|
||||
|
||||
if (debugger) {
|
||||
debugger->println("");
|
||||
debugger->println("Started...");
|
||||
debugger->print("Voltage: ");
|
||||
debugger->print(vcc);
|
||||
debugger->println("mV");
|
||||
}
|
||||
|
||||
if (vcc > 0 && vcc < 3.1) {
|
||||
if(debugger) {
|
||||
debugger->println("Voltage is too low, sleeping");
|
||||
debugger->flush();
|
||||
}
|
||||
ESP.deepSleep(10000000); //Deep sleep to allow output cap to charge up
|
||||
}
|
||||
|
||||
#if HAS_RGB_LED
|
||||
// Initialize RGB LED pins
|
||||
pinMode(LEDPIN_RGB_GREEN, OUTPUT);
|
||||
pinMode(LEDPIN_RGB_RED, OUTPUT);
|
||||
#endif
|
||||
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
led_on();
|
||||
|
||||
delay(1000);
|
||||
|
||||
// Initialize the AP
|
||||
ap.setup(AP_BUTTON_PIN, debugger);
|
||||
pinMode(AP_BUTTON_PIN, INPUT_PULLUP);
|
||||
|
||||
led_off();
|
||||
|
||||
if (!ap.isActivated)
|
||||
{
|
||||
setupWiFi();
|
||||
// Configure uart for AMS data
|
||||
if(ap.config.meterType == 3) {
|
||||
Serial.begin(2400, SERIAL_8N1);
|
||||
} else {
|
||||
Serial.begin(2400, SERIAL_8E1);
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
if(config.hasConfig()) {
|
||||
if(debugger) config.print(debugger);
|
||||
WiFi_connect();
|
||||
client = new WiFiClient();
|
||||
} else {
|
||||
if(debugger) {
|
||||
debugger->println("No configuration, booting AP");
|
||||
}
|
||||
while (!Serial);
|
||||
|
||||
hanReader.setup(&Serial, debugger);
|
||||
|
||||
// Compensate for the known Kaifa bug
|
||||
hanReader.compensateFor09HeaderBug = (ap.config.meterType == 1);
|
||||
swapWifiMode();
|
||||
}
|
||||
|
||||
#if SOFTWARE_SERIAL
|
||||
if(debugger) debugger->println("HAN has software serial");
|
||||
if(config.getMeterType() == 3) {
|
||||
hanSerial->begin(2400, SWSERIAL_8N1);
|
||||
} else {
|
||||
hanSerial->begin(2400, SWSERIAL_8E1);
|
||||
}
|
||||
#else
|
||||
if(debugger) {
|
||||
debugger->println("HAN has hardware serial");
|
||||
debugger->flush();
|
||||
}
|
||||
if(config.getMeterType() == 3) {
|
||||
hanSerial->begin(2400, SERIAL_8N1);
|
||||
} else {
|
||||
hanSerial->begin(2400, SERIAL_8E1);
|
||||
}
|
||||
#if UART2
|
||||
hanSerial->swap();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
hanReader.setup(hanSerial, 0);
|
||||
|
||||
// Compensate for the known Kaifa bug
|
||||
hanReader.compensateFor09HeaderBug = (config.getMeterType() == 1);
|
||||
|
||||
// Empty buffer before starting
|
||||
while (hanSerial->available() > 0) {
|
||||
hanSerial->read();
|
||||
}
|
||||
|
||||
ws.setup(&config, debugger, &mqtt);
|
||||
|
||||
#if HAS_RGB_LED
|
||||
//Signal startup by blinking red / green / yellow
|
||||
rgb_led(RGB_RED, 2);
|
||||
delay(250);
|
||||
rgb_led(RGB_GREEN, 2);
|
||||
delay(250);
|
||||
rgb_led(RGB_YELLOW, 2);
|
||||
#endif
|
||||
}
|
||||
|
||||
// the loop function runs over and over again until power down or reset
|
||||
void loop()
|
||||
{
|
||||
int buttonTimer = 0;
|
||||
bool buttonActive = false;
|
||||
unsigned long longPressTime = 5000;
|
||||
bool longPressActive = false;
|
||||
|
||||
bool wifiConnected = false;
|
||||
|
||||
unsigned long lastTemperatureRead = 0;
|
||||
double temperature = -127;
|
||||
|
||||
bool even = true;
|
||||
unsigned long lastRead = 0;
|
||||
unsigned long lastSuccessfulRead = 0;
|
||||
|
||||
unsigned long lastErrorBlink = 0;
|
||||
int lastError = 0;
|
||||
|
||||
void loop() {
|
||||
unsigned long now = millis();
|
||||
if(AP_BUTTON_PIN != INVALID_BUTTON_PIN) {
|
||||
if (digitalRead(AP_BUTTON_PIN) == LOW) {
|
||||
if (buttonActive == false) {
|
||||
buttonActive = true;
|
||||
buttonTimer = now;
|
||||
}
|
||||
|
||||
if ((now - buttonTimer > longPressTime) && (longPressActive == false)) {
|
||||
longPressActive = true;
|
||||
swapWifiMode();
|
||||
}
|
||||
} else {
|
||||
if (buttonActive == true) {
|
||||
if (longPressActive == true) {
|
||||
longPressActive = false;
|
||||
} else {
|
||||
// Single press action
|
||||
}
|
||||
buttonActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(now - lastTemperatureRead > 5000) {
|
||||
temperature = hw.getTemperature();
|
||||
lastTemperatureRead = now;
|
||||
}
|
||||
|
||||
if(now > 10000 && now - lastErrorBlink > 3000) {
|
||||
errorBlink();
|
||||
}
|
||||
|
||||
// Only do normal stuff if we're not booted as AP
|
||||
if (!ap.loop())
|
||||
{
|
||||
// Turn off the LED
|
||||
if (WiFi.getMode() != WIFI_AP) {
|
||||
led_off();
|
||||
|
||||
// allow the MQTT client some resources
|
||||
mqtt.loop();
|
||||
delay(10); // <- fixes some issues with WiFi stability
|
||||
|
||||
// Reconnect to WiFi and MQTT as needed
|
||||
if (!mqtt.connected()) {
|
||||
MQTT_connect();
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
wifiConnected = false;
|
||||
WiFi_connect();
|
||||
} else {
|
||||
if(!wifiConnected) {
|
||||
wifiConnected = true;
|
||||
if(debugger) {
|
||||
debugger->println("Successfully connected to WiFi!");
|
||||
debugger->println(WiFi.localIP());
|
||||
}
|
||||
}
|
||||
if (!config.getMqttHost().isEmpty()) {
|
||||
mqtt.loop();
|
||||
delay(10); // Needed to preserve power. After adding this, the voltage is super smooth on a HAN powered device
|
||||
if(!mqtt.connected() || config.isMqttChanged()) {
|
||||
MQTT_connect();
|
||||
}
|
||||
} else if(mqtt.connected()) {
|
||||
mqtt.disconnect();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read data from the HAN port
|
||||
readHanPort();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
dnsServer.processNextRequest();
|
||||
// Continously flash the LED when AP mode
|
||||
if (millis() / 1000 % 2 == 0) led_on();
|
||||
else led_off();
|
||||
if (now / 50 % 64 == 0) led_on();
|
||||
else led_off();
|
||||
|
||||
}
|
||||
if(now - lastRead > 100) {
|
||||
yield();
|
||||
readHanPort();
|
||||
lastRead = now;
|
||||
}
|
||||
ws.loop();
|
||||
delay(1); // Needed for auto modem sleep
|
||||
}
|
||||
|
||||
|
||||
@@ -153,35 +277,56 @@ void led_off()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void setupWiFi()
|
||||
{
|
||||
// Turn off AP
|
||||
WiFi.enableAP(false);
|
||||
|
||||
// Connect to WiFi
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ap.config.ssid, ap.config.ssidPassword);
|
||||
|
||||
// Wait for WiFi connection
|
||||
if (debugger) debugger->print("\nWaiting for WiFi to connect...");
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
if (debugger) debugger->print(".");
|
||||
delay(500);
|
||||
void errorBlink() {
|
||||
if(lastError == 3)
|
||||
lastError = 0;
|
||||
lastErrorBlink = millis();
|
||||
for(;lastError < 3;lastError++) {
|
||||
switch(lastError) {
|
||||
case 0:
|
||||
if(lastErrorBlink - lastSuccessfulRead > 30000) {
|
||||
rgb_led(1, 2); // If no message received from AMS in 30 sec, blink once
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(!config.getMqttHost().isEmpty() && mqtt.lastError() != 0) {
|
||||
rgb_led(1, 3); // If MQTT error, blink twice
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if(WiFi.getMode() != WIFI_AP && WiFi.status() != WL_CONNECTED) {
|
||||
rgb_led(1, 4); // If WiFi not connected, blink three times
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (debugger) debugger->println(" connected");
|
||||
}
|
||||
|
||||
client = new WiFiClient();
|
||||
mqtt.begin(ap.config.mqtt, *client);
|
||||
void swapWifiMode() {
|
||||
led_on();
|
||||
WiFiMode_t mode = WiFi.getMode();
|
||||
dnsServer.stop();
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
yield();
|
||||
|
||||
// Direct incoming MQTT messages
|
||||
if (ap.config.mqttSubscribeTopic != 0 && strlen(ap.config.mqttSubscribeTopic) > 0) {
|
||||
mqtt.subscribe(ap.config.mqttSubscribeTopic);
|
||||
mqtt.onMessage(mqttMessageReceived);
|
||||
if (mode != WIFI_AP || !config.hasConfig()) {
|
||||
if(debugger) debugger->println("Swapping to AP mode");
|
||||
WiFi.softAP("AMS2MQTT");
|
||||
WiFi.mode(WIFI_AP);
|
||||
|
||||
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer.start(53, "*", WiFi.softAPIP());
|
||||
} else {
|
||||
if(debugger) debugger->println("Swapping to STA mode");
|
||||
WiFi_connect();
|
||||
}
|
||||
|
||||
// Notify everyone we're here!
|
||||
sendMqttData("Connected!");
|
||||
delay(500);
|
||||
led_off();
|
||||
}
|
||||
|
||||
void mqttMessageReceived(String &topic, String &payload)
|
||||
@@ -199,177 +344,253 @@ void mqttMessageReceived(String &topic, String &payload)
|
||||
// Ideas could be to query for values or to initiate OTA firmware update
|
||||
}
|
||||
|
||||
void readHanPort()
|
||||
{
|
||||
if (hanReader.read())
|
||||
{
|
||||
// Flash LED on, this shows us that data is received
|
||||
led_on();
|
||||
void readHanPort() {
|
||||
if (hanReader.read()) {
|
||||
// Empty serial buffer. For some reason this seems to make a difference. Some garbage on the wire after package?
|
||||
while(hanSerial->available()) {
|
||||
hanSerial->read();
|
||||
}
|
||||
|
||||
// Get the timestamp (as unix time) from the package
|
||||
time_t time = hanReader.getPackageTime();
|
||||
if (debugger) debugger->print("Time of the package is: ");
|
||||
if (debugger) debugger->println(time);
|
||||
lastSuccessfulRead = millis();
|
||||
|
||||
// Define a json object to keep the data
|
||||
StaticJsonDocument<500> json;
|
||||
if(config.getMeterType() > 0) {
|
||||
#if HAS_RGB_LED
|
||||
rgb_led(RGB_GREEN, 1);
|
||||
#else
|
||||
led_on();
|
||||
#endif
|
||||
|
||||
// Any generic useful info here
|
||||
json["id"] = WiFi.macAddress();
|
||||
json["up"] = millis();
|
||||
json["t"] = time;
|
||||
AmsData data(config.getMeterType(), hanReader);
|
||||
ws.setData(data);
|
||||
|
||||
// Add a sub-structure to the json object,
|
||||
// to keep the data from the meter itself
|
||||
JsonObject data = json.createNestedObject("data");
|
||||
if(!config.getMqttHost().isEmpty() && !config.getMqttPublishTopic().isEmpty()) {
|
||||
StaticJsonDocument<512> json;
|
||||
hanToJson(json, data, hw, temperature);
|
||||
if (debugger) {
|
||||
debugger->print("Sending data to MQTT: ");
|
||||
serializeJsonPretty(json, *debugger);
|
||||
debugger->println();
|
||||
}
|
||||
|
||||
#if HAS_DALLAS_TEMP_SENSOR
|
||||
// Get the temperature too
|
||||
tempSensor.requestTemperatures();
|
||||
data["temp"] = tempSensor.getTempCByIndex(0);
|
||||
String msg;
|
||||
serializeJson(json, msg);
|
||||
mqtt.publish(config.getMqttPublishTopic(), msg.c_str());
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
#if HAS_RGB_LED
|
||||
rgb_led(RGB_GREEN, 0);
|
||||
#else
|
||||
led_off();
|
||||
#endif
|
||||
} else {
|
||||
// Auto detect meter if not set
|
||||
for(int i = 1; i <= 3; i++) {
|
||||
String list;
|
||||
switch(i) {
|
||||
case 1:
|
||||
list = hanReader.getString((int) Kaifa_List1Phase::ListVersionIdentifier);
|
||||
break;
|
||||
case 2:
|
||||
list = hanReader.getString((int) Aidon_List1Phase::ListVersionIdentifier);
|
||||
break;
|
||||
case 3:
|
||||
list = hanReader.getString((int) Kamstrup_List1Phase::ListVersionIdentifier);
|
||||
break;
|
||||
}
|
||||
if(!list.isEmpty()) {
|
||||
list.toLowerCase();
|
||||
if(list.startsWith("kfm")) {
|
||||
config.setMeterType(1);
|
||||
if(debugger) debugger->println("Detected Kaifa meter");
|
||||
break;
|
||||
} else if(list.startsWith("aidon")) {
|
||||
config.setMeterType(2);
|
||||
if(debugger) debugger->println("Detected Aidon meter");
|
||||
break;
|
||||
} else if(list.startsWith("kamstrup")) {
|
||||
config.setMeterType(3);
|
||||
if(debugger) debugger->println("Detected Kamstrup meter");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
hanReader.compensateFor09HeaderBug = (config.getMeterType() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Switch parity if meter is still not detected
|
||||
if(config.getMeterType() == 0 && millis() - lastSuccessfulRead > 10000) {
|
||||
lastSuccessfulRead = millis();
|
||||
if(debugger) debugger->println("No data for current setting, switching parity");
|
||||
#if SOFTWARE_SERIAL
|
||||
if(even) {
|
||||
hanSerial->begin(2400, SWSERIAL_8N1);
|
||||
} else {
|
||||
hanSerial->begin(2400, SWSERIAL_8E1);
|
||||
}
|
||||
#else
|
||||
if(even) {
|
||||
hanSerial->begin(2400, SERIAL_8N1);
|
||||
} else {
|
||||
hanSerial->begin(2400, SERIAL_8E1);
|
||||
}
|
||||
#endif
|
||||
|
||||
hanToJson(data, ap.config.meterType, hanReader);
|
||||
|
||||
// Write the json to the debug port
|
||||
if (debugger) {
|
||||
debugger->print("Sending data to MQTT: ");
|
||||
serializeJsonPretty(json, *debugger);
|
||||
debugger->println();
|
||||
}
|
||||
|
||||
// Make sure we have configured a publish topic
|
||||
if (! ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0)
|
||||
{
|
||||
// Publish the json to the MQTT server
|
||||
String msg;
|
||||
serializeJson(json, msg);
|
||||
|
||||
mqtt.publish(ap.config.mqttPublishTopic, msg.c_str());
|
||||
mqtt.loop();
|
||||
}
|
||||
|
||||
// Flash LED off
|
||||
led_off();
|
||||
even = !even;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Function to connect and reconnect as necessary to the MQTT server.
|
||||
// Should be called in the loop function and it will take care if connecting.
|
||||
void MQTT_connect()
|
||||
{
|
||||
// Connect to WiFi access point.
|
||||
if (debugger)
|
||||
{
|
||||
debugger->println();
|
||||
debugger->println();
|
||||
debugger->print("Connecting to WiFi network ");
|
||||
debugger->println(ap.config.ssid);
|
||||
}
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED)
|
||||
{
|
||||
// Make one first attempt at connect, this seems to considerably speed up the first connection
|
||||
WiFi.disconnect();
|
||||
WiFi.begin(ap.config.ssid, ap.config.ssidPassword);
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Wait for the WiFi connection to complete
|
||||
long vTimeout = millis() + WIFI_CONNECTION_TIMEOUT;
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
unsigned long wifiTimeout = WIFI_CONNECTION_TIMEOUT;
|
||||
unsigned long lastWifiRetry = -WIFI_CONNECTION_TIMEOUT;
|
||||
void WiFi_connect() {
|
||||
if(millis() - lastWifiRetry < wifiTimeout) {
|
||||
delay(50);
|
||||
if (debugger) debugger->print(".");
|
||||
|
||||
// If we timed out, disconnect and try again
|
||||
if (vTimeout < millis())
|
||||
{
|
||||
if (debugger)
|
||||
{
|
||||
debugger->print("Timout during connect. WiFi status is: ");
|
||||
debugger->println(WiFi.status());
|
||||
}
|
||||
WiFi.disconnect();
|
||||
WiFi.begin(ap.config.ssid, ap.config.ssidPassword);
|
||||
vTimeout = millis() + WIFI_CONNECTION_TIMEOUT;
|
||||
}
|
||||
yield();
|
||||
return;
|
||||
}
|
||||
lastWifiRetry = millis();
|
||||
|
||||
if (debugger) {
|
||||
debugger->println();
|
||||
debugger->println("WiFi connected");
|
||||
debugger->println("IP address: ");
|
||||
debugger->println(WiFi.localIP());
|
||||
debugger->print("\nconnecting to MQTT: ");
|
||||
debugger->print(ap.config.mqtt);
|
||||
debugger->println();
|
||||
debugger->print("Connecting to WiFi network ");
|
||||
debugger->println(config.getWifiSsid());
|
||||
}
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
WiFi.disconnect();
|
||||
yield();
|
||||
|
||||
WiFi.enableAP(false);
|
||||
WiFi.mode(WIFI_STA);
|
||||
if(!config.getWifiIp().isEmpty()) {
|
||||
IPAddress ip, gw, sn(255,255,255,0);
|
||||
ip.fromString(config.getWifiIp());
|
||||
gw.fromString(config.getWifiGw());
|
||||
sn.fromString(config.getWifiSubnet());
|
||||
WiFi.config(ip, gw, sn);
|
||||
}
|
||||
WiFi.begin(config.getWifiSsid().c_str(), config.getWifiPassword().c_str());
|
||||
yield();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long lastMqttRetry = -10000;
|
||||
void MQTT_connect() {
|
||||
if(config.getMqttHost().isEmpty()) {
|
||||
if(debugger) debugger->println("No MQTT config");
|
||||
return;
|
||||
}
|
||||
if(millis() - lastMqttRetry < 5000) {
|
||||
yield();
|
||||
return;
|
||||
}
|
||||
lastMqttRetry = millis();
|
||||
if(debugger) {
|
||||
debugger->print("Connecting to MQTT: ");
|
||||
debugger->print(config.getMqttHost());
|
||||
debugger->print(", port: ");
|
||||
debugger->print(ap.config.mqttPort);
|
||||
debugger->print(config.getMqttPort());
|
||||
debugger->println();
|
||||
}
|
||||
|
||||
// Wait for the MQTT connection to complete
|
||||
while (!mqtt.connected()) {
|
||||
// Connect to a unsecure or secure MQTT server
|
||||
if ((ap.config.mqttUser == 0 && mqtt.connect(ap.config.mqttClientID)) ||
|
||||
(ap.config.mqttUser != 0 && mqtt.connect(ap.config.mqttClientID, ap.config.mqttUser, ap.config.mqttPass)))
|
||||
{
|
||||
if (debugger) debugger->println("\nSuccessfully connected to MQTT!");
|
||||
mqtt.disconnect();
|
||||
yield();
|
||||
|
||||
// Subscribe to the chosen MQTT topic, if set in configuration
|
||||
if (ap.config.mqttSubscribeTopic != 0 && strlen(ap.config.mqttSubscribeTopic) > 0)
|
||||
{
|
||||
mqtt.subscribe(ap.config.mqttSubscribeTopic);
|
||||
if (debugger) debugger->printf(" Subscribing to [%s]\r\n", ap.config.mqttSubscribeTopic);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugger)
|
||||
{
|
||||
debugger->print(".");
|
||||
debugger->print("failed, ");
|
||||
debugger->println(" trying again in 5 seconds");
|
||||
}
|
||||
mqtt.begin(config.getMqttHost().c_str(), config.getMqttPort(), *client);
|
||||
|
||||
// Wait 2 seconds before retrying
|
||||
mqtt.disconnect();
|
||||
// Connect to a unsecure or secure MQTT server
|
||||
if ((config.getMqttUser().isEmpty() && mqtt.connect(config.getMqttClientId().c_str())) ||
|
||||
(!config.getMqttUser().isEmpty() && mqtt.connect(config.getMqttClientId().c_str(), config.getMqttUser().c_str(), config.getMqttPassword().c_str()))) {
|
||||
if (debugger) debugger->println("\nSuccessfully connected to MQTT!");
|
||||
config.ackMqttChange();
|
||||
|
||||
delay(2000);
|
||||
// Subscribe to the chosen MQTT topic, if set in configuration
|
||||
if (!config.getMqttSubscribeTopic().isEmpty()) {
|
||||
mqtt.subscribe(config.getMqttSubscribeTopic());
|
||||
if (debugger) debugger->printf(" Subscribing to [%s]\r\n", config.getMqttSubscribeTopic().c_str());
|
||||
}
|
||||
|
||||
// Allow some resources for the WiFi connection
|
||||
yield();
|
||||
delay(2000);
|
||||
sendMqttData("Connected!");
|
||||
} else {
|
||||
if (debugger) {
|
||||
debugger->print(" failed, ");
|
||||
debugger->println(" trying again in 5 seconds");
|
||||
}
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
// Send a simple string embedded in json over MQTT
|
||||
void sendMqttData(String data)
|
||||
{
|
||||
// Make sure we have configured a publish topic
|
||||
if (ap.config.mqttPublishTopic == 0 || strlen(ap.config.mqttPublishTopic) == 0)
|
||||
if (config.getMqttPublishTopic().isEmpty())
|
||||
return;
|
||||
|
||||
// Make sure we're connected
|
||||
if (!client->connected() || !mqtt.connected()) {
|
||||
MQTT_connect();
|
||||
}
|
||||
|
||||
// Build a json with the message in a "data" attribute
|
||||
StaticJsonDocument<500> json;
|
||||
StaticJsonDocument<128> json;
|
||||
json["id"] = WiFi.macAddress();
|
||||
json["up"] = millis();
|
||||
json["data"] = data;
|
||||
double vcc = hw.getVcc();
|
||||
if(vcc > 0) {
|
||||
json["vcc"] = vcc;
|
||||
}
|
||||
float rssi = WiFi.RSSI();
|
||||
rssi = isnan(rssi) ? -100.0 : rssi;
|
||||
json["rssi"] = rssi;
|
||||
|
||||
// Stringify the json
|
||||
String msg;
|
||||
serializeJson(json, msg);
|
||||
|
||||
// Send the json over MQTT
|
||||
mqtt.publish(ap.config.mqttPublishTopic, msg.c_str());
|
||||
mqtt.publish(config.getMqttPublishTopic(), msg.c_str());
|
||||
|
||||
if (debugger) debugger->print("sendMqttData: ");
|
||||
if (debugger) debugger->println(data);
|
||||
}
|
||||
|
||||
void rgb_led(int color, int mode) {
|
||||
// Activate red and green LEDs if RGB LED is present (HAS_RGB_LED=1)
|
||||
// If no RGB LED present (HAS_RGB_LED=0 or not defined), all output goes to ESP onboard LED
|
||||
// color: 1=red, 2=green, 3=yellow
|
||||
// mode: 0=OFF, 1=ON, >=2 -> Short blink(s), number of blinks: (mode - 1)
|
||||
#ifndef HAS_RGB_LED
|
||||
#define LEDPIN_RGB_RED LED_PIN
|
||||
#define LEDPIN_RGB_GREEN LED_PIN
|
||||
#endif
|
||||
int blinkduration = 50; // milliseconds
|
||||
switch (mode) {
|
||||
case 0: //OFF
|
||||
digitalWrite(LEDPIN_RGB_RED, HIGH);
|
||||
digitalWrite(LEDPIN_RGB_GREEN, HIGH);
|
||||
break;
|
||||
case 1: //ON
|
||||
switch (color) {
|
||||
case 1: //Red
|
||||
digitalWrite(LEDPIN_RGB_RED, LOW);
|
||||
digitalWrite(LEDPIN_RGB_GREEN, HIGH);
|
||||
break;
|
||||
case 2: //Green
|
||||
digitalWrite(LEDPIN_RGB_RED, HIGH);
|
||||
digitalWrite(LEDPIN_RGB_GREEN, LOW);
|
||||
break;
|
||||
case 3: //Yellow
|
||||
digitalWrite(LEDPIN_RGB_RED, LOW);
|
||||
digitalWrite(LEDPIN_RGB_GREEN, LOW);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default: // Blink
|
||||
for(int i = 1; i < mode; i++) {
|
||||
rgb_led(color, 1);
|
||||
delay(blinkduration);
|
||||
rgb_led(color, 0);
|
||||
if(i != mode)
|
||||
delay(blinkduration);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
46
src/HanToJson.cpp
Normal file
46
src/HanToJson.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "HanToJson.h"
|
||||
|
||||
void hanToJson(JsonDocument& json, AmsData& data, HwTools& hw, double temperature) {
|
||||
json["id"] = WiFi.macAddress();
|
||||
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();
|
||||
}
|
||||
}
|
||||
16
src/HanToJson.h
Normal file
16
src/HanToJson.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#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);
|
||||
|
||||
#endif
|
||||
40
src/HwTools.cpp
Normal file
40
src/HwTools.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "HwTools.h"
|
||||
|
||||
double HwTools::getVcc() {
|
||||
#if defined(ARDUINO_ESP8266_WEMOS_D1MINI)
|
||||
return (((double) ESP.getVcc()) / 900); // This board has a voltage divider on VCC. Yes, 900 is correct
|
||||
#elif defined(ESP8266)
|
||||
#if defined(ESP_VCC_CALIB_FACTOR)
|
||||
return ((double) ESP.getVcc()) / 1024 * ESP_VCC_CALIB_FACTOR;
|
||||
#else
|
||||
return ((double) ESP.getVcc()) / 1024;
|
||||
#endif
|
||||
#endif
|
||||
return -1;
|
||||
}
|
||||
|
||||
double HwTools::getTemperature() {
|
||||
|
||||
#if defined TEMP_SENSOR_PIN
|
||||
if(!tempSensorInit) {
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
return DEVICE_DISCONNECTED_C;
|
||||
}
|
||||
|
||||
int HwTools::getWifiRssi() {
|
||||
int rssi = WiFi.RSSI();
|
||||
return isnan(rssi) ? -100.0 : rssi;
|
||||
}
|
||||
43
src/HwTools.h
Normal file
43
src/HwTools.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef _HWTOOLS_H
|
||||
#define _HWTOOLS_H
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#include <DallasTemperature.h>
|
||||
#include <OneWire.h>
|
||||
|
||||
#if HW_ROARFRED
|
||||
#define TEMP_SENSOR_PIN 5
|
||||
#elif defined(ARDUINO_LOLIN_D32)
|
||||
#define TEMP_SENSOR_PIN 14
|
||||
#elif defined(ARDUINO_ESP8266_WEMOS_D1MINI)
|
||||
#define TEMP_SENSOR_PIN D5
|
||||
#else
|
||||
#define TEMP_SENSOR_PIN 0xFFFFFFFF
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
class HwTools {
|
||||
public:
|
||||
double getVcc();
|
||||
double getTemperature();
|
||||
int getWifiRssi();
|
||||
|
||||
HwTools() {
|
||||
oneWire = new OneWire(TEMP_SENSOR_PIN);
|
||||
tempSensor = new DallasTemperature(this->oneWire);
|
||||
};
|
||||
private:
|
||||
bool tempSensorInit, hasTempSensor;
|
||||
OneWire *oneWire;
|
||||
DallasTemperature *tempSensor;
|
||||
};
|
||||
|
||||
#endif
|
||||
524
src/web/AmsWebServer.cpp
Normal file
524
src/web/AmsWebServer.cpp
Normal file
@@ -0,0 +1,524 @@
|
||||
#include "AmsWebServer.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "root/index_html.h"
|
||||
#include "root/configmeter_html.h"
|
||||
#include "root/configwifi_html.h"
|
||||
#include "root/configmqtt_html.h"
|
||||
#include "root/configweb_html.h"
|
||||
#include "root/boot_css.h"
|
||||
#include "root/gaugemeter_js.h"
|
||||
|
||||
#include "Base64.h"
|
||||
|
||||
|
||||
void AmsWebServer::setup(AmsConfiguration* config, Stream* debugger, MQTTClient* mqtt) {
|
||||
this->config = config;
|
||||
this->debugger = debugger;
|
||||
this->mqtt = mqtt;
|
||||
|
||||
server.on("/", std::bind(&AmsWebServer::indexHtml, this));
|
||||
server.on("/config-meter", std::bind(&AmsWebServer::configMeterHtml, this));
|
||||
server.on("/config-wifi", std::bind(&AmsWebServer::configWifiHtml, this));
|
||||
server.on("/config-mqtt", std::bind(&AmsWebServer::configMqttHtml, this));
|
||||
server.on("/config-web", std::bind(&AmsWebServer::configWebHtml, this));
|
||||
server.on("/boot.css", std::bind(&AmsWebServer::bootCss, this));
|
||||
server.on("/gaugemeter.js", std::bind(&AmsWebServer::gaugemeterJs, this));
|
||||
server.on("/data.json", std::bind(&AmsWebServer::dataJson, this));
|
||||
|
||||
server.on("/save", std::bind(&AmsWebServer::handleSave, this));
|
||||
|
||||
server.begin(); // Web server start
|
||||
}
|
||||
|
||||
void AmsWebServer::loop() {
|
||||
server.handleClient();
|
||||
}
|
||||
|
||||
|
||||
void AmsWebServer::setData(AmsData& data) {
|
||||
this->data.apply(data);
|
||||
|
||||
if(maxPwr == 0 && data.getListType() > 1 && config->hasConfig() && config->getMainFuse() > 0 && config->getDistributionSystem() > 0) {
|
||||
int volt = config->getDistributionSystem() == 2 ? 400 : 230;
|
||||
if(data.isThreePhase()) {
|
||||
maxPwr = config->getMainFuse() * sqrt(3) * volt;
|
||||
} else {
|
||||
maxPwr = config->getMainFuse() * 230;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsWebServer::checkSecurity(byte level) {
|
||||
bool access = WiFi.getMode() == WIFI_AP || !config->hasConfig() || config->getAuthSecurity() < level;
|
||||
if(!access && config->getAuthSecurity() >= level && server.hasHeader("Authorization")) {
|
||||
println(" forcing web security");
|
||||
String expectedAuth = String(config->getAuthUser()) + ":" + String(config->getAuthPassword());
|
||||
|
||||
String providedPwd = server.header("Authorization");
|
||||
providedPwd.replace("Basic ", "");
|
||||
char inputString[providedPwd.length()];
|
||||
providedPwd.toCharArray(inputString, providedPwd.length()+1);
|
||||
|
||||
int inputStringLength = sizeof(inputString);
|
||||
int decodedLength = Base64.decodedLength(inputString, inputStringLength);
|
||||
char decodedString[decodedLength];
|
||||
Base64.decode(decodedString, inputString, inputStringLength);
|
||||
print("Received auth: ");
|
||||
println(decodedString);
|
||||
access = String(decodedString).equals(expectedAuth);
|
||||
}
|
||||
|
||||
if(!access) {
|
||||
println(" no access, requesting user/pass");
|
||||
server.sendHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"");
|
||||
server.setContentLength(0);
|
||||
server.send(401, "text/html", "");
|
||||
}
|
||||
return access;
|
||||
}
|
||||
|
||||
void AmsWebServer::indexHtml() {
|
||||
println("Serving /index.html over http...");
|
||||
|
||||
if(!checkSecurity(2))
|
||||
return;
|
||||
|
||||
String html = String((const __FlashStringHelper*) INDEX_HTML);
|
||||
html.replace("${version}", VERSION);
|
||||
|
||||
if(WiFi.getMode() != WIFI_AP) {
|
||||
html.replace("boot.css", BOOTSTRAP_URL);
|
||||
}
|
||||
|
||||
double u1 = data.getL1Voltage();
|
||||
double u2 = data.getL2Voltage();
|
||||
double u3 = data.getL3Voltage();
|
||||
double i1 = data.getL1Current();
|
||||
double i2 = data.getL2Current();
|
||||
double i3 = data.getL3Current();
|
||||
double tpi = data.getActiveImportCounter();
|
||||
double tpo = data.getActiveExportCounter();
|
||||
double tqi = data.getReactiveImportCounter();
|
||||
double tqo = data.getReactiveExportCounter();
|
||||
|
||||
html.replace("${data.P}", String(data.getActiveImportPower()));
|
||||
html.replace("${data.PO}", String(data.getActiveExportPower()));
|
||||
html.replace("${display.export}", config->getProductionCapacity() > 0 ? "" : "none");
|
||||
html.replace("${text.import}", config->getProductionCapacity() > 0 ? "Import" : "Consumption");
|
||||
|
||||
html.replace("${data.U1}", u1 > 0 ? String(u1, 1) : "");
|
||||
html.replace("${data.I1}", u1 > 0 ? String(i1, 1) : "");
|
||||
html.replace("${display.P1}", u1 > 0 ? "" : "none");
|
||||
|
||||
html.replace("${data.U2}", u2 > 0 ? String(u2, 1) : "");
|
||||
html.replace("${data.I2}", u2 > 0 ? String(i2, 1) : "");
|
||||
html.replace("${display.P2}", u2 > 0 ? "" : "none");
|
||||
|
||||
html.replace("${data.U3}", u3 > 0 ? String(u3, 1) : "");
|
||||
html.replace("${data.I3}", u3 > 0 ? String(i3, 1) : "");
|
||||
html.replace("${display.P3}", u3 > 0 ? "" : "none");
|
||||
|
||||
html.replace("${data.tPI}", tpi > 0 ? String(tpi, 1) : "");
|
||||
html.replace("${data.tPO}", tpi > 0 ? String(tpo, 1) : "");
|
||||
html.replace("${data.tQI}", tpi > 0 ? String(tqi, 1) : "");
|
||||
html.replace("${data.tQO}", tpi > 0 ? String(tqo, 1) : "");
|
||||
html.replace("${display.accumulative}", tpi > 0 ? "" : "none");
|
||||
|
||||
double vcc = hw.getVcc();
|
||||
html.replace("${vcc}", vcc > 0 ? String(vcc, 2) : "");
|
||||
|
||||
double temp = hw.getTemperature();
|
||||
html.replace("${temp}", temp > 0 ? String(temp, 1) : "");
|
||||
html.replace("${display.temp}", temp != DEVICE_DISCONNECTED_C ? "" : "none");
|
||||
|
||||
int rssi = hw.getWifiRssi();
|
||||
html.replace("${wifi.rssi}", vcc > 0 ? String(rssi) : "");
|
||||
html.replace("${wifi.channel}", WiFi.channel() > 0 ? String(WiFi.channel()) : "");
|
||||
html.replace("${wifi.ssid}", !WiFi.SSID().isEmpty() ? String(WiFi.SSID()) : "");
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
|
||||
server.setContentLength(html.length());
|
||||
server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
void AmsWebServer::configMeterHtml() {
|
||||
println("Serving /config/meter.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
String html = String((const __FlashStringHelper*) CONFIGMETER_HTML);
|
||||
html.replace("${version}", VERSION);
|
||||
|
||||
if(WiFi.getMode() != WIFI_AP) {
|
||||
html.replace("boot.css", BOOTSTRAP_URL);
|
||||
}
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
|
||||
html.replace("${config.meterType}", String(config->getMainFuse()));
|
||||
for(int i = 0; i<4; i++) {
|
||||
html.replace("${config.meterType" + String(i) + "}", config->getMeterType() == i ? "selected" : "");
|
||||
}
|
||||
html.replace("${config.distributionSystem}", String(config->getDistributionSystem()));
|
||||
for(int i = 0; i<3; i++) {
|
||||
html.replace("${config.distributionSystem" + String(i) + "}", config->getDistributionSystem() == i ? "selected" : "");
|
||||
}
|
||||
html.replace("${config.mainFuse}", String(config->getMainFuse()));
|
||||
for(int i = 0; i<64; i++) {
|
||||
html.replace("${config.mainFuse" + String(i) + "}", config->getMainFuse() == i ? "selected" : "");
|
||||
}
|
||||
html.replace("${config.productionCapacity}", String(config->getProductionCapacity()));
|
||||
|
||||
server.setContentLength(html.length());
|
||||
server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
void AmsWebServer::configWifiHtml() {
|
||||
println("Serving /config/wifi.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
String html = String((const __FlashStringHelper*) CONFIGWIFI_HTML);
|
||||
html.replace("${version}", VERSION);
|
||||
|
||||
if(WiFi.getMode() != WIFI_AP) {
|
||||
html.replace("boot.css", BOOTSTRAP_URL);
|
||||
}
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
|
||||
html.replace("${config.wifiSsid}", config->getWifiSsid());
|
||||
html.replace("${config.wifiPassword}", config->getWifiPassword());
|
||||
html.replace("${config.wifiIpType1}", config->getWifiIp().isEmpty() ? "" : "selected");
|
||||
html.replace("${config.wifiIp}", config->getWifiIp());
|
||||
html.replace("${config.wifiGw}", config->getWifiGw());
|
||||
html.replace("${config.wifiSubnet}", config->getWifiSubnet());
|
||||
|
||||
server.setContentLength(html.length());
|
||||
server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
void AmsWebServer::configMqttHtml() {
|
||||
println("Serving /config/mqtt.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
String html = String((const __FlashStringHelper*) CONFIGMQTT_HTML);
|
||||
html.replace("${version}", VERSION);
|
||||
|
||||
if(WiFi.getMode() != WIFI_AP) {
|
||||
html.replace("boot.css", BOOTSTRAP_URL);
|
||||
}
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
|
||||
html.replace("${config.mqtt}", config->getMqttHost() == 0 ? "" : "checked");
|
||||
html.replace("${config.mqttHost}", config->getMqttHost());
|
||||
html.replace("${config.mqttPort}", String(config->getMqttPort()));
|
||||
html.replace("${config.mqttClientId}", config->getMqttClientId());
|
||||
html.replace("${config.mqttPublishTopic}", config->getMqttPublishTopic());
|
||||
html.replace("${config.mqttSubscribeTopic}", config->getMqttSubscribeTopic());
|
||||
html.replace("${config.mqttUser}", config->getMqttUser());
|
||||
html.replace("${config.mqttPassword}", config->getMqttPassword());
|
||||
|
||||
server.setContentLength(html.length());
|
||||
server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
void AmsWebServer::configWebHtml() {
|
||||
println("Serving /config/web.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
String html = String((const __FlashStringHelper*) CONFIGWEB_HTML);
|
||||
html.replace("${version}", VERSION);
|
||||
|
||||
if(WiFi.getMode() != WIFI_AP) {
|
||||
html.replace("boot.css", BOOTSTRAP_URL);
|
||||
}
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
|
||||
html.replace("${config.authSecurity}", String(config->getAuthSecurity()));
|
||||
for(int i = 0; i<3; i++) {
|
||||
html.replace("${config.authSecurity" + String(i) + "}", config->getAuthSecurity() == i ? "selected" : "");
|
||||
}
|
||||
html.replace("${config.authUser}", config->getAuthUser());
|
||||
html.replace("${config.authPassword}", config->getAuthPassword());
|
||||
|
||||
server.setContentLength(html.length());
|
||||
server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
void AmsWebServer::bootCss() {
|
||||
println("Serving /boot.css over http...");
|
||||
|
||||
String css = String((const __FlashStringHelper*) BOOT_CSS);
|
||||
|
||||
server.sendHeader("Cache-Control", "public, max-age=3600");
|
||||
|
||||
server.setContentLength(css.length());
|
||||
server.send(200, "text/css", css);
|
||||
}
|
||||
|
||||
void AmsWebServer::gaugemeterJs() {
|
||||
println("Serving /gaugemeter.js over http...");
|
||||
|
||||
String js = String((const __FlashStringHelper*) GAUGEMETER_JS);
|
||||
|
||||
server.sendHeader("Cache-Control", "public, max-age=3600");
|
||||
|
||||
server.setContentLength(js.length());
|
||||
server.send(200, "application/javascript", js);
|
||||
}
|
||||
|
||||
void AmsWebServer::dataJson() {
|
||||
println("Serving /data.json over http...");
|
||||
|
||||
if(!checkSecurity(2))
|
||||
return;
|
||||
|
||||
StaticJsonDocument<768> json;
|
||||
|
||||
String jsonStr;
|
||||
if(data.getLastUpdateMillis() > 0) {
|
||||
int maxPwr = this->maxPwr;
|
||||
if(maxPwr == 0) {
|
||||
if(data.isThreePhase()) {
|
||||
maxPwr = 20000;
|
||||
} else {
|
||||
maxPwr = 10000;
|
||||
}
|
||||
}
|
||||
|
||||
json["up"] = data.getLastUpdateMillis();
|
||||
json["t"] = data.getPackageTimestamp();
|
||||
json.createNestedObject("data");
|
||||
json["data"]["P"] = data.getActiveImportPower();
|
||||
json["data"]["PO"] = data.getActiveExportPower();
|
||||
|
||||
double u1 = data.getL1Voltage();
|
||||
double u2 = data.getL2Voltage();
|
||||
double u3 = data.getL3Voltage();
|
||||
double i1 = data.getL1Current();
|
||||
double i2 = data.getL2Current();
|
||||
double i3 = data.getL3Current();
|
||||
double tpi = data.getActiveImportCounter();
|
||||
double tpo = data.getActiveExportCounter();
|
||||
double tqi = data.getReactiveImportCounter();
|
||||
double tqo = data.getReactiveExportCounter();
|
||||
|
||||
if(u1 > 0) {
|
||||
json["data"]["U1"] = u1;
|
||||
json["data"]["I1"] = i1;
|
||||
}
|
||||
if(u2 > 0) {
|
||||
json["data"]["U2"] = u2;
|
||||
json["data"]["I2"] = i2;
|
||||
}
|
||||
if(u3 > 0) {
|
||||
json["data"]["U3"] = u3;
|
||||
json["data"]["I3"] = i3;
|
||||
}
|
||||
|
||||
if(tpi > 0) {
|
||||
json["data"]["tPI"] = tpi;
|
||||
json["data"]["tPO"] = tpo;
|
||||
json["data"]["tQI"] = tqi;
|
||||
json["data"]["tQO"] = tqo;
|
||||
}
|
||||
|
||||
json["p_pct"] = min(data.getActiveImportPower()*100/maxPwr, 100);
|
||||
|
||||
if(config->getProductionCapacity() > 0) {
|
||||
int maxPrd = config->getProductionCapacity() * 1000;
|
||||
json["po_pct"] = min(data.getActiveExportPower()*100/maxPrd, 100);
|
||||
}
|
||||
} else {
|
||||
json["p_pct"] = -1;
|
||||
json["po_pct"] = -1;
|
||||
}
|
||||
|
||||
unsigned long now = millis();
|
||||
json["id"] = WiFi.macAddress();
|
||||
json["maxPower"] = maxPwr;
|
||||
json["meterType"] = config->getMeterType();
|
||||
json["currentMillis"] = now;
|
||||
double vcc = hw.getVcc();
|
||||
json["vcc"] = serialized(String(vcc, 3));
|
||||
|
||||
double temp = hw.getTemperature();
|
||||
json["temp"] = serialized(String(temp, 2));
|
||||
|
||||
json.createNestedObject("wifi");
|
||||
float rssi = WiFi.RSSI();
|
||||
rssi = isnan(rssi) ? -100.0 : rssi;
|
||||
json["wifi"]["ssid"] = WiFi.SSID();
|
||||
json["wifi"]["channel"] = (int) WiFi.channel();
|
||||
json["wifi"]["rssi"] = rssi;
|
||||
|
||||
json.createNestedObject("status");
|
||||
|
||||
String espStatus;
|
||||
if(vcc == 0) {
|
||||
espStatus = "secondary";
|
||||
} else if(vcc > 3.1) {
|
||||
espStatus = "success";
|
||||
} else if(vcc > 2.8) {
|
||||
espStatus = "warning";
|
||||
} else {
|
||||
espStatus = "danger";
|
||||
}
|
||||
json["status"]["esp"] = espStatus;
|
||||
|
||||
String hanStatus;
|
||||
if(config->getMeterType() == 0) {
|
||||
hanStatus = "secondary";
|
||||
} else if(now - data.getLastUpdateMillis() < 15000) {
|
||||
hanStatus = "success";
|
||||
} else if(now - data.getLastUpdateMillis() < 30000) {
|
||||
hanStatus = "warning";
|
||||
} else {
|
||||
hanStatus = "danger";
|
||||
}
|
||||
json["status"]["han"] = hanStatus;
|
||||
|
||||
String wifiStatus;
|
||||
if(config->getWifiSsid().isEmpty()) {
|
||||
wifiStatus = "secondary";
|
||||
} else if(rssi > -75) {
|
||||
wifiStatus = "success";
|
||||
} else if(rssi > -95) {
|
||||
wifiStatus = "warning";
|
||||
} else {
|
||||
wifiStatus = "danger";
|
||||
}
|
||||
json["status"]["wifi"] = wifiStatus;
|
||||
|
||||
String mqttStatus;
|
||||
if(config->getMqttHost().isEmpty()) {
|
||||
mqttStatus = "secondary";
|
||||
} else if(mqtt->connected()) {
|
||||
mqttStatus = "success";
|
||||
} else if(mqtt->lastError() == 0) {
|
||||
mqttStatus = "warning";
|
||||
} else {
|
||||
mqttStatus = "danger";
|
||||
}
|
||||
json["status"]["mqtt"] = mqttStatus;
|
||||
|
||||
json.createNestedObject("mqtt");
|
||||
json["mqtt"]["lastError"] = (int) mqtt->lastError();
|
||||
|
||||
serializeJson(json, jsonStr);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
|
||||
server.setContentLength(jsonStr.length());
|
||||
server.send(200, "application/json", jsonStr);
|
||||
}
|
||||
|
||||
void AmsWebServer::handleSave() {
|
||||
String temp;
|
||||
|
||||
if(server.hasArg("meterConfig") && server.arg("meterConfig") == "true") {
|
||||
config->setMeterType(server.arg("meterType").toInt());
|
||||
config->setDistributionSystem(server.arg("distributionSystem").toInt());
|
||||
config->setMainFuse(server.arg("mainFuse").toInt());
|
||||
config->setProductionCapacity(server.arg("productionCapacity").toInt());
|
||||
}
|
||||
|
||||
if(server.hasArg("wifiConfig") && server.arg("wifiConfig") == "true") {
|
||||
config->setWifiSsid(server.arg("wifiSsid"));
|
||||
config->setWifiPassword(server.arg("wifiPassword"));
|
||||
if(server.hasArg("wifiIpType") && server.arg("wifiIpType").toInt() == 1) {
|
||||
config->setWifiIp(server.arg("wifiIp"));
|
||||
config->setWifiGw(server.arg("wifiGw"));
|
||||
config->setWifiSubnet(server.arg("wifiSubnet"));
|
||||
} else {
|
||||
config->clearWifiIp();
|
||||
}
|
||||
}
|
||||
|
||||
if(server.hasArg("mqttConfig") && server.arg("mqttConfig") == "true") {
|
||||
if(server.hasArg("mqtt") && server.arg("mqtt") == "true") {
|
||||
config->setMqttHost(server.arg("mqttHost"));
|
||||
config->setMqttPort(server.arg("mqttPort").toInt());
|
||||
config->setMqttClientId(server.arg("mqttClientId"));
|
||||
config->setMqttPublishTopic(server.arg("mqttPublishTopic"));
|
||||
config->setMqttSubscribeTopic(server.arg("mqttSubscribeTopic"));
|
||||
config->setMqttUser(server.arg("mqttUser"));
|
||||
config->setMqttPassword(server.arg("mqttPassword"));
|
||||
} else {
|
||||
config->clearMqtt();
|
||||
}
|
||||
}
|
||||
|
||||
if(server.hasArg("authConfig") && server.arg("authConfig") == "true") {
|
||||
config->setAuthSecurity((byte)server.arg("authSecurity").toInt());
|
||||
if(config->getAuthSecurity() > 0) {
|
||||
config->setAuthUser(server.arg("authUser"));
|
||||
config->setAuthPassword(server.arg("authPassword"));
|
||||
} else {
|
||||
config->clearAuth();
|
||||
}
|
||||
}
|
||||
|
||||
println("Saving configuration now...");
|
||||
|
||||
if (debugger) config->print(debugger);
|
||||
if (config->save()) {
|
||||
println("Successfully saved.");
|
||||
if(config->isWifiChanged()) {
|
||||
String html = "<html><body><h1>Successfully Saved!</h1><a href=\"/\">Go to index</a></form>";
|
||||
server.send(200, "text/html", html);
|
||||
yield();
|
||||
println("Wifi config changed, rebooting");
|
||||
delay(1000);
|
||||
#if defined(ESP8266)
|
||||
ESP.reset();
|
||||
#elif defined(ESP32)
|
||||
ESP.restart();
|
||||
#endif
|
||||
} else {
|
||||
server.sendHeader("Location", String("/"), true);
|
||||
server.send ( 302, "text/plain", "");
|
||||
}
|
||||
} else {
|
||||
println("Error saving configuration");
|
||||
String html = "<html><body><h1>Error saving configuration!</h1></form>";
|
||||
server.send(500, "text/html", html);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
size_t AmsWebServer::print(const char* text)
|
||||
{
|
||||
if (debugger) debugger->print(text);
|
||||
}
|
||||
size_t AmsWebServer::println(const char* text)
|
||||
{
|
||||
if (debugger) debugger->println(text);
|
||||
}
|
||||
size_t AmsWebServer::print(const Printable& data)
|
||||
{
|
||||
if (debugger) debugger->print(data);
|
||||
}
|
||||
size_t AmsWebServer::println(const Printable& data)
|
||||
{
|
||||
if (debugger) debugger->println(data);
|
||||
}
|
||||
69
src/web/AmsWebServer.h
Normal file
69
src/web/AmsWebServer.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef _AMSWEBSERVER_h
|
||||
#define _AMSWEBSERVER_h
|
||||
|
||||
#define BOOTSTRAP_URL "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css"
|
||||
|
||||
#include <MQTT.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HwTools.h"
|
||||
#include "AmsData.h"
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
class AmsWebServer {
|
||||
public:
|
||||
void setup(AmsConfiguration* config, Stream* debugger, MQTTClient* mqtt);
|
||||
void loop();
|
||||
|
||||
void setData(AmsData& data);
|
||||
|
||||
private:
|
||||
int maxPwr;
|
||||
HwTools hw;
|
||||
AmsConfiguration* config;
|
||||
AmsData data;
|
||||
Stream* debugger;
|
||||
MQTTClient* mqtt;
|
||||
|
||||
#if defined(ESP8266)
|
||||
ESP8266WebServer server;
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
WebServer server;
|
||||
#endif
|
||||
|
||||
bool checkSecurity(byte level);
|
||||
|
||||
void indexHtml();
|
||||
void configMeterHtml();
|
||||
void configWifiHtml();
|
||||
void configMqttHtml();
|
||||
void configWebHtml();
|
||||
void bootCss();
|
||||
void gaugemeterJs();
|
||||
void dataJson();
|
||||
|
||||
void handleSave();
|
||||
|
||||
size_t print(const char* text);
|
||||
size_t println(const char* text);
|
||||
size_t print(const Printable& data);
|
||||
size_t println(const Printable& data);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
491
web/boot.css
Normal file
491
web/boot.css
Normal file
@@ -0,0 +1,491 @@
|
||||
/* Ripped necessary style from bootstrap 4.4.1 to make the page look good without internet access. Meant to be overridden by CSS from CDN */
|
||||
:root {
|
||||
--blue: #007bff;
|
||||
--indigo: #6610f2;
|
||||
--purple: #6f42c1;
|
||||
--pink: #e83e8c;
|
||||
--red: #dc3545;
|
||||
--orange: #fd7e14;
|
||||
--yellow: #ffc107;
|
||||
--green: #28a745;
|
||||
--teal: #20c997;
|
||||
--cyan: #17a2b8;
|
||||
--white: #fff;
|
||||
--gray: #6c757d;
|
||||
--gray-dark: #343a40;
|
||||
--primary: #007bff;
|
||||
--secondary: #6c757d;
|
||||
--success: #28a745;
|
||||
--info: #17a2b8;
|
||||
--warning: #ffc107;
|
||||
--danger: #dc3545;
|
||||
--light: #f8f9fa;
|
||||
--dark: #343a40;
|
||||
--breakpoint-xs: 0;
|
||||
--breakpoint-sm: 576px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 992px;
|
||||
--breakpoint-xl: 1200px;
|
||||
--font-family-sans-serif: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
||||
--font-family-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
||||
}
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
color: -internal-root-color;
|
||||
}
|
||||
body {
|
||||
display: block;
|
||||
margin: 8px;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
.bg-white {
|
||||
background-color: #fff!important;
|
||||
}
|
||||
.bg-light {
|
||||
background-color: #f8f9fa!important;
|
||||
}
|
||||
.bg-purple {
|
||||
background-color: var(--purple);
|
||||
}
|
||||
.text-white-50 {
|
||||
color: rgba(255,255,255,.5)!important;
|
||||
}
|
||||
.mb-0, .my-0 {
|
||||
margin-bottom: 0!important;
|
||||
}
|
||||
.m-2 {
|
||||
margin: .5rem!important;
|
||||
}
|
||||
.mb-2, .my-2 {
|
||||
margin-bottom: .5rem!important;
|
||||
}
|
||||
.mt-2, .my-2 {
|
||||
margin-top: .5rem!important;
|
||||
}
|
||||
.pb-2, .py-2 {
|
||||
padding-bottom: .5rem!important;
|
||||
}
|
||||
.p-3 {
|
||||
padding: 1rem!important;
|
||||
}
|
||||
.mb-3, .my-3 {
|
||||
margin-bottom: 1rem!important;
|
||||
}
|
||||
.mt-3, .my-3 {
|
||||
margin-top: 1rem!important;
|
||||
}
|
||||
.mb-4, .my-4 {
|
||||
margin-bottom: 1.5rem!important;
|
||||
}
|
||||
.shadow {
|
||||
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;
|
||||
}
|
||||
.align-items-center {
|
||||
-ms-flex-align: center!important;
|
||||
align-items: center!important;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #dee2e6!important;
|
||||
}
|
||||
.rounded {
|
||||
border-radius: .25rem!important;
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
.text-white {
|
||||
color: #fff!important;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right!important;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center!important;
|
||||
}
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
margin-bottom: .5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
.h6, h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.row {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
margin-right: -15px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
.col, .col-1, .col-10, .col-11, .col-12, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-auto, .col-lg, .col-lg-1, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-auto, .col-md, .col-md-1, .col-md-10, .col-md-11, .col-md-12, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-auto, .col-sm, .col-sm-1, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-auto, .col-xl, .col-xl-1, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-auto {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
.col-2 {
|
||||
-ms-flex: 0 0 16.666667%;
|
||||
flex: 0 0 16.666667%;
|
||||
max-width: 16.666667%;
|
||||
}
|
||||
.col-3 {
|
||||
-ms-flex: 0 0 25%;
|
||||
flex: 0 0 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
.col-4 {
|
||||
-ms-flex: 0 0 33.333333%;
|
||||
flex: 0 0 33.333333%;
|
||||
max-width: 33.333333%;
|
||||
}
|
||||
.col-5 {
|
||||
-ms-flex: 0 0 41.666667%;
|
||||
flex: 0 0 41.666667%;
|
||||
max-width: 41.666667%;
|
||||
}
|
||||
.col-6 {
|
||||
-ms-flex: 0 0 50%;
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
.col-8 {
|
||||
-ms-flex: 0 0 66.666667%;
|
||||
flex: 0 0 66.666667%;
|
||||
max-width: 66.666667%;
|
||||
}
|
||||
.col-9 {
|
||||
-ms-flex: 0 0 75%;
|
||||
flex: 0 0 75%;
|
||||
max-width: 75%;
|
||||
}
|
||||
.d-none {
|
||||
display: none!important;
|
||||
}
|
||||
.d-flex {
|
||||
display: -ms-flexbox!important;
|
||||
display: flex!important;
|
||||
}
|
||||
.flex-row {
|
||||
-ms-flex-direction: row!important;
|
||||
flex-direction: row!important;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
-ms-flex-direction: column!important;
|
||||
flex-direction: column!important;
|
||||
}
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
color: #212529;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: .25rem;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
.btn-outline-secondary {
|
||||
color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
.navbar {
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
.navbar-dark .navbar-brand {
|
||||
color: #fff;
|
||||
}
|
||||
.navbar-expand {
|
||||
-ms-flex-flow: row nowrap;
|
||||
flex-flow: row nowrap;
|
||||
-ms-flex-pack: start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.navbar-nav {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
padding-left: 0;
|
||||
margin-bottom: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.navbar-expand .navbar-nav {
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.navbar-brand {
|
||||
display: inline-block;
|
||||
padding-top: .3125rem;
|
||||
padding-bottom: .3125rem;
|
||||
margin-right: 1rem;
|
||||
font-size: 1.25rem;
|
||||
line-height: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.navbar-dark .navbar-nav .nav-link {
|
||||
color: rgba(255,255,255,.5);
|
||||
}
|
||||
.navbar-expand .navbar-nav .nav-link {
|
||||
padding-right: .5rem;
|
||||
padding-left: .5rem;
|
||||
}
|
||||
.navbar-nav .nav-link {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
.nav-link {
|
||||
display: block;
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: .25em .4em;
|
||||
font-size: 75%;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: .25rem;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
.badge-secondary {
|
||||
color: #fff;
|
||||
background-color: #6c757d;
|
||||
}
|
||||
.badge-success {
|
||||
color: #fff;
|
||||
background-color: #28a745;
|
||||
}
|
||||
.badge-warning {
|
||||
color: #212529;
|
||||
background-color: #ffc107;
|
||||
}
|
||||
.badge-danger {
|
||||
color: #fff;
|
||||
background-color: #dc3545;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(1.5em + .75rem + 2px);
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
.form-control:disabled, .form-control[readonly] {
|
||||
background-color: #e9ecef;
|
||||
opacity: 1;
|
||||
}
|
||||
input:not([type="image" i]) {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
button, input {
|
||||
overflow: visible;
|
||||
}
|
||||
button, input, optgroup, select, textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
input[type="radio"], input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
.input-group {
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-ms-flex-align: stretch;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
.input-group-append {
|
||||
margin-left: -1px;
|
||||
}
|
||||
.input-group-append, .input-group-prepend {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
.input-group>.input-group-append>.btn, .input-group>.input-group-append>.input-group-text, .input-group>.input-group-prepend:first-child>.btn:not(:first-child), .input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child), .input-group>.input-group-prepend:not(:first-child)>.btn, .input-group>.input-group-prepend:not(:first-child)>.input-group-text {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.input-group-text {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
padding: .375rem .75rem;
|
||||
margin-bottom: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
background-color: #e9ecef;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
.input-group>.custom-select:not(:last-child), .input-group>.form-control:not(:last-child) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.input-group>.custom-file, .input-group>.custom-select, .input-group>.form-control, .input-group>.form-control-plaintext {
|
||||
position: relative;
|
||||
-ms-flex: 1 1 0%;
|
||||
flex: 1 1 0%;
|
||||
min-width: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
hr {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 0;
|
||||
border-top: 1px solid rgba(0,0,0,.1);
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
ul {
|
||||
display: block;
|
||||
list-style-type: disc;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
padding-inline-start: 40px;
|
||||
}
|
||||
li {
|
||||
display: list-item;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
dl, ol, ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 576px) {
|
||||
.container, .container-sm {
|
||||
max-width: 540px;
|
||||
}
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container, .container-md, .container-sm {
|
||||
max-width: 720px;
|
||||
}
|
||||
.col-md-3 {
|
||||
-ms-flex: 0 0 25%;
|
||||
flex: 0 0 25%;
|
||||
max-width: 25%;
|
||||
}
|
||||
.col-md-4 {
|
||||
-ms-flex: 0 0 33.333333%;
|
||||
flex: 0 0 33.333333%;
|
||||
max-width: 33.333333%;
|
||||
}
|
||||
.col-md-6 {
|
||||
-ms-flex: 0 0 50%;
|
||||
flex: 0 0 50%;
|
||||
max-width: 50%;
|
||||
}
|
||||
.d-md-flex {
|
||||
display: -ms-flexbox!important;
|
||||
display: flex!important;
|
||||
}
|
||||
.flex-md-row {
|
||||
-ms-flex-direction: row!important;
|
||||
flex-direction: row!important;
|
||||
}
|
||||
.ml-md-auto, .mx-md-auto {
|
||||
margin-left: auto!important;
|
||||
}
|
||||
}
|
||||
@media (min-width: 992px) {
|
||||
.container, .container-lg, .container-md, .container-sm {
|
||||
max-width: 960px;
|
||||
}
|
||||
.col-lg-4 {
|
||||
-ms-flex: 0 0 33.333333%;
|
||||
flex: 0 0 33.333333%;
|
||||
max-width: 33.333333%;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
.container, .container-lg, .container-md, .container-sm, .container-xl {
|
||||
max-width: 1140px;
|
||||
}
|
||||
}
|
||||
|
||||
*, ::after, ::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
103
web/configmeter.html
Normal file
103
web/configmeter.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader - Meter configuration</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" type="text/css" href="boot.css"/>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bg-purple rounded mt-2 mb-4" style="background-color: var(--purple);">
|
||||
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
|
||||
<div class="navbar-nav-scroll">
|
||||
<ul class="navbar-nav bd-navbar-nav flex-row">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/config-meter">Meter</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-wifi">WiFi</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-mqtt">MQTT</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-web">Web</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/gskjold/AmsToMqttBridge" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.36" focusable="false"><title>GitHub</title><path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<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-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-6">Meter type</label>
|
||||
<div class="col-6">
|
||||
<select class="form-control" 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="row form-group">
|
||||
<label class="col-6">Distribution system</label>
|
||||
<div class="col-6">
|
||||
<select class="form-control" 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>
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-6">Main fuse</label>
|
||||
<div class="col-6">
|
||||
<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="row form-group">
|
||||
<label class="col-6">Production capacity</label>
|
||||
<div class="col-6">
|
||||
<div class="input-group">
|
||||
<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>
|
||||
</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>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
118
web/configmqtt.html
Normal file
118
web/configmqtt.html
Normal file
@@ -0,0 +1,118 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader - WiFi configuration</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" type="text/css" href="boot.css"/>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bg-purple rounded mt-2 mb-4" style="background-color: var(--purple);">
|
||||
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
|
||||
<div class="navbar-nav-scroll">
|
||||
<ul class="navbar-nav bd-navbar-nav flex-row">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-meter">Meter</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-wifi">WiFi</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/config-mqtt">MQTT</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-web">Web</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/gskjold/AmsToMqttBridge" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.36" focusable="false"><title>GitHub</title><path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="mqttConfig" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Enable</label>
|
||||
<div class="col-8">
|
||||
<input id="mqttEnable" type="checkbox" name="mqtt" value="true" ${config.mqtt}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Hostname</label>
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control mqtt-config" name="mqttHost" value="${config.mqttHost}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Port</label>
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control mqtt-config" name="mqttPort" value="${config.mqttPort}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Client ID</label>
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control mqtt-config" name="mqttClientId" value="${config.mqttClientId}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Topic</label>
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control mqtt-config" name="mqttPublishTopic" value="${config.mqttPublishTopic}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Username</label>
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control mqtt-config" name="mqttUser" value="${config.mqttUser}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Password</label>
|
||||
<div class="col-8">
|
||||
<input type="password" class="form-control mqtt-config" name="mqttPassword" value="${config.mqttPassword}"/>
|
||||
</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>
|
||||
</main>
|
||||
<script>
|
||||
$('#mqttEnable').on('change', function() {
|
||||
var inputs = $('.mqtt-config');
|
||||
inputs.prop('disabled', !$(this).is(':checked'));
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$('#mqttEnable').trigger('change');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
94
web/configweb.html
Normal file
94
web/configweb.html
Normal file
@@ -0,0 +1,94 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader - Meter configuration</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" type="text/css" href="boot.css"/>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bg-purple rounded mt-2 mb-4" style="background-color: var(--purple);">
|
||||
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
|
||||
<div class="navbar-nav-scroll">
|
||||
<ul class="navbar-nav bd-navbar-nav flex-row">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/config-meter">Meter</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-wifi">WiFi</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-mqtt">MQTT</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/config-web">Web</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/gskjold/AmsToMqttBridge" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.36" focusable="false"><title>GitHub</title><path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="authConfig" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Security</label>
|
||||
<div class="col-8">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Username</label>
|
||||
<div class="col-8">
|
||||
<input type="text" class="form-control auth-config" name="authUser" value="${config.authUser}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-4">Password</label>
|
||||
<div class="col-8">
|
||||
<input type="password" class="form-control auth-config" name="authPassword" value="${config.authPassword}"/>
|
||||
</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>
|
||||
</main>
|
||||
<script>
|
||||
$('#authSecurity').on('change', function() {
|
||||
var inputs = $('.auth-config');
|
||||
inputs.prop('disabled', $(this).val() == 0);
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$('#authSecurity').trigger('change');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
111
web/configwifi.html
Normal file
111
web/configwifi.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader - WiFi configuration</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" type="text/css" href="boot.css"/>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bg-purple rounded mt-2 mb-4" style="background-color: var(--purple);">
|
||||
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
|
||||
<div class="navbar-nav-scroll">
|
||||
<ul class="navbar-nav bd-navbar-nav flex-row">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-meter">Meter</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/config-wifi">WiFi</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-mqtt">MQTT</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-web">Web</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/gskjold/AmsToMqttBridge" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.36" focusable="false"><title>GitHub</title><path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="wifiConfig" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-3">SSID</label>
|
||||
<div class="col-9">
|
||||
<input type="text" class="form-control" name="wifiSsid" value="${config.wifiSsid}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-3">Password</label>
|
||||
<div class="col-9">
|
||||
<input type="password" class="form-control" name="wifiPassword" value="${config.wifiPassword}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-6">IP configuration</label>
|
||||
<div class="col-6">
|
||||
<select id="wifiIpType" class="form-control" name="wifiIpType">
|
||||
<option value="0" ${config.wifiIpType0}>DHCP</option>
|
||||
<option value="1" ${config.wifiIpType1}>Static</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-3">IP</label>
|
||||
<div class="col-9">
|
||||
<input type="text" class="form-control wifiip-config" name="wifiIp" value="${config.wifiIp}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row form-group">
|
||||
<label class="col-3">Subnet</label>
|
||||
<div class="col-9">
|
||||
<input type="text" class="form-control wifiip-config" name="wifiSubnet" value="${config.wifiSubnet}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-3">Gateway</label>
|
||||
<div class="col-9">
|
||||
<input type="text" class="form-control wifiip-config" name="wifiGw" value="${config.wifiGw}"/>
|
||||
</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>
|
||||
</main>
|
||||
<script>
|
||||
$('#wifiIpType').on('change', function() {
|
||||
var inputs = $('.wifiip-config');
|
||||
inputs.prop('disabled', $(this).val() != 1);
|
||||
});
|
||||
|
||||
$(function() {
|
||||
$('#wifiIpType').trigger('change');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
275
web/gaugemeter.js
Normal file
275
web/gaugemeter.js
Normal file
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* AshAlom Gauge Meter. Version 2.0.0
|
||||
* Copyright AshAlom.com All rights reserved.
|
||||
* https://github.com/AshAlom/GaugeMeter <- Deleted!
|
||||
* https://github.com/githubsrinath/GaugeMeter <- Backup original.
|
||||
*
|
||||
* Original created by Dr Ash Alom
|
||||
*
|
||||
* This is a bug fixed and modified version of the AshAlom Gauge Meter.
|
||||
* Copyright 2018 Michael Wolf (Mictronics)
|
||||
* https://github.com/mictronics/GaugeMeter
|
||||
*
|
||||
*/
|
||||
!function ($) {
|
||||
$.fn.gaugeMeter = function (t) {
|
||||
var defaults = $.extend({
|
||||
id: "",
|
||||
percent: 0,
|
||||
used: null,
|
||||
min: null,
|
||||
total: null,
|
||||
size: 100,
|
||||
prepend: "",
|
||||
append: "",
|
||||
theme: "Red-Gold-Green",
|
||||
color: "",
|
||||
back: "RGBa(0,0,0,.06)",
|
||||
width: 3,
|
||||
style: "Full",
|
||||
stripe: "0",
|
||||
animationstep: 1,
|
||||
animate_gauge_colors: false,
|
||||
animate_text_colors: false,
|
||||
label: "",
|
||||
label_color: "Black",
|
||||
text: "",
|
||||
text_size: 0.22,
|
||||
fill: "",
|
||||
showvalue: false
|
||||
}, t);
|
||||
return this.each(function () {
|
||||
|
||||
function getThemeColor(e) {
|
||||
var t = "#2C94E0";
|
||||
return e || (e = 1e-14),
|
||||
"Red-Gold-Green" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#e32100"), e > 20 && (t = "#f35100"), e > 30 && (t = "#ff8700"), e > 40 && (t = "#ffb800"), e > 50 && (t = "#ffd900"), e > 60 && (t = "#dcd800"), e > 70 && (t = "#a6d900"), e > 80 && (t = "#69d900"), e > 90 && (t = "#32d900")),
|
||||
"Green-Gold-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#69d900"), e > 20 && (t = "#a6d900"), e > 30 && (t = "#dcd800"), e > 40 && (t = "#ffd900"), e > 50 && (t = "#ffb800"), e > 60 && (t = "#ff8700"), e > 70 && (t = "#f35100"), e > 80 && (t = "#e32100"), e > 90 && (t = "#d90000")),
|
||||
"Green-Red" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#41c900"), e > 20 && (t = "#56b300"), e > 30 && (t = "#6f9900"), e > 40 && (t = "#8a7b00"), e > 50 && (t = "#a75e00"), e > 60 && (t = "#c24000"), e > 70 && (t = "#db2600"), e > 80 && (t = "#f01000"), e > 90 && (t = "#ff0000")),
|
||||
"Red-Green" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#f01000"), e > 20 && (t = "#db2600"), e > 30 && (t = "#c24000"), e > 40 && (t = "#a75e00"), e > 50 && (t = "#8a7b00"), e > 60 && (t = "#6f9900"), e > 70 && (t = "#56b300"), e > 80 && (t = "#41c900"), e > 90 && (t = "#32d900")),
|
||||
"DarkBlue-LightBlue" === option.theme && (e > 0 && (t = "#2c94e0"), e > 10 && (t = "#2b96e1"), e > 20 && (t = "#2b99e4"), e > 30 && (t = "#2a9ce7"), e > 40 && (t = "#28a0e9"), e > 50 && (t = "#26a4ed"), e > 60 && (t = "#25a8f0"), e > 70 && (t = "#24acf3"), e > 80 && (t = "#23aff5"), e > 90 && (t = "#21b2f7")),
|
||||
"LightBlue-DarkBlue" === option.theme && (e > 0 && (t = "#21b2f7"), e > 10 && (t = "#23aff5"), e > 20 && (t = "#24acf3"), e > 30 && (t = "#25a8f0"), e > 40 && (t = "#26a4ed"), e > 50 && (t = "#28a0e9"), e > 60 && (t = "#2a9ce7"), e > 70 && (t = "#2b99e4"), e > 80 && (t = "#2b96e1"), e > 90 && (t = "#2c94e0")),
|
||||
"DarkRed-LightRed" === option.theme && (e > 0 && (t = "#d90000"), e > 10 && (t = "#dc0000"), e > 20 && (t = "#e00000"), e > 30 && (t = "#e40000"), e > 40 && (t = "#ea0000"), e > 50 && (t = "#ee0000"), e > 60 && (t = "#f30000"), e > 70 && (t = "#f90000"), e > 80 && (t = "#fc0000"), e > 90 && (t = "#ff0000")),
|
||||
"LightRed-DarkRed" === option.theme && (e > 0 && (t = "#ff0000"), e > 10 && (t = "#fc0000"), e > 20 && (t = "#f90000"), e > 30 && (t = "#f30000"), e > 40 && (t = "#ee0000"), e > 50 && (t = "#ea0000"), e > 60 && (t = "#e40000"), e > 70 && (t = "#e00000"), e > 80 && (t = "#dc0000"), e > 90 && (t = "#d90000")),
|
||||
"DarkGreen-LightGreen" === option.theme && (e > 0 && (t = "#32d900"), e > 10 && (t = "#33db00"), e > 20 && (t = "#34df00"), e > 30 && (t = "#34e200"), e > 40 && (t = "#36e700"), e > 50 && (t = "#37ec00"), e > 60 && (t = "#38f100"), e > 70 && (t = "#38f600"), e > 80 && (t = "#39f900"), e > 90 && (t = "#3afc00")),
|
||||
"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")),
|
||||
"White" === option.theme && (t = "#fff"),
|
||||
"Black" === option.theme && (t = "#000"),
|
||||
t;
|
||||
}
|
||||
/* The label below gauge. */
|
||||
function createLabel(t, a) {
|
||||
if(t.children("b").length === 0){
|
||||
$("<b></b>").appendTo(t).html(option.label).css({
|
||||
"line-height": option.size + 5 * a + "px",
|
||||
color: option.label_color
|
||||
});
|
||||
}
|
||||
}
|
||||
/* Prepend and append text, the gauge text or percentage value. */
|
||||
function createSpanTag(t) {
|
||||
var fgcolor = "";
|
||||
if (option.animate_text_colors === true){
|
||||
fgcolor = option.fgcolor;
|
||||
}
|
||||
var child = t.children("span");
|
||||
if(child.length !== 0){
|
||||
child.html(r).css({color: fgcolor});
|
||||
return;
|
||||
}
|
||||
if(option.text_size <= 0.0 || Number.isNaN(option.text_size)){
|
||||
option.text_size = 0.22;
|
||||
}
|
||||
if(option.text_size > 0.5){
|
||||
option.text_size = 0.5;
|
||||
}
|
||||
$("<span></span>").appendTo(t).html(r).css({
|
||||
"line-height": option.size + "px",
|
||||
"font-size": option.text_size * option.size + "px",
|
||||
color: fgcolor
|
||||
});
|
||||
}
|
||||
/* Get data attributes as options from div tag. Fall back to defaults when not exists. */
|
||||
function getDataAttr(t) {
|
||||
$.each(dataAttr, function (index, element) {
|
||||
if(t.data(element) !== undefined && t.data(element) !== null){
|
||||
option[element] = t.data(element);
|
||||
} else {
|
||||
option[element] = $(defaults).attr(element);
|
||||
}
|
||||
|
||||
if(element === "fill"){
|
||||
s = option[element];
|
||||
}
|
||||
|
||||
if((element === "size" ||
|
||||
element === "width" ||
|
||||
element === "animationstep" ||
|
||||
element === "stripe"
|
||||
) && !Number.isInteger(option[element])){
|
||||
option[element] = parseInt(option[element]);
|
||||
}
|
||||
|
||||
if(element === "text_size"){
|
||||
option[element] = parseFloat(option[element]);
|
||||
}
|
||||
});
|
||||
}
|
||||
/* Draws the gauge. */
|
||||
function drawGauge(a) {
|
||||
if(M < 0) M = 0;
|
||||
if(M > 100) M = 100;
|
||||
var lw = option.width < 1 || isNaN(option.width) ? option.size / 20 : option.width;
|
||||
g.clearRect(0, 0, b.width, b.height);
|
||||
g.beginPath();
|
||||
g.arc(m, v, x, G, k, !1);
|
||||
if(s){
|
||||
g.fillStyle = option.fill;
|
||||
g.fill();
|
||||
}
|
||||
g.lineWidth = lw;
|
||||
g.strokeStyle = option.back;
|
||||
option.stripe > parseInt(0) ? g.setLineDash([option.stripe], 1) : g.lineCap = "round";
|
||||
g.stroke();
|
||||
g.beginPath();
|
||||
g.arc(m, v, x, -I, P * a - I, !1);
|
||||
g.lineWidth = lw;
|
||||
g.strokeStyle = option.fgcolor;
|
||||
g.stroke();
|
||||
c > M && (M += z, requestAnimationFrame(function(){
|
||||
drawGauge(Math.min(M, c) / 100);
|
||||
}, p));
|
||||
}
|
||||
|
||||
$(this).attr("data-id", $(this).attr("id"));
|
||||
var r,
|
||||
dataAttr = ["percent",
|
||||
"used",
|
||||
"min",
|
||||
"total",
|
||||
"size",
|
||||
"prepend",
|
||||
"append",
|
||||
"theme",
|
||||
"color",
|
||||
"back",
|
||||
"width",
|
||||
"style",
|
||||
"stripe",
|
||||
"animationstep",
|
||||
"animate_gauge_colors",
|
||||
"animate_text_colors",
|
||||
"label",
|
||||
"label_color",
|
||||
"text",
|
||||
"text_size",
|
||||
"fill",
|
||||
"showvalue"],
|
||||
option = {},
|
||||
c = 0,
|
||||
p = $(this),
|
||||
s = false;
|
||||
p.addClass("gaugeMeter");
|
||||
getDataAttr(p);
|
||||
|
||||
if(Number.isInteger(option.used) && Number.isInteger(option.total)){
|
||||
var u = option.used;
|
||||
var t = option.total;
|
||||
if(Number.isInteger(option.min)) {
|
||||
if(option.min < 0) {
|
||||
t -= option.min;
|
||||
u -= option.min;
|
||||
}
|
||||
}
|
||||
c = u / (t / 100);
|
||||
} else {
|
||||
if(Number.isInteger(option.percent)){
|
||||
c = option.percent;
|
||||
} else {
|
||||
c = parseInt(defaults.percent);
|
||||
}
|
||||
}
|
||||
if(c < 0) c = 0;
|
||||
if(c > 100) c = 100;
|
||||
|
||||
if( option.text !== "" && option.text !== null && option.text !== undefined){
|
||||
if(option.append !== "" && option.append !== null && option.append !== undefined){
|
||||
r = option.text + "<u>" + option.append + "</u>";
|
||||
} else {
|
||||
r = option.text;
|
||||
}
|
||||
if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){
|
||||
r = "<s>" + option.prepend + "</s>" + r;
|
||||
}
|
||||
} else {
|
||||
if(defaults.showvalue === true || option.showvalue === true){
|
||||
r = option.used;
|
||||
} else {
|
||||
r = c.toString();
|
||||
}
|
||||
if(option.prepend !== "" && option.prepend !== null && option.prepend !== undefined){
|
||||
r = "<s>" + option.prepend + "</s>" + r;
|
||||
}
|
||||
|
||||
if(option.append !== "" && option.append !== null && option.append !== undefined){
|
||||
r = r + "<u>" + option.append + "</u>";
|
||||
}
|
||||
}
|
||||
|
||||
option.fgcolor = getThemeColor(c);
|
||||
if(option.color !== "" && option.color !== null && option.color !== undefined){
|
||||
option.fgcolor = option.color;
|
||||
}
|
||||
|
||||
if(option.animate_gauge_colors === true){
|
||||
option.fgcolor = getThemeColor(c);
|
||||
}
|
||||
createSpanTag(p);
|
||||
|
||||
if(option.style !== "" && option.style !== null && option.style !== undefined){
|
||||
createLabel(p, option.size / 13);
|
||||
}
|
||||
|
||||
$(this).width(option.size + "px");
|
||||
|
||||
var b = $("<canvas></canvas>").attr({width: option.size, height: option.size}).get(0),
|
||||
g = b.getContext("2d"),
|
||||
m = b.width / 2,
|
||||
v = b.height / 2,
|
||||
_ = 360 * option.percent,
|
||||
x = (_ * (Math.PI / 180), b.width / 2.5),
|
||||
k = 2.3 * Math.PI,
|
||||
G = 0,
|
||||
M = 0 === option.animationstep ? c : 0,
|
||||
z = Math.max(option.animationstep, 0),
|
||||
P = 2 * Math.PI,
|
||||
I = Math.PI / 2,
|
||||
R = option.style;
|
||||
var child = $(this).children("canvas");
|
||||
if(child.length !== 0){
|
||||
/* Replace existing canvas when new percentage was written. */
|
||||
child.replaceWith(b);
|
||||
} else {
|
||||
/* Initially create canvas. */
|
||||
$(b).appendTo($(this));
|
||||
}
|
||||
|
||||
if ("Semi" === R){
|
||||
k = 2 * Math.PI;
|
||||
G = 3.13;
|
||||
P = 1 * Math.PI;
|
||||
I = Math.PI / .996;
|
||||
}
|
||||
if ("Arch" === R){
|
||||
k = 2.195 * Math.PI;
|
||||
G = 1, G = 655.99999;
|
||||
P = 1.4 * Math.PI;
|
||||
I = Math.PI / .8335;
|
||||
}
|
||||
drawGauge(M / 100);
|
||||
});
|
||||
};
|
||||
}
|
||||
(jQuery);
|
||||
355
web/index.html
Normal file
355
web/index.html
Normal file
@@ -0,0 +1,355 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>AMS reader</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="stylesheet" type="text/css" href="boot.css"/>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
|
||||
<script src="gaugemeter.js"></script>
|
||||
<style>
|
||||
.GaugeMeter {
|
||||
position: Relative;
|
||||
text-align: Center;
|
||||
overflow: Hidden;
|
||||
cursor: Default;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.GaugeMeter SPAN, .GaugeMeter B {
|
||||
width: 54%;
|
||||
position: Absolute;
|
||||
text-align: Center;
|
||||
display: Inline-Block;
|
||||
color: RGBa(0,0,0,.8);
|
||||
font-weight: 100;
|
||||
font-family: "Open Sans", Arial;
|
||||
overflow: Hidden;
|
||||
white-space: NoWrap;
|
||||
text-overflow: Ellipsis;
|
||||
margin: 0 23%;
|
||||
}
|
||||
|
||||
.GaugeMeter[data-style="Semi"] B {
|
||||
width: 80%;
|
||||
margin: 0 10%;
|
||||
}
|
||||
|
||||
.GaugeMeter S, .GaugeMeter U {
|
||||
text-decoration: None;
|
||||
font-size: .60em;
|
||||
font-weight: 200;
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
.GaugeMeter B {
|
||||
color: #000;
|
||||
font-weight: 200;
|
||||
opacity: .8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<main role="main" class="container">
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row rounded mt-2 mb-4" style="background-color: var(--purple);">
|
||||
<a href="/" class=""><h6 class="navbar-brand">AMS reader <small>${version}</small></h6></a>
|
||||
<div class="navbar-nav-scroll">
|
||||
<ul class="navbar-nav bd-navbar-nav flex-row">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-meter">Meter</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-wifi">WiFi</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-mqtt">MQTT</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link " href="/config-web">Web</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex-row ml-md-auto d-md-flex">
|
||||
<div id="esp" class="d-none m-2">ESP</div>
|
||||
<div id="han" class="d-none m-2">HAN</div>
|
||||
<div id="wifi" class="d-none m-2">WiFi</div>
|
||||
<div id="mqtt" class="d-none m-2">MQTT</div>
|
||||
</div>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link p-2" href="https://github.com/gskjold/AmsToMqttBridge" target="_blank" rel="noopener" aria-label="GitHub">
|
||||
<svg class="d-inline-block align-text-top" style="width: 2rem; height: 2rem;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 499.36" focusable="false"><title>GitHub</title><path d="M256 0C114.64 0 0 114.61 0 256c0 113.09 73.34 209 175.08 242.9 12.8 2.35 17.47-5.56 17.47-12.34 0-6.08-.22-22.18-.35-43.54-71.2 15.49-86.2-34.34-86.2-34.34-11.64-29.57-28.42-37.45-28.42-37.45-23.27-15.84 1.73-15.55 1.73-15.55 25.69 1.81 39.21 26.38 39.21 26.38 22.84 39.12 59.92 27.82 74.5 21.27 2.33-16.54 8.94-27.82 16.25-34.22-56.84-6.43-116.6-28.43-116.6-126.49 0-27.95 10-50.8 26.35-68.69-2.63-6.48-11.42-32.5 2.51-67.75 0 0 21.49-6.88 70.4 26.24a242.65 242.65 0 0 1 128.18 0c48.87-33.13 70.33-26.24 70.33-26.24 14 35.25 5.18 61.27 2.55 67.75 16.41 17.9 26.31 40.75 26.31 68.69 0 98.35-59.85 120-116.88 126.32 9.19 7.9 17.38 23.53 17.38 47.41 0 34.22-.31 61.83-.31 70.23 0 6.85 4.61 14.81 17.6 12.31C438.72 464.97 512 369.08 512 256.02 512 114.62 397.37 0 256 0z" fill="currentColor" fill-rule="evenodd"></path></svg>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</header>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<div id="P" class="SimpleMeter" style="display: inline;">
|
||||
${data.P} W
|
||||
</div>
|
||||
<div id="importMeter" class="GaugeMeter rounded"
|
||||
style="display: none;"
|
||||
data-size="200px"
|
||||
data-text_size="0.11"
|
||||
data-width="25"
|
||||
data-style="Arch"
|
||||
data-theme="Green-Gold-Red"
|
||||
data-animationstep="0"
|
||||
data-label="${text.import}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div id="U1-row" class="row" style="display: ${display.P1};">
|
||||
<div class="col-2">P1</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">P2</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">P3</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>
|
||||
</div>
|
||||
<div class="col-md-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-md-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>
|
||||
</div>
|
||||
</main>
|
||||
<script>
|
||||
var im = $("#importMeter")
|
||||
im.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
|
||||
var em = $("#exportMeter")
|
||||
em.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
|
||||
var setStatus = function(id, status) {
|
||||
var item = $('#'+id);
|
||||
item.removeClass('d-none');
|
||||
item.removeClass (function (index, className) {
|
||||
return (className.match (/(^|\s)badge-\S+/g) || []).join(' ');
|
||||
});
|
||||
item.addClass('badge badge-' + status);
|
||||
};
|
||||
|
||||
var interval = 5000;
|
||||
var fetch = function() {
|
||||
$.ajax({
|
||||
url: '/data.json',
|
||||
timeout: 10000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
$(".SimpleMeter").hide();
|
||||
im.show();
|
||||
em.show();
|
||||
|
||||
for(var id in json) {
|
||||
var str = json[id];
|
||||
if(typeof str === "object")
|
||||
continue;
|
||||
if(isNaN(str)) {
|
||||
$('#'+id).html(str);
|
||||
} else {
|
||||
var num = parseFloat(str);
|
||||
$('#'+id).html(num.toFixed(num < 0 ? 0 : num < 10 ? 2 : 1));
|
||||
}
|
||||
}
|
||||
|
||||
if(window.moment) {
|
||||
$('#currentMillis').html(moment.duration(parseInt(json.currentMillis)).humanize());
|
||||
$('#currentMillis').closest('.row').show();
|
||||
}
|
||||
|
||||
if(json.status) {
|
||||
for(var id in json.status) {
|
||||
setStatus(id, json.status[id]);
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
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 {
|
||||
im.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
|
||||
em.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
}
|
||||
setTimeout(fetch, interval);
|
||||
}).fail(function() {
|
||||
setTimeout(fetch, interval*4);
|
||||
|
||||
im.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
|
||||
em.gaugeMeter({
|
||||
percent: 0,
|
||||
text: "-",
|
||||
append: "W"
|
||||
});
|
||||
|
||||
setStatus("mqtt", "secondary");
|
||||
setStatus("wifi", "secondary");
|
||||
setStatus("han", "secondary");
|
||||
setStatus("esp", "danger");
|
||||
});
|
||||
}
|
||||
fetch();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user