mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-02-27 01:10:31 +00:00
Added support for IEC62056-21
This commit is contained in:
BIN
doc/Aidon-RJ12.pdf
Normal file
BIN
doc/Aidon-RJ12.pdf
Normal file
Binary file not shown.
BIN
doc/Slimme_meter_15_a727fce1f1.pdf
Normal file
BIN
doc/Slimme_meter_15_a727fce1f1.pdf
Normal file
Binary file not shown.
@@ -23,4 +23,12 @@ T FF FF DA SA SA C HC HC LD LS LQ AT AI AI AI AI AD
|
||||
C9 95 7E // CRC and end tag
|
||||
|
||||
|
||||
7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00 00 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 02 12 09 07 4B 46 4D 5F 30 30 31 09 10 XX XX XX XX XX XX XX XX XX XX 35 33 34 34 39 33 09 07 4D 41 33 30 34 48 34 06 00 00 09 99 06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05 06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E
|
||||
7E A0 9A 01 02 01 10 AA A5 E6 E7 00 0F 40 00 00 00 09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00
|
||||
02 12 09 07 4B 46 4D 5F 30 30 31
|
||||
09 10 XX XX XX XX XX XX XX XX XX XX 35 33 34 34 39 33
|
||||
09 07 4D 41 33 30 34 48 34
|
||||
06 00 00 09 99
|
||||
06 00 00 00 00 06 00 00 00 00 06 00 00 01 67 06 00 00 03 BF 06 00 00 05 05
|
||||
06 00 00 24 34 06 00 00 09 45 06 00 00 09 4F 06 00 00 09 3B
|
||||
09 0C 07 E5 03 17 02 13 00 0A FF 80 00 00 06 01 34 3B 5D 06 00 00 00 00 06 00 00 09 36 06 00 3C 7A 98 DA 15 7E
|
||||
7E A0 79 01 02 01 10 80 93 E6 E7 00 0F 40 00 00 00 09 0C 07 E1 09 0E 04 15 1F 14 FF 80 00 00 02 0D 09 07 4B 46 4D 5F 30 30 31 09 10 36 39 37 30 36 33 31 34 30 31 37 35 33 39 38 35 09 08 4D 41 33 30 34 48 33 45 06 00 00 04 0C 06 00 00 00 00 06 00 00 00 00 06 00 00 00 4E 06 00 00 07 C1 06 00 00 0C 9E 06 00 00 0D 7E 06 00 00 09 5F 06 00 00 00 00 06 00 00 09 66 87 96 7E
|
||||
@@ -738,9 +738,15 @@ bool AmsConfiguration::relocateConfig87() {
|
||||
MeterConfig meter;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START_87, meter87);
|
||||
meter.baud = 2400;
|
||||
meter.parity = meter87.type == 3 || meter87.type == 4 ? 3 : 11;
|
||||
meter.invert = false;
|
||||
if(meter87.type < 5) {
|
||||
meter.baud = 2400;
|
||||
meter.parity = meter87.type == 3 || meter87.type == 4 ? 3 : 11;
|
||||
meter.invert = false;
|
||||
} else {
|
||||
meter.baud = 115200;
|
||||
meter.parity = 3;
|
||||
meter.invert = meter87.type == 6;
|
||||
}
|
||||
meter.distributionSystem = meter87.distributionSystem;
|
||||
meter.mainFuse = meter87.mainFuse;
|
||||
meter.productionCapacity = meter87.productionCapacity;
|
||||
|
||||
188
src/AmsData.cpp
188
src/AmsData.cpp
@@ -1,195 +1,7 @@
|
||||
#include "AmsData.h"
|
||||
#include "ams/ams.h"
|
||||
|
||||
uint8_t AMS_OBIS_VERSION[6] = { 1, 1, 0, 2, 129, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL[6] = { 0, 0, 96, 1, 7, 255 };
|
||||
uint8_t AMS_OBIS_METER_ID[6] = { 0, 0, 96, 1, 0, 255 };
|
||||
uint8_t AMS_OBIS_METER_TIMESTAMP[4] = { 1, 0, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT[4] = { 1, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L1[4] = { 21, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2[4] = { 41, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3[4] = { 61, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT[4] = { 2, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT[4] = { 3, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_L1[4] = { 23, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_L2[4] = { 43, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_L3[4] = { 63, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT[4] = { 4, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_L1[4] = { 24, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_L2[4] = { 44, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_L3[4] = { 64, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT[4] = { 11, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT_L1[4] = { 31, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT_L2[4] = { 51, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT_L3[4] = { 71, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE[4] = { 12, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE_L1[4] = { 32, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE_L2[4] = { 52, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE_L3[4] = { 72, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_COUNT[4] = { 1, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_COUNT[4] = { 2, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_COUNT[4] = { 3, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_COUNT[4] = { 4, 8, 0, 255 };
|
||||
|
||||
AmsData::AmsData() {}
|
||||
|
||||
AmsData::AmsData(const char* d, bool substituteMissing) {
|
||||
uint32_t u32;
|
||||
int32_t s32;
|
||||
char str[64];
|
||||
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 1;
|
||||
activeImportPower = u32;
|
||||
}
|
||||
|
||||
meterType = AmsTypeUnknown;
|
||||
CosemData* version = AMS_findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d);
|
||||
if(version != NULL && version->base.type == CosemTypeString) {
|
||||
if(memcmp(version->str.data, "AIDON", 5) == 0) {
|
||||
meterType = AmsTypeAidon;
|
||||
} else if(memcmp(version->str.data, "Kamstrup", 8) == 0) {
|
||||
meterType = AmsTypeKamstrup;
|
||||
}
|
||||
}
|
||||
|
||||
u32 = AMS_getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
listId = String(str);
|
||||
}
|
||||
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
activeExportPower = u32;
|
||||
}
|
||||
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
reactiveImportPower = u32;
|
||||
}
|
||||
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
reactiveExportPower = u32;
|
||||
}
|
||||
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l1voltage = u32;
|
||||
}
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l2voltage = u32;
|
||||
}
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l3voltage = u32;
|
||||
}
|
||||
|
||||
s32 = AMS_getSignedNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d)));
|
||||
if(s32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l1current = s32;
|
||||
}
|
||||
s32 = AMS_getSignedNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d)));
|
||||
if(s32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l2current = s32;
|
||||
}
|
||||
s32 = AMS_getSignedNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d)));
|
||||
if(s32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l3current = s32;
|
||||
}
|
||||
|
||||
if(listType == 2) {
|
||||
int vdiv = 1;
|
||||
int voltage = l1voltage == 0 ? l2voltage == 0 ? l3voltage == 0 ? 0 : l3voltage : l2voltage : l1voltage;
|
||||
while(voltage > 1000) {
|
||||
vdiv *= 10;
|
||||
voltage /= 10;
|
||||
}
|
||||
|
||||
l1voltage = l1voltage != 0 ? l1voltage / vdiv : 0;
|
||||
l2voltage = l2voltage != 0 ? l2voltage / vdiv : 0;
|
||||
l3voltage = l3voltage != 0 ? l3voltage / vdiv : 0;
|
||||
|
||||
int adiv = 1;
|
||||
int watt = (l1voltage * l1current) + (l2voltage * l2current) + (l3voltage * l3current);
|
||||
while(watt / activeImportPower > 2) {
|
||||
adiv *= 10;
|
||||
watt /= 10;
|
||||
}
|
||||
|
||||
l1current = l1current != 0 ? l1current / adiv : 0;
|
||||
l2current = l2current != 0 ? l2current / adiv : 0;
|
||||
l3current = l3current != 0 ? l3current / adiv : 0;
|
||||
}
|
||||
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
activeImportCounter = u32 / 100.0;
|
||||
}
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
activeExportCounter = u32 / 100.0;
|
||||
}
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
reactiveImportCounter = u32 / 100.0;
|
||||
}
|
||||
u32 = AMS_getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
reactiveExportCounter = u32 / 100.0;
|
||||
}
|
||||
|
||||
u32 = AMS_getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
meterModel = String(str);
|
||||
}
|
||||
|
||||
u32 = AMS_getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
meterId = String(str);
|
||||
}
|
||||
|
||||
time_t ts = AMS_getTimestamp(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
|
||||
if(ts > 0) {
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
|
||||
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
|
||||
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
|
||||
|
||||
if(threePhase) {
|
||||
if(substituteMissing && l2current == 0) {
|
||||
l2current = (((activeImportPower - activeExportPower) * sqrt(3)) - (l1voltage * l1current) - (l3voltage * l3current)) / l2voltage;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
time_t packageTimestamp = 0;
|
||||
*/
|
||||
/*
|
||||
CosemData* model = AMS_findObis(AMS_OBIS_METER_MODEL, ((char *) (d)));
|
||||
if(model != NULL && model->base.type == CosemTypeString) {
|
||||
this->meterModel = String((const char*) model->str.data);
|
||||
}
|
||||
*/
|
||||
lastUpdateMillis = millis();
|
||||
}
|
||||
|
||||
void AmsData::apply(AmsData& other) {
|
||||
if(other.getListType() < 3) {
|
||||
unsigned long ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastUpdateMillis;
|
||||
|
||||
@@ -8,13 +8,15 @@ enum AmsType {
|
||||
AmsTypeAidon = 0x01,
|
||||
AmsTypeKaifa = 0x02,
|
||||
AmsTypeKamstrup = 0x03,
|
||||
AmsTypeIskra = 0x08,
|
||||
AmsTypeLandis = 0x09,
|
||||
AmsTypeSagemcom = 0x0A,
|
||||
AmsTypeUnknown = 0xFF
|
||||
};
|
||||
|
||||
class AmsData {
|
||||
public:
|
||||
AmsData();
|
||||
AmsData(const char* d, bool substituteMissing);
|
||||
|
||||
void apply(AmsData& other);
|
||||
|
||||
@@ -52,7 +54,7 @@ public:
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
|
||||
private:
|
||||
protected:
|
||||
unsigned long lastUpdateMillis = 0;
|
||||
uint8_t listType = 0, meterType = AmsTypeUnknown;
|
||||
time_t packageTimestamp = 0;
|
||||
|
||||
@@ -44,6 +44,9 @@ ADC_MODE(ADC_VCC);
|
||||
#define BUF_SIZE (1024)
|
||||
#include "ams/hdlc.h"
|
||||
|
||||
#include "IEC6205621.h"
|
||||
#include "IEC6205675.h"
|
||||
|
||||
HwTools hw;
|
||||
|
||||
DNSServer* dnsServer = NULL;
|
||||
@@ -136,16 +139,30 @@ void setup() {
|
||||
config.getMeterConfig(meterConfig);
|
||||
if(gpioConfig.hanPin == 3) {
|
||||
shared = true;
|
||||
SerialConfig serialConfig;
|
||||
#if defined(ESP8266)
|
||||
SerialConfig serialConfig;
|
||||
#elif defined(ESP32)
|
||||
uint32_t serialConfig;
|
||||
#endif;
|
||||
switch(meterConfig.parity) {
|
||||
case 2:
|
||||
serialConfig = SERIAL_7N1;
|
||||
break;
|
||||
case 3:
|
||||
serialConfig = SERIAL_8N1;
|
||||
break;
|
||||
case 10:
|
||||
serialConfig = SERIAL_7E1;
|
||||
break;
|
||||
default:
|
||||
serialConfig = SERIAL_8E1;
|
||||
break;
|
||||
}
|
||||
Serial.begin(meterConfig.baud, serialConfig);
|
||||
#if defined(ESP32)
|
||||
Serial.begin(meterConfig.baud, serialConfig, -1, -1, meterConfig.invert);
|
||||
#else
|
||||
Serial.begin(meterConfig.baud, serialConfig, SERIAL_FULL, 1, meterConfig.invert);
|
||||
#endif
|
||||
}
|
||||
|
||||
if(!shared) {
|
||||
@@ -475,18 +492,31 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
if(hwSerial != NULL) {
|
||||
debugD("Hardware serial");
|
||||
Serial.flush();
|
||||
|
||||
SerialConfig serialConfig;
|
||||
#if defined(ESP8266)
|
||||
SerialConfig serialConfig;
|
||||
#elif defined(ESP32)
|
||||
uint32_t serialConfig;
|
||||
#endif
|
||||
switch(parityOrdinal) {
|
||||
case 2:
|
||||
serialConfig = SERIAL_7N1;
|
||||
break;
|
||||
case 3:
|
||||
serialConfig = SERIAL_8N1;
|
||||
break;
|
||||
case 10:
|
||||
serialConfig = SERIAL_7E1;
|
||||
break;
|
||||
default:
|
||||
serialConfig = SERIAL_8E1;
|
||||
break;
|
||||
}
|
||||
|
||||
hwSerial->begin(baud, serialConfig, SERIAL_FULL, -1, invert);
|
||||
#if defined(ESP32)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
#else
|
||||
hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert);
|
||||
#endif
|
||||
hanSerial = hwSerial;
|
||||
} else {
|
||||
debugD("Software serial");
|
||||
@@ -494,9 +524,15 @@ void setupHanPort(uint8_t pin, uint32_t baud, uint8_t parityOrdinal, bool invert
|
||||
|
||||
SoftwareSerialConfig serialConfig;
|
||||
switch(parityOrdinal) {
|
||||
case 2:
|
||||
serialConfig = SWSERIAL_7N1;
|
||||
break;
|
||||
case 3:
|
||||
serialConfig = SWSERIAL_8N1;
|
||||
break;
|
||||
case 10:
|
||||
serialConfig = SWSERIAL_7E1;
|
||||
break;
|
||||
default:
|
||||
serialConfig = SWSERIAL_8E1;
|
||||
break;
|
||||
@@ -592,69 +628,101 @@ void mqttMessageReceived(String &topic, String &payload)
|
||||
}
|
||||
|
||||
HDLCConfig* hc = NULL;
|
||||
int currentMeterType = 0;
|
||||
int currentMeterType = -1;
|
||||
void readHanPort() {
|
||||
uint8_t buf[BUF_SIZE];
|
||||
if(!hanSerial->available()) return;
|
||||
size_t len = hanSerial->readBytes(buf, BUF_SIZE); // TODO: read one byte at the time. This blocks up the GUI
|
||||
if(len > 0) {
|
||||
int pos = HDLC_validate((uint8_t *) buf, len, hc);
|
||||
if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) {
|
||||
hc = new HDLCConfig();
|
||||
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
||||
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
||||
}
|
||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
debugD("Frame dump:");
|
||||
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, 8);
|
||||
|
||||
if(currentMeterType == -1) {
|
||||
while(hanSerial->available()) hanSerial->read();
|
||||
currentMeterType = 0;
|
||||
return;
|
||||
}
|
||||
if(currentMeterType == 0) {
|
||||
uint8_t flag = hanSerial->read();
|
||||
if(flag == 0x7E) currentMeterType = 1;
|
||||
else currentMeterType = 2;
|
||||
while(hanSerial->available()) hanSerial->read();
|
||||
return;
|
||||
}
|
||||
AmsData data;
|
||||
if(currentMeterType == 1) {
|
||||
uint8_t buf[BUF_SIZE];
|
||||
size_t len = hanSerial->readBytes(buf, BUF_SIZE); // TODO: read one byte at the time. This blocks up the GUI
|
||||
if(len > 0) {
|
||||
int pos = HDLC_validate((uint8_t *) buf, len, hc);
|
||||
if(pos == HDLC_ENCRYPTION_CONFIG_MISSING) {
|
||||
hc = new HDLCConfig();
|
||||
memcpy(hc->encryption_key, meterConfig.encryptionKey, 16);
|
||||
memcpy(hc->authentication_key, meterConfig.authenticationKey, 16);
|
||||
}
|
||||
}
|
||||
if(pos >= 0) {
|
||||
debugI("Valid HDLC, start at %d", pos);
|
||||
if(!hw.ledBlink(LED_GREEN, 1))
|
||||
hw.ledBlink(LED_INTERNAL, 1);
|
||||
AmsData data = AmsData(((char *) (buf)) + pos, true);
|
||||
if(data.getListType() > 0) {
|
||||
if(mqttEnabled && mqttHandler != NULL) {
|
||||
if(mqttHandler->publish(&data, &meterState)) {
|
||||
if(data.getListType() == 3 && eapi != NULL) {
|
||||
mqttHandler->publishPrices(eapi);
|
||||
}
|
||||
if(data.getListType() >= 2) {
|
||||
mqttHandler->publishSystem(&hw);
|
||||
}
|
||||
time_t now = time(nullptr);
|
||||
if(now < EPOCH_2021_01_01 && data.getListType() == 3 && !ntpEnabled) {
|
||||
if(data.getMeterTimestamp() > EPOCH_2021_01_01) {
|
||||
debugI("Using timestamp from meter");
|
||||
now = data.getMeterTimestamp();
|
||||
} else if(data.getPackageTimestamp() > EPOCH_2021_01_01) {
|
||||
debugI("Using timestamp from meter (DLMS)");
|
||||
now = data.getPackageTimestamp();
|
||||
}
|
||||
if(now > EPOCH_2021_01_01) {
|
||||
timeval tv { now, 0};
|
||||
settimeofday(&tv, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
debugD("Frame dump:");
|
||||
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, 8);
|
||||
}
|
||||
meterState.apply(data);
|
||||
}
|
||||
if(pos >= 0) {
|
||||
debugI("Valid HDLC, start at %d", pos);
|
||||
data = IEC6205675(((char *) (buf)) + pos, meterState.getMeterType());
|
||||
} else {
|
||||
debugW("Invalid HDLC, returned with %d", pos);
|
||||
currentMeterType = 0;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
debugW("Invalid HDLC, returned with %d", pos);
|
||||
return;
|
||||
}
|
||||
} else if(currentMeterType == 2) {
|
||||
String payload = hanSerial->readString();
|
||||
data = IEC6205621(payload);
|
||||
if(data.getListType() == 0) {
|
||||
currentMeterType = 1;
|
||||
} else {
|
||||
if(Debug.isActive(RemoteDebug::DEBUG)) {
|
||||
debugD("Frame dump: %d", payload.length());
|
||||
debugD("%s", payload.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(data.getListType() > 0) {
|
||||
if(!hw.ledBlink(LED_GREEN, 1))
|
||||
hw.ledBlink(LED_INTERNAL, 1);
|
||||
if(mqttEnabled && mqttHandler != NULL) {
|
||||
if(mqttHandler->publish(&data, &meterState)) {
|
||||
if(data.getListType() == 3 && eapi != NULL) {
|
||||
mqttHandler->publishPrices(eapi);
|
||||
}
|
||||
if(data.getListType() >= 2) {
|
||||
mqttHandler->publishSystem(&hw);
|
||||
}
|
||||
time_t now = time(nullptr);
|
||||
if(now < EPOCH_2021_01_01 && data.getListType() == 3 && !ntpEnabled) {
|
||||
if(data.getMeterTimestamp() > EPOCH_2021_01_01) {
|
||||
debugI("Using timestamp from meter");
|
||||
now = data.getMeterTimestamp();
|
||||
} else if(data.getPackageTimestamp() > EPOCH_2021_01_01) {
|
||||
debugI("Using timestamp from meter (DLMS)");
|
||||
now = data.getPackageTimestamp();
|
||||
}
|
||||
if(now > EPOCH_2021_01_01) {
|
||||
timeval tv { now, 0};
|
||||
settimeofday(&tv, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
}
|
||||
meterState.apply(data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
108
src/IEC6205621.cpp
Normal file
108
src/IEC6205621.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "IEC6205621.h"
|
||||
|
||||
IEC6205621::IEC6205621(String payload) {
|
||||
if(payload.length() < 16)
|
||||
return;
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
|
||||
if(listId.startsWith("ADN")) {
|
||||
meterType == AmsTypeAidon;
|
||||
listId = listId.substring(0,4);
|
||||
} else if(listId.startsWith("KFM")) {
|
||||
meterType = AmsTypeKaifa;
|
||||
listId = listId.substring(0,4);
|
||||
} else if(listId.startsWith("KMP")) {
|
||||
meterType = AmsTypeKamstrup;
|
||||
listId = listId.substring(0,4);
|
||||
} else if(listId.startsWith("ISk")) {
|
||||
meterType = AmsTypeIskra;
|
||||
listId = listId.substring(0,5);
|
||||
} else if(listId.startsWith("XMX")) {
|
||||
meterType = AmsTypeLandis;
|
||||
listId = listId.substring(0,6);
|
||||
} else if(listId.startsWith("Ene")) {
|
||||
meterType = AmsTypeSagemcom;
|
||||
listId = listId.substring(0,4);
|
||||
} else {
|
||||
meterType = AmsTypeUnknown;
|
||||
listId = listId.substring(0,4);
|
||||
}
|
||||
|
||||
meterId = extract(payload, "96.1.0");
|
||||
if(meterId.isEmpty()) {
|
||||
meterId = extract(payload, "0.0.5");
|
||||
}
|
||||
|
||||
meterModel = extract(payload, "96.1.1");
|
||||
if(meterModel.isEmpty()) {
|
||||
meterModel = extract(payload, "96.1.7");
|
||||
if(meterModel.isEmpty()) {
|
||||
meterModel = payload.substring(payload.indexOf(listId) + listId.length(), payload.indexOf("\n"));
|
||||
meterModel.trim();
|
||||
}
|
||||
}
|
||||
|
||||
String timestamp = extract(payload, "1.0.0");
|
||||
if(timestamp.length() > 10) {
|
||||
tmElements_t tm;
|
||||
tm.Year = (timestamp.substring(0,2).toInt() + 2000) - 1970;
|
||||
tm.Month = timestamp.substring(4,6).toInt();
|
||||
tm.Day = timestamp.substring(2,4).toInt();
|
||||
tm.Hour = timestamp.substring(6,8).toInt();
|
||||
tm.Minute = timestamp.substring(8,10).toInt();
|
||||
tm.Second = timestamp.substring(10,12).toInt();
|
||||
meterTimestamp = makeTime(tm); // TODO: Adjust for time zone
|
||||
}
|
||||
|
||||
activeImportPower = (uint16_t) (extractDouble(payload, "1.7.0") * 1000);
|
||||
activeExportPower = (uint16_t) (extractDouble(payload, "2.7.0") * 1000);
|
||||
reactiveImportPower = (uint16_t) (extractDouble(payload, "3.7.0") * 1000);
|
||||
reactiveExportPower = (uint16_t) (extractDouble(payload, "4.7.0") * 1000);
|
||||
|
||||
if(activeImportPower > 0)
|
||||
listType = 1;
|
||||
|
||||
l1voltage = extractDouble(payload, "32.7.0");
|
||||
l2voltage = extractDouble(payload, "52.7.0");
|
||||
l3voltage = extractDouble(payload, "72.7.0");
|
||||
|
||||
l1current = extractDouble(payload, "31.7.0");
|
||||
l2current = extractDouble(payload, "51.7.0");
|
||||
l3current = extractDouble(payload, "71.7.0");
|
||||
|
||||
if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0)
|
||||
listType = 2;
|
||||
|
||||
activeImportCounter = extractDouble(payload, "1.8.0");
|
||||
activeExportCounter = extractDouble(payload, "2.8.0");
|
||||
reactiveImportCounter = extractDouble(payload, "3.8.0");
|
||||
reactiveExportCounter = extractDouble(payload, "4.8.0");
|
||||
|
||||
if(activeImportCounter > 0 || activeExportCounter > 0 || reactiveImportCounter > 0 || reactiveExportCounter > 0)
|
||||
listType = 3;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String IEC6205621::extract(String payload, String obis) {
|
||||
int a = payload.indexOf(String(":" + obis + "("));
|
||||
if(a > 0) {
|
||||
int b = payload.indexOf(")", a);
|
||||
if(b > a) {
|
||||
return payload.substring(a+obis.length()+2, b);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
double IEC6205621::extractDouble(String payload, String obis) {
|
||||
return extract(payload, obis).toDouble();
|
||||
}
|
||||
14
src/IEC6205621.h
Normal file
14
src/IEC6205621.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef _IEC62056_21_H
|
||||
#define _IEC62056_21_H
|
||||
|
||||
#include "AmsData.h"
|
||||
|
||||
class IEC6205621 : public AmsData {
|
||||
public:
|
||||
IEC6205621(String payload);
|
||||
|
||||
private:
|
||||
String extract(String payload, String obis);
|
||||
double extractDouble(String payload, String obis);
|
||||
};
|
||||
#endif
|
||||
432
src/IEC6205675.cpp
Normal file
432
src/IEC6205675.cpp
Normal file
@@ -0,0 +1,432 @@
|
||||
#include "IEC6205675.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType) {
|
||||
uint32_t u32;
|
||||
int32_t s32;
|
||||
char str[64];
|
||||
|
||||
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
|
||||
if(u32 == 0xFFFFFFFF) {
|
||||
CosemData* data = getCosemDataAt(1, ((char *) (d)));
|
||||
|
||||
// Kaifa special case...
|
||||
if(data->base.type == CosemTypeOctetString) {
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
String listId = String(str);
|
||||
if(listId.startsWith("KFM_001")) {
|
||||
this->listId = listId;
|
||||
meterType = AmsTypeKaifa;
|
||||
|
||||
int idx = 0;
|
||||
data = getCosemDataAt(idx, ((char *) (d)));
|
||||
idx+=2;
|
||||
if(data->base.length == 0x0D || data->base.length == 0x12) {
|
||||
listType = data->base.length == 0x12 ? 3 : 2;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterModel = String(str);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1current = ntohl(data->dlu.data) / 1000.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2current = ntohl(data->dlu.data) / 1000.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3current = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1voltage = ntohl(data->dlu.data) / 10.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2voltage = ntohl(data->dlu.data) / 10.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3voltage = ntohl(data->dlu.data) / 10.0;
|
||||
} else if(data->base.length == 0x09 || data->base.length == 0x0E) {
|
||||
listType = data->base.length == 0x0E ? 3 : 2;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterModel = String(str);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1current = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1voltage = ntohl(data->dlu.data) / 10.0;
|
||||
}
|
||||
|
||||
if(listType == 3) {
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
switch(data->base.type) {
|
||||
case CosemTypeOctetString: {
|
||||
if(data->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* ts = (AmsOctetTimestamp*) data;
|
||||
tmElements_t tm;
|
||||
tm.Year = ntohs(ts->year) - 1970;
|
||||
tm.Month = ts->month;
|
||||
tm.Day = ts->dayOfMonth;
|
||||
tm.Hour = ts->hour;
|
||||
tm.Minute = ts->minute;
|
||||
tm.Second = ts->second;
|
||||
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(ts->deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
}
|
||||
meterTimestamp = time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
}
|
||||
} else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
listType = 1;
|
||||
meterType = AmsTypeKaifa;
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
lastUpdateMillis = millis();
|
||||
}
|
||||
// Kaifa end
|
||||
} else {
|
||||
listType = 1;
|
||||
activeImportPower = u32;
|
||||
|
||||
meterType = AmsTypeUnknown;
|
||||
CosemData* version = findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d);
|
||||
if(version != NULL && version->base.type == CosemTypeString) {
|
||||
if(memcmp(version->str.data, "AIDON", 5) == 0) {
|
||||
meterType = AmsTypeAidon;
|
||||
} else if(memcmp(version->str.data, "Kamstrup", 8) == 0) {
|
||||
meterType = AmsTypeKamstrup;
|
||||
}
|
||||
}
|
||||
|
||||
u32 = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
listId = String(str);
|
||||
}
|
||||
|
||||
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
activeExportPower = u32;
|
||||
}
|
||||
|
||||
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
reactiveImportPower = u32;
|
||||
}
|
||||
|
||||
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
reactiveExportPower = u32;
|
||||
}
|
||||
|
||||
u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l1voltage = u32;
|
||||
}
|
||||
u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l2voltage = u32;
|
||||
}
|
||||
u32 = getUnsignedNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l3voltage = u32;
|
||||
}
|
||||
|
||||
s32 = getSignedNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d)));
|
||||
if(s32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l1current = s32;
|
||||
}
|
||||
s32 = getSignedNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d)));
|
||||
if(s32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l2current = s32;
|
||||
}
|
||||
s32 = getSignedNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d)));
|
||||
if(s32 != 0xFFFFFFFF) {
|
||||
listType = 2;
|
||||
l3current = s32;
|
||||
}
|
||||
|
||||
if(listType == 2) {
|
||||
int vdiv = 1;
|
||||
int voltage = l1voltage == 0 ? l2voltage == 0 ? l3voltage == 0 ? 0 : l3voltage : l2voltage : l1voltage;
|
||||
while(voltage > 1000) {
|
||||
vdiv *= 10;
|
||||
voltage /= 10;
|
||||
}
|
||||
|
||||
l1voltage = l1voltage != 0 ? l1voltage / vdiv : 0;
|
||||
l2voltage = l2voltage != 0 ? l2voltage / vdiv : 0;
|
||||
l3voltage = l3voltage != 0 ? l3voltage / vdiv : 0;
|
||||
|
||||
int adiv = 1;
|
||||
int watt = (l1voltage * l1current) + (l2voltage * l2current) + (l3voltage * l3current);
|
||||
while(watt / activeImportPower > 2) {
|
||||
adiv *= 10;
|
||||
watt /= 10;
|
||||
}
|
||||
|
||||
l1current = l1current != 0 ? l1current / adiv : 0;
|
||||
l2current = l2current != 0 ? l2current / adiv : 0;
|
||||
l3current = l3current != 0 ? l3current / adiv : 0;
|
||||
}
|
||||
|
||||
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
activeImportCounter = u32 / 100.0;
|
||||
}
|
||||
u32 = getUnsignedNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
activeExportCounter = u32 / 100.0;
|
||||
}
|
||||
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
reactiveImportCounter = u32 / 100.0;
|
||||
}
|
||||
u32 = getUnsignedNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d)));
|
||||
if(u32 != 0xFFFFFFFF) {
|
||||
listType = 3;
|
||||
reactiveExportCounter = u32 / 100.0;
|
||||
}
|
||||
|
||||
u32 = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
meterModel = String(str);
|
||||
} else {
|
||||
u32 = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
meterModel = String(str);
|
||||
}
|
||||
}
|
||||
|
||||
u32 = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
meterId = String(str);
|
||||
} else {
|
||||
u32 = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str);
|
||||
if(u32 > 0) {
|
||||
meterId = String(str);
|
||||
}
|
||||
}
|
||||
|
||||
time_t ts = getTimestamp(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
|
||||
if(ts > 0) {
|
||||
meterTimestamp = ts;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
|
||||
CosemData* item = (CosemData*) ptr;
|
||||
int i = 0;
|
||||
char* pos = (char*) ptr;
|
||||
do {
|
||||
item = (CosemData*) pos;
|
||||
if(i == index) return item;
|
||||
switch(item->base.type) {
|
||||
case CosemTypeArray:
|
||||
case CosemTypeStructure:
|
||||
pos += 2;
|
||||
break;
|
||||
case CosemTypeOctetString:
|
||||
case CosemTypeString:
|
||||
pos += 2 + item->base.length;
|
||||
break;
|
||||
case CosemTypeLongSigned:
|
||||
pos += 5;
|
||||
break;
|
||||
case CosemTypeLongUnsigned:
|
||||
pos += 3;
|
||||
break;
|
||||
case CosemTypeDLongUnsigned:
|
||||
pos += 5;
|
||||
break;
|
||||
case CosemTypeNull:
|
||||
return NULL;
|
||||
default:
|
||||
pos += 2;
|
||||
}
|
||||
i++;
|
||||
} while(item->base.type != HDLC_FLAG);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CosemData* IEC6205675::findObis(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = (CosemData*) ptr;
|
||||
int ret = 0;
|
||||
char* pos = (char*) ptr;
|
||||
do {
|
||||
item = (CosemData*) pos;
|
||||
if(ret == 1) return item;
|
||||
switch(item->base.type) {
|
||||
case CosemTypeArray:
|
||||
case CosemTypeStructure:
|
||||
pos += 2;
|
||||
break;
|
||||
case CosemTypeOctetString: {
|
||||
ret = 1;
|
||||
uint8_t* found = item->oct.data;
|
||||
int x = 6 - matchlength;
|
||||
for(int i = x; i < 6; i++) {
|
||||
if(found[i] != obis[i-x]) ret = 0;
|
||||
}
|
||||
} // Fallthrough
|
||||
case CosemTypeString: {
|
||||
pos += 2 + item->base.length;
|
||||
break;
|
||||
}
|
||||
case CosemTypeLongSigned:
|
||||
pos += 5;
|
||||
break;
|
||||
case CosemTypeLongUnsigned:
|
||||
pos += 3;
|
||||
break;
|
||||
case CosemTypeDLongUnsigned:
|
||||
pos += 5;
|
||||
break;
|
||||
case CosemTypeNull:
|
||||
return NULL;
|
||||
default:
|
||||
pos += 2;
|
||||
}
|
||||
} while(item->base.type != HDLC_FLAG);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t IEC6205675::getString(uint8_t* obis, int matchlength, const char* ptr, char* target) {
|
||||
CosemData* item = findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeString:
|
||||
memcpy(target, item->str.data, item->str.length);
|
||||
target[item->str.length] = 0;
|
||||
return item->str.length;
|
||||
case CosemTypeOctetString:
|
||||
memcpy(target, item->oct.data, item->oct.length);
|
||||
target[item->oct.length] = 0;
|
||||
return item->oct.length;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t IEC6205675::getSignedNumber(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongUnsigned:
|
||||
return ntohs(item->lu.data);
|
||||
case CosemTypeDLongUnsigned:
|
||||
return ntohl(item->dlu.data);
|
||||
case CosemTypeLongSigned:
|
||||
return ntohs(item->lu.data);
|
||||
}
|
||||
}
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
uint32_t IEC6205675::getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongUnsigned:
|
||||
return ntohs(item->lu.data);
|
||||
case CosemTypeDLongUnsigned:
|
||||
return ntohl(item->dlu.data);
|
||||
}
|
||||
}
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
time_t IEC6205675::getTimestamp(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeOctetString: {
|
||||
if(item->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* ts = (AmsOctetTimestamp*) item;
|
||||
tmElements_t tm;
|
||||
tm.Year = ntohs(ts->year) - 1970;
|
||||
tm.Month = ts->month;
|
||||
tm.Day = ts->dayOfMonth;
|
||||
tm.Hour = ts->hour;
|
||||
tm.Minute = ts->minute;
|
||||
tm.Second = ts->second;
|
||||
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(ts->deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
68
src/IEC6205675.h
Normal file
68
src/IEC6205675.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef _IEC62056_7_5_H
|
||||
#define _IEC62056_7_5_H
|
||||
|
||||
#include "AmsData.h"
|
||||
#include "ams/hdlc.h"
|
||||
|
||||
struct AmsOctetTimestamp {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t dayOfMonth;
|
||||
uint8_t dayOfWeek;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t hundredths;
|
||||
int16_t deviation;
|
||||
uint8_t status;
|
||||
} __attribute__((packed));
|
||||
|
||||
class IEC6205675 : public AmsData {
|
||||
public:
|
||||
IEC6205675(const char* payload, uint8_t useMeterType);
|
||||
|
||||
private:
|
||||
CosemData* getCosemDataAt(uint8_t index, const char* ptr);
|
||||
CosemData* findObis(uint8_t* obis, int matchlength, const char* ptr);
|
||||
uint8_t getString(uint8_t* obis, int matchlength, const char* ptr, char* target);
|
||||
uint32_t getSignedNumber(uint8_t* obis, int matchlength, const char* ptr);
|
||||
uint32_t getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr);
|
||||
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
|
||||
|
||||
uint8_t AMS_OBIS_VERSION[6] = { 1, 1, 0, 2, 129, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL[4] = { 96, 1, 1, 255 };
|
||||
uint8_t AMS_OBIS_METER_MODEL_2[4] = { 96, 1, 7, 255 };
|
||||
uint8_t AMS_OBIS_METER_ID[4] = { 96, 1, 0, 255 };
|
||||
uint8_t AMS_OBIS_METER_ID_2[4] = { 0, 0, 5, 255 };
|
||||
uint8_t AMS_OBIS_METER_TIMESTAMP[4] = { 1, 0, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT[4] = { 1, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L1[4] = { 21, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2[4] = { 41, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3[4] = { 61, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT[4] = { 2, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1[4] = { 22, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2[4] = { 42, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3[4] = { 62, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT[4] = { 3, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_L1[4] = { 23, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_L2[4] = { 43, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_L3[4] = { 63, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT[4] = { 4, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_L1[4] = { 24, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_L2[4] = { 44, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_L3[4] = { 64, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT[4] = { 11, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT_L1[4] = { 31, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT_L2[4] = { 51, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_CURRENT_L3[4] = { 71, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE[4] = { 12, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE_L1[4] = { 32, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE_L2[4] = { 52, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_VOLTAGE_L3[4] = { 72, 7, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_IMPORT_COUNT[4] = { 1, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_ACTIVE_EXPORT_COUNT[4] = { 2, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_IMPORT_COUNT[4] = { 3, 8, 0, 255 };
|
||||
uint8_t AMS_OBIS_REACTIVE_EXPORT_COUNT[4] = { 4, 8, 0, 255 };
|
||||
|
||||
};
|
||||
#endif
|
||||
119
src/ams/ams.cpp
119
src/ams/ams.cpp
@@ -1,119 +0,0 @@
|
||||
#include "ams.h"
|
||||
#include <string.h>
|
||||
#include "lwip/def.h"
|
||||
#include "Time.h"
|
||||
|
||||
time_t AMS_getTimestamp(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = AMS_findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeOctetString: {
|
||||
if(item->oct.length == 0x0C) {
|
||||
AmsOctetTimestamp* ts = (AmsOctetTimestamp*) item;
|
||||
tmElements_t tm;
|
||||
tm.Year = ntohs(ts->year) - 1970;
|
||||
tm.Month = ts->month;
|
||||
tm.Day = ts->dayOfMonth;
|
||||
tm.Hour = ts->hour;
|
||||
tm.Minute = ts->minute;
|
||||
tm.Second = ts->second;
|
||||
|
||||
time_t time = makeTime(tm);
|
||||
int16_t deviation = ntohs(ts->deviation);
|
||||
if(deviation >= -720 && deviation <= 720) {
|
||||
time -= deviation * 60;
|
||||
}
|
||||
return time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t AMS_getString(uint8_t* obis, int matchlength, const char* ptr, char* target) {
|
||||
CosemData* item = AMS_findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeString:
|
||||
memcpy(target, item->str.data, item->str.length);
|
||||
target[item->str.length] = 0;
|
||||
return item->str.length;
|
||||
case CosemTypeOctetString:
|
||||
memcpy(target, item->oct.data, item->oct.length);
|
||||
target[item->oct.length] = 0;
|
||||
return item->oct.length;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t AMS_getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = AMS_findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongUnsigned:
|
||||
return ntohs(item->lu.data);
|
||||
case CosemTypeDLongUnsigned:
|
||||
return ntohl(item->dlu.data);
|
||||
}
|
||||
}
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
int32_t AMS_getSignedNumber(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = AMS_findObis(obis, matchlength, ptr);
|
||||
if(item != NULL) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongUnsigned:
|
||||
return ntohs(item->lu.data);
|
||||
case CosemTypeDLongUnsigned:
|
||||
return ntohl(item->dlu.data);
|
||||
case CosemTypeLongSigned:
|
||||
return ntohs(item->lu.data);
|
||||
}
|
||||
}
|
||||
return 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
CosemData* AMS_findObis(uint8_t* obis, int matchlength, const char* ptr) {
|
||||
CosemData* item = (CosemData*) ptr;
|
||||
int ret = 0;
|
||||
char* pos = (char*) ptr;
|
||||
do {
|
||||
item = (CosemData*) pos;
|
||||
if(ret == 1) return item;
|
||||
switch(item->base.type) {
|
||||
case CosemTypeArray:
|
||||
case CosemTypeStructure:
|
||||
pos += 2;
|
||||
break;
|
||||
case CosemTypeOctetString: {
|
||||
ret = 1;
|
||||
uint8_t* found = item->oct.data;
|
||||
int x = 6 - matchlength;
|
||||
for(int i = x; i < 6; i++) {
|
||||
if(found[i] != obis[i-x]) ret = 0;
|
||||
}
|
||||
} // Fallthrough
|
||||
case CosemTypeString: {
|
||||
pos += 2 + item->base.length;
|
||||
break;
|
||||
}
|
||||
case CosemTypeLongSigned:
|
||||
pos += 5;
|
||||
break;
|
||||
case CosemTypeLongUnsigned:
|
||||
pos += 3;
|
||||
break;
|
||||
case CosemTypeDLongUnsigned:
|
||||
pos += 5;
|
||||
break;
|
||||
case CosemTypeNull:
|
||||
return NULL;
|
||||
default:
|
||||
pos += 2;
|
||||
}
|
||||
} while(item->base.type != HDLC_FLAG);
|
||||
return NULL;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef _AMS_H
|
||||
#define _AMS_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "hdlc.h"
|
||||
|
||||
struct AmsOctetTimestamp {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t dayOfMonth;
|
||||
uint8_t dayOfWeek;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t hundredths;
|
||||
int16_t deviation;
|
||||
uint8_t status;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
CosemData* AMS_findObis(uint8_t* obis, int matchlength, const char* ptr);
|
||||
uint32_t AMS_getUnsignedNumber(uint8_t* obis, int matchlength, const char* ptr);
|
||||
int32_t AMS_getSignedNumber(uint8_t* obis, int matchlength, const char* ptr);
|
||||
uint8_t AMS_getString(uint8_t* obis, int matchlength, const char* ptr, char* target);
|
||||
time_t AMS_getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
|
||||
|
||||
#endif
|
||||
@@ -121,8 +121,8 @@ int HDLC_validate(const uint8_t* d, int len, HDLCConfig* config) {
|
||||
if (0 != success ) {
|
||||
return -92;
|
||||
}
|
||||
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), initialization_vector, sizeof(initialization_vector),
|
||||
additional_authenticated_data, sizeof(additional_authenticated_data), authentication_tag, sizeof(authentication_tag),
|
||||
success = mbedtls_gcm_auth_decrypt(&m_ctx, sizeof(cipher_text), config->initialization_vector, sizeof(config->initialization_vector),
|
||||
config->additional_authenticated_data, sizeof(config->additional_authenticated_data), config->authentication_tag, sizeof(config->authentication_tag),
|
||||
cipher_text, (unsigned char*)(d + headersize + 18));
|
||||
if (0 != success) {
|
||||
return -91;
|
||||
|
||||
@@ -399,9 +399,21 @@ void AmsWebServer::configMeterHtml() {
|
||||
case AmsTypeAidon:
|
||||
manufacturer = "Aidon";
|
||||
break;
|
||||
case AmsTypeKaifa:
|
||||
manufacturer = "Kaifa";
|
||||
break;
|
||||
case AmsTypeKamstrup:
|
||||
manufacturer = "Kamstrup";
|
||||
break;
|
||||
case AmsTypeIskra:
|
||||
manufacturer = "Iskra";
|
||||
break;
|
||||
case AmsTypeLandis:
|
||||
manufacturer = "Landis + Gyro";
|
||||
break;
|
||||
case AmsTypeSagemcom:
|
||||
manufacturer = "Sagemcom";
|
||||
break;
|
||||
default:
|
||||
manufacturer = "Unknown";
|
||||
break;
|
||||
@@ -412,9 +424,12 @@ void AmsWebServer::configMeterHtml() {
|
||||
html.replace("{mid}", meterState->getMeterId());
|
||||
html.replace("{b}", String(meterConfig->baud));
|
||||
html.replace("{b2400}", meterConfig->baud == 2400 ? "selected" : "");
|
||||
html.replace("{b9600}", meterConfig->baud == 9600 ? "selected" : "");
|
||||
html.replace("{b115200}", meterConfig->baud == 115200 ? "selected" : "");
|
||||
html.replace("{c}", String(meterConfig->baud));
|
||||
html.replace("{c2}", meterConfig->parity == 2 ? "selected" : "");
|
||||
html.replace("{c3}", meterConfig->parity == 3 ? "selected" : "");
|
||||
html.replace("{c10}", meterConfig->parity == 10 ? "selected" : "");
|
||||
html.replace("{c11}", meterConfig->parity == 11 ? "selected" : "");
|
||||
html.replace("{i}", meterConfig->invert ? "checked" : "");
|
||||
html.replace("{d}", String(meterConfig->distributionSystem));
|
||||
@@ -715,7 +730,7 @@ void AmsWebServer::dataJson() {
|
||||
mqttStatus = 3;
|
||||
}
|
||||
|
||||
char json[290];
|
||||
char json[300];
|
||||
snprintf_P(json, sizeof(json), DATA_JSON,
|
||||
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
||||
meterConfig->productionCapacity,
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
</div>
|
||||
<select class="form-control sd" name="b">
|
||||
<option value="2400" {b2400}>2400</option>
|
||||
<option value="9600" {b9600}>9600</option>
|
||||
<option value="115200" {b115200}>115200</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -46,7 +47,9 @@
|
||||
<span class="input-group-text">Parity</span>
|
||||
</div>
|
||||
<select class="form-control sd" name="c">
|
||||
<option value="2" {c2}>7N1</option>
|
||||
<option value="3" {c3}>8N1</option>
|
||||
<option value="10" {c10}>7E1</option>
|
||||
<option value="11" {c11}>8E1</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user