Compare commits

...

119 Commits

Author SHA1 Message Date
Gunnar Skjold
c936f605d6 Fixes #45 2020-03-25 20:21:39 +01:00
Gunnar Skjold
9d0ceb9ca8 Fixes problem with release 2020-03-25 20:07:23 +01:00
Gunnar Skjold
b33273e3cc Fixes #47 2020-03-25 20:06:42 +01:00
Gunnar Skjold
ab016fff93 Merge pull request #54 from Allram/master
Increased MQTT buffer to work with Aidon 6525 meters
2020-03-24 20:11:00 +01:00
Vegard Fladby
a215aa7766 Increased MQTT buffer to work with Aidon 6525 meters 2020-03-24 19:49:40 +01:00
Gunnar Skjold
30d73c3a6e Updated documentation 2020-03-01 13:24:06 +01:00
Gunnar Skjold
bd905c3595 Merge pull request #44 from gskjold/dev-v1.1.0
Dev v1.1.0
2020-03-01 12:56:12 +01:00
Gunnar Skjold
1a2e70b1fb Merge pull request #42 from ArnieO/dev-v1.1.0
Dev v1.1.0 Optional ESP8266 Vcc calibration factor in *.ini file
2020-03-01 12:53:56 +01:00
Gunnar Skjold
d6da7b2715 Merge branch 'dev-v1.1.0' of github.com:gskjold/AmsToMqttBridge into dev-v1.1.0 2020-03-01 09:41:18 +01:00
Gunnar Skjold
059a430f9a Fixed problem with AP mode with INVALID_BUTTON_PIN set 2020-03-01 09:39:58 +01:00
Gunnar Skjold
8ade50c2a6 Initialize all meter data to 0. Removed calculations for i2 2020-02-26 09:06:29 +01:00
Gunnar Skjold
5b94d8ff61 Added calculated value for L2 current when missing from Aidon 2020-02-25 14:08:04 +01:00
ArnieO
232b9c279d Optional Vcc calibration factor defined in *.ino 2020-02-24 20:05:34 +01:00
ArnieO
9b6a6af6ec Merge remote-tracking branch 'upstream/dev-v1.1.0' into dev-v1.1.0 2020-02-24 13:32:26 +01:00
Gunnar Skjold
9671e1eba3 Fixed bug where UI showed stalled HAN when exporting power. Also, maybe fixed issue with Vcc precision in UI? 2020-02-24 12:01:51 +01:00
Gunnar Skjold
78e531a7e9 Changed term for production and consumption when local production is defined 2020-02-24 07:47:39 +01:00
Gunnar Skjold
478d63505d Allow compiling when RGB is not defined 2020-02-24 07:47:12 +01:00
Gunnar Skjold
d8dfaaa730 Version hardening 2020-02-24 07:46:48 +01:00
Gunnar Skjold
f311b70b37 Merge branch 'master' into dev-v1.1.0 2020-02-24 07:17:48 +01:00
Gunnar Skjold
52bc2b14c9 Some changes after testing 2020-02-23 20:31:10 +01:00
ArnieO
2ddfc16d6a Updated comment for rgbled() function 2020-02-23 17:12:26 +01:00
Gunnar Skjold
51ab4a7f07 Reducing precision on temperature and vcc in json to reduce length 2020-02-23 11:34:25 +01:00
Gunnar Skjold
e90a30bfde Implemented blinking for red LED on error 2020-02-22 17:23:56 +01:00
Gunnar Skjold
a63e6962d3 Merge pull request #38 from ArnieO/dev-v1.1.0
Added code for optional RGB LED
2020-02-22 16:42:19 +01:00
Gunnar Skjold
951ccf7de5 Merge branch 'dev-v1.1.0' into dev-v1.1.0 2020-02-22 16:42:04 +01:00
Gunnar Skjold
001e3376df Merge pull request #39 from gskjold/dev-v1.1.0-mqttraw
Merged code preparations for other MQTT payload formats
2020-02-22 16:37:29 +01:00
Gunnar Skjold
9e4bf93f0a Some changes to reduce dlms read errors 2020-02-22 12:29:04 +01:00
ArnieO
e5ac823f69 Changed mqtt buffer to 384, JSON buffer to <512> 2020-02-21 23:29:01 +01:00
Gunnar Skjold
73fd228e2f Merge branch 'dev-v1.1.0' into dev-v1.1.0-mqttraw 2020-02-21 20:58:17 +01:00
Gunnar Skjold
fdac8f88af Minor optimization 2020-02-21 20:52:55 +01:00
Gunnar Skjold
9c8552869c Preparations for selectable MQTT payload format 2020-02-21 20:51:55 +01:00
ArnieO
be75b013c0 Added code for RGB LED 2020-02-21 19:13:44 +01:00
ArnieO
79274e490d Added code for RGB LED 2020-02-21 18:53:05 +01:00
ArnieO
47293eebe4 Added code for RGB LED 2020-02-21 18:38:28 +01:00
Gunnar Skjold
90b62638ed Dependency hardening 2020-02-20 20:36:12 +01:00
Gunnar Skjold
78e766156d Minimizing calls to web server 2020-02-18 21:33:41 +01:00
Gunnar Skjold
4b32af69a7 Fixed typo 2020-02-18 07:37:42 +01:00
Gunnar Skjold
916b5550d9 Fixed typo 2020-02-18 07:25:10 +01:00
Gunnar Skjold
48016240f9 Some cleanup after testing with ESP32 2020-02-17 20:06:52 +01:00
Gunnar Skjold
518b408205 Minor changes to fix build 2020-02-17 17:23:18 +01:00
Gunnar Skjold
73a0359597 Minor changes to fix build 2020-02-17 17:21:34 +01:00
Gunnar Skjold
bbe82f9cdc Added a small delay that helped smooth the voltage on a HAN powered device. No more sudden reboots 2020-02-16 21:45:58 +01:00
Gunnar Skjold
4786735d4c Support older config versions. Fixed css for AP mode. Some cleanup and changes to preserve power 2020-02-16 20:09:29 +01:00
Gunnar Skjold
227eb7b6ff Now using external base64 lib. Also updated some previously commited errors 2020-02-16 16:03:01 +01:00
Gunnar Skjold
00edd94033 Fixed some bugs from late night coding 2020-02-16 15:46:01 +01:00
Gunnar Skjold
ccd818e1ab Merge branch 'master' into dev-v1.1.0 2020-02-16 15:28:47 +01:00
Gunnar Skjold
fc42fb22d8 Merge pull request #33 from gskjold/dev-v1.0.1
Dev v1.0.1
2020-02-16 14:49:36 +01:00
Gunnar Skjold
f6066fbbf3 Cleanup and fix bugs found during testing 2020-02-15 22:13:04 +01:00
Gunnar Skjold
9c09740a41 Production graph and fixed vcc value 2020-02-15 21:04:18 +01:00
Gunnar Skjold
4e24e2949a Updated featheresp32 config to use Serial2 for Mbus 2020-02-15 18:01:10 +01:00
Gunnar Skjold
eff00f1fe0 Only show temp if sensor is attached. Added common functions to get temp and vcc 2020-02-14 21:48:03 +01:00
Gunnar Skjold
f484f3eb0e Merge branch 'dev-v1.0.1' into dev-v1.1.0 2020-02-14 20:19:15 +01:00
Gunnar Skjold
b2174dd521 If debug mode and HW_ROARFRED, use software serial to set upp RX for HAN with 2400 and TX for debugging with 115200 2020-02-14 20:14:30 +01:00
Gunnar Skjold
fe79a96827 Added change to allow auto modem sleep 2020-02-14 19:52:15 +01:00
Gunnar Skjold
517a40b0a6 Finalized new configuration menu and moved AP button trigger from setup to loop 2020-02-14 19:49:25 +01:00
Gunnar Skjold
75f3c8c592 Merge branch 'dev-v1.0.1' into dev-v1.1.0 2020-02-14 16:07:54 +01:00
Gunnar Skjold
554cc7c023 Added date and time from meter as rtc in json 2020-02-14 16:03:52 +01:00
Gunnar Skjold
6d0c722c98 Using kwh and kvarh as standard for accumulated numbers from kamstrup 2020-02-14 12:25:40 +01:00
Gunnar Skjold
d2f93c07bd Not sure if this does anything, but increased json and mqtt limits to see if Kamstrup list 3 is received 2020-02-13 20:50:13 +01:00
Gunnar Skjold
d63516fd49 Deleted autosave 2020-02-13 20:41:08 +01:00
Gunnar Skjold
c114b777c7 Minor changes to UI 2020-02-13 20:40:33 +01:00
Gunnar Skjold
a06729b535 Bugfix for previous commit 2020-02-12 20:11:55 +01:00
Gunnar Skjold
0ea21991ea Continued work with v1.1.0
- Merge branch 'low_power' into dev-v1.1.0
- Corrected accumulated import/export from Aidon
- Added VCC and RSSI to MQTT messages

