Merge branch 'main' into pnu

# Conflicts:
#	lib/HwTools/src/HwTools.cpp
#	lib/SvelteUi/app/dist/index.css
#	lib/SvelteUi/app/dist/index.js
#	lib/SvelteUi/app/vite.config.js
This commit is contained in:
Gunnar Skjold 2024-11-29 08:05:49 +01:00
commit 07205b8008
49 changed files with 2366 additions and 725 deletions

View File

@ -31,3 +31,59 @@ Object with three values Value Object with two values
02 03 09 06 01 00 04 08 00 FF 06 00 0A F5 EC 02 02 0F 01 16 20
51 D7
7E
Received 21.09.24 20:00:10:
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 02 71 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 01 F0 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 00 06 02 02 0F FF 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 05 02 02 0F FF 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 17 02 02
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 3F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 09 36 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 22 02 02 0F FF 16 23 02 02 09
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 09 15 06 13 00
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
(V) 00 8D 98 13 02 02 0F 01 16 1E 02 03 09 06 01 00
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 6E 02 02
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
(V) 13 F2 06 02 02 0F 01 16 20 C8 1D 7E
Received 27.10.24 11:00:13:
(V) 7E A1 8A 41 08 83 13 EB FD E6 E7 00 0F 40 00 00
(V) 00 00 01 12 02 02 09 06 01 01 00 02 81 FF 0A 0B
(V) 41 49 44 4F 4E 5F 56 30 30 30 31 02 02 09 06 00
(V) 00 60 01 00 FF 0A 10 37 33 35 39 39 39 32 38 39
(V) 30 34 39 37 39 39 37 02 02 09 06 00 00 60 01 07
(V) FF 0A 04 36 35 33 34 02 03 09 06 01 00 01 07 00
(V) FF 06 00 00 02 AA 02 02 0F 00 16 1B 02 03 09 06
(V) 01 00 02 07 00 FF 06 00 00 00 00 02 02 0F 00 16
(V) 1B 02 03 09 06 01 00 03 07 00 FF 06 00 00 00 00
(V) 02 02 0F 00 16 1D 02 03 09 06 01 00 04 07 00 FF
(V) 06 00 00 01 CC 02 02 0F 00 16 1D 02 03 09 06 01
(V) 00 1F 07 00 FF 10 00 02 02 02 0F FF 16 21 02 03
(V) 09 06 01 00 33 07 00 FF 10 00 0B 02 02 0F FF 16
(V) 21 02 03 09 06 01 00 47 07 00 FF 10 00 16 02 02
(V) 0F FF 16 21 02 03 09 06 01 00 20 07 00 FF 12 09
(V) 2F 02 02 0F FF 16 23 02 03 09 06 01 00 34 07 00
(V) FF 12 09 00 02 02 0F FF 16 23 02 03 09 06 01 00
(V) 48 07 00 FF 12 09 0F 02 02 0F FF 16 23 02 02 09
(V) 06 00 00 01 00 00 FF 09 0C 07 E8 0A 1B 00 0B 00
(V) 00 FF 00 00 00 02 03 09 06 01 00 01 08 00 FF 06
(V) 00 8F 25 6F 02 02 0F 01 16 1E 02 03 09 06 01 00
(V) 02 08 00 FF 06 00 00 00 00 02 02 0F 01 16 1E 02
(V) 03 09 06 01 00 03 08 00 FF 06 00 00 29 8D 02 02
(V) 0F 01 16 20 02 03 09 06 01 00 04 08 00 FF 06 00
(V) 14 44 26 02 02 0F 01 16 20 84 09 7E

View File

@ -31,4 +31,183 @@ C9 95 7E // CRC and end tag
06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05
06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B
09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
01.10.24 19:00 ish:
(D) READY to update (internal clock 16:59:58 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 78 01 02 01 10 C4 98 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 00 FF 80 00 00 02
(V) 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06 00
(V) 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00 00
(V) 09 69 06 00 00 09 59 18 EB 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 00 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
(V) 06 00 00 4C DF 06 00 00 05 0C 06 00 00 03 9B 06
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 00 FF
(V) 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
(V) 4C DF 06 00 00 05 0C 06 00 00 03 9B 06 00 00 09
(V) 34 06 00 00 09 69 06 00 00 09 59
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DF 06
(V) 00 00 05 0C 06 00 00 03 9B 06 00 00 09 34 06 00
(V) 00 09 69 06 00 00 09 59
(V) DLMS
(D) READY to update (internal clock 17:00:01 UTC, meter clock: 00:00:00, list type 2, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 02 FF 80 00 00 02
(V) 01 06 00 00 13 C1 56 F5 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 02 FF 80 00 00 02 01 06 00 00 13 C1
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 02 FF
(V) 80 00 00 02 01 06 00 00 13 C1
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 C1
(V) DLMS
(D) READY to update (internal clock 17:00:02 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 04 FF 80 00 00 02
(V) 01 06 00 00 13 BC F9 5A 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 04 FF 80 00 00 02 01 06 00 00 13 BC
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 04 FF
(V) 80 00 00 02 01 06 00 00 13 BC
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BC
(V) DLMS
(D) READY to update (internal clock 17:00:04 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 06 FF 80 00 00 02
(V) 01 06 00 00 13 BC 42 58 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 06 FF 80 00 00 02 01 06 00 00 13 BC
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 06 FF
(V) 80 00 00 02 01 06 00 00 13 BC
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BC
(V) DLMS
(D) READY to update (internal clock 17:00:06 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 08 FF 80 00 00 02
(V) 01 06 00 00 13 BB DC 21 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 08 FF 80 00 00 02 01 06 00 00 13 BB
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 08 FF
(V) 80 00 00 02 01 06 00 00 13 BB
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BB
(V) DLMS
(D) READY to update (internal clock 17:00:08 UTC, meter clock: 00:00:00, list type 1, est: 1, using clock: 1)
(V) HDLC frame:
(V) 7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 0A FF 80 00 00 02
(V) 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30
(V) 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D 41
(V) 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00 06
(V) 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06 00
(V) 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00 00
(V) 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13 00
(V) 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00 06
(V) 00 97 35 DE 06 00 08 97 2F FA E5 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30
(V) 31 09 10 36 39 37 30 36 33 31 34 30 37 32 36 32
(V) 39 38 36 09 07 4D 41 33 30 34 48 34 06 00 00 13
(V) C0 06 00 00 00 00 06 00 00 00 00 06 00 00 01 D5
(V) 06 00 00 4C DD 06 00 00 05 0E 06 00 00 03 90 06
(V) 00 00 09 34 06 00 00 09 69 06 00 00 09 59 09 0C
(V) 07 E8 0A 01 02 13 00 0A FF 80 00 00 06 02 8E 4B
(V) 5E 06 00 00 00 00 06 00 97 35 DE 06 00 08 97 2F
(V)
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0A FF
(V) 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10
(V) 36 39 37 30 36 33 31 34 30 37 32 36 32 39 38 36
(V) 09 07 4D 41 33 30 34 48 34 06 00 00 13 C0 06 00
(V) 00 00 00 06 00 00 00 00 06 00 00 01 D5 06 00 00
(V) 4C DD 06 00 00 05 0E 06 00 00 03 90 06 00 00 09
(V) 34 06 00 00 09 69 06 00 00 09 59 09 0C 07 E8 0A
(V) 01 02 13 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00
(V) 00 00 00 06 00 97 35 DE 06 00 08 97 2F
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37
(V) 30 36 33 31 34 30 37 32 36 32 39 38 36 09 07 4D
(V) 41 33 30 34 48 34 06 00 00 13 C0 06 00 00 00 00
(V) 06 00 00 00 00 06 00 00 01 D5 06 00 00 4C DD 06
(V) 00 00 05 0E 06 00 00 03 90 06 00 00 09 34 06 00
(V) 00 09 69 06 00 00 09 59 09 0C 07 E8 0A 01 02 13
(V) 00 0A FF 80 00 00 06 02 8E 4B 5E 06 00 00 00 00
(V) 06 00 97 35 DE 06 00 08 97 2F
(V) DLMS
(D) READY to update (internal clock 17:00:12 UTC, meter clock: 17:00:10, list type 3, est: 1, using clock: 0)
(D) Updating data storage using actual data
(D) Clearing hours from 16 to 17
(D) Clearing days from 1 to 1
(D) Day is not happy
(D) - normal
(I) Saving data
(I) Saving energy accounting
(W) Used 1249ms to read HAN port (true)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 0C FF 80 00 00 02
(V) 01 06 00 00 13 BC 15 50 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 0C FF 80 00 00 02 01 06 00 00 13 BC
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0C FF
(V) 80 00 00 02 01 06 00 00 13 BC
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 BC
(V) DLMS
(D) NOT Ready to update (internal clock 17:00:12 UTC, meter clock: 00:00:00, list type 1, est: 0)
(V) HDLC frame:
(V) 7E A0 27 01 02 01 10 5A 87 E6 E7 00 0F 40 00 00
(V) 00 09 0C 07 E8 0A 01 02 13 00 0E FF 80 00 00 02
(V) 01 06 00 00 13 B7 7D EC 7E
(V) LLC frame:
(V) E6 E7 00 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13
(V) 00 0E FF 80 00 00 02 01 06 00 00 13 B7
(V) DLMS frame:
(V) 0F 40 00 00 00 09 0C 07 E8 0A 01 02 13 00 0E FF
(V) 80 00 00 02 01 06 00 00 13 B7
(D) Received valid DLMS at 31
(V) Using application data:
(V) 02 01 06 00 00 13 B7

View File

@ -95,7 +95,9 @@ struct MqttConfig {
uint8_t magic;
bool stateUpdate;
uint16_t stateUpdateInterval;
}; // 680
uint16_t timeout;
uint8_t keepalive;
}; // 685
struct WebConfig {
uint8_t security;
@ -249,7 +251,8 @@ struct CloudConfig {
char hostname[64];
uint16_t port;
uint8_t clientId[16];
}; // 69
uint8_t proto;
}; // 84
class AmsConfiguration {
public:

View File

@ -147,10 +147,14 @@ bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_MQTT_START, config);
EEPROM.end();
if(config.magic != 0x7B) {
config.stateUpdate = false;
config.stateUpdateInterval = 10;
config.magic = 0x7B;
if(config.magic != 0x9C) {
if(config.magic != 0x7B) {
config.stateUpdate = false;
config.stateUpdateInterval = 10;
}
config.timeout = 1000;
config.keepalive = 60;
config.magic = 0x9C;
}
return true;
} else {
@ -183,6 +187,10 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
stripNonAscii((uint8_t*) config.subscribeTopic, 64);
stripNonAscii((uint8_t*) config.username, 128);
stripNonAscii((uint8_t*) config.password, 256);
if(config.timeout < 500) config.timeout = 1000;
if(config.timeout > 10000) config.timeout = 1000;
if(config.keepalive < 5) config.keepalive = 60;
if(config.keepalive > 240) config.keepalive = 60;
EEPROM.begin(EEPROM_SIZE);
EEPROM.put(CONFIG_MQTT_START, config);
@ -205,6 +213,8 @@ void AmsConfiguration::clearMqtt(MqttConfig& config) {
config.magic = 0x7B;
config.stateUpdate = false;
config.stateUpdateInterval = 10;
config.timeout = 1000;
config.keepalive = 60;
}
void AmsConfiguration::setMqttChanged() {
@ -818,6 +828,7 @@ bool AmsConfiguration::getCloudConfig(CloudConfig& config) {
EEPROM.begin(EEPROM_SIZE);
EEPROM.get(CONFIG_CLOUD_START, config);
EEPROM.end();
if(config.proto > 2) config.proto = 0;
return true;
} else {
clearCloudConfig(config);
@ -831,6 +842,7 @@ bool AmsConfiguration::setCloudConfig(CloudConfig& config) {
cloudChanged |= config.enabled != existing.enabled;
cloudChanged |= config.interval!= existing.interval;
cloudChanged |= config.port!= existing.port;
cloudChanged |= config.proto!= existing.proto;
cloudChanged |= strcmp(config.hostname, existing.hostname) != 0;
cloudChanged |= memcmp(config.clientId, existing.clientId, 16) != 0;
} else {
@ -849,6 +861,7 @@ bool AmsConfiguration::setCloudConfig(CloudConfig& config) {
void AmsConfiguration::clearCloudConfig(CloudConfig& config) {
config.enabled = false;
strcpy_P(config.hostname, PSTR("cloud.amsleser.no"));
config.proto = 1;
config.port = 7443;
config.interval = 10;
memset(config.clientId, 0, 16);

View File

@ -67,11 +67,31 @@ bool AmsDataStorage::update(AmsData* data, time_t now) {
uint64_t exportCounter = data->getActiveExportCounter() * 1000;
// Clear hours between last update and now
if(day.lastMeterReadTime > now) {
if(!isDayHappy(now) && day.lastMeterReadTime > now) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Day was updated in the future, resetting\n"));
day.activeImport = importCounter;
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
} else if((importCounter > 0 && day.activeImport == 0) || now - day.lastMeterReadTime > 86400) {
} else if(importCounter > 0 && day.activeImport == 0) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Initializing day data\n"));
day.activeImport = importCounter;
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
for(int i = 0; i<24; i++) {
setHourImport(i, 0);
setHourExport(i, 0);
}
} else if(now - day.lastMeterReadTime > 86400) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Day was updated to long ago, clearing\n"));
day.activeImport = importCounter;
day.activeExport = exportCounter;
day.lastMeterReadTime = now;
@ -83,6 +103,10 @@ bool AmsDataStorage::update(AmsData* data, time_t now) {
tmElements_t last;
breakTime(day.lastMeterReadTime, last);
uint8_t endHour = utc.Hour;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Clearing hours from %d to %d\n"), last.Hour, endHour);
if(last.Hour > utc.Hour){
for(int i = 0; i < utc.Hour; i++) {
setHourImport(i, 0);
@ -97,11 +121,31 @@ bool AmsDataStorage::update(AmsData* data, time_t now) {
}
// Clear days between last update and now
if(month.lastMeterReadTime > now) {
if(!isMonthHappy(now) && month.lastMeterReadTime > now) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Month was updated in the future, resetting\n"));
month.activeImport = importCounter;
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
} else if((importCounter > 0 && month.activeImport == 0) || now - month.lastMeterReadTime > 2682000) {
} else if(importCounter > 0 && month.activeImport == 0) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Initializing month data\n"));
month.activeImport = importCounter;
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
for(int i = 1; i<=31; i++) {
setDayImport(i, 0);
setDayExport(i, 0);
}
} else if(now - month.lastMeterReadTime > 2682000) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Month was updated to long ago, clearing\n"));
month.activeImport = importCounter;
month.activeExport = exportCounter;
month.lastMeterReadTime = now;
@ -113,6 +157,10 @@ bool AmsDataStorage::update(AmsData* data, time_t now) {
tmElements_t last;
breakTime(tz->toLocal(month.lastMeterReadTime), last);
uint8_t endDay = ltz.Day;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Clearing days from %d to %d\n"), last.Day, endDay);
if(last.Day > ltz.Day) {
for(int i = 1; i < ltz.Day; i++) {
setDayImport(i, 0);

View File

@ -100,6 +100,8 @@ bool AmsMqttHandler::connect() {
}
mqttConfigChanged = false;
mqtt.setTimeout(mqttConfig.timeout);
mqtt.setKeepAlive(mqttConfig.keepalive);
mqtt.begin(mqttConfig.host, mqttConfig.port, *actualClient);
String statusTopic = String(mqttConfig.publishTopic) + "/status";
mqtt.setWill(statusTopic.c_str(), "offline", true, 0);

View File

@ -37,7 +37,7 @@
#warning "Unsupported board type"
#endif
#define CC_BUF_SIZE 2048
#define CC_BUF_SIZE 4096
static const char CC_JSON_POWER[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu}";
static const char CC_JSON_POWER_LIST3[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu,\"tP\":%.3f,\"tQ\":%.3f}";
@ -58,12 +58,13 @@ public:
#else
CloudConnector(Stream*);
#endif
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc);
bool setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc, PriceService* ps);
void setMqttHandler(AmsMqttHandler* mqttHandler);
void update(AmsData& data, EnergyAccounting& ea);
void setPriceConfig(PriceServiceConfig&);
void setEnergyAccountingConfig(EnergyAccountingConfig&);
void forceUpdate();
void forcePriceUpdate();
void setConnectionHandler(ConnectionHandler* ch);
String generateSeed();
@ -76,6 +77,7 @@ private:
HwTools* hw = NULL;
ConnectionHandler* ch = NULL;
ResetDataContainer* rdc = NULL;
PriceService* ps = NULL;
AmsMqttHandler* mqttHandler = NULL;
CloudConfig config;
PriceServiceConfig priceConfig;
@ -84,6 +86,7 @@ private:
unsigned long lastEac = 0;
HTTPClient http;
WiFiUDP udp;
WiFiClient tcp;
int maxPwr = 0;
uint8_t boardType = 0;
char timezone[32];
@ -99,6 +102,7 @@ private:
String seed = "";
char clearBuffer[CC_BUF_SIZE];
uint8_t* httpBuffer = NULL;
unsigned char encryptedBuffer[256];
mbedtls_rsa_context* rsa = nullptr;
mbedtls_ctr_drbg_context ctr_drbg;

View File

@ -50,7 +50,7 @@ CloudConnector::CloudConnector(Stream* debugger) {
sprintf_P(this->apmac, PSTR("%02X:%02X:%02X:%02X:%02X:%02X"), apmac[0], apmac[1], apmac[2], apmac[3], apmac[4], apmac[5]);
}
bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc) {
bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig& system, NtpConfig& ntp, HwTools* hw, ResetDataContainer* rdc, PriceService* ps) {
bool ret = false;
#if defined(ESP32)
if(!ESPRandom::isValidV4Uuid(config.clientId)) {
@ -63,6 +63,7 @@ bool CloudConnector::setup(CloudConfig& config, MeterConfig& meter, SystemConfig
this->config = config;
this->hw = hw;
this->rdc = rdc;
this->ps = ps;
this->boardType = system.boardType;
strcpy(this->timezone, ntp.timezone);
@ -200,6 +201,7 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"seed\":\"%s\""), seed.c_str());
}
bool sendData = true;
if(lastUpdate == 0) {
seed.clear();
if(mainFuse > 0 && distributionSystem > 0) {
@ -256,8 +258,62 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
dns2.toString().c_str()
);
} else if(lastPriceConfig == 0) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"price\":{\"area\":\"%s\",\"currency\":\"%s\"}"), priceConfig.area, priceConfig.currency);
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"price\":{\"area\":\"%s\",\"currency\":\"%s\",\"modifiers\":["), priceConfig.area, priceConfig.currency);
if(ps != NULL) {
std::vector<PriceConfig> pc = ps->getPriceConfig();
if(pc.size() > 0) {
for(uint8_t i = 0; i < pc.size(); i++) {
PriceConfig& p = pc.at(i);
String days;
for(uint8_t d = 0; d < 7; d++) {
if((p.days >> d) & 0x1 == 0x1) {
days += String(d, 10) + ",";
}
}
days = days.substring(0, days.length()-1);
String hours;
for(uint8_t h = 0; h < 24; h++) {
if((p.hours >> h) & 0x1 == 0x1) {
hours += String(h, 10) + ",";
}
}
hours = hours.substring(0, hours.length()-1);
char start[8];
memset(start, 0, 8);
if(p.start_dayofmonth > 0 && p.start_month > 0) {
snprintf_P(start, 8, PSTR("[%d,%d]"), p.start_month, p.start_dayofmonth);
} else {
strcpy_P(start, PSTR("null"));
}
char end[8];
memset(end, 0, 8);
if(p.end_dayofmonth > 0 && p.end_month > 0) {
snprintf_P(end, 8, PSTR("[%d,%d]"), p.end_month, p.end_dayofmonth);
} else {
strcpy_P(end, PSTR("null"));
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"type\":%d,\"name\":\"%s\",\"dir\":%d,\"days\":[%s],\"hours\":[%s],\"value\":%.4f,\"start\":%s,\"end\":%s}%s"),
p.type,
p.name,
p.direction,
days.c_str(),
hours.c_str(),
p.value / 10000.0,
start,
end,
i == pc.size()-1 ? "" : ","
);
}
}
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("]}"));
lastPriceConfig = now;
sendData = false;
} else if(lastEac == 0) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"accounting\":{\"hours\":%d,\"thresholds\":[%d,%d,%d,%d,%d,%d,%d,%d,%d]}"),
eac.hours,
@ -274,132 +330,133 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
lastEac = now;
}
float vcc = 0.0;
int rssi = 0;
float temperature = -127;
if(hw != NULL) {
vcc = hw->getVcc();
rssi = hw->getWifiRssi();
temperature = hw->getTemperature();
}
uint8_t espStatus;
#if defined(ESP8266)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 2.8 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 2.7 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
#elif defined(ESP32)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 3.1 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 3.0 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
#endif
uint8_t hanStatus;
if(data.getLastError() != 0) {
hanStatus = 3;
} else if(data.getLastUpdateMillis() == 0 && now < 30000) {
hanStatus = 0;
} else if(now - data.getLastUpdateMillis() < 15000) {
hanStatus = 1;
} else if(now - data.getLastUpdateMillis() < 30000) {
hanStatus = 2;
} else {
hanStatus = 3;
}
uint8_t wifiStatus;
if(rssi > -75) {
wifiStatus = 1;
} else if(rssi > -95) {
wifiStatus = 2;
} else {
wifiStatus = 3;
}
uint8_t mqttStatus;
if(mqttHandler == NULL) {
mqttStatus = 0;
} else if(mqttHandler->connected()) {
mqttStatus = 1;
} else if(mqttHandler->lastError() == 0) {
mqttStatus = 2;
} else {
mqttStatus = 3;
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
(uint32_t) time(nullptr),
(uint32_t) (millis64()/1000),
(uint32_t) (data.getLastUpdateMillis()/1000),
data.isCounterEstimated() ? "true" : "false"
);
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
}
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
}
if(data.getListType() > 1) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
bool first = true;
if(data.getL1Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
}
first = false;
if(sendData) {
float vcc = 0.0;
int rssi = 0;
float temperature = -127;
if(hw != NULL) {
vcc = hw->getVcc();
rssi = hw->getWifiRssi();
temperature = hw->getTemperature();
}
if(data.getL2Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), String(data.getL2Current(), 2).c_str(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.isL2currentMissing() ? "null" : String(data.getL2Current(), 2).c_str());
}
first = false;
uint8_t espStatus;
#if defined(ESP8266)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 2.8 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 2.7 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
if(data.getL3Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str());
}
first = false;
#elif defined(ESP32)
if(vcc < 2.0) { // Voltage not correct, ESP would not run on this voltage
espStatus = 1;
} else if(vcc > 3.1 && vcc < 3.5) {
espStatus = 1;
} else if(vcc > 3.0 && vcc < 3.6) {
espStatus = 2;
} else {
espStatus = 3;
}
#endif
uint8_t hanStatus;
if(data.getLastError() != 0) {
hanStatus = 3;
} else if(data.getLastUpdateMillis() == 0 && now < 30000) {
hanStatus = 0;
} else if(now - data.getLastUpdateMillis() < 15000) {
hanStatus = 1;
} else if(now - data.getLastUpdateMillis() < 30000) {
hanStatus = 2;
} else {
hanStatus = 3;
}
uint8_t wifiStatus;
if(rssi > -75) {
wifiStatus = 1;
} else if(rssi > -95) {
wifiStatus = 2;
} else {
wifiStatus = 3;
}
uint8_t mqttStatus;
if(mqttHandler == NULL) {
mqttStatus = 0;
} else if(mqttHandler->connected()) {
mqttStatus = 1;
} else if(mqttHandler->lastError() == 0) {
mqttStatus = 2;
} else {
mqttStatus = 3;
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
(uint32_t) time(nullptr),
(uint32_t) (millis64()/1000),
(uint32_t) (data.getLastUpdateMillis()/1000),
data.isCounterEstimated() ? "true" : "false"
);
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
}
if(data.getListType() > 2) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
}
if(data.getListType() > 1) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
bool first = true;
if(data.getL1Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), String(data.getL1Current(), 2).c_str());
}
first = false;
}
if(data.getL2Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), String(data.getL2Current(), 2).c_str(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.isL2currentMissing() ? "null" : String(data.getL2Current(), 2).c_str());
}
first = false;
}
if(data.getL3Voltage() > 0.0) {
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
} else {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), String(data.getL3Current(), 2).c_str());
}
first = false;
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
}
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS,
espStatus, 0,
hanStatus, data.getLastError(),
wifiStatus, 0,
mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
);
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
}
if(data.getListType() > 3) {
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
}
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), vcc, temperature, rssi, ESP.getFreeHeap());
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_STATUS,
espStatus, 0,
hanStatus, data.getLastError(),
wifiStatus, 0,
mqttStatus, mqttHandler == NULL ? 0 : mqttHandler->lastError()
);
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
uint16_t crc = crc16((uint8_t*) clearBuffer, pos);
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"crc\":\"%04X\"}"), crc);
@ -420,11 +477,54 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
memset(encryptedBuffer, 0, rsa->len);
int maxlen = rsa->len - 11; // 11 should be the correct padding size for PKCS1
udp.beginPacket(config.hostname,7443);
Stream *stream = NULL;
if(config.proto == 0) {
udp.beginPacket(config.hostname,7443);
stream = &udp;
} else if(config.proto == 1) {
if(!tcp.connected()) {
int ret = tcp.connect(config.hostname, config.port, 1000);
if(ret != 1) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("tcp.connect(%s, %d) return code: %d\n"), config.hostname, config.port, ret);
return;
}
tcp.setTimeout(config.interval * 2);
}
while(tcp.available()) tcp.read(); // Empty incoming buffer
stream = &tcp;
} else if(config.proto == 2) {
if(!http.connected()) {
http.setReuse(true);
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/data"), config.hostname);
if(!http.begin(clearBuffer)) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(CloudConnector) Unable to start HTTP connector\n"));
http.end();
return;
}
}
if(httpBuffer == NULL) {
httpBuffer = (uint8_t*) malloc(CC_BUF_SIZE);
}
}
int sendBytes = 0;
for(int i = 0; i < pos; i += maxlen) {
int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, maxlen, (unsigned char*) (clearBuffer+i), encryptedBuffer);
if(ret == 0) {
udp.write(encryptedBuffer, rsa->len);
if(stream != NULL) {
stream->write(encryptedBuffer, rsa->len);
} else {
memcpy(httpBuffer + sendBytes, encryptedBuffer, rsa->len);
}
sendBytes += rsa->len;
delay(1);
} else {
#if defined(AMS_REMOTE_DEBUG)
@ -438,9 +538,39 @@ void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
debugger->printf_P(PSTR("%s\n"), clearBuffer);
}
}
udp.endPacket();
if(config.proto == 0) {
udp.endPacket();
} else if(config.proto == 1) {
tcp.write("\r\n");
tcp.flush();
} else if(config.proto == 2) {
http.addHeader("Content-Type", "application/octet-stream");
int status = http.POST(httpBuffer, sendBytes);
if(status != 200) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("(CloudConnector) Communication error 2, returned status: %d\n"), status);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf(http.errorToString(status).c_str());
debugger->println();
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf(http.getString().c_str());
http.end();
}
}
lastUpdate = now;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("%d bytes sent to %s:%d from %s\n"), sendBytes, config.hostname, config.proto == 2 ? 80 : config.port, uuid.c_str());
}
void CloudConnector::forceUpdate() {
@ -449,6 +579,10 @@ void CloudConnector::forceUpdate() {
lastEac = 0;
}
void CloudConnector::forcePriceUpdate() {
lastPriceConfig = 0;
}
void CloudConnector::setConnectionHandler(ConnectionHandler* ch) {
this->ch = ch;
}

View File

@ -60,6 +60,14 @@ bool EthernetConnectionHandler::connect(NetworkConfig config, SystemConfig sys)
ethPowerPin = 16;
ethMdc = 23;
ethMdio = 18;
} else if (sys.boardType == 245) { // wESP32
ethType = ETH_PHY_RTL8201;
ethEnablePin = -1;
ethAddr = 0;
ethClkMode = ETH_CLOCK_GPIO0_IN;
ethPowerPin = -1;
ethMdc = 16;
ethMdio = 17;
} else if(sys.boardType == 244) {
return false; // TODO
} else {

View File

@ -18,10 +18,10 @@
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f,
"PF" : %.2f,
"PF1" : %.2f,
"PF2" : %.2f,
"PF3" : %.2f,
"PF" : %d,
"PF1" : %d,
"PF2" : %d,
"PF3" : %d,
"tPI1" : %.3f,
"tPI2" : %.3f,
"tPI3" : %.3f,

View File

@ -124,10 +124,10 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
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(),
data->getPowerFactor() == 0 ? 100 : (int) (data->getPowerFactor() * 100),
data->getPowerFactor() == 0 ? 100 : (int) (data->getL1PowerFactor() * 100),
data->getPowerFactor() == 0 ? 100 : (int) (data->getL2PowerFactor() * 100),
data->getPowerFactor() == 0 ? 100 : (int) (data->getL3PowerFactor() * 100),
data->getL1ActiveImportCounter(),
data->getL2ActiveImportCounter(),
data->getL3ActiveImportCounter(),

View File

@ -75,6 +75,9 @@ bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterC
case 243: // WT32-ETH01
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
return true;
case 245: // wESP32
gpioConfig.apPin = 0;
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
case 201: // D32
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
gpioConfig.apPin = 4;

View File

@ -30,6 +30,10 @@ public:
void configure(MeterConfig&);
bool loop();
AmsData* getData(AmsData& meterState);
int getLastError();
void getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
private:
KmpTalker* talker = NULL;
};

