Compare commits

...

7 Commits

Author SHA1 Message Date
Gunnar Skjold
8d448533c7 Fixed config export for esp32 2022-12-19 18:36:04 +01:00
Gunnar Skjold
43cb9a0000 Fixed DSMR CRC check 2022-12-19 18:14:12 +01:00
Gunnar Skjold
8f057e687c Merge pull request #387 from lassebm/lg-e360-fixes
Landis+Gyr E360 fixes
2022-12-19 16:45:52 +01:00
Lasse Bang Mikkelsen
5b4f680114 Align Landis+Gyr naming 2022-12-17 12:38:18 +01:00
Lasse Bang Mikkelsen
fabdfbadf4 Add support for Landis+Gyr meters using "LGF" manufacturer ID 2022-12-16 20:20:40 +01:00
Lasse Bang Mikkelsen
afa47ea633 Use list type 4 when phase active import/export power is available 2022-12-16 20:19:15 +01:00
Gunnar Skjold
c40e20c8e9 Extract active import/export per phase from DSMR 2022-12-15 18:25:13 +01:00
11 changed files with 68 additions and 34 deletions

View File

@@ -10,7 +10,7 @@ enum AmsType {
AmsTypeKaifa = 0x02, AmsTypeKaifa = 0x02,
AmsTypeKamstrup = 0x03, AmsTypeKamstrup = 0x03,
AmsTypeIskra = 0x08, AmsTypeIskra = 0x08,
AmsTypeLandis = 0x09, AmsTypeLandisGyr = 0x09,
AmsTypeSagemcom = 0x0A, AmsTypeSagemcom = 0x0A,
AmsTypeLng = 0x0B, AmsTypeLng = 0x0B,
AmsTypeCustom = 0x88, AmsTypeCustom = 0x88,

View File

@@ -21,7 +21,7 @@ bool AmsDataStorage::update(AmsData* data) {
} }
time_t now = time(nullptr); time_t now = time(nullptr);
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Time is: %lld\n", (int64_t) now); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Time is: %lu\n", (int32_t) now);
if(tz == NULL) { if(tz == NULL) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Timezone is missing\n"); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Timezone is missing\n");
return false; return false;
@@ -30,18 +30,18 @@ bool AmsDataStorage::update(AmsData* data) {
if(data->getMeterTimestamp() > BUILD_EPOCH) { if(data->getMeterTimestamp() > BUILD_EPOCH) {
now = data->getMeterTimestamp(); now = data->getMeterTimestamp();
if(debugger->isActive(RemoteDebug::DEBUG)) { if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Using meter timestamp, which is: %lld\n", (int64_t) now); debugger->printf("(AmsDataStorage) Using meter timestamp, which is: %lu\n", (int32_t) now);
} }
} else if(data->getPackageTimestamp() > BUILD_EPOCH) { } else if(data->getPackageTimestamp() > BUILD_EPOCH) {
now = data->getPackageTimestamp(); now = data->getPackageTimestamp();
if(debugger->isActive(RemoteDebug::DEBUG)) { if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Using package timestamp, which is: %lld\n", (int64_t) now); debugger->printf("(AmsDataStorage) Using package timestamp, which is: %lu\n", (int32_t) now);
} }
} }
} }
if(now < BUILD_EPOCH) { if(now < BUILD_EPOCH) {
if(debugger->isActive(RemoteDebug::VERBOSE)) { if(debugger->isActive(RemoteDebug::VERBOSE)) {
debugger->printf("(AmsDataStorage) Invalid time: %lld\n", (int64_t) now); debugger->printf("(AmsDataStorage) Invalid time: %lu\n", (int32_t) now);
} }
return false; return false;
} }
@@ -63,7 +63,7 @@ bool AmsDataStorage::update(AmsData* data) {
return true; return true;
} else { } else {
if(debugger->isActive(RemoteDebug::DEBUG)) { if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Last day update: %lld\n", (int64_t) day.lastMeterReadTime); debugger->printf("(AmsDataStorage) Last day update: %lu\n", (int32_t) day.lastMeterReadTime);
} }
tmElements_t last; tmElements_t last;
breakTime(day.lastMeterReadTime, last); breakTime(day.lastMeterReadTime, last);
@@ -86,7 +86,7 @@ bool AmsDataStorage::update(AmsData* data) {
month.lastMeterReadTime = now; month.lastMeterReadTime = now;
} else { } else {
if(debugger->isActive(RemoteDebug::DEBUG)) { if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Last month update: %lld\n", (int64_t) month.lastMeterReadTime); debugger->printf("(AmsDataStorage) Last month update: %lu\n", (int32_t) month.lastMeterReadTime);
} }
tmElements_t last; tmElements_t last;
breakTime(tz->toLocal(month.lastMeterReadTime), last); breakTime(tz->toLocal(month.lastMeterReadTime), last);
@@ -156,7 +156,7 @@ bool AmsDataStorage::update(AmsData* data) {
setHourExport(last.Hour, exp); setHourExport(last.Hour, exp);
if(debugger->isActive(RemoteDebug::INFO)) { if(debugger->isActive(RemoteDebug::INFO)) {
debugger->printf("(AmsDataStorage) Estimated usage for hour %u: %.1f - %.1f (%lld)\n", last.Hour, imp, exp, (int64_t) cur); debugger->printf("(AmsDataStorage) Estimated usage for hour %u: %.1f - %.1f (%lu)\n", last.Hour, imp, exp, (int32_t) cur);
} }
day.activeImport += imp; day.activeImport += imp;
@@ -199,7 +199,7 @@ bool AmsDataStorage::update(AmsData* data) {
breakTime(tz->toLocal(month.lastMeterReadTime), last); breakTime(tz->toLocal(month.lastMeterReadTime), last);
month.lastMeterReadTime = month.lastMeterReadTime - (last.Hour * 3600) - (last.Minute * 60) - last.Second; month.lastMeterReadTime = month.lastMeterReadTime - (last.Hour * 3600) - (last.Minute * 60) - last.Second;
if(debugger->isActive(RemoteDebug::DEBUG)) { if(debugger->isActive(RemoteDebug::DEBUG)) {
debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lld\n", (int64_t) month.lastMeterReadTime); debugger->printf("(AmsDataStorage) Last month read after resetting to midnight: %lu\n", (int32_t) month.lastMeterReadTime);
} }
float hrs = (now - month.lastMeterReadTime) / 3600.0; float hrs = (now - month.lastMeterReadTime) / 3600.0;
@@ -224,7 +224,7 @@ bool AmsDataStorage::update(AmsData* data) {
setDayExport(last.Day, exp); setDayExport(last.Day, exp);
if(debugger->isActive(RemoteDebug::INFO)) { if(debugger->isActive(RemoteDebug::INFO)) {
debugger->printf("(AmsDataStorage) Estimated usage for day %u: %.1f - %.1f (%lld)\n", last.Day, imp, exp, (int64_t) cur); debugger->printf("(AmsDataStorage) Estimated usage for day %u: %.1f - %.1f (%lu)\n", last.Day, imp, exp, (int32_t) cur);
} }
month.activeImport += imp; month.activeImport += imp;
@@ -383,11 +383,11 @@ bool AmsDataStorage::isDayHappy() {
tmElements_t tm, last; tmElements_t tm, last;
if(now < day.lastMeterReadTime) { if(now < day.lastMeterReadTime) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld < %lld\n", (int64_t) now, (int64_t) day.lastMeterReadTime); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lu < %lu\n", (int32_t) now, (int32_t) day.lastMeterReadTime);
return false; return false;
} }
if(now-day.lastMeterReadTime > 3600) { if(now-day.lastMeterReadTime > 3600) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lld - %lld > 3600\n", (int64_t) now, (int64_t) day.lastMeterReadTime); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Day %lu - %lu > 3600\n", (int32_t) now, (int32_t) day.lastMeterReadTime);
return false; return false;
} }
breakTime(tz->toLocal(now), tm); breakTime(tz->toLocal(now), tm);
@@ -411,11 +411,11 @@ bool AmsDataStorage::isMonthHappy() {
tmElements_t tm, last; tmElements_t tm, last;
if(now < month.lastMeterReadTime) { if(now < month.lastMeterReadTime) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld < %lld\n", (int64_t) now, (int64_t) month.lastMeterReadTime); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lu < %lu\n", (int32_t) now, (int32_t) month.lastMeterReadTime);
return false; return false;
} }
if(now-month.lastMeterReadTime > 86400) { if(now-month.lastMeterReadTime > 86400) {
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lld - %lld > 3600\n", (int64_t) now, (int64_t) month.lastMeterReadTime); if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lu - %lu > 3600\n", (int32_t) now, (int32_t) month.lastMeterReadTime);
return false; return false;
} }
breakTime(tz->toLocal(now), tm); breakTime(tz->toLocal(now), tm);