Changes in UI:
- New top navbar
- Show VCC and WiFi information
- Show MQTT error messages
- Show ESP, HAN, WiFi and MQTT status badge
- Show accumulated Import/export
2020-02-12 19:55:06 +01:00
Gunnar Skjold
1994f16b82 Merge branch 'dev-v1.0.1' into dev-v1.1.0 2020-02-12 16:41:04 +01:00
Gunnar Skjold
b6efae656f Expose export power in JSON 2020-02-12 16:40:51 +01:00
Gunnar Skjold
fa0f93cefe Switched to reactive import for Q 2020-02-12 13:25:36 +01:00
Gunnar Skjold
058dc658c1 Switched to reactive import for Q 2020-02-12 13:16:49 +01:00
Gunnar Skjold
f6df84bf9a Auto detect meter type 2020-02-11 18:40:38 +01:00
Gunnar Skjold
4b9cb27e58 Disable web security if AP mode is triggered 2020-02-11 17:32:18 +01:00
Gunnar Skjold
5f14517258 Minor changes after testing MQTT reconnect 2020-02-11 17:25:52 +01:00
Gunnar Skjold
ad820fff01 Allow other services to get resources while trying to connect to mqtt 2020-02-11 11:04:37 +01:00
Gunnar Skjold
6d3d4adc7e Merge branch 'master' into low_power 2020-02-09 13:45:27 +01:00
Gunnar Skjold
99a23b3bf7 Cropped webui image 2020-02-09 13:19:48 +01:00
Gunnar Skjold
15e861ce70 Added AP mode pin for both Wemos D1 and D32 2020-02-09 12:18:08 +01:00
Gunnar Skjold
fdfadb9bf9 Updated Aidon parser with support for 3 phase IT/TT system 2020-02-08 18:23:56 +01:00
Gunnar Skjold
388d35c390 Updated documentation 2020-02-07 23:12:27 +01:00
Gunnar Skjold
e466a45b5c Fixed missing check in checkbox when configuration opened in firefox 2020-02-07 22:33:55 +01:00
Gunnar Skjold
2e7b9e8e43 Fixed missing check in checkbox when configuration opened in firefox 2020-02-07 22:26:17 +01:00
Gunnar Skjold
deb2148e9a Software serial for development boards. Added a few extra boards 2020-02-07 17:27:58 +01:00
Gunnar Skjold
27cb5dd833 Trying a large sleep when cap is not charged 2020-02-06 19:15:06 +01:00
Gunnar Skjold
7e6ac860fd Merge branch 'master' into low_power 2020-02-06 19:02:14 +01:00
Gunnar Skjold
3a7f6078f4 Use builtin LED for Lolin D32 2020-02-06 19:02:05 +01:00
Gunnar Skjold
dbc551a41d Moved configuration object to main file and load the config before start. Fixed issue where partiy is switched for Kamstrup half way when in debug mode 2020-02-05 20:43:37 +01:00
Gunnar Skjold
9bb596aab1 Reverted what was intended as support for earlier configuration versions. 2020-02-02 08:15:49 +01:00
Gunnar Skjold
4a3d22e75c Fixed script to generate webroot header files 2020-02-01 09:57:43 +01:00
Gunnar Skjold
d68ffc827d Trigger build on changes in scripts and web 2020-02-01 09:56:14 +01:00
Gunnar Skjold
20c62a63cf Fixed script to generate webroot header files 2020-02-01 09:54:58 +01:00
Gunnar Skjold
8f85b43fc3 Made MQTT optional 2020-02-01 09:52:37 +01:00
Gunnar Skjold
57d8603790 Added option for electric distribution system (230/400) 2020-01-31 21:30:20 +01:00
Gunnar Skjold
fc1f1554d8 Extracted webroot into usable files 2020-01-31 21:08:27 +01:00
Gunnar Skjold
70ed0b538a Merge branch 'master' into low_power 2020-01-29 21:00:10 +01:00
Gunnar Skjold
ced6f125fd Trigger build on changing ini file 2020-01-29 20:58:09 +01:00
Gunnar Skjold
f76cacf835 Trigger build on changing ini file 2020-01-29 20:56:31 +01:00
Gunnar Skjold
a8205edae9 Fixed build after merge 2020-01-29 20:53:52 +01:00
Gunnar Skjold
0a317450f6 Merge branch 'master' into low_power 2020-01-29 20:51:28 +01:00
Gunnar Skjold
e9ed6d825a Merge pull request #14 from gskjold/issue-9
New web interface that is always available
2020-01-29 20:50:26 +01:00
Gunnar Skjold
a6751c787c Merge branch 'issue-9' into low_power 2020-01-29 20:39:07 +01:00
Gunnar Skjold
378dbbcc99 Only show phases with power, and always deliver volts and amps to frontend 2020-01-29 20:38:53 +01:00
Gunnar Skjold
aed56b0b56 Merge branch 'issue-9' into low_power 2020-01-28 21:19:01 +01:00
Gunnar Skjold
36b37eb2a6 Calculate max power when receiving json from reader 2020-01-28 21:18:48 +01:00
Gunnar Skjold
550b216ba6 Merge branch 'issue-9' into low_power 2020-01-28 21:12:00 +01:00
Gunnar Skjold
7e4047096b Stripped away bootstrap css and created a smaller boot.css as replacement. Load real bootstrap from CDN. Added HTML only (non-js) view of data 2020-01-28 21:10:54 +01:00
Gunnar Skjold
7fdedd8bcf Forgot some changes 2020-01-28 19:20:50 +01:00
Gunnar Skjold
f693869bd4 Merge branch 'master' into issue-9 2020-01-28 19:18:13 +01:00
Gunnar Skjold
3312f88804 Some changes to make it compile for both ESP8266 and ESP32 2020-01-28 18:17:20 +01:00
Gunnar Skjold
50faaca559 Moved Base64 lib 2020-01-24 14:54:42 +01:00
Gunnar Skjold
bf44849ecf Added base64 lib directly into this project to fix build problem with esp32 2020-01-24 14:52:14 +01:00
Gunnar Skjold
4bfd9dee9a Merge pull request #10 from gskjold/issue-6
Make the configuration page available all the time
2020-01-24 14:47:29 +01:00
Gunnar Skjold
32d58fdd71 Stripped jquery and full bootstrap from image 2020-01-23 19:45:29 +01:00
Gunnar Skjold
aef78962fb Some modifications to reduce power consumption i AP mode 2020-01-23 18:47:02 +01:00
Gunnar Skjold
8ee713b616 Included some code from @dakarym to support the self powered board for testing 2020-01-22 15:26:07 +01:00
Gunnar Skjold
0fa3e3585a Finalized live update of meter data in web gui 2020-01-19 21:37:56 +01:00
Gunnar Skjold
a88291c0f0 Implemented new user interface and prepared for live view 2020-01-17 21:49:36 +01:00
Gunnar Skjold
ac1609bfac Merge branch 'issue-6' into issue-9 2020-01-17 19:17:44 +01:00
Gunnar Skjold
22d5839d60 Added HTML, CSS and JS for new user interface 2020-01-17 14:41:02 +01:00
Gunnar Skjold
8f0932f1f1 Added configurable basic auth to web server 2020-01-15 21:22:06 +01:00
Gunnar Skjold
aeb161455e Enable config page on runtime with populated form 2020-01-15 20:17:06 +01:00
Gunnar Skjold
568180d7b2 Update release.yml 2019-12-12 19:49:22 +01:00
Gunnar Skjold
0d99da6c9a Update build.yml 2019-12-12 19:48:22 +01:00
46 changed files with 4651 additions and 1109 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -6,4 +6,7 @@
*.sw[op]
.vscode
.pio
platformio-user.ini
platformio-user.ini
/src/version.h
/src/web/root
/src/AmsToMqttBridge.ino.cpp

View File

@@ -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

Binary file not shown.

View File

@@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,3 @@
EESchema-DOCLIB Version 2.0
#
#End Doc Library

View 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

View File

@@ -1,2 +1,3 @@
(sym_lib_table
(lib (name HAN_ESP_TSS721-rescue)(type Legacy)(uri ${KIPRJMOD}/HAN_ESP_TSS721-rescue.lib)(options "")(descr ""))
)

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -3,7 +3,6 @@
#ifndef _KAMSTRUP_h
#define _KAMSTRUP_h
enum class Kamstrup
{
List3PhaseShort = 0x19,

View File

@@ -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=*

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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>

BIN
webui.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB