Restructuring to be able to include precompiled kmp lib

This commit is contained in:
Gunnar Skjold
2024-07-11 13:15:22 +02:00
parent 4407526d96
commit 8a4efd0047
32 changed files with 105 additions and 25 deletions

View File

@@ -0,0 +1,24 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#ifndef _IEC62056_21_H
#define _IEC62056_21_H
#include "Arduino.h"
#include "AmsData.h"
#include "Timezone.h"
#include "AmsConfiguration.h"
class IEC6205621 : public AmsData {
public:
IEC6205621(const char* payload, Timezone* tz, MeterConfig* meterConfig);
private:
String extract(String payload, String obis);
double extractDouble(String payload, String obis);
float extractFloat(String payload, String obis);
};
#endif

View File

@@ -0,0 +1,74 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#ifndef _IEC62056_7_5_H
#define _IEC62056_7_5_H
#include "AmsData.h"
#include "AmsConfiguration.h"
#include "DataParser.h"
#include "Cosem.h"
#define NOVALUE 0xFFFFFFFF
struct AmsOctetTimestamp {
uint8_t type;
CosemDateTime dt;
} __attribute__((packed));
class IEC6205675 : public AmsData {
public:
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state);
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);
float getNumber(uint8_t* obis, int matchlength, const char* ptr);
float getNumber(CosemData*);
time_t getTimestamp(uint8_t* obis, int matchlength, const char* ptr);
uint8_t AMS_OBIS_UNKNOWN_1[4] = { 25, 9, 0, 255 };
uint8_t AMS_OBIS_VERSION[4] = { 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_EXPORT[4] = { 2, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_IMPORT[4] = { 3, 7, 0, 255 };
uint8_t AMS_OBIS_REACTIVE_EXPORT[4] = { 4, 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_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 };
uint8_t AMS_OBIS_POWER_FACTOR[4] = { 13, 7, 0, 255 };
uint8_t AMS_OBIS_POWER_FACTOR_L1[4] = { 33, 7, 0, 255 };
uint8_t AMS_OBIS_POWER_FACTOR_L2[4] = { 53, 7, 0, 255 };
uint8_t AMS_OBIS_POWER_FACTOR_L3[4] = { 73, 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_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_ACTIVE_IMPORT_L1_COUNT[4] = { 21, 8, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L2_COUNT[4] = { 41, 8, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_IMPORT_L3_COUNT[4] = { 61, 8, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L1_COUNT[4] = { 22, 8, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L2_COUNT[4] = { 42, 8, 0, 255 };
uint8_t AMS_OBIS_ACTIVE_EXPORT_L3_COUNT[4] = { 62, 8, 0, 255 };
};
#endif

View File

@@ -0,0 +1,12 @@
#ifndef _IMPULSEAMSDATA_H
#define _IMPULSEAMSDATA_H
#include "AmsData.h"
class ImpulseAmsData : public AmsData {
public:
ImpulseAmsData(AmsData &state, uint16_t pulsePerKwh, uint8_t pulses);
ImpulseAmsData(double activeImportCounter);
};
#endif

View File

@@ -0,0 +1,35 @@
/**
* @copyright Utilitech AS 2024
* License: Fair Source
*
*/
#pragma once
#include "PassiveMeterCommunicator.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsConfiguration.h"
#include "Timezone.h"
#include "ImpulseAmsData.h"
#if defined(ESP8266)
#include "SoftwareSerial.h"
#endif
#include "KmpTalker.h"
class KmpCommunicator : public PassiveMeterCommunicator {
public:
#if defined(AMS_REMOTE_DEBUG)
KmpCommunicator(RemoteDebug* debugger) : PassiveMeterCommunicator(debugger) {};
#else
KmpCommunicator(Stream* debugger) : PassiveMeterCommunicator(debugger) {};
#endif
void configure(MeterConfig&);
bool loop();
AmsData* getData(AmsData& meterState);
private:
KmpTalker* talker = NULL;
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include <Stream.h>
struct KmpDataHolder {
uint32_t activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
float l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
uint32_t l1activeImportPower = 0, l2activeImportPower = 0, l3activeImportPower = 0;
uint32_t l1activeExportPower = 0, l2activeExportPower = 0, l3activeExportPower = 0;
double l1activeImportCounter = 0, l2activeImportCounter = 0, l3activeImportCounter = 0;
double l1activeExportCounter = 0, l2activeExportCounter = 0, l3activeExportCounter = 0;
float powerFactor = 0, l1PowerFactor = 0, l2PowerFactor = 0, l3PowerFactor = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
uint16_t meterId;
};
class KmpTalker {
public:
KmpTalker(Stream *hanSerial);
bool loop();
void getData(KmpDataHolder& data);
};

View File

@@ -0,0 +1,37 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#ifndef _LNG_H
#define _LNG_H
#include "AmsData.h"
#include "AmsConfiguration.h"
#include "DataParser.h"
#include "Cosem.h"
struct LngHeader {
uint8_t tag;
uint8_t values;
uint8_t arrayTag;
uint8_t arrayLength;
} __attribute__((packed));
struct LngObisDescriptor {
uint8_t ignore1[5];
uint8_t octetTag;
uint8_t octetLength;
uint8_t obis[6];
uint8_t ignore2[5];
} __attribute__((packed));
class LNG : public AmsData {
public:
LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
uint64_t getNumber(CosemData* item);
};
#endif

View File

@@ -0,0 +1,42 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#ifndef _LNG2_H
#define _LNG2_H
#include "AmsData.h"
#include "AmsConfiguration.h"
#include "DataParser.h"
#include "Cosem.h"
struct Lng2Data_3p {
CosemBasic header;
CosemLongUnsigned u1;
CosemLongUnsigned u2;
CosemLongUnsigned u3;
CosemLongUnsigned i1;
CosemLongUnsigned i2;
CosemLongUnsigned i3;
CosemDLongUnsigned activeImport;
CosemDLongUnsigned activeExport;
CosemDLongUnsigned acumulatedImport;
CosemDLongUnsigned accumulatedExport;
CosemLongUnsigned x;
CosemLongUnsigned y;
CosemLongUnsigned z;
CosemString meterId;
} __attribute__((packed));
class LNG2 : public AmsData {
public:
LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
private:
uint8_t getString(CosemData* item, char* target);
};
#endif

View File

@@ -0,0 +1,28 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#ifndef _METERCOMMUNICATOR_H
#define _METERCOMMUNICATOR_H
#include <Arduino.h>
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsData.h"
#include "AmsConfiguration.h"
class MeterCommunicator {
public:
virtual ~MeterCommunicator() {};
virtual void configure(MeterConfig&, Timezone*);
virtual bool loop();
virtual AmsData* getData(AmsData& meterState);
virtual int getLastError();
virtual bool isConfigChanged();
virtual void getCurrentConfig(MeterConfig& meterConfig);
};
#endif

View File

@@ -0,0 +1,96 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#ifndef _PASSIVEMETERCOMMUNICATOR_H
#define _PASSIVEMETERCOMMUNICATOR_H
#include "MeterCommunicator.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsConfiguration.h"
#include "DataParsers.h"
#include "Timezone.h"
#include "PassthroughMqttHandler.h"
#if defined(ESP8266)
#include "SoftwareSerial.h"
#endif
const uint32_t AUTO_BAUD_RATES[] = { 2400, 115200 };
class PassiveMeterCommunicator : public MeterCommunicator {
public:
#if defined(AMS_REMOTE_DEBUG)
PassiveMeterCommunicator(RemoteDebug* debugger);
#else
PassiveMeterCommunicator(Stream* debugger);
#endif
void configure(MeterConfig&, Timezone*);
bool loop();
AmsData* getData(AmsData& meterState);
int getLastError();
bool isConfigChanged();
void getCurrentConfig(MeterConfig& meterConfig);
void setPassthroughMqttHandler(PassthroughMqttHandler*);
HardwareSerial* getHwSerial();
void rxerr(int err);
protected:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger = NULL;
#else
Stream* debugger = NULL;
#endif
MeterConfig meterConfig;
bool configChanged = false;
Timezone* tz;
PassthroughMqttHandler* pt = NULL;
uint8_t *hanBuffer = NULL;
uint16_t hanBufferSize = 0;
Stream *hanSerial;
#if defined(ESP8266)
SoftwareSerial *swSerial = NULL;
#endif
HardwareSerial *hwSerial = NULL;
uint8_t rxBufferErrors = 0;
bool autodetect = false, validDataReceived = false;
unsigned long meterAutodetectLastChange = 0;
long rate = 10000;
uint32_t autodetectBaud = 0;
uint8_t autodetectParity = 11;
bool autodetectInvert = false;
uint8_t autodetectCount = 0;
bool dataAvailable = false;
int len = 0;
int pos = DATA_PARSE_INCOMPLETE;
int lastError = DATA_PARSE_OK;
bool serialInit = false;
bool maxDetectPayloadDetectDone = false;
uint8_t maxDetectedPayloadSize = 64;
DataParserContext ctx = {0,0,0,0};
HDLCParser *hdlcParser = NULL;
MBUSParser *mbusParser = NULL;
GBTParser *gbtParser = NULL;
GCMParser *gcmParser = NULL;
LLCParser *llcParser = NULL;
DLMSParser *dlmsParser = NULL;
DSMRParser *dsmrParser = NULL;
void setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive = true);
int16_t unwrapData(uint8_t *buf, DataParserContext &context);
void debugPrint(byte *buffer, int start, int length);
void printHanReadError(int pos);
void handleAutodetect(unsigned long now);
};
#endif

View File

@@ -0,0 +1,51 @@
/**
* @copyright Utilitech AS 2024
* License: Fair Source
*
*/
#ifndef _PULSEMETERCOMMUNICATOR_H
#define _PULSEMETERCOMMUNICATOR_H
#include "MeterCommunicator.h"
#if defined(AMS_REMOTE_DEBUG)
#include "RemoteDebug.h"
#endif
#include "AmsConfiguration.h"
#include "Timezone.h"
#include "ImpulseAmsData.h"
class PulseMeterCommunicator : public MeterCommunicator {
public:
#if defined(AMS_REMOTE_DEBUG)
PulseMeterCommunicator(RemoteDebug* debugger);
#else
PulseMeterCommunicator(Stream* debugger);
#endif
void configure(MeterConfig& config, Timezone* tz);
bool loop();
AmsData* getData(AmsData& meterState);
int getLastError();
bool isConfigChanged();
void getCurrentConfig(MeterConfig& meterConfig);
void onPulse(uint8_t pulses);
protected:
#if defined(AMS_REMOTE_DEBUG)
RemoteDebug* debugger = NULL;
#else
Stream* debugger = NULL;
#endif
MeterConfig meterConfig;
bool configChanged = false;
Timezone* tz;
bool updated = false;
bool initialized = false;
AmsData state;
uint64_t lastUpdate = 0;
void setupGpio();
};
#endif

View File

@@ -0,0 +1,204 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "IEC6205621.h"
#include "Uptime.h"
IEC6205621::IEC6205621(const char* p, Timezone* tz, MeterConfig* meterConfig) {
if(strlen(p) < 16)
return;
String payload(p+1);
lastUpdateMillis = millis64();
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
if(listId.startsWith(F("ADN"))) {
meterType = AmsTypeAidon;
listId = listId.substring(0,4);
} else if(listId.startsWith(F("KFM"))) {
meterType = AmsTypeKaifa;
listId = listId.substring(0,4);
} else if(listId.startsWith(F("KMP"))) {
meterType = AmsTypeKamstrup;
listId = listId.substring(0,4);
} else if(listId.startsWith(F("KAM"))) {
meterType = AmsTypeKamstrup;
listId = listId.substring(0,4);
} else if(listId.startsWith(F("ISk"))) {
meterType = AmsTypeIskra;
listId = listId.substring(0,5);
} else if(listId.startsWith(F("XMX"))) {
meterType = AmsTypeLandisGyr;
listId = listId.substring(0,6);
} else if(listId.startsWith(F("Ene")) || listId.startsWith(F("EST"))) {
meterType = AmsTypeSagemcom;
listId = listId.substring(0,4);
} else if(listId.startsWith(F("LGF"))) {
meterType = AmsTypeLandisGyr;
listId = listId.substring(0,4);
} else {
meterType = AmsTypeUnknown;
listId = listId.substring(0,4);
}
meterId = extract(payload, F("96.1.0"));
if(meterId.isEmpty()) {
meterId = extract(payload, F("0.0.5"));
}
meterModel = extract(payload, F("96.1.1"));
if(meterModel.isEmpty()) {
meterModel = extract(payload, F("96.1.7"));
if(meterModel.isEmpty()) {
meterModel = payload.substring(payload.indexOf(listId) + listId.length(), payload.indexOf(F("\n")));
meterModel.trim();
}
}
String timestamp = extract(payload, F("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);
if(tz != NULL) meterTimestamp = tz->toUTC(meterTimestamp);
}
activeImportPower = (uint16_t) (extractDouble(payload, F("1.7.0")));
activeExportPower = (uint16_t) (extractDouble(payload, F("2.7.0")));
reactiveImportPower = (uint16_t) (extractDouble(payload, F("3.7.0")));
reactiveExportPower = (uint16_t) (extractDouble(payload, F("4.7.0")));
if(activeImportPower > 0)
listType = 1;
l1voltage = extractFloat(payload, F("32.7.0"));
l2voltage = extractFloat(payload, F("52.7.0"));
l3voltage = extractFloat(payload, F("72.7.0"));
l1current = extractFloat(payload, F("31.7.0"));
l2current = extractFloat(payload, F("51.7.0"));
l3current = extractFloat(payload, F("71.7.0"));
l1activeImportPower = extractFloat(payload, F("21.7.0"));
l2activeImportPower = extractFloat(payload, F("41.7.0"));
l3activeImportPower = extractFloat(payload, F("61.7.0"));
l1activeExportPower = extractFloat(payload, F("22.7.0"));
l2activeExportPower = extractFloat(payload, F("42.7.0"));
l3activeExportPower = extractFloat(payload, F("62.7.0"));
if(l1voltage > 0 || l2voltage > 0 || l3voltage > 0)
listType = 2;
double val = 0.0;
val = extractDouble(payload, F("1.8.0"));
if(val == 0) {
for(int i = 1; i < 9; i++) {
val += extractDouble(payload, "1.8." + String(i,10));
}
}
if(val > 0) activeImportCounter = val / 1000;
val = extractDouble(payload, F("2.8.0"));
if(val == 0) {
for(int i = 1; i < 9; i++) {
val += extractDouble(payload, "2.8." + String(i,10));
}
}
if(val > 0) activeExportCounter = val / 1000;
val = extractDouble(payload, F("3.8.0"));
if(val == 0) {
for(int i = 1; i < 9; i++) {
val += extractDouble(payload, "3.8." + String(i,10));
}
}
if(val > 0) reactiveImportCounter = val / 1000;
val = extractDouble(payload, F("4.8.0"));
if(val == 0) {
for(int i = 1; i < 9; i++) {
val += extractDouble(payload, "4.8." + String(i,10));
}
}
if(val > 0) reactiveExportCounter = val / 1000;
if(activeImportCounter > 0 || activeExportCounter > 0 || reactiveImportCounter > 0 || reactiveExportCounter > 0)
listType = 3;
if (l1activeImportPower > 0 || l2activeImportPower > 0 || l3activeImportPower > 0 || l1activeExportPower > 0 || l2activeExportPower > 0 || l3activeExportPower > 0)
listType = 4;
if(meterConfig->wattageMultiplier > 0) {
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
if(meterConfig->voltageMultiplier > 0) {
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
}
if(meterConfig->amperageMultiplier > 0) {
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
}
if(meterConfig->accumulatedMultiplier > 0) {
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
}
String IEC6205621::extract(String payload, String obis) {
int a = payload.indexOf(String(":" + obis + "("));
if(a > 0) {
int b = payload.indexOf(F(")"), a);
if(b > a) {
return payload.substring(a+obis.length()+2, b);
}
}
return "";
}
double IEC6205621::extractDouble(String payload, String obis) {
String str = extract(payload, obis);
if(str.isEmpty()) {
return 0.0;
}
int a = str.indexOf(F("*"));
String val = str.substring(0,a);
String unit = str.substring(a+1);
return unit.startsWith(F("k")) ? val.toDouble() * 1000 : val.toDouble();
}
float IEC6205621::extractFloat(String payload, String obis) {
String str = extract(payload, obis);
if(str.isEmpty()) {
return 0.0;
}
int a = str.indexOf(F("*"));
String val = str.substring(0,a);
String unit = str.substring(a+1);
return unit.startsWith(F("k")) ? val.toFloat() * 1000 : val.toFloat();
}

View File

@@ -0,0 +1,763 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "IEC6205675.h"
#include "lwip/def.h"
#include "Timezone.h"
#include "ntohll.h"
#include "Uptime.h"
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state) {
float val;
char str[64];
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
Timezone tz(CEST, CET);
this->packageTimestamp = ctx.timestamp;
val = getNumber(AMS_OBIS_ACTIVE_IMPORT, sizeof(AMS_OBIS_ACTIVE_IMPORT), ((char *) (d)));
if(val == NOVALUE) {
CosemData* data = getCosemDataAt(1, ((char *) (d)));
// Kaifa special case...
if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
listType = 1;
meterType = AmsTypeKaifa;
activeImportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
} else if(data->base.type == CosemTypeOctetString) {
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
String listId = String(str);
if(listId.startsWith(F("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 >= 2 && memcmp(meterModel.c_str(), "MA304T3", 7) == 0) {
l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60 * (PI/180)), 2) + pow(l3voltage * sin(60 * (PI/180)),2));
l2currentMissing = true;
}
if(listType == 3) {
data = getCosemDataAt(idx++, ((char *) (d)));
switch(data->base.type) {
case CosemTypeOctetString: {
if(data->oct.length == 0x0C) {
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) data;
time_t ts = decodeCosemDateTime(amst->dt);
meterTimestamp = tz.toUTC(ts);
}
}
}
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 = millis64();
} else if(listId.startsWith("ISK")) { // Iskra special case
this->listId = listId;
meterType = AmsTypeIskra;
int idx = 0;
data = getCosemDataAt(idx++, ((char *) (d)));
if(data->base.length == 0x12) {
listType = 2;
idx++;
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)));
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)));
l1voltage = ntohs(data->lu.data) / 10.0;
data = getCosemDataAt(idx++, ((char *) (d)));
l2voltage = ntohs(data->lu.data) / 10.0;
data = getCosemDataAt(idx++, ((char *) (d)));
l3voltage = ntohs(data->lu.data) / 10.0;
data = getCosemDataAt(idx++, ((char *) (d)));
l1current = ntohs(data->lu.data) / 100.0;
data = getCosemDataAt(idx++, ((char *) (d)));
l2current = ntohs(data->lu.data) / 100.0;
data = getCosemDataAt(idx++, ((char *) (d)));
l3current = ntohs(data->lu.data) / 100.0;
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeImportPower = ntohl(data->dlu.data);
data = getCosemDataAt(idx++, ((char *) (d)));
l2activeImportPower = ntohl(data->dlu.data);
data = getCosemDataAt(idx++, ((char *) (d)));
l3activeImportPower = ntohl(data->dlu.data);
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeExportPower = ntohl(data->dlu.data);
data = getCosemDataAt(idx++, ((char *) (d)));
l2activeExportPower = ntohl(data->dlu.data);
data = getCosemDataAt(idx++, ((char *) (d)));
l3activeExportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
} else if(data->base.length == 0x0C) {
apply(state);
listType = 3;
idx += 4;
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
idx += 2;
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
idx += 2;
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 = millis64();
}
} else if(useMeterType == AmsTypeIskra && data->base.type == CosemTypeOctetString) { // Iskra special case
meterType = AmsTypeIskra;
uint8_t idx = 5;
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportPower = ntohl(data->dlu.data);
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportPower = ntohl(data->dlu.data);
}
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
if(str_len > 0) {
meterId = String(str);
}
listType = 3;
lastUpdateMillis = millis64();
} else if(useMeterType == AmsTypeUnknown) {
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
if(str_len > 0) {
meterType = AmsTypeIskra;
meterId = String(str);
lastUpdateMillis = millis64();
listType = 3;
}
}
}
} else {
listType = 1;
activeImportPower = val;
meterType = AmsTypeUnknown;
CosemData* version = findObis(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), d);
if(version != NULL && (version->base.type == CosemTypeString || version->base.type == CosemTypeOctetString)) {
if(memcmp(version->str.data, "AIDON", 5) == 0) {
meterType = AmsTypeAidon;
} else if(memcmp(version->str.data, "Kamstrup", 8) == 0) {
meterType = AmsTypeKamstrup;
} else if(memcmp(version->str.data, "KFM", 3) == 0) {
meterType = AmsTypeKaifa;
}
} else {
version = getCosemDataAt(1, ((char *) (d)));
if(version->base.type == CosemTypeString) {
if(memcmp(version->str.data, "Kamstrup", 8) == 0) {
meterType = AmsTypeKamstrup;
}
}
}
// Try system title
if(meterType == AmsTypeUnknown) {
if(memcmp(ctx.system_title, "SAGY", 4) == 0) {
meterType = AmsTypeSagemcom;
} else if(memcmp(ctx.system_title, "KFM", 3) == 0) {
meterType = AmsTypeKaifa;
}
}
if(this->packageTimestamp > 0) {
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
this->packageTimestamp = this->packageTimestamp - 3600;
}
}
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_VERSION, sizeof(AMS_OBIS_VERSION), ((char *) (d)), str);
if(str_len > 0) {
listId = String(str);
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT, sizeof(AMS_OBIS_ACTIVE_EXPORT), ((char *) (d)));
if(val != NOVALUE) {
activeExportPower = val;
}
val = getNumber(AMS_OBIS_REACTIVE_IMPORT, sizeof(AMS_OBIS_REACTIVE_IMPORT), ((char *) (d)));
if(val != NOVALUE) {
reactiveImportPower = val;
}
val = getNumber(AMS_OBIS_REACTIVE_EXPORT, sizeof(AMS_OBIS_REACTIVE_EXPORT), ((char *) (d)));
if(val != NOVALUE) {
reactiveExportPower = val;
}
val = getNumber(AMS_OBIS_VOLTAGE_L1, sizeof(AMS_OBIS_VOLTAGE_L1), ((char *) (d)));
if(val != NOVALUE) {
listType = 2;
l1voltage = val;
}
val = getNumber(AMS_OBIS_VOLTAGE_L2, sizeof(AMS_OBIS_VOLTAGE_L2), ((char *) (d)));
if(val != NOVALUE) {
listType = 2;
l2voltage = val;
}
val = getNumber(AMS_OBIS_VOLTAGE_L3, sizeof(AMS_OBIS_VOLTAGE_L3), ((char *) (d)));
if(val != NOVALUE) {
listType = 2;
l3voltage = val;
}
val = getNumber(AMS_OBIS_CURRENT_L1, sizeof(AMS_OBIS_CURRENT_L1), ((char *) (d)));
if(val != NOVALUE) {
listType = 2;
l1current = val;
}
val = getNumber(AMS_OBIS_CURRENT_L2, sizeof(AMS_OBIS_CURRENT_L2), ((char *) (d)));
if(val != NOVALUE) {
listType = 2;
l2current = val;
} else if(listType == 2) {
l2currentMissing = true;
}
val = getNumber(AMS_OBIS_CURRENT_L3, sizeof(AMS_OBIS_CURRENT_L3), ((char *) (d)));
if(val != NOVALUE) {
listType = 2;
l3current = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_COUNT), ((char *) (d)));
if(val != NOVALUE) {
listType = 3;
activeImportCounter = val / 1000.0;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_COUNT), ((char *) (d)));
if(val != NOVALUE) {
listType = 3;
activeExportCounter = val / 1000.0;
}
val = getNumber(AMS_OBIS_REACTIVE_IMPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_IMPORT_COUNT), ((char *) (d)));
if(val != NOVALUE) {
listType = 3;
reactiveImportCounter = val / 1000.0;
}
val = getNumber(AMS_OBIS_REACTIVE_EXPORT_COUNT, sizeof(AMS_OBIS_REACTIVE_EXPORT_COUNT), ((char *) (d)));
if(val != NOVALUE) {
listType = 3;
reactiveExportCounter = val / 1000.0;
}
str_len = getString(AMS_OBIS_METER_MODEL, sizeof(AMS_OBIS_METER_MODEL), ((char *) (d)), str);
if(str_len > 0) {
meterModel = String(str);
} else {
str_len = getString(AMS_OBIS_METER_MODEL_2, sizeof(AMS_OBIS_METER_MODEL_2), ((char *) (d)), str);
if(str_len > 0) {
meterModel = String(str);
}
}
str_len = getString(AMS_OBIS_METER_ID, sizeof(AMS_OBIS_METER_ID), ((char *) (d)), str);
if(str_len > 0) {
meterId = String(str);
} else {
str_len = getString(AMS_OBIS_METER_ID_2, sizeof(AMS_OBIS_METER_ID_2), ((char *) (d)), str);
if(str_len > 0) {
meterId = String(str);
}
}
CosemData* meterTs = findObis(AMS_OBIS_METER_TIMESTAMP, sizeof(AMS_OBIS_METER_TIMESTAMP), ((char *) (d)));
if(meterTs != NULL) {
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
time_t ts = decodeCosemDateTime(amst->dt);
if(meterType == AmsTypeAidon || meterType == AmsTypeKamstrup) {
meterTimestamp = ts - 3600;
} else {
meterTimestamp = ts;
}
}
val = getNumber(AMS_OBIS_POWER_FACTOR, sizeof(AMS_OBIS_POWER_FACTOR), ((char *) (d)));
if(val != NOVALUE) {
listType = 4;
powerFactor = val;
}
val = getNumber(AMS_OBIS_POWER_FACTOR_L1, sizeof(AMS_OBIS_POWER_FACTOR_L1), ((char *) (d)));
if(val != NOVALUE) {
listType = 4;
l1PowerFactor = val;
}
val = getNumber(AMS_OBIS_POWER_FACTOR_L2, sizeof(AMS_OBIS_POWER_FACTOR_L2), ((char *) (d)));
if(val != NOVALUE) {
listType = 4;
l2PowerFactor = val;
}
val = getNumber(AMS_OBIS_POWER_FACTOR_L3, sizeof(AMS_OBIS_POWER_FACTOR_L3), ((char *) (d)));
if(val != NOVALUE) {
listType = 4;
l3PowerFactor = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L1, sizeof(AMS_OBIS_ACTIVE_IMPORT_L1), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l1activeImportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L2, sizeof(AMS_OBIS_ACTIVE_IMPORT_L2), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l2activeImportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L3, sizeof(AMS_OBIS_ACTIVE_IMPORT_L3), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l3activeImportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L1, sizeof(AMS_OBIS_ACTIVE_EXPORT_L1), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l1activeExportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l2activeExportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L3, sizeof(AMS_OBIS_ACTIVE_EXPORT_L3), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l3activeExportPower = val;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L1_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L1_COUNT), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l1activeImportCounter = val/1000;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L2_COUNT), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l2activeImportCounter = val/1000;
}
val = getNumber(AMS_OBIS_ACTIVE_IMPORT_L3_COUNT, sizeof(AMS_OBIS_ACTIVE_IMPORT_L3_COUNT), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l3activeImportCounter = val/1000;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L1_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L1_COUNT), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l1activeExportCounter = val/1000;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l2activeExportCounter = val/1000;
}
val = getNumber(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT, sizeof(AMS_OBIS_ACTIVE_EXPORT_L2_COUNT), ((char *) (d)));
if (val != NOVALUE) {
listType = 4;
l3activeExportCounter = val/1000;
}
if(meterType == AmsTypeKamstrup) {
if(listType >= 3) {
activeImportCounter *= 10;
activeExportCounter *= 10;
reactiveImportCounter *= 10;
reactiveExportCounter *= 10;
l1activeImportCounter *= 10;
l2activeImportCounter *= 10;
l3activeImportCounter *= 10;
l1activeExportCounter *= 10;
l2activeExportCounter *= 10;
l3activeExportCounter *= 10;
}
if(l1current != 0)
l1current /= 100;
if(l2current != 0)
l2current /= 100;
if(l3current != 0)
l3current /= 100;
if(powerFactor != 0)
powerFactor /= 100;
if(l1PowerFactor != 0)
l1PowerFactor /= 100;
if(l2PowerFactor != 0)
l2PowerFactor /= 100;
if(l3PowerFactor != 0)
l3PowerFactor /= 100;
} else if(meterType == AmsTypeSagemcom) {
CosemData* meterTs = getCosemDataAt(1, ((char *) (d)));
if(meterTs != NULL) {
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
time_t ts = decodeCosemDateTime(amst->dt);
meterTimestamp = ts;
}
CosemData* mid = getCosemDataAt(58, ((char *) (d))); // TODO: Get last item
if(mid != NULL) {
switch(mid->base.type) {
case CosemTypeString:
memcpy(str, mid->oct.data, mid->oct.length);
str[mid->oct.length] = 0x00;
meterId = String(str);
break;
case CosemTypeOctetString:
memcpy(str, mid->str.data, mid->str.length);
str[mid->str.length] = 0x00;
meterId = String(str);
break;
}
}
}
lastUpdateMillis = millis64();
}
if(meterConfig->wattageMultiplier > 0) {
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
if(meterConfig->voltageMultiplier > 0) {
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
}
if(meterConfig->amperageMultiplier > 0) {
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
}
if(meterConfig->accumulatedMultiplier > 0) {
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
if(!threePhase)
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
// Special case for Norwegian IT/TT meters that does not report all values
if(meterConfig->distributionSystem == 1) {
if(twoPhase && l1current > 0.0 && l2current > 0.0 && l3current > 0.0) {
l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60.0 * (PI/180.0)), 2) + pow(l3voltage * sin(60.0 * (PI/180.0)),2));
threePhase = true;
}
}
}
CosemData* IEC6205675::getCosemDataAt(uint8_t index, const char* ptr) {
CosemData* item = (CosemData*) ptr;
int i = 0;
char* pos = (char*) ptr;
while(pos-ptr < 900) {
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:
case CosemTypeLongUnsigned:
pos += 3;
break;
case CosemTypeDLongSigned:
case CosemTypeDLongUnsigned:
pos += 5;
break;
case CosemTypeLong64Signed:
case CosemTypeLong64Unsigned:
pos += 9;
break;
case CosemTypeNull:
pos += 1;
break;
default:
pos += 2;
}
i++;
}
return NULL;
}
CosemData* IEC6205675::findObis(uint8_t* obis, int matchlength, const char* ptr) {
CosemData* item = (CosemData*) ptr;
int ret = 0;
char* pos = (char*) ptr;
while(pos-ptr < 900) {
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:
case CosemTypeLongUnsigned:
pos += 3;
break;
case CosemTypeDLongSigned:
case CosemTypeDLongUnsigned:
pos += 5;
break;
case CosemTypeLong64Signed:
case CosemTypeLong64Unsigned:
pos += 9;
break;
case CosemTypeNull:
pos += 1;
break;
default:
pos += 2;
}
}
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;
}
float IEC6205675::getNumber(uint8_t* obis, int matchlength, const char* ptr) {
CosemData* item = findObis(obis, matchlength, ptr);
return getNumber(item);
}
float IEC6205675::getNumber(CosemData* item) {
if(item != NULL) {
float ret = 0.0;
char* pos = ((char*) item);
switch(item->base.type) {
case CosemTypeLongSigned: {
int16_t i16 = ntohs(item->ls.data);
ret = (i16 * 1.0);
pos += 3;
break;
}
case CosemTypeLongUnsigned: {
uint16_t u16 = ntohs(item->lu.data);
ret = (u16 * 1.0);
pos += 3;
break;
}
case CosemTypeDLongSigned: {
int32_t i32 = ntohl(item->dlu.data);
ret = (i32 * 1.0);
pos += 5;
break;
}
case CosemTypeDLongUnsigned: {
uint32_t u32 = ntohl(item->dlu.data);
ret = (u32 * 1.0);
pos += 5;
break;
}
case CosemTypeLong64Signed: {
int64_t i64 = ntohll(item->l64s.data);
ret = (i64 * 1.0);
pos += 9;
break;
}
case CosemTypeLong64Unsigned: {
uint64_t u64 = ntohll(item->l64u.data);
ret = (u64 * 1.0);
pos += 9;
break;
}
}
if(pos != NULL) {
if(*pos++ == 0x02 && *pos++ == 0x02) {
int8_t scale = *++pos;
ret *= pow(10, scale);
}
}
return ret;
}
return NOVALUE;
}
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;
return decodeCosemDateTime(ts->dt);
}
}
}
}
return 0;
}