View File

@@ -1391,7 +1391,7 @@ void MQTT_connect() {
#if defined(ESP8266) #if defined(ESP8266)
if(mqttSecureClient) { if(mqttSecureClient) {
time_t epoch = time(nullptr); time_t epoch = time(nullptr);
debugD("Setting NTP time %lld for secure MQTT connection", epoch); debugD("Setting NTP time %lu for secure MQTT connection", epoch);
mqttSecureClient->setX509Time(epoch); mqttSecureClient->setX509Time(epoch);
} }
#endif #endif

View File

@@ -42,7 +42,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
if(!init) { if(!init) {
currentHour = local.Hour; currentHour = local.Hour;
currentDay = local.Day; currentDay = local.Day;
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lld\n", (int64_t) now); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing data at %lu\n", (int32_t) now);
if(!load()) { if(!load()) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n"); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n");
data = { 4, local.Month, data = { 4, local.Month,
@@ -63,7 +63,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
} }
if(!initPrice && eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) { if(!initPrice && eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lld\n", (int64_t) now); if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EnergyAccounting) Initializing prices at %lu\n", (int32_t) now);
calcDayCost(); calcDayCost();
} }

View File

@@ -1,16 +1,10 @@
#include "IEC6205621.h" #include "IEC6205621.h"
#include "ams/crc.h"
IEC6205621::IEC6205621(const char* p) { IEC6205621::IEC6205621(const char* p) {
if(strlen(p) < 16) if(strlen(p) < 16)
return; return;
String payload(p+1); String payload(p+1);
int crc_pos = payload.lastIndexOf("!");
String crc = payload.substring(crc_pos+1, crc_pos+5);
//uint16_t crc_calc = crc16_x25((uint8_t*) (payload.startsWith("/") ? p+1 : p), crc_pos);
//Serial.printf("CRC %s :: %04X\n", crc.c_str(), crc_calc);
lastUpdateMillis = millis(); lastUpdateMillis = millis();
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n")); listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
@@ -28,11 +22,14 @@ IEC6205621::IEC6205621(const char* p) {
meterType = AmsTypeIskra; meterType = AmsTypeIskra;
listId = listId.substring(0,5); listId = listId.substring(0,5);
} else if(listId.startsWith("XMX")) { } else if(listId.startsWith("XMX")) {
meterType = AmsTypeLandis; meterType = AmsTypeLandisGyr;
listId = listId.substring(0,6); listId = listId.substring(0,6);
} else if(listId.startsWith("Ene") || listId.startsWith("EST")) { } else if(listId.startsWith("Ene") || listId.startsWith("EST")) {
meterType = AmsTypeSagemcom; meterType = AmsTypeSagemcom;
listId = listId.substring(0,4); listId = listId.substring(0,4);
} else if(listId.startsWith("LGF")) {
meterType = AmsTypeLandisGyr;
listId = listId.substring(0,4);
} else { } else {
meterType = AmsTypeUnknown; meterType = AmsTypeUnknown;
listId = listId.substring(0,4); listId = listId.substring(0,4);
@@ -80,6 +77,14 @@ IEC6205621::IEC6205621(const char* p) {
l2current = extractDouble(payload, "51.7.0"); l2current = extractDouble(payload, "51.7.0");
l3current = extractDouble(payload, "71.7.0"); l3current = extractDouble(payload, "71.7.0");
l1activeImportPower = extractDouble(payload, "21.7.0");
l2activeImportPower = extractDouble(payload, "41.7.0");
l3activeImportPower = extractDouble(payload, "61.7.0");
l1activeExportPower = extractDouble(payload, "22.7.0");
l2activeExportPower = extractDouble(payload, "42.7.0");
l3activeExportPower = extractDouble(payload, "62.7.0");
if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0) if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0)
listType = 2; listType = 2;
@@ -128,6 +133,9 @@ IEC6205621::IEC6205621(const char* p) {
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage; l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
} }
} }
if (l1activeImportPower > 0 || l2activeImportPower > 0 || l3activeImportPower > 0 || l1activeExportPower > 0 || l2activeExportPower > 0 || l3activeExportPower > 0)
listType = 4;
} }
String IEC6205621::extract(String payload, String obis) { String IEC6205621::extract(String payload, String obis) {

View File

@@ -1,4 +1,7 @@
#include "DsmrParser.h" #include "DsmrParser.h"
#include "crc.h"
#include "hexutils.h"
#include "lwip/def.h"
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) { int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
uint16_t crcPos = 0; uint16_t crcPos = 0;
@@ -14,8 +17,13 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
if(!reachedEnd) return DATA_PARSE_INCOMPLETE; if(!reachedEnd) return DATA_PARSE_INCOMPLETE;
buf[ctx.length+1] = '\0'; buf[ctx.length+1] = '\0';
if(crcPos > 0) { if(crcPos > 0) {
// TODO: CRC uint16_t crc_calc = crc16(buf, crcPos);
Serial.printf("CRC: %s\n", buf+crcPos); uint16_t crc = 0x0000;
fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2);
crc = ntohs(crc);
if(crc != crc_calc)
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
} }
return DATA_PARSE_OK; return DATA_PARSE_OK;
} }

