mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 04:57:28 +00:00
Compare commits
107 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34ebe9601a | ||
|
|
fa299198fc | ||
|
|
6d81b0a856 | ||
|
|
dd095da97b | ||
|
|
1b6ce203b7 | ||
|
|
2850be4e48 | ||
|
|
7eca31de84 | ||
|
|
a64f960cc7 | ||
|
|
ce3a47a7e6 | ||
|
|
8395e1dc77 | ||
|
|
39a4761415 | ||
|
|
867ab9d6c2 | ||
|
|
5e03e3d3c2 | ||
|
|
adb5050621 | ||
|
|
026cd25c8c | ||
|
|
06ec97b42a | ||
|
|
b420a0e6f4 | ||
|
|
7cd52d5689 | ||
|
|
ad78ff3082 | ||
|
|
fe7be81f1e | ||
|
|
7674fc2ad0 | ||
|
|
d50181c347 | ||
|
|
8ca771fa5a | ||
|
|
7d557b2679 | ||
|
|
2f0c912388 | ||
|
|
57e6d0fbe3 | ||
|
|
e7c25fafda | ||
|
|
2a4772fe25 | ||
|
|
f1f7408208 | ||
|
|
feb8e5007b | ||
|
|
c4af1ee74f | ||
|
|
992e1b6121 | ||
|
|
9cc7529934 | ||
|
|
ef8715be6d | ||
|
|
e232b875fa | ||
|
|
f18171fecc | ||
|
|
a3c7a09211 | ||
|
|
a6f3bc3f71 | ||
|
|
44bcd386d1 | ||
|
|
01547f9a52 | ||
|
|
940d38af5c | ||
|
|
4e451c51e1 | ||
|
|
43def1c311 | ||
|
|
2978116207 | ||
|
|
a055465ce0 | ||
|
|
998b986604 | ||
|
|
7799431405 | ||
|
|
508c14a671 | ||
|
|
4a7ef87269 | ||
|
|
3a4fc707b0 | ||
|
|
5d2e320b07 | ||
|
|
d12613b91a | ||
|
|
e6a02f34ab | ||
|
|
0b2ffbfd77 | ||
|
|
cab6c54ed9 | ||
|
|
313024f273 | ||
|
|
91fc078c5e | ||
|
|
5b9d44a3e9 | ||
|
|
ed523bda24 | ||
|
|
e3a1a242d2 | ||
|
|
ddfade65d8 | ||
|
|
0ef8390df0 | ||
|
|
f43fe5f864 | ||
|
|
f9ddd7533a | ||
|
|
f9f18bc106 | ||
|
|
280dd8605b | ||
|
|
41324cf590 | ||
|
|
64132d5ce3 | ||
|
|
0e75732022 | ||
|
|
6975714922 | ||
|
|
7f3e0e6089 | ||
|
|
6ff5518738 | ||
|
|
be29daee77 | ||
|
|
a90ed40aee | ||
|
|
32ba6c31f0 | ||
|
|
caf00f99e3 | ||
|
|
aaa318e511 | ||
|
|
648cac0b06 | ||
|
|
a5d1a2c0ee | ||
|
|
55f1b4b129 | ||
|
|
b805363c60 | ||
|
|
846b166ae9 | ||
|
|
4e039c2cc9 | ||
|
|
e5556fe80b | ||
|
|
c5aa65a780 | ||
|
|
30cf1435da | ||
|
|
fca46a3f54 | ||
|
|
0dca85d67b | ||
|
|
d9a5a21fe0 | ||
|
|
68eaa7d39b | ||
|
|
3231d0747e | ||
|
|
2617956b38 | ||
|
|
07614226b3 | ||
|
|
cddb170f24 | ||
|
|
ba7915ca7c | ||
|
|
ee12db4f51 | ||
|
|
f01fbfca53 | ||
|
|
fab637cf60 | ||
|
|
b2e144efcf | ||
|
|
10308ce738 | ||
|
|
70f5b0f912 | ||
|
|
d724a90085 | ||
|
|
1ab529785a | ||
|
|
3a81e62bbe | ||
|
|
4882916b5c | ||
|
|
1f7e43256a | ||
|
|
a0d3632fd7 |
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
5
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -24,16 +24,19 @@ A clear description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Hardware information:**
|
||||
- Country: [e.g. Norway]
|
||||
- Meter: [e.g. Aidon]
|
||||
- Encryption enabled [yes/no]
|
||||
- AMS reader: [e.g. Pow-U, ESP32 etc]
|
||||
- M-bus adapter (if applicable):
|
||||
|
||||
**Relevant firmware information:**
|
||||
- Version: [e.g. 1.5.0]
|
||||
- Version: [e.g. 2.1.0]
|
||||
- MQTT: [yes/no]
|
||||
- HAN GPIO: [e.g. GPIO5]
|
||||
- HAN baud and parity: [e.g. 2400 8E1]
|
||||
- Temperature sensors [e.g. 3xDS18B20]
|
||||
- ENTSO-E API enabled: [yes/no]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/support.md
vendored
6
.github/ISSUE_TEMPLATE/support.md
vendored
@@ -11,12 +11,16 @@ assignees: ''
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
**Hardware information:**
|
||||
- Country: [e.g. Norway]
|
||||
- Meter: [e.g. Aidon]
|
||||
- Encryption enabled [yes/no]
|
||||
- AMS reader: [e.g. Pow-U, ESP32 etc]
|
||||
- M-bus adapter (if applicable):
|
||||
|
||||
**Relevant firmware information:**
|
||||
- Version: [e.g. 1.5.0]
|
||||
- Version: [e.g. 2.1.0]
|
||||
- MQTT: [yes/no]
|
||||
- HAN GPIO: [e.g. GPIO5]
|
||||
- HAN baud and parity: [e.g. 2400 8E1]
|
||||
- Temperature sensors [e.g. 3xDS18B20]
|
||||
- ENTSO-E API enabled: [yes/no]
|
||||
|
||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -104,6 +104,15 @@ jobs:
|
||||
asset_path: .pio/build/esp32/firmware.bin
|
||||
asset_name: ams2mqtt-esp32-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32solo binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: .pio/build/esp32solo/firmware.bin
|
||||
asset_name: ams2mqtt-esp32solo-${{ steps.release_tag.outputs.tag }}.bin
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload esp32s2 binary to release
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
|
||||
BIN
doc/Switzerland/RWB_SmartMeter_Bedienungsanleitung.pdf
Normal file
BIN
doc/Switzerland/RWB_SmartMeter_Bedienungsanleitung.pdf
Normal file
Binary file not shown.
@@ -6,7 +6,7 @@ DB // Encrypted
|
||||
08 4B 41 4D 45 01 AC 4D 6E // System title
|
||||
82 // Prefix for 2-byte length
|
||||
01 D0 // Length 464
|
||||
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 0=Authentication, 0000= Security Suite ID
|
||||
30 // Security tag 0011 0000, 0=Compression off, 0=Unicast, 1=Encryption, 1=Authentication, 0000= Security Suite ID
|
||||
00 00 A3 2F // Frame counter
|
||||
|
||||
// Decrypted frame below
|
||||
|
||||
79
frames/lng.raw
Normal file
79
frames/lng.raw
Normal file
@@ -0,0 +1,79 @@
|
||||
7E // Flag
|
||||
A08B
|
||||
CEFF03
|
||||
13
|
||||
EEE1
|
||||
E6E700
|
||||
E0 // GBT (Green book 9.4.6.13)
|
||||
40 // Block control 0100 0000, last block=no, streaming=yes, remainig=window
|
||||
0001 // Block sequence
|
||||
0000 // Block sequence ack
|
||||
77 // How many bytes in this block
|
||||
|
||||
0F 00000DB7 // APDU tag, Invoke ID and priority
|
||||
|
||||
0C07E604020607220FFF800000 // Date and time
|
||||
0205 // Structure with 5 items
|
||||
0105 // Array with 5 items
|
||||
020412002809060008190900FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (0-8:25.9.0;2)
|
||||
020412002809060008190900FF0F01120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (0-8:25.9.0;1)
|
||||
020412000109060000600101FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (96.1.1 - Meter model)
|
||||
020412000309060100010700FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (1.7.0 Active import)
|
||||
020412000309060100020700FF0F02120000 // Structure with 4 items, uint16, OBIS, int8, uint16 (2.7.0 Active export)
|
||||
09060008190900 // OBIS 0-8:25.9.0 Object list push settings consumer information 1
|
||||
ABA6
|
||||
7E
|
||||
|
||||
|
||||
|
||||
7E
|
||||
A024
|
||||
CEFF03
|
||||
13
|
||||
D661
|
||||
E0 // GBT
|
||||
C0 // Block control 0100 0000, last block=yes, streaming=yes, remainig=window
|
||||
0002 // Block sequence
|
||||
0000 // Block sequence ack
|
||||
13 // How many bytes in this block
|
||||
|
||||
FF // Last byte of OBIS in previous block
|
||||
0906363031313039 // Device ID
|
||||
0600000028 // Accumulated import
|
||||
0600000000 // Accumulated export
|
||||
8BA4
|
||||
7E
|
||||
|
||||
|
||||
|
||||
|
||||
7E A1 23 CE FF 03 13 21 55 E6 E7 00
|
||||
|
||||
0F 00 00 08 E2
|
||||
0C 07 E5 07 13 01 0C 1A 0A FF 80 00 00
|
||||
|
||||
02 0B // 11
|
||||
01 0B // 11
|
||||
02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 28 09 06 00 08 19 09 00 FF 0F 01 12 00 00
|
||||
02 04 12 00 01 09 06 00 00 60 01 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 00 01 07 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 00 02 07 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 01 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 02 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 05 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 06 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 07 08 00 FF 0F 02 12 00 00
|
||||
02 04 12 00 03 09 06 01 01 08 08 00 FF 0F 02 12 00 00
|
||||
09 06 00 08 19 09 00 FF
|
||||
09 08 34 33 30 39 34 33 35 31
|
||||
06 00 00 00 0B
|
||||
06 00 00 00 00
|
||||
06 00 00 00 10
|
||||
06 00 00 00 04
|
||||
06 00 00 00 00
|
||||
06 00 00 00 08
|
||||
06 00 00 00 00
|
||||
06 00 00 00 01
|
||||
7C 8B
|
||||
7E
|
||||
34
hardware/wemos_mbus_shield/README.md
Normal file
34
hardware/wemos_mbus_shield/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
## Wemos shield
|
||||
|
||||
Fits D1 mini and S2 mini.
|
||||
|
||||
<img src="img1.jpg"/>
|
||||
<img src="img2.jpg"/>
|
||||
|
||||
**WARNING** Not properly tested
|
||||
|
||||
**Resistors**
|
||||
- R1: 470 (for U1)
|
||||
- R2: 220 (for U1)
|
||||
- R3: 82k (for U1)
|
||||
- R4: 220 (for U1)
|
||||
- R5: 100k (for U1)
|
||||
- R6: 10k (for AP button)
|
||||
- R7: 4.7k (for DS18B20)
|
||||
- R8-R10: 1k (for RGB LED)
|
||||
|
||||
**Capacitors**
|
||||
- C1: 100n (for U1)
|
||||
- C2: 1u (for U1)
|
||||
- C3: 100n (for U1)
|
||||
|
||||
**Other components**
|
||||
- U1: TSS521 or TSS721
|
||||
- U2: DS18B20 (Digital temperature sensor)
|
||||
- D1: RGB LED
|
||||
- J1: RJ45
|
||||
- SW1: AP button (to D3 GPIO0)
|
||||
|
||||
**Jumper**
|
||||
- JP1 & JP4: Jump these to receive power (5V) via RJ45 on pin 7 (GND) and 8 (+5)
|
||||
- JP2 & JP3: Jump these to attach DS18B20 sensors via RJ45 on pin 4 (Data) and pin 5 (GND)
|
||||
BIN
hardware/wemos_mbus_shield/img1.jpg
Normal file
BIN
hardware/wemos_mbus_shield/img1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 237 KiB |
BIN
hardware/wemos_mbus_shield/img2.jpg
Normal file
BIN
hardware/wemos_mbus_shield/img2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 296 KiB |
3
hardware/wemos_mbus_shield/kicad/.gitignore
vendored
Normal file
3
hardware/wemos_mbus_shield/kicad/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
gerber
|
||||
rescue-backup
|
||||
*-bak
|
||||
326
hardware/wemos_mbus_shield/kicad/d1_mini_shield-cache.lib
Normal file
326
hardware/wemos_mbus_shield/kicad/d1_mini_shield-cache.lib
Normal file
@@ -0,0 +1,326 @@
|
||||
EESchema-LIBRARY Version 2.4
|
||||
#encoding utf-8
|
||||
#
|
||||
# Connector_RJ45
|
||||
#
|
||||
DEF Connector_RJ45 J 0 40 Y Y 1 F N
|
||||
F0 "J" -200 550 50 H V R CNN
|
||||
F1 "Connector_RJ45" 100 550 50 H V L CNN
|
||||
F2 "" 0 25 50 V I C CNN
|
||||
F3 "" 0 25 50 V I C CNN
|
||||
ALIAS RJ31 RJ32 RJ33 RJ34 RJ35 RJ41 RJ45 RJ49 RJ61
|
||||
$FPLIST
|
||||
8P8C*
|
||||
RJ31*
|
||||
RJ32*
|
||||
RJ33*
|
||||
RJ34*
|
||||
RJ35*
|
||||
RJ41*
|
||||
RJ45*
|
||||
RJ49*
|
||||
RJ61*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S 300 500 -300 -400 0 1 10 f
|
||||
P 2 0 1 0 -200 175 -250 175 N
|
||||
P 2 0 1 0 -200 225 -250 225 N
|
||||
P 3 0 1 0 -250 -125 -200 -125 -200 -125 N
|
||||
P 3 0 1 0 -250 -75 -200 -75 -200 -75 N
|
||||
P 3 0 1 0 -250 -25 -200 -25 -200 -25 N
|
||||
P 3 0 1 0 -250 25 -200 25 -200 25 N
|
||||
P 3 0 1 0 -250 75 -200 75 -200 75 N
|
||||
P 3 0 1 0 -200 125 -250 125 -250 125 N
|
||||
P 14 0 1 0 -250 -175 -250 275 150 275 150 175 200 175 200 125 250 125 250 -25 200 -25 200 -75 150 -75 150 -175 -250 -175 -250 -175 N
|
||||
X ~ 1 400 -300 100 L 50 50 1 1 P
|
||||
X ~ 2 400 -200 100 L 50 50 1 1 P
|
||||
X ~ 3 400 -100 100 L 50 50 1 1 P
|
||||
X ~ 4 400 0 100 L 50 50 1 1 P
|
||||
X ~ 5 400 100 100 L 50 50 1 1 P
|
||||
X ~ 6 400 200 100 L 50 50 1 1 P
|
||||
X ~ 7 400 300 100 L 50 50 1 1 P
|
||||
X ~ 8 400 400 100 L 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# Device_C
|
||||
#
|
||||
DEF Device_C C 0 10 N Y 1 F N
|
||||
F0 "C" 25 100 50 H V L CNN
|
||||
F1 "Device_C" 25 -100 50 H V L CNN
|
||||
F2 "" 38 -150 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
C_*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
P 2 0 1 20 -80 -30 80 -30 N
|
||||
P 2 0 1 20 -80 30 80 30 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
|
||||
#
|
||||
# Device_Jumper
|
||||
#
|
||||
DEF Device_Jumper JP 0 30 Y N 1 F N
|
||||
F0 "JP" 0 150 50 H V C CNN
|
||||
F1 "Device_Jumper" 0 -80 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
SolderJumper*
|
||||
$ENDFPLIST
|
||||
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
|
||||
#
|
||||
# Device_LED_RGB
|
||||
#
|
||||
DEF Device_LED_RGB D 0 0 Y N 1 F N
|
||||
F0 "D" 0 370 50 H V C CNN
|
||||
F1 "Device_LED_RGB" 0 -350 50 H V C CNN
|
||||
F2 "" 0 -50 50 H I C CNN
|
||||
F3 "" 0 -50 50 H I C CNN
|
||||
$FPLIST
|
||||
LED*
|
||||
LED_SMD:*
|
||||
LED_THT:*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
T 0 -75 -250 50 0 0 0 B Normal 0 C C
|
||||
T 0 -75 -50 50 0 0 0 G Normal 0 C C
|
||||
T 0 -75 150 50 0 0 0 R Normal 0 C C
|
||||
S 50 -50 50 50 0 1 0 N
|
||||
S 50 50 50 50 0 1 0 N
|
||||
S 50 150 50 250 0 1 0 N
|
||||
S 50 250 50 250 0 1 0 N
|
||||
S 110 330 -110 -300 0 1 10 f
|
||||
P 2 0 1 0 -50 -200 -100 -200 N
|
||||
P 2 0 1 0 -50 -200 50 -200 N
|
||||
P 2 0 1 0 -50 -150 -50 -250 N
|
||||
P 2 0 1 8 -50 -150 -50 -250 N
|
||||
P 2 0 1 0 -50 0 -100 0 N
|
||||
P 2 0 1 8 -50 50 -50 -50 N
|
||||
P 2 0 1 0 -50 200 -100 200 N
|
||||
P 2 0 1 0 -50 200 50 200 N
|
||||
P 2 0 1 8 -50 250 -50 150 N
|
||||
P 2 0 1 0 50 -200 100 -200 N
|
||||
P 2 0 1 0 50 0 -50 0 N
|
||||
P 2 0 1 0 50 0 100 0 N
|
||||
P 2 0 1 0 50 200 100 200 N
|
||||
P 3 0 1 0 -50 50 -50 -50 -50 -50 N
|
||||
P 3 0 1 0 -50 250 -50 150 -50 150 N
|
||||
P 4 0 1 8 50 -150 50 -250 -50 -200 50 -150 N
|
||||
P 4 0 1 8 50 50 50 -50 -50 0 50 50 N
|
||||
P 4 0 1 8 50 250 50 150 -50 200 50 250 N
|
||||
P 5 0 1 0 -40 -150 20 -90 -10 -90 20 -90 20 -120 N
|
||||
P 5 0 1 0 -40 50 20 110 -10 110 20 110 20 80 N
|
||||
P 5 0 1 0 -40 250 20 310 -10 310 20 310 20 280 N
|
||||
P 5 0 1 0 0 -150 60 -90 30 -90 60 -90 60 -120 N
|
||||
P 5 0 1 0 0 50 60 110 30 110 60 110 60 80 N
|
||||
P 5 0 1 0 0 250 60 310 30 310 60 310 60 280 N
|
||||
X RK 1 -200 200 100 R 50 50 1 1 P
|
||||
X GK 2 -200 0 100 R 50 50 1 1 P
|
||||
X BK 3 -200 -200 100 R 50 50 1 1 P
|
||||
X BA 4 200 -200 100 L 50 50 1 1 P
|
||||
X GA 5 200 0 100 L 50 50 1 1 P
|
||||
X RA 6 200 200 100 L 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# Device_R
|
||||
#
|
||||
DEF Device_R R 0 0 N Y 1 F N
|
||||
F0 "R" 80 0 50 V V C CNN
|
||||
F1 "Device_R" 0 0 50 V V C CNN
|
||||
F2 "" -70 0 50 V I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
$FPLIST
|
||||
R_*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -40 -100 40 100 0 1 10 N
|
||||
X ~ 1 0 150 50 D 50 50 1 1 P
|
||||
X ~ 2 0 -150 50 U 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# Sensor_Temperature_DS18B20
|
||||
#
|
||||
DEF Sensor_Temperature_DS18B20 U 0 40 Y Y 1 F N
|
||||
F0 "U" -150 250 50 H V C CNN
|
||||
F1 "Sensor_Temperature_DS18B20" 250 250 50 H V C CNN
|
||||
F2 "Package_TO_SOT_THT:TO-92_Inline" -1000 -250 50 H I C CNN
|
||||
F3 "" -150 250 50 H I C CNN
|
||||
ALIAS DS1822 DS18B20 DS18S20 DS1821C
|
||||
$FPLIST
|
||||
TO*92*
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
A -130 125 25 1 1799 0 1 10 N -105 125 -155 125
|
||||
C -130 -100 50 0 1 10 F
|
||||
S -200 200 200 -200 0 1 10 f
|
||||
S -105 -75 -155 0 0 1 10 F
|
||||
P 2 0 1 10 -155 25 -130 25 N
|
||||
P 2 0 1 10 -155 50 -130 50 N
|
||||
P 2 0 1 10 -155 75 -130 75 N
|
||||
P 2 0 1 10 -155 100 -130 100 N
|
||||
P 2 0 1 10 -155 125 -155 0 N
|
||||
P 2 0 1 10 -155 125 -130 125 N
|
||||
P 2 0 1 10 -105 125 -105 0 N
|
||||
X GND 1 0 -300 100 U 50 50 1 1 W
|
||||
X DQ 2 300 0 100 L 50 50 1 1 B
|
||||
X VDD 3 0 300 100 D 50 50 1 1 W
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# Switch_SW_Push
|
||||
#
|
||||
DEF Switch_SW_Push SW 0 40 N N 1 F N
|
||||
F0 "SW" 50 100 50 H V L CNN
|
||||
F1 "Switch_SW_Push" 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
|
||||
#
|
||||
# d1_mini_shield-rescue_CONN_01X08
|
||||
#
|
||||
DEF d1_mini_shield-rescue_CONN_01X08 P 0 40 Y N 1 F N
|
||||
F0 "P" 0 450 50 H V C CNN
|
||||
F1 "d1_mini_shield-rescue_CONN_01X08" 100 0 50 V V C CNN
|
||||
F2 "" 0 0 60 H V C CNN
|
||||
F3 "" 0 0 60 H V C CNN
|
||||
$FPLIST
|
||||
Pin_Header_Straight_1X08
|
||||
Pin_Header_Angled_1X08
|
||||
Socket_Strip_Straight_1X08
|
||||
Socket_Strip_Angled_1X08
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -50 -400 50 400 0 1 0 N
|
||||
S -50 -345 10 -355 0 1 0 N
|
||||
S -50 -245 10 -255 0 1 0 N
|
||||
S -50 -145 10 -155 0 1 0 N
|
||||
S -50 -45 10 -55 0 1 0 N
|
||||
S -50 55 10 45 0 1 0 N
|
||||
S -50 155 10 145 0 1 0 N
|
||||
S -50 255 10 245 0 1 0 N
|
||||
S -50 355 10 345 0 1 0 N
|
||||
X P1 1 -200 350 150 R 50 50 1 1 P
|
||||
X P2 2 -200 250 150 R 50 50 1 1 P
|
||||
X P3 3 -200 150 150 R 50 50 1 1 P
|
||||
X P4 4 -200 50 150 R 50 50 1 1 P
|
||||
X P5 5 -200 -50 150 R 50 50 1 1 P
|
||||
X P6 6 -200 -150 150 R 50 50 1 1 P
|
||||
X P7 7 -200 -250 150 R 50 50 1 1 P
|
||||
X P8 8 -200 -350 150 R 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# power_+3.3V
|
||||
#
|
||||
DEF power_+3.3V #PWR 0 0 Y Y 1 F P
|
||||
F0 "#PWR" 0 -150 50 H I C CNN
|
||||
F1 "power_+3.3V" 0 140 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
ALIAS +3.3V
|
||||
DRAW
|
||||
P 2 0 1 0 -30 50 0 100 N
|
||||
P 2 0 1 0 0 0 0 100 N
|
||||
P 2 0 1 0 0 100 30 50 N
|
||||
X +3V3 1 0 0 0 U 50 50 1 1 W N
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# power_+3V3
|
||||
#
|
||||
DEF power_+3V3 #PWR 0 0 Y Y 1 F P
|
||||
F0 "#PWR" 0 -150 50 H I C CNN
|
||||
F1 "power_+3V3" 0 140 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
DRAW
|
||||
P 2 0 1 0 -30 50 0 100 N
|
||||
P 2 0 1 0 0 0 0 100 N
|
||||
P 2 0 1 0 0 100 30 50 N
|
||||
X +3V3 1 0 0 0 U 50 50 1 1 W N
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# power_+5V
|
||||
#
|
||||
DEF power_+5V #PWR 0 0 Y Y 1 F P
|
||||
F0 "#PWR" 0 -150 50 H I C CNN
|
||||
F1 "power_+5V" 0 140 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
DRAW
|
||||
P 2 0 1 0 -30 50 0 100 N
|
||||
P 2 0 1 0 0 0 0 100 N
|
||||
P 2 0 1 0 0 100 30 50 N
|
||||
X +5V 1 0 0 0 U 50 50 1 1 W N
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
# power_GND
|
||||
#
|
||||
DEF power_GND #PWR 0 0 Y Y 1 F P
|
||||
F0 "#PWR" 0 -250 50 H I C CNN
|
||||
F1 "power_GND" 0 -150 50 H V C CNN
|
||||
F2 "" 0 0 50 H I C CNN
|
||||
F3 "" 0 0 50 H I C CNN
|
||||
DRAW
|
||||
P 6 0 1 0 0 0 0 -50 50 -50 0 -100 -50 -50 0 -50 N
|
||||
X GND 1 0 0 0 D 50 50 1 1 W N
|
||||
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
|
||||
ALIAS TSS721A
|
||||
$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
|
||||
#
|
||||
#End Library
|
||||
38
hardware/wemos_mbus_shield/kicad/d1_mini_shield-rescue.lib
Normal file
38
hardware/wemos_mbus_shield/kicad/d1_mini_shield-rescue.lib
Normal file
@@ -0,0 +1,38 @@
|
||||
EESchema-LIBRARY Version 2.4
|
||||
#encoding utf-8
|
||||
#
|
||||
# CONN_01X08
|
||||
#
|
||||
DEF CONN_01X08 P 0 40 Y N 1 F N
|
||||
F0 "P" 0 450 50 H V C CNN
|
||||
F1 "CONN_01X08" 100 0 50 V V C CNN
|
||||
F2 "" 0 0 60 H V C CNN
|
||||
F3 "" 0 0 60 H V C CNN
|
||||
$FPLIST
|
||||
Pin_Header_Straight_1X08
|
||||
Pin_Header_Angled_1X08
|
||||
Socket_Strip_Straight_1X08
|
||||
Socket_Strip_Angled_1X08
|
||||
$ENDFPLIST
|
||||
DRAW
|
||||
S -50 -400 50 400 0 1 0 N
|
||||
S -50 -345 10 -355 0 1 0 N
|
||||
S -50 -245 10 -255 0 1 0 N
|
||||
S -50 -145 10 -155 0 1 0 N
|
||||
S -50 -45 10 -55 0 1 0 N
|
||||
S -50 55 10 45 0 1 0 N
|
||||
S -50 155 10 145 0 1 0 N
|
||||
S -50 255 10 245 0 1 0 N
|
||||
S -50 355 10 345 0 1 0 N
|
||||
X P1 1 -200 350 150 R 50 50 1 1 P
|
||||
X P2 2 -200 250 150 R 50 50 1 1 P
|
||||
X P3 3 -200 150 150 R 50 50 1 1 P
|
||||
X P4 4 -200 50 150 R 50 50 1 1 P
|
||||
X P5 5 -200 -50 150 R 50 50 1 1 P
|
||||
X P6 6 -200 -150 150 R 50 50 1 1 P
|
||||
X P7 7 -200 -250 150 R 50 50 1 1 P
|
||||
X P8 8 -200 -350 150 R 50 50 1 1 P
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
#End Library
|
||||
1852
hardware/wemos_mbus_shield/kicad/d1_mini_shield.kicad_pcb
Normal file
1852
hardware/wemos_mbus_shield/kicad/d1_mini_shield.kicad_pcb
Normal file
File diff suppressed because it is too large
Load Diff
482
hardware/wemos_mbus_shield/kicad/d1_mini_shield.net
Normal file
482
hardware/wemos_mbus_shield/kicad/d1_mini_shield.net
Normal file
@@ -0,0 +1,482 @@
|
||||
(export (version D)
|
||||
(design
|
||||
(source /home/gunnar/src/AmsToMqttBridge/hardware/wemos_mbus_shield/kicad/d1_mini_shield.sch)
|
||||
(date "sø. 26. juni 2022 kl. 12.19 +0200")
|
||||
(tool "Eeschema 5.1.5+dfsg1-2build2")
|
||||
(sheet (number 1) (name /) (tstamps /)
|
||||
(title_block
|
||||
(title)
|
||||
(company)
|
||||
(rev)
|
||||
(date)
|
||||
(source d1_mini_shield.sch)
|
||||
(comment (number 1) (value ""))
|
||||
(comment (number 2) (value ""))
|
||||
(comment (number 3) (value ""))
|
||||
(comment (number 4) (value "")))))
|
||||
(components
|
||||
(comp (ref P1)
|
||||
(value CONN_01X08)
|
||||
(footprint D1_mini:D1_mini_Pin_Header)
|
||||
(libsource (lib d1_mini_shield-rescue) (part CONN_01X08) (description ""))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 5763EB78))
|
||||
(comp (ref P2)
|
||||
(value CONN_01X08)
|
||||
(footprint D1_mini:D1_mini_Pin_Header)
|
||||
(libsource (lib d1_mini_shield-rescue) (part CONN_01X08) (description ""))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 5763EBF2))
|
||||
(comp (ref U1)
|
||||
(value TSS721)
|
||||
(footprint Package_SO:SOIC-16_3.9x9.9mm_P1.27mm)
|
||||
(datasheet http://www.ti.com/lit/ds/symlink/tss721a.pdf)
|
||||
(libsource (lib tss721) (part TSS721) (description "TSS721A Meter Bus Transceiver, SOIC-16/TSSOP-16"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62B826D2))
|
||||
(comp (ref J1)
|
||||
(value RJ45)
|
||||
(footprint Connector_RJ:RJ45_Amphenol_54602-x08_Horizontal)
|
||||
(datasheet ~)
|
||||
(libsource (lib Connector) (part RJ45) (description "RJ connector, 8P8C (8 positions 8 connected)"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62B8AC34))
|
||||
(comp (ref R4)
|
||||
(value 220)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62B8CAA8))
|
||||
(comp (ref R2)
|
||||
(value 220)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62B8DB92))
|
||||
(comp (ref R3)
|
||||
(value 82k)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62B9F861))
|
||||
(comp (ref R1)
|
||||
(value 470)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62B9FC37))
|
||||
(comp (ref C2)
|
||||
(value 1u)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part C) (description "Unpolarized capacitor"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62BA06BE))
|
||||
(comp (ref C1)
|
||||
(value 100n)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part C) (description "Unpolarized capacitor"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62BA09E7))
|
||||
(comp (ref R5)
|
||||
(value 100k)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62BED413))
|
||||
(comp (ref C3)
|
||||
(value 100n)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part C) (description "Unpolarized capacitor"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62BED9ED))
|
||||
(comp (ref JP1)
|
||||
(value Jumper)
|
||||
(footprint Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part Jumper) (description "Jumper, normally closed"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C331C0))
|
||||
(comp (ref JP2)
|
||||
(value Jumper)
|
||||
(footprint Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part Jumper) (description "Jumper, normally closed"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C365CA))
|
||||
(comp (ref JP3)
|
||||
(value Jumper)
|
||||
(footprint Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part Jumper) (description "Jumper, normally closed"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C36C70))
|
||||
(comp (ref D1)
|
||||
(value LED_RGB)
|
||||
(footprint LED_SMD:LED_RGB_PLCC-6)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part LED_RGB) (description "RGB LED, 6 pin package"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C43A7B))
|
||||
(comp (ref R9)
|
||||
(value 1k)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C495F7))
|
||||
(comp (ref R8)
|
||||
(value 1k)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C4BDE9))
|
||||
(comp (ref R10)
|
||||
(value 1k)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C4C336))
|
||||
(comp (ref U2)
|
||||
(value DS18B20)
|
||||
(footprint Package_TO_SOT_THT:TO-92_Inline)
|
||||
(datasheet http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf)
|
||||
(libsource (lib Sensor_Temperature) (part DS18B20) (description "Programmable Resolution 1-Wire Digital Thermometer TO-92"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C6296F))
|
||||
(comp (ref R7)
|
||||
(value 4k7)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C6405D))
|
||||
(comp (ref SW1)
|
||||
(value SW_Push)
|
||||
(footprint Button_Switch_SMD:SW_SPST_B3U-3000P)
|
||||
(datasheet ~)
|
||||
(libsource (lib Switch) (part SW_Push) (description "Push button switch, generic, two pins"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C73229))
|
||||
(comp (ref R6)
|
||||
(value 10k)
|
||||
(footprint Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part R) (description Resistor))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62C73CE8))
|
||||
(comp (ref JP4)
|
||||
(value Jumper)
|
||||
(footprint Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm)
|
||||
(datasheet ~)
|
||||
(libsource (lib Device) (part Jumper) (description "Jumper, normally closed"))
|
||||
(sheetpath (names /) (tstamps /))
|
||||
(tstamp 62D1E97A)))
|
||||
(libparts
|
||||
(libpart (lib Connector) (part 8P8C)
|
||||
(aliases
|
||||
(alias RJ31)
|
||||
(alias RJ32)
|
||||
(alias RJ33)
|
||||
(alias RJ34)
|
||||
(alias RJ35)
|
||||
(alias RJ41)
|
||||
(alias RJ45)
|
||||
(alias RJ49)
|
||||
(alias RJ61))
|
||||
(description "RJ connector, 8P8C (8 positions 8 connected), RJ31/RJ32/RJ33/RJ34/RJ35/RJ41/RJ45/RJ49/RJ61")
|
||||
(docs ~)
|
||||
(footprints
|
||||
(fp 8P8C*)
|
||||
(fp RJ31*)
|
||||
(fp RJ32*)
|
||||
(fp RJ33*)
|
||||
(fp RJ34*)
|
||||
(fp RJ35*)
|
||||
(fp RJ41*)
|
||||
(fp RJ45*)
|
||||
(fp RJ49*)
|
||||
(fp RJ61*))
|
||||
(fields
|
||||
(field (name Reference) J)
|
||||
(field (name Value) 8P8C))
|
||||
(pins
|
||||
(pin (num 1) (name ~) (type passive))
|
||||
(pin (num 2) (name ~) (type passive))
|
||||
(pin (num 3) (name ~) (type passive))
|
||||
(pin (num 4) (name ~) (type passive))
|
||||
(pin (num 5) (name ~) (type passive))
|
||||
(pin (num 6) (name ~) (type passive))
|
||||
(pin (num 7) (name ~) (type passive))
|
||||
(pin (num 8) (name ~) (type passive))))
|
||||
(libpart (lib Device) (part C)
|
||||
(description "Unpolarized capacitor")
|
||||
(docs ~)
|
||||
(footprints
|
||||
(fp C_*))
|
||||
(fields
|
||||
(field (name Reference) C)
|
||||
(field (name Value) C))
|
||||
(pins
|
||||
(pin (num 1) (name ~) (type passive))
|
||||
(pin (num 2) (name ~) (type passive))))
|
||||
(libpart (lib Device) (part Jumper)
|
||||
(description "Jumper, normally closed")
|
||||
(docs ~)
|
||||
(footprints
|
||||
(fp SolderJumper*))
|
||||
(fields
|
||||
(field (name Reference) JP)
|
||||
(field (name Value) Jumper))
|
||||
(pins
|
||||
(pin (num 1) (name 1) (type passive))
|
||||
(pin (num 2) (name 2) (type passive))))
|
||||
(libpart (lib Device) (part LED_RGB)
|
||||
(description "RGB LED, 6 pin package")
|
||||
(docs ~)
|
||||
(footprints
|
||||
(fp LED*)
|
||||
(fp LED_SMD:*)
|
||||
(fp LED_THT:*))
|
||||
(fields
|
||||
(field (name Reference) D)
|
||||
(field (name Value) LED_RGB))
|
||||
(pins
|
||||
(pin (num 1) (name RK) (type passive))
|
||||
(pin (num 2) (name GK) (type passive))
|
||||
(pin (num 3) (name BK) (type passive))
|
||||
(pin (num 4) (name BA) (type passive))
|
||||
(pin (num 5) (name GA) (type passive))
|
||||
(pin (num 6) (name RA) (type passive))))
|
||||
(libpart (lib Device) (part R)
|
||||
(description Resistor)
|
||||
(docs ~)
|
||||
(footprints
|
||||
(fp R_*))
|
||||
(fields
|
||||
(field (name Reference) R)
|
||||
(field (name Value) R))
|
||||
(pins
|
||||
(pin (num 1) (name ~) (type passive))
|
||||
(pin (num 2) (name ~) (type passive))))
|
||||
(libpart (lib Sensor_Temperature) (part MAX31820)
|
||||
(aliases
|
||||
(alias DS1822)
|
||||
(alias DS18B20)
|
||||
(alias DS18S20)
|
||||
(alias DS1821C))
|
||||
(description "1-Wire Ambient Temperature Sensor TO-92")
|
||||
(docs http://datasheets.maximintegrated.com/en/ds/MAX31820.pdf)
|
||||
(footprints
|
||||
(fp TO*92*))
|
||||
(fields
|
||||
(field (name Reference) U)
|
||||
(field (name Value) MAX31820)
|
||||
(field (name Footprint) Package_TO_SOT_THT:TO-92_Inline))
|
||||
(pins
|
||||
(pin (num 1) (name GND) (type power_in))
|
||||
(pin (num 2) (name DQ) (type BiDi))
|
||||
(pin (num 3) (name VDD) (type power_in))))
|
||||
(libpart (lib Switch) (part SW_Push)
|
||||
(description "Push button switch, generic, two pins")
|
||||
(docs ~)
|
||||
(fields
|
||||
(field (name Reference) SW)
|
||||
(field (name Value) SW_Push))
|
||||
(pins
|
||||
(pin (num 1) (name 1) (type passive))
|
||||
(pin (num 2) (name 2) (type passive))))
|
||||
(libpart (lib d1_mini_shield-rescue) (part CONN_01X08)
|
||||
(footprints
|
||||
(fp Pin_Header_Straight_1X08)
|
||||
(fp Pin_Header_Angled_1X08)
|
||||
(fp Socket_Strip_Straight_1X08)
|
||||
(fp Socket_Strip_Angled_1X08))
|
||||
(fields
|
||||
(field (name Reference) P)
|
||||
(field (name Value) CONN_01X08))
|
||||
(pins
|
||||
(pin (num 1) (name P1) (type passive))
|
||||
(pin (num 2) (name P2) (type passive))
|
||||
(pin (num 3) (name P3) (type passive))
|
||||
(pin (num 4) (name P4) (type passive))
|
||||
(pin (num 5) (name P5) (type passive))
|
||||
(pin (num 6) (name P6) (type passive))
|
||||
(pin (num 7) (name P7) (type passive))
|
||||
(pin (num 8) (name P8) (type passive))))
|
||||
(libpart (lib tss721) (part TSS721)
|
||||
(aliases
|
||||
(alias TSS721A))
|
||||
(description "TSS721A Meter Bus Transceiver, SOIC-16/TSSOP-16")
|
||||
(docs http://www.ti.com/lit/ds/symlink/tss721a.pdf)
|
||||
(footprints
|
||||
(fp SOIC*3.9x9.9mm*Pitch1.27mm*)
|
||||
(fp TSSOP*4.4x5mm*Pitch0.65mm*))
|
||||
(fields
|
||||
(field (name Reference) U)
|
||||
(field (name Value) TSS721))
|
||||
(pins
|
||||
(pin (num 1) (name BUSL2) (type input))
|
||||
(pin (num 2) (name VB) (type passive))
|
||||
(pin (num 3) (name STC) (type passive))
|
||||
(pin (num 4) (name RIDD) (type output))
|
||||
(pin (num 5) (name PF) (type input))
|
||||
(pin (num 6) (name SC) (type passive))
|
||||
(pin (num 7) (name TXI) (type output))
|
||||
(pin (num 8) (name TX) (type output))
|
||||
(pin (num 9) (name BAT) (type input))
|
||||
(pin (num 10) (name VS) (type passive))
|
||||
(pin (num 11) (name VDD) (type power_in))
|
||||
(pin (num 12) (name RX) (type input))
|
||||
(pin (num 13) (name RXI) (type input))
|
||||
(pin (num 14) (name RIS) (type input))
|
||||
(pin (num 15) (name GND) (type power_in))
|
||||
(pin (num 16) (name BUSL1) (type input)))))
|
||||
(libraries
|
||||
(library (logical Connector)
|
||||
(uri /usr/share/kicad/library/Connector.lib))
|
||||
(library (logical Device)
|
||||
(uri /usr/share/kicad/library/Device.lib))
|
||||
(library (logical Sensor_Temperature)
|
||||
(uri /usr/share/kicad/library/Sensor_Temperature.lib))
|
||||
(library (logical Switch)
|
||||
(uri /usr/share/kicad/library/Switch.lib))
|
||||
(library (logical d1_mini_shield-rescue)
|
||||
(uri /home/gunnar/src/AmsToMqttBridge/hardware/wemos_mbus_shield/kicad/d1_mini_shield-rescue.lib))
|
||||
(library (logical tss721)
|
||||
(uri /home/gunnar/src/AmsToMqttBridge/hardware/wemos_mbus_shield/kicad/tss721.lib)))
|
||||
(nets
|
||||
(net (code 1) (name TEMP)
|
||||
(node (ref R7) (pin 2))
|
||||
(node (ref U2) (pin 2))
|
||||
(node (ref P1) (pin 5))
|
||||
(node (ref JP3) (pin 2)))
|
||||
(net (code 2) (name +3V3)
|
||||
(node (ref R7) (pin 1))
|
||||
(node (ref U1) (pin 9))
|
||||
(node (ref P2) (pin 1))
|
||||
(node (ref R6) (pin 2))
|
||||
(node (ref D1) (pin 4))
|
||||
(node (ref D1) (pin 5))
|
||||
(node (ref D1) (pin 6))
|
||||
(node (ref U2) (pin 3)))
|
||||
(net (code 3) (name "Net-(D1-Pad1)")
|
||||
(node (ref R9) (pin 2))
|
||||
(node (ref D1) (pin 1)))
|
||||
(net (code 4) (name "Net-(D1-Pad2)")
|
||||
(node (ref D1) (pin 2))
|
||||
(node (ref R8) (pin 2)))
|
||||
(net (code 5) (name "Net-(D1-Pad3)")
|
||||
(node (ref D1) (pin 3))
|
||||
(node (ref R10) (pin 2)))
|
||||
(net (code 6) (name MBUS2)
|
||||
(node (ref J1) (pin 2))
|
||||
(node (ref R4) (pin 1)))
|
||||
(net (code 7) (name BTN_AP)
|
||||
(node (ref P1) (pin 4))
|
||||
(node (ref SW1) (pin 1))
|
||||
(node (ref R6) (pin 1)))
|
||||
(net (code 8) (name "Net-(R4-Pad2)")
|
||||
(node (ref U1) (pin 1))
|
||||
(node (ref R4) (pin 2)))
|
||||
(net (code 9) (name "Net-(R2-Pad2)")
|
||||
(node (ref R2) (pin 2))
|
||||
(node (ref U1) (pin 16)))
|
||||
(net (code 10) (name MBUS1)
|
||||
(node (ref J1) (pin 1))
|
||||
(node (ref R2) (pin 1)))
|
||||
(net (code 11) (name LED_R)
|
||||
(node (ref R9) (pin 1))
|
||||
(node (ref P2) (pin 4)))
|
||||
(net (code 12) (name "Net-(J1-Pad8)")
|
||||
(node (ref J1) (pin 8))
|
||||
(node (ref JP1) (pin 1)))
|
||||
(net (code 13) (name "Net-(J1-Pad5)")
|
||||
(node (ref J1) (pin 5))
|
||||
(node (ref JP2) (pin 1)))
|
||||
(net (code 14) (name "Net-(J1-Pad4)")
|
||||
(node (ref J1) (pin 4))
|
||||
(node (ref JP3) (pin 1)))
|
||||
(net (code 15) (name +5V)
|
||||
(node (ref P1) (pin 1))
|
||||
(node (ref JP1) (pin 2)))
|
||||
(net (code 16) (name "Net-(J1-Pad7)")
|
||||
(node (ref J1) (pin 7))
|
||||
(node (ref JP4) (pin 1)))
|
||||
(net (code 17) (name GND)
|
||||
(node (ref P1) (pin 2))
|
||||
(node (ref C3) (pin 2))
|
||||
(node (ref R3) (pin 1))
|
||||
(node (ref R1) (pin 1))
|
||||
(node (ref C2) (pin 1))
|
||||
(node (ref C1) (pin 1))
|
||||
(node (ref R5) (pin 2))
|
||||
(node (ref U1) (pin 15))
|
||||
(node (ref U2) (pin 1))
|
||||
(node (ref SW1) (pin 2))
|
||||
(node (ref JP4) (pin 2))
|
||||
(node (ref JP2) (pin 2)))
|
||||
(net (code 18) (name LED_G)
|
||||
(node (ref R8) (pin 1))
|
||||
(node (ref P2) (pin 5)))
|
||||
(net (code 19) (name "Net-(U1-Pad10)")
|
||||
(node (ref U1) (pin 10)))
|
||||
(net (code 20) (name "Net-(U1-Pad12)")
|
||||
(node (ref U1) (pin 12)))
|
||||
(net (code 21) (name "Net-(U1-Pad13)")
|
||||
(node (ref U1) (pin 13)))
|
||||
(net (code 22) (name "Net-(U1-Pad2)")
|
||||
(node (ref U1) (pin 2)))
|
||||
(net (code 23) (name "Net-(U1-Pad5)")
|
||||
(node (ref U1) (pin 5)))
|
||||
(net (code 24) (name "Net-(U1-Pad7)")
|
||||
(node (ref U1) (pin 7)))
|
||||
(net (code 25) (name "Net-(J1-Pad3)")
|
||||
(node (ref J1) (pin 3)))
|
||||
(net (code 26) (name "Net-(J1-Pad6)")
|
||||
(node (ref J1) (pin 6)))
|
||||
(net (code 27) (name /RST)
|
||||
(node (ref P2) (pin 8)))
|
||||
(net (code 28) (name /D4)
|
||||
(node (ref P1) (pin 3)))
|
||||
(net (code 29) (name /D1)
|
||||
(node (ref P1) (pin 6)))
|
||||
(net (code 30) (name /RX)
|
||||
(node (ref P1) (pin 7)))
|
||||
(net (code 31) (name /TX)
|
||||
(node (ref P1) (pin 8)))
|
||||
(net (code 32) (name /D8)
|
||||
(node (ref P2) (pin 2)))
|
||||
(net (code 33) (name /D0)
|
||||
(node (ref P2) (pin 6)))
|
||||
(net (code 34) (name /A0)
|
||||
(node (ref P2) (pin 7)))
|
||||
(net (code 35) (name "Net-(R3-Pad2)")
|
||||
(node (ref R3) (pin 2))
|
||||
(node (ref U1) (pin 4)))
|
||||
(net (code 36) (name HAN_TX)
|
||||
(node (ref R10) (pin 1))
|
||||
(node (ref U1) (pin 8))
|
||||
(node (ref P2) (pin 3)))
|
||||
(net (code 37) (name "Net-(C3-Pad1)")
|
||||
(node (ref U1) (pin 11))
|
||||
(node (ref C3) (pin 1))
|
||||
(node (ref R5) (pin 1)))
|
||||
(net (code 38) (name "Net-(R1-Pad2)")
|
||||
(node (ref R1) (pin 2))
|
||||
(node (ref U1) (pin 14)))
|
||||
(net (code 39) (name "Net-(C2-Pad2)")
|
||||
(node (ref C2) (pin 2))
|
||||
(node (ref U1) (pin 3)))
|
||||
(net (code 40) (name "Net-(C1-Pad2)")
|
||||
(node (ref U1) (pin 6))
|
||||
(node (ref C1) (pin 2)))))
|
||||
246
hardware/wemos_mbus_shield/kicad/d1_mini_shield.pro
Normal file
246
hardware/wemos_mbus_shield/kicad/d1_mini_shield.pro
Normal file
@@ -0,0 +1,246 @@
|
||||
update=sø. 26. juni 2022 kl. 11.55 +0200
|
||||
version=1
|
||||
last_client=kicad
|
||||
[general]
|
||||
version=1
|
||||
[cvpcb]
|
||||
version=1
|
||||
NetIExt=net
|
||||
[eeschema]
|
||||
version=1
|
||||
LibDir=
|
||||
[schematic_editor]
|
||||
version=1
|
||||
PageLayoutDescrFile=
|
||||
PlotDirectoryName=
|
||||
SubpartIdSeparator=0
|
||||
SubpartFirstId=65
|
||||
NetFmtName=Pcbnew
|
||||
SpiceAjustPassiveValues=0
|
||||
LabSize=50
|
||||
ERC_TestSimilarLabels=1
|
||||
[pcbnew]
|
||||
version=1
|
||||
PageLayoutDescrFile=
|
||||
LastNetListRead=d1_mini_shield.net
|
||||
CopperLayerCount=2
|
||||
BoardThickness=1.6
|
||||
AllowMicroVias=0
|
||||
AllowBlindVias=0
|
||||
RequireCourtyardDefinitions=0
|
||||
ProhibitOverlappingCourtyards=1
|
||||
MinTrackWidth=0.2
|
||||
MinViaDiameter=0.4
|
||||
MinViaDrill=0.3
|
||||
MinMicroViaDiameter=0.2
|
||||
MinMicroViaDrill=0.09999999999999999
|
||||
MinHoleToHole=0.25
|
||||
TrackWidth1=0.25
|
||||
TrackWidth2=0.5
|
||||
ViaDiameter1=0.6
|
||||
ViaDrill1=0.4
|
||||
dPairWidth1=0.2
|
||||
dPairGap1=0.25
|
||||
dPairViaGap1=0.25
|
||||
SilkLineWidth=0.15
|
||||
SilkTextSizeV=1
|
||||
SilkTextSizeH=1
|
||||
SilkTextSizeThickness=0.15
|
||||
SilkTextItalic=0
|
||||
SilkTextUpright=1
|
||||
CopperLineWidth=0.2
|
||||
CopperTextSizeV=1.5
|
||||
CopperTextSizeH=1.5
|
||||
CopperTextThickness=0.3
|
||||
CopperTextItalic=0
|
||||
CopperTextUpright=1
|
||||
EdgeCutLineWidth=0.15
|
||||
CourtyardLineWidth=0.05
|
||||
OthersLineWidth=0.15
|
||||
OthersTextSizeV=1
|
||||
OthersTextSizeH=1
|
||||
OthersTextSizeThickness=0.15
|
||||
OthersTextItalic=0
|
||||
OthersTextUpright=1
|
||||
SolderMaskClearance=0.2
|
||||
SolderMaskMinWidth=0
|
||||
SolderPasteClearance=0
|
||||
SolderPasteRatio=-0
|
||||
[pcbnew/Layer.F.Cu]
|
||||
Name=F.Cu
|
||||
Type=0
|
||||
Enabled=1
|
||||
[pcbnew/Layer.In1.Cu]
|
||||
Name=In1.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In2.Cu]
|
||||
Name=In2.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In3.Cu]
|
||||
Name=In3.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In4.Cu]
|
||||
Name=In4.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In5.Cu]
|
||||
Name=In5.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In6.Cu]
|
||||
Name=In6.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In7.Cu]
|
||||
Name=In7.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In8.Cu]
|
||||
Name=In8.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In9.Cu]
|
||||
Name=In9.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In10.Cu]
|
||||
Name=In10.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In11.Cu]
|
||||
Name=In11.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In12.Cu]
|
||||
Name=In12.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In13.Cu]
|
||||
Name=In13.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In14.Cu]
|
||||
Name=In14.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In15.Cu]
|
||||
Name=In15.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In16.Cu]
|
||||
Name=In16.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In17.Cu]
|
||||
Name=In17.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In18.Cu]
|
||||
Name=In18.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In19.Cu]
|
||||
Name=In19.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In20.Cu]
|
||||
Name=In20.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In21.Cu]
|
||||
Name=In21.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In22.Cu]
|
||||
Name=In22.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In23.Cu]
|
||||
Name=In23.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In24.Cu]
|
||||
Name=In24.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In25.Cu]
|
||||
Name=In25.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In26.Cu]
|
||||
Name=In26.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In27.Cu]
|
||||
Name=In27.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In28.Cu]
|
||||
Name=In28.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In29.Cu]
|
||||
Name=In29.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.In30.Cu]
|
||||
Name=In30.Cu
|
||||
Type=0
|
||||
Enabled=0
|
||||
[pcbnew/Layer.B.Cu]
|
||||
Name=B.Cu
|
||||
Type=0
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Adhes]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Adhes]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Paste]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Paste]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.SilkS]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.SilkS]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Mask]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Mask]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Dwgs.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Cmts.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Eco1.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Eco2.User]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Edge.Cuts]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Margin]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.CrtYd]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.CrtYd]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.B.Fab]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.F.Fab]
|
||||
Enabled=1
|
||||
[pcbnew/Layer.Rescue]
|
||||
Enabled=0
|
||||
[pcbnew/Netclasses]
|
||||
[pcbnew/Netclasses/Default]
|
||||
Name=Default
|
||||
Clearance=0.2
|
||||
TrackWidth=0.25
|
||||
ViaDiameter=0.6
|
||||
ViaDrill=0.4
|
||||
uViaDiameter=0.3
|
||||
uViaDrill=0.1
|
||||
dPairWidth=0.2
|
||||
dPairGap=0.25
|
||||
dPairViaGap=0.25
|
||||
725
hardware/wemos_mbus_shield/kicad/d1_mini_shield.sch
Normal file
725
hardware/wemos_mbus_shield/kicad/d1_mini_shield.sch
Normal file
@@ -0,0 +1,725 @@
|
||||
EESchema Schematic File Version 4
|
||||
EELAYER 30 0
|
||||
EELAYER END
|
||||
$Descr A4 11693 8268
|
||||
encoding utf-8
|
||||
Sheet 1 1
|
||||
Title ""
|
||||
Date ""
|
||||
Rev ""
|
||||
Comp ""
|
||||
Comment1 ""
|
||||
Comment2 ""
|
||||
Comment3 ""
|
||||
Comment4 ""
|
||||
$EndDescr
|
||||
$Comp
|
||||
L d1_mini_shield-rescue:CONN_01X08 P1
|
||||
U 1 1 5763EB78
|
||||
P 8300 1850
|
||||
F 0 "P1" H 8300 2300 50 0000 C CNN
|
||||
F 1 "CONN_01X08" V 8400 1850 50 0000 C CNN
|
||||
F 2 "D1_mini:D1_mini_Pin_Header" H 8300 1850 50 0001 C CNN
|
||||
F 3 "" H 8300 1850 50 0000 C CNN
|
||||
1 8300 1850
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L d1_mini_shield-rescue:CONN_01X08 P2
|
||||
U 1 1 5763EBF2
|
||||
P 9400 1850
|
||||
F 0 "P2" H 9400 2300 50 0000 C CNN
|
||||
F 1 "CONN_01X08" V 9500 1850 50 0000 C CNN
|
||||
F 2 "D1_mini:D1_mini_Pin_Header" H 9400 1850 50 0001 C CNN
|
||||
F 3 "" H 9400 1850 50 0000 C CNN
|
||||
1 9400 1850
|
||||
-1 0 0 -1
|
||||
$EndComp
|
||||
Text Label 8100 1500 2 60 ~ 0
|
||||
+5V
|
||||
Text Label 8100 1600 2 60 ~ 0
|
||||
GND
|
||||
Text Label 8100 1700 2 60 ~ 0
|
||||
D4
|
||||
Text Label 8100 1800 2 60 ~ 0
|
||||
D3
|
||||
Text Label 8100 1900 2 60 ~ 0
|
||||
D2
|
||||
Text Label 8100 2000 2 60 ~ 0
|
||||
D1
|
||||
Text Label 8100 2100 2 60 ~ 0
|
||||
RX
|
||||
Text Label 8100 2200 2 60 ~ 0
|
||||
TX
|
||||
Text Label 9600 1500 0 60 ~ 0
|
||||
+3.3V
|
||||
Text Label 9600 1600 0 60 ~ 0
|
||||
D8
|
||||
Text Label 9600 1700 0 60 ~ 0
|
||||
D7
|
||||
Text Label 9600 1800 0 60 ~ 0
|
||||
D6
|
||||
Text Label 9600 1900 0 60 ~ 0
|
||||
D5
|
||||
Text Label 9600 2000 0 60 ~ 0
|
||||
D0
|
||||
Text Label 9600 2100 0 60 ~ 0
|
||||
A0
|
||||
Text Label 9600 2200 0 60 ~ 0
|
||||
RST
|
||||
Text Notes 8750 1750 2 60 ~ 0
|
||||
GPIO2
|
||||
Text Notes 8750 1850 2 60 ~ 0
|
||||
GPIO0
|
||||
Text Notes 8750 1950 2 60 ~ 0
|
||||
GPIO4
|
||||
Text Notes 8750 2050 2 60 ~ 0
|
||||
GPIO5
|
||||
Text Notes 8900 1650 0 60 ~ 0
|
||||
GPIO15
|
||||
Text Notes 8900 1750 0 60 ~ 0
|
||||
GPIO13
|
||||
Text Notes 8900 1850 0 60 ~ 0
|
||||
GPIO12
|
||||
Text Notes 8900 1950 0 60 ~ 0
|
||||
GPIO14
|
||||
Text Notes 8900 2050 0 60 ~ 0
|
||||
GPIO16
|
||||
Text Notes 7550 1050 0 60 ~ 0
|
||||
D1 Mini Shield
|
||||
$Comp
|
||||
L tss721:TSS721 U1
|
||||
U 1 1 62B826D2
|
||||
P 3650 5550
|
||||
F 0 "U1" H 3650 6621 50 0000 C CNN
|
||||
F 1 "TSS721" H 3650 6530 50 0000 C CNN
|
||||
F 2 "Package_SO:SOIC-16_3.9x9.9mm_P1.27mm" H 3650 4700 50 0001 C CIN
|
||||
F 3 "http://www.ti.com/lit/ds/symlink/tss721a.pdf" H 3650 6439 50 0000 C CNN
|
||||
1 3650 5550
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:+3.3V #PWR0101
|
||||
U 1 1 62B89C7B
|
||||
P 2850 4750
|
||||
F 0 "#PWR0101" H 2850 4600 50 0001 C CNN
|
||||
F 1 "+3.3V" V 2865 4878 50 0000 L CNN
|
||||
F 2 "" H 2850 4750 50 0001 C CNN
|
||||
F 3 "" H 2850 4750 50 0001 C CNN
|
||||
1 2850 4750
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L Connector:RJ45 J1
|
||||
U 1 1 62B8AC34
|
||||
P 1950 1850
|
||||
F 0 "J1" H 2007 2517 50 0000 C CNN
|
||||
F 1 "RJ45" H 2007 2426 50 0000 C CNN
|
||||
F 2 "Connector_RJ:RJ45_Amphenol_54602-x08_Horizontal" V 1950 1875 50 0001 C CNN
|
||||
F 3 "~" V 1950 1875 50 0001 C CNN
|
||||
1 1950 1850
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R4
|
||||
U 1 1 62B8CAA8
|
||||
P 2750 5150
|
||||
F 0 "R4" V 2957 5150 50 0000 C CNN
|
||||
F 1 "220" V 2866 5150 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 2680 5150 50 0001 C CNN
|
||||
F 3 "~" H 2750 5150 50 0001 C CNN
|
||||
1 2750 5150
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R2
|
||||
U 1 1 62B8DB92
|
||||
P 2500 5050
|
||||
F 0 "R2" V 2707 5050 50 0000 C CNN
|
||||
F 1 "220" V 2616 5050 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 2430 5050 50 0001 C CNN
|
||||
F 3 "~" H 2500 5050 50 0001 C CNN
|
||||
1 2500 5050
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:GND #PWR0102
|
||||
U 1 1 62B9F029
|
||||
P 1850 6050
|
||||
F 0 "#PWR0102" H 1850 5800 50 0001 C CNN
|
||||
F 1 "GND" H 1855 5877 50 0000 C CNN
|
||||
F 2 "" H 1850 6050 50 0001 C CNN
|
||||
F 3 "" H 1850 6050 50 0001 C CNN
|
||||
1 1850 6050
|
||||
-1 0 0 1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R3
|
||||
U 1 1 62B9F861
|
||||
P 2500 6300
|
||||
F 0 "R3" H 2570 6346 50 0000 L CNN
|
||||
F 1 "82k" H 2570 6255 50 0000 L CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 2430 6300 50 0001 C CNN
|
||||
F 3 "~" H 2500 6300 50 0001 C CNN
|
||||
1 2500 6300
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R1
|
||||
U 1 1 62B9FC37
|
||||
P 2250 6300
|
||||
F 0 "R1" H 2320 6346 50 0000 L CNN
|
||||
F 1 "470" H 2320 6255 50 0000 L CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 2180 6300 50 0001 C CNN
|
||||
F 3 "~" H 2250 6300 50 0001 C CNN
|
||||
1 2250 6300
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:C C2
|
||||
U 1 1 62BA06BE
|
||||
P 2850 6050
|
||||
F 0 "C2" V 3102 6050 50 0000 C CNN
|
||||
F 1 "1u" V 3011 6050 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" H 2888 5900 50 0001 C CNN
|
||||
F 3 "~" H 2850 6050 50 0001 C CNN
|
||||
1 2850 6050
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:C C1
|
||||
U 1 1 62BA09E7
|
||||
P 1850 6300
|
||||
F 0 "C1" H 1965 6346 50 0000 L CNN
|
||||
F 1 "100n" H 1965 6255 50 0000 L CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" H 1888 6150 50 0001 C CNN
|
||||
F 3 "~" H 1850 6300 50 0001 C CNN
|
||||
1 1850 6300
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
1850 6050 1850 6150
|
||||
Wire Wire Line
|
||||
2250 6050 2250 6150
|
||||
Wire Wire Line
|
||||
2500 6050 2500 6150
|
||||
Connection ~ 1850 6050
|
||||
Connection ~ 2250 6050
|
||||
Wire Wire Line
|
||||
2250 6050 2500 6050
|
||||
Wire Wire Line
|
||||
1850 6050 2250 6050
|
||||
Connection ~ 2500 6050
|
||||
Wire Wire Line
|
||||
3850 6450 3850 6350
|
||||
Wire Wire Line
|
||||
2250 6450 2250 6550
|
||||
Wire Wire Line
|
||||
3950 6550 3950 6350
|
||||
Wire Wire Line
|
||||
1850 6450 1850 6650
|
||||
Wire Wire Line
|
||||
4050 6650 4050 6350
|
||||
Wire Wire Line
|
||||
3050 6050 3000 6050
|
||||
Wire Wire Line
|
||||
2500 6050 2700 6050
|
||||
Wire Wire Line
|
||||
2700 6050 2700 6350
|
||||
Wire Wire Line
|
||||
2700 6350 3650 6350
|
||||
Connection ~ 2700 6050
|
||||
Wire Wire Line
|
||||
3850 6450 2500 6450
|
||||
Wire Wire Line
|
||||
2250 6550 3950 6550
|
||||
Wire Wire Line
|
||||
4050 6650 1850 6650
|
||||
Text GLabel 4350 5350 2 50 Output ~ 0
|
||||
HAN_TX
|
||||
Wire Wire Line
|
||||
4350 5350 4300 5350
|
||||
Wire Wire Line
|
||||
2850 4750 3550 4750
|
||||
$Comp
|
||||
L Device:R R5
|
||||
U 1 1 62BED413
|
||||
P 4650 4550
|
||||
F 0 "R5" V 4857 4550 50 0000 C CNN
|
||||
F 1 "100k" V 4766 4550 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 4580 4550 50 0001 C CNN
|
||||
F 3 "~" H 4650 4550 50 0001 C CNN
|
||||
1 4650 4550
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:C C3
|
||||
U 1 1 62BED9ED
|
||||
P 4650 4900
|
||||
F 0 "C3" V 4902 4900 50 0000 C CNN
|
||||
F 1 "100n" V 4811 4900 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" H 4688 4750 50 0001 C CNN
|
||||
F 3 "~" H 4650 4900 50 0001 C CNN
|
||||
1 4650 4900
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:GND #PWR0103
|
||||
U 1 1 62BF4151
|
||||
P 4800 5050
|
||||
F 0 "#PWR0103" H 4800 4800 50 0001 C CNN
|
||||
F 1 "GND" H 4805 4877 50 0000 C CNN
|
||||
F 2 "" H 4800 5050 50 0001 C CNN
|
||||
F 3 "" H 4800 5050 50 0001 C CNN
|
||||
1 4800 5050
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
4800 4550 4800 4900
|
||||
Wire Wire Line
|
||||
4800 4900 4800 5050
|
||||
Connection ~ 4800 4900
|
||||
Wire Wire Line
|
||||
4500 4900 4500 4550
|
||||
Wire Wire Line
|
||||
4500 4550 3650 4550
|
||||
Wire Wire Line
|
||||
3650 4550 3650 4750
|
||||
Connection ~ 4500 4550
|
||||
Wire Notes Line
|
||||
1500 4050 5050 4050
|
||||
Wire Notes Line
|
||||
5050 4050 5050 6850
|
||||
Wire Notes Line
|
||||
5050 6850 1500 6850
|
||||
Wire Notes Line
|
||||
1500 6850 1500 4050
|
||||
Wire Notes Line
|
||||
7550 950 10200 950
|
||||
Wire Notes Line
|
||||
10200 950 10200 2750
|
||||
Wire Notes Line
|
||||
10200 2750 7550 2750
|
||||
Wire Notes Line
|
||||
7550 2750 7550 950
|
||||
Text GLabel 9800 1700 2 50 Input ~ 0
|
||||
HAN_TX
|
||||
Wire Wire Line
|
||||
9800 1700 9600 1700
|
||||
$Comp
|
||||
L power:+3V3 #PWR0104
|
||||
U 1 1 62C0EBC2
|
||||
P 9850 1500
|
||||
F 0 "#PWR0104" H 9850 1350 50 0001 C CNN
|
||||
F 1 "+3V3" V 9865 1628 50 0000 L CNN
|
||||
F 2 "" H 9850 1500 50 0001 C CNN
|
||||
F 3 "" H 9850 1500 50 0001 C CNN
|
||||
1 9850 1500
|
||||
0 1 1 0
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
9850 1500 9600 1500
|
||||
Text GLabel 9800 1800 2 50 Output ~ 0
|
||||
LED_R
|
||||
Text GLabel 9800 1900 2 50 Output ~ 0
|
||||
LED_G
|
||||
Wire Wire Line
|
||||
9800 1800 9600 1800
|
||||
Wire Wire Line
|
||||
9800 1900 9600 1900
|
||||
$Comp
|
||||
L power:GND #PWR0105
|
||||
U 1 1 62C13452
|
||||
P 7850 1600
|
||||
F 0 "#PWR0105" H 7850 1350 50 0001 C CNN
|
||||
F 1 "GND" V 7855 1472 50 0000 R CNN
|
||||
F 2 "" H 7850 1600 50 0001 C CNN
|
||||
F 3 "" H 7850 1600 50 0001 C CNN
|
||||
1 7850 1600
|
||||
0 1 1 0
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
8100 1600 7850 1600
|
||||
Text GLabel 7950 1800 0 50 Input ~ 0
|
||||
BTN_AP
|
||||
Wire Wire Line
|
||||
8100 1800 7950 1800
|
||||
Text GLabel 7950 1900 0 50 Input ~ 0
|
||||
TEMP
|
||||
Wire Wire Line
|
||||
8100 1900 7950 1900
|
||||
Wire Wire Line
|
||||
3050 5150 2900 5150
|
||||
Wire Wire Line
|
||||
3050 5050 2650 5050
|
||||
Text GLabel 2200 5050 0 50 Input ~ 0
|
||||
MBUS1
|
||||
Text GLabel 2200 5150 0 50 Input ~ 0
|
||||
MBUS2
|
||||
Wire Wire Line
|
||||
2350 5050 2200 5050
|
||||
Wire Wire Line
|
||||
2600 5150 2200 5150
|
||||
Text GLabel 2500 2150 2 50 Output ~ 0
|
||||
MBUS1
|
||||
Text GLabel 2500 2050 2 50 Output ~ 0
|
||||
MBUS2
|
||||
Wire Wire Line
|
||||
2500 2050 2350 2050
|
||||
Wire Wire Line
|
||||
2350 2150 2500 2150
|
||||
$Comp
|
||||
L Device:Jumper JP1
|
||||
U 1 1 62C331C0
|
||||
P 3300 1250
|
||||
F 0 "JP1" H 3300 1514 50 0000 C CNN
|
||||
F 1 "Jumper" H 3300 1423 50 0000 C CNN
|
||||
F 2 "Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm" H 3300 1250 50 0001 C CNN
|
||||
F 3 "~" H 3300 1250 50 0001 C CNN
|
||||
1 3300 1250
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:Jumper JP2
|
||||
U 1 1 62C365CA
|
||||
P 3300 1850
|
||||
F 0 "JP2" H 3300 2114 50 0000 C CNN
|
||||
F 1 "Jumper" H 3300 2023 50 0000 C CNN
|
||||
F 2 "Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm" H 3300 1850 50 0001 C CNN
|
||||
F 3 "~" H 3300 1850 50 0001 C CNN
|
||||
1 3300 1850
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:Jumper JP3
|
||||
U 1 1 62C36C70
|
||||
P 3300 2200
|
||||
F 0 "JP3" H 3300 2464 50 0000 C CNN
|
||||
F 1 "Jumper" H 3300 2373 50 0000 C CNN
|
||||
F 2 "Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm" H 3300 2200 50 0001 C CNN
|
||||
F 3 "~" H 3300 2200 50 0001 C CNN
|
||||
1 3300 2200
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
3000 1850 2900 1850
|
||||
Wire Wire Line
|
||||
2900 1850 2900 1750
|
||||
Wire Wire Line
|
||||
2350 1750 2900 1750
|
||||
Wire Wire Line
|
||||
2350 1850 2850 1850
|
||||
Wire Wire Line
|
||||
2850 1850 2850 2200
|
||||
Wire Wire Line
|
||||
2850 2200 3000 2200
|
||||
$Comp
|
||||
L power:GND #PWR0107
|
||||
U 1 1 62C3D94F
|
||||
P 3750 1850
|
||||
F 0 "#PWR0107" H 3750 1600 50 0001 C CNN
|
||||
F 1 "GND" V 3755 1722 50 0000 R CNN
|
||||
F 2 "" H 3750 1850 50 0001 C CNN
|
||||
F 3 "" H 3750 1850 50 0001 C CNN
|
||||
1 3750 1850
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
Text GLabel 3750 2200 2 50 Output ~ 0
|
||||
TEMP
|
||||
Wire Wire Line
|
||||
3750 1850 3600 1850
|
||||
Wire Wire Line
|
||||
3750 2200 3600 2200
|
||||
Wire Notes Line
|
||||
1450 950 4350 950
|
||||
Wire Notes Line
|
||||
4350 950 4350 2450
|
||||
Wire Notes Line
|
||||
4350 2450 1450 2450
|
||||
Wire Notes Line
|
||||
1450 2450 1450 950
|
||||
$Comp
|
||||
L Device:LED_RGB D1
|
||||
U 1 1 62C43A7B
|
||||
P 9550 4800
|
||||
F 0 "D1" H 9550 5297 50 0000 C CNN
|
||||
F 1 "LED_RGB" H 9550 5206 50 0000 C CNN
|
||||
F 2 "LED_SMD:LED_RGB_PLCC-6" H 9550 4750 50 0001 C CNN
|
||||
F 3 "~" H 9550 4750 50 0001 C CNN
|
||||
1 9550 4800
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:+3.3V #PWR0108
|
||||
U 1 1 62C44A94
|
||||
P 10000 4800
|
||||
F 0 "#PWR0108" H 10000 4650 50 0001 C CNN
|
||||
F 1 "+3.3V" V 10015 4928 50 0000 L CNN
|
||||
F 2 "" H 10000 4800 50 0001 C CNN
|
||||
F 3 "" H 10000 4800 50 0001 C CNN
|
||||
1 10000 4800
|
||||
0 1 1 0
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
9750 4800 9900 4800
|
||||
Wire Wire Line
|
||||
9750 4600 9900 4600
|
||||
Wire Wire Line
|
||||
9900 4600 9900 4800
|
||||
Connection ~ 9900 4800
|
||||
Wire Wire Line
|
||||
9900 4800 10000 4800
|
||||
Wire Wire Line
|
||||
9900 4800 9900 5000
|
||||
Wire Wire Line
|
||||
9900 5000 9750 5000
|
||||
$Comp
|
||||
L Device:R R9
|
||||
U 1 1 62C495F7
|
||||
P 9100 4600
|
||||
F 0 "R9" V 9307 4600 50 0000 C CNN
|
||||
F 1 "1k" V 9216 4600 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 9030 4600 50 0001 C CNN
|
||||
F 3 "~" H 9100 4600 50 0001 C CNN
|
||||
1 9100 4600
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R8
|
||||
U 1 1 62C4BDE9
|
||||
P 8850 4800
|
||||
F 0 "R8" V 9057 4800 50 0000 C CNN
|
||||
F 1 "1k" V 8966 4800 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 8780 4800 50 0001 C CNN
|
||||
F 3 "~" H 8850 4800 50 0001 C CNN
|
||||
1 8850 4800
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R10
|
||||
U 1 1 62C4C336
|
||||
P 9100 5000
|
||||
F 0 "R10" V 9307 5000 50 0000 C CNN
|
||||
F 1 "1k" V 9216 5000 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 9030 5000 50 0001 C CNN
|
||||
F 3 "~" H 9100 5000 50 0001 C CNN
|
||||
1 9100 5000
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
9350 4600 9250 4600
|
||||
Wire Wire Line
|
||||
9350 4800 9000 4800
|
||||
Wire Wire Line
|
||||
9350 5000 9250 5000
|
||||
Text GLabel 8650 4600 0 50 Input ~ 0
|
||||
LED_R
|
||||
Text GLabel 8650 4800 0 50 Input ~ 0
|
||||
LED_G
|
||||
Text GLabel 8650 5000 0 50 Input ~ 0
|
||||
LED_B
|
||||
Wire Wire Line
|
||||
8700 4800 8650 4800
|
||||
Wire Wire Line
|
||||
8950 4600 8650 4600
|
||||
Wire Wire Line
|
||||
8950 5000 8650 5000
|
||||
Wire Notes Line
|
||||
8200 4050 10550 4050
|
||||
Wire Notes Line
|
||||
10550 4050 10550 5400
|
||||
Wire Notes Line
|
||||
10550 5400 8200 5400
|
||||
Wire Notes Line
|
||||
8200 5400 8200 4050
|
||||
$Comp
|
||||
L Sensor_Temperature:DS18B20 U2
|
||||
U 1 1 62C6296F
|
||||
P 6600 4900
|
||||
F 0 "U2" H 6370 4946 50 0000 R CNN
|
||||
F 1 "DS18B20" H 6370 4855 50 0000 R CNN
|
||||
F 2 "Package_TO_SOT_THT:TO-92_Inline" H 5600 4650 50 0001 C CNN
|
||||
F 3 "http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf" H 6450 5150 50 0001 C CNN
|
||||
1 6600 4900
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:GND #PWR0109
|
||||
U 1 1 62C6373E
|
||||
P 6600 5350
|
||||
F 0 "#PWR0109" H 6600 5100 50 0001 C CNN
|
||||
F 1 "GND" H 6605 5177 50 0000 C CNN
|
||||
F 2 "" H 6600 5350 50 0001 C CNN
|
||||
F 3 "" H 6600 5350 50 0001 C CNN
|
||||
1 6600 5350
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:+3.3V #PWR0110
|
||||
U 1 1 62C63C02
|
||||
P 6600 4450
|
||||
F 0 "#PWR0110" H 6600 4300 50 0001 C CNN
|
||||
F 1 "+3.3V" H 6615 4623 50 0000 C CNN
|
||||
F 2 "" H 6600 4450 50 0001 C CNN
|
||||
F 3 "" H 6600 4450 50 0001 C CNN
|
||||
1 6600 4450
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R7
|
||||
U 1 1 62C6405D
|
||||
P 6900 4600
|
||||
F 0 "R7" H 6970 4646 50 0000 L CNN
|
||||
F 1 "4k7" H 6970 4555 50 0000 L CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 6830 4600 50 0001 C CNN
|
||||
F 3 "~" H 6900 4600 50 0001 C CNN
|
||||
1 6900 4600
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
6600 5200 6600 5350
|
||||
Wire Wire Line
|
||||
6600 4600 6600 4450
|
||||
Wire Wire Line
|
||||
6900 4900 6900 4750
|
||||
Wire Wire Line
|
||||
6900 4450 6600 4450
|
||||
Connection ~ 6600 4450
|
||||
Text GLabel 7050 4900 2 50 Output ~ 0
|
||||
TEMP
|
||||
Wire Wire Line
|
||||
7050 4900 6900 4900
|
||||
Connection ~ 6900 4900
|
||||
Wire Notes Line
|
||||
5750 4050 7500 4050
|
||||
Wire Notes Line
|
||||
7500 4050 7500 5750
|
||||
Wire Notes Line
|
||||
7500 5750 5750 5750
|
||||
Wire Notes Line
|
||||
5750 5750 5750 4050
|
||||
$Comp
|
||||
L Switch:SW_Push SW1
|
||||
U 1 1 62C73229
|
||||
P 5950 1400
|
||||
F 0 "SW1" H 5950 1685 50 0000 C CNN
|
||||
F 1 "SW_Push" H 5950 1594 50 0000 C CNN
|
||||
F 2 "Button_Switch_SMD:SW_SPST_B3U-3000P" H 5950 1600 50 0001 C CNN
|
||||
F 3 "~" H 5950 1600 50 0001 C CNN
|
||||
1 5950 1400
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:R R6
|
||||
U 1 1 62C73CE8
|
||||
P 5950 1700
|
||||
F 0 "R6" V 6157 1700 50 0000 C CNN
|
||||
F 1 "10k" V 6066 1700 50 0000 C CNN
|
||||
F 2 "Resistor_SMD:R_0805_2012Metric_Pad1.15x1.40mm_HandSolder" V 5880 1700 50 0001 C CNN
|
||||
F 3 "~" H 5950 1700 50 0001 C CNN
|
||||
1 5950 1700
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:GND #PWR0111
|
||||
U 1 1 62C76961
|
||||
P 6350 1400
|
||||
F 0 "#PWR0111" H 6350 1150 50 0001 C CNN
|
||||
F 1 "GND" V 6355 1272 50 0000 R CNN
|
||||
F 2 "" H 6350 1400 50 0001 C CNN
|
||||
F 3 "" H 6350 1400 50 0001 C CNN
|
||||
1 6350 1400
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L power:+3.3V #PWR0112
|
||||
U 1 1 62C776AD
|
||||
P 6350 1700
|
||||
F 0 "#PWR0112" H 6350 1550 50 0001 C CNN
|
||||
F 1 "+3.3V" V 6365 1828 50 0000 L CNN
|
||||
F 2 "" H 6350 1700 50 0001 C CNN
|
||||
F 3 "" H 6350 1700 50 0001 C CNN
|
||||
1 6350 1700
|
||||
0 1 1 0
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
6350 1400 6150 1400
|
||||
Wire Wire Line
|
||||
6350 1700 6100 1700
|
||||
Wire Wire Line
|
||||
5750 1400 5550 1400
|
||||
Wire Wire Line
|
||||
5550 1400 5550 1550
|
||||
Wire Wire Line
|
||||
5550 1700 5800 1700
|
||||
Text GLabel 5450 1550 0 50 Output ~ 0
|
||||
BTN_AP
|
||||
Wire Wire Line
|
||||
5450 1550 5550 1550
|
||||
Connection ~ 5550 1550
|
||||
Wire Wire Line
|
||||
5550 1550 5550 1700
|
||||
Wire Notes Line
|
||||
5000 950 6850 950
|
||||
Wire Notes Line
|
||||
6850 950 6850 1950
|
||||
Wire Notes Line
|
||||
6850 1950 5000 1950
|
||||
Wire Notes Line
|
||||
5000 1950 5000 950
|
||||
Text GLabel 4350 5450 2 50 Output ~ 0
|
||||
LED_B
|
||||
Wire Wire Line
|
||||
4350 5450 4300 5450
|
||||
Wire Wire Line
|
||||
4300 5450 4300 5350
|
||||
Connection ~ 4300 5350
|
||||
Wire Wire Line
|
||||
4300 5350 4250 5350
|
||||
$Comp
|
||||
L power:+5V #PWR0106
|
||||
U 1 1 62D05402
|
||||
P 7850 1500
|
||||
F 0 "#PWR0106" H 7850 1350 50 0001 C CNN
|
||||
F 1 "+5V" V 7865 1628 50 0000 L CNN
|
||||
F 2 "" H 7850 1500 50 0001 C CNN
|
||||
F 3 "" H 7850 1500 50 0001 C CNN
|
||||
1 7850 1500
|
||||
0 -1 -1 0
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
8100 1500 7850 1500
|
||||
$Comp
|
||||
L power:+5V #PWR0113
|
||||
U 1 1 62D0B1E1
|
||||
P 3750 1250
|
||||
F 0 "#PWR0113" H 3750 1100 50 0001 C CNN
|
||||
F 1 "+5V" V 3765 1378 50 0000 L CNN
|
||||
F 2 "" H 3750 1250 50 0001 C CNN
|
||||
F 3 "" H 3750 1250 50 0001 C CNN
|
||||
1 3750 1250
|
||||
0 1 1 0
|
||||
$EndComp
|
||||
$Comp
|
||||
L Device:Jumper JP4
|
||||
U 1 1 62D1E97A
|
||||
P 3300 1550
|
||||
F 0 "JP4" H 3300 1814 50 0000 C CNN
|
||||
F 1 "Jumper" H 3300 1723 50 0000 C CNN
|
||||
F 2 "Jumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mm" H 3300 1550 50 0001 C CNN
|
||||
F 3 "~" H 3300 1550 50 0001 C CNN
|
||||
1 3300 1550
|
||||
1 0 0 -1
|
||||
$EndComp
|
||||
Wire Wire Line
|
||||
3000 1250 2350 1250
|
||||
Wire Wire Line
|
||||
2350 1250 2350 1450
|
||||
Wire Wire Line
|
||||
3600 1250 3750 1250
|
||||
Wire Wire Line
|
||||
3600 1550 3600 1850
|
||||
Connection ~ 3600 1850
|
||||
Wire Wire Line
|
||||
2350 1550 3000 1550
|
||||
$EndSCHEMATC
|
||||
75272
hardware/wemos_mbus_shield/kicad/fp-info-cache
Normal file
75272
hardware/wemos_mbus_shield/kicad/fp-info-cache
Normal file
File diff suppressed because it is too large
Load Diff
3
hardware/wemos_mbus_shield/kicad/fp-lib-table
Normal file
3
hardware/wemos_mbus_shield/kicad/fp-lib-table
Normal file
@@ -0,0 +1,3 @@
|
||||
(fp_lib_table
|
||||
(lib (name D1_mini)(type KiCad)(uri "$(KIPRJMOD)/lib/D1_mini.pretty")(options "")(descr ""))
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
(module Pin_Headers:Pin_Header_Straight_1x08 (layer F.Cu) (tedit 5766A5B6)
|
||||
(descr "Through hole pin header")
|
||||
(tags "pin header")
|
||||
(fp_text reference P1 (at 0.11176 -2.43586) (layer F.SilkS)
|
||||
(effects (font (size 1 1) (thickness 0.15)))
|
||||
)
|
||||
(fp_text value CONN_01X08 (at 0 -3.1) (layer F.Fab) hide
|
||||
(effects (font (size 1 1) (thickness 0.15)))
|
||||
)
|
||||
(fp_line (start -1.75 -1.75) (end -1.75 19.55) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start 1.75 -1.75) (end 1.75 19.55) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start -1.75 -1.75) (end 1.75 -1.75) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start -1.75 19.55) (end 1.75 19.55) (layer F.CrtYd) (width 0.05))
|
||||
(pad 1 thru_hole rect (at 0 0) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(pad 2 thru_hole oval (at 0 2.54) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(pad 3 thru_hole oval (at 0 5.08) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(pad 4 thru_hole oval (at 0 7.62) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(pad 5 thru_hole oval (at 0 10.16) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(pad 6 thru_hole oval (at 0 12.7) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(pad 7 thru_hole oval (at 0 15.24) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(pad 8 thru_hole oval (at 0 17.78) (size 1.8 1.8) (drill 1.016) (layers *.Cu *.Mask F.SilkS))
|
||||
(model Pin_Headers.3dshapes/Pin_Header_Straight_1x08.wrl
|
||||
(at (xyz 0 -0.35 0))
|
||||
(scale (xyz 1 1 1))
|
||||
(rotate (xyz 0 0 90))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
(module OSHW (layer F.Cu) (tedit 5766B04E)
|
||||
(descr OSHW)
|
||||
(tags "Symbol, OSHW-Logo, Silk Screen,")
|
||||
(fp_text reference "" (at 0.09906 -4.38912) (layer F.SilkS) hide
|
||||
(effects (font (size 1 1) (thickness 0.15)))
|
||||
)
|
||||
(fp_text value OSHW (at 0 5) (layer F.Fab) hide
|
||||
(effects (font (size 1 1) (thickness 0.15)))
|
||||
)
|
||||
(fp_line (start -1.78054 0.92964) (end -2.03962 1.49098) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -2.03962 1.49098) (end -1.50114 2.00914) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -1.50114 2.00914) (end -0.98044 1.7399) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.98044 1.7399) (end -0.70104 1.89992) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.73914 1.8796) (end 1.06934 1.6891) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.06934 1.6891) (end 1.50876 2.0193) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.50876 2.0193) (end 1.9812 1.52908) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.9812 1.52908) (end 1.69926 1.04902) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.69926 1.04902) (end 1.88976 0.57912) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.88976 0.57912) (end 2.49936 0.39116) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 2.49936 0.39116) (end 2.49936 -0.28956) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 2.49936 -0.28956) (end 1.94056 -0.42926) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.94056 -0.42926) (end 1.7399 -1.00076) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.7399 -1.00076) (end 2.00914 -1.47066) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 2.00914 -1.47066) (end 1.53924 -1.9812) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.53924 -1.9812) (end 1.02108 -1.71958) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.02108 -1.71958) (end 0.55118 -1.92024) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.55118 -1.92024) (end 0.381 -2.46126) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.381 -2.46126) (end -0.30988 -2.47904) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.30988 -2.47904) (end -0.5207 -1.9304) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.5207 -1.9304) (end -0.9398 -1.76022) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.9398 -1.76022) (end -1.49098 -2.02946) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -1.49098 -2.02946) (end -2.00914 -1.50114) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -2.00914 -1.50114) (end -1.76022 -0.96012) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -1.76022 -0.96012) (end -1.9304 -0.48006) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -1.9304 -0.48006) (end -2.47904 -0.381) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -2.47904 -0.381) (end -2.4892 0.32004) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -2.4892 0.32004) (end -1.9304 0.5207) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -1.9304 0.5207) (end -1.7907 0.91948) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.35052 0.89916) (end 0.65024 0.7493) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.65024 0.7493) (end 0.8509 0.55118) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.8509 0.55118) (end 1.00076 0.14986) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.00076 0.14986) (end 1.00076 -0.24892) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 1.00076 -0.24892) (end 0.8509 -0.59944) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.8509 -0.59944) (end 0.39878 -0.94996) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.39878 -0.94996) (end -0.0508 -1.00076) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.0508 -1.00076) (end -0.44958 -0.89916) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.44958 -0.89916) (end -0.8509 -0.55118) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.8509 -0.55118) (end -1.00076 -0.09906) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -1.00076 -0.09906) (end -0.94996 0.39878) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.94996 0.39878) (end -0.70104 0.70104) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.70104 0.70104) (end -0.35052 0.89916) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start -0.35052 0.89916) (end -0.70104 1.89992) (layer F.SilkS) (width 0.15))
|
||||
(fp_line (start 0.35052 0.89916) (end 0.7493 1.89992) (layer F.SilkS) (width 0.15))
|
||||
)
|
||||
BIN
hardware/wemos_mbus_shield/kicad/meta/brd.png
Normal file
BIN
hardware/wemos_mbus_shield/kicad/meta/brd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
BIN
hardware/wemos_mbus_shield/kicad/meta/icon.png
Normal file
BIN
hardware/wemos_mbus_shield/kicad/meta/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
21
hardware/wemos_mbus_shield/kicad/meta/info.html
Normal file
21
hardware/wemos_mbus_shield/kicad/meta/info.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<title>D1Shield</title>
|
||||
</HEAD>
|
||||
<BODY>
|
||||
<P>This project template is the basis of an expansion board for the
|
||||
<A HREF="https://www.wemos.cc/en/latest/d1/d1_mini.html" TARGET="blank">D1 mini board</A>.
|
||||
<BR><BR>This base project includes a PCB edge defined
|
||||
as the same size as the D1 mini PCB with the connectors placed
|
||||
correctly to align the two boards. All IO present on the D1 mini
|
||||
board is connected to the project through the expansion
|
||||
headers. <BR><BR>The board looks like the following:
|
||||
</P>
|
||||
<P><IMG SRC="brd.png">
|
||||
</P>
|
||||
<p><i>Dwgs.User</i> layer show mechanical components of the D1mini board.</p>
|
||||
<P>©2016 Jérôme Labidurie<br/>
|
||||
Licence: <a href="https://creativecommons.org/licenses/by-sa/3.0/">CC:BY-SA 3.0</a><br/>
|
||||
<a href="http://dinask.eu">dinask.eu</a></P>
|
||||
</BODY>
|
||||
</HTML>
|
||||
4
hardware/wemos_mbus_shield/kicad/sym-lib-table
Normal file
4
hardware/wemos_mbus_shield/kicad/sym-lib-table
Normal file
@@ -0,0 +1,4 @@
|
||||
(sym_lib_table
|
||||
(lib (name d1_mini_shield-rescue)(type Legacy)(uri ${KIPRJMOD}/d1_mini_shield-rescue.lib)(options "")(descr ""))
|
||||
(lib (name tss721)(type Legacy)(uri ${KIPRJMOD}/tss721.lib)(options "")(descr ""))
|
||||
)
|
||||
9
hardware/wemos_mbus_shield/kicad/tss721.dcm
Normal file
9
hardware/wemos_mbus_shield/kicad/tss721.dcm
Normal file
@@ -0,0 +1,9 @@
|
||||
EESchema-DOCLIB Version 2.0
|
||||
#
|
||||
$CMP TSS721
|
||||
D TSS721A Meter Bus Transceiver, SOIC-16/TSSOP-16
|
||||
K MBUS Tranciever
|
||||
F http://www.ti.com/lit/ds/symlink/tss721a.pdf
|
||||
$ENDCMP
|
||||
#
|
||||
#End Doc Library
|
||||
37
hardware/wemos_mbus_shield/kicad/tss721.lib
Normal file
37
hardware/wemos_mbus_shield/kicad/tss721.lib
Normal file
@@ -0,0 +1,37 @@
|
||||
EESchema-LIBRARY Version 2.4
|
||||
#encoding utf-8
|
||||
#
|
||||
# TSS721
|
||||
#
|
||||
DEF TSS721 U 0 40 Y Y 1 F N
|
||||
F0 "U" 200 850 50 H V L CNN
|
||||
F1 "TSS721" 200 750 50 H V L CNN
|
||||
F2 "" 0 -850 50 H V C CIN
|
||||
F3 "" -200 -800 50 H V C CNN
|
||||
ALIAS TSS721A
|
||||
$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
|
||||
#
|
||||
#End Library
|
||||
@@ -17,22 +17,26 @@ extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
# Sticking to v2.0.3 because of #298
|
||||
|
||||
[env:esp32]
|
||||
platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3/platform-espressif32-2.0.3.zip
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
board_build.f_cpu = 160000000L
|
||||
build_flags = -D WEBSOCKET_DISABLED=1
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||
lib_deps = ${common.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
# Tasmota has pre-built platform for C3, S2, S3 and Solo, more information at:
|
||||
# https://github.com/Jason2866/esp32-arduino-lib-builder
|
||||
|
||||
[env:esp32s2]
|
||||
platform = https://github.com/tasmota/platform-espressif32.git#v2.0.3
|
||||
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.2
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3/platform-espressif32-2.0.3.zip
|
||||
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.3
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
board_build.mcu = esp32s2
|
||||
@@ -46,3 +50,15 @@ lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
[env:esp32solo]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/v.2.0.3/platform-espressif32-solo1-v.2.0.3.zip
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
board_build.f_cpu = 160000000L
|
||||
build_flags = -D WEBSOCKET_DISABLED=1 -fexceptions
|
||||
lib_deps = ${common.lib_deps}
|
||||
lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
scripts/makeweb.py
|
||||
|
||||
@@ -19,6 +19,6 @@ hf = """
|
||||
#define VERSION "{}"
|
||||
#endif
|
||||
#define BUILD_EPOCH {}
|
||||
""".format(version, time())
|
||||
""".format(version, round(time()))
|
||||
with open(FILENAME_VERSION_H, 'w+') as f:
|
||||
f.write(hf)
|
||||
|
||||
40
scripts/esp32solo/flash.sh
Executable file
40
scripts/esp32solo/flash.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$1" ];then
|
||||
echo "Usage: "
|
||||
echo " Flashing first time : $0 flash /dev/ttyUSB0"
|
||||
echo " When upgrading to new version : $0 upgrade /dev/ttyUSB0"
|
||||
echo " NOTE: Replace /dev/ttyUSB0 with correct port"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$2" ];then
|
||||
echo "Please specify port"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
esptool=`which esptool`
|
||||
if [ -z "$esptool" ];then
|
||||
esptool=`which esptool.py`
|
||||
fi
|
||||
if [ -z "$esptool" ];then
|
||||
if [ -f esptool.py ];then
|
||||
esptool="esptool.py"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$esptool" ];then
|
||||
echo "esptool.py not available to run following command: "
|
||||
esptool="echo esptool.py"
|
||||
fi
|
||||
|
||||
if [ "$1" = "flash" ];then
|
||||
$esptool --chip esp32 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect \
|
||||
0x1000 bootloader_dio_40m.bin \
|
||||
0x8000 partitions.bin \
|
||||
0xe000 boot_app0.bin \
|
||||
0x10000 firmware.bin
|
||||
exit $?
|
||||
elif [ "$1" = "upgrade" ];then
|
||||
$esptool --chip esp32 --port $2 --baud 460800 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x10000 firmware.bin
|
||||
exit $?
|
||||
fi
|
||||
14
scripts/esp32solo/mkzip.sh
Executable file
14
scripts/esp32solo/mkzip.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
env="esp32solo"
|
||||
build_dir=".pio/build/$env/"
|
||||
|
||||
if [ ! -d $build_dir ];then
|
||||
echo "No build directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/sdk/$env/bin/bootloader_dio_40m.bin $build_dir
|
||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin $build_dir
|
||||
chmod +x scripts/$env/flash.sh
|
||||
zip -j $env.zip $build_dir/*.bin scripts/$env/flash.sh
|
||||
@@ -63,8 +63,10 @@ void AmsConfiguration::clearWifi(WiFiConfig& config) {
|
||||
uint16_t chipId;
|
||||
#if defined(ESP32)
|
||||
chipId = ESP.getEfuseMac();
|
||||
config.power = 195;
|
||||
#else
|
||||
chipId = ESP.getChipId();
|
||||
config.power = 205;
|
||||
#endif
|
||||
strcpy(config.hostname, (String("ams-") + String(chipId, HEX)).c_str());
|
||||
config.mdns = true;
|
||||
@@ -515,6 +517,7 @@ bool AmsConfiguration::getEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config) {
|
||||
if(config.hours > 5) config.hours = 5;
|
||||
EnergyAccountingConfig existing;
|
||||
if(getEnergyAccountingConfig(existing)) {
|
||||
for(int i = 0; i < 9; i++) {
|
||||
@@ -523,6 +526,7 @@ bool AmsConfiguration::setEnergyAccountingConfig(EnergyAccountingConfig& config)
|
||||
}
|
||||
}
|
||||
config.thresholds[9] = 255;
|
||||
energyAccountingChanged |= config.hours != existing.hours;
|
||||
} else {
|
||||
energyAccountingChanged = true;
|
||||
}
|
||||
@@ -657,6 +661,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 94:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig94()) {
|
||||
configVersion = 95;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -731,7 +743,7 @@ bool AmsConfiguration::relocateConfig86() {
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig87() {
|
||||
MeterConfig87 meter87;
|
||||
MeterConfig87 meter87 = {0,0,0,0,0,0,0};
|
||||
MeterConfig meter;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START_87, meter87);
|
||||
@@ -822,6 +834,18 @@ bool AmsConfiguration::relocateConfig93() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig94() {
|
||||
EnergyAccountingConfig eac;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
eac.hours = 1;
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 95);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 94 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 95 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
@@ -99,7 +99,9 @@ struct MeterConfig {
|
||||
uint16_t voltageMultiplier;
|
||||
uint16_t amperageMultiplier;
|
||||
uint16_t accumulatedMultiplier;
|
||||
}; // 49
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 50
|
||||
|
||||
struct MeterConfig87 {
|
||||
uint8_t type;
|
||||
@@ -161,7 +163,8 @@ struct EntsoeConfig {
|
||||
|
||||
struct EnergyAccountingConfig {
|
||||
uint8_t thresholds[10];
|
||||
}; // 10
|
||||
uint8_t hours;
|
||||
}; // 11
|
||||
|
||||
struct TempSensorConfig {
|
||||
uint8_t address[8];
|
||||
@@ -264,7 +267,8 @@ private:
|
||||
bool relocateConfig90(); // 2.0.0
|
||||
bool relocateConfig91(); // 2.0.2
|
||||
bool relocateConfig92(); // 2.0.3
|
||||
bool relocateConfig93(); // 2.1 beta
|
||||
bool relocateConfig93(); // 2.1.0
|
||||
bool relocateConfig94(); // 2.1.4
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
|
||||
@@ -45,6 +45,12 @@ void AmsData::apply(AmsData& other) {
|
||||
this->l1PowerFactor = other.getL1PowerFactor();
|
||||
this->l2PowerFactor = other.getL2PowerFactor();
|
||||
this->l3PowerFactor = other.getL3PowerFactor();
|
||||
this->l1activeImportPower = other.getL1ActiveImportPower();
|
||||
this->l2activeImportPower = other.getL2ActiveImportPower();
|
||||
this->l3activeImportPower = other.getL3ActiveImportPower();
|
||||
this->l1activeExportPower = other.getL1ActiveExportPower();
|
||||
this->l2activeExportPower = other.getL2ActiveExportPower();
|
||||
this->l3activeExportPower = other.getL3ActiveExportPower();
|
||||
case 3:
|
||||
this->meterTimestamp = other.getMeterTimestamp();
|
||||
this->activeImportCounter = other.getActiveImportCounter();
|
||||
@@ -161,6 +167,30 @@ float AmsData::getL3PowerFactor() {
|
||||
return this->l3PowerFactor;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveImportPower() {
|
||||
return this->l1activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveImportPower() {
|
||||
return this->l2activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveImportPower() {
|
||||
return this->l3activeImportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL1ActiveExportPower() {
|
||||
return this->l1activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL2ActiveExportPower() {
|
||||
return this->l2activeExportPower;
|
||||
}
|
||||
|
||||
float AmsData::getL3ActiveExportPower() {
|
||||
return this->l3activeExportPower;
|
||||
}
|
||||
|
||||
double AmsData::getActiveImportCounter() {
|
||||
return this->activeImportCounter;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ enum AmsType {
|
||||
AmsTypeIskra = 0x08,
|
||||
AmsTypeLandis = 0x09,
|
||||
AmsTypeSagemcom = 0x0A,
|
||||
AmsTypeLng = 0x0B,
|
||||
AmsTypeCustom = 0x88,
|
||||
AmsTypeUnknown = 0xFF
|
||||
};
|
||||
@@ -53,6 +54,14 @@ public:
|
||||
float getL2PowerFactor();
|
||||
float getL3PowerFactor();
|
||||
|
||||
float getL1ActiveImportPower();
|
||||
float getL2ActiveImportPower();
|
||||
float getL3ActiveImportPower();
|
||||
|
||||
float getL1ActiveExportPower();
|
||||
float getL2ActiveExportPower();
|
||||
float getL3ActiveExportPower();
|
||||
|
||||
double getActiveImportCounter();
|
||||
double getReactiveImportCounter();
|
||||
double getActiveExportCounter();
|
||||
@@ -70,6 +79,8 @@ protected:
|
||||
time_t meterTimestamp = 0;
|
||||
uint16_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
|
||||
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
|
||||
float l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
|
||||
float l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
|
||||
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
|
||||
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
|
||||
bool threePhase = false, twoPhase = false, counterEstimated = false;
|
||||
|
||||
@@ -383,17 +383,17 @@ bool AmsDataStorage::isDayHappy() {
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < day.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Day %lld < %lld\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld < %lld\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
if(now-day.lastMeterReadTime > 3600) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Day %lld - %lld > 3600\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld - %lld > 3600\n", (int64_t) now, (int64_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(day.lastMeterReadTime), last);
|
||||
if(tm.Hour > last.Hour) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Day %d > %d\n", tm.Hour, last.Hour);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %d > %d\n", tm.Hour, last.Hour);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -411,17 +411,17 @@ bool AmsDataStorage::isMonthHappy() {
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < month.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Month %lld < %lld\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld < %lld\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
if(now-month.lastMeterReadTime > 86400) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Month %lld - %lld > 3600\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld - %lld > 3600\n", (int64_t) now, (int64_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
if(tm.Day > last.Day) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(AmsDataStorage) Month %d > %d\n", tm.Day, last.Day);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %d > %d\n", tm.Day, last.Day);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#ifndef _AMSTOMQTTBRIDGE_H
|
||||
#define _AMSTOMQTTBRIDGE_H
|
||||
|
||||
#define WIFI_CONNECTION_TIMEOUT 30000;
|
||||
#define WIFI_CONNECTION_TIMEOUT 30000
|
||||
|
||||
#define INVALID_BUTTON_PIN 0xFFFFFFFF
|
||||
|
||||
#define MAX_PEM_SIZE 4096
|
||||
|
||||
#define METER_SOURCE_NONE 0
|
||||
#define METER_SOURCE_SERIAL 1
|
||||
#define METER_SOURCE_MQTT 2
|
||||
#define METER_SOURCE_ESPNOW 3
|
||||
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
|
||||
@@ -32,6 +32,10 @@ ADC_MODE(ADC_VCC);
|
||||
#endif
|
||||
#define WDT_TIMEOUT 60
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
#include <driver/uart.h>
|
||||
#endif
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include "AmsToMqttBridge.h"
|
||||
@@ -61,12 +65,13 @@ ADC_MODE(ADC_VCC);
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
#define BUF_SIZE_COMMON (2048)
|
||||
#define BUF_SIZE_HAN (1024)
|
||||
#include "ams/hdlc.h"
|
||||
#include "MbusAssembler.h"
|
||||
#define BUF_SIZE_HAN (1280)
|
||||
|
||||
#include "IEC6205621.h"
|
||||
#include "IEC6205675.h"
|
||||
#include "LNG.h"
|
||||
|
||||
#include "ams/DataParsers.h"
|
||||
|
||||
uint8_t commonBuffer[BUF_SIZE_COMMON];
|
||||
uint8_t hanBuffer[BUF_SIZE_HAN];
|
||||
@@ -86,13 +91,12 @@ Timezone* tz;
|
||||
AmsWebServer ws(commonBuffer, &Debug, &hw);
|
||||
|
||||
MQTTClient *mqtt = NULL;
|
||||
WiFiClient *mqttClient = new WiFiClient();
|
||||
WiFiClient *mqttClient = NULL;
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
AmsMqttHandler* mqttHandler = NULL;
|
||||
|
||||
Stream *hanSerial;
|
||||
SoftwareSerial *swSerial = NULL;
|
||||
HDLCConfig* hc = NULL;
|
||||
|
||||
GpioConfig gpioConfig;
|
||||
MeterConfig meterConfig;
|
||||
@@ -106,8 +110,15 @@ EnergyAccounting ea(&Debug);
|
||||
|
||||
uint8_t wifiReconnectCount = 0;
|
||||
|
||||
HDLCParser *hdlcParser = NULL;
|
||||
MBUSParser *mbusParser = NULL;
|
||||
GBTParser *gbtParser = NULL;
|
||||
GCMParser *gcmParser = NULL;
|
||||
LLCParser *llcParser = NULL;
|
||||
DLMSParser *dlmsParser = NULL;
|
||||
DSMRParser *dsmrParser = NULL;
|
||||
|
||||
void setup() {
|
||||
WiFiConfig wifi;
|
||||
Serial.begin(115200);
|
||||
|
||||
if(!config.getGpioConfig(gpioConfig)) {
|
||||
@@ -141,6 +152,8 @@ void setup() {
|
||||
gpioConfig.hanPin = 3;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
gpioConfig.hanPin = 18;
|
||||
#elif defined(ESP32)
|
||||
gpioConfig.hanPin = 16;
|
||||
gpioConfig.ledPin = 2;
|
||||
@@ -148,6 +161,7 @@ void setup() {
|
||||
gpioConfig.tempSensorPin = 14;
|
||||
#endif
|
||||
}
|
||||
|
||||
delay(1);
|
||||
config.loadTempSensors();
|
||||
hw.setup(&gpioConfig, &config);
|
||||
@@ -231,7 +245,6 @@ void setup() {
|
||||
}
|
||||
|
||||
Debug.setSerialEnabled(true);
|
||||
DebugConfig debug;
|
||||
delay(1);
|
||||
|
||||
float vcc = hw.getVcc();
|
||||
@@ -370,7 +383,8 @@ void setup() {
|
||||
config.setEnergyAccountingConfig(*eac);
|
||||
config.ackEnergyAccountingChange();
|
||||
}
|
||||
ea.setup(&ds, eapi, eac);
|
||||
ea.setup(&ds, eac);
|
||||
ea.setEapi(eapi);
|
||||
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds, &ea);
|
||||
|
||||
#if defined(ESP32)
|
||||
@@ -420,6 +434,10 @@ void loop() {
|
||||
}
|
||||
}
|
||||
|
||||
if(now > 10000 && now - lastErrorBlink > 3000) {
|
||||
errorBlink();
|
||||
}
|
||||
|
||||
// Only do normal stuff if we're not booted as AP
|
||||
if (WiFi.getMode() != WIFI_AP) {
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
@@ -490,13 +508,10 @@ void loop() {
|
||||
MDNS.update();
|
||||
#endif
|
||||
|
||||
if(now > 10000 && now - lastErrorBlink > 3000) {
|
||||
errorBlink();
|
||||
}
|
||||
|
||||
if (mqttEnabled || config.isMqttChanged()) {
|
||||
if(mqtt == NULL || !mqtt->connected() || config.isMqttChanged()) {
|
||||
MQTT_connect();
|
||||
config.ackMqttChange();
|
||||
}
|
||||
} else if(mqtt != NULL && mqtt->connected()) {
|
||||
mqttClient->stop();
|
||||
@@ -516,6 +531,7 @@ void loop() {
|
||||
if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) {
|
||||
if(eapi == NULL) {
|
||||
eapi = new EntsoeApi(&Debug);
|
||||
ea.setEapi(eapi);
|
||||
ws.setEntsoeApi(eapi);
|
||||
}
|
||||
eapi->setup(entsoe);
|
||||
@@ -553,14 +569,14 @@ void loop() {
|
||||
config.getMeterConfig(meterConfig);
|
||||
setupHanPort(gpioConfig.hanPin, meterConfig.baud, meterConfig.parity, meterConfig.invert);
|
||||
config.ackMeterChanged();
|
||||
delete hc;
|
||||
hc = NULL;
|
||||
delete gcmParser;
|
||||
gcmParser = NULL;
|
||||
}
|
||||
|
||||
if(config.isEnergyAccountingChanged()) {
|
||||
EnergyAccountingConfig *eac = ea.getConfig();
|
||||
config.getEnergyAccountingConfig(*eac);
|
||||
ea.setup(&ds, eapi, eac);
|
||||
ea.setup(&ds, eac);
|
||||
config.ackEnergyAccountingChange();
|
||||
}
|
||||
|
||||
@@ -573,11 +589,11 @@ void loop() {
|
||||
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
|
||||
mqttHandler->publishTemperatures(&config, &hw);
|
||||
}
|
||||
debugD("Used %d ms to update temperature", millis()-start);
|
||||
debugD("Used %ld ms to update temperature", millis()-start);
|
||||
}
|
||||
if(now - lastSysupdate > 10000) {
|
||||
if(mqtt != NULL && mqttHandler != NULL && WiFi.getMode() != WIFI_AP && WiFi.status() == WL_CONNECTED && mqtt->connected() && !topic.isEmpty()) {
|
||||
mqttHandler->publishSystem(&hw);
|
||||
mqttHandler->publishSystem(&hw, eapi, &ea);
|
||||
}
|
||||
lastSysupdate = now;
|
||||
}
|
||||
@@ -607,6 +623,7 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
hwSerial = &Serial2;
|
||||
}
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
hwSerial = &Serial1;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
#endif
|
||||
#endif
|
||||
@@ -639,7 +656,11 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
hwSerial->setRxBufferSize(768);
|
||||
uart_set_pin(UART_NUM_1, UART_PIN_NO_CHANGE, pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
#elif defined(ESP32)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
hwSerial->setRxBufferSize(768);
|
||||
#else
|
||||
@@ -702,22 +723,25 @@ void errorBlink() {
|
||||
if(lastError == 3)
|
||||
lastError = 0;
|
||||
lastErrorBlink = millis();
|
||||
for(;lastError < 3;lastError++) {
|
||||
switch(lastError) {
|
||||
while(lastError < 3) {
|
||||
switch(lastError++) {
|
||||
case 0:
|
||||
if(lastErrorBlink - meterState.getLastUpdateMillis() > 30000) {
|
||||
debugW("No HAN data received last 30s, single blink");
|
||||
hw.ledBlink(LED_RED, 1); // If no message received from AMS in 30 sec, blink once
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(mqttEnabled && mqtt != NULL && mqtt->lastError() != 0) {
|
||||
debugW("MQTT connection not available, double blink");
|
||||
hw.ledBlink(LED_RED, 2); // If MQTT error, blink twice
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if(WiFi.getMode() != WIFI_AP && WiFi.status() != WL_CONNECTED) {
|
||||
debugW("WiFi not connected, tripe blink");
|
||||
hw.ledBlink(LED_RED, 3); // If WiFi not connected, blink three times
|
||||
return;
|
||||
}
|
||||
@@ -764,162 +788,96 @@ void swapWifiMode() {
|
||||
}
|
||||
|
||||
int len = 0;
|
||||
MbusAssembler* ma = NULL;
|
||||
int currentMeterType = -1;
|
||||
bool serialInit = false;
|
||||
bool readHanPort() {
|
||||
if(!hanSerial->available()) return false;
|
||||
|
||||
if(currentMeterType == -1) {
|
||||
// Before reading, empty serial buffer to increase chance of getting first byte of a data transfer
|
||||
if(!serialInit) {
|
||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||
currentMeterType = 0;
|
||||
serialInit = true;
|
||||
return false;
|
||||
}
|
||||
if(currentMeterType == 0) {
|
||||
uint8_t flag = hanSerial->read();
|
||||
if(flag == 0x7E || flag == 0x68) currentMeterType = 1;
|
||||
else currentMeterType = 2;
|
||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||
return false;
|
||||
}
|
||||
CosemDateTime timestamp = {0};
|
||||
AmsData data;
|
||||
if(currentMeterType == 1) {
|
||||
int pos = HDLC_FRAME_INCOMPLETE;
|
||||
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
||||
hanBuffer[len++] = hanSerial->read();
|
||||
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp);
|
||||
}
|
||||
if(len > 0) {
|
||||
if(len >= BUF_SIZE_HAN) {
|
||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||
len = 0;
|
||||
debugI("Buffer overflow, resetting");
|
||||
return false;
|
||||
}
|
||||
if(pos == MBUS_FRAME_INTERMEDIATE_SEGMENT) {
|
||||
debugI("Intermediate segment");
|
||||
if(ma == NULL) {
|
||||
ma = new MbusAssembler();
|
||||
}
|
||||
if(ma->append((uint8_t *) hanBuffer, len) < 0)
|
||||
pos = -77;
|
||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
debugD("Frame dump (%db):", len);
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
}
|
||||
len = 0;
|
||||
return false;
|
||||
} else if(pos == MBUS_FRAME_LAST_SEGMENT) {
|
||||
debugI("Final segment");
|
||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
debugD("Frame dump (%db):", len);
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
}
|
||||
if(ma->append((uint8_t *) hanBuffer, len) >= 0) {
|
||||
len = ma->write((uint8_t *) hanBuffer);
|
||||
pos = HDLC_validate((uint8_t *) hanBuffer, len, hc, ×tamp);
|
||||
} else {
|
||||
pos = -77;
|
||||
}
|
||||
}
|
||||
if(pos == HDLC_FRAME_INCOMPLETE) {
|
||||
return false;
|
||||
}
|
||||
for(int i = len; i<BUF_SIZE_HAN; i++) {
|
||||
hanBuffer[i] = 0x00;
|
||||
}
|
||||
if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) {
|
||||
hc = new HDLCConfig();
|
||||
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
||||
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
||||
}
|
||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
debugD("Frame dump (%db):", len);
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
}
|
||||
if(hc != NULL && Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
debugD("System title:");
|
||||
debugPrint(hc->system_title, 0, 8);
|
||||
debugD("Initialization vector:");
|
||||
debugPrint(hc->initialization_vector, 0, 12);
|
||||
debugD("Additional authenticated data:");
|
||||
debugPrint(hc->additional_authenticated_data, 0, 17);
|
||||
debugD("Authentication tag:");
|
||||
debugPrint(hc->authentication_tag, 0, 12);
|
||||
}
|
||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||
mqtt->publish(topic.c_str(), toHex(hanBuffer, len));
|
||||
}
|
||||
|
||||
DataParserContext ctx = {0};
|
||||
int pos = DATA_PARSE_INCOMPLETE;
|
||||
// For each byte received, check if we have a complete frame we can handle
|
||||
while(hanSerial->available() && pos == DATA_PARSE_INCOMPLETE) {
|
||||
// If buffer was overflowed, reset
|
||||
if(len >= BUF_SIZE_HAN) {
|
||||
hanSerial->readBytes(hanBuffer, BUF_SIZE_HAN);
|
||||
len = 0;
|
||||
if(pos > 0) {
|
||||
debugD("Valid data, start at byte %d", pos);
|
||||
data = IEC6205675(((char *) (hanBuffer)) + pos, meterState.getMeterType(), &meterConfig, timestamp, hc);
|
||||
debugI("Buffer overflow, resetting");
|
||||
return false;
|
||||
}
|
||||
hanBuffer[len++] = hanSerial->read();
|
||||
ctx.length = len;
|
||||
pos = unwrapData((uint8_t *) hanBuffer, ctx);
|
||||
if(pos >= 0) {
|
||||
if(ctx.type == DATA_TAG_DLMS) {
|
||||
debugV("Received valid DLMS at %d", pos);
|
||||
} else if(ctx.type == DATA_TAG_DSMR) {
|
||||
debugV("Received valid DSMR at %d", pos);
|
||||
} else {
|
||||
if(Debug.isActive(RemoteDebug::WARNING)) {
|
||||
switch(pos) {
|
||||
case HDLC_BOUNDRY_FLAG_MISSING:
|
||||
debugW("Boundry flag missing");
|
||||
break;
|
||||
case HDLC_HCS_ERROR:
|
||||
debugW("Header checksum error");
|
||||
break;
|
||||
case HDLC_FCS_ERROR:
|
||||
debugW("Frame checksum error");
|
||||
break;
|
||||
case HDLC_FRAME_INCOMPLETE:
|
||||
debugW("Received frame is incomplete");
|
||||
break;
|
||||
case HDLC_ENCRYPTION_CONFIG_MISSING:
|
||||
debugI("Encryption configuration requested, initializing");
|
||||
break;
|
||||
case HDLC_ENCRYPTION_AUTH_FAILED:
|
||||
debugW("Decrypt authentication failed");
|
||||
break;
|
||||
case HDLC_ENCRYPTION_KEY_FAILED:
|
||||
debugW("Setting decryption key failed");
|
||||
break;
|
||||
case HDLC_ENCRYPTION_DECRYPT_FAILED:
|
||||
debugW("Decryption failed");
|
||||
break;
|
||||
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
||||
debugW("Frame length mismatch");
|
||||
break;
|
||||
case MBUS_FRAME_INTERMEDIATE_SEGMENT:
|
||||
case MBUS_FRAME_LAST_SEGMENT:
|
||||
debugW("Partial frame dropped");
|
||||
break;
|
||||
case HDLC_TIMESTAMP_UNKNOWN:
|
||||
debugW("Frame timestamp is not correctly formatted");
|
||||
break;
|
||||
case HDLC_UNKNOWN_DATA:
|
||||
debugW("Unknown data format %02X", hanBuffer[0]);
|
||||
currentMeterType = 0;
|
||||
break;
|
||||
default:
|
||||
debugW("Unspecified error while reading data: %d", pos);
|
||||
}
|
||||
}
|
||||
// TODO: Move this so that payload is sent to MQTT
|
||||
debugE("Unknown tag %02X at pos %d", ctx.type, pos);
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if(currentMeterType == 2) {
|
||||
String payload = hanSerial->readString();
|
||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||
mqtt->publish(topic.c_str(), payload);
|
||||
}
|
||||
data = IEC6205621(payload);
|
||||
if(data.getListType() == 0) {
|
||||
currentMeterType = 1;
|
||||
return false;
|
||||
} else {
|
||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
debugD("Frame dump: %d", payload.length());
|
||||
debugD("%s", payload.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
if(pos == DATA_PARSE_INCOMPLETE) {
|
||||
return false;
|
||||
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len);
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(pos == DATA_PARSE_INTERMEDIATE_SEGMENT) {
|
||||
len = 0;
|
||||
return false;
|
||||
} else if(pos < 0) {
|
||||
printHanReadError(pos);
|
||||
len += hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len);
|
||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||
mqtt->publish(topic.c_str(), toHex(hanBuffer+pos, len));
|
||||
mqtt->loop();
|
||||
}
|
||||
while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above
|
||||
len = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data is valid, clear the rest of the buffer to avoid tainted parsing
|
||||
for(int i = pos+ctx.length; i<BUF_SIZE_HAN; i++) {
|
||||
hanBuffer[i] = 0x00;
|
||||
}
|
||||
|
||||
AmsData data;
|
||||
char* payload = ((char *) (hanBuffer)) + pos;
|
||||
if(ctx.type == DATA_TAG_DLMS) {
|
||||
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
|
||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||
mqtt->publish(topic.c_str(), toHex((byte*) payload, ctx.length));
|
||||
mqtt->loop();
|
||||
}
|
||||
|
||||
debugV("Using application data:");
|
||||
if(Debug.isActive(RemoteDebug::VERBOSE)) debugPrint((byte*) payload, 0, ctx.length);
|
||||
|
||||
// Rudimentary detector for L&G proprietary format
|
||||
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
|
||||
data = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
|
||||
} else {
|
||||
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
|
||||
data = IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx);
|
||||
}
|
||||
} else if(ctx.type == DATA_TAG_DSMR) {
|
||||
data = IEC6205621(payload);
|
||||
}
|
||||
len = 0;
|
||||
|
||||
if(data.getListType() > 0) {
|
||||
if(!hw.ledBlink(LED_GREEN, 1))
|
||||
@@ -953,14 +911,14 @@ bool readHanPort() {
|
||||
|
||||
bool saveData = false;
|
||||
if(!ds.isHappy() && now > BUILD_EPOCH) {
|
||||
debugD("Its time to update data storage");
|
||||
debugV("Its time to update data storage");
|
||||
tmElements_t tm;
|
||||
breakTime(now, tm);
|
||||
if(tm.Minute == 0) {
|
||||
debugD(" using actual data");
|
||||
debugV(" using actual data");
|
||||
saveData = ds.update(&data);
|
||||
} else if(meterState.getListType() >= 3) {
|
||||
debugD(" using estimated data");
|
||||
debugV(" using estimated data");
|
||||
saveData = ds.update(&meterState);
|
||||
}
|
||||
if(saveData) {
|
||||
@@ -978,6 +936,45 @@ bool readHanPort() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void printHanReadError(int pos) {
|
||||
if(Debug.isActive(RemoteDebug::WARNING)) {
|
||||
switch(pos) {
|
||||
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
|
||||
debugW("Boundry flag missing");
|
||||
break;
|
||||
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
|
||||
debugW("Header checksum error");
|
||||
break;
|
||||
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
|
||||
debugW("Frame checksum error");
|
||||
break;
|
||||
case DATA_PARSE_INCOMPLETE:
|
||||
debugW("Received frame is incomplete");
|
||||
break;
|
||||
case GCM_AUTH_FAILED:
|
||||
debugW("Decrypt authentication failed");
|
||||
break;
|
||||
case GCM_ENCRYPTION_KEY_FAILED:
|
||||
debugW("Setting decryption key failed");
|
||||
break;
|
||||
case GCM_DECRYPT_FAILED:
|
||||
debugW("Decryption failed");
|
||||
break;
|
||||
case MBUS_FRAME_LENGTH_NOT_EQUAL:
|
||||
debugW("Frame length mismatch");
|
||||
break;
|
||||
case DATA_PARSE_INTERMEDIATE_SEGMENT:
|
||||
debugI("Intermediate segment received");
|
||||
break;
|
||||
case DATA_PARSE_UNKNOWN_DATA:
|
||||
debugW("Unknown data format %02X", hanBuffer[0]);
|
||||
break;
|
||||
default:
|
||||
debugW("Unspecified error while reading data: %d", pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
@@ -994,17 +991,34 @@ void debugPrint(byte *buffer, int start, int length) {
|
||||
Debug.println("");
|
||||
}
|
||||
|
||||
unsigned long wifiTimeout = WIFI_CONNECTION_TIMEOUT;
|
||||
unsigned long lastWifiRetry = -WIFI_CONNECTION_TIMEOUT;
|
||||
void WiFi_connect() {
|
||||
if(millis() - lastWifiRetry < wifiTimeout) {
|
||||
delay(50);
|
||||
return;
|
||||
}
|
||||
lastWifiRetry = millis();
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
if(WiFi.status() == WL_DISCONNECTED) {
|
||||
if(millis() - lastWifiRetry < WIFI_CONNECTION_TIMEOUT) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(WiFi.getMode() != WIFI_OFF) {
|
||||
switch(WiFi.status()) {
|
||||
case WL_NO_SSID_AVAIL:
|
||||
debugE("WiFi error, no SSID available");
|
||||
break;
|
||||
case WL_CONNECT_FAILED:
|
||||
debugE("WiFi error, connection failed");
|
||||
break;
|
||||
case WL_CONNECTION_LOST:
|
||||
debugE("WiFi error, connection lost");
|
||||
break;
|
||||
#if defined(ESP8266)
|
||||
case WL_WRONG_PASSWORD:
|
||||
debugE("WiFi error, wrong password");
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
debugE("WiFi error, %d", WiFi.status());
|
||||
break;
|
||||
}
|
||||
if(wifiReconnectCount > 3) {
|
||||
ESP.restart();
|
||||
return;
|
||||
@@ -1038,11 +1052,13 @@ void WiFi_connect() {
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.enableAP(false);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
#if defined(ESP8266)
|
||||
WiFi.forceSleepBegin();
|
||||
#endif
|
||||
yield();
|
||||
wifiTimeout = 5000;
|
||||
return;
|
||||
}
|
||||
wifiTimeout = WIFI_CONNECTION_TIMEOUT;
|
||||
lastWifiRetry = millis();
|
||||
|
||||
WiFiConfig wifi;
|
||||
if(!config.getWiFiConfig(wifi) || strlen(wifi.ssid) == 0) {
|
||||
@@ -1060,6 +1076,7 @@ void WiFi_connect() {
|
||||
}
|
||||
#endif
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.setSleep(WIFI_PS_MAX_MODEM);
|
||||
#if defined(ESP32)
|
||||
if(wifi.power >= 195)
|
||||
WiFi.setTxPower(WIFI_POWER_19_5dBm);
|
||||
@@ -1083,6 +1100,8 @@ void WiFi_connect() {
|
||||
WiFi.setTxPower(WIFI_POWER_5dBm);
|
||||
else if(wifi.power >= 20)
|
||||
WiFi.setTxPower(WIFI_POWER_2dBm);
|
||||
else
|
||||
WiFi.setTxPower(WIFI_POWER_MINUS_1dBm);
|
||||
#elif defined(ESP8266)
|
||||
WiFi.setOutputPower(wifi.power / 10.0);
|
||||
#endif
|
||||
@@ -1125,6 +1144,131 @@ void WiFi_connect() {
|
||||
}
|
||||
}
|
||||
|
||||
void mqttMessageReceived(String &topic, String &payload) {
|
||||
debugI("Received message for topic %s", topic.c_str() );
|
||||
//if(meterConfig.source == METER_SOURCE_MQTT) {
|
||||
//DataParserContext ctx = {static_cast<uint8_t>(payload.length()/2)};
|
||||
//fromHex(hanBuffer, payload, ctx.length);
|
||||
//uint16_t pos = unwrapData(hanBuffer, ctx);
|
||||
// TODO: Run through DLMS/DMSR parser and apply AmsData
|
||||
//}
|
||||
}
|
||||
|
||||
int16_t unwrapData(uint8_t *buf, DataParserContext &context) {
|
||||
int16_t ret = 0;
|
||||
bool doRet = false;
|
||||
uint16_t end = BUF_SIZE_HAN;
|
||||
uint8_t tag = (*buf);
|
||||
uint8_t lastTag = DATA_TAG_NONE;
|
||||
while(tag != DATA_TAG_NONE) {
|
||||
int16_t curLen = context.length;
|
||||
int8_t res = 0;
|
||||
switch(tag) {
|
||||
case DATA_TAG_HDLC:
|
||||
if(hdlcParser == NULL) hdlcParser = new HDLCParser();
|
||||
res = hdlcParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_MBUS:
|
||||
if(mbusParser == NULL) mbusParser = new MBUSParser();
|
||||
res = mbusParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_GBT:
|
||||
if(gbtParser == NULL) gbtParser = new GBTParser();
|
||||
res = gbtParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_GCM:
|
||||
if(gcmParser == NULL) gcmParser = new GCMParser(meterConfig.encryptionKey, meterConfig.authenticationKey);
|
||||
res = gcmParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_LLC:
|
||||
if(llcParser == NULL) llcParser = new LLCParser();
|
||||
res = llcParser->parse(buf, context);
|
||||
break;
|
||||
case DATA_TAG_DLMS:
|
||||
if(dlmsParser == NULL) dlmsParser = new DLMSParser();
|
||||
res = dlmsParser->parse(buf, context);
|
||||
if(res >= 0) doRet = true;
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
if(dsmrParser == NULL) dsmrParser = new DSMRParser();
|
||||
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
|
||||
if(res >= 0) doRet = true;
|
||||
break;
|
||||
default:
|
||||
debugE("Ended up in default case while unwrapping...");
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
lastTag = tag;
|
||||
if(res == DATA_PARSE_INCOMPLETE) {
|
||||
return res;
|
||||
}
|
||||
if(context.length > end) return false;
|
||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
switch(tag) {
|
||||
case DATA_TAG_HDLC:
|
||||
debugV("HDLC frame:");
|
||||
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
|
||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||
mqtt->publish(topic.c_str(), toHex(buf, curLen));
|
||||
mqtt->loop();
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_MBUS:
|
||||
debugV("MBUS frame:");
|
||||
// If MQTT bytestream payload is selected (mqttHandler == NULL), send the payload to MQTT
|
||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||
mqtt->publish(topic.c_str(), toHex(buf, curLen));
|
||||
mqtt->loop();
|
||||
}
|
||||
break;
|
||||
case DATA_TAG_GBT:
|
||||
debugV("GBT frame:");
|
||||
break;
|
||||
case DATA_TAG_GCM:
|
||||
debugV("GCM frame:");
|
||||
break;
|
||||
case DATA_TAG_LLC:
|
||||
debugV("LLC frame:");
|
||||
break;
|
||||
case DATA_TAG_DLMS:
|
||||
debugV("DLMS frame:");
|
||||
break;
|
||||
case DATA_TAG_DSMR:
|
||||
debugV("DSMR frame:");
|
||||
if(mqttEnabled && mqtt != NULL && mqttHandler == NULL) {
|
||||
mqtt->publish(topic.c_str(), (char*) buf);
|
||||
mqtt->loop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
debugPrint(buf, 0, curLen);
|
||||
}
|
||||
if(res == DATA_PARSE_FINAL_SEGMENT) {
|
||||
if(tag == DATA_TAG_MBUS) {
|
||||
res = mbusParser->write(buf, context);
|
||||
}
|
||||
}
|
||||
|
||||
if(res < 0) {
|
||||
return res;
|
||||
}
|
||||
buf += res;
|
||||
end -= res;
|
||||
ret += res;
|
||||
|
||||
// If we are ready to return, do that
|
||||
if(doRet) {
|
||||
context.type = tag;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Use start byte of new buffer position as tag for next round in loop
|
||||
tag = (*buf);
|
||||
}
|
||||
debugE("Got to end of unwrap method...");
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
|
||||
unsigned long lastMqttRetry = -10000;
|
||||
void MQTT_connect() {
|
||||
MqttConfig mqttConfig;
|
||||
@@ -1132,7 +1276,6 @@ void MQTT_connect() {
|
||||
if(Debug.isActive(RemoteDebug::WARNING)) debugW("No MQTT config");
|
||||
mqttEnabled = false;
|
||||
ws.setMqttEnabled(false);
|
||||
config.ackMqttChange();
|
||||
return;
|
||||
}
|
||||
if(mqtt != NULL) {
|
||||
@@ -1147,23 +1290,19 @@ void MQTT_connect() {
|
||||
}
|
||||
|
||||
mqtt->disconnect();
|
||||
if(config.isMqttChanged()) {
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient->stop();
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
} else {
|
||||
mqttClient->stop();
|
||||
}
|
||||
mqttClient = NULL;
|
||||
}
|
||||
yield();
|
||||
} else {
|
||||
uint16_t size = 128;
|
||||
switch(mqttConfig.payloadFormat) {
|
||||
case 0: // JSON
|
||||
case 4: // Home Assistant
|
||||
size = 768;
|
||||
break;
|
||||
case 3: // Domoticz
|
||||
size = 256;
|
||||
break;
|
||||
case 255: // Raw frame
|
||||
size = 1024;
|
||||
break;
|
||||
}
|
||||
|
||||
mqtt = new MQTTClient(size);
|
||||
mqtt = new MQTTClient(1024);
|
||||
ws.setMqtt(mqtt);
|
||||
}
|
||||
|
||||
@@ -1195,49 +1334,57 @@ void MQTT_connect() {
|
||||
}
|
||||
|
||||
if(mqttConfig.ssl) {
|
||||
debugI("MQTT SSL is configured");
|
||||
debugI("MQTT SSL is configured (%dkb free heap)", ESP.getFreeHeap());
|
||||
if(mqttSecureClient == NULL) {
|
||||
mqttSecureClient = new WiFiClientSecure();
|
||||
}
|
||||
#if defined(ESP8266)
|
||||
mqttSecureClient->setBufferSizes(512, 512);
|
||||
#endif
|
||||
#if defined(ESP8266)
|
||||
mqttSecureClient->setBufferSizes(512, 512);
|
||||
#endif
|
||||
|
||||
if(LittleFS.begin()) {
|
||||
File file;
|
||||
|
||||
if(LittleFS.begin()) {
|
||||
char *ca = NULL;
|
||||
char *cert = NULL;
|
||||
char *key = NULL;
|
||||
File file;
|
||||
if(LittleFS.exists(FILE_MQTT_CA)) {
|
||||
debugI("Found MQTT CA file (%dkb free heap)", ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CA, "r");
|
||||
#if defined(ESP8266)
|
||||
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
|
||||
mqttSecureClient->setTrustAnchors(serverTrustedCA);
|
||||
#elif defined(ESP32)
|
||||
mqttSecureClient->loadCACert(file, file.size());
|
||||
#endif
|
||||
file.close();
|
||||
}
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_CA)) {
|
||||
debugI("Found MQTT CA file");
|
||||
file = LittleFS.open(FILE_MQTT_CA, "r");
|
||||
#if defined(ESP8266)
|
||||
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
|
||||
mqttSecureClient->setTrustAnchors(serverTrustedCA);
|
||||
#elif defined(ESP32)
|
||||
mqttSecureClient->loadCACert(file, file.size());
|
||||
#endif
|
||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
#if defined(ESP8266)
|
||||
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
||||
file.close();
|
||||
|
||||
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
||||
file.close();
|
||||
|
||||
debugD("Setting client certificates (%dkb free heap)", ESP.getFreeHeap());
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
#elif defined(ESP32)
|
||||
debugI("Found MQTT certificate file (%dkb free heap)", ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
||||
mqttSecureClient->loadCertificate(file, file.size());
|
||||
file.close();
|
||||
|
||||
debugI("Found MQTT key file (%dkb free heap)", ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||
file.close();
|
||||
#endif
|
||||
}
|
||||
LittleFS.end();
|
||||
debugD("MQTT SSL setup complete (%dkb free heap)", ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
#if defined(ESP8266)
|
||||
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
#elif defined(ESP32)
|
||||
debugI("Found MQTT certificate file");
|
||||
file = LittleFS.open(FILE_MQTT_CERT, "r");
|
||||
mqttSecureClient->loadCertificate(file, file.size());
|
||||
|
||||
debugI("Found MQTT key file");
|
||||
file = LittleFS.open(FILE_MQTT_KEY, "r");
|
||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||
#endif
|
||||
}
|
||||
LittleFS.end();
|
||||
}
|
||||
mqttClient = mqttSecureClient;
|
||||
} else if(mqttClient == NULL) {
|
||||
@@ -1253,7 +1400,7 @@ void MQTT_connect() {
|
||||
#if defined(ESP8266)
|
||||
if(mqttSecureClient) {
|
||||
time_t epoch = time(nullptr);
|
||||
debugD("Setting NTP time %i for secure MQTT connection", epoch);
|
||||
debugD("Setting NTP time %lld for secure MQTT connection", epoch);
|
||||
mqttSecureClient->setX509Time(epoch);
|
||||
}
|
||||
#endif
|
||||
@@ -1262,10 +1409,16 @@ void MQTT_connect() {
|
||||
if ((strlen(mqttConfig.username) == 0 && mqtt->connect(mqttConfig.clientId)) ||
|
||||
(strlen(mqttConfig.username) > 0 && mqtt->connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI("Successfully connected to MQTT!");
|
||||
config.ackMqttChange();
|
||||
|
||||
if(mqttHandler != NULL) {
|
||||
mqttHandler->publishSystem(&hw);
|
||||
mqttHandler->publishSystem(&hw, eapi, &ea);
|
||||
}
|
||||
|
||||
// Subscribe to the chosen MQTT topic, if set in configuration
|
||||
if (strlen(mqttConfig.subscribeTopic) > 0) {
|
||||
mqtt->onMessage(mqttMessageReceived);
|
||||
mqtt->subscribe(String(mqttConfig.subscribeTopic) + "/#");
|
||||
debugI(" Subscribing to [%s]\r\n", mqttConfig.subscribeTopic);
|
||||
}
|
||||
} else {
|
||||
if (Debug.isActive(RemoteDebug::ERROR)) {
|
||||
@@ -1302,6 +1455,8 @@ void configFileParse() {
|
||||
bool lNtp = false;
|
||||
bool lEntsoe = false;
|
||||
bool lEac = false;
|
||||
bool sEa = false;
|
||||
bool sDs = false;
|
||||
|
||||
SystemConfig sys;
|
||||
WiFiConfig wifi;
|
||||
@@ -1528,6 +1683,7 @@ void configFileParse() {
|
||||
i++;
|
||||
}
|
||||
ds.setDayData(day);
|
||||
sDs = true;
|
||||
} else if(strncmp(buf, "monthplot ", 10) == 0) {
|
||||
int i = 0;
|
||||
MonthDataPoints month = { 5 }; // Use a version we know the multiplier of the data points
|
||||
@@ -1550,31 +1706,53 @@ void configFileParse() {
|
||||
i++;
|
||||
}
|
||||
ds.setMonthData(month);
|
||||
sDs = true;
|
||||
} else if(strncmp(buf, "energyaccounting ", 17) == 0) {
|
||||
int i = 0;
|
||||
EnergyAccountingData ead = { 1 };
|
||||
uint8_t i = 0;
|
||||
EnergyAccountingData ead = { 4, 0,
|
||||
0, 0, 0,
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
char * pch = strtok (buf+17," ");
|
||||
while (pch != NULL) {
|
||||
if(i == 1) {
|
||||
if(i == 0) {
|
||||
// Ignore version
|
||||
} else if(i == 1) {
|
||||
long val = String(pch).toInt();
|
||||
ead.month = val;
|
||||
} else if(i == 2) {
|
||||
double val = String(pch).toDouble();
|
||||
ead.maxHour = val * 100;
|
||||
if(val > 0.0) {
|
||||
ead.peaks[0] = { 1, (uint16_t) (val*100) };
|
||||
}
|
||||
} else if(i == 3) {
|
||||
double val = String(pch).toDouble();
|
||||
ead.costYesterday = val * 100;
|
||||
ead.costYesterday = val * 10;
|
||||
} else if(i == 4) {
|
||||
double val = String(pch).toDouble();
|
||||
ead.costThisMonth = val * 100;
|
||||
ead.costThisMonth = val;
|
||||
} else if(i == 5) {
|
||||
double val = String(pch).toDouble();
|
||||
ead.costLastMonth = val * 100;
|
||||
ead.costLastMonth = val;
|
||||
} else if(i >= 6 && i < 18) {
|
||||
uint8_t hour = i-6;
|
||||
if(hour%2 == 0) {
|
||||
long val = String(pch).toInt();
|
||||
ead.peaks[hour/2].day = val;
|
||||
} else {
|
||||
double val = String(pch).toDouble();
|
||||
ead.peaks[hour/2].value = val * 100;
|
||||
}
|
||||
}
|
||||
pch = strtok (NULL, " ");
|
||||
i++;
|
||||
}
|
||||
ea.setData(ead);
|
||||
sEa = true;
|
||||
}
|
||||
memset(buf, 0, 1024);
|
||||
}
|
||||
@@ -1594,7 +1772,7 @@ void configFileParse() {
|
||||
if(lDomo) config.setDomoticzConfig(domo);
|
||||
if(lNtp) config.setNtpConfig(ntp);
|
||||
if(lEntsoe) config.setEntsoeConfig(entsoe);
|
||||
ds.save();
|
||||
ea.save();
|
||||
if(sDs) ds.save();
|
||||
if(sEa) ea.save();
|
||||
config.save();
|
||||
}
|
||||
|
||||
@@ -8,13 +8,16 @@ EnergyAccounting::EnergyAccounting(RemoteDebug* debugger) {
|
||||
this->debugger = debugger;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setup(AmsDataStorage *ds, EntsoeApi *eapi, EnergyAccountingConfig *config) {
|
||||
void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config) {
|
||||
this->ds = ds;
|
||||
this->eapi = eapi;
|
||||
this->config = config;
|
||||
this->currentThresholdIdx = 0;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setEapi(EntsoeApi *eapi) {
|
||||
this->eapi = eapi;
|
||||
}
|
||||
|
||||
EnergyAccountingConfig* EnergyAccounting::getConfig() {
|
||||
return config;
|
||||
}
|
||||
@@ -24,6 +27,7 @@ void EnergyAccounting::setTimezone(Timezone* tz) {
|
||||
}
|
||||
|
||||
bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(config == NULL) return false;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return false;
|
||||
if(tz == NULL) {
|
||||
@@ -38,36 +42,52 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(!init) {
|
||||
currentHour = local.Hour;
|
||||
currentDay = local.Day;
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now);
|
||||
if(!load()) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data");
|
||||
data = { 1, local.Month, 0, 0, 0, 0 };
|
||||
if(calcDayUse()) ret = true;
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n");
|
||||
data = { 4, local.Month,
|
||||
0, 0, 0,
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
} else if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
debugger->printf("(EnergyAccounting) Peak hour from day %d: %d\n", data.peaks[i].day, data.peaks[i].value*10);
|
||||
}
|
||||
debugger->printf("(EnergyAccounting) Loaded cost yesterday: %.2f, this month: %d, last month: %d\n", data.costYesterday / 10.0, data.costThisMonth, data.costLastMonth);
|
||||
}
|
||||
init = true;
|
||||
}
|
||||
|
||||
if(!initPrice && eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Initializing prices at %lld\n", (int64_t) now);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lld\n", (int64_t) now);
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
if(local.Hour != currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New local hour %d\n", local.Hour);
|
||||
|
||||
if(calcDayUse()) ret = true;
|
||||
tmElements_t oneHrAgo;
|
||||
breakTime(now-3600, oneHrAgo);
|
||||
uint16_t val = ds->getHourImport(oneHrAgo.Hour) / 10;
|
||||
ret |= updateMax(val, local.Day);
|
||||
|
||||
if(local.Hour > 0) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
use = 0;
|
||||
produce = 0;
|
||||
costHour = 0;
|
||||
currentHour = local.Hour;
|
||||
|
||||
if(local.Day != currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New day %d\n", local.Day);
|
||||
data.costYesterday = costDay * 100;
|
||||
data.costThisMonth += costDay * 100;
|
||||
data.costYesterday = costDay * 10;
|
||||
data.costThisMonth += costDay;
|
||||
costDay = 0;
|
||||
currentDay = local.Day;
|
||||
ret = true;
|
||||
@@ -77,7 +97,9 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New month %d\n", local.Month);
|
||||
data.costLastMonth = data.costThisMonth;
|
||||
data.costThisMonth = 0;
|
||||
data.maxHour = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
data.peaks[i] = { 0, 0 };
|
||||
}
|
||||
data.month = local.Month;
|
||||
currentThresholdIdx = 0;
|
||||
ret = true;
|
||||
@@ -85,19 +107,24 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
}
|
||||
|
||||
unsigned long ms = this->lastUpdateMillis > amsData->getLastUpdateMillis() ? 0 : amsData->getLastUpdateMillis() - this->lastUpdateMillis;
|
||||
float kwh = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
lastUpdateMillis = amsData->getLastUpdateMillis();
|
||||
if(kwh > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh\n", kwh);
|
||||
use += kwh;
|
||||
if(kwhi > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh import\n", kwhi);
|
||||
use += kwhi;
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
float price = eapi->getValueForHour(0);
|
||||
float cost = price * kwh;
|
||||
float cost = price * kwhi;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) and %.4f %s\n", cost / 100.0, eapi->getCurrency());
|
||||
costHour += cost;
|
||||
costDay += cost;
|
||||
}
|
||||
}
|
||||
if(kwhe > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) Adding %.4f kWh export\n", kwhe);
|
||||
produce += kwhe;
|
||||
}
|
||||
|
||||
if(config != NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(EnergyAccounting) calculating threshold, currently at %d\n", currentThresholdIdx);
|
||||
@@ -108,24 +135,6 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::calcDayUse() {
|
||||
time_t now = time(nullptr);
|
||||
tmElements_t local, utc;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
bool ret = false;
|
||||
uint8_t lim = local.Day == 1 ? local.Hour : 24;
|
||||
for(int i = 0; i < lim; i++) {
|
||||
breakTime(now - ((lim - i) * 3600), utc);
|
||||
int16_t val = ds->getHourImport(utc.Hour) / 10.0;
|
||||
if(val > data.maxHour) {
|
||||
data.maxHour = val;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EnergyAccounting::calcDayCost() {
|
||||
time_t now = time(nullptr);
|
||||
tmElements_t local, utc;
|
||||
@@ -133,10 +142,10 @@ void EnergyAccounting::calcDayCost() {
|
||||
|
||||
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(initPrice) costDay = 0;
|
||||
for(int i = 0; i < local.Hour; i++) {
|
||||
float price = eapi->getValueForHour(i - local.Hour);
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
float price = eapi->getValueForHour(i - currentHour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
breakTime(now - ((currentHour - i) * 3600), utc);
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
costDay += price * (wh / 1000.0);
|
||||
}
|
||||
@@ -148,52 +157,73 @@ double EnergyAccounting::getUseThisHour() {
|
||||
return use;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostThisHour() {
|
||||
return costHour;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getUseToday() {
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
tmElements_t local, utc;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(int i = 0; i < local.Hour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
tmElements_t utc;
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
breakTime(now - ((currentHour - i) * 3600), utc);
|
||||
ret += ds->getHourImport(utc.Hour) / 1000.0;
|
||||
}
|
||||
return ret + getUseThisHour();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
float ret = 0;
|
||||
for(int i = 0; i < currentDay; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getProducedThisHour() {
|
||||
return produce;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getProducedToday() {
|
||||
float ret = 0.0;
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
tmElements_t utc;
|
||||
for(int i = 0; i < currentHour; i++) {
|
||||
breakTime(now - ((currentHour - i) * 3600), utc);
|
||||
ret += ds->getHourExport(utc.Hour) / 1000.0;
|
||||
}
|
||||
return ret + getProducedThisHour();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getProducedThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
float ret = 0;
|
||||
for(int i = 0; i < currentDay; i++) {
|
||||
ret += ds->getDayExport(i) / 1000.0;
|
||||
}
|
||||
return ret + getProducedToday();
|
||||
}
|
||||
|
||||
|
||||
double EnergyAccounting::getCostThisHour() {
|
||||
return costHour;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostToday() {
|
||||
return costDay;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostYesterday() {
|
||||
return data.costYesterday / 100.0;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < BUILD_EPOCH) return 0;
|
||||
tmElements_t tm;
|
||||
if(tz != NULL)
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
else
|
||||
breakTime(now, tm);
|
||||
float ret = 0;
|
||||
for(int i = 0; i < tm.Day; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
return data.costYesterday / 10.0;
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostThisMonth() {
|
||||
return (data.costThisMonth / 100.0) + getCostToday();
|
||||
return data.costThisMonth + getCostToday();
|
||||
}
|
||||
|
||||
double EnergyAccounting::getCostLastMonth() {
|
||||
return data.costLastMonth / 100.0;
|
||||
uint16_t EnergyAccounting::getCostLastMonth() {
|
||||
return data.costLastMonth;
|
||||
}
|
||||
|
||||
uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
@@ -203,7 +233,61 @@ uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getMonthMax() {
|
||||
return data.maxHour / 100.0;
|
||||
uint8_t count = 0;
|
||||
uint32_t maxHour = 0.0;
|
||||
bool included[5] = { false, false, false, false, false };
|
||||
|
||||
while(count < config->hours && count <= 5) {
|
||||
uint8_t maxIdx = 0;
|
||||
uint16_t maxVal = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(included[i]) continue;
|
||||
if(data.peaks[i].day == 0) continue;
|
||||
if(data.peaks[i].value > maxVal) {
|
||||
maxVal = data.peaks[i].value;
|
||||
maxIdx = i;
|
||||
}
|
||||
}
|
||||
included[maxIdx] = true;
|
||||
count++;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(!included[i]) continue;
|
||||
maxHour += data.peaks[i].value;
|
||||
}
|
||||
return maxHour > 0 ? maxHour / count / 100.0 : 0.0;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getPeak(uint8_t num) {
|
||||
if(num < 1 || num > 5) return 0.0;
|
||||
|
||||
uint8_t count = 0;
|
||||
bool included[5] = { false, false, false, false, false };
|
||||
|
||||
while(count < config->hours && count <= 5) {
|
||||
uint8_t maxIdx = 0;
|
||||
uint16_t maxVal = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(included[i]) continue;
|
||||
if(data.peaks[i].value > maxVal) {
|
||||
maxVal = data.peaks[i].value;
|
||||
maxIdx = i;
|
||||
}
|
||||
}
|
||||
included[maxIdx] = true;
|
||||
count++;
|
||||
}
|
||||
|
||||
uint8_t pos = 0;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(!included[i]) continue;
|
||||
pos++;
|
||||
if(pos == num) {
|
||||
return data.peaks[i].value / 100.0;
|
||||
}
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::load() {
|
||||
@@ -219,15 +303,64 @@ bool EnergyAccounting::load() {
|
||||
File file = LittleFS.open(FILE_ENERGYACCOUNTING, "r");
|
||||
char buf[file.size()];
|
||||
file.readBytes(buf, file.size());
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
file.close();
|
||||
|
||||
if(data->version == 1) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Data version %d\n", buf[0]);
|
||||
if(buf[0] == 4) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
memcpy(&this->data, data, sizeof(this->data));
|
||||
ret = true;
|
||||
} else if(buf[0] == 3) {
|
||||
EnergyAccountingData* data = (EnergyAccountingData*) buf;
|
||||
this->data = { 4, data->month,
|
||||
(uint16_t) (data->costYesterday / 10), (uint16_t) (data->costThisMonth / 100), (uint16_t) (data->costLastMonth / 100),
|
||||
data->peaks[0].day, data->peaks[0].value,
|
||||
data->peaks[1].day, data->peaks[1].value,
|
||||
data->peaks[2].day, data->peaks[2].value,
|
||||
data->peaks[3].day, data->peaks[3].value,
|
||||
data->peaks[4].day, data->peaks[4].value
|
||||
};
|
||||
ret = true;
|
||||
} else {
|
||||
ret = false;
|
||||
data = { 4, 0,
|
||||
0, 0, 0,
|
||||
0, 0, // Peak 1
|
||||
0, 0, // Peak 2
|
||||
0, 0, // Peak 3
|
||||
0, 0, // Peak 4
|
||||
0, 0 // Peak 5
|
||||
};
|
||||
if(buf[0] == 2) {
|
||||
EnergyAccountingData1* data = (EnergyAccountingData1*) buf;
|
||||
this->data.month = data->month;
|
||||
this->data.costYesterday = (uint16_t) (data->costYesterday / 10);
|
||||
this->data.costThisMonth = (uint16_t) (data->costThisMonth / 100);
|
||||
this->data.costLastMonth = (uint16_t) (data->costLastMonth / 100);
|
||||
uint8_t b = 0;
|
||||
for(uint8_t i = sizeof(this->data); i < file.size(); i+=2) {
|
||||
this->data.peaks[b].day = b;
|
||||
memcpy(&this->data.peaks[b].value, buf+i, 2);
|
||||
b++;
|
||||
if(b >= config->hours || b >= 5) break;
|
||||
}
|
||||
ret = true;
|
||||
} else if(buf[0] == 1) {
|
||||
EnergyAccountingData1* data = (EnergyAccountingData1*) buf;
|
||||
this->data.month = data->month;
|
||||
this->data.costYesterday = (uint16_t) (data->costYesterday / 10);
|
||||
this->data.costThisMonth = (uint16_t) (data->costThisMonth / 100);
|
||||
this->data.costLastMonth = (uint16_t) (data->costLastMonth / 100);
|
||||
this->data.peaks[0].day = 1;
|
||||
this->data.peaks[0].value = data->maxHour;
|
||||
ret = true;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf("(EnergyAccounting) Unknown version\n");
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf("(EnergyAccounting) File not found\n");
|
||||
}
|
||||
|
||||
LittleFS.end();
|
||||
@@ -246,7 +379,7 @@ bool EnergyAccounting::save() {
|
||||
File file = LittleFS.open(FILE_ENERGYACCOUNTING, "w");
|
||||
char buf[sizeof(data)];
|
||||
memcpy(buf, &data, sizeof(data));
|
||||
for(unsigned long i = 0; i < sizeof(data); i++) {
|
||||
for(uint8_t i = 0; i < sizeof(buf); i++) {
|
||||
file.write(buf[i]);
|
||||
}
|
||||
file.close();
|
||||
@@ -263,3 +396,34 @@ EnergyAccountingData EnergyAccounting::getData() {
|
||||
void EnergyAccounting::setData(EnergyAccountingData& data) {
|
||||
this->data = data;
|
||||
}
|
||||
|
||||
bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(data.peaks[i].day == day || data.peaks[i].day == 0) {
|
||||
if(val > data.peaks[i].value) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Adding new max %d for day %d which is larger than %d\n", val*10, day, data.peaks[i].value*10);
|
||||
data.peaks[i].day = day;
|
||||
data.peaks[i].value = val;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uint16_t test = 0;
|
||||
uint8_t idx = 255;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(val > data.peaks[i].value) {
|
||||
if(test < data.peaks[i].value) {
|
||||
test = data.peaks[i].value;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(idx < 5) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Adding new max %d for day %d\n", val*10, day);
|
||||
data.peaks[idx].value = val;
|
||||
data.peaks[idx].day = day;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -6,7 +6,21 @@
|
||||
#include "AmsDataStorage.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
|
||||
struct EnergyAccountingPeak {
|
||||
uint8_t day;
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
struct EnergyAccountingData {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t costYesterday;
|
||||
uint16_t costThisMonth;
|
||||
uint16_t costLastMonth;
|
||||
EnergyAccountingPeak peaks[5];
|
||||
};
|
||||
|
||||
struct EnergyAccountingData1 {
|
||||
uint8_t version;
|
||||
uint8_t month;
|
||||
uint16_t maxHour;
|
||||
@@ -15,26 +29,35 @@ struct EnergyAccountingData {
|
||||
uint16_t costLastMonth;
|
||||
};
|
||||
|
||||
|
||||
class EnergyAccounting {
|
||||
public:
|
||||
EnergyAccounting(RemoteDebug*);
|
||||
void setup(AmsDataStorage *ds, EntsoeApi *eapi, EnergyAccountingConfig *config);
|
||||
void setup(AmsDataStorage *ds, EnergyAccountingConfig *config);
|
||||
void setEapi(EntsoeApi *eapi);
|
||||
void setTimezone(Timezone*);
|
||||
EnergyAccountingConfig* getConfig();
|
||||
bool update(AmsData* amsData);
|
||||
bool load();
|
||||
bool save();
|
||||
|
||||
double getUseThisHour();
|
||||
double getCostThisHour();
|
||||
double getUseToday();
|
||||
double getUseThisMonth();
|
||||
|
||||
double getProducedThisHour();
|
||||
double getProducedToday();
|
||||
double getProducedThisMonth();
|
||||
|
||||
double getCostThisHour();
|
||||
double getCostToday();
|
||||
double getCostYesterday();
|
||||
double getUseThisMonth();
|
||||
double getCostThisMonth();
|
||||
double getCostLastMonth();
|
||||
uint16_t getCostLastMonth();
|
||||
|
||||
float getMonthMax();
|
||||
uint8_t getCurrentThreshold();
|
||||
float getPeak(uint8_t);
|
||||
|
||||
EnergyAccountingData getData();
|
||||
void setData(EnergyAccountingData&);
|
||||
@@ -49,11 +72,11 @@ private:
|
||||
Timezone *tz = NULL;
|
||||
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
|
||||
double use, costHour, costDay;
|
||||
double produce;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
bool load();
|
||||
bool calcDayUse();
|
||||
void calcDayCost();
|
||||
bool updateMax(uint16_t val, uint8_t day);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,10 +14,32 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
config->tempSensorPin = 0xFF;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
analogReadResolution(13);
|
||||
analogRange = 8192;
|
||||
analogSetAttenuation(ADC_11db);
|
||||
#elif defined(ESP32)
|
||||
analogReadResolution(12);
|
||||
analogRange = 4096;
|
||||
analogSetAttenuation(ADC_6db);
|
||||
#endif
|
||||
if(config->vccPin > 0 && config->vccPin < 40) {
|
||||
getAdcChannel(config->vccPin, voltAdc);
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
getAdcChannel(config->vccPin, voltAdc);
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
if(voltAdc.unit == ADC_UNIT_1) {
|
||||
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_13, 1100, voltAdcChar);
|
||||
adc1_config_channel_atten((adc1_channel_t) voltAdc.channel, ADC_ATTEN_DB_11);
|
||||
} else if(voltAdc.unit == ADC_UNIT_2) {
|
||||
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_13, 1100, voltAdcChar);
|
||||
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_11);
|
||||
}
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
getAdcChannel(config->vccPin, voltAdc);
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
if(voltAdc.unit == ADC_UNIT_1) {
|
||||
voltAdcChar = (esp_adc_cal_characteristics_t*) calloc(1, sizeof(esp_adc_cal_characteristics_t));
|
||||
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar);
|
||||
@@ -27,10 +49,10 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
esp_adc_cal_value_t adcVal = esp_adc_cal_characterize((adc_unit_t) voltAdc.unit, ADC_ATTEN_DB_6, ADC_WIDTH_BIT_12, 1100, voltAdcChar);
|
||||
adc2_config_channel_atten((adc2_channel_t) voltAdc.channel, ADC_ATTEN_DB_6);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
}
|
||||
#else
|
||||
pinMode(config->vccPin, INPUT);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
voltAdc.unit = 0xFF;
|
||||
voltAdc.channel = 0xFF;
|
||||
@@ -109,6 +131,16 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_7;
|
||||
break;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
case ADC1_CHANNEL_8_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_8;
|
||||
break;
|
||||
case ADC1_CHANNEL_9_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_1;
|
||||
config.channel = ADC1_CHANNEL_9;
|
||||
break;
|
||||
#endif
|
||||
case ADC2_CHANNEL_0_GPIO_NUM:
|
||||
config.unit = ADC_UNIT_2;
|
||||
config.channel = ADC2_CHANNEL_0;
|
||||
@@ -156,7 +188,7 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
||||
double HwTools::getVcc() {
|
||||
double volts = 0.0;
|
||||
if(config->vccPin != 0xFF) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32)
|
||||
#if defined(ESP32)
|
||||
if(voltAdc.unit != 0xFF) {
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
@@ -164,7 +196,11 @@ double HwTools::getVcc() {
|
||||
x += adc1_get_raw((adc1_channel_t) voltAdc.channel);
|
||||
} else if(voltAdc.unit == ADC_UNIT_2) {
|
||||
int v = 0;
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_13, &v);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32)
|
||||
adc2_get_raw((adc2_channel_t) voltAdc.channel, ADC_WIDTH_BIT_12, &v);
|
||||
#endif
|
||||
x += v;
|
||||
}
|
||||
}
|
||||
@@ -176,20 +212,22 @@ double HwTools::getVcc() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
}
|
||||
volts = x / 40950;
|
||||
volts = (x * 3.3) / 10.0 / analogRange;
|
||||
}
|
||||
#else
|
||||
uint32_t x = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
x += analogRead(config->vccPin);
|
||||
}
|
||||
volts = x / 10240;
|
||||
volts = (x * 3.3) / 10.0 / analogRange;
|
||||
#endif
|
||||
} else {
|
||||
#if defined(ESP8266)
|
||||
volts = ESP.getVcc() / 1024.0;
|
||||
#endif
|
||||
}
|
||||
if(volts == 0.0) return 0.0;
|
||||
|
||||
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
|
||||
volts *= ((double) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
|
||||
}
|
||||
@@ -303,12 +341,7 @@ double HwTools::getTemperature() {
|
||||
double HwTools::getTemperatureAnalog() {
|
||||
if(config->tempAnalogSensorPin != 0xFF) {
|
||||
float adcCalibrationFactor = 1.06587;
|
||||
int volts;
|
||||
#if defined(ESP8266)
|
||||
volts = (analogRead(config->tempAnalogSensorPin) / 1024.0) * 3.3;
|
||||
#elif defined(ESP32)
|
||||
volts = (analogRead(config->tempAnalogSensorPin) / 4095.0) * 3.3;
|
||||
#endif
|
||||
int volts = ((double) analogRead(config->tempAnalogSensorPin) / analogRange) * 3.3;
|
||||
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
|
||||
}
|
||||
return DEVICE_DISCONNECTED_C;
|
||||
@@ -340,8 +373,8 @@ bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
|
||||
if(!ledOn(color)) return false;
|
||||
delay(50);
|
||||
ledOff(color);
|
||||
if(i != blink)
|
||||
delay(50);
|
||||
if(i != blink-1)
|
||||
delay(200);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public:
|
||||
|
||||
HwTools() {};
|
||||
private:
|
||||
uint16_t analogRange = 1024;
|
||||
AdcConfig voltAdc, tempAdc;
|
||||
#if defined(ESP32)
|
||||
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
#include "IEC6205621.h"
|
||||
#include "ams/crc.h"
|
||||
|
||||
IEC6205621::IEC6205621(String payload) {
|
||||
if(payload.length() < 16)
|
||||
IEC6205621::IEC6205621(const char* p) {
|
||||
if(strlen(p) < 16)
|
||||
return;
|
||||
|
||||
String payload(p+1);
|
||||
int crc_pos = payload.lastIndexOf("!");
|
||||
String crc = payload.substring(crc_pos+1, crc_pos+5);
|
||||
//uint16_t crc_calc = crc16_x25((uint8_t*) (payload.startsWith("/") ? p+1 : p), crc_pos);
|
||||
|
||||
//Serial.printf("CRC %s :: %04X\n", crc.c_str(), crc_calc);
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
|
||||
|
||||
if(listId.startsWith("ADN")) {
|
||||
meterType = AmsTypeAidon;
|
||||
listId = listId.substring(0,4);
|
||||
@@ -21,7 +30,7 @@ IEC6205621::IEC6205621(String payload) {
|
||||
} else if(listId.startsWith("XMX")) {
|
||||
meterType = AmsTypeLandis;
|
||||
listId = listId.substring(0,6);
|
||||
} else if(listId.startsWith("Ene")) {
|
||||
} else if(listId.startsWith("Ene") || listId.startsWith("EST")) {
|
||||
meterType = AmsTypeSagemcom;
|
||||
listId = listId.substring(0,4);
|
||||
} else {
|
||||
@@ -55,10 +64,10 @@ IEC6205621::IEC6205621(String payload) {
|
||||
meterTimestamp = makeTime(tm); // TODO: Adjust for time zone
|
||||
}
|
||||
|
||||
activeImportPower = (uint16_t) (extractDouble(payload, "1.7.0") * 1000);
|
||||
activeExportPower = (uint16_t) (extractDouble(payload, "2.7.0") * 1000);
|
||||
reactiveImportPower = (uint16_t) (extractDouble(payload, "3.7.0") * 1000);
|
||||
reactiveExportPower = (uint16_t) (extractDouble(payload, "4.7.0") * 1000);
|
||||
activeImportPower = (uint16_t) (extractDouble(payload, "1.7.0"));
|
||||
activeExportPower = (uint16_t) (extractDouble(payload, "2.7.0"));
|
||||
reactiveImportPower = (uint16_t) (extractDouble(payload, "3.7.0"));
|
||||
reactiveExportPower = (uint16_t) (extractDouble(payload, "4.7.0"));
|
||||
|
||||
if(activeImportPower > 0)
|
||||
listType = 1;
|
||||
@@ -73,11 +82,40 @@ IEC6205621::IEC6205621(String payload) {
|
||||
|
||||
if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0)
|
||||
listType = 2;
|
||||
|
||||
double val = 0.0;
|
||||
|
||||
activeImportCounter = extractDouble(payload, "1.8.0");
|
||||
activeExportCounter = extractDouble(payload, "2.8.0");
|
||||
reactiveImportCounter = extractDouble(payload, "3.8.0");
|
||||
reactiveExportCounter = extractDouble(payload, "4.8.0");
|
||||
val = extractDouble(payload, "1.8.0");
|
||||
if(val == 0) {
|
||||
for(int i = 1; i < 9; i++) {
|
||||
val += extractDouble(payload, "1.8." + String(i,10));
|
||||
}
|
||||
}
|
||||
if(val > 0) activeImportCounter = val / 1000;
|
||||
|
||||
val = extractDouble(payload, "2.8.0");
|
||||
if(val == 0) {
|
||||
for(int i = 1; i < 9; i++) {
|
||||
val += extractDouble(payload, "2.8." + String(i,10));
|
||||
}
|
||||
}
|
||||
if(val > 0) activeExportCounter = val / 1000;
|
||||
|
||||
val = extractDouble(payload, "3.8.0");
|
||||
if(val == 0) {
|
||||
for(int i = 1; i < 9; i++) {
|
||||
val += extractDouble(payload, "3.8." + String(i,10));
|
||||
}
|
||||
}
|
||||
if(val > 0) reactiveImportCounter = val / 1000;
|
||||
|
||||
val = extractDouble(payload, "4.8.0");
|
||||
if(val == 0) {
|
||||
for(int i = 1; i < 9; i++) {
|
||||
val += extractDouble(payload, "4.8." + String(i,10));
|
||||
}
|
||||
}
|
||||
if(val > 0) reactiveExportCounter = val / 1000;
|
||||
|
||||
if(activeImportCounter > 0 || activeExportCounter > 0 || reactiveImportCounter > 0 || reactiveExportCounter > 0)
|
||||
listType = 3;
|
||||
@@ -104,5 +142,14 @@ String IEC6205621::extract(String payload, String obis) {
|
||||
}
|
||||
|
||||
double IEC6205621::extractDouble(String payload, String obis) {
|
||||
return extract(payload, obis).toDouble();
|
||||
String str = extract(payload, obis);
|
||||
if(str.isEmpty()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
int a = str.indexOf("*");
|
||||
String val = str.substring(0,a);
|
||||
String unit = str.substring(a+1);
|
||||
|
||||
return unit.startsWith("k") ? val.toDouble() * 1000 : val.toDouble();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
#ifndef _IEC62056_21_H
|
||||
#define _IEC62056_21_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "AmsData.h"
|
||||
|
||||
class IEC6205621 : public AmsData {
|
||||
public:
|
||||
IEC6205621(String payload);
|
||||
IEC6205621(const char* payload);
|
||||
|
||||
private:
|
||||
String extract(String payload, String obis);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#include "IEC6205675.h"
|
||||
#include "lwip/def.h"
|
||||
#include "Timezone.h"
|
||||
#include "ams/ntohll.h"
|
||||
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, CosemDateTime packageTimestamp, HDLCConfig* hc) {
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
|
||||
double val;
|
||||
char str[64];
|
||||
|
||||
@@ -10,7 +11,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
this->packageTimestamp = getTimestamp(packageTimestamp);
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
|
||||
if(val == NOVALUE) {
|
||||
@@ -107,7 +108,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
case CosemTypeOctetString: {
|
||||
if(data->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) data;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
time_t ts = decodeCosemDateTime(amst->dt);
|
||||
meterTimestamp = tz.toUTC(ts);
|
||||
}
|
||||
}
|
||||
@@ -157,8 +158,8 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
}
|
||||
}
|
||||
// Try system title
|
||||
if(meterType == AmsTypeUnknown && hc != NULL) {
|
||||
if(memcmp(hc->system_title, "SAGY", 4)) {
|
||||
if(meterType == AmsTypeUnknown) {
|
||||
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
|
||||
meterType = AmsTypeSagemcom;
|
||||
}
|
||||
}
|
||||
@@ -264,8 +265,10 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
CosemData* meterTs = findObis(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
|
||||
if(meterTs != NULL) {
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
if(meterType == AmsTypeKamstrup || meterType == AmsTypeAidon) {
|
||||
time_t ts = decodeCosemDateTime(amst->dt);
|
||||
if(meterType == AmsTypeAidon) {
|
||||
meterTimestamp = ts - 3600;
|
||||
} else if(meterType == AmsTypeKamstrup) {
|
||||
meterTimestamp = tz.toUTC(ts);
|
||||
} else {
|
||||
meterTimestamp = ts;
|
||||
@@ -293,6 +296,38 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
l3PowerFactor = val;
|
||||
}
|
||||
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L1, sizeof(AMS_OBIS_ACTIVE_IMPORT_L1), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l1activeImportPower = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L2, sizeof(AMS_OBIS_ACTIVE_IMPORT_L2), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l2activeImportPower = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L3, sizeof(AMS_OBIS_ACTIVE_IMPORT_L3), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l3activeImportPower = val;
|
||||
}
|
||||
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L1, sizeof(AMS_OBIS_ACTIVE_EXPORT_L1), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l1activeExportPower = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l2activeExportPower = val;
|
||||
}
|
||||
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L3, sizeof(AMS_OBIS_ACTIVE_EXPORT_L3), ((char *) (d)));
|
||||
if (val != NOVALUE) {
|
||||
listType = 4;
|
||||
l3activeExportPower = val;
|
||||
}
|
||||
|
||||
if(meterType == AmsTypeKamstrup) {
|
||||
if(listType >= 3) {
|
||||
activeImportCounter *= 10;
|
||||
@@ -325,7 +360,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
CosemData* meterTs = getCosemDataAt(1, ((char *) (d)));
|
||||
if(meterTs != NULL) {
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
time_t ts = decodeCosemDateTime(amst->dt);
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
|
||||
@@ -333,12 +368,16 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
if(mid != NULL) {
|
||||
switch(mid->base.type) {
|
||||
case CosemTypeString:
|
||||
memcpy(&meterId, mid->str.data, mid->str.length);
|
||||
memcpy(str, mid->oct.data, mid->oct.length);
|
||||
str[mid->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
meterId[mid->str.length] = 0;
|
||||
break;
|
||||
case CosemTypeOctetString:
|
||||
memcpy(&meterId, mid->oct.data, mid->oct.length);
|
||||
meterId[mid->oct.length] = 0;
|
||||
memcpy(str, mid->str.data, mid->str.length);
|
||||
str[mid->str.length] = 0x00;
|
||||
meterId = String(str);
|
||||
meterId[mid->str.length] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -407,7 +446,7 @@ CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
|
||||
CosemData* item = (CosemData*) ptr;
|
||||
int i = 0;
|
||||
char* pos = (char*) ptr;
|
||||
do {
|
||||
while(pos-ptr < 900) {
|
||||
item = (CosemData*) pos;
|
||||
if(i == index) return item;
|
||||
switch(item->base.type) {
|
||||
@@ -438,8 +477,7 @@ CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
|
||||
pos += 2;
|
||||
}
|
||||
i++;
|
||||
if(pos-ptr > 900) break;
|
||||
} while(item->base.type != HDLC_FLAG);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -447,7 +485,7 @@ CosemData* IEC6205675::findObis(uint8_t* obis, int matchlength, const char* ptr)
|
||||
CosemData* item = (CosemData*) ptr;
|
||||
int ret = 0;
|
||||
char* pos = (char*) ptr;
|
||||
do {
|
||||
while(pos-ptr < 900) {
|
||||
item = (CosemData*) pos;
|
||||
if(ret == 1) return item;
|
||||
switch(item->base.type) {
|
||||
@@ -485,8 +523,7 @@ CosemData* IEC6205675::findObis(uint8_t* obis, int matchlength, const char* ptr)
|
||||
default:
|
||||
pos += 2;
|
||||
}
|
||||
if(pos-ptr > 900) break;
|
||||
} while(item->base.type != HDLC_FLAG);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -572,31 +609,10 @@ time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr)
|
||||
case CosemTypeOctetString: {
|
||||
if(item->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* ts = (AmsOctetTimestamp*) item;
|
||||
return getTimestamp(ts->dt);
|
||||
return decodeCosemDateTime(ts->dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
time_t IEC6205675::getTimestamp(CosemDateTime timestamp) {
|
||||
tmElements_t tm;
|
||||
uint16_t year = ntohs(timestamp.year);
|
||||
if(year < 1970) return 0;
|
||||
tm.Year = year - 1970;
|
||||
tm.Month = timestamp.month;
|
||||
tm.Day = timestamp.dayOfMonth;
|
||||
tm.Hour = timestamp.hour;
|
||||
tm.Minute = timestamp.minute;
|
||||
tm.Second = timestamp.second;
|
||||
|
||||
//Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status);
|
||||
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(timestamp.deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#define _IEC62056_7_5_H
|
||||
|
||||
#include "AmsData.h"
|
||||
#include "ams/hdlc.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "ams/DataParser.h"
|
||||
#include "ams/Cosem.h"
|
||||
|
||||
#define NOVALUE 0xFFFFFFFF
|
||||
|
||||
@@ -14,7 +15,7 @@ struct AmsOctetTimestamp {
|
||||
|
||||
class IEC6205675 : public AmsData {
|
||||
public:
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, CosemDateTime packageTimestamp, HDLCConfig* hc);
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
|
||||
|
||||
private:
|
||||
CosemData* getCosemDataAt(uint8_t index, const char* ptr);
|
||||
@@ -23,7 +24,6 @@ private:
|
||||
double getNumber(uint8_t* obis, int matchlength, const char* ptr);
|
||||
double getNumber(CosemData*);
|
||||
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
|
||||
time_t getTimestamp(CosemDateTime timestamp);
|
||||
|
||||
uint8_t AMS_OBIS_VERSION[4] = { 0, 2, 129, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL[4] = { 96, 1, 1, 255 };
|
||||
@@ -49,6 +49,12 @@ private:
|
||||
uint8_t AMS_OBIS_POWER_FACTOR_L1[4] = { 33, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_POWER_FACTOR_L2[4] = { 53, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_POWER_FACTOR_L3[4] = { 73, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L1[4] = { 21, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2[4] = { 41, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3[4] = { 61, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 };
|
||||
|
||||
};
|
||||
#endif
|
||||
|
||||
111
src/LNG.cpp
Normal file
111
src/LNG.cpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#include "LNG.h"
|
||||
#include "lwip/def.h"
|
||||
#include "ams/Cosem.h"
|
||||
|
||||
LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
|
||||
LngHeader* h = (LngHeader*) payload;
|
||||
if(h->tag == CosemTypeStructure && h->arrayTag == CosemTypeArray) {
|
||||
meterType = AmsTypeLng;
|
||||
this->packageTimestamp = ctx.timestamp;
|
||||
|
||||
uint8_t* ptr = (uint8_t*) &h[1];
|
||||
uint8_t* data = ptr + (18*h->arrayLength); // Skip descriptors
|
||||
|
||||
uint16_t o170 = 0, o270 = 0;
|
||||
uint16_t o181 = 0, o182 = 0;
|
||||
uint16_t o281 = 0, o282 = 0;
|
||||
LngObisDescriptor* descriptor = (LngObisDescriptor*) ptr;
|
||||
for(uint8_t x = 0; x < h->arrayLength-1; x++) {
|
||||
ptr = (uint8_t*) &descriptor[1];
|
||||
descriptor = (LngObisDescriptor*) ptr;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(L&G) OBIS %d.%d.%d with type 0x%02X", descriptor->obis[2], descriptor->obis[3], descriptor->obis[4], *data);
|
||||
|
||||
CosemData* item = (CosemData*) data;
|
||||
if(descriptor->obis[2] == 1) {
|
||||
if(descriptor->obis[3] == 7) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
o170 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
}
|
||||
} else if(descriptor->obis[3] == 8) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
activeImportCounter = ntohl(item->dlu.data) / 1000.0;
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
o181 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
o182 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
}
|
||||
}
|
||||
} else if(descriptor->obis[2] == 2) {
|
||||
if(descriptor->obis[3] == 7) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
o270 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
}
|
||||
} else if(descriptor->obis[3] == 8) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
activeExportCounter = ntohl(item->dlu.data) / 1000.0;
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
o281 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
o282 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
}
|
||||
}
|
||||
} else if(descriptor->obis[2] == 96) {
|
||||
if(descriptor->obis[3] == 1) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
char str[item->oct.length+1];
|
||||
memcpy(str, item->oct.data, item->oct.length);
|
||||
str[item->oct.length] = '\0';
|
||||
meterId = String(str);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %s (oct)", str);
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
char str[item->oct.length+1];
|
||||
memcpy(str, item->oct.data, item->oct.length);
|
||||
str[item->oct.length] = '\0';
|
||||
meterModel = String(str);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %s (oct)", str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("\n");
|
||||
|
||||
if(o170 > 0 || o270 > 0) {
|
||||
int32_t sum = o170-o270;
|
||||
if(sum > 0) {
|
||||
listType = listType >= 1 ? listType : 1;
|
||||
activeImportPower = sum;
|
||||
} else {
|
||||
listType = listType >= 2 ? listType : 2;
|
||||
activeExportPower = sum * -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(o181 > 0 || o182 > 0) {
|
||||
activeImportCounter = (o181 + o182) / 1000.0;
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
}
|
||||
if(o281 > 0 || o282 > 0) {
|
||||
activeExportCounter = (o281 + o282) / 1000.0;
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
}
|
||||
|
||||
if((*data) == 0x09) {
|
||||
data += (*(data+1))+2;
|
||||
} else {
|
||||
data += 5;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/LNG.h
Normal file
30
src/LNG.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef _LNG_H
|
||||
#define _LNG_H
|
||||
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "ams/DataParser.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct LngHeader {
|
||||
uint8_t tag;
|
||||
uint8_t values;
|
||||
uint8_t arrayTag;
|
||||
uint8_t arrayLength;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct LngObisDescriptor {
|
||||
uint8_t ignore1[5];
|
||||
uint8_t octetTag;
|
||||
uint8_t octetLength;
|
||||
uint8_t obis[6];
|
||||
uint8_t ignore2[5];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
class LNG : public AmsData {
|
||||
public:
|
||||
LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,57 +0,0 @@
|
||||
#include "Arduino.h"
|
||||
#include "MbusAssembler.h"
|
||||
#include "ams/hdlc.h"
|
||||
|
||||
MbusAssembler::MbusAssembler() {
|
||||
buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ?
|
||||
}
|
||||
|
||||
uint8_t MbusAssembler::append(const uint8_t* d, int length) {
|
||||
MbusHeader* h = (MbusHeader*) d;
|
||||
uint8_t* ptr = (uint8_t*) &h[1];
|
||||
|
||||
uint8_t len = h->len1;
|
||||
|
||||
uint8_t control = *ptr;
|
||||
ptr++; len--;
|
||||
|
||||
uint8_t address = *ptr;
|
||||
ptr++; len--;
|
||||
|
||||
uint8_t ci = *ptr;
|
||||
ptr++; len--;
|
||||
|
||||
uint8_t stsap = *ptr;
|
||||
ptr++; len--;
|
||||
|
||||
uint8_t dtsap = *ptr;
|
||||
ptr++; len--;
|
||||
|
||||
uint8_t sequenceNumber = ci & 0x0F;
|
||||
if(sequenceNumber == 0) {
|
||||
memcpy(buf, d, length - 2); // Do not include FCS and MBUS_STOP
|
||||
buf[6] = 0x10; // Mark that this is a single, complete frame
|
||||
pos = length - 2;
|
||||
lastSequenceNumber = 0;
|
||||
return 0;
|
||||
} else if(pos + len > 1024 || sequenceNumber != (lastSequenceNumber + 1)) { // TODO return error
|
||||
pos = 0;
|
||||
lastSequenceNumber = -1;
|
||||
return -1;
|
||||
} else {
|
||||
if(len > length) return -1;
|
||||
memcpy(buf + pos, ptr, len);
|
||||
pos += len;
|
||||
lastSequenceNumber = sequenceNumber;
|
||||
return 0;
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
uint16_t MbusAssembler::write(const uint8_t* d) {
|
||||
buf[1] = buf[2] = 0x00;
|
||||
buf[pos++] = mbusChecksum(buf+4, pos-4);
|
||||
buf[pos++] = MBUS_END;
|
||||
memcpy((uint8_t *) d, buf, pos);
|
||||
return pos;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef _MBUS_ASSEMBLER_H
|
||||
#define _MBUS_ASSEMBLER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class MbusAssembler {
|
||||
public:
|
||||
MbusAssembler();
|
||||
uint8_t append(const uint8_t* d, int length);
|
||||
uint16_t write(const uint8_t* d);
|
||||
|
||||
private:
|
||||
uint16_t pos = 0;
|
||||
uint8_t *buf;
|
||||
uint8_t lastSequenceNumber = -1;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "Uptime.h"
|
||||
|
||||
uint32_t _uptime_last_value = 0;
|
||||
uint32_t _uptime_rollovers = 0;
|
||||
|
||||
uint64_t millis64() {
|
||||
uint32_t new_low32 = millis();
|
||||
if (new_low32 < _uptime_last_value) _uptime_rollovers++;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
static uint32_t _uptime_last_value = 0;
|
||||
static uint32_t _uptime_rollovers = 0;
|
||||
uint64_t millis64();
|
||||
|
||||
#endif
|
||||
|
||||
24
src/ams/Cosem.cpp
Normal file
24
src/ams/Cosem.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "Cosem.h"
|
||||
#include "lwip/def.h"
|
||||
#include <TimeLib.h>
|
||||
|
||||
time_t decodeCosemDateTime(CosemDateTime timestamp) {
|
||||
tmElements_t tm;
|
||||
uint16_t year = ntohs(timestamp.year);
|
||||
if(year < 1970) return 0;
|
||||
tm.Year = year - 1970;
|
||||
tm.Month = timestamp.month;
|
||||
tm.Day = timestamp.dayOfMonth;
|
||||
tm.Hour = timestamp.hour;
|
||||
tm.Minute = timestamp.minute;
|
||||
tm.Second = timestamp.second;
|
||||
|
||||
//Serial.printf("\nY: %d, M: %d, D: %d, h: %d, m: %d, s: %d, deviation: 0x%2X, status: 0x%1X\n", tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second, timestamp.deviation, timestamp.status);
|
||||
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(timestamp.deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
92
src/ams/Cosem.h
Normal file
92
src/ams/Cosem.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#ifndef _COSEM_H
|
||||
#define _COSEM_H
|
||||
|
||||
#include "lwip/def.h"
|
||||
|
||||
// Blue book, Table 2
|
||||
enum CosemType {
|
||||
CosemTypeNull = 0x00,
|
||||
CosemTypeArray = 0x01,
|
||||
CosemTypeStructure = 0x02,
|
||||
CosemTypeOctetString = 0x09,
|
||||
CosemTypeString = 0x0A,
|
||||
CosemTypeDLongSigned = 0x05,
|
||||
CosemTypeDLongUnsigned = 0x06,
|
||||
CosemTypeLongSigned = 0x10,
|
||||
CosemTypeLongUnsigned = 0x12,
|
||||
CosemTypeLong64Signed = 0x14,
|
||||
CosemTypeLong64Unsigned = 0x15,
|
||||
CosemTypeDateTime = 0x19
|
||||
};
|
||||
|
||||
struct CosemBasic {
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemString {
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
uint8_t data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLongSigned {
|
||||
uint8_t type;
|
||||
int16_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLongUnsigned {
|
||||
uint8_t type;
|
||||
uint16_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemDLongSigned {
|
||||
uint8_t type;
|
||||
int32_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemDLongUnsigned {
|
||||
uint8_t type;
|
||||
uint32_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLong64Signed {
|
||||
uint8_t type;
|
||||
int64_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLong64Unsigned {
|
||||
uint8_t type;
|
||||
uint64_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemDateTime {
|
||||
uint8_t type;
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t dayOfMonth;
|
||||
uint8_t dayOfWeek;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t hundredths;
|
||||
int16_t deviation;
|
||||
uint8_t status;
|
||||
} __attribute__((packed));
|
||||
|
||||
typedef union {
|
||||
struct CosemBasic base;
|
||||
struct CosemString str;
|
||||
struct CosemString oct;
|
||||
struct CosemLongSigned ls;
|
||||
struct CosemLongUnsigned lu;
|
||||
struct CosemDLongSigned dls;
|
||||
struct CosemDLongUnsigned dlu;
|
||||
struct CosemLong64Signed l64s;
|
||||
struct CosemLong64Unsigned l64u;
|
||||
struct CosemDateTime dt;
|
||||
} CosemData;
|
||||
|
||||
time_t decodeCosemDateTime(CosemDateTime timestamp);
|
||||
|
||||
#endif
|
||||
31
src/ams/DataParser.h
Normal file
31
src/ams/DataParser.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef _DATAPASERSER_H
|
||||
#define _DATAPASERSER_H
|
||||
|
||||
#define DATA_TAG_NONE 0x00
|
||||
#define DATA_TAG_AUTO 0x01
|
||||
#define DATA_TAG_HDLC 0x7E
|
||||
#define DATA_TAG_LLC 0xE6
|
||||
#define DATA_TAG_DLMS 0x0F
|
||||
#define DATA_TAG_DSMR 0x2F
|
||||
#define DATA_TAG_MBUS 0x68
|
||||
#define DATA_TAG_GBT 0xE0
|
||||
#define DATA_TAG_GCM 0xDB
|
||||
|
||||
#define DATA_PARSE_OK 0
|
||||
#define DATA_PARSE_FAIL -1
|
||||
#define DATA_PARSE_INCOMPLETE -2
|
||||
#define DATA_PARSE_BOUNDRY_FLAG_MISSING -3
|
||||
#define DATA_PARSE_HEADER_CHECKSUM_ERROR -4
|
||||
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
|
||||
#define DATA_PARSE_INTERMEDIATE_SEGMENT -6
|
||||
#define DATA_PARSE_FINAL_SEGMENT -7
|
||||
#define DATA_PARSE_UNKNOWN_DATA -9
|
||||
|
||||
struct DataParserContext {
|
||||
uint8_t type;
|
||||
uint16_t length;
|
||||
time_t timestamp;
|
||||
uint8_t system_title[8];
|
||||
};
|
||||
|
||||
#endif
|
||||
13
src/ams/DataParsers.h
Normal file
13
src/ams/DataParsers.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef _DATAPASERSERS_H
|
||||
#define _DATAPASERSERS_H
|
||||
|
||||
#include "HdlcParser.h"
|
||||
#include "DlmsParser.h"
|
||||
#include "DsmrParser.h"
|
||||
#include "MbusParser.h"
|
||||
#include "GbtParser.h"
|
||||
#include "GcmParser.h"
|
||||
#include "LlcParser.h"
|
||||
|
||||
#endif
|
||||
|
||||
38
src/ams/DlmsParser.cpp
Normal file
38
src/ams/DlmsParser.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "DlmsParser.h"
|
||||
#include "Cosem.h"
|
||||
|
||||
int8_t DLMSParser::parse(uint8_t *buf, DataParserContext &ctx) {
|
||||
if(ctx.length < 6) return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
uint8_t* ptr = buf+1;
|
||||
ptr += 4; // Skip invoke ID and priority
|
||||
|
||||
CosemData* item = (CosemData*) ptr;
|
||||
if(item->base.type == CosemTypeOctetString) {
|
||||
if(item->base.length == 0x0C) {
|
||||
CosemDateTime* dateTime = (CosemDateTime*) (ptr+1);
|
||||
ctx.timestamp = decodeCosemDateTime(*dateTime);
|
||||
}
|
||||
uint8_t len = 5+14;
|
||||
ctx.length -= len;
|
||||
return len;
|
||||
} else if(item->base.type == CosemTypeNull) {
|
||||
ctx.timestamp = 0;
|
||||
uint8_t len = 5+1;
|
||||
ctx.length -= len;
|
||||
return len;
|
||||
} else if(item->base.type == CosemTypeDateTime) {
|
||||
CosemDateTime* dateTime = (CosemDateTime*) (ptr);
|
||||
ctx.timestamp = decodeCosemDateTime(*dateTime);
|
||||
uint8_t len = 5+13;
|
||||
ctx.length -= len;
|
||||
return len;
|
||||
} else if(item->base.type == 0x0C) { // Kamstrup bug...
|
||||
CosemDateTime* dateTime = (CosemDateTime*) (ptr);
|
||||
ctx.timestamp = decodeCosemDateTime(*dateTime);
|
||||
uint8_t len = 5+13;
|
||||
ctx.length -= len;
|
||||
return len;
|
||||
}
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
12
src/ams/DlmsParser.h
Normal file
12
src/ams/DlmsParser.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#ifndef _DLMSPARSER_H
|
||||
#define _DLMSPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
|
||||
class DLMSParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
};
|
||||
|
||||
#endif
|
||||
21
src/ams/DsmrParser.cpp
Normal file
21
src/ams/DsmrParser.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#include "DsmrParser.h"
|
||||
|
||||
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
|
||||
uint16_t crcPos = 0;
|
||||
bool reachedEnd = verified;
|
||||
uint8_t lastByte = 0x00;
|
||||
for(int pos = 0; pos < ctx.length; pos++) {
|
||||
uint8_t b = *(buf+pos);
|
||||
if(pos == 0 && b != '/') return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
if(pos > 0 && b == '!' && lastByte == '\n') crcPos = pos+1;
|
||||
if(crcPos > 0 && b == '\n') reachedEnd = true;
|
||||
lastByte = b;
|
||||
}
|
||||
if(!reachedEnd) return DATA_PARSE_INCOMPLETE;
|
||||
buf[ctx.length+1] = '\0';
|
||||
if(crcPos > 0) {
|
||||
// TODO: CRC
|
||||
Serial.printf("CRC: %s\n", buf+crcPos);
|
||||
}
|
||||
return DATA_PARSE_OK;
|
||||
}
|
||||
13
src/ams/DsmrParser.h
Normal file
13
src/ams/DsmrParser.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef _DSMRPARSER_H
|
||||
#define _DSMRPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
|
||||
class DSMRParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx, bool verified);
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
32
src/ams/GbtParser.cpp
Normal file
32
src/ams/GbtParser.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "GbtParser.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
int8_t GBTParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
GBTHeader* h = (GBTHeader*) (d);
|
||||
uint16_t sequence = ntohs(h->sequence);
|
||||
|
||||
if(h->flag != GBT_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
|
||||
if(sequence == 1) {
|
||||
if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ?
|
||||
pos = 0;
|
||||
} else if(lastSequenceNumber != sequence-1) {
|
||||
return DATA_PARSE_FAIL;
|
||||
}
|
||||
|
||||
if(buf == NULL) return DATA_PARSE_FAIL;
|
||||
|
||||
uint8_t* ptr = (uint8_t*) &h[1];
|
||||
memcpy(buf + pos, ptr, h->size);
|
||||
pos += h->size;
|
||||
lastSequenceNumber = sequence;
|
||||
|
||||
if((h->control & 0x80) == 0x00) {
|
||||
return DATA_PARSE_INTERMEDIATE_SEGMENT;
|
||||
} else {
|
||||
memcpy((uint8_t *) d, buf, pos);
|
||||
}
|
||||
ctx.length = pos;
|
||||
return DATA_PARSE_OK;
|
||||
|
||||
}
|
||||
26
src/ams/GbtParser.h
Normal file
26
src/ams/GbtParser.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef _GBTPARSER_H
|
||||
#define _GBTPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
|
||||
#define GBT_TAG 0xE0
|
||||
|
||||
typedef struct GBTHeader {
|
||||
uint8_t flag;
|
||||
uint8_t control;
|
||||
uint16_t sequence;
|
||||
uint16_t sequenceAck;
|
||||
uint8_t size;
|
||||
} __attribute__((packed)) GBTHeader;
|
||||
|
||||
class GBTParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
private:
|
||||
uint8_t lastSequenceNumber = 0;
|
||||
uint16_t pos = 0;
|
||||
uint8_t *buf = NULL;
|
||||
};
|
||||
|
||||
#endif
|
||||
131
src/ams/GcmParser.cpp
Normal file
131
src/ams/GcmParser.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "GcmParser.h"
|
||||
#include "lwip/def.h"
|
||||
#if defined(ESP8266)
|
||||
#include "bearssl/bearssl.h"
|
||||
#elif defined(ESP32)
|
||||
#include "mbedtls/gcm.h"
|
||||
#endif
|
||||
|
||||
GCMParser::GCMParser(uint8_t *encryption_key, uint8_t *authentication_key) {
|
||||
memcpy(this->encryption_key, encryption_key, 16);
|
||||
memcpy(this->authentication_key, authentication_key, 16);
|
||||
}
|
||||
|
||||
int8_t GCMParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
if(ctx.length < 12) return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
uint8_t* ptr = (uint8_t*) d;
|
||||
if(*ptr != GCM_TAG) return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
ptr++;
|
||||
// Encrypted APDU
|
||||
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
|
||||
|
||||
uint8_t systemTitleLength = *ptr;
|
||||
ptr++;
|
||||
|
||||
uint8_t initialization_vector[12];
|
||||
memcpy(ctx.system_title, ptr, systemTitleLength);
|
||||
memcpy(initialization_vector, ctx.system_title, systemTitleLength);
|
||||
|
||||
int len = 0;
|
||||
int headersize = 2 + systemTitleLength;
|
||||
ptr += systemTitleLength;
|
||||
if(((*ptr) & 0xFF) == 0x81) {
|
||||
ptr++;
|
||||
len = *ptr;
|
||||
// 1-byte payload length
|
||||
ptr++;
|
||||
headersize += 2;
|
||||
} else if(((*ptr) & 0xFF) == 0x82) {
|
||||
GCMSizeDef* h = (GCMSizeDef*) ptr;
|
||||
|
||||
// 2-byte payload length
|
||||
len = (ntohs(h->format) & 0xFFFF);
|
||||
|
||||
ptr += 3;
|
||||
headersize += 3;
|
||||
}
|
||||
if(len + headersize > ctx.length)
|
||||
return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
//Serial.printf("\nL: %d : %d, %d\n", length, len, headersize);
|
||||
|
||||
uint8_t additional_authenticated_data[17];
|
||||
memcpy(additional_authenticated_data, ptr, 1);
|
||||
|
||||
// Security tag
|
||||
uint8_t sec = *ptr;
|
||||
ptr++;
|
||||
headersize++;
|
||||
|
||||
// Frame counter
|
||||
memcpy(initialization_vector + 8, ptr, 4);
|
||||
ptr += 4;
|
||||
headersize += 4;
|
||||
|
||||
int footersize = 0;
|
||||
|
||||
// Authentication enabled
|
||||
uint8_t authentication_tag[12];
|
||||
uint8_t authkeylen = 0, aadlen = 0;
|
||||
if((sec & 0x10) == 0x10) {
|
||||
authkeylen = 12;
|
||||
aadlen = 17;
|
||||
footersize += authkeylen;
|
||||
memcpy(additional_authenticated_data + 1, authentication_key, 16);
|
||||
memcpy(authentication_tag, ptr + len - footersize - 5, authkeylen);
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
br_gcm_context gcmCtx;
|
||||
br_aes_ct_ctr_keys bc;
|
||||
br_aes_ct_ctr_init(&bc, encryption_key, 16);
|
||||
br_gcm_init(&gcmCtx, &bc.vtable, br_ghash_ctmul32);
|
||||
br_gcm_reset(&gcmCtx, initialization_vector, sizeof(initialization_vector));
|
||||
if(authkeylen > 0) {
|
||||
br_gcm_aad_inject(&gcmCtx, additional_authenticated_data, aadlen);
|
||||
}
|
||||
br_gcm_flip(&gcmCtx);
|
||||
br_gcm_run(&gcmCtx, 0, (void*) (ptr), len - authkeylen - 5); // 5 == security tag and frame counter
|
||||
if(authkeylen > 0 && br_gcm_check_tag_trunc(&gcmCtx, authentication_tag, authkeylen) != 1) {
|
||||
return GCM_AUTH_FAILED;
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
uint8_t cipher_text[len - authkeylen - 5];
|
||||
memcpy(cipher_text, ptr, len - authkeylen - 5);
|
||||
|
||||
mbedtls_gcm_context m_ctx;
|
||||
mbedtls_gcm_init(&m_ctx);
|
||||
int success = mbedtls_gcm_setkey(&m_ctx, MBEDTLS_CIPHER_ID_AES, encryption_key, 128);
|
||||
if (0 != success) {
|
||||
return GCM_ENCRYPTION_KEY_FAILED;
|
||||
}
|
||||
if (0 < authkeylen) {
|
||||
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), initialization_vector, sizeof(initialization_vector),
|
||||
additional_authenticated_data, aadlen, authentication_tag, authkeylen,
|
||||
cipher_text, (unsigned char*)(ptr));
|
||||
if (authkeylen > 0 && success == MBEDTLS_ERR_GCM_AUTH_FAILED) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return GCM_AUTH_FAILED;
|
||||
} else if(success == MBEDTLS_ERR_GCM_BAD_INPUT) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return GCM_DECRYPT_FAILED;
|
||||
}
|
||||
} else {
|
||||
success = mbedtls_gcm_starts(&m_ctx, MBEDTLS_GCM_DECRYPT, initialization_vector, sizeof(initialization_vector),NULL, 0);
|
||||
if (0 != success) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return GCM_DECRYPT_FAILED;
|
||||
}
|
||||
success = mbedtls_gcm_update(&m_ctx, sizeof(cipher_text), cipher_text, (unsigned char*)(ptr));
|
||||
if (0 != success) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return GCM_DECRYPT_FAILED;
|
||||
}
|
||||
}
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
#endif
|
||||
|
||||
ctx.length -= footersize + headersize;
|
||||
return ptr-d;
|
||||
}
|
||||
27
src/ams/GcmParser.h
Normal file
27
src/ams/GcmParser.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef _GCMPARSER_H
|
||||
#define _GCMPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
|
||||
#define GCM_TAG 0xDB
|
||||
#define GCM_AUTH_FAILED -51
|
||||
#define GCM_DECRYPT_FAILED -52
|
||||
#define GCM_ENCRYPTION_KEY_FAILED -53
|
||||
|
||||
typedef struct GCMSizeDef {
|
||||
uint8_t flag;
|
||||
uint16_t format;
|
||||
} __attribute__((packed)) GCMSizeDef;
|
||||
|
||||
|
||||
class GCMParser {
|
||||
public:
|
||||
GCMParser(uint8_t *encryption_key, uint8_t *authentication_key);
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
private:
|
||||
uint8_t encryption_key[16];
|
||||
uint8_t authentication_key[16];
|
||||
};
|
||||
|
||||
#endif
|
||||
56
src/ams/HdlcParser.cpp
Normal file
56
src/ams/HdlcParser.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include "HdlcParser.h"
|
||||
#include "lwip/def.h"
|
||||
#include "crc.h"
|
||||
|
||||
int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
int len;
|
||||
|
||||
uint8_t* ptr;
|
||||
if(ctx.length < 3)
|
||||
return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
HDLCHeader* h = (HDLCHeader*) d;
|
||||
ptr = (uint8_t*) &h[1];
|
||||
|
||||
// Frame format type 3
|
||||
if((h->format & 0xF0) == 0xA0) {
|
||||
// Length field (11 lsb of format)
|
||||
len = (ntohs(h->format) & 0x7FF) + 2;
|
||||
if(len > ctx.length)
|
||||
return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f);
|
||||
|
||||
// First and last byte should be HDLC_FLAG
|
||||
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
|
||||
// Verify FCS
|
||||
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
|
||||
// Skip destination address, LSB marks last byte
|
||||
while(((*ptr) & 0x01) == 0x00) {
|
||||
ptr++;
|
||||
}
|
||||
ptr++;
|
||||
|
||||
// Skip source address, LSB marks last byte
|
||||
while(((*ptr) & 0x01) == 0x00) {
|
||||
ptr++;
|
||||
}
|
||||
ptr++;
|
||||
|
||||
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
||||
|
||||
// Verify HCS
|
||||
if(ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
||||
return DATA_PARSE_HEADER_CHECKSUM_ERROR;
|
||||
ptr += 3;
|
||||
|
||||
// Exclude all of header and 3 byte footer
|
||||
ctx.length -= ptr-d+3;
|
||||
return ptr-d;
|
||||
}
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
29
src/ams/HdlcParser.h
Normal file
29
src/ams/HdlcParser.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#ifndef _HDLCPARSER_H
|
||||
#define _HDLCPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
|
||||
#define HDLC_FLAG 0x7E
|
||||
|
||||
typedef struct HDLCHeader {
|
||||
uint8_t flag;
|
||||
uint16_t format;
|
||||
} __attribute__((packed)) HDLCHeader;
|
||||
|
||||
typedef struct HDLCFooter {
|
||||
uint16_t fcs;
|
||||
uint8_t flag;
|
||||
} __attribute__((packed)) HDLCFooter;
|
||||
|
||||
typedef struct HDLC3CtrlHcs {
|
||||
uint8_t control;
|
||||
uint16_t hcs;
|
||||
} __attribute__((packed)) HDLC3CtrlHcs;
|
||||
|
||||
class HDLCParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
};
|
||||
|
||||
#endif
|
||||
6
src/ams/LlcParser.cpp
Normal file
6
src/ams/LlcParser.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "LlcParser.h"
|
||||
|
||||
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
|
||||
ctx.length -= 3;
|
||||
return 3;
|
||||
}
|
||||
18
src/ams/LlcParser.h
Normal file
18
src/ams/LlcParser.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef _LLCPARSER_H
|
||||
#define _LLCPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
|
||||
typedef struct LLCHeader {
|
||||
uint8_t dst;
|
||||
uint8_t src;
|
||||
uint8_t control;
|
||||
} __attribute__((packed)) LLCHeader;
|
||||
|
||||
class LLCParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
};
|
||||
|
||||
#endif
|
||||
87
src/ams/MbusParser.cpp
Normal file
87
src/ams/MbusParser.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "MbusParser.h"
|
||||
|
||||
int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
int len;
|
||||
int headersize = 3;
|
||||
int footersize = 1;
|
||||
|
||||
uint8_t* ptr;
|
||||
|
||||
// https://m-bus.com/documentation-wired/06-application-layer
|
||||
if(ctx.length < 4)
|
||||
return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
MbusHeader* mh = (MbusHeader*) d;
|
||||
if(mh->flag1 != MBUS_START || mh->flag2 != MBUS_START)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
|
||||
// First two bytes is 1-byte length value repeated. Only used for last segment
|
||||
if(mh->len1 != mh->len2)
|
||||
return MBUS_FRAME_LENGTH_NOT_EQUAL;
|
||||
len = mh->len1;
|
||||
ptr = (uint8_t*) &mh[1];
|
||||
headersize = 4;
|
||||
footersize = 2;
|
||||
|
||||
if(len == 0x00)
|
||||
len = ctx.length - headersize - footersize;
|
||||
// Payload can max be 255 bytes, so I think the following case is only valid for austrian meters
|
||||
if(len < headersize)
|
||||
len += 256;
|
||||
|
||||
if((headersize + footersize + len) > ctx.length)
|
||||
return DATA_PARSE_INCOMPLETE;
|
||||
|
||||
MbusFooter* mf = (MbusFooter*) (d + len + headersize);
|
||||
if(mf->flag != MBUS_END)
|
||||
return DATA_PARSE_BOUNDRY_FLAG_MISSING;
|
||||
if(checksum(d + headersize, len) != mf->fcs)
|
||||
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
|
||||
|
||||
ptr += 2; len -= 2;
|
||||
|
||||
// Control information field
|
||||
uint8_t ci = *ptr;
|
||||
|
||||
// Skip CI, STSAP and DTSAP
|
||||
ptr += 3; len -= 3;
|
||||
|
||||
// Bits 7 6 5 4 3 2 1 0
|
||||
// 0 0 0 Finished Sequence number
|
||||
uint8_t sequenceNumber = (ci & 0x0F);
|
||||
if((ci & 0x10) == 0x00) { // Not finished yet
|
||||
if(sequenceNumber == 0) {
|
||||
if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024); // TODO find out from first package ?
|
||||
pos = 0;
|
||||
} else if(buf == NULL || pos + len > 1024 || sequenceNumber != (lastSequenceNumber + 1)) {
|
||||
return DATA_PARSE_FAIL;
|
||||
}
|
||||
memcpy(buf+pos, ptr, len);
|
||||
pos += len;
|
||||
lastSequenceNumber = sequenceNumber;
|
||||
return DATA_PARSE_INTERMEDIATE_SEGMENT;
|
||||
} else if(sequenceNumber > 0) { // This is the last frame of multiple, assembly needed
|
||||
if(buf == NULL || pos + len > 1024 || sequenceNumber != (lastSequenceNumber + 1)) {
|
||||
return DATA_PARSE_FAIL;
|
||||
}
|
||||
memcpy(buf+pos, ptr, len);
|
||||
pos += len;
|
||||
return DATA_PARSE_FINAL_SEGMENT;
|
||||
}
|
||||
return ptr-d;
|
||||
}
|
||||
|
||||
uint16_t MBUSParser::write(const uint8_t* d, DataParserContext &ctx) {
|
||||
if(buf != NULL) {
|
||||
memcpy((uint8_t *) d, buf, pos);
|
||||
ctx.length = pos;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t MBUSParser::checksum(const uint8_t* p, int len) {
|
||||
uint8_t ret = 0;
|
||||
while(len--)
|
||||
ret += *p++;
|
||||
return ret;
|
||||
}
|
||||
34
src/ams/MbusParser.h
Normal file
34
src/ams/MbusParser.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef _MBUSPARSER_H
|
||||
#define _MBUSPARSER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "DataParser.h"
|
||||
|
||||
#define MBUS_START 0x68
|
||||
#define MBUS_END 0x16
|
||||
#define MBUS_FRAME_LENGTH_NOT_EQUAL -41
|
||||
|
||||
typedef struct MbusHeader {
|
||||
uint8_t flag1;
|
||||
uint8_t len1;
|
||||
uint8_t len2;
|
||||
uint8_t flag2;
|
||||
} __attribute__((packed)) MbusHeader;
|
||||
|
||||
typedef struct MbusFooter {
|
||||
uint8_t fcs;
|
||||
uint8_t flag;
|
||||
} __attribute__((packed)) MbusFooter;
|
||||
|
||||
class MBUSParser {
|
||||
public:
|
||||
int8_t parse(uint8_t *buf, DataParserContext &ctx);
|
||||
uint16_t write(const uint8_t* d, DataParserContext &ctx);
|
||||
private:
|
||||
uint8_t lastSequenceNumber = 0;
|
||||
uint16_t pos = 0;
|
||||
uint8_t *buf = NULL;
|
||||
uint8_t checksum(const uint8_t* p, int len);
|
||||
};
|
||||
|
||||
#endif
|
||||
308
src/ams/hdlc.cpp
308
src/ams/hdlc.cpp
@@ -1,308 +0,0 @@
|
||||
#include "Arduino.h"
|
||||
#include "hdlc.h"
|
||||
#include "crc.h"
|
||||
#include "lwip/def.h"
|
||||
#if defined(ESP8266)
|
||||
#include "bearssl/bearssl.h"
|
||||
#elif defined(ESP32)
|
||||
#include "mbedtls/gcm.h"
|
||||
#endif
|
||||
|
||||
void mbus_hexdump(const uint8_t* buf, int len) {
|
||||
printf("\nDUMP (%db) [ ", len);
|
||||
for(const uint8_t* p = buf; p-buf < len; ++p)
|
||||
printf("%02X ", *p);
|
||||
printf("]\n");
|
||||
}
|
||||
|
||||
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp) {
|
||||
int len;
|
||||
int headersize = 3;
|
||||
int footersize = 1;
|
||||
|
||||
uint8_t flag = *d;
|
||||
|
||||
uint8_t* ptr;
|
||||
if(flag == HDLC_FLAG) {
|
||||
if(length < 3)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
HDLCHeader* h = (HDLCHeader*) d;
|
||||
ptr = (uint8_t*) &h[1];
|
||||
|
||||
// Frame format type 3
|
||||
if((h->format & 0xF0) == 0xA0) {
|
||||
// Length field (11 lsb of format)
|
||||
len = (ntohs(h->format) & 0x7FF) + 2;
|
||||
if(len > length)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f);
|
||||
footersize = sizeof *f;
|
||||
|
||||
// First and last byte should be MBUS_HAN_TAG
|
||||
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
|
||||
return HDLC_BOUNDRY_FLAG_MISSING;
|
||||
|
||||
// Verify FCS
|
||||
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
return HDLC_FCS_ERROR;
|
||||
|
||||
// Skip destination address, LSB marks last byte
|
||||
while(((*ptr) & 0x01) == 0x00) {
|
||||
ptr++;
|
||||
headersize++;
|
||||
}
|
||||
headersize++;
|
||||
ptr++;
|
||||
|
||||
// Skip source address, LSB marks last byte
|
||||
while(((*ptr) & 0x01) == 0x00) {
|
||||
ptr++;
|
||||
headersize++;
|
||||
}
|
||||
headersize++;
|
||||
ptr++;
|
||||
|
||||
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
||||
headersize += 3;
|
||||
|
||||
// Verify HCS
|
||||
if(ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
||||
return HDLC_HCS_ERROR;
|
||||
|
||||
ptr += sizeof *t3;
|
||||
|
||||
// Extract LLC
|
||||
HDLCLLC* llc = (HDLCLLC*) ptr;
|
||||
ptr += sizeof *llc;
|
||||
headersize += sizeof *llc;
|
||||
} else {
|
||||
return HDLC_UNKNOWN_DATA;
|
||||
}
|
||||
} else if(flag == MBUS_START) {
|
||||
// https://m-bus.com/documentation-wired/06-application-layer
|
||||
if(length < 4)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
MbusHeader* mh = (MbusHeader*) d;
|
||||
if(mh->flag1 != MBUS_START || mh->flag2 != MBUS_START)
|
||||
return MBUS_BOUNDRY_FLAG_MISSING;
|
||||
|
||||
// First two bytes is 1-byte length value repeated. Only used for last segment
|
||||
if(mh->len1 != mh->len2)
|
||||
return MBUS_FRAME_LENGTH_NOT_EQUAL;
|
||||
len = mh->len1;
|
||||
ptr = (uint8_t*) &mh[1];
|
||||
headersize = 4;
|
||||
footersize = 2;
|
||||
|
||||
if(len == 0x00)
|
||||
len = length - headersize - footersize;
|
||||
// Payload can max be 255 bytes, so I think the following case is only valid for austrian meters
|
||||
if(len < headersize)
|
||||
len += 256;
|
||||
|
||||
if((headersize + footersize + len) > length)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
MbusFooter* mf = (MbusFooter*) (d + len + headersize);
|
||||
if(mf->flag != MBUS_END)
|
||||
return MBUS_BOUNDRY_FLAG_MISSING;
|
||||
if(mbusChecksum(d + headersize, len) != mf->fcs)
|
||||
return MBUS_CHECKSUM_ERROR;
|
||||
|
||||
ptr += 2;
|
||||
|
||||
// Control information field
|
||||
uint8_t ci = *ptr;
|
||||
|
||||
// Bits 7 6 5 4 3 2 1 0
|
||||
// 0 0 0 Finished Sequence number
|
||||
uint8_t sequenceNumber = (ci & 0x0F);
|
||||
if((ci & 0x10) == 0x00) { // Not finished yet
|
||||
return MBUS_FRAME_INTERMEDIATE_SEGMENT;
|
||||
} else if(sequenceNumber > 0) { // This is the last frame of multiple, assembly needed
|
||||
return MBUS_FRAME_LAST_SEGMENT;
|
||||
}
|
||||
|
||||
// Skip CI, STSAP and DTSAP
|
||||
ptr += 3;
|
||||
headersize += 5; // And also control and address that we didn't skip earlier, needed these for checksum.
|
||||
} else {
|
||||
return HDLC_UNKNOWN_DATA;
|
||||
}
|
||||
|
||||
if(((*ptr) & 0xFF) == 0x0F) {
|
||||
// Unencrypted APDU
|
||||
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
||||
ptr += sizeof *adpu;
|
||||
|
||||
// ADPU timestamp
|
||||
CosemData* dateTime = (CosemData*) ptr;
|
||||
if(dateTime->base.type == CosemTypeOctetString) {
|
||||
if(dateTime->base.length == 0x0C) {
|
||||
memcpy(timestamp, ptr+1, dateTime->base.length+1);
|
||||
}
|
||||
ptr += 2 + dateTime->base.length;
|
||||
} else if(dateTime->base.type == CosemTypeNull) {
|
||||
timestamp = 0;
|
||||
ptr++;
|
||||
} else if(dateTime->base.type == CosemTypeDateTime) {
|
||||
memcpy(timestamp, ptr, dateTime->base.length);
|
||||
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
|
||||
memcpy(timestamp, ptr, 13);
|
||||
ptr += 13;
|
||||
} else {
|
||||
return HDLC_TIMESTAMP_UNKNOWN;
|
||||
}
|
||||
|
||||
return ptr-d;
|
||||
} else if(((*ptr) & 0xFF) == 0xDB) {
|
||||
if(length < headersize + 18)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
ptr++;
|
||||
// Encrypted APDU
|
||||
// http://www.weigu.lu/tutorials/sensors2bus/04_encryption/index.html
|
||||
if(config == NULL)
|
||||
return HDLC_ENCRYPTION_CONFIG_MISSING;
|
||||
|
||||
uint8_t systemTitleLength = *ptr;
|
||||
ptr++;
|
||||
memcpy(config->system_title, ptr, systemTitleLength);
|
||||
memcpy(config->initialization_vector, config->system_title, systemTitleLength);
|
||||
|
||||
headersize += 2 + systemTitleLength;
|
||||
ptr += systemTitleLength;
|
||||
if(((*ptr) & 0xFF) == 0x81) {
|
||||
ptr++;
|
||||
len = *ptr;
|
||||
// 1-byte payload length
|
||||
ptr++;
|
||||
headersize += 2;
|
||||
} else if(((*ptr) & 0xFF) == 0x82) {
|
||||
HDLCHeader* h = (HDLCHeader*) ptr;
|
||||
|
||||
// 2-byte payload length
|
||||
len = (ntohs(h->format) & 0xFFFF);
|
||||
|
||||
ptr += 3;
|
||||
headersize += 3;
|
||||
}
|
||||
if(len + headersize + footersize > length)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
//Serial.printf("\nL: %d : %d, %d : %d\n", length, len, headersize, footersize);
|
||||
|
||||
memcpy(config->additional_authenticated_data, ptr, 1);
|
||||
|
||||
// Security tag
|
||||
uint8_t sec = *ptr;
|
||||
ptr++;
|
||||
headersize++;
|
||||
|
||||
// Frame counter
|
||||
memcpy(config->initialization_vector + 8, ptr, 4);
|
||||
ptr += 4;
|
||||
headersize += 4;
|
||||
|
||||
// Authentication enabled
|
||||
uint8_t authkeylen = 0, aadlen = 0;
|
||||
if((sec & 0x10) == 0x10) {
|
||||
authkeylen = 12;
|
||||
aadlen = 17;
|
||||
footersize += authkeylen;
|
||||
memcpy(config->additional_authenticated_data + 1, config->authentication_key, 16);
|
||||
memcpy(config->authentication_tag, ptr + len - footersize - 2, authkeylen);
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
br_gcm_context gcmCtx;
|
||||
br_aes_ct_ctr_keys bc;
|
||||
br_aes_ct_ctr_init(&bc, config->encryption_key, 16);
|
||||
br_gcm_init(&gcmCtx, &bc.vtable, br_ghash_ctmul32);
|
||||
br_gcm_reset(&gcmCtx, config->initialization_vector, sizeof(config->initialization_vector));
|
||||
if(authkeylen > 0) {
|
||||
br_gcm_aad_inject(&gcmCtx, config->additional_authenticated_data, aadlen);
|
||||
}
|
||||
br_gcm_flip(&gcmCtx);
|
||||
br_gcm_run(&gcmCtx, 0, (void*) (ptr), len - authkeylen - 5); // 5 == security tag and frame counter
|
||||
if(authkeylen > 0 && br_gcm_check_tag_trunc(&gcmCtx, config->authentication_tag, authkeylen) != 1) {
|
||||
return HDLC_ENCRYPTION_AUTH_FAILED;
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
uint8_t cipher_text[len - authkeylen - 5];
|
||||
memcpy(cipher_text, ptr, len - authkeylen - 5);
|
||||
|
||||
mbedtls_gcm_context m_ctx;
|
||||
mbedtls_gcm_init(&m_ctx);
|
||||
int success = mbedtls_gcm_setkey(&m_ctx, MBEDTLS_CIPHER_ID_AES, config->encryption_key, 128);
|
||||
if (0 != success) {
|
||||
return HDLC_ENCRYPTION_KEY_FAILED;
|
||||
}
|
||||
if (0 < authkeylen) {
|
||||
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), config->initialization_vector, sizeof(config->initialization_vector),
|
||||
config->additional_authenticated_data, aadlen, config->authentication_tag, authkeylen,
|
||||
cipher_text, (unsigned char*)(ptr));
|
||||
if (authkeylen > 0 && success == MBEDTLS_ERR_GCM_AUTH_FAILED) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return HDLC_ENCRYPTION_AUTH_FAILED;
|
||||
} else if(success == MBEDTLS_ERR_GCM_BAD_INPUT) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
||||
}
|
||||
} else {
|
||||
success = mbedtls_gcm_starts(&m_ctx, MBEDTLS_GCM_DECRYPT, config->initialization_vector, sizeof(config->initialization_vector),NULL, 0);
|
||||
if (0 != success) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
||||
}
|
||||
success = mbedtls_gcm_update(&m_ctx, sizeof(cipher_text), cipher_text, (unsigned char*)(ptr));
|
||||
if (0 != success) {
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
return HDLC_ENCRYPTION_DECRYPT_FAILED;
|
||||
}
|
||||
}
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
#endif
|
||||
|
||||
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
||||
ptr += sizeof *adpu;
|
||||
|
||||
// ADPU timestamp
|
||||
CosemData* dateTime = (CosemData*) ptr;
|
||||
if(dateTime->base.type == CosemTypeOctetString) {
|
||||
if(dateTime->base.length == 0x0C) {
|
||||
memcpy(timestamp, ptr+1, dateTime->base.length);
|
||||
}
|
||||
ptr += 2 + dateTime->base.length;
|
||||
} else if(dateTime->base.type == CosemTypeNull) {
|
||||
timestamp = 0;
|
||||
ptr++;
|
||||
} else if(dateTime->base.type == CosemTypeDateTime) {
|
||||
memcpy(timestamp, ptr, dateTime->base.length);
|
||||
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
|
||||
memcpy(timestamp, ptr, 13);
|
||||
ptr += 13;
|
||||
} else {
|
||||
return HDLC_TIMESTAMP_UNKNOWN;
|
||||
}
|
||||
|
||||
return ptr-d;
|
||||
}
|
||||
|
||||
// Unknown payload
|
||||
return HDLC_UNKNOWN_DATA;
|
||||
}
|
||||
|
||||
uint8_t mbusChecksum(const uint8_t* p, int len) {
|
||||
uint8_t ret = 0;
|
||||
while(len--)
|
||||
ret += *p++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t ntohll(uint64_t x) {
|
||||
return (((uint64_t)ntohl((uint32_t)x)) << 32) + ntohl(x >> 32);
|
||||
}
|
||||
167
src/ams/hdlc.h
167
src/ams/hdlc.h
@@ -1,167 +0,0 @@
|
||||
#ifndef _HDLC_H
|
||||
#define _HDLC_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <stdint.h>
|
||||
#include "lwip/def.h"
|
||||
|
||||
#define HDLC_FLAG 0x7E
|
||||
#define HDLC_BOUNDRY_FLAG_MISSING -1
|
||||
#define HDLC_FCS_ERROR -2
|
||||
#define HDLC_HCS_ERROR -3
|
||||
#define HDLC_FRAME_INCOMPLETE -4
|
||||
#define HDLC_UNKNOWN_DATA -9
|
||||
#define HDLC_ENCRYPTION_CONFIG_MISSING -90
|
||||
#define HDLC_ENCRYPTION_AUTH_FAILED -91
|
||||
#define HDLC_ENCRYPTION_KEY_FAILED -92
|
||||
#define HDLC_ENCRYPTION_DECRYPT_FAILED -93
|
||||
#define HDLC_TIMESTAMP_UNKNOWN -99
|
||||
|
||||
#define MBUS_START 0x68
|
||||
#define MBUS_END 0x16
|
||||
#define MBUS_BOUNDRY_FLAG_MISSING -1
|
||||
#define MBUS_FRAME_LENGTH_NOT_EQUAL -40
|
||||
#define MBUS_FRAME_INTERMEDIATE_SEGMENT -41
|
||||
#define MBUS_FRAME_LAST_SEGMENT -42
|
||||
#define MBUS_CHECKSUM_ERROR -2
|
||||
|
||||
struct HDLCConfig {
|
||||
uint8_t encryption_key[32];
|
||||
uint8_t authentication_key[32];
|
||||
uint8_t system_title[8];
|
||||
uint8_t initialization_vector[12];
|
||||
uint8_t additional_authenticated_data[17];
|
||||
uint8_t authentication_tag[12];
|
||||
};
|
||||
|
||||
typedef struct HDLCHeader {
|
||||
uint8_t flag;
|
||||
uint16_t format;
|
||||
} __attribute__((packed)) HDLCHeader;
|
||||
|
||||
typedef struct HDLCFooter {
|
||||
uint16_t fcs;
|
||||
uint8_t flag;
|
||||
} __attribute__((packed)) HDLCFooter;
|
||||
|
||||
typedef struct HDLC3CtrlHcs {
|
||||
uint8_t control;
|
||||
uint16_t hcs;
|
||||
} __attribute__((packed)) HDLC3CtrlHcs;
|
||||
|
||||
typedef struct HDLCLLC {
|
||||
uint8_t dst;
|
||||
uint8_t src;
|
||||
uint8_t control;
|
||||
} __attribute__((packed)) HDLCLLC;
|
||||
|
||||
typedef struct HDLCADPU {
|
||||
uint8_t flag;
|
||||
uint32_t id;
|
||||
} __attribute__((packed)) HDLCADPU;
|
||||
|
||||
typedef struct MbusHeader {
|
||||
uint8_t flag1;
|
||||
uint8_t len1;
|
||||
uint8_t len2;
|
||||
uint8_t flag2;
|
||||
} __attribute__((packed)) MbusHeader;
|
||||
|
||||
typedef struct MbusFooter {
|
||||
uint8_t fcs;
|
||||
uint8_t flag;
|
||||
} __attribute__((packed)) MbusFooter;
|
||||
|
||||
|
||||
// Blue book, Table 2
|
||||
enum CosemType {
|
||||
CosemTypeNull = 0x00,
|
||||
CosemTypeArray = 0x01,
|
||||
CosemTypeStructure = 0x02,
|
||||
CosemTypeOctetString = 0x09,
|
||||
CosemTypeString = 0x0A,
|
||||
CosemTypeDLongSigned = 0x05,
|
||||
CosemTypeDLongUnsigned = 0x06,
|
||||
CosemTypeLongSigned = 0x10,
|
||||
CosemTypeLongUnsigned = 0x12,
|
||||
CosemTypeLong64Signed = 0x14,
|
||||
CosemTypeLong64Unsigned = 0x15,
|
||||
CosemTypeDateTime = 0x19
|
||||
};
|
||||
|
||||
struct CosemBasic {
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemString {
|
||||
uint8_t type;
|
||||
uint8_t length;
|
||||
uint8_t data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLongSigned {
|
||||
uint8_t type;
|
||||
int16_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLongUnsigned {
|
||||
uint8_t type;
|
||||
uint16_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemDLongSigned {
|
||||
uint8_t type;
|
||||
int32_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemDLongUnsigned {
|
||||
uint8_t type;
|
||||
uint32_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLong64Signed {
|
||||
uint8_t type;
|
||||
int64_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemLong64Unsigned {
|
||||
uint8_t type;
|
||||
uint64_t data;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct CosemDateTime {
|
||||
uint8_t type;
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t dayOfMonth;
|
||||
uint8_t dayOfWeek;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t hundredths;
|
||||
int16_t deviation;
|
||||
uint8_t status;
|
||||
} __attribute__((packed));
|
||||
|
||||
typedef union {
|
||||
struct CosemBasic base;
|
||||
struct CosemString str;
|
||||
struct CosemString oct;
|
||||
struct CosemLongSigned ls;
|
||||
struct CosemLongUnsigned lu;
|
||||
struct CosemDLongSigned dls;
|
||||
struct CosemDLongUnsigned dlu;
|
||||
struct CosemLong64Signed l64s;
|
||||
struct CosemLong64Unsigned l64u;
|
||||
struct CosemDateTime dt;
|
||||
} CosemData;
|
||||
|
||||
void mbus_hexdump(const uint8_t* buf, int len);
|
||||
int HDLC_validate(const uint8_t* d, int len, HDLCConfig* config, CosemDateTime* timestamp);
|
||||
|
||||
uint8_t mbusChecksum(const uint8_t* p, int len);
|
||||
|
||||
uint64_t ntohll(uint64_t x);
|
||||
|
||||
#endif
|
||||
5
src/ams/ntohll.cpp
Normal file
5
src/ams/ntohll.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "ntohll.h"
|
||||
|
||||
uint64_t ntohll(uint64_t x) {
|
||||
return (((uint64_t)ntohl((uint32_t)x)) << 32) + ntohl(x >> 32);
|
||||
}
|
||||
8
src/ams/ntohll.h
Normal file
8
src/ams/ntohll.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef _NTOHLL_H
|
||||
#define _NTOHLL_H
|
||||
|
||||
#include "lwip/def.h"
|
||||
|
||||
uint64_t ntohll(uint64_t x);
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "DnbCurrParser.h"
|
||||
#include "Arduino.h"
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
float DnbCurrParser::getValue() {
|
||||
return value;
|
||||
@@ -23,20 +22,21 @@ void DnbCurrParser::flush() {
|
||||
}
|
||||
|
||||
size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
write(buffer[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t DnbCurrParser::write(uint8_t byte) {
|
||||
if(pos >= 64) pos = 0;
|
||||
if(pos >= 128) pos = 0;
|
||||
if(pos == 0) {
|
||||
if(byte == '<') {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
} else if(byte == '>') {
|
||||
buf[pos++] = byte;
|
||||
buf[pos++] = '\0';
|
||||
if(strncmp(buf, "<Series", 7) == 0) {
|
||||
for(int i = 0; i < pos; i++) {
|
||||
if(strncmp(buf+i, "UNIT_MULT=\"", 11) == 0) {
|
||||
|
||||
@@ -18,7 +18,7 @@ private:
|
||||
uint8_t scale = 0;
|
||||
float value = 1.0;
|
||||
|
||||
char buf[64];
|
||||
char buf[128];
|
||||
uint8_t pos = 0;
|
||||
uint8_t mode = 0;
|
||||
};
|
||||
|
||||
@@ -5,6 +5,10 @@ EntsoeA44Parser::EntsoeA44Parser() {
|
||||
for(int i = 0; i < 24; i++) points[i] = ENTSOE_NO_VALUE;
|
||||
}
|
||||
|
||||
EntsoeA44Parser::~EntsoeA44Parser() {
|
||||
|
||||
}
|
||||
|
||||
char* EntsoeA44Parser::getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
@@ -35,7 +39,7 @@ void EntsoeA44Parser::flush() {
|
||||
}
|
||||
|
||||
size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
write(buffer[i]);
|
||||
}
|
||||
return size;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
class EntsoeA44Parser: public Stream {
|
||||
public:
|
||||
EntsoeA44Parser();
|
||||
virtual ~EntsoeA44Parser();
|
||||
|
||||
char* getCurrency();
|
||||
char* getMeasurementUnit();
|
||||
|
||||
@@ -27,7 +27,10 @@ void EntsoeApi::setup(EntsoeConfig& config) {
|
||||
this->config = new EntsoeConfig();
|
||||
}
|
||||
memcpy(this->config, &config, sizeof(config));
|
||||
lastCurrencyFetch = 0;
|
||||
lastTodayFetch = lastTomorrowFetch = lastCurrencyFetch = 0;
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) delete tomorrow;
|
||||
today = tomorrow = NULL;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getToken() {
|
||||
@@ -103,11 +106,11 @@ bool EntsoeApi::loop() {
|
||||
if(midnightMillis == 0) {
|
||||
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
|
||||
midnightMillis = now + (SECS_PER_DAY * 1000) - curDayMillis;
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %lu\n", midnightMillis);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Setting midnight millis %llu\n", midnightMillis);
|
||||
currentDay = tm.Day;
|
||||
return false;
|
||||
} else if(now > midnightMillis && currentDay != tm.Day) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lld\n", t);
|
||||
if(today != NULL) delete today;
|
||||
if(tomorrow != NULL) {
|
||||
today = tomorrow;
|
||||
|
||||
@@ -15,11 +15,12 @@ public:
|
||||
this->mqtt = mqtt;
|
||||
this->json = buf;
|
||||
};
|
||||
virtual ~AmsMqttHandler() {};
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
virtual bool publishPrices(EntsoeApi* eapi);
|
||||
virtual bool publishSystem(HwTools*);
|
||||
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*);
|
||||
|
||||
protected:
|
||||
MQTTClient* mqtt;
|
||||
|
||||
@@ -71,6 +71,6 @@ bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw) {
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include "web/root/ha1_json.h"
|
||||
#include "web/root/ha2_json.h"
|
||||
#include "web/root/ha3_json.h"
|
||||
#include "web/root/ha4_json.h"
|
||||
#include "web/root/jsonsys_json.h"
|
||||
#include "web/root/jsonprices_json.h"
|
||||
#include "web/root/hadiscover_json.h"
|
||||
#include "web/root/realtime_json.h"
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
@@ -24,17 +26,20 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
mqtt->publish(topic + "/energy", json);
|
||||
mqtt->loop();
|
||||
}
|
||||
String meterModel = data->getMeterModel();
|
||||
meterModel.replace("\\", "\\\\");
|
||||
if(data->getListType() == 1) { // publish power counts
|
||||
snprintf_P(json, BufferSize, HA1_JSON,
|
||||
data->getActiveImportPower()
|
||||
);
|
||||
return mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() >= 2) { // publish power counts and volts/amps
|
||||
mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() <= 3) { // publish power counts and volts/amps
|
||||
snprintf_P(json, BufferSize, HA3_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
data->getMeterModel().c_str(),
|
||||
meterModel.c_str(),
|
||||
data->getActiveImportPower(),
|
||||
data->getReactiveImportPower(),
|
||||
data->getActiveExportPower(),
|
||||
@@ -44,15 +49,62 @@ bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, En
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage()
|
||||
);
|
||||
mqtt->publish(topic + "/power", json);
|
||||
} else if(data->getListType() == 4) { // publish power counts and volts/amps/phase power and PF
|
||||
snprintf_P(json, BufferSize, HA4_JSON,
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
meterModel.c_str(),
|
||||
data->getActiveImportPower(),
|
||||
data->getL1ActiveImportPower(),
|
||||
data->getL2ActiveImportPower(),
|
||||
data->getL3ActiveImportPower(),
|
||||
data->getReactiveImportPower(),
|
||||
data->getActiveExportPower(),
|
||||
data->getL1ActiveExportPower(),
|
||||
data->getL2ActiveExportPower(),
|
||||
data->getL3ActiveExportPower(),
|
||||
data->getReactiveExportPower(),
|
||||
data->getL1Current(),
|
||||
data->getL2Current(),
|
||||
data->getL3Current(),
|
||||
data->getL1Voltage(),
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getPowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL1PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor()
|
||||
);
|
||||
return mqtt->publish(topic + "/power", json);
|
||||
mqtt->publish(topic + "/power", json);
|
||||
}
|
||||
return false;
|
||||
|
||||
String peaks = "";
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i), 2);
|
||||
}
|
||||
snprintf_P(json, BufferSize, REALTIME_JSON,
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCostThisHour(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCostToday(),
|
||||
ea->getProducedToday(),
|
||||
ea->getUseThisMonth(),
|
||||
ea->getCostThisMonth(),
|
||||
ea->getProducedThisMonth()
|
||||
);
|
||||
mqtt->publish(topic + "/realtime", json);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
@@ -89,10 +141,11 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr, min3hr, min6hr;
|
||||
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[24] = {0};
|
||||
float values[24];
|
||||
for(int i = 0;i < 24; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
values[i] = val;
|
||||
@@ -138,7 +191,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
}
|
||||
|
||||
char ts1hr[21];
|
||||
char ts1hr[24];
|
||||
if(min1hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
|
||||
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
|
||||
@@ -146,7 +199,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts3hr[21];
|
||||
char ts3hr[24];
|
||||
if(min3hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
|
||||
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
|
||||
@@ -154,7 +207,7 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts6hr[21];
|
||||
char ts6hr[24];
|
||||
if(min6hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
|
||||
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
|
||||
@@ -183,10 +236,10 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
return mqtt->publish(topic + "/prices", json);
|
||||
return mqtt->publish(topic + "/prices", json, true, 0);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected()){
|
||||
sequence = 0;
|
||||
return false;
|
||||
@@ -199,7 +252,8 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
||||
(uint32_t) (millis64()/1000),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature()
|
||||
hw->getTemperature(),
|
||||
VERSION
|
||||
);
|
||||
mqtt->publish(topic + "/state", json);
|
||||
}
|
||||
@@ -212,27 +266,46 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw) {
|
||||
#endif
|
||||
String haUrl = "http://" + haUID + ".local/";
|
||||
// Could this be necessary? haUID.replace("-", "_");
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
|
||||
for(int i=0;i<sensors;i++){
|
||||
uint8_t peaks = 0;
|
||||
for(int i=0;i<HA_SENSOR_COUNT;i++) {
|
||||
HomeAssistantSensor sensor = HA_SENSORS[i];
|
||||
String uid = String(sensor.path);
|
||||
uid.replace(".", "");
|
||||
uid.replace("[", "");
|
||||
uid.replace("]", "");
|
||||
uid.replace("'", "");
|
||||
String uom = String(sensor.uom);
|
||||
if(strncmp(sensor.devcl, "monetary", 8) == 0) {
|
||||
if(eapi == NULL) continue;
|
||||
uom = String(eapi->getCurrency());
|
||||
}
|
||||
if(strncmp(sensor.path, "peaks[", 6) == 0) {
|
||||
if(peaks >= peakCount) continue;
|
||||
peaks++;
|
||||
}
|
||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||
FPSTR(HA_NAMES[i]),
|
||||
topic.c_str(), FPSTR(HA_TOPICS[i]),
|
||||
haUID.c_str(), FPSTR(HA_PARAMS[i]),
|
||||
haUID.c_str(), FPSTR(HA_PARAMS[i]),
|
||||
FPSTR(HA_UOM[i]),
|
||||
FPSTR(HA_PARAMS[i]),
|
||||
FPSTR(HA_DEVCL[i]),
|
||||
sensor.name,
|
||||
topic.c_str(), sensor.topic,
|
||||
haUID.c_str(), uid.c_str(),
|
||||
haUID.c_str(), uid.c_str(),
|
||||
uom.c_str(),
|
||||
sensor.path,
|
||||
sensor.devcl,
|
||||
haUID.c_str(),
|
||||
haName.c_str(),
|
||||
haModel.c_str(),
|
||||
VERSION,
|
||||
haManuf.c_str(),
|
||||
haUrl.c_str(),
|
||||
strlen_P(HA_STACL[i]) > 0 ? ", \"stat_cla\" :" : "",
|
||||
strlen_P(HA_STACL[i]) > 0 ? (char *) FPSTR(HA_STACL[i]) : ""
|
||||
strlen_P(sensor.stacl) > 0 ? ", \"stat_cla\" :" : "",
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : ""
|
||||
);
|
||||
mqtt->publish(haTopic + haUID + "_" + FPSTR(HA_PARAMS[i]) + "/config", json, true, 0);
|
||||
mqtt->publish(haTopic + haUID + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
}
|
||||
|
||||
autodiscoverInit = true;
|
||||
}
|
||||
if(listType>0) sequence++;
|
||||
|
||||
@@ -13,11 +13,9 @@ public:
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
private:
|
||||
static const uint8_t sensors = 17;
|
||||
|
||||
String haTopic = "homeassistant/sensor/";
|
||||
|
||||
String haName = "AMS reader";
|
||||
|
||||
@@ -3,12 +3,79 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
|
||||
const char* HA_TOPICS[17] PROGMEM = {"/state", "/state", "/state", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/power", "/energy", "/energy", "/energy", "/energy"};
|
||||
const char* HA_NAMES[17] PROGMEM = {"Status", "Supply volt", "Temperature", "Active import", "Reactive import", "Active export", "Reactive export", "L1 current", "L2 current", "L3 current",
|
||||
"L1 voltage", "L2 voltage", "L3 voltage", "Accumulated active import", "Accumulated active export", "Accumulated reactive import", "Accumulated reactive export"};
|
||||
const char* HA_PARAMS[17] PROGMEM = {"rssi", "vcc", "temp", "P", "Q", "PO", "QO", "I1", "I2", "I3", "U1", "U2", "U3", "tPI", "tPO", "tQI", "tQO"};
|
||||
const char* HA_UOM[17] PROGMEM = {"dBm", "V", "C", "W", "W", "W", "W", "A", "A", "A", "V", "V", "V", "kWh", "kWh", "kWh", "kWh"};
|
||||
const char* HA_DEVCL[17] PROGMEM = {"signal_strength", "voltage", "temperature", "power", "power", "power", "power", "current", "current", "current", "voltage", "voltage", "voltage", "energy", "energy", "energy", "energy"};
|
||||
const char* HA_STACL[17] PROGMEM = {"", "", "", "\"measurement\"", "\"measurement\"", "\"measurement\"", "\"measurement\"", "", "", "", "", "", "", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\"", "\"total_increasing\""};
|
||||
struct HomeAssistantSensor {
|
||||
const char* name;
|
||||
const char* topic;
|
||||
const char* path;
|
||||
const char* uom;
|
||||
const char* devcl;
|
||||
const char* stacl;
|
||||
};
|
||||
|
||||
|
||||
const uint8_t HA_SENSOR_COUNT PROGMEM = 60;
|
||||
HomeAssistantSensor HA_SENSORS[HA_SENSOR_COUNT] PROGMEM = {
|
||||
{"Status", "/state", "rssi", "dBm", "signal_strength", "\"measurement\""},
|
||||
{"Supply volt", "/state", "vcc", "V", "voltage", "\"measurement\""},
|
||||
{"Temperature", "/state", "temp", "C", "temperature", "\"measurement\""},
|
||||
{"Active import", "/power", "P", "W", "power", "\"measurement\""},
|
||||
{"L1 active import", "/power", "P1", "W", "power", "\"measurement\""},
|
||||
{"L2 active import", "/power", "P2", "W", "power", "\"measurement\""},
|
||||
{"L3 active import", "/power", "P3", "W", "power", "\"measurement\""},
|
||||
{"Reactive import", "/power", "Q", "VAr", "reactive_power", "\"measurement\""},
|
||||
{"Active export", "/power", "PO", "W", "power", "\"measurement\""},
|
||||
{"L1 active export", "/power", "PO1", "W", "power", "\"measurement\""},
|
||||
{"L2 active export", "/power", "PO2", "W", "power", "\"measurement\""},
|
||||
{"L3 active export", "/power", "PO3", "W", "power", "\"measurement\""},
|
||||
{"Reactive export", "/power", "QO", "VAr", "reactive_power", "\"measurement\""},
|
||||
{"L1 current", "/power", "I1", "A", "current", "\"measurement\""},
|
||||
{"L2 current", "/power", "I2", "A", "current", "\"measurement\""},
|
||||
{"L3 current", "/power", "I3", "A", "current", "\"measurement\""},
|
||||
{"L1 voltage", "/power", "U1", "V", "voltage", "\"measurement\""},
|
||||
{"L2 voltage", "/power", "U2", "V", "voltage", "\"measurement\""},
|
||||
{"L3 voltage", "/power", "U3", "V", "voltage", "\"measurement\""},
|
||||
{"Accumulated active import", "/energy", "tPI", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Accumulated active export", "/energy", "tPO", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Accumulated reactive import","/energy", "tQI", "kVArh","energy", "\"total_increasing\""},
|
||||
{"Accumulated reactive export","/energy", "tQO", "kVArh","energy", "\"total_increasing\""},
|
||||
{"Power factor", "/power", "PF", "", "power_factor", "\"measurement\""},
|
||||
{"L1 power factor", "/power", "PF1", "", "power_factor", "\"measurement\""},
|
||||
{"L2 power factor", "/power", "PF2", "", "power_factor", "\"measurement\""},
|
||||
{"L3 power factor", "/power", "PF3", "", "power_factor", "\"measurement\""},
|
||||
{"Price current hour", "/prices", "prices['0']", "", "monetary", ""},
|
||||
{"Price next hour", "/prices", "prices['1']", "", "monetary", ""},
|
||||
{"Price in two hour", "/prices", "prices['2']", "", "monetary", ""},
|
||||
{"Price in three hour", "/prices", "prices['3']", "", "monetary", ""},
|
||||
{"Price in four hour", "/prices", "prices['4']", "", "monetary", ""},
|
||||
{"Price in five hour", "/prices", "prices['5']", "", "monetary", ""},
|
||||
{"Price in six hour", "/prices", "prices['6']", "", "monetary", ""},
|
||||
{"Price in seven hour", "/prices", "prices['7']", "", "monetary", ""},
|
||||
{"Price in eight hour", "/prices", "prices['8']", "", "monetary", ""},
|
||||
{"Price in nine hour", "/prices", "prices['9']", "", "monetary", ""},
|
||||
{"Price in ten hour", "/prices", "prices['10']", "", "monetary", ""},
|
||||
{"Price in eleven hour", "/prices", "prices['11']", "", "monetary", ""},
|
||||
{"Minimum price ahead", "/prices", "prices.min", "", "monetary", ""},
|
||||
{"Maximum price ahead", "/prices", "prices.max", "", "monetary", ""},
|
||||
{"Cheapest 1hr period ahead", "/prices", "prices.cheapest1hr","", "timestamp", ""},
|
||||
{"Cheapest 3hr period ahead", "/prices", "prices.cheapest3hr","", "timestamp", ""},
|
||||
{"Cheapest 6hr period ahead", "/prices", "prices.cheapest6hr","", "timestamp", ""},
|
||||
{"Month max", "/realtime","max", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Tariff threshold", "/realtime","threshold", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour used", "/realtime","hour.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current hour cost", "/realtime","hour.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current hour produced", "/realtime","hour.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day used", "/realtime","day.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current day cost", "/realtime","day.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current day produced", "/realtime","day.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month used", "/realtime","month.use", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month cost", "/realtime","month.cost", "", "monetary", "\"total_increasing\""},
|
||||
{"Current month produced", "/realtime","month.produced", "kWh", "energy", "\"total_increasing\""},
|
||||
{"Current month peak 1", "/realtime","peaks[0]", "kWh", "energy", ""},
|
||||
{"Current month peak 2", "/realtime","peaks[1]", "kWh", "energy", ""},
|
||||
{"Current month peak 3", "/realtime","peaks[2]", "kWh", "energy", ""},
|
||||
{"Current month peak 4", "/realtime","peaks[3]", "kWh", "energy", ""},
|
||||
{"Current month peak 5", "/realtime","peaks[4]", "kWh", "energy", ""},
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "JsonMqttHandler.h"
|
||||
#include "version.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
#include "web/root/json1_json.h"
|
||||
@@ -12,6 +13,8 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
String meterModel = data->getMeterModel();
|
||||
meterModel.replace("\\", "\\\\");
|
||||
if(data->getListType() == 1) {
|
||||
snprintf_P(json, BufferSize, JSON1_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
@@ -25,7 +28,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax()
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
} else if(data->getListType() == 2) {
|
||||
@@ -39,7 +44,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
hw->getTemperature(),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
data->getMeterModel().c_str(),
|
||||
meterModel.c_str(),
|
||||
data->getActiveImportPower(),
|
||||
data->getReactiveImportPower(),
|
||||
data->getActiveExportPower(),
|
||||
@@ -53,7 +58,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax()
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
} else if(data->getListType() == 3) {
|
||||
@@ -67,7 +74,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
hw->getTemperature(),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
data->getMeterModel().c_str(),
|
||||
meterModel.c_str(),
|
||||
data->getActiveImportPower(),
|
||||
data->getReactiveImportPower(),
|
||||
data->getActiveExportPower(),
|
||||
@@ -86,7 +93,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax()
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
} else if(data->getListType() == 4) {
|
||||
@@ -100,10 +109,16 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
hw->getTemperature(),
|
||||
data->getListId().c_str(),
|
||||
data->getMeterId().c_str(),
|
||||
data->getMeterModel().c_str(),
|
||||
meterModel.c_str(),
|
||||
data->getActiveImportPower(),
|
||||
data->getL1ActiveImportPower(),
|
||||
data->getL2ActiveImportPower(),
|
||||
data->getL3ActiveImportPower(),
|
||||
data->getReactiveImportPower(),
|
||||
data->getActiveExportPower(),
|
||||
data->getL1ActiveExportPower(),
|
||||
data->getL2ActiveExportPower(),
|
||||
data->getL3ActiveExportPower(),
|
||||
data->getReactiveExportPower(),
|
||||
data->getL1Current(),
|
||||
data->getL2Current(),
|
||||
@@ -123,7 +138,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
ea->getUseThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getMonthMax()
|
||||
ea->getMonthMax(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
}
|
||||
@@ -132,8 +149,9 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
|
||||
bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw) {
|
||||
int count = hw->getTempSensorCount();
|
||||
if(count < 2)
|
||||
if(count < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
snprintf(json, 24, "{\"temperatures\":{");
|
||||
|
||||
@@ -162,10 +180,11 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr, min3hr, min6hr;
|
||||
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[24] = {0};
|
||||
float values[24];
|
||||
for(int i = 0;i < 24; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
values[i] = val;
|
||||
@@ -211,7 +230,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
}
|
||||
|
||||
char ts1hr[21];
|
||||
char ts1hr[24];
|
||||
if(min1hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
|
||||
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
|
||||
@@ -219,7 +238,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts3hr[21];
|
||||
char ts3hr[24];
|
||||
if(min3hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
|
||||
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
|
||||
@@ -227,7 +246,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts6hr[21];
|
||||
char ts6hr[24];
|
||||
if(min6hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
|
||||
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
|
||||
@@ -259,7 +278,7 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
return mqtt->publish(topic, json);
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw) {
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(init || topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
@@ -269,7 +288,8 @@ bool JsonMqttHandler::publishSystem(HwTools* hw) {
|
||||
(uint32_t) (millis64()/1000),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature()
|
||||
hw->getTemperature(),
|
||||
VERSION
|
||||
);
|
||||
init = mqtt->publish(topic, json);
|
||||
return init;
|
||||
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
private:
|
||||
String clientId;
|
||||
|
||||
@@ -11,6 +11,24 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
|
||||
}
|
||||
switch(data->getListType()) {
|
||||
case 4:
|
||||
if(full || meterState->getL1ActiveImportPower() != data->getL1ActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/l1", String(data->getL1ActiveImportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL2ActiveImportPower() != data->getL2ActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/l2", String(data->getL2ActiveImportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL3ActiveImportPower() != data->getL3ActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/l3", String(data->getL3ActiveImportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL1ActiveExportPower() != data->getL1ActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/l1", String(data->getL1ActiveExportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL2ActiveExportPower() != data->getL2ActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/l2", String(data->getL2ActiveExportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL3ActiveExportPower() != data->getL3ActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/l3", String(data->getL3ActiveExportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getPowerFactor() != data->getPowerFactor()) {
|
||||
mqtt->publish(topic + "/meter/powerfactor", String(data->getPowerFactor(), 2));
|
||||
}
|
||||
@@ -74,8 +92,13 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
|
||||
}
|
||||
mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
|
||||
mqtt->publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2));
|
||||
for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) {
|
||||
mqtt->publish(topic + "/realtime/import/peak/" + String(i, 10), String(ea->getPeak(i), 10), true, 0);
|
||||
}
|
||||
mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
||||
mqtt->publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0);
|
||||
mqtt->publish(topic + "/realtime/export/hour", String(ea->getProducedThisHour(), 3));
|
||||
mqtt->publish(topic + "/realtime/export/day", String(ea->getProducedToday(), 2));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,11 +124,11 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
time_t now = time(nullptr);
|
||||
|
||||
float min1hr, min3hr, min6hr;
|
||||
float min1hr = 0.0, min3hr = 0.0, min6hr = 0.0;
|
||||
int8_t min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
|
||||
float min = INT16_MAX, max = INT16_MIN;
|
||||
float values[34] = {0};
|
||||
memset(values, ENTSOE_NO_VALUE, 34);
|
||||
float values[34];
|
||||
for(int i = 0;i < 34; i++) values[i] = ENTSOE_NO_VALUE;
|
||||
for(uint8_t i = 0; i < 34; i++) {
|
||||
float val = eapi->getValueForHour(now, i);
|
||||
values[i] = val;
|
||||
@@ -152,7 +175,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
|
||||
}
|
||||
|
||||
char ts1hr[21];
|
||||
char ts1hr[24];
|
||||
if(min1hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
|
||||
//Serial.printf("1hr: %d %lu\n", min1hrIdx, ts);
|
||||
@@ -160,7 +183,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts3hr[21];
|
||||
char ts3hr[24];
|
||||
if(min3hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
|
||||
//Serial.printf("3hr: %d %lu\n", min3hrIdx, ts);
|
||||
@@ -168,7 +191,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
breakTime(ts, tm);
|
||||
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
char ts6hr[21];
|
||||
char ts6hr[24];
|
||||
if(min6hrIdx > -1) {
|
||||
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
|
||||
//Serial.printf("6hr: %d %lu\n", min6hrIdx, ts);
|
||||
@@ -206,7 +229,7 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishSystem(HwTools* hw) {
|
||||
bool RawMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
return false;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
|
||||
private:
|
||||
String topic;
|
||||
|
||||
@@ -4,20 +4,11 @@
|
||||
#include "hexutils.h"
|
||||
#include "AmsData.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include "root/head8266_html.h"
|
||||
#define HEAD_HTML HEAD8266_HTML
|
||||
#define HEAD_HTML_LEN HEAD8266_HTML_LEN
|
||||
#elif defined(ESP32)
|
||||
#include "root/head32_html.h"
|
||||
#define HEAD_HTML HEAD32_HTML
|
||||
#define HEAD_HTML_LEN HEAD32_HTML_LEN
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
#include "root/head_html.h"
|
||||
#include "root/foot_html.h"
|
||||
#include "root/index_html.h"
|
||||
#include "root/application_js.h"
|
||||
@@ -43,7 +34,6 @@
|
||||
#include "root/notfound_html.h"
|
||||
#include "root/data_json.h"
|
||||
#include "root/tempsensor_json.h"
|
||||
#include "root/lowmem_html.h"
|
||||
#include "root/dayplot_json.h"
|
||||
#include "root/monthplot_json.h"
|
||||
#include "root/energyprice_json.h"
|
||||
@@ -100,7 +90,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
server.on("/debugging", HTTP_GET, std::bind(&AmsWebServer::configDebugHtml, this));
|
||||
|
||||
server.on("/firmware", HTTP_GET, std::bind(&AmsWebServer::firmwareHtml, this));
|
||||
server.on("/firmware", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::firmwareUpload, this));
|
||||
server.on("/firmware", HTTP_POST, std::bind(&AmsWebServer::firmwarePost, this), std::bind(&AmsWebServer::firmwareUpload, this));
|
||||
server.on("/upgrade", HTTP_GET, std::bind(&AmsWebServer::firmwareDownload, this));
|
||||
server.on("/restart", HTTP_GET, std::bind(&AmsWebServer::restartHtml, this));
|
||||
server.on("/restart", HTTP_POST, std::bind(&AmsWebServer::restartPost, this));
|
||||
@@ -346,6 +336,9 @@ void AmsWebServer::configMeterHtml() {
|
||||
case AmsTypeSagemcom:
|
||||
manufacturer = "Sagemcom";
|
||||
break;
|
||||
case AmsTypeLng:
|
||||
manufacturer = "L&G";
|
||||
break;
|
||||
default:
|
||||
manufacturer = "Unknown";
|
||||
break;
|
||||
@@ -356,7 +349,11 @@ void AmsWebServer::configMeterHtml() {
|
||||
html.replace("{mid}", meterState->getMeterId());
|
||||
html.replace("{b}", String(meterConfig->baud));
|
||||
html.replace("{b2400}", meterConfig->baud == 2400 ? "selected" : "");
|
||||
html.replace("{b4800}", meterConfig->baud == 4800 ? "selected" : "");
|
||||
html.replace("{b9600}", meterConfig->baud == 9600 ? "selected" : "");
|
||||
html.replace("{b19200}", meterConfig->baud == 19200 ? "selected" : "");
|
||||
html.replace("{b38400}", meterConfig->baud == 38400 ? "selected" : "");
|
||||
html.replace("{b57600}", meterConfig->baud == 57600 ? "selected" : "");
|
||||
html.replace("{b115200}", meterConfig->baud == 115200 ? "selected" : "");
|
||||
html.replace("{c}", String(meterConfig->baud));
|
||||
html.replace("{c2}", meterConfig->parity == 2 ? "selected" : "");
|
||||
@@ -573,6 +570,20 @@ void AmsWebServer::configEntsoeHtml() {
|
||||
html.replace("{eaDk1}", strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : "");
|
||||
html.replace("{eaDk2}", strcmp(entsoe.area, "10YDK-2--------M") == 0 ? "selected" : "");
|
||||
|
||||
html.replace("{at}", strcmp(entsoe.area, "10YAT-APG------L") == 0 ? "selected" : "");
|
||||
html.replace("{be}", strcmp(entsoe.area, "10YBE----------2") == 0 ? "selected" : "");
|
||||
html.replace("{cz}", strcmp(entsoe.area, "10YCZ-CEPS-----N") == 0 ? "selected" : "");
|
||||
html.replace("{ee}", strcmp(entsoe.area, "10Y1001A1001A39I") == 0 ? "selected" : "");
|
||||
html.replace("{fi}", strcmp(entsoe.area, "10YFI-1--------U") == 0 ? "selected" : "");
|
||||
html.replace("{fr}", strcmp(entsoe.area, "10YFR-RTE------C") == 0 ? "selected" : "");
|
||||
html.replace("{de}", strcmp(entsoe.area, "10Y1001A1001A83F") == 0 ? "selected" : "");
|
||||
html.replace("{gb}", strcmp(entsoe.area, "10YGB----------A") == 0 ? "selected" : "");
|
||||
html.replace("{lv}", strcmp(entsoe.area, "10YLV-1001A00074") == 0 ? "selected" : "");
|
||||
html.replace("{lt}", strcmp(entsoe.area, "10YLT-1001A0008Q") == 0 ? "selected" : "");
|
||||
html.replace("{nl}", strcmp(entsoe.area, "10YNL----------L") == 0 ? "selected" : "");
|
||||
html.replace("{pl}", strcmp(entsoe.area, "10YPL-AREA-----S") == 0 ? "selected" : "");
|
||||
html.replace("{ch}", strcmp(entsoe.area, "10YCH-SWISSGRIDZ") == 0 ? "selected" : "");
|
||||
|
||||
html.replace("{ecNOK}", strcmp(entsoe.currency, "NOK") == 0 ? "selected" : "");
|
||||
html.replace("{ecSEK}", strcmp(entsoe.currency, "SEK") == 0 ? "selected" : "");
|
||||
html.replace("{ecDKK}", strcmp(entsoe.currency, "DKK") == 0 ? "selected" : "");
|
||||
@@ -583,10 +594,7 @@ void AmsWebServer::configEntsoeHtml() {
|
||||
server.sendContent(html);
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
} else {
|
||||
server.setContentLength(LOWMEM_HTML_LEN + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
server.send_P(200, MIME_HTML, HEAD_HTML);
|
||||
server.sendContent_P(LOWMEM_HTML);
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
notFound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,8 +608,9 @@ void AmsWebServer::configThresholdsHtml() {
|
||||
|
||||
String html = String((const __FlashStringHelper*) THRESHOLDS_HTML);
|
||||
for(int i = 0; i < 9; i++) {
|
||||
html.replace("{t" + String(i) + "}", String(config->thresholds[i]));
|
||||
html.replace("{t" + String(i) + "}", String(config->thresholds[i], 10));
|
||||
}
|
||||
html.replace("{h}", String(config->hours, 10));
|
||||
|
||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
server.send_P(200, MIME_HTML, HEAD_HTML);
|
||||
@@ -716,6 +725,12 @@ void AmsWebServer::dataJson() {
|
||||
if(eapi != NULL && strlen(eapi->getToken()) > 0)
|
||||
price = eapi->getValueForHour(0);
|
||||
|
||||
String peaks = "";
|
||||
for(uint8_t i = 1; i <= ea->getConfig()->hours; i++) {
|
||||
if(!peaks.isEmpty()) peaks += ",";
|
||||
peaks += String(ea->getPeak(i));
|
||||
}
|
||||
|
||||
snprintf_P(buf, BufferSize, DATA_JSON,
|
||||
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
||||
meterConfig->productionCapacity,
|
||||
@@ -752,13 +767,17 @@ void AmsWebServer::dataJson() {
|
||||
meterState->getMeterType(),
|
||||
meterConfig->distributionSystem,
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
ea->getUseThisHour(),
|
||||
ea->getCostThisHour(),
|
||||
ea->getProducedThisHour(),
|
||||
ea->getUseToday(),
|
||||
ea->getCostToday(),
|
||||
ea->getProducedToday(),
|
||||
ea->getUseThisMonth(),
|
||||
ea->getCostThisMonth(),
|
||||
ea->getProducedThisMonth(),
|
||||
(uint32_t) time(nullptr)
|
||||
);
|
||||
|
||||
@@ -935,42 +954,42 @@ void AmsWebServer::energyPriceJson() {
|
||||
|
||||
snprintf_P(buf, BufferSize, ENERGYPRICE_JSON,
|
||||
eapi == NULL ? "" : eapi->getCurrency(),
|
||||
prices[0] == ENTSOE_NO_VALUE ? "null" : String(prices[0], 2).c_str(),
|
||||
prices[1] == ENTSOE_NO_VALUE ? "null" : String(prices[1], 2).c_str(),
|
||||
prices[2] == ENTSOE_NO_VALUE ? "null" : String(prices[2], 2).c_str(),
|
||||
prices[3] == ENTSOE_NO_VALUE ? "null" : String(prices[3], 2).c_str(),
|
||||
prices[4] == ENTSOE_NO_VALUE ? "null" : String(prices[4], 2).c_str(),
|
||||
prices[5] == ENTSOE_NO_VALUE ? "null" : String(prices[5], 2).c_str(),
|
||||
prices[6] == ENTSOE_NO_VALUE ? "null" : String(prices[6], 2).c_str(),
|
||||
prices[7] == ENTSOE_NO_VALUE ? "null" : String(prices[7], 2).c_str(),
|
||||
prices[8] == ENTSOE_NO_VALUE ? "null" : String(prices[8], 2).c_str(),
|
||||
prices[9] == ENTSOE_NO_VALUE ? "null" : String(prices[9], 2).c_str(),
|
||||
prices[10] == ENTSOE_NO_VALUE ? "null" : String(prices[10], 2).c_str(),
|
||||
prices[11] == ENTSOE_NO_VALUE ? "null" : String(prices[11], 2).c_str(),
|
||||
prices[12] == ENTSOE_NO_VALUE ? "null" : String(prices[12], 2).c_str(),
|
||||
prices[13] == ENTSOE_NO_VALUE ? "null" : String(prices[13], 2).c_str(),
|
||||
prices[14] == ENTSOE_NO_VALUE ? "null" : String(prices[14], 2).c_str(),
|
||||
prices[15] == ENTSOE_NO_VALUE ? "null" : String(prices[15], 2).c_str(),
|
||||
prices[16] == ENTSOE_NO_VALUE ? "null" : String(prices[16], 2).c_str(),
|
||||
prices[17] == ENTSOE_NO_VALUE ? "null" : String(prices[17], 2).c_str(),
|
||||
prices[18] == ENTSOE_NO_VALUE ? "null" : String(prices[18], 2).c_str(),
|
||||
prices[19] == ENTSOE_NO_VALUE ? "null" : String(prices[19], 2).c_str(),
|
||||
prices[20] == ENTSOE_NO_VALUE ? "null" : String(prices[20], 2).c_str(),
|
||||
prices[21] == ENTSOE_NO_VALUE ? "null" : String(prices[21], 2).c_str(),
|
||||
prices[22] == ENTSOE_NO_VALUE ? "null" : String(prices[22], 2).c_str(),
|
||||
prices[23] == ENTSOE_NO_VALUE ? "null" : String(prices[23], 2).c_str(),
|
||||
prices[24] == ENTSOE_NO_VALUE ? "null" : String(prices[24], 2).c_str(),
|
||||
prices[25] == ENTSOE_NO_VALUE ? "null" : String(prices[25], 2).c_str(),
|
||||
prices[26] == ENTSOE_NO_VALUE ? "null" : String(prices[26], 2).c_str(),
|
||||
prices[27] == ENTSOE_NO_VALUE ? "null" : String(prices[27], 2).c_str(),
|
||||
prices[28] == ENTSOE_NO_VALUE ? "null" : String(prices[28], 2).c_str(),
|
||||
prices[29] == ENTSOE_NO_VALUE ? "null" : String(prices[29], 2).c_str(),
|
||||
prices[30] == ENTSOE_NO_VALUE ? "null" : String(prices[30], 2).c_str(),
|
||||
prices[31] == ENTSOE_NO_VALUE ? "null" : String(prices[31], 2).c_str(),
|
||||
prices[32] == ENTSOE_NO_VALUE ? "null" : String(prices[32], 2).c_str(),
|
||||
prices[33] == ENTSOE_NO_VALUE ? "null" : String(prices[33], 2).c_str(),
|
||||
prices[34] == ENTSOE_NO_VALUE ? "null" : String(prices[34], 2).c_str(),
|
||||
prices[35] == ENTSOE_NO_VALUE ? "null" : String(prices[35], 2).c_str()
|
||||
prices[0] == ENTSOE_NO_VALUE ? "null" : String(prices[0], 4).c_str(),
|
||||
prices[1] == ENTSOE_NO_VALUE ? "null" : String(prices[1], 4).c_str(),
|
||||
prices[2] == ENTSOE_NO_VALUE ? "null" : String(prices[2], 4).c_str(),
|
||||
prices[3] == ENTSOE_NO_VALUE ? "null" : String(prices[3], 4).c_str(),
|
||||
prices[4] == ENTSOE_NO_VALUE ? "null" : String(prices[4], 4).c_str(),
|
||||
prices[5] == ENTSOE_NO_VALUE ? "null" : String(prices[5], 4).c_str(),
|
||||
prices[6] == ENTSOE_NO_VALUE ? "null" : String(prices[6], 4).c_str(),
|
||||
prices[7] == ENTSOE_NO_VALUE ? "null" : String(prices[7], 4).c_str(),
|
||||
prices[8] == ENTSOE_NO_VALUE ? "null" : String(prices[8], 4).c_str(),
|
||||
prices[9] == ENTSOE_NO_VALUE ? "null" : String(prices[9], 4).c_str(),
|
||||
prices[10] == ENTSOE_NO_VALUE ? "null" : String(prices[10], 4).c_str(),
|
||||
prices[11] == ENTSOE_NO_VALUE ? "null" : String(prices[11], 4).c_str(),
|
||||
prices[12] == ENTSOE_NO_VALUE ? "null" : String(prices[12], 4).c_str(),
|
||||
prices[13] == ENTSOE_NO_VALUE ? "null" : String(prices[13], 4).c_str(),
|
||||
prices[14] == ENTSOE_NO_VALUE ? "null" : String(prices[14], 4).c_str(),
|
||||
prices[15] == ENTSOE_NO_VALUE ? "null" : String(prices[15], 4).c_str(),
|
||||
prices[16] == ENTSOE_NO_VALUE ? "null" : String(prices[16], 4).c_str(),
|
||||
prices[17] == ENTSOE_NO_VALUE ? "null" : String(prices[17], 4).c_str(),
|
||||
prices[18] == ENTSOE_NO_VALUE ? "null" : String(prices[18], 4).c_str(),
|
||||
prices[19] == ENTSOE_NO_VALUE ? "null" : String(prices[19], 4).c_str(),
|
||||
prices[20] == ENTSOE_NO_VALUE ? "null" : String(prices[20], 4).c_str(),
|
||||
prices[21] == ENTSOE_NO_VALUE ? "null" : String(prices[21], 4).c_str(),
|
||||
prices[22] == ENTSOE_NO_VALUE ? "null" : String(prices[22], 4).c_str(),
|
||||
prices[23] == ENTSOE_NO_VALUE ? "null" : String(prices[23], 4).c_str(),
|
||||
prices[24] == ENTSOE_NO_VALUE ? "null" : String(prices[24], 4).c_str(),
|
||||
prices[25] == ENTSOE_NO_VALUE ? "null" : String(prices[25], 4).c_str(),
|
||||
prices[26] == ENTSOE_NO_VALUE ? "null" : String(prices[26], 4).c_str(),
|
||||
prices[27] == ENTSOE_NO_VALUE ? "null" : String(prices[27], 4).c_str(),
|
||||
prices[28] == ENTSOE_NO_VALUE ? "null" : String(prices[28], 4).c_str(),
|
||||
prices[29] == ENTSOE_NO_VALUE ? "null" : String(prices[29], 4).c_str(),
|
||||
prices[30] == ENTSOE_NO_VALUE ? "null" : String(prices[30], 4).c_str(),
|
||||
prices[31] == ENTSOE_NO_VALUE ? "null" : String(prices[31], 4).c_str(),
|
||||
prices[32] == ENTSOE_NO_VALUE ? "null" : String(prices[32], 4).c_str(),
|
||||
prices[33] == ENTSOE_NO_VALUE ? "null" : String(prices[33], 4).c_str(),
|
||||
prices[34] == ENTSOE_NO_VALUE ? "null" : String(prices[34], 4).c_str(),
|
||||
prices[35] == ENTSOE_NO_VALUE ? "null" : String(prices[35], 4).c_str()
|
||||
);
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
|
||||
@@ -988,7 +1007,7 @@ void AmsWebServer::handleSetup() {
|
||||
server.sendHeader("Location", String("/"), true);
|
||||
server.send (302, MIME_PLAIN, "");
|
||||
} else {
|
||||
SystemConfig sys { server.arg("board").toInt() };
|
||||
SystemConfig sys { static_cast<uint8_t>(server.arg("board").toInt()) };
|
||||
|
||||
DebugConfig debugConfig;
|
||||
config->getDebugConfig(debugConfig);
|
||||
@@ -1044,7 +1063,27 @@ void AmsWebServer::handleSetup() {
|
||||
gpioConfig->ledPinRed = 13;
|
||||
gpioConfig->ledPinGreen = 14;
|
||||
gpioConfig->ledRgbInverted = true;
|
||||
gpioConfig->vccPin = 35;
|
||||
gpioConfig->vccPin = 10;
|
||||
gpioConfig->vccResistorGnd = 22;
|
||||
gpioConfig->vccResistorVcc = 33;
|
||||
break;
|
||||
case 6: // Pow-P1
|
||||
gpioConfig->hanPin = 16;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPinRed = 13;
|
||||
gpioConfig->ledPinGreen = 14;
|
||||
gpioConfig->ledRgbInverted = true;
|
||||
gpioConfig->vccPin = 10;
|
||||
gpioConfig->vccResistorGnd = 22;
|
||||
gpioConfig->vccResistorVcc = 33;
|
||||
break;
|
||||
case 7: // Pow-U+
|
||||
gpioConfig->hanPin = 16;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPinRed = 13;
|
||||
gpioConfig->ledPinGreen = 14;
|
||||
gpioConfig->ledRgbInverted = true;
|
||||
gpioConfig->vccPin = 10;
|
||||
gpioConfig->vccResistorGnd = 22;
|
||||
gpioConfig->vccResistorVcc = 33;
|
||||
break;
|
||||
@@ -1081,6 +1120,15 @@ void AmsWebServer::handleSetup() {
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = false;
|
||||
break;
|
||||
case 50: // S2
|
||||
gpioConfig->hanPin = 18;
|
||||
break;
|
||||
case 51: // S2-mini
|
||||
gpioConfig->hanPin = 18;
|
||||
gpioConfig->ledPin = 15;
|
||||
gpioConfig->ledInverted = false;
|
||||
gpioConfig->apPin = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
WiFiConfig wifi;
|
||||
@@ -1239,11 +1287,11 @@ void AmsWebServer::handleSave() {
|
||||
if(server.hasArg("dc") && server.arg("dc") == "true") {
|
||||
printD("Received Domoticz config");
|
||||
DomoticzConfig domo {
|
||||
server.arg("elidx").toInt(),
|
||||
server.arg("vl1idx").toInt(),
|
||||
server.arg("vl2idx").toInt(),
|
||||
server.arg("vl3idx").toInt(),
|
||||
server.arg("cl1idx").toInt()
|
||||
static_cast<uint16_t>(server.arg("elidx").toInt()),
|
||||
static_cast<uint16_t>(server.arg("vl1idx").toInt()),
|
||||
static_cast<uint16_t>(server.arg("vl2idx").toInt()),
|
||||
static_cast<uint16_t>(server.arg("vl3idx").toInt()),
|
||||
static_cast<uint16_t>(server.arg("cl1idx").toInt())
|
||||
};
|
||||
config->setDomoticzConfig(domo);
|
||||
}
|
||||
@@ -1264,24 +1312,24 @@ void AmsWebServer::handleSave() {
|
||||
config->setWebConfig(webConfig);
|
||||
}
|
||||
|
||||
if(server.hasArg("gpioConfig") && server.arg("gpioConfig") == "true") {
|
||||
if(server.hasArg("gc") && server.arg("gc") == "true") {
|
||||
printD("Received GPIO config");
|
||||
gpioConfig->hanPin = server.hasArg("hanPin") && !server.arg("hanPin").isEmpty() ? server.arg("hanPin").toInt() : 3;
|
||||
gpioConfig->ledPin = server.hasArg("ledPin") && !server.arg("ledPin").isEmpty() ? server.arg("ledPin").toInt() : 0xFF;
|
||||
gpioConfig->ledInverted = server.hasArg("ledInverted") && server.arg("ledInverted") == "true";
|
||||
gpioConfig->ledPinRed = server.hasArg("ledPinRed") && !server.arg("ledPinRed").isEmpty() ? server.arg("ledPinRed").toInt() : 0xFF;
|
||||
gpioConfig->ledPinGreen = server.hasArg("ledPinGreen") && !server.arg("ledPinGreen").isEmpty() ? server.arg("ledPinGreen").toInt() : 0xFF;
|
||||
gpioConfig->ledPinBlue = server.hasArg("ledPinBlue") && !server.arg("ledPinBlue").isEmpty() ? server.arg("ledPinBlue").toInt() : 0xFF;
|
||||
gpioConfig->ledRgbInverted = server.hasArg("ledRgbInverted") && server.arg("ledRgbInverted") == "true";
|
||||
gpioConfig->apPin = server.hasArg("apPin") && !server.arg("apPin").isEmpty() ? server.arg("apPin").toInt() : 0xFF;
|
||||
gpioConfig->tempSensorPin = server.hasArg("tempSensorPin") && !server.arg("tempSensorPin").isEmpty() ?server.arg("tempSensorPin").toInt() : 0xFF;
|
||||
gpioConfig->tempAnalogSensorPin = server.hasArg("tempAnalogSensorPin") && !server.arg("tempAnalogSensorPin").isEmpty() ?server.arg("tempAnalogSensorPin").toInt() : 0xFF;
|
||||
gpioConfig->vccPin = server.hasArg("vccPin") && !server.arg("vccPin").isEmpty() ? server.arg("vccPin").toInt() : 0xFF;
|
||||
gpioConfig->vccOffset = server.hasArg("vccOffset") && !server.arg("vccOffset").isEmpty() ? server.arg("vccOffset").toFloat() * 100 : 0;
|
||||
gpioConfig->vccMultiplier = server.hasArg("vccMultiplier") && !server.arg("vccMultiplier").isEmpty() ? server.arg("vccMultiplier").toFloat() * 1000 : 1000;
|
||||
gpioConfig->vccBootLimit = server.hasArg("vccBootLimit") && !server.arg("vccBootLimit").isEmpty() ? server.arg("vccBootLimit").toFloat() * 10 : 0;
|
||||
gpioConfig->vccResistorGnd = server.hasArg("vccResistorGnd") && !server.arg("vccResistorGnd").isEmpty() ? server.arg("vccResistorGnd").toInt() : 0;
|
||||
gpioConfig->vccResistorVcc = server.hasArg("vccResistorVcc") && !server.arg("vccResistorVcc").isEmpty() ? server.arg("vccResistorVcc").toInt() : 0;
|
||||
gpioConfig->hanPin = server.hasArg("h") && !server.arg("h").isEmpty() ? server.arg("h").toInt() : 3;
|
||||
gpioConfig->ledPin = server.hasArg("l") && !server.arg("l").isEmpty() ? server.arg("l").toInt() : 0xFF;
|
||||
gpioConfig->ledInverted = server.hasArg("i") && server.arg("i") == "true";
|
||||
gpioConfig->ledPinRed = server.hasArg("r") && !server.arg("r").isEmpty() ? server.arg("r").toInt() : 0xFF;
|
||||
gpioConfig->ledPinGreen = server.hasArg("e") && !server.arg("e").isEmpty() ? server.arg("e").toInt() : 0xFF;
|
||||
gpioConfig->ledPinBlue = server.hasArg("b") && !server.arg("b").isEmpty() ? server.arg("b").toInt() : 0xFF;
|
||||
gpioConfig->ledRgbInverted = server.hasArg("n") && server.arg("n") == "true";
|
||||
gpioConfig->apPin = server.hasArg("a") && !server.arg("a").isEmpty() ? server.arg("a").toInt() : 0xFF;
|
||||
gpioConfig->tempSensorPin = server.hasArg("t") && !server.arg("t").isEmpty() ?server.arg("t").toInt() : 0xFF;
|
||||
gpioConfig->tempAnalogSensorPin = server.hasArg("m") && !server.arg("m").isEmpty() ?server.arg("m").toInt() : 0xFF;
|
||||
gpioConfig->vccPin = server.hasArg("v") && !server.arg("v").isEmpty() ? server.arg("v").toInt() : 0xFF;
|
||||
gpioConfig->vccOffset = server.hasArg("o") && !server.arg("o").isEmpty() ? server.arg("o").toFloat() * 100 : 0;
|
||||
gpioConfig->vccMultiplier = server.hasArg("u") && !server.arg("u").isEmpty() ? server.arg("u").toFloat() * 1000 : 1000;
|
||||
gpioConfig->vccBootLimit = server.hasArg("c") && !server.arg("c").isEmpty() ? server.arg("c").toFloat() * 10 : 0;
|
||||
gpioConfig->vccResistorGnd = server.hasArg("d") && !server.arg("d").isEmpty() ? server.arg("d").toInt() : 0;
|
||||
gpioConfig->vccResistorVcc = server.hasArg("s") && !server.arg("s").isEmpty() ? server.arg("s").toInt() : 0;
|
||||
config->setGpioConfig(*gpioConfig);
|
||||
}
|
||||
|
||||
@@ -1320,8 +1368,8 @@ void AmsWebServer::handleSave() {
|
||||
NtpConfig ntp {
|
||||
server.hasArg("n") && server.arg("n") == "true",
|
||||
server.hasArg("nd") && server.arg("nd") == "true",
|
||||
server.arg("o").toInt() / 10,
|
||||
server.arg("so").toInt() / 10
|
||||
static_cast<int16_t>(server.arg("o").toInt() / 10),
|
||||
static_cast<int16_t>(server.arg("so").toInt() / 10)
|
||||
};
|
||||
strcpy(ntp.server, server.arg("ns").c_str());
|
||||
config->setNtpConfig(ntp);
|
||||
@@ -1349,6 +1397,7 @@ void AmsWebServer::handleSave() {
|
||||
eac.thresholds[6] = server.arg("t6").toInt();
|
||||
eac.thresholds[7] = server.arg("t7").toInt();
|
||||
eac.thresholds[8] = server.arg("t8").toInt();
|
||||
eac.hours = server.arg("h").toInt();
|
||||
config->setEnergyAccountingConfig(eac);
|
||||
}
|
||||
|
||||
@@ -1412,32 +1461,32 @@ void AmsWebServer::configGpioHtml() {
|
||||
String html = String((const __FlashStringHelper*) GPIO_HTML);
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
html.replace("${gpio.max}", "44");
|
||||
html.replace("${g}", "44");
|
||||
#elif defined(ESP32)
|
||||
html.replace("${gpio.max}", "39");
|
||||
html.replace("${g}", "39");
|
||||
#else
|
||||
html.replace("${gpio.max}", "16");
|
||||
html.replace("${g}", "16");
|
||||
#endif
|
||||
|
||||
html.replace("${options.han}", getSerialSelectOptions(gpioConfig->hanPin));
|
||||
html.replace("${h}", getSerialSelectOptions(gpioConfig->hanPin));
|
||||
|
||||
html.replace("${config.ledPin}", gpioConfig->ledPin == 0xFF ? "" : String(gpioConfig->ledPin));
|
||||
html.replace("${config.ledInverted}", gpioConfig->ledInverted ? "checked" : "");
|
||||
html.replace("${config.ledPinRed}", gpioConfig->ledPinRed == 0xFF ? "" : String(gpioConfig->ledPinRed));
|
||||
html.replace("${config.ledPinGreen}", gpioConfig->ledPinGreen == 0xFF ? "" : String(gpioConfig->ledPinGreen));
|
||||
html.replace("${config.ledPinBlue}", gpioConfig->ledPinBlue == 0xFF ? "" : String(gpioConfig->ledPinBlue));
|
||||
html.replace("${config.ledRgbInverted}", gpioConfig->ledRgbInverted ? "checked" : "");
|
||||
html.replace("${config.apPin}", gpioConfig->apPin == 0xFF ? "" : String(gpioConfig->apPin));
|
||||
html.replace("${config.tempSensorPin}", gpioConfig->tempSensorPin == 0xFF ? "" : String(gpioConfig->tempSensorPin));
|
||||
html.replace("${config.tempAnalogSensorPin}", gpioConfig->tempAnalogSensorPin == 0xFF ? "" : String(gpioConfig->tempAnalogSensorPin));
|
||||
html.replace("${config.vccPin}", gpioConfig->vccPin == 0xFF ? "" : String(gpioConfig->vccPin));
|
||||
html.replace("${l}", gpioConfig->ledPin == 0xFF ? "" : String(gpioConfig->ledPin));
|
||||
html.replace("${i}", gpioConfig->ledInverted ? "checked" : "");
|
||||
html.replace("${r}", gpioConfig->ledPinRed == 0xFF ? "" : String(gpioConfig->ledPinRed));
|
||||
html.replace("${e}", gpioConfig->ledPinGreen == 0xFF ? "" : String(gpioConfig->ledPinGreen));
|
||||
html.replace("${b}", gpioConfig->ledPinBlue == 0xFF ? "" : String(gpioConfig->ledPinBlue));
|
||||
html.replace("${n}", gpioConfig->ledRgbInverted ? "checked" : "");
|
||||
html.replace("${a}", gpioConfig->apPin == 0xFF ? "" : String(gpioConfig->apPin));
|
||||
html.replace("${t}", gpioConfig->tempSensorPin == 0xFF ? "" : String(gpioConfig->tempSensorPin));
|
||||
html.replace("${m}", gpioConfig->tempAnalogSensorPin == 0xFF ? "" : String(gpioConfig->tempAnalogSensorPin));
|
||||
html.replace("${v}", gpioConfig->vccPin == 0xFF ? "" : String(gpioConfig->vccPin));
|
||||
|
||||
html.replace("${config.vccOffset}", gpioConfig->vccOffset > 0 ? String(gpioConfig->vccOffset / 100.0, 2) : "");
|
||||
html.replace("${config.vccMultiplier}", gpioConfig->vccMultiplier > 0 ? String(gpioConfig->vccMultiplier / 1000.0, 2) : "");
|
||||
html.replace("${config.vccBootLimit}", gpioConfig->vccBootLimit > 0 ? String(gpioConfig->vccBootLimit / 10.0, 1) : "");
|
||||
html.replace("${o}", gpioConfig->vccOffset > 0 ? String(gpioConfig->vccOffset / 100.0, 2) : "");
|
||||
html.replace("${u}", gpioConfig->vccMultiplier > 0 ? String(gpioConfig->vccMultiplier / 1000.0, 2) : "");
|
||||
html.replace("${c}", gpioConfig->vccBootLimit > 0 ? String(gpioConfig->vccBootLimit / 10.0, 1) : "");
|
||||
|
||||
html.replace("${config.vccResistorGnd}", gpioConfig->vccResistorGnd > 0 ? String(gpioConfig->vccResistorGnd) : "");
|
||||
html.replace("${config.vccResistorVcc}", gpioConfig->vccResistorVcc > 0 ? String(gpioConfig->vccResistorVcc) : "");
|
||||
html.replace("${d}", gpioConfig->vccResistorGnd > 0 ? String(gpioConfig->vccResistorGnd) : "");
|
||||
html.replace("${s}", gpioConfig->vccResistorVcc > 0 ? String(gpioConfig->vccResistorVcc) : "");
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
@@ -1482,8 +1531,13 @@ String AmsWebServer::getSerialSelectOptions(int selected) {
|
||||
gpioOptions += "<option value=\"3\">UART0 (GPIO3)</option>";
|
||||
}
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
int numGpio = 31;
|
||||
int gpios[] = {4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,21,22,23,25,32,33,34,35,36,39,40,41,42,43,44};
|
||||
int numGpio = 30;
|
||||
int gpios[] = {4,5,6,7,8,9,10,11,12,13,14,15,16,17,19,21,22,23,25,32,33,34,35,36,39,40,41,42,43,44};
|
||||
if(selected == 18) {
|
||||
gpioOptions += "<option value=\"18\" selected>UART1 (GPIO18)</option>";
|
||||
} else {
|
||||
gpioOptions += "<option value=\"18\">UART1 (GPIO18)</option>";
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
int numGpio = 24;
|
||||
int gpios[] = {4,5,6,7,8,10,11,12,13,14,15,17,18,19,21,22,23,25,32,33,34,35,36,39};
|
||||
@@ -1545,21 +1599,21 @@ HTTPUpload& AmsWebServer::uploadFile(const char* path) {
|
||||
}
|
||||
file = LittleFS.open(path, "w");
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("handleFileUpload Open file and write: %lu\n", upload.currentSize);
|
||||
debugger->printf("handleFileUpload Open file and write: %u\n", upload.currentSize);
|
||||
}
|
||||
size_t written = file.write(upload.buf, upload.currentSize);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("handleFileUpload Written: %lu\n", written);
|
||||
debugger->printf("handleFileUpload Written: %u\n", written);
|
||||
}
|
||||
}
|
||||
} else if(upload.status == UPLOAD_FILE_WRITE) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("handleFileUpload Writing: %lu\n", upload.currentSize);
|
||||
debugger->printf("handleFileUpload Writing: %u\n", upload.currentSize);
|
||||
}
|
||||
if(file) {
|
||||
size_t written = file.write(upload.buf, upload.currentSize);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("handleFileUpload Written: %lu\n", written);
|
||||
debugger->printf("handleFileUpload Written: %u\n", written);
|
||||
}
|
||||
delay(1);
|
||||
if(written != upload.currentSize) {
|
||||
@@ -1605,11 +1659,15 @@ void AmsWebServer::firmwareHtml() {
|
||||
String html = String((const __FlashStringHelper*) FIRMWARE_HTML);
|
||||
|
||||
#if defined(ESP8266)
|
||||
html.replace("{chipset}", "ESP8266");
|
||||
html.replace("{chipset}", "ESP8266");
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
html.replace("{chipset}", "ESP32S2");
|
||||
html.replace("{chipset}", "ESP32S2");
|
||||
#elif defined(ESP32)
|
||||
html.replace("{chipset}", "ESP32");
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
html.replace("{chipset}", "ESP32SOLO");
|
||||
#else
|
||||
html.replace("{chipset}", "ESP32");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
@@ -1618,13 +1676,40 @@ void AmsWebServer::firmwareHtml() {
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
}
|
||||
|
||||
void AmsWebServer::firmwarePost() {
|
||||
printD("Handlling firmware post...");
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(rebootForUpgrade) {
|
||||
server.send(200);
|
||||
} else {
|
||||
if(server.hasArg("url")) {
|
||||
String url = server.arg("url");
|
||||
if(!url.isEmpty() && (url.startsWith("http://") || url.startsWith("https://"))) {
|
||||
printD("Custom firmware URL was provided");
|
||||
customFirmwareUrl = url;
|
||||
performUpgrade = true;
|
||||
server.sendHeader("Location","/restart-wait");
|
||||
server.send(303);
|
||||
return;
|
||||
}
|
||||
}
|
||||
server.sendHeader("Location","/firmware");
|
||||
server.send(303);
|
||||
}
|
||||
}
|
||||
|
||||
void AmsWebServer::firmwareUpload() {
|
||||
printD("Handlling firmware upload...");
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
HTTPUpload& upload = server.upload();
|
||||
String filename = upload.filename;
|
||||
if(filename.isEmpty()) return;
|
||||
|
||||
if(upload.status == UPLOAD_FILE_START) {
|
||||
String filename = upload.filename;
|
||||
if(!filename.endsWith(".bin")) {
|
||||
server.send(500, MIME_PLAIN, "500: couldn't create file");
|
||||
} else {
|
||||
@@ -1638,7 +1723,7 @@ void AmsWebServer::firmwareUpload() {
|
||||
}
|
||||
uploadFile(FILE_FIRMWARE);
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
performRestart = true;
|
||||
rebootForUpgrade = true;
|
||||
server.sendHeader("Location","/restart-wait");
|
||||
server.send(303);
|
||||
}
|
||||
@@ -1651,99 +1736,9 @@ void AmsWebServer::firmwareDownload() {
|
||||
return;
|
||||
|
||||
printD("Firmware download URL triggered");
|
||||
if(server.hasArg("version")) {
|
||||
String version = server.arg("version");
|
||||
String versionStripped = version.substring(1);
|
||||
printI("Downloading firmware...");
|
||||
HTTPClient httpClient;
|
||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
httpClient.setTimeout(20000);
|
||||
httpClient.addHeader("User-Agent", "ams2mqtt/" + String(VERSION));
|
||||
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
String url = "http://ams2mqtt.no23.cc/releases/download/" + version + "/ams2mqtt-esp8266-" + versionStripped + ".bin";
|
||||
/*
|
||||
t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION);
|
||||
switch(ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
printE("[update] Update failed.");
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
printI("[update] Update no Update.");
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
printI("[update] Update ok."); // may not be called since we reboot the ESP
|
||||
performRestart = true;
|
||||
server.sendHeader("Location","/restart-wait");
|
||||
server.send(303);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
*/
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
WiFiClientSecure client;
|
||||
client.setInsecure();
|
||||
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32s2-" + versionStripped + ".bin";
|
||||
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
|
||||
#elif defined(ESP32)
|
||||
WiFiClientSecure client;
|
||||
client.setInsecure();
|
||||
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32-" + versionStripped + ".bin";
|
||||
httpClient.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
|
||||
#endif
|
||||
|
||||
if(httpClient.begin(client, url)) {
|
||||
printD("HTTP client setup successful");
|
||||
int status = httpClient.GET();
|
||||
if(status == HTTP_CODE_OK) {
|
||||
printD("Received OK from server");
|
||||
if(LittleFS.begin()) {
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_delete(NULL);
|
||||
esp_task_wdt_deinit();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtDisable();
|
||||
#endif
|
||||
|
||||
printI("Downloading firmware to LittleFS");
|
||||
file = LittleFS.open(FILE_FIRMWARE, "w");
|
||||
int len = httpClient.writeToStream(&file);
|
||||
file.close();
|
||||
LittleFS.end();
|
||||
performRestart = true;
|
||||
server.sendHeader("Location","/restart-wait");
|
||||
server.send(303);
|
||||
} else {
|
||||
printE("Unable to open LittleFS for writing");
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
}
|
||||
} else {
|
||||
printE("Communication error: ");
|
||||
printE(httpClient.errorToString(status));
|
||||
printI(url);
|
||||
printD(httpClient.getString());
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
}
|
||||
} else {
|
||||
printE("Unable to configure HTTP client");
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
}
|
||||
httpClient.end();
|
||||
client.stop();
|
||||
} else {
|
||||
printI("No firmware version specified...");
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
}
|
||||
performUpgrade = true;
|
||||
server.sendHeader("Location","/restart-wait");
|
||||
server.send(303);
|
||||
}
|
||||
|
||||
void AmsWebServer::restartHtml() {
|
||||
@@ -1793,6 +1788,14 @@ void AmsWebServer::restartWaitHtml() {
|
||||
}
|
||||
html.replace("${hostname}", wifi.hostname);
|
||||
|
||||
if(performUpgrade || rebootForUpgrade) {
|
||||
html.replace("{rs}", "d-none");
|
||||
html.replace("{us}", "");
|
||||
} else {
|
||||
html.replace("{rs}", "");
|
||||
html.replace("{us}", "d-none");
|
||||
}
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
|
||||
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
|
||||
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
|
||||
@@ -1801,7 +1804,7 @@ void AmsWebServer::restartWaitHtml() {
|
||||
server.send(200, MIME_HTML, html);
|
||||
|
||||
yield();
|
||||
if(performRestart) {
|
||||
if(performRestart || rebootForUpgrade) {
|
||||
if(ds != NULL) {
|
||||
ds->save();
|
||||
}
|
||||
@@ -1813,6 +1816,42 @@ void AmsWebServer::restartWaitHtml() {
|
||||
ESP.restart();
|
||||
#endif
|
||||
performRestart = false;
|
||||
} else if(performUpgrade) {
|
||||
WiFiClient client;
|
||||
String url = customFirmwareUrl.isEmpty() || !customFirmwareUrl.startsWith("http") ? "http://ams2mqtt.rewiredinvent.no/hub/firmware/update" : customFirmwareUrl;
|
||||
#if defined(ESP8266)
|
||||
String chipType = "esp8266";
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
String chipType = "esp32s2";
|
||||
#elif defined(ESP32)
|
||||
#if defined(CONFIG_FREERTOS_UNICORE)
|
||||
String chipType = "esp32solo";
|
||||
#else
|
||||
String chipType = "esp32";
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
t_httpUpdate_return ret = ESPhttpUpdate.update(client, url, VERSION);
|
||||
#elif defined(ESP32)
|
||||
HTTPUpdate httpUpdate;
|
||||
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
HTTPUpdateResult ret = httpUpdate.update(client, url, String(VERSION) + "-" + chipType);
|
||||
#endif
|
||||
|
||||
switch(ret) {
|
||||
case HTTP_UPDATE_FAILED:
|
||||
printE("Update failed");
|
||||
break;
|
||||
case HTTP_UPDATE_NO_UPDATES:
|
||||
printI("No Update");
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
printI("Update OK");
|
||||
break;
|
||||
}
|
||||
performUpgrade = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1857,7 +1896,7 @@ void AmsWebServer::mqttCa() {
|
||||
}
|
||||
LittleFS.end();
|
||||
} else {
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
}
|
||||
}
|
||||
@@ -1869,7 +1908,7 @@ void AmsWebServer::mqttCaUpload() {
|
||||
uploadFile(FILE_MQTT_CA);
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
|
||||
MqttConfig mqttConfig;
|
||||
@@ -1885,7 +1924,7 @@ void AmsWebServer::mqttCaDelete() {
|
||||
|
||||
if(!uploading) { // Not an upload
|
||||
deleteFile(FILE_MQTT_CA);
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
MqttConfig mqttConfig;
|
||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||
@@ -1911,7 +1950,7 @@ void AmsWebServer::mqttCert() {
|
||||
}
|
||||
LittleFS.end();
|
||||
} else {
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
}
|
||||
}
|
||||
@@ -1923,7 +1962,7 @@ void AmsWebServer::mqttCertUpload() {
|
||||
uploadFile(FILE_MQTT_CERT);
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
MqttConfig mqttConfig;
|
||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||
@@ -1938,7 +1977,7 @@ void AmsWebServer::mqttCertDelete() {
|
||||
|
||||
if(!uploading) { // Not an upload
|
||||
deleteFile(FILE_MQTT_CERT);
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
MqttConfig mqttConfig;
|
||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||
@@ -1964,7 +2003,7 @@ void AmsWebServer::mqttKey() {
|
||||
}
|
||||
LittleFS.end();
|
||||
} else {
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
}
|
||||
}
|
||||
@@ -1976,7 +2015,7 @@ void AmsWebServer::mqttKeyUpload() {
|
||||
uploadFile(FILE_MQTT_KEY);
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
MqttConfig mqttConfig;
|
||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||
@@ -1991,7 +2030,7 @@ void AmsWebServer::mqttKeyDelete() {
|
||||
|
||||
if(!uploading) { // Not an upload
|
||||
deleteFile(FILE_MQTT_KEY);
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
server.sendHeader("Location","/mqtt");
|
||||
server.send(303);
|
||||
MqttConfig mqttConfig;
|
||||
if(config->getMqttConfig(mqttConfig) && mqttConfig.ssl) {
|
||||
@@ -2245,7 +2284,7 @@ void AmsWebServer::configFileDownload() {
|
||||
EnergyAccountingConfig eac;
|
||||
config->getEnergyAccountingConfig(eac);
|
||||
|
||||
if(eac.thresholds[9] > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, (char*) F("thresholds %d %d %d %d %d %d %d %d %d %d\n"),
|
||||
if(eac.thresholds[9] > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, (char*) F("thresholds %d %d %d %d %d %d %d %d %d %d %d\n"),
|
||||
eac.thresholds[0],
|
||||
eac.thresholds[1],
|
||||
eac.thresholds[2],
|
||||
@@ -2255,7 +2294,8 @@ void AmsWebServer::configFileDownload() {
|
||||
eac.thresholds[6],
|
||||
eac.thresholds[7],
|
||||
eac.thresholds[8],
|
||||
eac.thresholds[9]
|
||||
eac.thresholds[9],
|
||||
eac.hours
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2292,7 +2332,7 @@ void AmsWebServer::configFileDownload() {
|
||||
ds->getHourImport(23)
|
||||
));
|
||||
if(day.activeExport > 0) {
|
||||
server.sendContent(buf, snprintf(buf, BufferSize, " %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
|
||||
server.sendContent(buf, snprintf(buf, BufferSize, " %u %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
|
||||
day.activeExport,
|
||||
ds->getHourExport(0),
|
||||
ds->getHourExport(1),
|
||||
@@ -2361,7 +2401,7 @@ void AmsWebServer::configFileDownload() {
|
||||
ds->getDayImport(31)
|
||||
));
|
||||
if(month.activeExport > 0) {
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, " %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, " %u %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
|
||||
month.activeExport,
|
||||
ds->getDayExport(1),
|
||||
ds->getDayExport(2),
|
||||
@@ -2401,15 +2441,28 @@ void AmsWebServer::configFileDownload() {
|
||||
}
|
||||
|
||||
if(ea != NULL) {
|
||||
EnergyAccountingConfig eac;
|
||||
config->getEnergyAccountingConfig(eac);
|
||||
EnergyAccountingData ead = ea->getData();
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, (char*) F("energyaccounting %d %d %.2f %.2f %.2f %.2f"),
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, (char*) F("energyaccounting %d %d %.2f %.2f %.2f %.2f %d %.2f %d %.2f %d %.2f %d %.2f %d %.2f"),
|
||||
ead.version,
|
||||
ead.month,
|
||||
ead.maxHour / 100.0,
|
||||
ead.costYesterday / 100.0,
|
||||
ead.costThisMonth / 100.0,
|
||||
ead.costLastMonth / 100.0
|
||||
0.0, // Old max
|
||||
ead.costYesterday / 10.0,
|
||||
ead.costThisMonth / 1.0,
|
||||
ead.costLastMonth / 1.0,
|
||||
ead.peaks[0].day,
|
||||
ead.peaks[0].value / 100.0,
|
||||
ead.peaks[1].day,
|
||||
ead.peaks[1].value / 100.0,
|
||||
ead.peaks[2].day,
|
||||
ead.peaks[2].value / 100.0,
|
||||
ead.peaks[3].day,
|
||||
ead.peaks[3].value / 100.0,
|
||||
ead.peaks[4].day,
|
||||
ead.peaks[4].value / 100.0
|
||||
));
|
||||
server.sendContent("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HTTPUpdate.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
@@ -58,6 +59,13 @@ private:
|
||||
bool uploading = false;
|
||||
File file;
|
||||
bool performRestart = false;
|
||||
bool performUpgrade = false;
|
||||
bool rebootForUpgrade = false;
|
||||
#if defined(AMS2MQTT_FIRMWARE_URL)
|
||||
String customFirmwareUrl = AMS2MQTT_FIRMWARE_URL;
|
||||
#else
|
||||
String customFirmwareUrl;
|
||||
#endif
|
||||
|
||||
static const uint16_t BufferSize = 2048;
|
||||
char* buf;
|
||||
@@ -101,6 +109,7 @@ private:
|
||||
|
||||
String getSerialSelectOptions(int selected);
|
||||
void firmwareHtml();
|
||||
void firmwarePost();
|
||||
void firmwareUpload();
|
||||
void firmwareDownload();
|
||||
void restartHtml();
|
||||
|
||||
@@ -316,6 +316,7 @@ $(function() {
|
||||
url: swv.data('url'),
|
||||
dataType: 'json'
|
||||
}).done(function(releases) {
|
||||
var isnew = false;
|
||||
if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(swv.text()) && fwl.length == 0) {
|
||||
releases.reverse();
|
||||
var next_patch;
|
||||
@@ -352,10 +353,13 @@ $(function() {
|
||||
});
|
||||
if(next_minor) {
|
||||
nextVersion = next_minor;
|
||||
isnew = true;
|
||||
} else if(next_major) {
|
||||
nextVersion = next_major;
|
||||
isnew = true;
|
||||
} else if(next_patch) {
|
||||
nextVersion = next_patch;
|
||||
isnew = true;
|
||||
}
|
||||
} else {
|
||||
nextVersion = releases[0];
|
||||
@@ -375,9 +379,11 @@ $(function() {
|
||||
}
|
||||
});
|
||||
};
|
||||
$('#newVersionTag').text(nextVersion.tag_name);
|
||||
$('#newVersionUrl').prop('href', nextVersion.html_url);
|
||||
$('#newVersion').removeClass('d-none');
|
||||
if(isnew) {
|
||||
$('#newVersionTag').text(nextVersion.tag_name);
|
||||
$('#newVersionUrl').prop('href', nextVersion.html_url);
|
||||
$('#newVersion').removeClass('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -478,7 +484,7 @@ var drawDay = function() {
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }]];
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var hour = moment.utc().hours();
|
||||
var offset = moment().utcOffset()/60;
|
||||
@@ -486,13 +492,13 @@ var drawDay = function() {
|
||||
for(var i = hour; i<24; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(1) : imp.toFixed(1) + '\n' + -exp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
|
||||
min = Math.min(0, -exp);
|
||||
};
|
||||
for(var i = 0; i < hour; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(1) : imp.toFixed(1) + '\n' + -exp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad((i+offset)%24), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(1), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(1)];
|
||||
min = Math.min(0, -exp);
|
||||
};
|
||||
ea = google.visualization.arrayToDataTable(data);
|
||||
@@ -511,7 +517,7 @@ var drawMonth = function() {
|
||||
timeout: 30000,
|
||||
dataType: 'json',
|
||||
}).done(function(json) {
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }]];
|
||||
data = [['Hour', 'Import', { role: 'style' }, { role: 'annotation' }, 'Export', { role: 'style' }, { role: 'annotation' }]];
|
||||
var r = 1;
|
||||
var day = moment().date();
|
||||
var eom = moment().subtract(1, 'months').endOf('month').date();
|
||||
@@ -519,13 +525,13 @@ var drawMonth = function() {
|
||||
for(var i = day; i<=eom; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(0) : imp.toFixed(0) + '\n' + -exp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
|
||||
min = Math.min(0, -exp);
|
||||
}
|
||||
for(var i = 1; i < day; i++) {
|
||||
var imp = json["i"+zeropad(i)];
|
||||
var exp = json["e"+zeropad(i)];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", exp == 0 ? imp.toFixed(0) : imp.toFixed(0) + '\n' + -exp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;"];
|
||||
data[r++] = [zeropad(i), imp, "opacity: 0.9;", imp == 0 ? "" : imp.toFixed(0), exp == 0 ? 0 : -exp, "opacity: 0.9;", exp == 0 ? "" : -exp.toFixed(0)];
|
||||
min = Math.min(0, -exp);
|
||||
}
|
||||
ma = google.visualization.arrayToDataTable(data);
|
||||
@@ -790,6 +796,12 @@ var fetch = function() {
|
||||
if(currency) {
|
||||
$('.sp').show();
|
||||
}
|
||||
if(om > 0) {
|
||||
$('.se').removeClass('d-none');
|
||||
$('#eache').html(json.ea.h.p.toFixed(2));
|
||||
$('#eacde').html(json.ea.d.p.toFixed(1));
|
||||
$('#eacme').html(json.ea.m.p.toFixed(0));
|
||||
}
|
||||
}
|
||||
|
||||
if(json.me) {
|
||||
@@ -878,7 +890,7 @@ var fetch = function() {
|
||||
|
||||
var upgrade = function() {
|
||||
if(nextVersion) {
|
||||
if(confirm("Are you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
|
||||
if(confirm("WARNING: If you have a M-BUS powered device (Pow-U), please keep USB power connected while upgrading.\n\nAre you sure you want to perform upgrade to " + nextVersion.tag_name + "?")) {
|
||||
$('#loading-indicator').show();
|
||||
window.location.href="/upgrade?version=" + nextVersion.tag_name;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"v" : %.3f,
|
||||
"r" : %d,
|
||||
"t" : %.2f,
|
||||
"u" : %lu,
|
||||
"m" : %lu,
|
||||
"u" : %u,
|
||||
"m" : %u,
|
||||
"em" : %d,
|
||||
"hm" : %d,
|
||||
"wm" : %d,
|
||||
@@ -35,19 +35,23 @@
|
||||
"ds" : %d,
|
||||
"ea" : {
|
||||
"x" : %.1f,
|
||||
"p" : [ %s ],
|
||||
"t" : %d,
|
||||
"h" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f
|
||||
"c" : %.2f,
|
||||
"p" : %.2f
|
||||
},
|
||||
"d" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f
|
||||
"c" : %.2f,
|
||||
"p" : %.2f
|
||||
},
|
||||
"m" : {
|
||||
"u" : %.2f,
|
||||
"c" : %.2f
|
||||
"c" : %.2f,
|
||||
"p" : %.2f
|
||||
}
|
||||
},
|
||||
"c" : %lu
|
||||
"c" : %u
|
||||
}
|
||||
@@ -34,6 +34,19 @@
|
||||
<option value="10YDK-1--------W" {eaDk1}>DK1</option>
|
||||
<option value="10YDK-2--------M" {eaDk2}>DK2</option>
|
||||
</optgroup>
|
||||
<option value="10YAT-APG------L" {at}>Austria</option>
|
||||
<option value="10YBE----------2" {be}>Belgium</option>
|
||||
<option value="10YCZ-CEPS-----N" {cz}>Czech Republic</option>
|
||||
<option value="10Y1001A1001A39I" {ee}>Estonia</option>
|
||||
<option value="10YFI-1--------U" {fi}>Finland</option>
|
||||
<option value="10YFR-RTE------C" {fr}>France</option>
|
||||
<option value="10Y1001A1001A83F" {de}>Germany</option>
|
||||
<option value="10YGB----------A" {gb}>Great Britain</option>
|
||||
<option value="10YLV-1001A00074" {lv}>Latvia</option>
|
||||
<option value="10YLT-1001A0008Q" {lt}>Lithuania</option>
|
||||
<option value="10YNL----------L" {nl}>Netherland</option>
|
||||
<option value="10YPL-AREA-----S" {pl}>Poland</option>
|
||||
<option value="10YCH-SWISSGRIDZ" {ch}>Switzerland</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<div class="alert alert-danger">
|
||||
WARNING: Units powered over M-bus must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
WARNING: Units powered by M-BUS (Pow-U) must be connected to an external power supply during firmware upload. Failure to do so may cause power-down during upload resulting in non-functioning unit.
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
Your board is using {chipset} chipset. Only upload firmware designed for this chipset. Failure to do so may result in non-functioning unit.
|
||||
<span id="fwDownload" style="display: none;"><br/>Download latest firmware file <a id="fwLink" href="#" data-chipset="{chipset}">here</a></span>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
When using URL, only a valid ESP OTA server response will be accepted.
|
||||
</div>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="upload-form">
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Upload</span>
|
||||
<span class="input-group-text">Upload file</span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input name="file" type="file" class="custom-file-input" id="fileUploadField">
|
||||
@@ -21,6 +24,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">or</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Use URL</span>
|
||||
</div>
|
||||
<input type="text" name="url" class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="row form-group">
|
||||
@@ -28,7 +44,7 @@
|
||||
<a href="javascript:history.back();" class="btn btn-outline-secondary">Back</a>
|
||||
</div>
|
||||
<div class="col-6 text-right">
|
||||
<button class="btn btn-primary">Upload</button>
|
||||
<button class="btn btn-primary">Upgrade firmware</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
@@ -1,6 +1,5 @@
|
||||
<div id="newVersion" class="alert alert-info d-none">New version <span id="newVersionTag"></span>!
|
||||
<a id="newVersionUrl" href="#" target="_blank">view</a>
|
||||
<span class="d-none ssl-capable"> or <a href="javascript:upgrade();">upgrade</a></span>
|
||||
<a id="newVersionUrl" href="#" target="_blank">view</a> or <a href="javascript:upgrade();">click here to upgrade</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="alert alert-warning">!!WARNING!!<br/>Do not change anything here unless you know exactly what you are doing! Changing things here could cause the device to stop responding</div>
|
||||
<form method="post" action="/save">
|
||||
<input type="hidden" name="gpioConfig" value="true"/>
|
||||
<input type="hidden" name="gc" value="true"/>
|
||||
<div class="my-3 p-3 bg-white rounded shadow">
|
||||
<h6>GPIO settings</h6>
|
||||
<div class="d-flex flex-row flex-wrap">
|
||||
@@ -8,18 +8,18 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">HAN</span>
|
||||
</div>
|
||||
<select name="hanPin" class="form-control">
|
||||
${options.han}
|
||||
<select name="h" class="form-control">
|
||||
${h}
|
||||
</select>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">LED</span>
|
||||
</div>
|
||||
<input name="ledPin" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPin}"/>
|
||||
<input name="l" type="number" min="2" max="${g}" class="form-control" value="${l}"/>
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">
|
||||
<input type="checkbox" name="ledInverted" value="true" ${config.ledInverted}/> inv
|
||||
<input type="checkbox" name="i" value="true" ${i}/> inv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -27,12 +27,12 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">RGB</span>
|
||||
</div>
|
||||
<input name="ledPinRed" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinRed}"/>
|
||||
<input name="ledPinGreen" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinGreen}"/>
|
||||
<input name="ledPinBlue" type="number" min="2" max="${gpio.max}" class="form-control" value="${config.ledPinBlue}"/>
|
||||
<input name="r" type="number" min="2" max="${g}" class="form-control" value="${r}"/>
|
||||
<input name="e" type="number" min="2" max="${g}" class="form-control" value="${e}"/>
|
||||
<input name="b" type="number" min="2" max="${g}" class="form-control" value="${b}"/>
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">
|
||||
<input type="checkbox" name="ledRgbInverted" value="true" ${config.ledRgbInverted}/> inv
|
||||
<input type="checkbox" name="n" value="true" ${n}/> inv
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,31 +40,31 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">AP button</span>
|
||||
</div>
|
||||
<input name="apPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.apPin}"/>
|
||||
<input name="a" type="number" min="0" max="${g}" class="form-control" value="${a}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Temperature</span>
|
||||
</div>
|
||||
<input name="tempSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempSensorPin}"/>
|
||||
<input name="t" type="number" min="0" max="${g}" class="form-control" value="${t}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 150px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Analog temp</span>
|
||||
</div>
|
||||
<input name="tempAnalogSensorPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.tempAnalogSensorPin}"/>
|
||||
<input name="m" type="number" min="0" max="${g}" class="form-control" value="${m}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 100px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Vcc</span>
|
||||
</div>
|
||||
<input name="vccPin" type="number" min="0" max="${gpio.max}" class="form-control" value="${config.vccPin}"/>
|
||||
<input name="v" type="number" min="0" max="${g}" class="form-control" value="${v}"/>
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 200px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">GND resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorGnd" value="${config.vccResistorGnd}" />
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="d" value="${d}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
@@ -73,7 +73,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Vcc resistor</span>
|
||||
</div>
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="vccResistorVcc" value="${config.vccResistorVcc}" />
|
||||
<input type="number" min="1" max="1000" step="1" class="form-control" name="s" value="${s}" />
|
||||
<div class="input-group-append" title="Inverted">
|
||||
<label class="input-group-text">kΩ</label>
|
||||
</div>
|
||||
@@ -82,19 +82,19 @@
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Multiplier</span>
|
||||
</div>
|
||||
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="vccMultiplier" value="${config.vccMultiplier}" />
|
||||
<input type="number" min="0.1" max="10" step="0.01" class="form-control" name="u" value="${u}" />
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 120px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Offset</span>
|
||||
</div>
|
||||
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="vccOffset" value="${config.vccOffset}" />
|
||||
<input type="number" min="0.0" max="3.5" step="0.01" class="form-control" name="o" value="${o}" />
|
||||
</div>
|
||||
<div class="m-2 input-group input-group-sm" style="width: 130px;">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Boot limit</span>
|
||||
</div>
|
||||
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="vccBootLimit" value="${config.vccBootLimit}" />
|
||||
<input type="number" min="2.5" max="3.5" step="0.1" class="form-control" name="c" value="${c}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"tPO" : %.2f,
|
||||
"tQI" : %.2f,
|
||||
"tQO" : %.2f,
|
||||
"rtc" : %llu
|
||||
"rtc" : %lld
|
||||
}
|
||||
|
||||
@@ -11,9 +11,5 @@
|
||||
"I3" : %.2f,
|
||||
"U1" : %.2f,
|
||||
"U2" : %.2f,
|
||||
"U3" : %.2f,
|
||||
"PF" : %.2f,
|
||||
"PF1" : %.2f,
|
||||
"PF2" : %.2f,
|
||||
"PF3" : %.2f
|
||||
"U3" : %.2f
|
||||
}
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
"id" : "%s",
|
||||
"type" : "%s",
|
||||
"P" : %d,
|
||||
"P1" : %.2f,
|
||||
"P2" : %.2f,
|
||||
"P3" : %.2f,
|
||||
"Q" : %d,
|
||||
"PO" : %d,
|
||||
"PO1" : %.2f,
|
||||
"PO2" : %.2f,
|
||||
"PO3" : %.2f,
|
||||
"QO" : %d,
|
||||
"I1" : %.2f,
|
||||
"I2" : %.2f,
|
||||
@@ -15,10 +21,5 @@
|
||||
"PF" : %.2f,
|
||||
"PF1" : %.2f,
|
||||
"PF2" : %.2f,
|
||||
"PF3" : %.2f,
|
||||
"tPI" : %.2f,
|
||||
"tPO" : %.2f,
|
||||
"tQI" : %.2f,
|
||||
"tQO" : %.2f,
|
||||
"rtc" : %lu
|
||||
"PF3" : %.2f
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
@@ -63,7 +64,7 @@
|
||||
<a class="dropdown-item" href="/mqtt">MQTT</a>
|
||||
<a class="dropdown-item" href="/web">Web</a>
|
||||
<a class="dropdown-item" href="/ntp">NTP</a>
|
||||
<a class="dropdown-item" href="/entsoe">ENTSO-E API</a>
|
||||
<a class="dropdown-item d-none ssl-capable" href="/entsoe">ENTSO-E API</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="https://github.com/gskjold/AmsToMqttBridge/wiki" target="_blank">Documentation</a>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user