View File

@ -8,6 +8,16 @@
#include <Stream.h>
#define DATA_PARSE_OK 0
#define DATA_PARSE_FAIL -1
#define DATA_PARSE_INCOMPLETE -2
#define DATA_PARSE_FOOTER_CHECKSUM_ERROR -5
struct KmpParserContext {
uint8_t type;
uint16_t length;
};
struct KmpDataHolder {
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
@ -22,7 +32,25 @@ struct KmpDataHolder {
class KmpTalker {
public:
KmpTalker(Stream *hanSerial);
KmpTalker(Stream *hanSerial, uint8_t* hanBuffer, uint16_t hanBufferSize);
bool loop();
void getData(KmpDataHolder& data);
int getLastError();
private:
Stream *hanSerial;
uint8_t *hanBuffer = NULL;
uint16_t hanBufferSize = 0;
bool dataAvailable = false;
int len = 0;
int pos = DATA_PARSE_INCOMPLETE;
int lastError = DATA_PARSE_OK;
bool serialInit = false;
uint64_t lastUpdate = 0;
uint8_t batch = 0;
KmpParserContext ctx;
KmpDataHolder state;
};

View File

@ -22,6 +22,7 @@ public:
virtual AmsData* getData(AmsData& meterState);
virtual int getLastError();
virtual bool isConfigChanged();
virtual void ackConfigChanged();
virtual void getCurrentConfig(MeterConfig& meterConfig);
};

View File

@ -20,7 +20,7 @@
#include "SoftwareSerial.h"
#endif
const uint32_t AUTO_BAUD_RATES[] = { 2400, 115200 };
const uint32_t AUTO_BAUD_RATES[] = { 2400, 9600, 115200 };
class PassiveMeterCommunicator : public MeterCommunicator {
public:
@ -34,6 +34,7 @@ public:
AmsData* getData(AmsData& meterState);
int getLastError();
bool isConfigChanged();
void ackConfigChanged();
void getCurrentConfig(MeterConfig& meterConfig);
void setPassthroughMqttHandler(PassthroughMqttHandler*);

View File

@ -27,6 +27,7 @@ public:
AmsData* getData(AmsData& meterState);
int getLastError();
bool isConfigChanged();
void ackConfigChanged();
void getCurrentConfig(MeterConfig& meterConfig);
void onPulse(uint8_t pulses);

View File

@ -18,7 +18,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 = ctx.timestamp;
this->packageTimestamp = ctx.timestamp == 0 ? time(nullptr) : ctx.timestamp;
val = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
if(val == NOVALUE) {
@ -289,14 +289,6 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
}
}
}
// Try system title
if(meterType == AmsTypeUnknown) {
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
meterType = AmsTypeSagemcom;
} else if(memcmp(ctx.system_title, "KFM", 3) == 0) {
meterType = AmsTypeKaifa;
}
}
if(this->packageTimestamp > 0) {
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
@ -404,10 +396,10 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
if(meterTs != NULL) {
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
time_t ts = decodeCosemDateTime(amst->dt);
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
meterTimestamp = ts - 3600;
} else {
meterTimestamp = ts;
if(amst->dt.deviation == 0x8000) { // Deviation not specified, adjust from localtime to UTC
meterTimestamp = tz.toUTC(ts);
} else if(meterType == AmsTypeAidon) {
meterTimestamp = ts - 3600; // 21.09.24, the clock is now correct
}
}
@ -551,6 +543,21 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
lastUpdateMillis = millis64();
}
// Try system title
if(meterType == AmsTypeUnknown) {
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
meterType = AmsTypeSagemcom;
} else if(memcmp(ctx.system_title, "KFM", 3) == 0) {
meterType = AmsTypeKaifa;
} else if(memcmp(ctx.system_title, "ISK", 3) == 0) {
meterType = AmsTypeIskra;
}
if(meterType != AmsTypeUnknown) {
meterId = String((const char*)ctx.system_title);
}
}
if(meterConfig->wattageMultiplier > 0) {
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
@ -572,6 +579,12 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l1activeImportCounter = l1activeImportCounter > 0 ? l1activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l2activeImportCounter = l2activeImportCounter > 0 ? l2activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l3activeImportCounter = l3activeImportCounter > 0 ? l3activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l1activeExportCounter = l1activeExportCounter > 0 ? l1activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l2activeExportCounter = l2activeExportCounter > 0 ? l2activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
l3activeExportCounter = l3activeExportCounter > 0 ? l3activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;

View File

@ -12,16 +12,49 @@
void KmpCommunicator::configure(MeterConfig& meterConfig) {
this->meterConfig = meterConfig;
this->configChanged = false;
if(meterConfig.baud == 0) {
this->configChanged = true;
meterConfig.baud = 9600;
meterConfig.parity = 7;
meterConfig.invert = false;
}
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert, false);
talker = new KmpTalker(hanSerial);
talker = new KmpTalker(hanSerial, hanBuffer, hanBufferSize);
}
bool KmpCommunicator::loop() {
uint64_t now = millis64();
return talker->loop();
bool ret = talker->loop();
int lastError = getLastError();
if(ret) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("Successful loop\n"));
Serial.flush();
} else if(lastError < 0 && lastError != DATA_PARSE_INCOMPLETE) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Error code: %d\n"), getLastError());
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, hanBufferSize);
}
}
return ret;
}
int KmpCommunicator::getLastError() {
return talker == NULL ? DATA_PARSE_FAIL : talker->getLastError();
}
AmsData* KmpCommunicator::getData(AmsData& meterState) {
if(talker == NULL) return NULL;
KmpDataHolder kmpData;
talker->getData(kmpData);
AmsData* data = new AmsData();

View File

@ -306,11 +306,14 @@ bool PassiveMeterCommunicator::isConfigChanged() {
return configChanged;
}
void PassiveMeterCommunicator::ackConfigChanged() {
configChanged = false;
}
void PassiveMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &context) {
int16_t ret = 0;
bool doRet = false;
@ -578,7 +581,7 @@ void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal
int8_t txpin = passive ? -1 : meterConfig.txPin;
if(baud == 0) {
baud = 2400;
autodetectBaud = baud = 2400;
}
#if defined(AMS_REMOTE_DEBUG)

View File

@ -47,6 +47,10 @@ bool PulseMeterCommunicator::isConfigChanged() {
return this->configChanged;
}
void PulseMeterCommunicator::ackConfigChanged() {
configChanged = false;
}
void PulseMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}

