mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-02-10 18:31:01 +00:00
Merge branch 'gskjold:master' into homeassistant
This commit is contained in:
@@ -555,22 +555,6 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 88:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig88()) {
|
||||
configVersion = 89;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 89:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig89()) {
|
||||
configVersion = 90;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 90:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig90()) {
|
||||
@@ -579,6 +563,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 91:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig91()) {
|
||||
configVersion = 92;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -785,60 +777,6 @@ bool AmsConfiguration::relocateConfig87() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig88() {
|
||||
GpioConfig88 gpio88;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_GPIO_START_88, gpio88);
|
||||
|
||||
GpioConfig gpio {
|
||||
gpio88.hanPin,
|
||||
gpio88.apPin,
|
||||
gpio88.ledPin,
|
||||
gpio88.ledInverted,
|
||||
gpio88.ledPinRed,
|
||||
gpio88.ledPinGreen,
|
||||
gpio88.ledPinBlue,
|
||||
gpio88.ledRgbInverted,
|
||||
gpio88.tempSensorPin,
|
||||
gpio88.tempAnalogSensorPin,
|
||||
gpio88.vccPin,
|
||||
gpio88.vccOffset,
|
||||
gpio88.vccMultiplier,
|
||||
gpio88.vccBootLimit,
|
||||
0,
|
||||
0
|
||||
};
|
||||
EEPROM.put(CONFIG_GPIO_START, gpio);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 89);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig89() {
|
||||
EntsoeConfig89 entose89;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_ENTSOE_START_89, entose89);
|
||||
|
||||
uint32_t multiplier = entose89.multiplier;
|
||||
|
||||
EntsoeConfig entsoe = {
|
||||
0x0,
|
||||
0x0,
|
||||
0x0,
|
||||
multiplier
|
||||
};
|
||||
strcpy(entsoe.token, entose89.token);
|
||||
strcpy(entsoe.area, entose89.area);
|
||||
strcpy(entsoe.currency, entose89.currency);
|
||||
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 90);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig90() {
|
||||
EntsoeConfig entsoe;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
@@ -850,6 +788,27 @@ bool AmsConfiguration::relocateConfig90() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig91() {
|
||||
WiFiConfig91 wifi91;
|
||||
WiFiConfig wifi;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_WIFI_START_91, wifi91);
|
||||
strcpy(wifi.ssid, wifi91.ssid);
|
||||
strcpy(wifi.psk, wifi91.psk);
|
||||
strcpy(wifi.ip, wifi91.ip);
|
||||
strcpy(wifi.gateway, wifi91.gateway);
|
||||
strcpy(wifi.subnet, wifi91.subnet);
|
||||
strcpy(wifi.dns1, wifi91.dns1);
|
||||
strcpy(wifi.dns2, wifi91.dns2);
|
||||
strcpy(wifi.hostname, wifi91.hostname);
|
||||
wifi.mdns = wifi91.mdns;
|
||||
EEPROM.put(CONFIG_WIFI_START, wifi);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 92);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 91 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 92 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
#define CONFIG_SYSTEM_START 8
|
||||
#define CONFIG_WIFI_START 16
|
||||
#define CONFIG_METER_START 224
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_ENTSOE_START 290
|
||||
#define CONFIG_WIFI_START 360
|
||||
#define CONFIG_WEB_START 648
|
||||
#define CONFIG_DEBUG_START 824
|
||||
#define CONFIG_DOMOTICZ_START 856
|
||||
@@ -21,16 +21,15 @@
|
||||
|
||||
#define CONFIG_MQTT_START_86 224
|
||||
#define CONFIG_METER_START_87 784
|
||||
#define CONFIG_GPIO_START_88 832
|
||||
#define CONFIG_ENTSOE_START_89 944
|
||||
#define CONFIG_ENTSOE_START_90 286
|
||||
#define CONFIG_WIFI_START_91 16
|
||||
|
||||
|
||||
struct SystemConfig {
|
||||
uint8_t boardType;
|
||||
}; // 1
|
||||
|
||||
struct WiFiConfig {
|
||||
struct WiFiConfig91 {
|
||||
char ssid[32];
|
||||
char psk[64];
|
||||
char ip[15];
|
||||
@@ -42,6 +41,18 @@ struct WiFiConfig {
|
||||
bool mdns;
|
||||
}; // 204
|
||||
|
||||
struct WiFiConfig {
|
||||
char ssid[32];
|
||||
char psk[64];
|
||||
char ip[16];
|
||||
char gateway[16];
|
||||
char subnet[16];
|
||||
char dns1[16];
|
||||
char dns2[16];
|
||||
char hostname[32];
|
||||
bool mdns;
|
||||
}; // 209
|
||||
|
||||
struct MqttConfig86 {
|
||||
char host[128];
|
||||
uint16_t port;
|
||||
@@ -118,23 +129,6 @@ struct GpioConfig {
|
||||
uint16_t vccResistorVcc;
|
||||
}; // 20
|
||||
|
||||
struct GpioConfig88 {
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
bool ledInverted;
|
||||
uint8_t ledPinRed;
|
||||
uint8_t ledPinGreen;
|
||||
uint8_t ledPinBlue;
|
||||
bool ledRgbInverted;
|
||||
uint8_t tempSensorPin;
|
||||
uint8_t tempAnalogSensorPin;
|
||||
uint8_t vccPin;
|
||||
int16_t vccOffset;
|
||||
uint16_t vccMultiplier;
|
||||
uint8_t vccBootLimit;
|
||||
}; // 16
|
||||
|
||||
struct DomoticzConfig {
|
||||
uint16_t elidx;
|
||||
uint16_t vl1idx;
|
||||
@@ -151,13 +145,6 @@ struct NtpConfig {
|
||||
char server[64];
|
||||
}; // 70
|
||||
|
||||
struct EntsoeConfig89 {
|
||||
char token[37];
|
||||
char area[17];
|
||||
char currency[4];
|
||||
uint16_t multiplier;
|
||||
}; // 60
|
||||
|
||||
struct EntsoeConfig {
|
||||
char token[37];
|
||||
char area[17];
|
||||
@@ -324,9 +311,8 @@ private:
|
||||
bool loadConfig83(int address);
|
||||
bool relocateConfig86();
|
||||
bool relocateConfig87();
|
||||
bool relocateConfig88(); // dev 1.6
|
||||
bool relocateConfig89(); // dev 1.6
|
||||
bool relocateConfig90(); // 2.0.0
|
||||
bool relocateConfig91(); // 2.0.2
|
||||
|
||||
int readString(int pAddress, char* pString[]);
|
||||
int readInt(int pAddress, int *pValue);
|
||||
|
||||
@@ -16,7 +16,7 @@ void AmsDataStorage::setTimezone(Timezone* tz) {
|
||||
|
||||
bool AmsDataStorage::update(AmsData* data) {
|
||||
time_t now = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Time is: %d\n", now);
|
||||
}
|
||||
if(now < EPOCH_2021_01_01) {
|
||||
@@ -32,8 +32,14 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if(now < EPOCH_2021_01_01) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Invalid time: %d\n", now);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if(now-day.lastMeterReadTime < 3595) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) It is only %d seconds since last update, ignoring\n", (now-day.lastMeterReadTime));
|
||||
}
|
||||
return false;
|
||||
@@ -41,43 +47,49 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
|
||||
tmElements_t tm, last;
|
||||
breakTime(now, tm);
|
||||
if(now > EPOCH_2021_01_01) {
|
||||
tmElements_t last;
|
||||
|
||||
if(day.lastMeterReadTime > EPOCH_2021_01_01) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last day update: %d\n", day.lastMeterReadTime);
|
||||
}
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
for(int i = last.Hour; i < tm.Hour; i++) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Clearing hour: %d\n", i);
|
||||
}
|
||||
setHour(i, 0);
|
||||
if(day.lastMeterReadTime > EPOCH_2021_01_01) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last day update: %d\n", day.lastMeterReadTime);
|
||||
}
|
||||
breakTime(day.lastMeterReadTime, last);
|
||||
for(int i = last.Hour; i < tm.Hour; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Clearing hour: %d\n", i);
|
||||
}
|
||||
setHour(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(month.lastMeterReadTime > EPOCH_2021_01_01) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last month update: %d\n", month.lastMeterReadTime);
|
||||
}
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
} else {
|
||||
breakTime(now, tm);
|
||||
breakTime(month.lastMeterReadTime, last);
|
||||
}
|
||||
|
||||
if(month.lastMeterReadTime > EPOCH_2021_01_01) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last month update: %d\n", month.lastMeterReadTime);
|
||||
}
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
} else {
|
||||
breakTime(now, tm);
|
||||
breakTime(month.lastMeterReadTime, last);
|
||||
}
|
||||
|
||||
for(int i = last.Day; i < tm.Day; i++) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Clearing day: %d\n", i);
|
||||
}
|
||||
setDay(i, 0);
|
||||
for(int i = last.Day; i < tm.Day; i++) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) {
|
||||
debugger->printf("(AmsDataStorage) Clearing day: %d\n", i);
|
||||
}
|
||||
setDay(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if(day.lastMeterReadTime > now) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf("(AmsDataStorage) Invalid future timestamp for day plot, resetting\n");
|
||||
}
|
||||
day.activeImport = data->getActiveImportCounter() * 1000;
|
||||
day.activeExport = data->getActiveExportCounter() * 1000;
|
||||
day.lastMeterReadTime = now;
|
||||
}
|
||||
|
||||
if(data->getListType() != 3) return false;
|
||||
else if(tm.Minute > 5) return false;
|
||||
|
||||
@@ -144,8 +156,17 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
} else {
|
||||
breakTime(now, tm);
|
||||
}
|
||||
if(tm.Hour == 0 && now-month.lastMeterReadTime > 86300) {
|
||||
Serial.printf("\n%d %d %d %d\n", month.version, month.lastMeterReadTime, month.activeImport, month.activeExport);
|
||||
|
||||
if(month.lastMeterReadTime > now) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf("(AmsDataStorage) Invalid future timestamp for month plot, resetting\n");
|
||||
}
|
||||
month.activeImport = data->getActiveImportCounter() * 1000;
|
||||
month.activeExport = data->getActiveExportCounter() * 1000;
|
||||
month.lastMeterReadTime = now;
|
||||
}
|
||||
|
||||
if(tm.Hour == 0 && now - month.lastMeterReadTime > 86300) {
|
||||
if(month.activeImport == 0 || now - month.lastMeterReadTime > 2678400) {
|
||||
month.activeImport = data->getActiveImportCounter() * 1000;
|
||||
month.activeExport = data->getActiveExportCounter() * 1000;
|
||||
@@ -153,7 +174,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) {
|
||||
debugger->printf("(AmsDataStorage) Too long since last month update, clearing data\n");
|
||||
}
|
||||
for(int i = 0; i<31; i++) {
|
||||
for(int i = 1; i<=31; i++) {
|
||||
setDay(i, 0);
|
||||
}
|
||||
} else if(now - month.lastMeterReadTime < 87000) {
|
||||
@@ -181,12 +202,16 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
debugger->printf("(AmsDataStorage) Since last month update, hours: %.1f, import: %d (%.2f/hr), export: %d (%.2f/hr)\n", hrs, im, iph, ex, eph);
|
||||
}
|
||||
|
||||
// Make sure last month read is at midnight
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), tm);
|
||||
} else {
|
||||
breakTime(month.lastMeterReadTime, tm);
|
||||
}
|
||||
month.lastMeterReadTime = month.lastMeterReadTime - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lu", month.lastMeterReadTime);
|
||||
}
|
||||
|
||||
if(tz != NULL) {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
@@ -213,7 +238,7 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
|
||||
month.activeImport += iph * hours;
|
||||
month.activeExport += eph * hours;
|
||||
month.lastMeterReadTime += cur;
|
||||
month.lastMeterReadTime = cur;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,22 +246,22 @@ bool AmsDataStorage::update(AmsData* data) {
|
||||
}
|
||||
|
||||
void AmsDataStorage::setHour(uint8_t hour, int32_t val) {
|
||||
if(hour < 0) return;
|
||||
if(hour < 0 || hour > 24) return;
|
||||
day.points[hour] = val / 10;
|
||||
}
|
||||
|
||||
int16_t AmsDataStorage::getHour(uint8_t hour) {
|
||||
if(hour < 0) return 0;
|
||||
if(hour < 0 || hour > 24) return 0;
|
||||
return day.points[hour] * 10;
|
||||
}
|
||||
|
||||
void AmsDataStorage::setDay(uint8_t day, int32_t val) {
|
||||
if(day < 1) return;
|
||||
if(day < 1 || day > 31) return;
|
||||
month.points[day-1] = val / 10;
|
||||
}
|
||||
|
||||
int32_t AmsDataStorage::getDay(uint8_t day) {
|
||||
if(day < 1) return 0;
|
||||
if(day < 1 || day > 31) return 0;
|
||||
return (month.points[day-1] * 10);
|
||||
}
|
||||
|
||||
@@ -273,7 +298,7 @@ bool AmsDataStorage::load() {
|
||||
|
||||
if(month->version == 4) {
|
||||
memcpy(&this->month, month, sizeof(this->month));
|
||||
ret = true;
|
||||
ret = ret && true;
|
||||
} else {
|
||||
ret = false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef _AMSTOMQTTBRIDGE_H
|
||||
#define _AMSTOMQTTBRIDGE_H
|
||||
|
||||
#define WIFI_CONNECTION_TIMEOUT 25000;
|
||||
#define WIFI_CONNECTION_TIMEOUT 60000;
|
||||
|
||||
#define INVALID_BUTTON_PIN 0xFFFFFFFF
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
ADC_MODE(ADC_VCC);
|
||||
#else if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
#define WDT_TIMEOUT 10
|
||||
|
||||
#include "AmsToMqttBridge.h"
|
||||
#include "AmsStorage.h"
|
||||
@@ -40,6 +43,7 @@ ADC_MODE(ADC_VCC);
|
||||
|
||||
#define BUF_SIZE (1024)
|
||||
#include "ams/hdlc.h"
|
||||
#include "MbusAssembler.h"
|
||||
|
||||
#include "IEC6205621.h"
|
||||
#include "IEC6205675.h"
|
||||
@@ -286,7 +290,7 @@ void setup() {
|
||||
|
||||
NtpConfig ntp;
|
||||
if(config.getNtpConfig(ntp)) {
|
||||
configTime(ntp.offset*10, ntp.summerOffset*10, ntp.enable ? ntp.server : "");
|
||||
configTime(ntp.offset*10, ntp.summerOffset*10, ntp.enable ? strlen(ntp.server) > 0 ? ntp.server : "pool.ntp.org" : ""); // Add NTP server by default if none is configured
|
||||
sntp_servermode_dhcp(ntp.enable && ntp.dhcp ? 1 : 0);
|
||||
ntpEnabled = ntp.enable;
|
||||
TimeChangeRule std = {"STD", Last, Sun, Oct, 3, ntp.offset / 6};
|
||||
@@ -305,6 +309,13 @@ void setup() {
|
||||
}
|
||||
|
||||
ws.setup(&config, &gpioConfig, &meterConfig, &meterState, &ds);
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_init(WDT_TIMEOUT, true);
|
||||
esp_task_wdt_add(NULL);
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtEnable(WDT_TIMEOUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
int buttonTimer = 0;
|
||||
@@ -349,7 +360,7 @@ void loop() {
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
wifiConnected = false;
|
||||
Debug.stop();
|
||||
//WiFi_connect();
|
||||
WiFi_connect();
|
||||
} else {
|
||||
wifiReconnectCount = 0;
|
||||
if(!wifiConnected) {
|
||||
@@ -485,6 +496,11 @@ void loop() {
|
||||
}
|
||||
}
|
||||
delay(1); // Needed for auto modem sleep
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
}
|
||||
|
||||
void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert) {
|
||||
@@ -660,6 +676,7 @@ void swapWifiMode() {
|
||||
|
||||
int len = 0;
|
||||
uint8_t buf[BUF_SIZE];
|
||||
MbusAssembler* ma = NULL;
|
||||
int currentMeterType = -1;
|
||||
bool readHanPort() {
|
||||
if(!hanSerial->available()) return false;
|
||||
@@ -679,8 +696,10 @@ bool readHanPort() {
|
||||
CosemDateTime timestamp = {0};
|
||||
AmsData data;
|
||||
if(currentMeterType == 1) {
|
||||
while(hanSerial->available()) {
|
||||
int pos = HDLC_FRAME_INCOMPLETE;
|
||||
while(hanSerial->available() && pos == HDLC_FRAME_INCOMPLETE) {
|
||||
buf[len++] = hanSerial->read();
|
||||
pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp);
|
||||
delay(1);
|
||||
}
|
||||
if(len > 0) {
|
||||
@@ -690,7 +709,32 @@ bool readHanPort() {
|
||||
debugI("Buffer overflow, resetting");
|
||||
return false;
|
||||
}
|
||||
int pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp);
|
||||
if(pos == MBUS_FRAME_INTERMEDIATE_SEGMENT) {
|
||||
debugI("Intermediate segment");
|
||||
if(ma == NULL) {
|
||||
ma = new MbusAssembler();
|
||||
}
|
||||
if(ma->append((uint8_t *) buf, len) < 0)
|
||||
pos = -77;
|
||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
debugD("Frame dump (%db):", len);
|
||||
debugPrint(buf, 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(buf, 0, len);
|
||||
}
|
||||
if(ma->append((uint8_t *) buf, len) >= 0) {
|
||||
len = ma->write((uint8_t *) buf);
|
||||
pos = HDLC_validate((uint8_t *) buf, len, hc, ×tamp);
|
||||
} else {
|
||||
pos = -77;
|
||||
}
|
||||
}
|
||||
if(pos == HDLC_FRAME_INCOMPLETE) {
|
||||
return false;
|
||||
}
|
||||
@@ -702,28 +746,70 @@ bool readHanPort() {
|
||||
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
||||
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
||||
}
|
||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
if(Debug.isActive(RemoteDebug::VERBOSE)) {
|
||||
debugD("Frame dump (%db):", len);
|
||||
debugPrint(buf, 0, len);
|
||||
if(hc != NULL) {
|
||||
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(hc != NULL && Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
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);
|
||||
}
|
||||
len = 0;
|
||||
while(hanSerial->available()) hanSerial->read();
|
||||
if(pos > 0) {
|
||||
while(hanSerial->available()) hanSerial->read();
|
||||
debugI("Valid HDLC, start at %d", pos);
|
||||
data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType(), timestamp);
|
||||
debugI("Valid data, start at byte %d", pos);
|
||||
data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType(), meterConfig.distributionSystem, timestamp, hc);
|
||||
} else {
|
||||
debugW("Invalid HDLC, returned with %d", pos);
|
||||
currentMeterType = 0;
|
||||
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", buf[0]);
|
||||
currentMeterType = 0;
|
||||
break;
|
||||
default:
|
||||
debugW("Unspecified error while reading data: %d", pos);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -762,7 +848,7 @@ bool readHanPort() {
|
||||
}
|
||||
|
||||
time_t now = time(nullptr);
|
||||
if(now < EPOCH_2021_01_01 && data.getListType() == 3 && !ntpEnabled) {
|
||||
if(now < EPOCH_2021_01_01 && data.getListType() == 3) {
|
||||
if(data.getMeterTimestamp() > EPOCH_2021_01_01) {
|
||||
debugI("Using timestamp from meter");
|
||||
now = data.getMeterTimestamp();
|
||||
@@ -872,8 +958,16 @@ void WiFi_connect() {
|
||||
ip.fromString(wifi.ip);
|
||||
gw.fromString(wifi.gateway);
|
||||
sn.fromString(wifi.subnet);
|
||||
dns1.fromString(wifi.dns1);
|
||||
dns2.fromString(wifi.dns2);
|
||||
if(strlen(wifi.dns1) > 0) {
|
||||
dns1.fromString(wifi.dns1);
|
||||
} else if(strlen(wifi.gateway) > 0) {
|
||||
dns1.fromString(wifi.gateway); // If no DNS, set gateway by default
|
||||
}
|
||||
if(strlen(wifi.dns2) > 0) {
|
||||
dns2.fromString(wifi.dns2);
|
||||
} else if(dns1.toString().isEmpty()) {
|
||||
dns2.fromString("208.67.220.220"); // Add OpenDNS as second by default if nothing is configured
|
||||
}
|
||||
WiFi.config(ip, gw, sn, dns1, dns2);
|
||||
} else {
|
||||
#if defined(ESP32)
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
#include "lwip/def.h"
|
||||
#include "Timezone.h"
|
||||
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packageTimestamp) {
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, uint8_t distributionSystem, CosemDateTime packageTimestamp, HDLCConfig* hc) {
|
||||
uint32_t ui;
|
||||
double val;
|
||||
char str[64];
|
||||
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
this->packageTimestamp = getTimestamp(packageTimestamp);
|
||||
|
||||
ui = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
|
||||
@@ -15,6 +19,8 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
|
||||
// Kaifa special case...
|
||||
if(data->base.type == CosemTypeOctetString) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
String listId = String(str);
|
||||
@@ -91,13 +97,19 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
l1voltage = ntohl(data->dlu.data) / 10.0;
|
||||
}
|
||||
|
||||
if(listType >= 2 && memcmp(meterModel.c_str(), "MA304T3", 7) == 0) {
|
||||
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
|
||||
l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60 * (PI/180)), 2) + pow(l3voltage * sin(60 * (PI/180)),2));
|
||||
}
|
||||
|
||||
if(listType == 3) {
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
switch(data->base.type) {
|
||||
case CosemTypeOctetString: {
|
||||
if(data->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* ts = (AmsOctetTimestamp*) data;
|
||||
meterTimestamp = getTimestamp(ts->dt);
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) data;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
meterTimestamp = tz.toUTC(ts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,6 +128,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
lastUpdateMillis = millis();
|
||||
}
|
||||
} else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
listType = 1;
|
||||
meterType = AmsTypeKaifa;
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
@@ -140,13 +153,15 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
if(memcmp(version->str.data, "Kamstrup", 8) == 0) {
|
||||
meterType = AmsTypeKamstrup;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Try system title
|
||||
if(meterType == AmsTypeUnknown && hc != NULL) {
|
||||
if(memcmp(hc->system_title, "SAGY", 4)) {
|
||||
meterType = AmsTypeSagemcom;
|
||||
}
|
||||
}
|
||||
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
Timezone tz(CEST, CET);
|
||||
|
||||
if(meterType == AmsTypeKamstrup || meterType == AmsTypeAidon) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
}
|
||||
@@ -249,7 +264,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
if(meterType == AmsTypeKamstrup || meterType == AmsTypeAidon) {
|
||||
this->meterTimestamp = tz.toUTC(ts);
|
||||
meterTimestamp = tz.toUTC(ts);
|
||||
} else {
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
@@ -273,17 +288,47 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
}
|
||||
|
||||
if(meterType == AmsTypeKamstrup) {
|
||||
activeImportCounter *= 10;
|
||||
activeExportCounter *= 10;
|
||||
reactiveImportCounter *= 10;
|
||||
reactiveExportCounter *= 10;
|
||||
l1current /= 100;
|
||||
l2current /= 100;
|
||||
l3current /= 100;
|
||||
powerFactor /= 100;
|
||||
l1PowerFactor /= 100;
|
||||
l2PowerFactor /= 100;
|
||||
l3PowerFactor /= 100;
|
||||
if(listType >= 3) {
|
||||
activeImportCounter *= 10;
|
||||
activeExportCounter *= 10;
|
||||
reactiveImportCounter *= 10;
|
||||
reactiveExportCounter *= 10;
|
||||
}
|
||||
if(l1current != 0)
|
||||
l1current /= 100;
|
||||
if(l2current != 0)
|
||||
l2current /= 100;
|
||||
if(l3current != 0)
|
||||
l3current /= 100;
|
||||
if(powerFactor != 0)
|
||||
powerFactor /= 100;
|
||||
if(l1PowerFactor != 0)
|
||||
l1PowerFactor /= 100;
|
||||
if(l2PowerFactor != 0)
|
||||
l2PowerFactor /= 100;
|
||||
if(l3PowerFactor != 0)
|
||||
l3PowerFactor /= 100;
|
||||
} else if(meterType == AmsTypeSagemcom) {
|
||||
CosemData* meterTs = getCosemDataAt(1, ((char *) (d)));
|
||||
if(meterTs != NULL) {
|
||||
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
|
||||
time_t ts = getTimestamp(amst->dt);
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
|
||||
CosemData* mid = getCosemDataAt(58, ((char *) (d))); // TODO: Get last item
|
||||
if(mid != NULL) {
|
||||
switch(mid->base.type) {
|
||||
case CosemTypeString:
|
||||
memcpy(&meterId, mid->str.data, mid->str.length);
|
||||
meterId[mid->str.length] = 0;
|
||||
break;
|
||||
case CosemTypeOctetString:
|
||||
memcpy(&meterId, mid->oct.data, mid->oct.length);
|
||||
meterId[mid->oct.length] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
@@ -292,9 +337,18 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, CosemDateTime packag
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
|
||||
|
||||
if(threePhase) {
|
||||
if(l2current == 0 && l1current > 0 && l3current > 0) {
|
||||
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
|
||||
// Special case for Norwegian IT/TT meters that does not report all values
|
||||
if(distributionSystem == 1) {
|
||||
if(threePhase) {
|
||||
if(l2current == 0.0 && l1current > 0.0 && l3current > 0.0) {
|
||||
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
|
||||
if(activeExportPower == 0) {
|
||||
l2current = max((float) 0.0, l2current);
|
||||
}
|
||||
}
|
||||
} else if(twoPhase && l1current > 0.0 && l2current > 0.0 && l3current > 0.0) {
|
||||
l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60.0 * (PI/180.0)), 2) + pow(l3voltage * sin(60.0 * (PI/180.0)),2));
|
||||
threePhase = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,9 +470,11 @@ double IEC6205675::getNumber(CosemData* item) {
|
||||
pos += 3;
|
||||
break;
|
||||
}
|
||||
if(*pos++ == 0x02 && *pos++ == 0x02) {
|
||||
int8_t scale = *++pos;
|
||||
val *= pow(10, scale);
|
||||
if(pos != NULL) {
|
||||
if(*pos++ == 0x02 && *pos++ == 0x02) {
|
||||
int8_t scale = *++pos;
|
||||
val *= pow(10, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
return val;
|
||||
|
||||
@@ -11,7 +11,7 @@ struct AmsOctetTimestamp {
|
||||
|
||||
class IEC6205675 : public AmsData {
|
||||
public:
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, CosemDateTime packageTimestamp);
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, uint8_t distributionSystem, CosemDateTime packageTimestamp, HDLCConfig* hc);
|
||||
|
||||
private:
|
||||
CosemData* getCosemDataAt(uint8_t index, const char* ptr);
|
||||
|
||||
57
src/MbusAssembler.cpp
Normal file
57
src/MbusAssembler.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#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;
|
||||
}
|
||||
18
src/MbusAssembler.h
Normal file
18
src/MbusAssembler.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#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
|
||||
167
src/ams/hdlc.cpp
167
src/ams/hdlc.cpp
@@ -16,73 +16,125 @@ void mbus_hexdump(const uint8_t* buf, int len) {
|
||||
}
|
||||
|
||||
int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTime* timestamp) {
|
||||
if(length < 10)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
int len;
|
||||
int headersize = 3;
|
||||
int footersize = 1;
|
||||
HDLCHeader* h = (HDLCHeader*) d;
|
||||
uint8_t* ptr = (uint8_t*) &h[1];
|
||||
// Frame format type 3
|
||||
if(h->flag == HDLC_FLAG && (h->format & 0xF0) == 0xA0) {
|
||||
// Length field (11 lsb of format)
|
||||
len = (ntohs(h->format) & 0x7FF) + 2;
|
||||
if(len > length)
|
||||
|
||||
uint8_t flag = *d;
|
||||
|
||||
uint8_t* ptr;
|
||||
if(flag == HDLC_FLAG) {
|
||||
if(length < 3)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f);
|
||||
footersize = sizeof *f;
|
||||
HDLCHeader* h = (HDLCHeader*) d;
|
||||
ptr = (uint8_t*) &h[1];
|
||||
|
||||
// First and last byte should be MBUS_HAN_TAG
|
||||
if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
|
||||
return HDLC_BOUNDRY_FLAG_MISSING;
|
||||
// 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;
|
||||
|
||||
// Verify FCS
|
||||
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
|
||||
return HDLC_FCS_ERROR;
|
||||
HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f);
|
||||
footersize = sizeof *f;
|
||||
|
||||
// Skip destination address, LSB marks last byte
|
||||
while(((*ptr) & 0x01) == 0x00) {
|
||||
ptr++;
|
||||
// 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++;
|
||||
}
|
||||
headersize++;
|
||||
ptr++;
|
||||
|
||||
// Skip source address, LSB marks last byte
|
||||
while(((*ptr) & 0x01) == 0x00) {
|
||||
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;
|
||||
}
|
||||
headersize++;
|
||||
ptr++;
|
||||
} else if(flag == MBUS_START) {
|
||||
// https://m-bus.com/documentation-wired/06-application-layer
|
||||
if(length < 4)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
|
||||
headersize += 3;
|
||||
MbusHeader* mh = (MbusHeader*) d;
|
||||
if(mh->flag1 != MBUS_START || mh->flag2 != MBUS_START)
|
||||
return MBUS_BOUNDRY_FLAG_MISSING;
|
||||
|
||||
// Verify HCS
|
||||
if(ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
|
||||
return HDLC_HCS_ERROR;
|
||||
// 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;
|
||||
|
||||
ptr += sizeof *t3;
|
||||
} else if(h->flag == MBUS_START) {
|
||||
// TODO: Check that the two next bytes are identical
|
||||
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;
|
||||
|
||||
// Ignore: Control field + Address + Flag
|
||||
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 += 3;
|
||||
footersize++;
|
||||
headersize += 5; // And also control and address that we didn't skip earlier, needed these for checksum.
|
||||
} else {
|
||||
return HDLC_UNKNOWN_DATA;
|
||||
}
|
||||
|
||||
// Extract LLC
|
||||
HDLCLLC* llc = (HDLCLLC*) ptr;
|
||||
ptr += sizeof *llc;
|
||||
headersize += 3;
|
||||
|
||||
if(((*ptr) & 0xFF) == 0x0F) {
|
||||
// Unencrypted APDU
|
||||
int i = 0;
|
||||
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
||||
ptr += sizeof *adpu;
|
||||
|
||||
@@ -90,7 +142,7 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
||||
CosemData* dateTime = (CosemData*) ptr;
|
||||
if(dateTime->base.type == CosemTypeOctetString) {
|
||||
if(dateTime->base.length == 0x0C) {
|
||||
memcpy(timestamp, ptr+1, dateTime->base.length);
|
||||
memcpy(timestamp, ptr+1, dateTime->base.length+1);
|
||||
}
|
||||
ptr += 2 + dateTime->base.length;
|
||||
} else if(dateTime->base.type == CosemTypeNull) {
|
||||
@@ -99,10 +151,10 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
||||
} else if(dateTime->base.type == CosemTypeDateTime) {
|
||||
memcpy(timestamp, ptr, dateTime->base.length);
|
||||
} else if(dateTime->base.type == 0x0C) { // Kamstrup bug...
|
||||
memcpy(timestamp, ptr, 0x0C);
|
||||
memcpy(timestamp, ptr, 13);
|
||||
ptr += 13;
|
||||
} else {
|
||||
return -99;
|
||||
return HDLC_TIMESTAMP_UNKNOWN;
|
||||
}
|
||||
|
||||
return ptr-d;
|
||||
@@ -132,20 +184,17 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
||||
} else if(((*ptr) & 0xFF) == 0x82) {
|
||||
HDLCHeader* h = (HDLCHeader*) ptr;
|
||||
|
||||
// Length field
|
||||
// 2-byte payload length
|
||||
len = (ntohs(h->format) & 0xFFFF);
|
||||
|
||||
ptr += 3;
|
||||
headersize += 3;
|
||||
}
|
||||
//len = ceil(len/16.0) * 16; // Technically GCM is 128bit blocks. This works for Austrian meters, but not Danish...
|
||||
if(len + headersize + footersize > length)
|
||||
return HDLC_FRAME_INCOMPLETE;
|
||||
|
||||
//Serial.printf("\nL: %d : %d, %d : %d\n", length, len, headersize, footersize);
|
||||
|
||||
// TODO: FCS
|
||||
|
||||
memcpy(config->additional_authenticated_data, ptr, 1);
|
||||
|
||||
// Security tag
|
||||
@@ -203,7 +252,8 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
||||
mbedtls_gcm_free(&m_ctx);
|
||||
#endif
|
||||
|
||||
ptr += 5; // TODO: Come to this number in a proper way...
|
||||
HDLCADPU* adpu = (HDLCADPU*) (ptr);
|
||||
ptr += sizeof *adpu;
|
||||
|
||||
// ADPU timestamp
|
||||
CosemData* dateTime = (CosemData*) ptr;
|
||||
@@ -221,7 +271,7 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
||||
memcpy(timestamp, ptr, 0x0C);
|
||||
ptr += 13;
|
||||
} else {
|
||||
return -99;
|
||||
return HDLC_TIMESTAMP_UNKNOWN;
|
||||
}
|
||||
|
||||
return ptr-d;
|
||||
@@ -230,3 +280,10 @@ int HDLC_validate(const uint8_t* d, int length, HDLCConfig* config, CosemDateTim
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,15 @@
|
||||
#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];
|
||||
@@ -53,6 +59,12 @@ typedef struct HDLCADPU {
|
||||
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;
|
||||
@@ -126,4 +138,6 @@ typedef union {
|
||||
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);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,10 +15,10 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState) {
|
||||
mqtt->publish(topic + "/meter/id", data->getMeterId(), true, 0);
|
||||
mqtt->publish(topic + "/meter/type", data->getMeterModel(), true, 0);
|
||||
mqtt->publish(topic + "/meter/clock", String(data->getMeterTimestamp()));
|
||||
mqtt->publish(topic + "/meter/import/reactive/accumulated", String(data->getReactiveImportCounter(), 2), true, 0);
|
||||
mqtt->publish(topic + "/meter/import/active/accumulated", String(data->getActiveImportCounter(), 2), true, 0);
|
||||
mqtt->publish(topic + "/meter/export/reactive/accumulated", String(data->getReactiveExportCounter(), 2), true, 0);
|
||||
mqtt->publish(topic + "/meter/export/active/accumulated", String(data->getActiveExportCounter(), 2), true, 0);
|
||||
mqtt->publish(topic + "/meter/import/reactive/accumulated", String(data->getReactiveImportCounter(), 3), true, 0);
|
||||
mqtt->publish(topic + "/meter/import/active/accumulated", String(data->getActiveImportCounter(), 3), true, 0);
|
||||
mqtt->publish(topic + "/meter/export/reactive/accumulated", String(data->getReactiveExportCounter(), 3), true, 0);
|
||||
mqtt->publish(topic + "/meter/export/active/accumulated", String(data->getActiveExportCounter(), 3), true, 0);
|
||||
if(full || meterState->getPowerFactor() != data->getPowerFactor()) {
|
||||
mqtt->publish(topic + "/meter/powerfactor", String(data->getPowerFactor(), 2));
|
||||
}
|
||||
|
||||
@@ -747,7 +747,7 @@ void AmsWebServer::dataJson() {
|
||||
if(eapi != NULL && strlen(eapi->getToken()) > 0)
|
||||
price = eapi->getValueForHour(0);
|
||||
|
||||
char json[340];
|
||||
char json[384];
|
||||
snprintf_P(json, sizeof(json), DATA_JSON,
|
||||
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
||||
meterConfig->productionCapacity,
|
||||
@@ -781,7 +781,8 @@ void AmsWebServer::dataJson() {
|
||||
mqttStatus,
|
||||
mqtt == NULL ? 0 : (int) mqtt->lastError(),
|
||||
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(),
|
||||
time(nullptr)
|
||||
time(nullptr),
|
||||
meterState->getMeterType()
|
||||
);
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
@@ -1581,27 +1582,29 @@ void AmsWebServer::firmwareDownload() {
|
||||
String version = server.arg("version");
|
||||
String versionStripped = version.substring(1);
|
||||
printI("Downloading firmware...");
|
||||
WiFiClientSecure client;
|
||||
#if defined(ESP8266)
|
||||
client.setBufferSizes(512, 512);
|
||||
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp8266-" + versionStripped + ".bin";
|
||||
#elif defined(ESP32)
|
||||
String url = "https://github.com/gskjold/AmsToMqttBridge/releases/download/" + version + "/ams2mqtt-esp32-" + versionStripped + ".bin";
|
||||
#endif
|
||||
client.setInsecure();
|
||||
HTTPClient https;
|
||||
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
HTTPClient httpClient;
|
||||
httpClient.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
httpClient.addHeader("User-Agent", "ams2mqtt/" + String(VERSION));
|
||||
|
||||
https.addHeader("Referer", "https://github.com/gskjold/AmsToMqttBridge/releases");
|
||||
if(https.begin(client, url)) {
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
String url = "http://0.0.0.0/releases/download/" + version + "/ams2mqtt-esp8266-" + versionStripped + ".bin";
|
||||
#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 = https.GET();
|
||||
int status = httpClient.GET();
|
||||
if(status == HTTP_CODE_OK) {
|
||||
printD("Received OK from server");
|
||||
if(LittleFS.begin()) {
|
||||
printI("Downloading firmware to LittleFS");
|
||||
file = LittleFS.open(FILE_FIRMWARE, "w");
|
||||
int len = https.writeToStream(&file);
|
||||
int len = httpClient.writeToStream(&file);
|
||||
file.close();
|
||||
LittleFS.end();
|
||||
performRestart = true;
|
||||
@@ -1614,25 +1617,18 @@ void AmsWebServer::firmwareDownload() {
|
||||
}
|
||||
} else {
|
||||
printE("Communication error: ");
|
||||
printE(https.errorToString(status));
|
||||
printE(httpClient.errorToString(status));
|
||||
printI(url);
|
||||
printD(https.getString());
|
||||
printD(httpClient.getString());
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
}
|
||||
} else {
|
||||
printE("Unable to configure HTTP client");
|
||||
|
||||
#if defined(ESP8266)
|
||||
char buf[64];
|
||||
client.getLastSSLError(buf,64);
|
||||
printE(buf);
|
||||
#endif
|
||||
|
||||
server.sendHeader("Location","/");
|
||||
server.send(303);
|
||||
}
|
||||
https.end();
|
||||
httpClient.end();
|
||||
client.stop();
|
||||
} else {
|
||||
printI("No firmware version specified...");
|
||||
|
||||
Reference in New Issue
Block a user