View File

@@ -0,0 +1,21 @@
#include "ImpulseAmsData.h"
#include "Uptime.h"
ImpulseAmsData::ImpulseAmsData(AmsData& state, uint16_t pulsePerKwh, uint8_t pulses) {
listType = 1;
if(pulses > 0) {
lastUpdateMillis = millis64();
uint64_t lastStateMillis = state.getLastUpdateMillis();
if(lastStateMillis > 0) {
uint64_t ms = (lastUpdateMillis - lastStateMillis) / pulses;
activeImportPower = (1000.0 / pulsePerKwh) / (((float) ms) / 3600000.0);
}
} else {
lastUpdateMillis = state.getLastUpdateMillis();
}
}
ImpulseAmsData::ImpulseAmsData(double activeImportCounter) {
this->activeImportCounter = activeImportCounter;
this->listType = 3;
}

View File

@@ -0,0 +1,52 @@
#include "KmpCommunicator.h"
#include "Uptime.h"
#include "crc.h"
#include "OBIScodes.h"
void KmpCommunicator::configure(MeterConfig& meterConfig) {
this->meterConfig = meterConfig;
this->configChanged = false;
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert, false);
talker = new KmpTalker(hanSerial);
}
bool KmpCommunicator::loop() {
uint64_t now = millis64();
return talker->loop();
}
AmsData* KmpCommunicator::getData(AmsData& meterState) {
KmpDataHolder kmpData;
talker->getData(kmpData);
AmsData* data = new AmsData();
data->apply(OBIS_ACTIVE_IMPORT_COUNT, kmpData.activeImportCounter);
data->apply(OBIS_ACTIVE_EXPORT_COUNT, kmpData.activeExportCounter);
data->apply(OBIS_REACTIVE_IMPORT_COUNT, kmpData.reactiveImportCounter);
data->apply(OBIS_REACTIVE_EXPORT_COUNT, kmpData.reactiveExportCounter);
data->apply(OBIS_ACTIVE_IMPORT, kmpData.activeImportPower);
data->apply(OBIS_ACTIVE_EXPORT, kmpData.activeExportPower);
data->apply(OBIS_REACTIVE_IMPORT, kmpData.reactiveImportPower);
data->apply(OBIS_REACTIVE_EXPORT, kmpData.reactiveExportPower);
data->apply(OBIS_VOLTAGE_L1, kmpData.l1voltage);
data->apply(OBIS_VOLTAGE_L2, kmpData.l2voltage);
data->apply(OBIS_VOLTAGE_L3, kmpData.l3voltage);
data->apply(OBIS_CURRENT_L1, kmpData.l1current);
data->apply(OBIS_CURRENT_L2, kmpData.l2current);
data->apply(OBIS_CURRENT_L3, kmpData.l3current);
data->apply(OBIS_POWER_FACTOR_L1, kmpData.l1PowerFactor);
data->apply(OBIS_POWER_FACTOR_L2, kmpData.l2PowerFactor);
data->apply(OBIS_POWER_FACTOR_L3, kmpData.l3PowerFactor);
data->apply(OBIS_POWER_FACTOR, kmpData.powerFactor);
data->apply(OBIS_ACTIVE_IMPORT_L1, kmpData.l1activeImportPower);
data->apply(OBIS_ACTIVE_IMPORT_L2, kmpData.l2activeImportPower);
data->apply(OBIS_ACTIVE_IMPORT_L3, kmpData.l3activeImportPower);
data->apply(OBIS_ACTIVE_EXPORT_L1, kmpData.l1activeExportPower);
data->apply(OBIS_ACTIVE_EXPORT_L2, kmpData.l2activeExportPower);
data->apply(OBIS_ACTIVE_EXPORT_L3, kmpData.l3activeExportPower);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L1, kmpData.l1activeImportCounter);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L2, kmpData.l2activeImportCounter);
data->apply(OBIS_ACTIVE_IMPORT_COUNT_L3, kmpData.l3activeImportCounter);
data->apply(OBIS_METER_ID, kmpData.meterId);
data->apply(OBIS_NULL, AmsTypeKamstrup);
return data;
}

