Merge pull request #44 from gskjold/dev-v1.1.0

Dev v1.1.0
This commit is contained in:
Gunnar Skjold
2020-03-01 12:56:12 +01:00
committed by GitHub
40 changed files with 3292 additions and 1820 deletions

Binary file not shown.

View File

@@ -19,12 +19,14 @@ You can also use a ESP based development board and combine this with a M-Bus mod
[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 GPIO21
- 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

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

View File

@@ -1,142 +0,0 @@
/*
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*/
#include "Base64.h"
#include <Arduino.h>
#if (defined(__AVR__))
#include <avr\pgmspace.h>
#else
#include <pgmspace.h>
#endif
const char PROGMEM _Base64AlphabetTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
int Base64Class::encode(char *output, char *input, int inputLength) {
int i = 0, j = 0;
int encodedLength = 0;
unsigned char A3[3];
unsigned char A4[4];
while(inputLength--) {
A3[i++] = *(input++);
if(i == 3) {
fromA3ToA4(A4, A3);
for(i = 0; i < 4; i++) {
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[i]]);
}
i = 0;
}
}
if(i) {
for(j = i; j < 3; j++) {
A3[j] = '\0';
}
fromA3ToA4(A4, A3);
for(j = 0; j < i + 1; j++) {
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[j]]);
}
while((i++ < 3)) {
output[encodedLength++] = '=';
}
}
output[encodedLength] = '\0';
return encodedLength;
}
int Base64Class::decode(char * output, char * input, int inputLength) {
int i = 0, j = 0;
int decodedLength = 0;
unsigned char A3[3];
unsigned char A4[4];
while (inputLength--) {
if(*input == '=') {
break;
}
A4[i++] = *(input++);
if (i == 4) {
for (i = 0; i <4; i++) {
A4[i] = lookupTable(A4[i]);
}
fromA4ToA3(A3,A4);
for (i = 0; i < 3; i++) {
output[decodedLength++] = A3[i];
}
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++) {
A4[j] = '\0';
}
for (j = 0; j <4; j++) {
A4[j] = lookupTable(A4[j]);
}
fromA4ToA3(A3,A4);
for (j = 0; j < i - 1; j++) {
output[decodedLength++] = A3[j];
}
}
output[decodedLength] = '\0';
return decodedLength;
}
int Base64Class::encodedLength(int plainLength) {
int n = plainLength;
return (n + 2 - ((n + 2) % 3)) / 3 * 4;
}
int Base64Class::decodedLength(char * input, int inputLength) {
int i = 0;
int numEq = 0;
for(i = inputLength - 1; input[i] == '='; i--) {
numEq++;
}
return ((6 * inputLength) / 8) - numEq;
}
//Private utility functions
inline void Base64Class::fromA3ToA4(unsigned char * A4, unsigned char * A3) {
A4[0] = (A3[0] & 0xfc) >> 2;
A4[1] = ((A3[0] & 0x03) << 4) + ((A3[1] & 0xf0) >> 4);
A4[2] = ((A3[1] & 0x0f) << 2) + ((A3[2] & 0xc0) >> 6);
A4[3] = (A3[2] & 0x3f);
}
inline void Base64Class::fromA4ToA3(unsigned char * A3, unsigned char * A4) {
A3[0] = (A4[0] << 2) + ((A4[1] & 0x30) >> 4);
A3[1] = ((A4[1] & 0xf) << 4) + ((A4[2] & 0x3c) >> 2);
A3[2] = ((A4[2] & 0x3) << 6) + A4[3];
}
inline unsigned char Base64Class::lookupTable(char c) {
if(c >='A' && c <='Z') return c - 'A';
if(c >='a' && c <='z') return c - 71;
if(c >='0' && c <='9') return c + 4;
if(c == '+') return 62;
if(c == '/') return 63;
return -1;
}
Base64Class Base64;

View File

@@ -1,26 +0,0 @@
/*
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*/
#ifndef _BASE64_H
#define _BASE64_H
class Base64Class{
public:
int encode(char *output, char *input, int inputLength);
int decode(char * output, char * input, int inputLength);
int encodedLength(int plainLength);
int decodedLength(char * input, int inputLength);
private:
inline void fromA3ToA4(unsigned char * A4, unsigned char * A3);
inline void fromA4ToA3(unsigned char * A3, unsigned char * A4);
inline unsigned char lookupTable(char c);
};
extern Base64Class Base64;
#endif // _BASE64_H

View File

@@ -1,91 +0,0 @@
#include "HanConfigAp.h"
Stream* HanConfigAp::debugger;
bool HanConfigAp::hasConfig() {
return config->hasConfig();
}
void HanConfigAp::setup(int accessPointButtonPin, configuration* config, Stream* debugger)
{
this->debugger = debugger;
this->config = config;
// Test if we're missing configuration
if (!config->hasConfig())
{
print("No config. We're booting as AP. Look for SSID ");
println(this->AP_SSID);
isActivated = true;
}
else
{
// Load the configuration
if (this->debugger) config->print(this->debugger);
if (accessPointButtonPin != INVALID_BUTTON_PIN)
{
// Assign pin for boot as AP
pinMode(accessPointButtonPin, INPUT_PULLUP);
// Test if we're holding down the AP pin, over 5 seconds
int time = millis() + 5000;
print("Press the AP button now to boot as access point");
while (millis() < time)
{
print(".");
if (digitalRead(accessPointButtonPin) == LOW)
{
print("AP button was pressed. Booting as access point now. Look for SSID ");
println(this->AP_SSID);
isActivated = true;
break;
}
delay(100);
}
println("");
}
}
if (isActivated)
{
// Setup AP
WiFi.disconnect(true);
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
delay(2000);
WiFi.softAP(AP_SSID);
WiFi.mode(WIFI_AP);
/* Setup the DNS server redirecting all the domains to this IP */
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());
}
}
bool HanConfigAp::loop() {
if(isActivated) {
//DNS
dnsServer.processNextRequest();
}
return isActivated;
}
size_t HanConfigAp::print(const char* text)
{
if (debugger) debugger->print(text);
}
size_t HanConfigAp::println(const char* text)
{
if (debugger) debugger->println(text);
}
size_t HanConfigAp::print(const Printable& data)
{
if (debugger) debugger->print(data);
}
size_t HanConfigAp::println(const Printable& data)
{
if (debugger) debugger->println(data);
}

