Compare commits

..

11 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
Gunnar Skjold
6b0d540f39 Added 300 baud 2022-12-07 11:51:45 +01:00
Gunnar Skjold
33bd3da310 Various fix for realtime values 2022-12-06 15:45:43 +01:00
Gunnar Skjold
a239e1a63d Fix on special case for Kaifa MA304T3 2022-12-06 08:28:30 +01:00
Gunnar Skjold
1ef5703971 Fixed changing mdns settings 2022-12-01 18:06:19 +01:00
13 changed files with 84 additions and 41 deletions

View File

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

View File

@@ -21,7 +21,7 @@ bool AmsDataStorage::update(AmsData* data) {
}
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(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Timezone is missing\n");
return false;
@@ -30,18 +30,18 @@ bool AmsDataStorage::update(AmsData* data) {
if(data->getMeterTimestamp() > BUILD_EPOCH) {
now = data->getMeterTimestamp();
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) {
now = data->getPackageTimestamp();
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(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;
}
@@ -63,7 +63,7 @@ bool AmsDataStorage::update(AmsData* data) {
return true;
} else {
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;
breakTime(day.lastMeterReadTime, last);
@@ -86,7 +86,7 @@ bool AmsDataStorage::update(AmsData* data) {
month.lastMeterReadTime = now;
} else {
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;
breakTime(tz->toLocal(month.lastMeterReadTime), last);
@@ -156,7 +156,7 @@ bool AmsDataStorage::update(AmsData* data) {
setHourExport(last.Hour, exp);
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;
@@ -199,7 +199,7 @@ bool AmsDataStorage::update(AmsData* data) {
breakTime(tz->toLocal(month.lastMeterReadTime), last);
month.lastMeterReadTime = month.lastMeterReadTime - (last.Hour * 3600) - (last.Minute * 60) - last.Second;
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;
@@ -224,7 +224,7 @@ bool AmsDataStorage::update(AmsData* data) {
setDayExport(last.Day, exp);
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;
@@ -383,11 +383,11 @@ bool AmsDataStorage::isDayHappy() {
tmElements_t tm, last;
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;
}
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;
}
breakTime(tz->toLocal(now), tm);
@@ -411,11 +411,11 @@ bool AmsDataStorage::isMonthHappy() {
tmElements_t tm, last;
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;
}
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;
}
breakTime(tz->toLocal(now), tm);

View File

@@ -1391,7 +1391,7 @@ void MQTT_connect() {
#if defined(ESP8266)
if(mqttSecureClient) {
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);
}
#endif

View File

@@ -42,7 +42,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
if(!init) {
currentHour = local.Hour;
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(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) Unable to load existing data\n");
data = { 4, local.Month,
@@ -63,18 +63,21 @@ bool EnergyAccounting::update(AmsData* amsData) {
}
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();
}
if(local.Hour != currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EnergyAccounting) New local hour %d\n", local.Hour);
tmElements_t oneHrAgo;
tmElements_t oneHrAgo, oneHrAgoLocal;
breakTime(now-3600, oneHrAgo);
uint16_t val = ds->getHourImport(oneHrAgo.Hour) / 10;
ret |= updateMax(val, local.Day);
breakTime(tz->toLocal(now-3600), oneHrAgoLocal);
ret |= updateMax(val, oneHrAgoLocal.Day);
currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
if(local.Hour > 0) {
calcDayCost();
}
@@ -143,9 +146,9 @@ void EnergyAccounting::calcDayCost() {
if(eapi != NULL && eapi->getValueForHour(0) != ENTSOE_NO_VALUE) {
if(initPrice) costDay = 0;
for(int i = 0; i < currentHour; i++) {
float price = eapi->getValueForHour(i - currentHour);
float price = eapi->getValueForHour(i - local.Hour);
if(price == ENTSOE_NO_VALUE) break;
breakTime(now - ((currentHour - i) * 3600), utc);
breakTime(now - ((local.Hour - i) * 3600), utc);
int16_t wh = ds->getHourImport(utc.Hour);
costDay += price * (wh / 1000.0);
}
@@ -161,9 +164,10 @@ double EnergyAccounting::getUseToday() {
float ret = 0.0;
time_t now = time(nullptr);
if(now < BUILD_EPOCH) return 0;
tmElements_t utc;
tmElements_t utc, local;
breakTime(tz->toLocal(now), local);
for(int i = 0; i < currentHour; i++) {
breakTime(now - ((currentHour - i) * 3600), utc);
breakTime(now - ((local.Hour - i) * 3600), utc);
ret += ds->getHourImport(utc.Hour) / 1000.0;
}
return ret + getUseThisHour();

View File

@@ -1,16 +1,10 @@
#include "IEC6205621.h"
#include "ams/crc.h"
IEC6205621::IEC6205621(const char* p) {
if(strlen(p) < 16)
return;
String payload(p+1);
int crc_pos = payload.lastIndexOf("!");
String crc = payload.substring(crc_pos+1, crc_pos+5);
//uint16_t crc_calc = crc16_x25((uint8_t*) (payload.startsWith("/") ? p+1 : p), crc_pos);
//Serial.printf("CRC %s :: %04X\n", crc.c_str(), crc_calc);
lastUpdateMillis = millis();
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
@@ -28,11 +22,14 @@ IEC6205621::IEC6205621(const char* p) {
meterType = AmsTypeIskra;
listId = listId.substring(0,5);
} else if(listId.startsWith("XMX")) {
meterType = AmsTypeLandis;
meterType = AmsTypeLandisGyr;
listId = listId.substring(0,6);
} else if(listId.startsWith("Ene") || listId.startsWith("EST")) {
meterType = AmsTypeSagemcom;
listId = listId.substring(0,4);
} else if(listId.startsWith("LGF")) {
meterType = AmsTypeLandisGyr;
listId = listId.substring(0,4);
} else {
meterType = AmsTypeUnknown;
listId = listId.substring(0,4);
@@ -79,6 +76,14 @@ IEC6205621::IEC6205621(const char* p) {
l1current = extractDouble(payload, "31.7.0");
l2current = extractDouble(payload, "51.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)
listType = 2;
@@ -128,6 +133,9 @@ IEC6205621::IEC6205621(const char* p) {
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) {

View File

@@ -98,8 +98,10 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
}
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(l2voltage > 0) {
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
}
}
if(listType == 3) {

View File

@@ -1,4 +1,7 @@
#include "DsmrParser.h"
#include "crc.h"
#include "hexutils.h"
#include "lwip/def.h"
int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified) {
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;
buf[ctx.length+1] = '\0';
if(crcPos > 0) {
// TODO: CRC
Serial.printf("CRC: %s\n", buf+crcPos);
uint16_t crc_calc = crc16(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;
}

View File

@@ -10,3 +10,20 @@ uint16_t crc16_x25(const uint8_t* p, int len)
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 <stdint.h>
uint16_t crc16(const uint8_t* p, int len);
uint16_t crc16_x25(const uint8_t* p, int len);
#endif

View File

@@ -110,7 +110,7 @@ bool EntsoeApi::loop() {
currentDay = tm.Day;
return false;
} 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(tomorrow != NULL) {
today = tomorrow;

View File

@@ -329,8 +329,8 @@ void AmsWebServer::configMeterHtml() {
case AmsTypeIskra:
manufacturer = F("Iskra");
break;
case AmsTypeLandis:
manufacturer = F("Landis + Gyro");
case AmsTypeLandisGyr:
manufacturer = F("Landis+Gyr");
break;
case AmsTypeSagemcom:
manufacturer = F("Sagemcom");
@@ -347,6 +347,7 @@ void AmsWebServer::configMeterHtml() {
html.replace(F("{mod}"), meterState->getMeterModel());
html.replace(F("{mid}"), meterState->getMeterId());
html.replace(F("{b}"), String(meterConfig->baud));
html.replace(F("{b300}"), meterConfig->baud == 300 ? F("selected") : F(""));
html.replace(F("{b2400}"), meterConfig->baud == 2400 ? F("selected") : F(""));
html.replace(F("{b4800}"), meterConfig->baud == 4800 ? F("selected") : F(""));
html.replace(F("{b9600}"), meterConfig->baud == 9600 ? F("selected") : F(""));
@@ -1264,6 +1265,7 @@ void AmsWebServer::handleSave() {
if(server.hasArg(F("h")) && !server.arg(F("h")).isEmpty()) {
strcpy(wifi.hostname, server.arg(F("h")).c_str());
}
wifi.mdns = server.arg(F("m")) == F("true");
wifi.power = server.arg(F("w")).toFloat() * 10;
wifi.sleep = server.arg(F("z")).toInt();
config->setWiFiConfig(wifi);
@@ -2304,9 +2306,9 @@ void AmsWebServer::configFileDownload() {
if(ds != NULL) {
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,
(int64_t) day.lastMeterReadTime,
(int32_t) day.lastMeterReadTime,
day.activeImport,
ds->getHourImport(0),
ds->getHourImport(1),
@@ -2366,9 +2368,9 @@ void AmsWebServer::configFileDownload() {
}
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,
(int64_t) month.lastMeterReadTime,
(int32_t) month.lastMeterReadTime,
month.activeImport,
ds->getDayImport(1),
ds->getDayImport(2),

View File

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

View File

@@ -35,6 +35,7 @@
<span class="input-group-text">Baud rate</span>
</div>
<select class="form-control sd" name="b">
<option value="300" {b300}>300</option>
<option value="2400" {b2400}>2400</option>
<option value="4800" {b4800}>4800</option>
<option value="9600" {b9600}>9600</option>