View File

@@ -0,0 +1,247 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "LNG.h"
#include "lwip/def.h"
#include "ntohll.h"
#include "Uptime.h"
LNG::LNG(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
LngHeader* h = (LngHeader*) payload;
if(h->tag == CosemTypeStructure && h->arrayTag == CosemTypeArray) {
apply(meterState);
meterType = AmsTypeLandisGyr;
this->packageTimestamp = ctx.timestamp;
uint8_t* ptr = (uint8_t*) &h[1];
uint8_t* data = ptr + (18*h->arrayLength); // Skip descriptors
uint64_t o170 = 0, o270 = 0;
uint64_t o180 = 0, o280 = 0;
uint64_t o181 = 0, o182 = 0;
uint64_t o281 = 0, o282 = 0;
uint64_t o380 = 0, o480 = 0;
uint64_t o580 = 0, o680 = 0;
uint64_t o780 = 0, o880 = 0;
LngObisDescriptor* descriptor = (LngObisDescriptor*) ptr;
for(uint8_t x = 0; x < h->arrayLength-1; x++) {
ptr = (uint8_t*) &descriptor[1];
descriptor = (LngObisDescriptor*) ptr;
CosemData* item = (CosemData*) data;
if(descriptor->obis[3] == 7) {
if(descriptor->obis[4] == 0) {
if(descriptor->obis[2] > 1) {
listType = listType >= 2 ? listType : 2;
} else {
listType = listType >= 1 ? listType : 1;
}
switch(descriptor->obis[2]) {
case 1:
o170 = getNumber(item);
break;
case 2:
o270 = getNumber(item);
break;
case 3:
reactiveImportPower = getNumber(item);
break;
case 4:
reactiveExportPower = getNumber(item);
break;
case 31:
l1current = getNumber(item) / 100.0;
break;
case 51:
l2current = getNumber(item) / 100.0;
break;
case 71:
l3current = getNumber(item) / 100.0;
break;
case 32:
l1voltage = getNumber(item) / 10.0;
break;
case 52:
l2voltage = getNumber(item) / 10.0;
break;
case 72:
l3voltage = getNumber(item) / 10.0;
break;
}
}
} else if(descriptor->obis[3] == 8) {
listType = listType >= 3 ? listType : 3;
if(descriptor->obis[4] == 0) {
switch(descriptor->obis[2]) {
case 1:
o180 = getNumber(item);
activeImportCounter = o180 / 1000.0;
break;
case 2:
o280 = getNumber(item);
activeExportCounter = o280 / 1000.0;
break;
case 3:
o380 = getNumber(item);
reactiveImportCounter = o380 / 1000.0;
break;
case 4:
o480 = getNumber(item);
reactiveExportCounter = o480 / 1000.0;
break;
case 5:
o580 = getNumber(item);
break;
case 6:
o680 = getNumber(item);
break;
case 7:
o780 = getNumber(item);
break;
case 8:
o880 = getNumber(item);
break;
}
} else if(descriptor->obis[4] == 1) {
listType = listType >= 3 ? listType : 3;
switch(descriptor->obis[2]) {
case 1:
o181 = getNumber(item);
break;
case 2:
o281 = getNumber(item);
break;
}
} else if(descriptor->obis[4] == 2) {
listType = listType >= 3 ? listType : 3;
switch(descriptor->obis[2]) {
case 1:
o182 = getNumber(item);
break;
case 2:
o282 = getNumber(item);
break;
}
}
} else if(descriptor->obis[2] == 96) {
if(descriptor->obis[3] == 1) {
if(descriptor->obis[4] == 0) {
char str[item->oct.length+1];
memcpy(str, item->oct.data, item->oct.length);
str[item->oct.length] = '\0';
meterId = String(str);
listType = listType >= 2 ? listType : 2;
} else if(descriptor->obis[4] == 1) {
char str[item->oct.length+1];
memcpy(str, item->oct.data, item->oct.length);
str[item->oct.length] = '\0';
meterModel = String(str);
listType = listType >= 2 ? listType : 2;
}
}
}
if(o170 > 0 || o270 > 0) {
int32_t sum = o170-o270;
if(sum > 0) {
activeImportPower = sum;
activeExportPower = 0;
} else {
activeImportPower = 0;
activeExportPower = sum * -1;
listType = listType >= 2 ? listType : 2;
}
}
if(o181 > 0 || o182 > 0) {
activeImportCounter = (o181 + o182) / 1000.0;
}
if(o281 > 0 || o282 > 0) {
activeExportCounter = (o281 + o282) / 1000.0;
}
if(o580 > 0 || o680 > 0) {
reactiveImportCounter = (o580 + o680) / 1000.0;
}
if(o780 > 0 || o880 > 0) {
reactiveExportCounter = (o780 + o880) / 1000.0;
}
if((*data) == 0x09) {
data += (*(data+1))+2;
} else if((*data) == 0x15) {
data += 9;
} else if((*data) == 0x06) {
data += 5;
} else if((*data) == 0x12) {
data += 3;
}
lastUpdateMillis = millis64();
}
lastUpdateMillis = millis64();
if(meterConfig->wattageMultiplier > 0) {
activeImportPower = activeImportPower > 0 ? activeImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
activeExportPower = activeExportPower > 0 ? activeExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveImportPower = reactiveImportPower > 0 ? reactiveImportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
reactiveExportPower = reactiveExportPower > 0 ? reactiveExportPower * (meterConfig->wattageMultiplier / 1000.0) : 0;
}
if(meterConfig->voltageMultiplier > 0) {
l1voltage = l1voltage > 0 ? l1voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l2voltage = l2voltage > 0 ? l2voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
l3voltage = l3voltage > 0 ? l3voltage * (meterConfig->voltageMultiplier / 1000.0) : 0;
}
if(meterConfig->amperageMultiplier > 0) {
l1current = l1current > 0 ? l1current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l2current = l2current > 0 ? l2current * (meterConfig->amperageMultiplier / 1000.0) : 0;
l3current = l3current > 0 ? l3current * (meterConfig->amperageMultiplier / 1000.0) : 0;
}
if(meterConfig->accumulatedMultiplier > 0) {
activeImportCounter = activeImportCounter > 0 ? activeImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
activeExportCounter = activeExportCounter > 0 ? activeExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveImportCounter = reactiveImportCounter > 0 ? reactiveImportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
reactiveExportCounter = reactiveExportCounter > 0 ? reactiveExportCounter * (meterConfig->accumulatedMultiplier / 1000.0) : 0;
}
threePhase = l1voltage > 0 && l2voltage > 0 && l3voltage > 0;
if(!threePhase)
twoPhase = (l1voltage > 0 && l2voltage > 0) || (l2voltage > 0 && l3voltage > 0) || (l3voltage > 0 && l1voltage > 0);
}
}
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;
}