View File

@@ -1,50 +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>
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
#include <WiFi.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, configuration* config, Stream* debugger);
bool loop();
bool hasConfig();
bool isActivated = false;
private:
const char* AP_SSID = "AMS2MQTT";
configuration* config;
// 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);
static Stream* debugger;
};
#endif

View File

@@ -1,265 +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);
if(mqttHost) {
address += saveBool(address, true);
address += saveString(address, mqttHost);
address += saveInt(address, mqttPort);
address += saveString(address, mqttClientID);
address += saveString(address, mqttPublishTopic);
address += saveString(address, mqttSubscribeTopic);
} else {
address += saveBool(address, false);
}
if (isSecure()) {
address += saveBool(address, true);
address += saveString(address, mqttUser);
address += saveString(address, mqttPass);
}
else
address += saveBool(address, false);
address += saveByte(address, authSecurity);
if (authSecurity > 0) {
address += saveString(address, authUser);
address += saveString(address, authPass);
}
address += saveInt(address, fuseSize);
address += saveInt(address, distSys);
bool success = EEPROM.commit();
EEPROM.end();
return success;
}
bool configuration::load()
{
int address = EEPROM_CONFIG_ADDRESS;
bool success = false;
ssid = 0;
ssidPassword = 0;
meterType = (byte)0;
mqttHost = 0;
mqttClientID = 0;
mqttPublishTopic = 0;
mqttSubscribeTopic = 0;
mqttUser = 0;
mqttPass = 0;
mqttPort = 1883;
authSecurity = 0;
authUser = 0;
authPass = 0;
fuseSize = 0;
distSys = 0;
EEPROM.begin(EEPROM_SIZE);
int cs = EEPROM.read(address);
if (cs == EEPROM_CHECK_SUM)
{
address++;
address += readString(address, &ssid);
address += readString(address, &ssidPassword);
address += readByte(address, &meterType);
bool mqtt = false;
address += readBool(address, &mqtt);
if(mqtt) {
address += readString(address, &mqttHost);
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;
}
address += readByte(address, &authSecurity);
if (authSecurity > 0) {
address += readString(address, &authUser);
address += readString(address, &authPass);
} else {
authUser = 0;
authPass = 0;
}
address += readInt(address, &fuseSize);
address += readByte(address, &distSys);
success = true;
}
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)
{
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);
if(this->mqttHost) {
debugger->printf("mqttHost: %s\r\n", this->mqttHost);
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);
}
if (this->authSecurity > 0) {
debugger->printf("WEB AUTH:\r\n");
debugger->printf("authSecurity: %i\r\n", this->authSecurity);
debugger->printf("authUser: %s\r\n", this->authUser);
debugger->printf("authPass: %s\r\n", this->authPass);
}
debugger->printf("fuseSize: %i\r\n", this->fuseSize);
debugger->printf("distSys: %i\r\n", this->distSys);
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 = 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;
}

View File

