mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-26 12:13:10 +00:00
Restructuring to be able to include precompiled kmp lib
This commit is contained in:
24
lib/MeterCommunicators/include/IEC6205621.h
Normal file
24
lib/MeterCommunicators/include/IEC6205621.h
Normal 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
|
||||
74
lib/MeterCommunicators/include/IEC6205675.h
Normal file
74
lib/MeterCommunicators/include/IEC6205675.h
Normal 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
|
||||
12
lib/MeterCommunicators/include/ImpulseAmsData.h
Normal file
12
lib/MeterCommunicators/include/ImpulseAmsData.h
Normal 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
|
||||
35
lib/MeterCommunicators/include/KmpCommunicator.h
Normal file
35
lib/MeterCommunicators/include/KmpCommunicator.h
Normal 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;
|
||||
};
|
||||
22
lib/MeterCommunicators/include/KmpTalker.h
Normal file
22
lib/MeterCommunicators/include/KmpTalker.h
Normal 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);
|
||||
};
|
||||
37
lib/MeterCommunicators/include/LNG.h
Normal file
37
lib/MeterCommunicators/include/LNG.h
Normal 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
|
||||
42
lib/MeterCommunicators/include/LNG2.h
Normal file
42
lib/MeterCommunicators/include/LNG2.h
Normal 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
|
||||
28
lib/MeterCommunicators/include/MeterCommunicator.h
Normal file
28
lib/MeterCommunicators/include/MeterCommunicator.h
Normal 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
|
||||
96
lib/MeterCommunicators/include/PassiveMeterCommunicator.h
Normal file
96
lib/MeterCommunicators/include/PassiveMeterCommunicator.h
Normal 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
|
||||
51
lib/MeterCommunicators/include/PulseMeterCommunicator.h
Normal file
51
lib/MeterCommunicators/include/PulseMeterCommunicator.h
Normal 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
|
||||
204
lib/MeterCommunicators/src/IEC6205621.cpp
Normal file
204
lib/MeterCommunicators/src/IEC6205621.cpp
Normal 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();
|
||||
}
|
||||
763
lib/MeterCommunicators/src/IEC6205675.cpp
Normal file
763
lib/MeterCommunicators/src/IEC6205675.cpp
Normal 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;
|
||||
}
|
||||
21
lib/MeterCommunicators/src/ImpulseAmsData.cpp
Normal file
21
lib/MeterCommunicators/src/ImpulseAmsData.cpp
Normal 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;
|
||||
}
|
||||
52
lib/MeterCommunicators/src/KmpCommunicator.cpp
Normal file
52
lib/MeterCommunicators/src/KmpCommunicator.cpp
Normal 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;
|
||||
}
|
||||
247
lib/MeterCommunicators/src/LNG.cpp
Normal file
247
lib/MeterCommunicators/src/LNG.cpp
Normal 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;
|
||||
}
|
||||
54
lib/MeterCommunicators/src/LNG2.cpp
Normal file
54
lib/MeterCommunicators/src/LNG2.cpp
Normal 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;
|
||||
|
||||
}
|
||||
907
lib/MeterCommunicators/src/PassiveMeterCommunicator.cpp
Normal file
907
lib/MeterCommunicators/src/PassiveMeterCommunicator.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
81
lib/MeterCommunicators/src/PulseMeterCommunicator.cpp
Normal file
81
lib/MeterCommunicators/src/PulseMeterCommunicator.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user