View File

@@ -0,0 +1,54 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "LNG2.h"
#include "Uptime.h"
LNG2::LNG2(AmsData& meterState, const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
CosemBasic* h = (CosemBasic*) payload;
if(h->length == 0x0e) {
apply(meterState);
meterType = AmsTypeLandisGyr;
this->packageTimestamp = ctx.timestamp;
Lng2Data_3p* d = (Lng2Data_3p*) payload;
this->l1voltage = ntohs(d->u1.data);
this->l2voltage = ntohs(d->u2.data);
this->l3voltage = ntohs(d->u3.data);
this->l1current = ntohs(d->i1.data) / 100.0;
this->l2current = ntohs(d->i2.data) / 100.0;
this->l3current = ntohs(d->i3.data) / 100.0;
this->activeImportPower = ntohl(d->activeImport.data);
this->activeExportPower = ntohl(d->activeExport.data);
this->activeImportCounter = ntohl(d->acumulatedImport.data) / 1000.0;
this->activeExportCounter = ntohl(d->accumulatedExport.data) / 1000.0;
char str[64];
uint8_t str_len = getString((CosemData*) &d->meterId, str);
if(str_len > 0) {
this->meterId = String(str);
}
listType = 3;
lastUpdateMillis = millis64();
}
}
uint8_t LNG2::getString(CosemData* item, char* target) {
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;
}