View File

@@ -10,3 +10,20 @@ uint16_t crc16_x25(const uint8_t* p, int len)
return (~crc << 8) | (~crc >> 8 & 0xff); return (~crc << 8) | (~crc >> 8 & 0xff);
} }
uint16_t crc16 (const uint8_t *p, int len) {
uint16_t crc = 0;
while (len--) {
int i;
crc ^= *p++;
for (i = 0 ; i < 8 ; ++i) {
if (crc & 1)
crc = (crc >> 1) ^ 0xa001;
else
crc = (crc >> 1);
}
}
return crc;
}

View File

@@ -4,6 +4,7 @@
#include "Arduino.h" #include "Arduino.h"
#include <stdint.h> #include <stdint.h>
uint16_t crc16(const uint8_t* p, int len);
uint16_t crc16_x25(const uint8_t* p, int len); uint16_t crc16_x25(const uint8_t* p, int len);
#endif #endif

View File

@@ -110,7 +110,7 @@ bool EntsoeApi::loop() {
currentDay = tm.Day; currentDay = tm.Day;
return false; return false;
} else if(now > midnightMillis && currentDay != tm.Day) { } else if(now > midnightMillis && currentDay != tm.Day) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lld\n", t); if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Rotating price objects at %lu\n", t);
if(today != NULL) delete today; if(today != NULL) delete today;
if(tomorrow != NULL) { if(tomorrow != NULL) {
today = tomorrow; today = tomorrow;

View File

@@ -329,8 +329,8 @@ void AmsWebServer::configMeterHtml() {
case AmsTypeIskra: case AmsTypeIskra:
manufacturer = F("Iskra"); manufacturer = F("Iskra");
break; break;
case AmsTypeLandis: case AmsTypeLandisGyr:
manufacturer = F("Landis + Gyro"); manufacturer = F("Landis+Gyr");
break; break;
case AmsTypeSagemcom: case AmsTypeSagemcom:
manufacturer = F("Sagemcom"); manufacturer = F("Sagemcom");
@@ -2306,9 +2306,9 @@ void AmsWebServer::configFileDownload() {
if(ds != NULL) { if(ds != NULL) {
DayDataPoints day = ds->getDayData(); DayDataPoints day = ds->getDayData();
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dayplot %d %lld %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"), server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dayplot %d %lu %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"),
day.version, day.version,
(int64_t) day.lastMeterReadTime, (int32_t) day.lastMeterReadTime,
day.activeImport, day.activeImport,
ds->getHourImport(0), ds->getHourImport(0),
ds->getHourImport(1), ds->getHourImport(1),
@@ -2368,9 +2368,9 @@ void AmsWebServer::configFileDownload() {
} }
MonthDataPoints month = ds->getMonthData(); MonthDataPoints month = ds->getMonthData();
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("monthplot %d %lld %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"), server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("monthplot %d %lu %lu %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"),
month.version, month.version,
(int64_t) month.lastMeterReadTime, (int32_t) month.lastMeterReadTime,
month.activeImport, month.activeImport,
ds->getDayImport(1), ds->getDayImport(1),
ds->getDayImport(2), ds->getDayImport(2),

View File

@@ -854,7 +854,7 @@ var fetch = function() {
$('.jmt').html("Iskra"); $('.jmt').html("Iskra");
break; break;
case 9: case 9:
$('.jmt').html("Landis"); $('.jmt').html("Landis+Gyr");
break; break;
case 10: case 10:
$('.jmt').html("Sagemcom"); $('.jmt').html("Sagemcom");