@@ -1,63 +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* mqttHost;
int mqttPort;
char* mqttClientID;
char* mqttPublishTopic;
char* mqttSubscribeTopic;
char* mqttUser;
char* mqttPass;
byte meterType;
byte authSecurity;
char* authUser;
char* authPass;
int fuseSize;
byte distSys;
bool hasConfig();
bool isSecure();
bool save();
bool load();
void print(Stream* debugger);
protected:
private:
const int EEPROM_SIZE = 512;
const byte EEPROM_CHECK_SUM = 75; // 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,7 +3,6 @@
#ifndef _AIDON_h
#define _AIDON_h
enum class Aidon
{
List1 = 0x01,

View File

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

@@ -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,284 +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["PO"] = hanReader.getInt( (int)Kaifa_List3Phase::ActiveExportPower);
data["QO"] = hanReader.getInt( (int)Kaifa_List3Phase::ReactiveExportPower);
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["rtc"] = hanReader.getTime( (int)Kaifa_List3Phase::MeterClock);
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["PO"] = hanReader.getInt( (int)Kaifa_List1Phase::ActiveExportPower);
data["QO"] = hanReader.getInt( (int)Kaifa_List1Phase::ReactiveExportPower);
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["rtc"] = hanReader.getTime( (int)Kaifa_List1Phase::MeterClock);
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::ReactiveImportPower);
data["PO"] = hanReader.getInt( (int)Aidon_List3Phase::ActiveExportPower);
data["QO"] = 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["rtc"] = hanReader.getTime( (int)Aidon_List3Phase::Timestamp);
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);
}
}
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::ReactiveImportPower);
data["PO"] = hanReader.getInt( (int)Aidon_List1Phase::ActiveExportPower);
data["QO"] = 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;
}
if (listSize >= (int)Aidon::List1PhaseLong)
{
data["rtc"] = hanReader.getTime( (int)Aidon_List1Phase::Timestamp);
data["tPI"] = hanReader.getInt( (int)Aidon_List1Phase::CumulativeActiveImportEnergy);
data["tPO"] = hanReader.getInt( (int)Aidon_List1Phase::CumulativeActiveExportEnergy);
data["tQI"] = hanReader.getInt( (int)Aidon_List1Phase::CumulativeReactiveImportEnergy);
data["tQO"] = hanReader.getInt( (int)Aidon_List1Phase::CumulativeReactiveExportEnergy);
}
}
static void hanToJsonAidon3phaseIT(int listSize, JsonObject& data, HanReader& hanReader, Stream *debugger)
{
if (listSize >= (int)Aidon::List3PhaseITShort)
{
data["lv"] = hanReader.getString( (int)Aidon_List3PhaseIT::ListVersionIdentifier);
data["id"] = hanReader.getString( (int)Aidon_List3PhaseIT::MeterID);
data["type"] = hanReader.getString( (int)Aidon_List3PhaseIT::MeterType);
data["P"] = hanReader.getInt( (int)Aidon_List3PhaseIT::ActiveImportPower);
data["Q"] = hanReader.getInt( (int)Aidon_List3PhaseIT::ReactiveImportPower);
data["PO"] = hanReader.getInt( (int)Aidon_List3PhaseIT::ActiveExportPower);
data["QO"] = hanReader.getInt( (int)Aidon_List3PhaseIT::ReactiveExportPower);
data["I1"] = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::CurrentL1)) / 10;
data["I2"] = 0;
data["I3"] = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::CurrentL3)) / 10;
data["U1"] = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::VoltageL1)) / 10;
data["U2"] = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::VoltageL2)) / 10;
data["U3"] = ((double) hanReader.getInt( (int)Aidon_List3PhaseIT::VoltageL3)) / 10;
}
if (listSize >= (int)Aidon::List3PhaseITLong)
{
data["rtc"] = hanReader.getTime( (int)Aidon_List3PhaseIT::Timestamp);
data["tPI"] = hanReader.getInt( (int)Aidon_List3PhaseIT::CumulativeActiveImportEnergy);
data["tPO"] = hanReader.getInt( (int)Aidon_List3PhaseIT::CumulativeActiveExportEnergy);
data["tQI"] = hanReader.getInt( (int)Aidon_List3PhaseIT::CumulativeReactiveImportEnergy);
data["tQO"] = hanReader.getInt( (int)Aidon_List3PhaseIT::CumulativeReactiveExportEnergy);
}
}
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);
case (int)Aidon::List3PhaseITShort:
case (int)Aidon::List3PhaseITLong:
return hanToJsonAidon3phaseIT(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["PO"] = hanReader.getInt( (int)Kamstrup_List3Phase::ActiveExportPower);
data["QO"] = hanReader.getInt( (int)Kamstrup_List3Phase::ReactiveExportPower);
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["rtc"] = hanReader.getTime( (int)Kamstrup_List3Phase::MeterClock);
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["PO"] = hanReader.getInt( (int)Kamstrup_List1Phase::ActiveExportPower);
data["QO"] = hanReader.getInt( (int)Kamstrup_List1Phase::ReactiveExportPower);
data["I1"] = ((double) hanReader.getInt((int)Kamstrup_List1Phase::CurrentL1)) / 100;
data["U1"] = hanReader.getInt( (int)Kamstrup_List1Phase::VoltageL1);
}
if (listSize >= (int)Kamstrup::List1PhaseLong) {
data["rtc"] = hanReader.getTime( (int)Kamstrup_List1Phase::MeterClock);
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

@@ -4,10 +4,10 @@ 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, EspSoftwareSerial@^6.7.1
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}
@@ -18,7 +18,7 @@ extra_scripts =
scripts/makeweb.py
[env:esp12e]
platform = espressif8266
platform = espressif8266@2.3.3
board = esp12e
framework = ${common.framework}
lib_deps = ${common.lib_deps}
@@ -27,7 +27,7 @@ extra_scripts =
scripts/makeweb.py
[env:d1mini]
platform = espressif8266
platform = espressif8266@2.3.3
board = d1_mini
framework = ${common.framework}
lib_deps = ${common.lib_deps}
@@ -36,7 +36,7 @@ extra_scripts =
scripts/makeweb.py
[env:esp32]
platform = espressif32
platform = espressif32@1.11.2
board = esp32dev
framework = ${common.framework}
lib_deps = ${common.lib_deps}
@@ -45,7 +45,7 @@ extra_scripts =
scripts/makeweb.py
[env:lolind32]
platform = espressif32
platform = espressif32@1.11.2
board = lolin_d32
framework = ${common.framework}
lib_deps = ${common.lib_deps}
@@ -54,7 +54,7 @@ extra_scripts =
scripts/makeweb.py
[env:featheresp32]
platform = espressif32
platform = espressif32@1.11.2
board = featheresp32
framework = ${common.framework}
lib_deps = ${common.lib_deps}

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

View File

@@ -1,5 +1,10 @@
#ifndef _AMSTOMQTTBRIDGE_H
#define _AMSTOMQTTBRIDGE_H
#define WIFI_CONNECTION_TIMEOUT 30000;
#define INVALID_BUTTON_PIN 0xFFFFFFFF
#if defined(ESP8266)
#include <ESP8266WiFi.h>
@@ -13,17 +18,16 @@
#define LED_ACTIVE_HIGH 0
#define AP_BUTTON_PIN 0
#include <DallasTemperature.h>
#include <OneWire.h>
#define TEMP_SENSOR_PIN 5 // Temperature sensor connected to GPIO5
#if DEBUG_MODE
#define SOFTWARE_SERIAL 1
#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)
@@ -31,9 +35,7 @@ HardwareSerial *hanSerial = &Serial;
#define LED_ACTIVE_HIGH 0
#define AP_BUTTON_PIN 4
#define SOFTWARE_SERIAL 1
#include <SoftwareSerial.h>
SoftwareSerial *hanSerial = new SoftwareSerial(GPIO_NUM_21);
HardwareSerial *hanSerial = &Serial2;
// Build settings for Wemos D1 mini
#elif defined(ARDUINO_ESP8266_WEMOS_D1MINI)
@@ -63,8 +65,4 @@ HardwareSerial *hanSerial = &Serial2;
SoftwareSerial *hanSerial = new SoftwareSerial(5);
#endif
#if defined TEMP_SENSOR_PIN
OneWire oneWire(TEMP_SENSOR_PIN);
DallasTemperature tempSensor(&oneWire);
#endif
#endif

View File

