mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-11 21:15:30 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdee066c33 | ||
|
|
dd23a0fa60 | ||
|
|
e8fc6d48bf | ||
|
|
4b15ac74fc | ||
|
|
8d448533c7 | ||
|
|
43cb9a0000 | ||
|
|
8f057e687c | ||
|
|
5b4f680114 | ||
|
|
fabdfbadf4 | ||
|
|
afa47ea633 | ||
|
|
c40e20c8e9 | ||
|
|
6b0d540f39 | ||
|
|
33bd3da310 | ||
|
|
a239e1a63d | ||
|
|
1ef5703971 | ||
|
|
538de5ea99 | ||
|
|
042e2bcc85 | ||
|
|
775e5a0881 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
||||
custom: ["https://paypal.me/gskjold"]
|
||||
custom: ["https://amsleser.no"]
|
||||
|
||||
@@ -675,6 +675,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 95:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig95()) {
|
||||
configVersion = 96;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@@ -852,6 +860,23 @@ bool AmsConfiguration::relocateConfig94() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig95() {
|
||||
MeterConfig meter;
|
||||
MeterConfig95 meter95;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START, meter);
|
||||
EEPROM.get(CONFIG_METER_START, meter95);
|
||||
meter.wattageMultiplier = meter95.wattageMultiplier;
|
||||
meter.voltageMultiplier = meter95.voltageMultiplier;
|
||||
meter.amperageMultiplier = meter95.amperageMultiplier;
|
||||
meter.accumulatedMultiplier = meter95.accumulatedMultiplier;
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 96);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 95 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 96 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
@@ -88,6 +88,23 @@ struct WebConfig {
|
||||
}; // 129
|
||||
|
||||
struct MeterConfig {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_t productionCapacity;
|
||||
uint8_t encryptionKey[16];
|
||||
uint8_t authenticationKey[16];
|
||||
uint32_t wattageMultiplier;
|
||||
uint32_t voltageMultiplier;
|
||||
uint32_t amperageMultiplier;
|
||||
uint32_t accumulatedMultiplier;
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 50
|
||||
|
||||
struct MeterConfig95 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
@@ -270,6 +287,7 @@ private:
|
||||
bool relocateConfig92(); // 2.0.3
|
||||
bool relocateConfig93(); // 2.1.0
|
||||
bool relocateConfig94(); // 2.1.4
|
||||
bool relocateConfig95(); // 2.1.13
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
|
||||
@@ -10,7 +10,7 @@ enum AmsType {
|
||||
AmsTypeKaifa = 0x02,
|
||||
AmsTypeKamstrup = 0x03,
|
||||
AmsTypeIskra = 0x08,
|
||||
AmsTypeLandis = 0x09,
|
||||
AmsTypeLandisGyr = 0x09,
|
||||
AmsTypeSagemcom = 0x0A,
|
||||
AmsTypeLng = 0x0B,
|
||||
AmsTypeCustom = 0x88,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -122,44 +122,7 @@ void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
if(!config.getGpioConfig(gpioConfig)) {
|
||||
#if HW_ROARFRED
|
||||
gpioConfig.hanPin = 3;
|
||||
gpioConfig.apPin = 0;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.tempSensorPin = 5;
|
||||
#elif defined(ARDUINO_ESP8266_WEMOS_D1MINI)
|
||||
gpioConfig.hanPin = 5;
|
||||
gpioConfig.apPin = 4;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.tempSensorPin = 14;
|
||||
gpioConfig.vccMultiplier = 1100;
|
||||
#elif defined(ARDUINO_LOLIN_D32)
|
||||
gpioConfig.hanPin = 16;
|
||||
gpioConfig.ledPin = 5;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.tempSensorPin = 14;
|
||||
#elif defined(ARDUINO_FEATHER_ESP32)
|
||||
gpioConfig.hanPin = 16;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.tempSensorPin = 14;
|
||||
#elif defined(ARDUINO_ESP32_DEV)
|
||||
gpioConfig.hanPin = 16;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
#elif defined(ESP8266)
|
||||
gpioConfig.hanPin = 3;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
gpioConfig.hanPin = 18;
|
||||
#elif defined(ESP32)
|
||||
gpioConfig.hanPin = 16;
|
||||
gpioConfig.ledPin = 2;
|
||||
gpioConfig.ledInverted = true;
|
||||
gpioConfig.tempSensorPin = 14;
|
||||
#endif
|
||||
config.clearGpio(gpioConfig);
|
||||
}
|
||||
|
||||
delay(1);
|
||||
@@ -810,7 +773,7 @@ bool readHanPort() {
|
||||
hanBuffer[len++] = hanSerial->read();
|
||||
ctx.length = len;
|
||||
pos = unwrapData((uint8_t *) hanBuffer, ctx);
|
||||
if(pos >= 0) {
|
||||
if(ctx.type > 0 && pos >= 0) {
|
||||
if(ctx.type == DATA_TAG_DLMS) {
|
||||
debugV("Received valid DLMS at %d", pos);
|
||||
} else if(ctx.type == DATA_TAG_DSMR) {
|
||||
@@ -826,6 +789,7 @@ bool readHanPort() {
|
||||
if(pos == DATA_PARSE_INCOMPLETE) {
|
||||
return false;
|
||||
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
|
||||
debugV("Unknown data payload:");
|
||||
len = len + hanSerial->readBytes(hanBuffer+len, BUF_SIZE_HAN-len);
|
||||
debugPrint(hanBuffer, 0, len);
|
||||
len = 0;
|
||||
@@ -1115,7 +1079,17 @@ void WiFi_connect() {
|
||||
WiFi.persistent(true);
|
||||
if(WiFi.begin(wifi.ssid, wifi.psk)) {
|
||||
if(wifi.sleep <= 2) {
|
||||
WiFi.setSleep(wifi.sleep);
|
||||
switch(wifi.sleep) {
|
||||
case 0:
|
||||
WiFi.setSleep(WIFI_PS_NONE);
|
||||
break;
|
||||
case 1:
|
||||
WiFi.setSleep(WIFI_PS_MIN_MODEM);
|
||||
break;
|
||||
case 2:
|
||||
WiFi.setSleep(WIFI_PS_MAX_MODEM);
|
||||
break;
|
||||
}
|
||||
}
|
||||
yield();
|
||||
} else {
|
||||
@@ -1175,7 +1149,7 @@ int16_t unwrapData(uint8_t *buf, DataParserContext &context) {
|
||||
if(res >= 0) doRet = true;
|
||||
break;
|
||||
default:
|
||||
debugE("Ended up in default case while unwrapping...");
|
||||
debugE("Ended up in default case while unwrapping...(tag %02X)", tag);
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
}
|
||||
lastTag = tag;
|
||||
@@ -1380,7 +1354,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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
85
src/LNG.cpp
85
src/LNG.cpp
@@ -1,6 +1,6 @@
|
||||
#include "LNG.h"
|
||||
#include "lwip/def.h"
|
||||
#include "ams/Cosem.h"
|
||||
#include "ams/ntohll.h"
|
||||
|
||||
LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
|
||||
LngHeader* h = (LngHeader*) payload;
|
||||
@@ -11,9 +11,10 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
uint8_t* ptr = (uint8_t*) &h[1];
|
||||
uint8_t* data = ptr + (18*h->arrayLength); // Skip descriptors
|
||||
|
||||
uint16_t o170 = 0, o270 = 0;
|
||||
uint16_t o181 = 0, o182 = 0;
|
||||
uint16_t o281 = 0, o282 = 0;
|
||||
uint64_t o170 = 0, o270 = 0;
|
||||
uint64_t o180 = 0, o280 = 0;
|
||||
uint64_t o181 = 0, o182 = 0;
|
||||
uint64_t o281 = 0, o282 = 0;
|
||||
LngObisDescriptor* descriptor = (LngObisDescriptor*) ptr;
|
||||
for(uint8_t x = 0; x < h->arrayLength-1; x++) {
|
||||
ptr = (uint8_t*) &descriptor[1];
|
||||
@@ -24,39 +25,41 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
if(descriptor->obis[2] == 1) {
|
||||
if(descriptor->obis[3] == 7) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
o170 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
o170 = getNumber(item);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o170);
|
||||
}
|
||||
} else if(descriptor->obis[3] == 8) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
activeImportCounter = ntohl(item->dlu.data) / 1000.0;
|
||||
o180 = getNumber(item);
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o180);
|
||||
activeImportCounter = o180 / 1000.0;
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
o181 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
o181 = getNumber(item);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o181);
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
o182 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
o182 = getNumber(item);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o182);
|
||||
}
|
||||
}
|
||||
} else if(descriptor->obis[2] == 2) {
|
||||
if(descriptor->obis[3] == 7) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
o270 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
o270 = getNumber(item);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o270);
|
||||
}
|
||||
} else if(descriptor->obis[3] == 8) {
|
||||
if(descriptor->obis[4] == 0) {
|
||||
activeExportCounter = ntohl(item->dlu.data) / 1000.0;
|
||||
o280 = getNumber(item);
|
||||
listType = listType >= 3 ? listType : 3;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o280);
|
||||
activeExportCounter = o280 / 1000.0;
|
||||
} else if(descriptor->obis[4] == 1) {
|
||||
o281 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
o281 = getNumber(item);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o281);
|
||||
} else if(descriptor->obis[4] == 2) {
|
||||
o282 = ntohl(item->dlu.data);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu (dlu)", ntohl(item->dlu.data));
|
||||
o282 = getNumber(item);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf(" and value %lu", o282);
|
||||
}
|
||||
}
|
||||
} else if(descriptor->obis[2] == 96) {
|
||||
@@ -101,11 +104,49 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
|
||||
if((*data) == 0x09) {
|
||||
data += (*(data+1))+2;
|
||||
} else {
|
||||
} else if((*data) == 0x15) {
|
||||
data += 9;
|
||||
} else if((*data) == 0x06) {
|
||||
data += 5;
|
||||
} else if((*data) == 0x12) {
|
||||
data += 3;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t LNG::getNumber(CosemData* item) {
|
||||
if(item != NULL) {
|
||||
uint64_t ret = 0.0;
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongSigned: {
|
||||
int16_t i16 = ntohs(item->ls.data);
|
||||
return i16;
|
||||
}
|
||||
case CosemTypeLongUnsigned: {
|
||||
uint16_t u16 = ntohs(item->lu.data);
|
||||
return u16;
|
||||
}
|
||||
case CosemTypeDLongSigned: {
|
||||
int32_t i32 = ntohl(item->dlu.data);
|
||||
return i32;
|
||||
}
|
||||
case CosemTypeDLongUnsigned: {
|
||||
uint32_t u32 = ntohl(item->dlu.data);
|
||||
return u32;
|
||||
}
|
||||
case CosemTypeLong64Signed: {
|
||||
int64_t i64 = ntohll(item->l64s.data);
|
||||
return i64;
|
||||
}
|
||||
case CosemTypeLong64Unsigned: {
|
||||
uint64_t u64 = ntohll(item->l64u.data);
|
||||
return u64;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "ams/DataParser.h"
|
||||
#include "ams/Cosem.h"
|
||||
#include "RemoteDebug.h"
|
||||
|
||||
struct LngHeader {
|
||||
@@ -25,6 +26,7 @@ struct LngObisDescriptor {
|
||||
class LNG : public AmsData {
|
||||
public:
|
||||
LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger);
|
||||
uint64_t getNumber(CosemData* item);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(""));
|
||||
@@ -1022,6 +1023,7 @@ void AmsWebServer::handleSetup() {
|
||||
|
||||
switch(sys.boardType) {
|
||||
case 0: // roarfred
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
@@ -1029,6 +1031,7 @@ void AmsWebServer::handleSetup() {
|
||||
gpioConfig->tempSensorPin = 5;
|
||||
break;
|
||||
case 1: // Arnio Kamstrup
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
@@ -1038,6 +1041,7 @@ void AmsWebServer::handleSetup() {
|
||||
gpioConfig->ledRgbInverted = true;
|
||||
break;
|
||||
case 2: // spenceme
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
@@ -1047,6 +1051,7 @@ void AmsWebServer::handleSetup() {
|
||||
wifi.sleep = 1;
|
||||
break;
|
||||
case 3: // Pow UART0
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
@@ -1057,6 +1062,7 @@ void AmsWebServer::handleSetup() {
|
||||
wifi.sleep = 1;
|
||||
break;
|
||||
case 4: // Pow GPIO12
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 12;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
@@ -1067,6 +1073,7 @@ void AmsWebServer::handleSetup() {
|
||||
wifi.sleep = 1;
|
||||
break;
|
||||
case 5: // Pow-K+ UART2
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 16;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPinRed = 13;
|
||||
@@ -1078,6 +1085,7 @@ void AmsWebServer::handleSetup() {
|
||||
wifi.sleep = 1;
|
||||
break;
|
||||
case 6: // Pow-P1
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 16;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPinRed = 13;
|
||||
@@ -1088,6 +1096,7 @@ void AmsWebServer::handleSetup() {
|
||||
gpioConfig->vccResistorVcc = 33;
|
||||
break;
|
||||
case 7: // Pow-U+
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 16;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPinRed = 13;
|
||||
@@ -1264,6 +1273,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 +2314,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 +2376,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),
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user