View File

@@ -0,0 +1,907 @@
/**
* @copyright Utilitech AS 2023
* License: Fair Source
*
*/
#include "PassiveMeterCommunicator.h"
#include "IEC6205675.h"
#include "IEC6205621.h"
#include "LNG.h"
#include "LNG2.h"
#if defined(ESP32)
#include <driver/uart.h>
#endif
#if defined(AMS_REMOTE_DEBUG)
PassiveMeterCommunicator::PassiveMeterCommunicator(RemoteDebug* debugger) {
this->debugger = debugger;
}
#else
PassiveMeterCommunicator::PassiveMeterCommunicator(Stream* debugger) {
this->debugger = debugger;
}
#endif
void PassiveMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
this->meterConfig = meterConfig;
if(meterConfig.baud == 0) {
autodetect = true;
}
this->configChanged = false;
this->tz = tz;
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
if(gcmParser != NULL) {
delete gcmParser;
gcmParser = NULL;
}
}
bool PassiveMeterCommunicator::loop() {
if(hanBufferSize == 0) return false;
unsigned long now = millis();
if(autodetect) handleAutodetect(now);
unsigned long start, end;
if(!hanSerial->available()) {
return false;
}
// Before reading, empty serial buffer to increase chance of getting first byte of a data transfer
if(!serialInit) {
hanSerial->readBytes(hanBuffer, hanBufferSize);
serialInit = true;
return false;
}
dataAvailable = false;
ctx = {0,0,0,0};
memset(ctx.system_title, 0, 8);
pos = DATA_PARSE_INCOMPLETE;
// For each byte received, check if we have a complete frame we can handle
start = millis();
while(hanSerial->available() && pos == DATA_PARSE_INCOMPLETE) {
// If buffer was overflowed, reset
if(len >= hanBufferSize) {
hanSerial->readBytes(hanBuffer, hanBufferSize);
len = 0;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Buffer overflow, resetting\n"));
return false;
}
hanBuffer[len++] = hanSerial->read();
ctx.length = len;
pos = unwrapData((uint8_t *) hanBuffer, ctx);
if(ctx.type > 0 && pos >= 0) {
switch(ctx.type) {
case DATA_TAG_DLMS:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid DLMS at %d\n"), pos);
break;
case DATA_TAG_DSMR:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid DSMR at %d\n"), pos);
break;
case DATA_TAG_SNRM:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid SNMR at %d\n"), pos);
break;
case DATA_TAG_AARE:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid AARE at %d\n"), pos);
break;
case DATA_TAG_RES:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid Get Response at %d\n"), pos);
break;
case DATA_TAG_HDLC:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Received valid HDLC at %d\n"), pos);
break;
default:
// TODO: Move this so that payload is sent to MQTT
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Unknown tag %02X at pos %d\n"), ctx.type, pos);
len = 0;
return false;
}
}
yield();
}
end = millis();
if(end-start > 1000) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Used %dms to unwrap HAN data\n"), end-start);
}
if(pos == DATA_PARSE_INCOMPLETE) {
return false;
} else if(pos == DATA_PARSE_UNKNOWN_DATA) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Unknown data received\n"));
lastError = pos;
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, len);
}
len = 0;
return false;
}
if(pos == DATA_PARSE_INTERMEDIATE_SEGMENT) {
len = 0;
return false;
} else if(pos < 0) {
lastError = pos;
printHanReadError(pos);
len += hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
if(pt != NULL) {
pt->publishBytes(hanBuffer, len);
}
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, len);
}
while(hanSerial->available()) hanSerial->read(); // Make sure it is all empty, in case we overflowed buffer above
len = 0;
return false;
}
if(ctx.type == 0) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Ended up with context type %d, return code %d and length: %lu/%lu\n"), ctx.type, pos, ctx.length, len);
lastError = pos;
len = len + hanSerial->readBytes(hanBuffer+len, hanBufferSize-len);
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
{
debugger->printf_P(PSTR(" payload:\n"));
debugPrint(hanBuffer, 0, len);
}
len = 0;
return false;
}
// Data is valid, clear the rest of the buffer to avoid tainted parsing
for(int i = pos+ctx.length; i<hanBufferSize; i++) {
hanBuffer[i] = 0x00;
}
dataAvailable = true;
lastError = DATA_PARSE_OK;
return true;
}
AmsData* PassiveMeterCommunicator::getData(AmsData& meterState) {
if(!dataAvailable) return NULL;
if(ctx.length > hanBufferSize) {
debugger->printf_P(PSTR("Invalid context length\n"));
dataAvailable = false;
return NULL;
}
AmsData* data = NULL;
char* payload = ((char *) (hanBuffer)) + pos;
if(maxDetectedPayloadSize < pos) maxDetectedPayloadSize = pos;
if(ctx.type == DATA_TAG_DLMS) {
if(pt != NULL) {
pt->publishBytes((uint8_t*) payload, ctx.length);
}
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("Using application data:\n"));
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugPrint((byte*) payload, 0, ctx.length);
// Rudimentary detector for L&G proprietary format, this is terrible code... Fix later
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("LNG\n"));
LNG lngData = LNG(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
if(lngData.getListType() >= 1) {
data = new AmsData();
data->apply(meterState);
data->apply(lngData);
}
} else if(payload[0] == CosemTypeStructure &&
payload[2] == CosemTypeLongUnsigned &&
payload[5] == CosemTypeLongUnsigned &&
payload[8] == CosemTypeLongUnsigned &&
payload[11] == CosemTypeLongUnsigned &&
payload[14] == CosemTypeLongUnsigned &&
payload[17] == CosemTypeLongUnsigned
) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("LNG2\n"));
LNG2 lngData = LNG2(meterState, payload, meterState.getMeterType(), &meterConfig, ctx);
if(lngData.getListType() >= 1) {
data = new AmsData();
data->apply(meterState);
data->apply(lngData);
}
} else {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("DLMS\n"));
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState);
}
} else if(ctx.type == DATA_TAG_DSMR) {
data = new IEC6205621(payload, tz, &meterConfig);
}
len = 0;
if(data != NULL) {
if(data->getListType() > 0) {
validDataReceived = true;
if(rxBufferErrors > 0) rxBufferErrors--;
}
}
dataAvailable = false;
return data;
}
int PassiveMeterCommunicator::getLastError() {
#if defined ESP8266
if(hwSerial != NULL) {
if(hwSerial->hasRxError()) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Serial RX error\n"));
lastError = 96;
}
if(hwSerial->hasOverrun()) {
rxerr(2);
}
} else if(swSerial != NULL) {
if(swSerial->overflow()) {
rxerr(2);
}
}
#endif
return lastError;
}
bool PassiveMeterCommunicator::isConfigChanged() {
return configChanged;
}
void PassiveMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
int16_t PassiveMeterCommunicator::unwrapData(uint8_t *buf, DataParserContext &context) {
int16_t ret = 0;
bool doRet = false;
uint16_t end = hanBufferSize;
uint8_t tag = (*buf);
uint8_t lastTag = DATA_TAG_NONE;
while(tag != DATA_TAG_NONE) {
int16_t curLen = context.length;
int8_t res = 0;
switch(tag) {
case DATA_TAG_HDLC:
if(hdlcParser == NULL) hdlcParser = new HDLCParser();
res = hdlcParser->parse(buf, context);
if(context.length < 3) doRet = true;
break;
case DATA_TAG_MBUS:
if(mbusParser == NULL) mbusParser = new MBUSParser();
res = mbusParser->parse(buf, context);
break;
case DATA_TAG_GBT:
if(gbtParser == NULL) gbtParser = new GBTParser();
res = gbtParser->parse(buf, context);
break;
case DATA_TAG_GCM:
if(gcmParser == NULL) gcmParser = new GCMParser(meterConfig.encryptionKey, meterConfig.authenticationKey);
res = gcmParser->parse(buf, context);
break;
case DATA_TAG_LLC:
if(llcParser == NULL) llcParser = new LLCParser();
res = llcParser->parse(buf, context);
break;
case DATA_TAG_DLMS:
if(dlmsParser == NULL) dlmsParser = new DLMSParser();
res = dlmsParser->parse(buf, context);
if(res >= 0) doRet = true;
break;
case DATA_TAG_DSMR:
if(dsmrParser == NULL) dsmrParser = new DSMRParser();
res = dsmrParser->parse(buf, context, lastTag != DATA_TAG_NONE);
if(res >= 0) doRet = true;
break;
case DATA_TAG_SNRM:
case DATA_TAG_AARE:
case DATA_TAG_RES:
res = DATA_PARSE_OK;
doRet = true;
break;
default:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Ended up in default case while unwrapping...(tag %02X)\n"), tag);
return DATA_PARSE_UNKNOWN_DATA;
}
lastTag = tag;
if(res == DATA_PARSE_INCOMPLETE) {
return res;
}
if(context.length > end) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("Context length %lu > %lu:\n"), context.length, end);
context.type = 0;
context.length = 0;
return false;
}
switch(tag) {
case DATA_TAG_HDLC:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("HDLC frame:\n"));
if(pt != NULL) {
pt->publishBytes(buf, curLen);
}
break;
case DATA_TAG_MBUS:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("MBUS frame:\n"));
if(pt != NULL) {
pt->publishBytes(buf, curLen);
}
break;
case DATA_TAG_GBT:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("GBT frame:\n"));
break;
case DATA_TAG_GCM:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("GCM frame:\n"));
break;
case DATA_TAG_LLC:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("LLC frame:\n"));
break;
case DATA_TAG_DLMS:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("DLMS frame:\n"));
break;
case DATA_TAG_DSMR:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("DSMR frame:\n"));
if(pt != NULL) {
pt->publishString((char*) buf);
}
break;
case DATA_TAG_SNRM:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("SNMR frame:\n"));
break;
case DATA_TAG_AARE:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("AARE frame:\n"));
break;
case DATA_TAG_RES:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugger->printf_P(PSTR("RES frame:\n"));
break;
}
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
debugPrint(buf, 0, curLen);
if(res == DATA_PARSE_FINAL_SEGMENT) {
if(tag == DATA_TAG_MBUS) {
res = mbusParser->write(buf, context);
}
}
if(res < 0) {
return res;
}
buf += res;
end -= res;
ret += res;
// If we are ready to return, do that
if(doRet) {
context.type = tag;
return ret;
}
// Use start byte of new buffer position as tag for next round in loop
tag = (*buf);
}
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Got to end of unwrap method...\n"));
return DATA_PARSE_UNKNOWN_DATA;
}
void PassiveMeterCommunicator::debugPrint(byte *buffer, int start, int length) {
for (int i = start; i < start + length; i++) {
if (buffer[i] < 0x10)
debugger->print(F("0"));
debugger->print(buffer[i], HEX);
debugger->print(F(" "));
if ((i - start + 1) % 16 == 0)
debugger->println(F(""));
else if ((i - start + 1) % 4 == 0)
debugger->print(F(" "));
yield(); // Let other get some resources too
}
debugger->println(F(""));
}
void PassiveMeterCommunicator::printHanReadError(int pos) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
{
switch(pos) {
case DATA_PARSE_BOUNDRY_FLAG_MISSING:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Boundry flag missing\n"));
break;
case DATA_PARSE_HEADER_CHECKSUM_ERROR:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Header checksum error\n"));
break;
case DATA_PARSE_FOOTER_CHECKSUM_ERROR:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Frame checksum error\n"));
break;
case DATA_PARSE_INCOMPLETE:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Received frame is incomplete\n"));
break;
case GCM_AUTH_FAILED:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Decrypt authentication failed\n"));
break;
case GCM_ENCRYPTION_KEY_FAILED:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Setting decryption key failed\n"));
break;
case GCM_DECRYPT_FAILED:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Decryption failed\n"));
break;
case MBUS_FRAME_LENGTH_NOT_EQUAL:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Frame length mismatch\n"));
break;
case DATA_PARSE_INTERMEDIATE_SEGMENT:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Intermediate segment received\n"));
break;
case DATA_PARSE_UNKNOWN_DATA:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Unknown data format %02X\n"), hanBuffer[0]);
break;
default:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Unspecified error while reading data: %d\n"), pos);
}
}
}
void PassiveMeterCommunicator::setupHanPort(uint32_t baud, uint8_t parityOrdinal, bool invert, bool passive) {
int8_t rxpin = meterConfig.rxPin;
int8_t txpin = passive ? -1 : meterConfig.txPin;
if(baud == 0) {
baud = 2400;
}
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("(setupHanPort) Setting up HAN on pin %d/%d with baud %d and parity %d\n"), rxpin, txpin, baud, parityOrdinal);
if(parityOrdinal == 0) {
parityOrdinal = 3; // 8N1
}
if(rxpin == 3 || rxpin == 113) {
#if ARDUINO_USB_CDC_ON_BOOT
hwSerial = &Serial0;
#else
hwSerial = &Serial;
#endif
}
uint8_t uart_num = 0;
#if defined(ESP32)
hwSerial = &Serial1;
uart_num = UART_NUM_1;
#if defined(CONFIG_IDF_TARGET_ESP32)
if(rxpin == 16) {
hwSerial = &Serial2;
uart_num = UART_NUM_2;
}
#endif
#endif
if(rxpin == 0) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Invalid GPIO configured for HAN\n"));
return;
}
if(meterConfig.bufferSize < 1) meterConfig.bufferSize = 1;
if(meterConfig.bufferSize > 64) meterConfig.bufferSize = 64;
if(hwSerial != NULL) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Hardware serial\n"));
Serial.flush();
#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 7:
serialConfig = SERIAL_8N2;
break;
case 10:
serialConfig = SERIAL_7E1;
break;
default:
serialConfig = SERIAL_8E1;
break;
}
if(meterConfig.bufferSize < 4) meterConfig.bufferSize = 4; // 64 bytes (1) is default for software serial, 256 bytes (4) for hardware
hwSerial->setRxBufferSize(64 * meterConfig.bufferSize);
#if defined(ESP32)
hwSerial->begin(baud, serialConfig, -1, -1, invert);
uart_set_pin(uart_num, txpin, rxpin, -1, -1);
#else
hwSerial->begin(baud, serialConfig, SERIAL_FULL, 1, invert);
#endif
#if defined(ESP8266)
if(rxpin == 3) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Switching UART0 to pin 1 & 3\n"));
Serial.pins(1,3);
} else if(rxpin == 113) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Switching UART0 to pin 15 & 13\n"));
Serial.pins(15,13);
}
#endif
// Prevent pullup on TX pin if not uart0
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3)
if(txpin != 17) pinMode(17, INPUT);
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
if(txpin != 7) pinMode(7, INPUT);
#elif defined(ESP32)
if(rxpin == 9 && txpin != 10) {
pinMode(10, INPUT);
} else if(rxpin == 16 && txpin != 17) {
pinMode(17, INPUT);
}
#elif defined(ESP8266)
if(rxpin == 113) {
pinMode(15, INPUT);
}
#endif
hanSerial = hwSerial;
#if defined(ESP8266)
if(swSerial != NULL) {
swSerial->end();
delete swSerial;
swSerial = NULL;
}
#endif
} else {
#if defined(ESP8266)
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Software serial\n"));
Serial.flush();
if(swSerial == NULL) {
swSerial = new SoftwareSerial(rxpin, txpin, invert);
} else {
swSerial->end();
}
SoftwareSerialConfig serialConfig;
switch(parityOrdinal) {
case 2:
serialConfig = SWSERIAL_7N1;
break;
case 3:
serialConfig = SWSERIAL_8N1;
break;
case 7:
serialConfig = SWSERIAL_8N2;
break;
case 10:
serialConfig = SWSERIAL_7E1;
break;
default:
serialConfig = SWSERIAL_8E1;
break;
}
uint8_t bufferSize = meterConfig.bufferSize;
#if defined(ESP8266)
if(bufferSize > 2) bufferSize = 2;
#endif
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Using serial buffer size %d\n"), 64 * bufferSize);
swSerial->begin(baud, serialConfig, rxpin, txpin, invert, meterConfig.bufferSize * 64, meterConfig.bufferSize * 64);
hanSerial = swSerial;
hwSerial = NULL;
#else
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Software serial not available\n"));
return;
#endif
}
if(hanBuffer != NULL) {
free(hanBuffer);
}
hanBufferSize = max(64 * meterConfig.bufferSize * 3, 512);
hanBuffer = (uint8_t*) malloc(hanBufferSize);
// The library automatically sets the pullup in Serial.begin()
if(!meterConfig.rxPinPullup) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("HAN pin pullup disabled\n"));
pinMode(meterConfig.rxPin, INPUT);
}
if(meterConfig.txPin != 0xFF && passive) {
pinMode(meterConfig.txPin, OUTPUT);
digitalWrite(meterConfig.txPin, LOW);
}
hanSerial->setTimeout(250);
// Empty buffer before starting
while (hanSerial->available() > 0) {
hanSerial->read();
}
#if defined(ESP8266)
if(hwSerial != NULL) {
hwSerial->hasOverrun();
} else if(swSerial != NULL) {
swSerial->overflow();
}
#endif
}
HardwareSerial* PassiveMeterCommunicator::getHwSerial() {
return hwSerial;
}
void PassiveMeterCommunicator::rxerr(int err) {
if(err == 0) return;
switch(err) {
case 2:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Serial buffer overflow\n"));
rxBufferErrors++;
if(rxBufferErrors > 1 && meterConfig.bufferSize < 8) {
meterConfig.bufferSize += 2;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Increasing RX buffer to %d bytes\n"), meterConfig.bufferSize * 64);
configChanged = true;
rxBufferErrors = 0;
}
break;
case 3:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::ERROR))
#endif
debugger->printf_P(PSTR("Serial FIFO overflow\n"));
break;
case 4:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Serial frame error\n"));
break;
case 5:
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::WARNING))
#endif
debugger->printf_P(PSTR("Serial parity error\n"));
unsigned long now = millis();
if(now - meterAutodetectLastChange < 120000) {
switch(autodetectParity) {
case 2: // 7N1
autodetectParity = 10;
break;
case 10: // 7E1
autodetectParity = 6;
break;
case 6: // 7N2
autodetectParity = 14;
break;
case 14: // 7E2
autodetectParity = 2;
break;
case 3: // 8N1
autodetectParity = 11;
break;
case 11: // 8E1
autodetectParity = 7;
break;
case 7: // 8N2
autodetectParity = 15;
break;
case 15: // 8E2
autodetectParity = 3;
break;
default:
autodetectParity = 3;
break;
}
if(validDataReceived) {
meterConfig.parity = autodetectParity;
configChanged = true;
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
}
}
break;
}
// Do not include serial break
if(err > 1) lastError = 90+err;
}
void PassiveMeterCommunicator::handleAutodetect(unsigned long now) {
if(!autodetect) return;
if(!validDataReceived) {
if(now - meterAutodetectLastChange > 20000 && (meterConfig.baud == 0 || meterConfig.parity == 0)) {
autodetect = true;
if(autodetectCount == 2) {
autodetectInvert = !autodetectInvert;
autodetectCount = 0;
}
autodetectBaud = AUTO_BAUD_RATES[autodetectCount++];
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Meter serial autodetect, swapping to: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
meterConfig.bufferSize = max((uint32_t) 1, autodetectBaud / 14400);
setupHanPort(autodetectBaud, autodetectParity, autodetectInvert);
meterAutodetectLastChange = now;
}
} else if(autodetect) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Meter serial autodetected, saving: %d, %d, %s\n"), autodetectBaud, autodetectParity, autodetectInvert ? "true" : "false");
autodetect = false;
meterConfig.baud = autodetectBaud;
meterConfig.parity = autodetectParity;
meterConfig.invert = autodetectInvert;
configChanged = true;
setupHanPort(meterConfig.baud, meterConfig.parity, meterConfig.invert);
}
}