View File

@ -72,7 +72,10 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
} else if(docPos == DOCPOS_POSITION) {
if(byte == '<') {
buf[pos] = '\0';
pointNum = String(buf).toInt() - 1;
long pn = String(buf).toInt() - 1;
if(pn < 25) {
pointNum = pn;
}
docPos = DOCPOS_SEEK;
pos = 0;
} else {
@ -81,7 +84,10 @@ size_t EntsoeA44Parser::write(uint8_t byte) {
} else if(docPos == DOCPOS_AMOUNT) {
if(byte == '<') {
buf[pos] = '\0';
points[pointNum] = String(buf).toFloat();
float val = String(buf).toFloat();
for(uint8_t i = pointNum; i < 25; i++) {
points[i] = val;
}
docPos = DOCPOS_SEEK;
pos = 0;
} else {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,11 @@
"build": "vite build",
"preview": "vite preview"
},
"overrides": {
"svelte-navigator": {
"svelte": ">=4.x"
}
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.1.0",
"@tailwindcss/forms": "^0.5.3",
@ -16,7 +21,7 @@
"http-proxy-middleware": "^2.0.6",
"postcss": "^8.4.31",
"postcss-load-config": "^4.0.1",
"svelte": "^3.58.0",
"svelte": "^4.2.19",
"svelte-navigator": "^3.2.2",
"svelte-preprocess": "^5.0.3",
"svelte-qrcode": "^1.0.0",

View File

@ -45,7 +45,7 @@
{#if hasCost}<div class="text-right">{fmtnum(data.m.c)} {currency}</div>{/if}
<div>{translations.realtime?.last_mo ?? "Last mo."}</div>
<div class="text-right">{ril[0]} {ril[1]}</div>
{#if hasCost}<div class="text-right">{fmtnum(sysinfo.last_month.c)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(sysinfo.last_month?.c)} {currency}</div>{/if}
</div>
<strong>{translations.common?.export ?? "Export"}</strong>
<div class="grid grid-cols-{cols}">
@ -60,7 +60,7 @@
{#if hasCost}<div class="text-right">{fmtnum(data.m.i)} {currency}</div>{/if}
<div>{translations.realtime?.last_mo ?? "Last mo."}</div>
<div class="text-right">{rel[0]} {rel[1]}</div>
{#if hasCost}<div class="text-right">{fmtnum(sysinfo.last_month.i)} {currency}</div>{/if}
{#if hasCost}<div class="text-right">{fmtnum(sysinfo.last_month?.i)} {currency}</div>{/if}
</div>
{:else}
<strong>{translations.realtime?.consumption ?? "Consumption"}</strong>
@ -84,7 +84,7 @@
<div>{capitalize(translations.common?.month ?? "Month")}</div>
<div class="text-right">{fmtnum(data.m.c)} {currency}</div>
<div>{translations.realtime?.last_month ?? "Last month"}</div>
<div class="text-right">{fmtnum(sysinfo.last_month.c)} {currency}</div>
<div class="text-right">{fmtnum(sysinfo.last_month?.c)} {currency}</div>
</div>
{/if}
{/if}

View File

@ -30,6 +30,7 @@
<option value={241}>{boardtype(chip, 241)}</option>
<option value={242}>{boardtype(chip, 242)}</option>
<option value={243}>{boardtype(chip, 243)}</option>
<option value={245}>{boardtype(chip, 245)}</option>
<option value={200}>{boardtype(chip, 200)}</option>
</optgroup>
{/if}

View File

@ -598,15 +598,15 @@
<div class="my-1 flex">
<div>
{translations.conf?.mqtt?.id ?? "Client ID"}<br/>
<input name="qc" bind:value={configuration.q.c} type="text" class="in-f w-full"/>
<input name="qc" bind:value={configuration.q.c} type="text" class="in-f w-full" required={configuration.q.h}/>
</div>
<div>
{translations.conf?.mqtt?.payload ?? "Payload"}<br/>
<select name="qm" bind:value={configuration.q.m} class="in-l">
<option value={1}>Raw (minimal)</option>
<option value={2}>Raw (full)</option>
<option value={3}>{translations.conf?.mqtt?.domoticz?.title ?? "Domoticz"}</option>
<option value={4}>{translations.conf?.mqtt?.ha?.title ?? "Home-Assistant"}</option>
<option value={3}>Domoticz</option>
<option value={4}>Home-Assistant</option>
<option value={0}>JSON (classic)</option>
<option value={5}>JSON (multi topic)</option>
<option value={6}>JSON (flat)</option>
@ -620,15 +620,23 @@
</div>
<div class="my-1">
{translations.conf?.mqtt?.update ?? "Update method"}
<span class="float-right">Interval</span>
<span class="float-right">{translations.conf?.mqtt?.interval ?? "Interval"}</span>
<div class="flex">
<select name="qt" bind:value={configuration.q.t} class="in-f w-1/2">
<option value={0}>Real time</option>
<option value={1}>Interval</option>
<option value={0}>{translations.conf?.mqtt?.realtime ?? "Real time"}</option>
<option value={1}>{translations.conf?.mqtt?.interval ?? "Interval"}</option>
</select>
<input name="qd" bind:value={configuration.q.d} type="number" min="1" max="3600" class="in-l tr w-1/2" disabled={configuration?.q?.t != 1}/>
</div>
</div>
<div class="my-1">
{translations.conf?.mqtt?.timeout ?? "Timeout"}
<span class="float-right">{translations.conf?.mqtt?.keepalive ?? "Keep-alive"}</span>
<div class="flex">
<input name="qi" bind:value={configuration.q.i} type="number" min="500" max="10000" class="in-f tr w-1/2"/>
<input name="qk" bind:value={configuration.q.k} type="number" min="5" max="180" class="in-l tr w-1/2"/>
</div>
</div>
</div>
{/if}
{#if configuration?.q?.m == 3}
@ -682,10 +690,22 @@
<input type="hidden" name="c" value="true"/>
<div class="my-1">
<label><input type="checkbox" name="ce" value="true" bind:checked={configuration.c.e} class="rounded mb-1"/> {translations.conf?.cloud?.ams ?? "AMS reader cloud"}</label>
{#if cloudenabled}
<button type="button" on:click={cloudBind} class="text-blue-500 ml-6">Connect to my cloud account</button>
{#if configuration.c.e}
<div class="ml-6">
<label for="cp">Protocol</label>
<select name="cp" bind:value={configuration.c.p} class="in-s">
{#if configuration.c.p == 0}
<option value={0} title="No longer recommended">UDP</option>
{/if}
<option value={1}>TCP</option>
<option value={2}>HTTP</option>
</select>
</div>
{#if cloudenabled}
<button type="button" on:click={cloudBind} class="text-blue-500 ml-6">Connect device to my cloud account</button>
{/if}
{/if}
</div>
</div>
<div class="my-1">
<label><input type="checkbox" class="rounded mb-1" name="ces" value="true" bind:checked={configuration.c.es}/> {translations.conf?.cloud?.es ?? "Energy Speedometer"}</label>
{#if configuration?.c?.es}

View File

@ -18,9 +18,13 @@
let xTicks = [];
let points = [];
min = max = 0;
let cur = addHours(new Date(), -24);
let currentHour = new Date().getUTCHours();
addHours(cur, sysinfo.clock_offset - ((24 + cur.getHours() - cur.getUTCHours())%24));
let cur = new Date();
let lm = new Date();
lm.setDate(0);
lm.setHours(12);
let clock_adjust = ((lm.getHours() - lm.getUTCHours())%24) - sysinfo.clock_offset;
let currentHour = cur.getUTCHours();
addHours(cur, -clock_adjust-24);
for(i = currentHour; i<24; i++) {
let imp = json["i"+zeropad(i)];
let exp = json["e"+zeropad(i)];

View File

@ -55,10 +55,10 @@
<div class="flex-none my-auto">{translations.header?.mem ?? "Free"}: {data.m ? (data.m/1000).toFixed(1) : '-'}kb</div>
</div>
<div class="flex-auto flex-wrap my-auto justify-center p-2">
<Badge title={translations.header?.esp ?? "ESP"} text={sysinfo.booting ? (translations.header?.booting ?? "Booting") : data.v > 2.0 ? data.v.toFixed(2)+"V" : (translations.header?.esp ?? "ESP")} color={bcol(sysinfo.booting ? 2 : data.em)}/>
<Badge title={translations.header?.han ?? "HAN"} text={translations.header?.han ?? "HAN"} color={bcol(sysinfo.booting ? 9 : data.hm)}/>
<Badge title={translations.header?.wifi ?? "WiFi"} text={data.r ? data.r.toFixed(0)+"dBm" : (translations.header?.wifi ?? "WiFi")} color={bcol(sysinfo.booting ? 9 : data.wm)}/>
<Badge title={translations.header?.mqtt ?? "MQTT"} text={translations.header?.mqtt ?? "MQTT"} color={bcol(sysinfo.booting ? 9 : data.mm)}/>
<Badge title="ESP" text={sysinfo.booting ? (translations.header?.booting ?? "Booting") : data.v > 2.0 ? data.v.toFixed(2)+"V" : "ESP"} color={bcol(sysinfo.booting ? 2 : data.em)}/>
<Badge title="HAN" text="HAN" color={bcol(sysinfo.booting ? 9 : data.hm)}/>
<Badge title="WiFi" text={data.r ? data.r.toFixed(0)+"dBm" : "WiFi"} color={bcol(sysinfo.booting ? 9 : data.wm)}/>
<Badge title="MQTT" text="MQTT" color={bcol(sysinfo.booting ? 9 : data.mm)}/>
</div>
{#if data.he < 0 || data.he > 0}
<div class="bd-red">{ (translations.header?.han ?? "HAN") + ': ' + (translations.errors?.han?.[data.he] ?? data.he) }</div>

View File

@ -85,6 +85,8 @@ export function boardtype(c, b) {
return "M5 PoESP32";
case 243:
return "WT32-ETH01";
case 245:
return "wESP32";
case 200:
return "Generic ESP32";
case 2:

View File

@ -20,9 +20,12 @@
min = max = 0;
let cur = new Date();
let lm = new Date();
addHours(cur, sysinfo.clock_offset - ((24 + cur.getHours() - cur.getUTCHours())%24));
addHours(lm, sysinfo.clock_offset - ((24 + lm.getHours() - lm.getUTCHours())%24));
lm.setDate(0);
lm.setHours(12);
let clock_adjust = ((lm.getHours() - lm.getUTCHours())%24) - sysinfo.clock_offset;
addHours(cur, -clock_adjust);
addHours(lm, -clock_adjust);
for(i = cur.getDate(); i<=lm.getDate(); i++) {
let imp = json["i"+zeropad(i)];

View File

@ -1,7 +1,7 @@
<script>
import { priceConfigStore, getPriceConfig } from './ConfigurationStore'
import { translationsStore } from './TranslationService';
import { wiki } from './Helpers.js';
import { wiki, zeropad } from './Helpers.js';
import Mask from './Mask.svelte'
import { navigate } from 'svelte-navigator';
@ -170,7 +170,7 @@
<select name="rsm" class="in-m" bind:value={c.s.m}>
<option value={0}>-</option>
{#each {length: 12} as _,i}
<option value={i+1}>{translations.months?.[i]}</option>
<option value={i+1}>{translations.months ? translations.months?.[i] : zeropad(i+1)}</option>
{/each}
</select>
<input class="in-m" disabled value="to" style="width: 20px;color:#888;"/>
@ -183,7 +183,7 @@
<select name="rem" class="in-l" bind:value={c.e.m}>
<option value={0}>-</option>
{#each {length: 12} as _,i}
<option value={i+1}>{translations.months?.[i]}</option>
<option value={i+1}>{translations.months ? translations.months?.[i] : zeropad(i+1)}</option>
{/each}
</select>
</div>
@ -211,4 +211,4 @@
</div>
<Mask active={loading} message={translations.conf?.price?.mask_loading ?? "Loading"}/>
<Mask active={saving} message={translations.conf?.price?.mask_loading ?? "Saving"}/>
<Mask active={saving} message={translations.conf?.price?.mask_saving ?? "Saving"}/>

View File

@ -90,7 +90,7 @@
const formData = new FormData();
formData.append('file', configFiles[0]);
const upload = fetch('/configfile', {
const upload = fetch('configfile', {
method: 'POST',
body: formData
}).then((response) => response.json()).then((res) => {
@ -197,11 +197,11 @@
</div>
{#if sysinfo.net.ipv6}
<div class="my-2">
IPv6: {sysinfo.net.ipv6}
IPv6: <span style="font-size: 14px;">{sysinfo.net.ipv6.replace(/\b:?(?:0+:?){2,}/, '::')}</span>
</div>
<div class="my-2">
{#if sysinfo.net.dns1v6}DNSv6: {sysinfo.net.dns1v6}{/if}
{#if sysinfo.net.dns2v6}DNSv6: {sysinfo.net.dns2v6}{/if}
{#if sysinfo.net.dns1v6}DNSv6: <span style="font-size: 14px;">{sysinfo.net.dns1v6.replace(/\b:?(?:0+:?){2,}/, '::')}</span>{/if}
{#if sysinfo.net.dns2v6}DNSv6: <span style="font-size: 14px;">{sysinfo.net.dns2v6.replace(/\b:?(?:0+:?){2,}/, '::')}</span>{/if}
</div>
{/if}
</div>

View File

@ -33,7 +33,7 @@
color: dark ? '#5c2da5' : '#7c3aed'
});
xTicks.push({
label: peak.d > 0 ? zeropad(peak.d) + "." + translations.months?.[new Date().getMonth()] : "-"
label: peak.d > 0 ? zeropad(peak.d) + "." + (translations.months ? translations.months?.[new Date().getMonth()] : zeropad(new Date().getMonth()+1)) : "-"
})
max = Math.max(max, peak.v);
}

View File

@ -23,3 +23,4 @@
<option value={i}>GPIO{i}</option>
{/if}
{/each}

View File

@ -27,6 +27,7 @@ export default defineConfig({
"/tariff.json": "http://192.168.233.154",
"/realtime.json": "http://192.168.233.154",
"/priceconfig.json": "http://192.168.233.154",
"/translations.json": "http://192.168.233.154",
"/cloudkey.json": "http://192.168.233.154",
"/save": "http://192.168.233.154",
"/reboot": "http://192.168.233.154",

View File

@ -1,4 +1,5 @@
"c": {
"e" : %s,
"p" : %d,
"es": %s
}

View File

@ -13,5 +13,7 @@
"k": %s
},
"t": %d,
"d": %d
"d": %d,
"i": %d,
"k": %d
},

View File

@ -975,7 +975,9 @@ void AmsWebServer::configurationJson() {
qsr ? "true" : "false",
qsk ? "true" : "false",
mqttConfig.stateUpdate,
mqttConfig.stateUpdateInterval
mqttConfig.stateUpdateInterval,
mqttConfig.timeout,
mqttConfig.keepalive
);
server.sendContent(buf);
@ -1050,6 +1052,7 @@ void AmsWebServer::configurationJson() {
server.sendContent(buf);
snprintf_P(buf, BufferSize, CONF_CLOUD_JSON,
cloud.enabled ? "true" : "false",
cloud.proto,
#if defined(ESP32) && defined(ENERGY_SPEEDOMETER_PASS)
sysConfig.energyspeedometer == 7 ? "true" : "false"
#else
@ -1388,6 +1391,8 @@ void AmsWebServer::handleSave() {
mqtt.stateUpdate = server.arg(F("qt")).toInt() == 1;
mqtt.stateUpdateInterval = server.arg(F("qd")).toInt();
mqtt.timeout = server.arg(F("qi")).toInt();
mqtt.keepalive = server.arg(F("qk")).toInt();
} else {
config->clearMqtt(mqtt);
}
@ -1570,6 +1575,7 @@ void AmsWebServer::handleSave() {
CloudConfig cloud;
config->getCloudConfig(cloud);
cloud.enabled = server.hasArg(F("ce")) && server.arg(F("ce")) == F("true");
cloud.proto = server.arg(F("cp")).toInt();
config->setCloudConfig(cloud);
}
@ -1628,6 +1634,11 @@ void AmsWebServer::handleSave() {
}
ps->cropPriceConfig(count);
ps->save();
#if defined(_CLOUDCONNECTOR_H)
if(cloud != NULL) {
cloud->forcePriceUpdate();
}
#endif
} else {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
@ -2492,6 +2503,9 @@ void AmsWebServer::redirectToMain() {
context = String(webConfig.context);
}
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
server.sendHeader(HEADER_LOCATION, "/" + context);
server.send(302);
}
@ -2555,11 +2569,20 @@ void AmsWebServer::addConditionalCloudHeaders() {
String ref;
if(server.hasHeader(HEADER_ORIGIN)) {
ref = server.header(HEADER_ORIGIN);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Origin: %s\n"), ref.c_str());
} else if(server.hasHeader(HEADER_REFERER)) {
ref = server.header(HEADER_REFERER);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Referer: %s\n"), ref.c_str());
} else {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("No Origin or Referer\n"));
}
if(!ref.startsWith(ORIGIN_AMSLESER_CLOUD)) {

View File

@ -38,7 +38,7 @@ platform = https://github.com/tasmota/platform-espressif32/releases/download/202
framework = arduino
board = esp32dev
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} -D AMS_REMOTE_DEBUG=1
build_flags = ${common.build_flags}
lib_ldf_mode = off
lib_compat_mode = off
lib_deps = ${esp32.lib_deps}
@ -57,7 +57,12 @@ board_build.variant = esp32s2
board_build.flash_mode = qio
board_build.f_cpu = 160000000L
board_build.f_flash = 40000000L
build_flags = ${common.build_flags} -D AMS_REMOTE_DEBUG=1
build_flags =
${common.build_flags}
-D AMS_REMOTE_DEBUG=1
-D AMS_KMP=1
-L precompiled/esp32s2
-lKmpTalker
lib_ldf_mode = off
lib_compat_mode = off
lib_deps = ${esp32.lib_deps}
@ -69,7 +74,13 @@ platform = https://github.com/tasmota/platform-espressif32/releases/download/202
framework = arduino
board = esp32-solo1
board_build.f_cpu = 160000000L
build_flags = ${common.build_flags} -DFRAMEWORK_ARDUINO_SOLO1 -D AMS_REMOTE_DEBUG=1
build_flags =
${common.build_flags}
-DFRAMEWORK_ARDUINO_SOLO1
-D AMS_REMOTE_DEBUG=1
-D AMS_KMP=1
-L precompiled/esp32
-lKmpTalker
lib_ldf_mode = off
lib_compat_mode = off
lib_deps = ${esp32.lib_deps}
@ -93,7 +104,12 @@ platform = https://github.com/tasmota/platform-espressif32/releases/download/202
framework = arduino
board = esp32-s3-devkitc-1
board_build.mcu = esp32s3
build_flags = ${common.build_flags} -D AMS_REMOTE_DEBUG=1
build_flags =
${common.build_flags}
-D AMS_REMOTE_DEBUG=1
-D AMS_KMP=1
-L precompiled/esp32s3
-lKmpTalker
lib_ldf_mode = off
lib_compat_mode = off
lib_deps = ${esp32.lib_deps}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -287,11 +287,6 @@ void rxerr(int err) {
if(passiveMc != NULL) {
passiveMc->rxerr(err);
}
#if defined(AMS_KMP)
if(kmpMc != NULL) {
kmpMc->rxerr(err);
}
#endif
}
#endif
@ -706,7 +701,7 @@ void loop() {
}
NtpConfig ntp;
config.getNtpConfig(ntp);
if(cloud->setup(cc, meterConfig, sysConfig, ntp, &hw, &rdc)) {
if(cloud->setup(cc, meterConfig, sysConfig, ntp, &hw, &rdc, ps)) {
config.setCloudConfig(cc);
}
cloud->setConnectionHandler(ch);
@ -1314,6 +1309,7 @@ bool readHanPort() {
if(mc->isConfigChanged()) {
mc->getCurrentConfig(meterConfig);
config.setMeterConfig(meterConfig);
mc->ackConfigChanged();
}
meterState.setLastError(mc->getLastError());
@ -1366,29 +1362,35 @@ void handleDataSuccess(AmsData* data) {
}
}
bool wasCounterEstimated = meterState.isCounterEstimated();
meterState.apply(*data);
rtp.update(meterState);
time_t dataUpdateTime = now;
if(abs(now - meterTime) < 300) {
dataUpdateTime = meterTime;
}
tmElements_t tm, mtm;
breakTime(now, tm);
breakTime(meterTime, mtm);
bool saveData = false;
if(!ds.isHappy(now) && now > FirmwareVersion::BuildEpoch) { // Must use "isHappy()" in case day state gets reset and lastTimestamp is "now"
tmElements_t tm, mtm;
breakTime(now, tm);
breakTime(meterTime, mtm);
if(data->getListType() >= 3) {
if(tm.Minute == 0) {
debugD_P(PSTR("Updating data storage, triggered by internal clock %02d:%02d:%02d UTC (Meter clock: %02d:%02d:%02d)"), tm.Hour, tm.Minute, tm.Second, mtm.Hour, mtm.Minute, mtm.Second);
saveData = ds.update(data, now);
} else if(mtm.Minute == 0 && meterTime > FirmwareVersion::BuildEpoch) {
debugD_P(PSTR("Updating data storage, triggered by meter clock %02d:%02d:%02d UTC (Internal clock: %02d:%02d:%02d)"), mtm.Hour, mtm.Minute, mtm.Second, tm.Hour, tm.Minute, tm.Second);
saveData = ds.update(data, meterTime);
}
if(!ds.isHappy(dataUpdateTime) && dataUpdateTime > FirmwareVersion::BuildEpoch) { // Must use "isHappy()" in case day state gets reset and lastTimestamp is "now"
debugD_P(PSTR("READY to update (internal clock %02d:%02d:%02d UTC, meter clock: %02d:%02d:%02d, list type %d, est: %d, using clock: %d)"), tm.Hour, tm.Minute, tm.Second, mtm.Hour, mtm.Minute, mtm.Second, data->getListType(), wasCounterEstimated, dataUpdateTime == now);
tmElements_t dtm;
breakTime(dataUpdateTime, dtm);
if(dtm.Minute < 2 && data->getListType() >= 3) {
debugD_P(PSTR("Updating data storage using actual data"));
saveData = ds.update(data, dataUpdateTime);
#if defined(_CLOUDCONNECTOR_H)
if(saveData && cloud != NULL) cloud->forceUpdate();
#endif
} else if(tm.Minute == 1) {
} else if(dtm.Minute == 2) {
debugW_P(PSTR("Did not receive necessary data for previous hour, clearing"));
AmsData nullData;
saveData = ds.update(&nullData, now);
saveData = ds.update(&nullData, dataUpdateTime);
}
if(saveData) {
debugI_P(PSTR("Saving data"));
@ -1396,6 +1398,8 @@ void handleDataSuccess(AmsData* data) {
debugW_P(PSTR("Unable to save data storage"));
}
}
} else {
debugD_P(PSTR("NOT Ready to update (internal clock %02d:%02d:%02d UTC, meter clock: %02d:%02d:%02d, list type %d, est: %d)"), tm.Hour, tm.Minute, tm.Second, mtm.Hour, mtm.Minute, mtm.Second, data->getListType(), wasCounterEstimated);
}
if(ea.update(data)) {
@ -1666,6 +1670,7 @@ void configFileParse() {
meter.baud = String(buf+10).toInt();
} else if(strncmp_P(buf, PSTR("meterParity "), 12) == 0) {
if(!lMeter) { config.getMeterConfig(meter); lMeter = true; };
meter.parity = 0;
if(strncmp_P(buf+12, PSTR("7N1"), 3) == 0) meter.parity = 2;
if(strncmp_P(buf+12, PSTR("8N1"), 3) == 0) meter.parity = 3;
if(strncmp_P(buf+12, PSTR("8N2"), 3) == 0) meter.parity = 7;