@@ -7,32 +7,38 @@
#include "AmsToMqttBridge.h"
#include <ArduinoJson.h>
#include <MQTT.h>
#include <DNSServer.h>
#if defined(ESP8266)
ADC_MODE(ADC_VCC);
#endif
#include "HwTools.h"
#include "web/AmsWebServer.h"
#include "HanConfigAp.h"
#include "AmsConfiguration.h"
#include "HanReader.h"
#include "HanToJson.h"
// Configuration
configuration config;
#include "Aidon.h"
#include "Kaifa.h"
#include "Kamstrup.h"
// Object used to boot as Access Point
HanConfigAp ap;
HwTools hw;
DNSServer dnsServer;
AmsConfiguration config;
// Web server
AmsWebServer ws;
// WiFi client and MQTT client
WiFiClient *client;
MQTTClient mqtt(512);
MQTTClient mqtt(384);
// Object used for debugging
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() {
if(config.hasConfig()) {
config.load();
@@ -40,9 +46,18 @@ void setup() {
#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);
@@ -50,83 +65,181 @@ void setup() {
debugger = ser;
#endif
double vcc = hw.getVcc();
if (debugger) {
debugger->println("");
debugger->println("Started...");
debugger->print("Voltage: ");
debugger->print(vcc);
debugger->println("mV");
}
// Flash the LED, to indicate we can boot as AP now
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, &config, debugger);
pinMode(AP_BUTTON_PIN, INPUT_PULLUP);
led_off();
if (!ap.isActivated)
{
setupWiFi();
WiFi.disconnect(true);
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
// Configure uart for AMS data
#if defined SOFTWARE_SERIAL
if(config.meterType == 3) {
hanSerial->begin(2400, SWSERIAL_8N1);
} else {
hanSerial->begin(2400, SWSERIAL_8E1);
if(config.hasConfig()) {
if(debugger) config.print(debugger);
WiFi_connect();
client = new WiFiClient();
} else {
if(debugger) {
debugger->println("No configuration, booting AP");
}
#else
if(config.meterType == 3) {
hanSerial->begin(2400, SERIAL_8N1);
} else {
hanSerial->begin(2400, SERIAL_8E1);
}
#if defined UART2
hanSerial->swap();
#endif
#endif
while (!&hanSerial);
hanReader.setup(hanSerial, debugger);
// Compensate for the known Kaifa bug
hanReader.compensateFor09HeaderBug = (config.meterType == 1);
swapWifiMode();
}
ws.setup(&config, debugger);
#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()
{
// Only do normal stuff if we're not booted as AP
if (!ap.loop())
{
// Turn off the LED
led_off();
int buttonTimer = 0;
bool buttonActive = false;
unsigned long longPressTime = 5000;
bool longPressActive = false;
// Reconnect to WiFi and MQTT as needed
if (WiFi.status() != WL_CONNECTED) {
WiFi_connect();
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 (config.mqttHost) {
mqtt.loop();
yield();
if(!mqtt.connected()) {
MQTT_connect();
if (buttonActive == true) {
if (longPressActive == true) {
longPressActive = false;
} else {
// Single press action
}
buttonActive = false;
}
}
readHanPort();
}
else
{
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 (WiFi.getMode() != WIFI_AP) {
led_off();
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 {
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
@@ -152,25 +265,56 @@ void led_off()
#endif
}
void setupWiFi()
{
// Turn off AP
WiFi.enableAP(false);
// Connect to WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(config.ssid, 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();
void swapWifiMode() {
led_on();
WiFiMode_t mode = WiFi.getMode();
dnsServer.stop();
WiFi.disconnect(true);
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
yield();
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();
}
delay(500);
led_off();
}
void mqttMessageReceived(String &topic, String &payload)
@@ -188,113 +332,140 @@ void mqttMessageReceived(String &topic, String &payload)
// Ideas could be to query for values or to initiate OTA firmware update
}
void readHanPort()
{
if (hanReader.read() && config.hasConfig())
{
// 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<1024> 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 defined TEMP_SENSOR_PIN
// Get the temperature too
tempSensor.requestTemperatures();
data["temp"] = tempSensor.getTempCByIndex(0);
#endif
hanToJson(data, config.meterType, hanReader);
if(config.mqttHost != 0 && strlen(config.mqttHost) != 0 && config.mqttPublishTopic != 0 && strlen(config.mqttPublishTopic) != 0) {
// Write the json to the debug port
if (debugger) {
debugger->print("Sending data to MQTT: ");
serializeJsonPretty(json, *debugger);
debugger->println();
String msg;
serializeJson(json, msg);
mqtt.publish(config.getMqttPublishTopic(), msg.c_str());
mqtt.loop();
delay(10);
}
// Publish the json to the MQTT server
String msg;
serializeJson(json, msg);
mqtt.publish(config.mqttPublishTopic, msg.c_str());
mqtt.loop();
#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);
}
ws.setJson(json);
}
// Flash LED off
led_off();
// 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
even = !even;
}
}
unsigned long wifiTimeout = WIFI_CONNECTION_TIMEOUT;
unsigned long lastWifiRetry = -WIFI_CONNECTION_TIMEOUT;
void WiFi_connect() {
// Connect to WiFi access point.
if (debugger)
{
debugger->println();
debugger->println();
debugger->print("Connecting to WiFi network ");
debugger->println(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(config.ssid, config.ssidPassword);
delay(1000);
}
// Wait for the WiFi connection to complete
long vTimeout = millis() + WIFI_CONNECTION_TIMEOUT;
while (WiFi.status() != WL_CONNECTED) {
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(config.ssid, 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->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();
}
}
// 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.
unsigned long lastMqttRetry = -10000;
void MQTT_connect() {
if(!config.mqttHost) {
if(config.getMqttHost().isEmpty()) {
if(debugger) debugger->println("No MQTT config");
return;
}
@@ -305,25 +476,27 @@ void MQTT_connect() {
lastMqttRetry = millis();
if(debugger) {
debugger->print("Connecting to MQTT: ");
debugger->print(config.mqttHost);
debugger->print(config.getMqttHost());
debugger->print(", port: ");
debugger->print(config.mqttPort);
debugger->print(config.getMqttPort());
debugger->println();
}
mqtt.disconnect();
yield();
mqtt.begin(config.mqttHost, config.mqttPort, *client);
mqtt.begin(config.getMqttHost().c_str(), config.getMqttPort(), *client);
// Connect to a unsecure or secure MQTT server
if ((config.mqttUser == 0 && mqtt.connect(config.mqttClientID)) ||
(config.mqttUser != 0 && mqtt.connect(config.mqttClientID, config.mqttUser, config.mqttPass))) {
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();
// Subscribe to the chosen MQTT topic, if set in configuration
if (config.mqttSubscribeTopic != 0 && strlen(config.mqttSubscribeTopic) > 0) {
mqtt.subscribe(config.mqttSubscribeTopic);
if (debugger) debugger->printf(" Subscribing to [%s]\r\n", config.mqttSubscribeTopic);
if (!config.getMqttSubscribeTopic().isEmpty()) {
mqtt.subscribe(config.getMqttSubscribeTopic());
if (debugger) debugger->printf(" Subscribing to [%s]\r\n", config.getMqttSubscribeTopic().c_str());
}
sendMqttData("Connected!");
@@ -340,22 +513,72 @@ void MQTT_connect() {
void sendMqttData(String data)
{
// Make sure we have configured a publish topic
if (config.mqttPublishTopic == 0 || strlen(config.mqttPublishTopic) == 0)
if (config.getMqttPublishTopic().isEmpty())
return;
// 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(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;
}
}

View File

@@ -1,142 +0,0 @@
/*
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*/
#include "Base64.h"
#include <Arduino.h>
#if (defined(__AVR__))
#include <avr\pgmspace.h>
#else
#include <pgmspace.h>
#endif
const char PROGMEM _Base64AlphabetTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
int Base64Class::encode(char *output, char *input, int inputLength) {
int i = 0, j = 0;
int encodedLength = 0;
unsigned char A3[3];
unsigned char A4[4];
while(inputLength--) {
A3[i++] = *(input++);
if(i == 3) {
fromA3ToA4(A4, A3);
for(i = 0; i < 4; i++) {
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[i]]);
}
i = 0;
}
}
if(i) {
for(j = i; j < 3; j++) {
A3[j] = '\0';
}
fromA3ToA4(A4, A3);
for(j = 0; j < i + 1; j++) {
output[encodedLength++] = pgm_read_byte(&_Base64AlphabetTable[A4[j]]);
}
while((i++ < 3)) {
output[encodedLength++] = '=';
}
}
output[encodedLength] = '\0';
return encodedLength;
}
int Base64Class::decode(char * output, char * input, int inputLength) {
int i = 0, j = 0;
int decodedLength = 0;
unsigned char A3[3];
unsigned char A4[4];
while (inputLength--) {
if(*input == '=') {
break;
}
A4[i++] = *(input++);
if (i == 4) {
for (i = 0; i <4; i++) {
A4[i] = lookupTable(A4[i]);
}
fromA4ToA3(A3,A4);
for (i = 0; i < 3; i++) {
output[decodedLength++] = A3[i];
}
i = 0;
}
}
if (i) {
for (j = i; j < 4; j++) {
A4[j] = '\0';
}
for (j = 0; j <4; j++) {
A4[j] = lookupTable(A4[j]);
}
fromA4ToA3(A3,A4);
for (j = 0; j < i - 1; j++) {
output[decodedLength++] = A3[j];
}
}
output[decodedLength] = '\0';
return decodedLength;
}
int Base64Class::encodedLength(int plainLength) {
int n = plainLength;
return (n + 2 - ((n + 2) % 3)) / 3 * 4;
}
int Base64Class::decodedLength(char * input, int inputLength) {
int i = 0;
int numEq = 0;
for(i = inputLength - 1; input[i] == '='; i--) {
numEq++;
}
return ((6 * inputLength) / 8) - numEq;
}
//Private utility functions
inline void Base64Class::fromA3ToA4(unsigned char * A4, unsigned char * A3) {
A4[0] = (A3[0] & 0xfc) >> 2;
A4[1] = ((A3[0] & 0x03) << 4) + ((A3[1] & 0xf0) >> 4);
A4[2] = ((A3[1] & 0x0f) << 2) + ((A3[2] & 0xc0) >> 6);
A4[3] = (A3[2] & 0x3f);
}
inline void Base64Class::fromA4ToA3(unsigned char * A3, unsigned char * A4) {
A3[0] = (A4[0] << 2) + ((A4[1] & 0x30) >> 4);
A3[1] = ((A4[1] & 0xf) << 4) + ((A4[2] & 0x3c) >> 2);
A3[2] = ((A4[2] & 0x3) << 6) + A4[3];
}
inline unsigned char Base64Class::lookupTable(char c) {
if(c >='A' && c <='Z') return c - 'A';
if(c >='a' && c <='z') return c - 71;
if(c >='0' && c <='9') return c + 4;
if(c == '+') return 62;
if(c == '/') return 63;
return -1;
}
Base64Class Base64;

View File

@@ -1,26 +0,0 @@
/*
Copyright (C) 2016 Arturo Guadalupi. All right reserved.
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*/
#ifndef _BASE64_H
#define _BASE64_H
class Base64Class{
public:
int encode(char *output, char *input, int inputLength);
int decode(char * output, char * input, int inputLength);
int encodedLength(int plainLength);
int decodedLength(char * input, int inputLength);
private:
inline void fromA3ToA4(unsigned char * A4, unsigned char * A3);
inline void fromA4ToA3(unsigned char * A3, unsigned char * A4);
inline unsigned char lookupTable(char c);
};
extern Base64Class Base64;
#endif // _BASE64_H

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

View File

@@ -2,24 +2,26 @@
#include "version.h"
#include "root/index_html.h"
#include "root/configuration_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"
#if defined(ESP8266)
ESP8266WebServer server(80);
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
WebServer server(80);
#endif
void AmsWebServer::setup(configuration* config, Stream* debugger) {
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("/configuration", std::bind(&AmsWebServer::configurationHtml, 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));
@@ -27,70 +29,31 @@ void AmsWebServer::setup(configuration* config, Stream* debugger) {
server.on("/save", std::bind(&AmsWebServer::handleSave, this));
server.begin(); // Web server start
print("Web server is ready for config at http://");
if(WiFi.getMode() == WIFI_AP) {
print(WiFi.softAPIP());
} else {
print(WiFi.localIP());
}
println("/");
}
void AmsWebServer::loop() {
server.handleClient();
}
void AmsWebServer::setJson(StaticJsonDocument<1024> json) {
if(!json.isNull()) {
p = json["data"]["P"].as<int>();
if(json["data"].containsKey("U1")) {
u1 = json["data"]["U1"].as<double>();
i1 = json["data"]["I1"].as<double>();
if(json["data"].containsKey("U2")) {
u2 = json["data"]["U2"].as<double>();
i2 = json["data"]["I2"].as<double>();
void AmsWebServer::setData(AmsData& data) {
this->data.apply(data);
if(json["data"].containsKey("U3")) {
u3 = json["data"]["U3"].as<double>();
i3 = json["data"]["I3"].as<double>();
}
}
if(maxPwr == 0 && config->hasConfig() && config->fuseSize > 0 && config->distSys > 0) {
int volt = config->distSys == 2 ? 400 : 230;
if(u2 > 0) {
maxPwr = config->fuseSize * sqrt(3) * volt;
} else {
maxPwr = config->fuseSize * 230;
}
}
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 {
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;
}
maxPwr = config->getMainFuse() * 230;
}
this->json = json;
}
}
bool AmsWebServer::checkSecurity(byte level) {
bool access = WiFi.getMode() == WIFI_AP || !config->hasConfig() || config->authSecurity < level;
if(!access && config->authSecurity >= level && server.hasHeader("Authorization")) {
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->authUser) + ":" + String(config->authPass);
String expectedAuth = String(config->getAuthUser()) + ":" + String(config->getAuthPassword());
String providedPwd = server.header("Authorization");
providedPwd.replace("Basic ", "");
@@ -109,6 +72,7 @@ bool AmsWebServer::checkSecurity(byte level) {
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;
@@ -122,7 +86,26 @@ void AmsWebServer::indexHtml() {
String html = String((const __FlashStringHelper*) INDEX_HTML);
html.replace("${version}", VERSION);
html.replace("${data.P}", String(p));
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) : "");
@@ -136,101 +119,169 @@ void AmsWebServer::indexHtml() {
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::configurationHtml() {
println("Serving /configuration.html over http...");
void AmsWebServer::configMeterHtml() {
println("Serving /config/meter.html over http...");
if(!checkSecurity(1))
return;
String html = String((const __FlashStringHelper*) CONFIGURATION_HTML);
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");
server.sendHeader("Expires", "-1");
if(config->hasConfig()) {
html.replace("${config.ssid}", config->ssid);
html.replace("${config.ssidPassword}", config->ssidPassword);
html.replace("${config.meterType}", String(config->fuseSize));
for(int i = 0; i<4; i++) {
html.replace("${config.meterType" + String(i) + "}", config->meterType == i ? "selected" : "");
}
html.replace("${config.mqtt}", config->mqttHost == 0 ? "" : "checked");
html.replace("${config.mqttHost}", config->mqttHost);
html.replace("${config.mqttPort}", String(config->mqttPort));
html.replace("${config.mqttClientID}", config->mqttClientID);
html.replace("${config.mqttPublishTopic}", config->mqttPublishTopic);
html.replace("${config.mqttSubscribeTopic}", config->mqttSubscribeTopic);
html.replace("${config.mqttUser}", config->mqttUser);
html.replace("${config.mqttPass}", config->mqttPass);
html.replace("${config.authUser}", config->authUser);
html.replace("${config.authSecurity}", String(config->authSecurity));
for(int i = 0; i<3; i++) {
html.replace("${config.authSecurity" + String(i) + "}", config->authSecurity == i ? "selected" : "");
}
html.replace("${config.authPass}", config->authPass);
html.replace("${config.fuseSize}", String(config->fuseSize));
for(int i = 0; i<64; i++) {
html.replace("${config.fuseSize" + String(i) + "}", config->fuseSize == i ? "selected" : "");
}
for(int i = 0; i<3; i++) {
html.replace("${config.distSys" + String(i) + "}", config->distSys == i ? "selected" : "");
}
} else {
html.replace("${config.ssid}", "");
html.replace("${config.ssidPassword}", "");
html.replace("${config.meterType}", "");
for(int i = 0; i<4; i++) {
html.replace("${config.meterType" + String(i) + "}", i == 0 ? "selected" : "");
}
html.replace("${config.mqtt}", "");
html.replace("${config.mqttHost}", "");
html.replace("${config.mqttPort}", "1883");
html.replace("${config.mqttClientID}", "");
html.replace("${config.mqttPublishTopic}", "");
html.replace("${config.mqttSubscribeTopic}", "");
html.replace("${config.mqttUser}", "");
html.replace("${config.mqttPass}", "");
html.replace("${config.authSecurity}", "");
for(int i = 0; i<3; i++) {
html.replace("${config.authSecurity" + String(i) + "}", i == 0 ? "selected" : "");
}
html.replace("${config.authUser}", "");
html.replace("${config.authPass}", "");
html.replace("${config.fuseSize}", "");
for(int i = 0; i<64; i++) {
html.replace("${config.fuseSize" + String(i) + "}", i == 0 ? "selected" : "");
}
for(int i = 0; i<3; i++) {
html.replace("${config.distSys" + String(i) + "}", i == 0 ? "selected" : "");
}
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...");
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send(200, "text/css", BOOT_CSS);
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...");
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
server.sendHeader("Pragma", "no-cache");
server.sendHeader("Expires", "-1");
server.send(200, "application/javascript", GAUGEMETER_JS);
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() {
@@ -239,117 +290,217 @@ void AmsWebServer::dataJson() {
if(!checkSecurity(2))
return;
String jsonStr;
if(!json.isNull()) {
println(" json has data");
StaticJsonDocument<768> json;
String jsonStr;
if(data.getLastUpdateMillis() > 0) {
int maxPwr = this->maxPwr;
if(maxPwr == 0) {
if(u2 > 0) {
if(data.isThreePhase()) {
maxPwr = 20000;
} else {
maxPwr = 10000;
}
}
json["maxPower"] = maxPwr;
json["pct"] = min(p*100/maxPwr, 100);
json["meterType"] = config->meterType;
json["currentMillis"] = millis();
json["up"] = data.getLastUpdateMillis();
json["t"] = data.getPackageTimestamp();
json.createNestedObject("data");
json["data"]["P"] = data.getActiveImportPower();
json["data"]["PO"] = data.getActiveExportPower();
serializeJson(json, jsonStr);
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 {
println(" json is empty");
jsonStr = "{}";
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;
temp = server.arg("ssid");
config->ssid = new char[temp.length() + 1];
temp.toCharArray(config->ssid, temp.length() + 1, 0);
temp = server.arg("ssidPassword");
config->ssidPassword = new char[temp.length() + 1];
temp.toCharArray(config->ssidPassword, temp.length() + 1, 0);
config->meterType = (byte)server.arg("meterType").toInt();
if(server.hasArg("mqtt") && server.arg("mqtt") == "true") {
println("MQTT enabled");
temp = server.arg("mqttHost");
config->mqttHost = new char[temp.length() + 1];
temp.toCharArray(config->mqttHost, temp.length() + 1, 0);
config->mqttPort = (int)server.arg("mqttPort").toInt();
temp = server.arg("mqttClientID");
config->mqttClientID = new char[temp.length() + 1];
temp.toCharArray(config->mqttClientID, temp.length() + 1, 0);
temp = server.arg("mqttPublishTopic");
config->mqttPublishTopic = new char[temp.length() + 1];
temp.toCharArray(config->mqttPublishTopic, temp.length() + 1, 0);
temp = server.arg("mqttSubscribeTopic");
config->mqttSubscribeTopic = new char[temp.length() + 1];
temp.toCharArray(config->mqttSubscribeTopic, temp.length() + 1, 0);
temp = server.arg("mqttUser");
config->mqttUser = new char[temp.length() + 1];
temp.toCharArray(config->mqttUser, temp.length() + 1, 0);
temp = server.arg("mqttPass");
config->mqttPass = new char[temp.length() + 1];
temp.toCharArray(config->mqttPass, temp.length() + 1, 0);
} else {
println("MQTT disabled");
config->mqttHost = NULL;
config->mqttUser = NULL;
config->mqttPass = NULL;
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());
}
config->authSecurity = (byte)server.arg("authSecurity").toInt();
if(config->authSecurity > 0) {
temp = server.arg("authUser");
config->authUser = new char[temp.length() + 1];
temp.toCharArray(config->authUser, temp.length() + 1, 0);
temp = server.arg("authPass");
config->authPass = new char[temp.length() + 1];
temp.toCharArray(config->authPass, temp.length() + 1, 0);
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();
}
}
config->fuseSize = (int)server.arg("fuseSize").toInt();
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"));
config->setAuthUser(server.arg("authUser"));
config->setAuthPassword(server.arg("authPassword"));
} else {
config->clearMqtt();
}
}
config->distSys = (byte)server.arg("distSys").toInt();
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. Will reboot now.");
String html = "<html><body><h1>Successfully Saved!</h1><h3>Device is restarting now...</h3><a href=\"/\">Go to index</a></form>";
server.send(200, "text/html", html);
yield();
delay(1000);
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();
ESP.reset();
#elif defined(ESP32)
ESP.restart();
ESP.restart();
#endif
}
else
{
} 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);

View File

@@ -1,8 +1,13 @@
#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 "configuration.h"
#include "AmsConfiguration.h"
#include "HwTools.h"
#include "AmsData.h"
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
@@ -22,17 +27,18 @@
class AmsWebServer {
public:
void setup(configuration* config, Stream* debugger);
void setup(AmsConfiguration* config, Stream* debugger, MQTTClient* mqtt);
void loop();
void setJson(StaticJsonDocument<1024> json);
void setData(AmsData& data);
private:
configuration* config;
int maxPwr;
HwTools hw;
AmsConfiguration* config;
AmsData data;
Stream* debugger;
StaticJsonDocument<1024> json;
int maxPwr;
int p;
double u1, u2, u3, i1, i2, i3;
MQTTClient* mqtt;
#if defined(ESP8266)
ESP8266WebServer server;
@@ -43,7 +49,10 @@ private:
bool checkSecurity(byte level);
void indexHtml();
void configurationHtml();
void configMeterHtml();
void configWifiHtml();
void configMqttHtml();
void configWebHtml();
void bootCss();
void gaugemeterJs();
void dataJson();

View File

@@ -62,6 +62,9 @@ body {
.mb-0, .my-0 {
margin-bottom: 0!important;
}
.m-2 {
margin: .5rem!important;
}
.mb-2, .my-2 {
margin-bottom: .5rem!important;
}
@@ -92,7 +95,8 @@ body {
}
.border-bottom {
border-bottom: 1px solid #dee2e6!important;
}.rounded {
}
.rounded {
border-radius: .25rem!important;
}
div {
@@ -140,10 +144,6 @@ h1, h2, h3, h4, h5, h6 {
margin-right: -15px;
margin-left: -15px;
}
.d-flex {
display: -ms-flexbox!important;
display: flex!important;
}
.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%;
@@ -185,6 +185,22 @@ h1, h2, h3, h4, h5, h6 {
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;
@@ -218,6 +234,92 @@ a {
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;
}
@@ -256,6 +358,55 @@ 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;
@@ -265,6 +416,23 @@ hr {
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 {
@@ -275,6 +443,11 @@ hr {
.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%;
@@ -285,6 +458,17 @@ hr {
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 {

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>

View File

@@ -1,184 +0,0 @@
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AMS reader - configuration</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" type="text/css" href="boot.css"/>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css"/>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<style>
.bg-purple {
background-color: var(--purple);
}
</style>
</head>
<body class="bg-light">
<main role="main" class="container">
<div class="d-flex align-items-center p-3 my-2 text-white-50 bg-purple rounded shadow">
<div class="lh-100">
<h6 class="mb-0 text-white lh-100">AMS reader - configuration</h6>
<small>${version}</small>
</div>
</div>
<form method="post" action="/save">
<div class="row">
<div class="col-md-6 col-lg-4">
<div class="my-2 p-3 bg-white rounded shadow">
<h6 class="border-bottom border-gray pb-2 mb-4">WiFi</h6>
<div class="row form-group">
<label class="col-3">SSID</label>
<div class="col-9">
<input type="text" class="form-control" name="ssid" value="${config.ssid}"/>
</div>
</div>
<div class="row form-group">
<label class="col-3">Password</label>
<div class="col-9">
<input type="password" class="form-control" name="ssidPassword" value="${config.ssidPassword}"/>
</div>
</div>
</div>
<div class="my-3 p-3 bg-white rounded shadow">
<h6 class="border-bottom border-gray pb-2 mb-4">AMS meter</h6>
<div class="row form-group">
<label class="col-6">Distribution system</label>
<div class="col-6">
<select class="form-control" name="distSys">
<option value="" ${config.distSys0}></option>
<option value="1" ${config.distSys1}>IT (230V)</option>
<option value="2" ${config.distSys2}>TN (400V)</option>
</select>
</div>
</div>
<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} disabled></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">Main fuse</label>
<div class="col-6">
<select class="form-control" name="fuseSize">
<option value="" ${config.fuseSize0}></option>
<option value="25" ${config.fuseSize25}>25A</option>
<option value="32" ${config.fuseSize32}>32A</option>
<option value="35" ${config.fuseSize32}>35A</option>
<option value="40" ${config.fuseSize40}>40A</option>
<option value="50" ${config.fuseSize50}>50A</option>
<option value="63" ${config.fuseSize63}>63A</option>
</select>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="my-2 p-3 bg-white rounded shadow">
<h6 class="border-bottom border-gray pb-2 mb-4">MQTT</h6>
<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 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 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 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="mqttPass" value="${config.mqttPass}"/>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="my-2 p-3 bg-white rounded shadow">
<h6 class="border-bottom border-gray pb-2 mb-4">Web server</h6>
<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 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 class="row form-group">
<label class="col-4">Password</label>
<div class="col-8">
<input type="password" class="form-control auth-config" name="authPass" value="${config.authPass}"/>
</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'));
});
$('#authSecurity').on('change', function() {
var inputs = $('.auth-config');
inputs.prop('disabled', $(this).val() == 0);
});
$(function() {
$('#mqttEnable').trigger('change');
$('#authSecurity').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>

View File

@@ -5,14 +5,10 @@
<title>AMS reader</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" type="text/css" href="boot.css"/>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.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>
.bg-purple {
background-color: var(--purple);
}
.GaugeMeter {
position: Relative;
text-align: Center;
@@ -56,94 +52,257 @@
</head>
<body class="bg-light">
<main role="main" class="container">
<div class="d-flex align-items-center p-3 my-2 text-white-50 bg-purple rounded shadow">
<div class="lh-100">
<h6 class="mb-0 text-white lh-100">AMS reader</h6>
<small>${version}</small>
<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>
<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">
<h6 class="border-bottom border-gray pb-2 mb-4">Current meter values</h6>
<div class="row">
<div class="col-md-4">
<div class="col-md-3">
<div class="text-center">
<div id="P" class="SimpleMeter">
<div id="P" class="SimpleMeter" style="display: inline;">
${data.P} W
</div>
<div class="GaugeMeter rounded"
<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-animate_gauge_colors="1"
data-percent="0"
data-text="-"
data-label="Consumption"
data-append="W"
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-4">
<div id="P1" class="row" style="display: ${display.P1}">
<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="P2" class="row" style="display: ${display.P2}">
<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="P3" class="row" style="display: ${display.P3}">
<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>
<hr/>
<div class="row form-group">
<div class="col-6">
<a href="https://github.com/gskjold/AmsToMqttBridge/releases" class="btn btn-outline-secondary">Release notes</a>
</div>
<div class="col-6 text-right">
<a href="configuration" class="btn btn-primary">Configuration</a>
<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>
$(".GaugeMeter").gaugeMeter();
var im = $("#importMeter")
im.gaugeMeter({
percent: 0,
text: "-",
append: "W"
});
var wait = 500;
var nextrefresh = wait;
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();
var el = $(".GaugeMeter");
el.show();
var rate = 2500;
if(json.data) {
el.data('percent', json.pct);
if(json.data.P) {
var num = parseFloat(json.data.P);
if(num > 1000) {
num = num / 1000;
el.data('text', num.toFixed(1));
el.data('append','kW');
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 {
el.data('text', num);
el.data('append','W');
var num = parseFloat(str);
dst.html(num.toFixed(0));
$('#'+id+'-row').show();
}
}
el.gaugeMeter();
}
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];
@@ -152,48 +311,45 @@ var fetch = function() {
} else {
var num = parseFloat(str);
$('#'+id).html(num.toFixed(1));
$('#'+id+'-row').show();
}
}
if(json.data.U1 > 0) {
$('#P1').show();
}
if(json.data.U2 > 0) {
$('#P2').show();
}
if(json.data.U3 > 0) {
$('#P3').show();
}
if(json.meterType == 3) {
rate = 10000;
}
if(json.currentMillis && json.up) {
nextrefresh = rate - ((json.currentMillis - json.up) % rate) + wait;
} else {
nextrefresh = 2500;
}
} else {
el.data('percent', 0);
el.data('text', '-');
el.gaugeMeter();
nextrefresh = 2500;
im.gaugeMeter({
percent: 0,
text: "-",
append: "W"
});
em.gaugeMeter({
percent: 0,
text: "-",
append: "W"
});
}
if(!nextrefresh || nextrefresh < 500) {
nextrefresh = 2500;
}
setTimeout(fetch, nextrefresh);
setTimeout(fetch, interval);
}).fail(function() {
el.data('percent', 0);
el.data('text', '-');
el.gaugeMeter();
nextrefresh = 10000;
setTimeout(fetch, nextrefresh);
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");
});
}
setTimeout(fetch, nextrefresh);
fetch();
</script>
</body>
</html>