View File

@@ -0,0 +1,81 @@
#include "PulseMeterCommunicator.h"
#include "Uptime.h"
#if defined(AMS_REMOTE_DEBUG)
PulseMeterCommunicator::PulseMeterCommunicator(RemoteDebug* debugger) {
#else
PulseMeterCommunicator::PulseMeterCommunicator(Stream* debugger) {
#endif
this->debugger = debugger;
}
void PulseMeterCommunicator::configure(MeterConfig& meterConfig, Timezone* tz) {
this->meterConfig = meterConfig;
this->configChanged = false;
this->tz = tz;
setupGpio();
}
bool PulseMeterCommunicator::loop() {
return updated || !initialized;
}
AmsData* PulseMeterCommunicator::getData(AmsData& meterState) {
if(!initialized) {
state.apply(meterState);
initialized = true;
return NULL;
}
updated = false;
AmsData* ret = new AmsData();
ret->apply(state);
return ret;
}
int PulseMeterCommunicator::getLastError() {
return 0;
}
bool PulseMeterCommunicator::isConfigChanged() {
return this->configChanged;
}
void PulseMeterCommunicator::getCurrentConfig(MeterConfig& meterConfig) {
meterConfig = this->meterConfig;
}
void PulseMeterCommunicator::setupGpio() {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
#endif
debugger->printf_P(PSTR("Setting up Pulse Meter GPIO, rx: %d, tx: %d\n"), meterConfig.rxPin, meterConfig.txPin);
if(meterConfig.rxPin != NOT_A_PIN) {
pinMode(meterConfig.rxPin, meterConfig.rxPinPullup ? INPUT_PULLUP : INPUT);
}
// Export counter?
// if(meterConfig.txPin != NOT_A_PIN) {
// pinMode(meterConfig.txPin, meterConfig.rxPinPullup ? INPUT_PULLUP : INPUT);
// }
}
void PulseMeterCommunicator::onPulse(uint8_t pulses) {
uint64_t now = millis64();
if(initialized && pulses == 0) {
if(now - lastUpdate > 10000) {
ImpulseAmsData update(state, meterConfig.baud, pulses);
state.apply(update);
updated = true;
lastUpdate = now;
}
return;
}
if(!initialized) {
return;
}
ImpulseAmsData update(state, meterConfig.baud, pulses);
state.apply(update);
updated = true;
lastUpdate = now;
}