Merge branch 'development'
This commit is contained in:
commit
9da5be7fd8
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -52,6 +52,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get tags
|
||||
run: git fetch --force --tags origin
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
This is a fork from the Hoymiles project OpenDTU.
|
||||
|
||||

|
||||
|
||||
## Extensions to the original OpenDTU
|
||||
|
||||
This project is still under development and adds following features:
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <Hoymiles.h>
|
||||
#include <memory>
|
||||
#include "SDM.h"
|
||||
#include "sml.h"
|
||||
|
||||
#ifndef SDM_RX_PIN
|
||||
#define SDM_RX_PIN 13
|
||||
@ -16,6 +17,16 @@
|
||||
#define SDM_TX_PIN 32
|
||||
#endif
|
||||
|
||||
#ifndef SML_RX_PIN
|
||||
#define SML_RX_PIN 35
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
const unsigned char OBIS[6];
|
||||
void (*Fn)(double&);
|
||||
float* Arg;
|
||||
} OBISHandler;
|
||||
|
||||
class PowerMeterClass {
|
||||
public:
|
||||
enum SOURCE {
|
||||
@ -23,6 +34,7 @@ public:
|
||||
SOURCE_SDM1PH = 1,
|
||||
SOURCE_SDM3PH = 2,
|
||||
SOURCE_HTTP = 3,
|
||||
SOURCE_SML = 4
|
||||
};
|
||||
void init();
|
||||
void mqtt();
|
||||
@ -40,14 +52,20 @@ private:
|
||||
float _powerMeter1Power = 0.0;
|
||||
float _powerMeter2Power = 0.0;
|
||||
float _powerMeter3Power = 0.0;
|
||||
float _powerMeterTotalPower = 0.0;
|
||||
float _powerMeter1Voltage = 0.0;
|
||||
float _powerMeter2Voltage = 0.0;
|
||||
float _powerMeter3Voltage = 0.0;
|
||||
float _PowerMeterImport = 0.0;
|
||||
float _PowerMeterExport = 0.0;
|
||||
float _powerMeterImport = 0.0;
|
||||
float _powerMeterExport = 0.0;
|
||||
|
||||
bool mqttInitDone = false;
|
||||
|
||||
bool smlReadLoop();
|
||||
const std::list<OBISHandler> smlHandlerList{
|
||||
{{0x01, 0x00, 0x10, 0x07, 0x00, 0xff}, &smlOBISW, &_powerMeter1Power},
|
||||
{{0x01, 0x00, 0x01, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterImport},
|
||||
{{0x01, 0x00, 0x02, 0x08, 0x00, 0xff}, &smlOBISWh, &_powerMeterExport}
|
||||
};
|
||||
};
|
||||
|
||||
extern PowerMeterClass PowerMeter;
|
||||
|
||||
405
lib/SMLParser/sml.cpp
Normal file
405
lib/SMLParser/sml.cpp
Normal file
@ -0,0 +1,405 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sml.h"
|
||||
#include "smlCrcTable.h"
|
||||
|
||||
#ifdef SML_DEBUG
|
||||
char logBuff[200];
|
||||
|
||||
#ifdef SML_NATIVE
|
||||
#define SML_LOG(...) \
|
||||
do { \
|
||||
printf(__VA_ARGS__); \
|
||||
} while (0)
|
||||
#define SML_TREELOG(level, ...) \
|
||||
do { \
|
||||
printf("%.*s", level, " "); \
|
||||
printf(__VA_ARGS__); \
|
||||
} while (0)
|
||||
#elif ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define SML_LOG(...) \
|
||||
do { \
|
||||
sprintf(logBuff, __VA_ARGS__); \
|
||||
Serial.print(logBuff); \
|
||||
} while (0)
|
||||
#define SML_TREELOG(level, ...) \
|
||||
do { \
|
||||
sprintf(logBuff, __VA_ARGS__); \
|
||||
Serial.print(logBuff); \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define SML_LOG(...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#define SML_TREELOG(level, ...) \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define MAX_LIST_SIZE 80
|
||||
#define MAX_TREE_SIZE 10
|
||||
|
||||
static sml_states_t currentState = SML_START;
|
||||
static char nodes[MAX_TREE_SIZE];
|
||||
static unsigned char currentLevel = 0;
|
||||
static unsigned short crc = 0xFFFF;
|
||||
static signed char sc;
|
||||
static unsigned short crcMine = 0xFFFF;
|
||||
static unsigned short crcReceived = 0x0000;
|
||||
static unsigned char len = 4;
|
||||
static unsigned char listBuffer[MAX_LIST_SIZE]; /* keeps a list
|
||||
as length + state + data */
|
||||
static unsigned char listPos = 0;
|
||||
|
||||
void crc16(unsigned char &byte)
|
||||
{
|
||||
#ifdef ARDUINO
|
||||
crc =
|
||||
pgm_read_word_near(&smlCrcTable[(byte ^ crc) & 0xff]) ^ (crc >> 8 & 0xff);
|
||||
#else
|
||||
crc = smlCrcTable[(byte ^ crc) & 0xff] ^ (crc >> 8 & 0xff);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setState(sml_states_t state, int byteLen)
|
||||
{
|
||||
currentState = state;
|
||||
len = byteLen;
|
||||
}
|
||||
|
||||
void pushListBuffer(unsigned char byte)
|
||||
{
|
||||
if (listPos < MAX_LIST_SIZE) {
|
||||
listBuffer[listPos++] = byte;
|
||||
}
|
||||
}
|
||||
|
||||
void reduceList()
|
||||
{
|
||||
if (currentLevel <= MAX_TREE_SIZE && nodes[currentLevel] > 0)
|
||||
nodes[currentLevel]--;
|
||||
}
|
||||
|
||||
void smlNewList(unsigned char size)
|
||||
{
|
||||
reduceList();
|
||||
if (currentLevel < MAX_TREE_SIZE)
|
||||
currentLevel++;
|
||||
nodes[currentLevel] = size;
|
||||
SML_TREELOG(currentLevel, "LISTSTART on level %i with %i nodes\n",
|
||||
currentLevel, size);
|
||||
setState(SML_LISTSTART, size);
|
||||
// @todo workaround for lists inside obis lists
|
||||
if (size > 5) {
|
||||
listPos = 0;
|
||||
memset(listBuffer, '\0', MAX_LIST_SIZE);
|
||||
}
|
||||
else {
|
||||
pushListBuffer(size);
|
||||
pushListBuffer(currentState);
|
||||
}
|
||||
}
|
||||
|
||||
void checkMagicByte(unsigned char &byte)
|
||||
{
|
||||
unsigned int size = 0;
|
||||
while (currentLevel > 0 && nodes[currentLevel] == 0) {
|
||||
/* go back in tree if no nodes remaining */
|
||||
SML_TREELOG(currentLevel, "back to previous list\n");
|
||||
currentLevel--;
|
||||
}
|
||||
if (byte > 0x70 && byte <= 0x7F) {
|
||||
/* new list */
|
||||
size = byte & 0x0F;
|
||||
smlNewList(size);
|
||||
}
|
||||
else if (byte >= 0x01 && byte <= 0x6F && nodes[currentLevel] > 0) {
|
||||
if (byte == 0x01) {
|
||||
/* no data, get next */
|
||||
SML_TREELOG(currentLevel, " Data %i (empty)\n", nodes[currentLevel]);
|
||||
pushListBuffer(0);
|
||||
pushListBuffer(currentState);
|
||||
if (nodes[currentLevel] == 1) {
|
||||
setState(SML_LISTEND, 1);
|
||||
SML_TREELOG(currentLevel, "LISTEND\n");
|
||||
}
|
||||
else {
|
||||
setState(SML_NEXT, 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
size = (byte & 0x0F) - 1;
|
||||
setState(SML_DATA, size);
|
||||
if ((byte & 0xF0) == 0x50) {
|
||||
setState(SML_DATA_SIGNED_INT, size);
|
||||
}
|
||||
else if ((byte & 0xF0) == 0x60) {
|
||||
setState(SML_DATA_UNSIGNED_INT, size);
|
||||
}
|
||||
else if ((byte & 0xF0) == 0x00) {
|
||||
setState(SML_DATA_OCTET_STRING, size);
|
||||
}
|
||||
SML_TREELOG(currentLevel,
|
||||
" Data %i (length = %i%s): ", nodes[currentLevel], size,
|
||||
(currentState == SML_DATA_SIGNED_INT) ? ", signed int"
|
||||
: (currentState == SML_DATA_UNSIGNED_INT) ? ", unsigned int"
|
||||
: (currentState == SML_DATA_OCTET_STRING) ? ", octet string"
|
||||
: "");
|
||||
pushListBuffer(size);
|
||||
pushListBuffer(currentState);
|
||||
}
|
||||
reduceList();
|
||||
}
|
||||
else if (byte == 0x00) {
|
||||
/* end of block */
|
||||
reduceList();
|
||||
SML_TREELOG(currentLevel, "End of block at level %i\n", currentLevel);
|
||||
if (currentLevel == 0) {
|
||||
setState(SML_NEXT, 1);
|
||||
}
|
||||
else {
|
||||
setState(SML_BLOCKEND, 1);
|
||||
}
|
||||
}
|
||||
else if (byte & 0x80) {
|
||||
// MSB bit is set, another TL byte will follow
|
||||
if (byte >= 0x80 && byte <= 0x8F) {
|
||||
// Datatype Octet String
|
||||
setState(SML_HDATA, (byte & 0x0F) << 4);
|
||||
}
|
||||
else if (byte >= 0xF0 /*&& byte <= 0xFF*/) {
|
||||
/* Datatype List of ...*/
|
||||
setState(SML_LISTEXTENDED, (byte & 0x0F) << 4);
|
||||
}
|
||||
}
|
||||
else if (byte == 0x1B && currentLevel == 0) {
|
||||
/* end sequence */
|
||||
setState(SML_END, 3);
|
||||
}
|
||||
else {
|
||||
/* Unexpected Byte */
|
||||
SML_TREELOG(currentLevel,
|
||||
"UNEXPECTED magicbyte >%02X< at currentLevel %i\n", byte,
|
||||
currentLevel);
|
||||
setState(SML_UNEXPECTED, 4);
|
||||
}
|
||||
}
|
||||
|
||||
sml_states_t smlState(unsigned char ¤tByte)
|
||||
{
|
||||
unsigned char size;
|
||||
if (len > 0)
|
||||
len--;
|
||||
crc16(currentByte);
|
||||
switch (currentState) {
|
||||
case SML_UNEXPECTED:
|
||||
case SML_CHECKSUM_ERROR:
|
||||
case SML_FINAL:
|
||||
case SML_START:
|
||||
currentState = SML_START;
|
||||
currentLevel = 0; // Reset current level at the begin of a new transmission
|
||||
// to prevent problems
|
||||
if (currentByte != 0x1b)
|
||||
setState(SML_UNEXPECTED, 4);
|
||||
if (len == 0) {
|
||||
SML_TREELOG(0, "START\n");
|
||||
/* completely clean any garbage from crc checksum */
|
||||
crc = 0xFFFF;
|
||||
currentByte = 0x1b;
|
||||
crc16(currentByte);
|
||||
crc16(currentByte);
|
||||
crc16(currentByte);
|
||||
crc16(currentByte);
|
||||
setState(SML_VERSION, 4);
|
||||
}
|
||||
break;
|
||||
case SML_VERSION:
|
||||
if (currentByte != 0x01)
|
||||
setState(SML_UNEXPECTED, 4);
|
||||
if (len == 0) {
|
||||
setState(SML_BLOCKSTART, 1);
|
||||
}
|
||||
break;
|
||||
case SML_END:
|
||||
if (currentByte != 0x1b) {
|
||||
SML_LOG("UNEXPECTED char >%02X< at SML_END\n", currentByte);
|
||||
setState(SML_UNEXPECTED, 4);
|
||||
}
|
||||
if (len == 0) {
|
||||
setState(SML_CHECKSUM, 4);
|
||||
}
|
||||
break;
|
||||
case SML_CHECKSUM:
|
||||
// SML_LOG("CHECK: %02X\n", currentByte);
|
||||
if (len == 2) {
|
||||
crcMine = crc ^ 0xFFFF;
|
||||
}
|
||||
if (len == 1) {
|
||||
crcReceived += currentByte;
|
||||
}
|
||||
if (len == 0) {
|
||||
crcReceived = crcReceived | (currentByte << 8);
|
||||
SML_LOG("Received checksum: %02X\n", crcReceived);
|
||||
SML_LOG("Calculated checksum: %02X\n", crcMine);
|
||||
if (crcMine == crcReceived) {
|
||||
setState(SML_FINAL, 4);
|
||||
}
|
||||
else {
|
||||
setState(SML_CHECKSUM_ERROR, 4);
|
||||
}
|
||||
crc = 0xFFFF;
|
||||
crcReceived = 0x000; /* reset CRC */
|
||||
}
|
||||
break;
|
||||
case SML_HDATA:
|
||||
size = len + currentByte - 1;
|
||||
setState(SML_DATA, size);
|
||||
pushListBuffer(size);
|
||||
pushListBuffer(currentState);
|
||||
SML_TREELOG(currentLevel, " Data (length = %i): ", size);
|
||||
break;
|
||||
case SML_LISTEXTENDED:
|
||||
size = len + (currentByte & 0x0F);
|
||||
SML_TREELOG(currentLevel, "Extended List with Size=%i\n", size);
|
||||
smlNewList(size);
|
||||
break;
|
||||
case SML_DATA:
|
||||
case SML_DATA_SIGNED_INT:
|
||||
case SML_DATA_UNSIGNED_INT:
|
||||
case SML_DATA_OCTET_STRING:
|
||||
SML_LOG("%02X ", currentByte);
|
||||
pushListBuffer(currentByte);
|
||||
if (nodes[currentLevel] == 0 && len == 0) {
|
||||
SML_LOG("\n");
|
||||
SML_TREELOG(currentLevel, "LISTEND on level %i\n", currentLevel);
|
||||
currentState = SML_LISTEND;
|
||||
}
|
||||
else if (len == 0) {
|
||||
currentState = SML_DATAEND;
|
||||
SML_LOG("\n");
|
||||
}
|
||||
break;
|
||||
case SML_DATAEND:
|
||||
case SML_NEXT:
|
||||
case SML_LISTSTART:
|
||||
case SML_LISTEND:
|
||||
case SML_BLOCKSTART:
|
||||
case SML_BLOCKEND:
|
||||
checkMagicByte(currentByte);
|
||||
break;
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
bool smlOBISCheck(const unsigned char *obis)
|
||||
{
|
||||
return (memcmp(obis, &listBuffer[2], 6) == 0);
|
||||
}
|
||||
|
||||
void smlOBISManufacturer(unsigned char *str, int maxSize)
|
||||
{
|
||||
int i = 0, pos = 0, size = 0;
|
||||
while (i < listPos) {
|
||||
size = (int)listBuffer[i];
|
||||
i++;
|
||||
pos++;
|
||||
if (pos == 6) {
|
||||
/* get manufacturer at position 6 in list */
|
||||
size = (size > maxSize - 1) ? maxSize : size;
|
||||
memcpy(str, &listBuffer[i + 1], size);
|
||||
str[size + 1] = 0;
|
||||
}
|
||||
i += size + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void smlPow(double &val, signed char &scaler)
|
||||
{
|
||||
if (scaler < 0) {
|
||||
while (scaler++) {
|
||||
val /= 10;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (scaler--) {
|
||||
val *= 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void smlOBISByUnit(long long int &val, signed char &scaler, sml_units_t unit)
|
||||
{
|
||||
unsigned char i = 0, pos = 0, size = 0, y = 0, skip = 0;
|
||||
sml_states_t type;
|
||||
val = -1; /* unknown or error */
|
||||
while (i < listPos) {
|
||||
pos++;
|
||||
size = (int)listBuffer[i++];
|
||||
type = (sml_states_t)listBuffer[i++];
|
||||
if (type == SML_LISTSTART && size > 0) {
|
||||
// skip a list inside an obis list
|
||||
skip = size;
|
||||
while (skip > 0) {
|
||||
size = (int)listBuffer[i++];
|
||||
type = (sml_states_t)listBuffer[i++];
|
||||
i += size;
|
||||
skip--;
|
||||
}
|
||||
size = 0;
|
||||
}
|
||||
if (pos == 4 && listBuffer[i] != unit) {
|
||||
/* return unknown (-1) if unit does not match */
|
||||
return;
|
||||
}
|
||||
if (pos == 5) {
|
||||
scaler = listBuffer[i];
|
||||
}
|
||||
if (pos == 6) {
|
||||
y = size;
|
||||
// initialize 64bit signed integer based on MSB from received value
|
||||
val =
|
||||
(type == SML_DATA_SIGNED_INT && (listBuffer[i] & (1 << 7))) ? ~0 : 0;
|
||||
for (y = 0; y < size; y++) {
|
||||
// left shift received bytes to 64 bit signed integer
|
||||
val = (val << 8) | listBuffer[i + y];
|
||||
}
|
||||
}
|
||||
i += size;
|
||||
}
|
||||
}
|
||||
|
||||
void smlOBISWh(double &wh)
|
||||
{
|
||||
long long int val;
|
||||
smlOBISByUnit(val, sc, SML_WATT_HOUR);
|
||||
wh = val;
|
||||
smlPow(wh, sc);
|
||||
}
|
||||
|
||||
void smlOBISW(double &w)
|
||||
{
|
||||
long long int val;
|
||||
smlOBISByUnit(val, sc, SML_WATT);
|
||||
w = val;
|
||||
smlPow(w, sc);
|
||||
}
|
||||
|
||||
void smlOBISVolt(double &v)
|
||||
{
|
||||
long long int val;
|
||||
smlOBISByUnit(val, sc, SML_VOLT);
|
||||
v = val;
|
||||
smlPow(v, sc);
|
||||
}
|
||||
|
||||
void smlOBISAmpere(double &a)
|
||||
{
|
||||
long long int val;
|
||||
smlOBISByUnit(val, sc, SML_AMPERE);
|
||||
a = val;
|
||||
smlPow(a, sc);
|
||||
}
|
||||
106
lib/SMLParser/sml.h
Normal file
106
lib/SMLParser/sml.h
Normal file
@ -0,0 +1,106 @@
|
||||
#ifndef SML_H
|
||||
#define SML_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
SML_START,
|
||||
SML_END,
|
||||
SML_VERSION,
|
||||
SML_NEXT,
|
||||
SML_LISTSTART,
|
||||
SML_LISTEND,
|
||||
SML_LISTEXTENDED,
|
||||
SML_DATA,
|
||||
SML_HDATA,
|
||||
SML_DATAEND,
|
||||
SML_BLOCKSTART,
|
||||
SML_BLOCKEND,
|
||||
SML_CHECKSUM,
|
||||
SML_CHECKSUM_ERROR, /* calculated checksum does not match */
|
||||
SML_UNEXPECTED, /* unexpected byte received */
|
||||
SML_FINAL, /* final state, checksum OK */
|
||||
SML_DATA_SIGNED_INT,
|
||||
SML_DATA_UNSIGNED_INT,
|
||||
SML_DATA_OCTET_STRING,
|
||||
} sml_states_t;
|
||||
|
||||
typedef enum {
|
||||
SML_YEAR = 1,
|
||||
SML_MONTH = 2,
|
||||
SML_WEEK = 3,
|
||||
SML_DAY = 4,
|
||||
SML_HOUR = 5,
|
||||
SML_MIN = 6,
|
||||
SML_SECOND = 7,
|
||||
SML_DEGREE = 8,
|
||||
SML_DEGREE_CELSIUS = 9,
|
||||
SML_CURRENCY = 10,
|
||||
SML_METRE = 11,
|
||||
SML_METRE_PER_SECOND = 12,
|
||||
SML_CUBIC_METRE = 13,
|
||||
SML_CUBIC_METRE_CORRECTED = 14,
|
||||
SML_CUBIC_METRE_PER_HOUR = 15,
|
||||
SML_CUBIC_METRE_PER_HOUR_CORRECTED = 16,
|
||||
SML_CUBIC_METRE_PER_DAY = 17,
|
||||
SML_CUBIC_METRE_PER_DAY_CORRECTED = 18,
|
||||
SML_LITRE = 19,
|
||||
SML_KILOGRAM = 20,
|
||||
SML_NEWTON = 21,
|
||||
SML_NEWTONMETER = 22,
|
||||
SML_PASCAL = 23,
|
||||
SML_BAR = 24,
|
||||
SML_JOULE = 25,
|
||||
SML_JOULE_PER_HOUR = 26,
|
||||
SML_WATT = 27,
|
||||
SML_VOLT_AMPERE = 28,
|
||||
SML_VAR = 29,
|
||||
SML_WATT_HOUR = 30,
|
||||
SML_VOLT_AMPERE_HOUR = 31,
|
||||
SML_VAR_HOUR = 32,
|
||||
SML_AMPERE = 33,
|
||||
SML_COULOMB = 34,
|
||||
SML_VOLT = 35,
|
||||
SML_VOLT_PER_METRE = 36,
|
||||
SML_FARAD = 37,
|
||||
SML_OHM = 38,
|
||||
SML_OHM_METRE = 39,
|
||||
SML_WEBER = 40,
|
||||
SML_TESLA = 41,
|
||||
SML_AMPERE_PER_METRE = 42,
|
||||
SML_HENRY = 43,
|
||||
SML_HERTZ = 44,
|
||||
SML_ACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE = 45,
|
||||
SML_REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE = 46,
|
||||
SML_APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE = 47,
|
||||
SML_VOLT_SQUARED_HOURS = 48,
|
||||
SML_AMPERE_SQUARED_HOURS = 49,
|
||||
SML_KILOGRAM_PER_SECOND = 50,
|
||||
SML_KELVIN = 52,
|
||||
SML_VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE = 53,
|
||||
SML_AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE = 54,
|
||||
SML_METER_CONSTANT_OR_PULSE_VALUE = 55,
|
||||
SML_PERCENTAGE = 56,
|
||||
SML_AMPERE_HOUR = 57,
|
||||
SML_ENERGY_PER_VOLUME = 60,
|
||||
SML_CALORIFIC_VALUE = 61,
|
||||
SML_MOLE_PERCENT = 62,
|
||||
SML_MASS_DENSITY = 63,
|
||||
SML_PASCAL_SECOND = 64,
|
||||
SML_RESERVED = 253,
|
||||
SML_OTHER_UNIT = 254,
|
||||
SML_COUNT = 255
|
||||
} sml_units_t;
|
||||
|
||||
sml_states_t smlState(unsigned char &byte);
|
||||
bool smlOBISCheck(const unsigned char *obis);
|
||||
void smlOBISManufacturer(unsigned char *str, int maxSize);
|
||||
void smlOBISByUnit(long long int &wh, signed char &scaler, sml_units_t unit);
|
||||
|
||||
// Be aware that double on Arduino UNO is just 32 bit
|
||||
void smlOBISWh(double &wh);
|
||||
void smlOBISW(double &w);
|
||||
void smlOBISVolt(double &v);
|
||||
void smlOBISAmpere(double &a);
|
||||
|
||||
#endif
|
||||
42
lib/SMLParser/smlCrcTable.h
Normal file
42
lib/SMLParser/smlCrcTable.h
Normal file
@ -0,0 +1,42 @@
|
||||
#ifndef SML_CRC_TABLE_H
|
||||
#define SML_CRC_TABLE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
static const uint16_t smlCrcTable[256] PROGMEM =
|
||||
#else
|
||||
static const uint16_t smlCrcTable[256] =
|
||||
#endif
|
||||
{0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF, 0x8C48,
|
||||
0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7, 0x1081, 0x0108,
|
||||
0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E, 0x9CC9, 0x8D40, 0xBFDB,
|
||||
0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876, 0x2102, 0x308B, 0x0210, 0x1399,
|
||||
0x6726, 0x76AF, 0x4434, 0x55BD, 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E,
|
||||
0xFAE7, 0xC87C, 0xD9F5, 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E,
|
||||
0x54B5, 0x453C, 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD,
|
||||
0xC974, 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
|
||||
0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3, 0x5285,
|
||||
0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A, 0xDECD, 0xCF44,
|
||||
0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72, 0x6306, 0x728F, 0x4014,
|
||||
0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9, 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5,
|
||||
0xA96A, 0xB8E3, 0x8A78, 0x9BF1, 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3,
|
||||
0x242A, 0x16B1, 0x0738, 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862,
|
||||
0x9AF9, 0x8B70, 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E,
|
||||
0xF0B7, 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
|
||||
0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036, 0x18C1,
|
||||
0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E, 0xA50A, 0xB483,
|
||||
0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5, 0x2942, 0x38CB, 0x0A50,
|
||||
0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD, 0xB58B, 0xA402, 0x9699, 0x8710,
|
||||
0xF3AF, 0xE226, 0xD0BD, 0xC134, 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7,
|
||||
0x6E6E, 0x5CF5, 0x4D7C, 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1,
|
||||
0xA33A, 0xB2B3, 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72,
|
||||
0x3EFB, 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
|
||||
0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A, 0xE70E,
|
||||
0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1, 0x6B46, 0x7ACF,
|
||||
0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9, 0xF78F, 0xE606, 0xD49D,
|
||||
0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330, 0x7BC7, 0x6A4E, 0x58D5, 0x495C,
|
||||
0x3DE3, 0x2C6A, 0x1EF1, 0x0F78};
|
||||
|
||||
#endif
|
||||
@ -27,12 +27,13 @@ build_unflags =
|
||||
|
||||
lib_deps =
|
||||
https://github.com/yubox-node-org/ESPAsyncWebServer
|
||||
bblanchon/ArduinoJson @ ^6.21.0
|
||||
bblanchon/ArduinoJson @ ^6.21.1
|
||||
https://github.com/bertmelis/espMqttClient.git#v1.4.2
|
||||
nrf24/RF24 @ ^1.4.5
|
||||
olikraus/U8g2 @ ^2.34.16
|
||||
olikraus/U8g2 @ ^2.34.17
|
||||
buelowp/sunset @ ^1.1.7
|
||||
https://github.com/coryjfowler/MCP_CAN_lib
|
||||
plerup/EspSoftwareSerial@^8.0.1
|
||||
mobizt/FirebaseJson @ ^3.0.6
|
||||
|
||||
extra_scripts =
|
||||
|
||||
@ -10,11 +10,14 @@
|
||||
#include "SDM.h"
|
||||
#include "MessageOutput.h"
|
||||
#include <ctime>
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
PowerMeterClass PowerMeter;
|
||||
|
||||
SDM sdm(Serial2, 9600, NOT_A_PIN, SERIAL_8N1, SDM_RX_PIN, SDM_TX_PIN);
|
||||
|
||||
SoftwareSerial inputSerial;
|
||||
|
||||
void PowerMeterClass::init()
|
||||
{
|
||||
using std::placeholders::_1;
|
||||
@ -29,7 +32,11 @@ void PowerMeterClass::init()
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (config.PowerMeter_Enabled && config.PowerMeter_Source == 0) {
|
||||
if (!config.PowerMeter_Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.PowerMeter_Source == SOURCE_MQTT) {
|
||||
if (strlen(config.PowerMeter_MqttTopicPowerMeter1) > 0) {
|
||||
MqttSettings.subscribe(config.PowerMeter_MqttTopicPowerMeter1, 0, std::bind(&PowerMeterClass::onMqttMessage, this, _1, _2, _3, _4, _5, _6));
|
||||
}
|
||||
@ -43,16 +50,30 @@ void PowerMeterClass::init()
|
||||
}
|
||||
}
|
||||
|
||||
mqttInitDone = true;
|
||||
|
||||
if(config.PowerMeter_Source == SOURCE_SDM1PH || config.PowerMeter_Source == SOURCE_SDM3PH) {
|
||||
sdm.begin();
|
||||
}
|
||||
|
||||
if (config.PowerMeter_Source == SOURCE_HTTP) {
|
||||
HttpPowerMeter.init();
|
||||
}
|
||||
|
||||
if (config.PowerMeter_Source == SOURCE_SML) {
|
||||
pinMode(SML_RX_PIN, INPUT);
|
||||
inputSerial.begin(9600, SWSERIAL_8N1, SML_RX_PIN, -1, false, 128, 95);
|
||||
inputSerial.enableRx(true);
|
||||
inputSerial.enableTx(false);
|
||||
inputSerial.flush();
|
||||
}
|
||||
|
||||
mqttInitDone = true;
|
||||
}
|
||||
|
||||
void PowerMeterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if (config.PowerMeter_Enabled && config.PowerMeter_Source != SOURCE_MQTT) {
|
||||
|
||||
if (!config.PowerMeter_Enabled || config.PowerMeter_Source != SOURCE_MQTT) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -96,8 +117,8 @@ void PowerMeterClass::mqtt()
|
||||
MqttSettings.publish(topic + "/voltage1", String(_powerMeter1Voltage));
|
||||
MqttSettings.publish(topic + "/voltage2", String(_powerMeter2Voltage));
|
||||
MqttSettings.publish(topic + "/voltage3", String(_powerMeter3Voltage));
|
||||
MqttSettings.publish(topic + "/import", String(_PowerMeterImport));
|
||||
MqttSettings.publish(topic + "/export", String(_PowerMeterExport));
|
||||
MqttSettings.publish(topic + "/import", String(_powerMeterImport));
|
||||
MqttSettings.publish(topic + "/export", String(_powerMeterExport));
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +126,12 @@ void PowerMeterClass::loop()
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (config.PowerMeter_Enabled && config.PowerMeter_Source == SOURCE_SML) {
|
||||
if (!smlReadLoop()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.PowerMeter_Enabled
|
||||
|| (millis() - _lastPowerMeterCheck) < (config.PowerMeter_Interval * 1000)) {
|
||||
return;
|
||||
@ -112,15 +139,15 @@ void PowerMeterClass::loop()
|
||||
|
||||
uint8_t _address = config.PowerMeter_SdmAddress;
|
||||
|
||||
if (config.PowerMeter_Source== SOURCE_SDM1PH) {
|
||||
if (config.PowerMeter_Source == SOURCE_SDM1PH) {
|
||||
_powerMeter1Power = static_cast<float>(sdm.readVal(SDM_PHASE_1_POWER, _address));
|
||||
_powerMeter2Power = 0.0;
|
||||
_powerMeter3Power = 0.0;
|
||||
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
||||
_powerMeter2Voltage = 0.0;
|
||||
_powerMeter3Voltage = 0.0;
|
||||
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
_powerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_powerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
else if (config.PowerMeter_Source == SOURCE_SDM3PH) {
|
||||
@ -130,8 +157,8 @@ void PowerMeterClass::loop()
|
||||
_powerMeter1Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_1_VOLTAGE, _address));
|
||||
_powerMeter2Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_2_VOLTAGE, _address));
|
||||
_powerMeter3Voltage = static_cast<float>(sdm.readVal(SDM_PHASE_3_VOLTAGE, _address));
|
||||
_PowerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_PowerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
_powerMeterImport = static_cast<float>(sdm.readVal(SDM_IMPORT_ACTIVE_ENERGY, _address));
|
||||
_powerMeterExport = static_cast<float>(sdm.readVal(SDM_EXPORT_ACTIVE_ENERGY, _address));
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
else if (config.PowerMeter_Source == SOURCE_HTTP) {
|
||||
@ -149,3 +176,24 @@ void PowerMeterClass::loop()
|
||||
|
||||
_lastPowerMeterCheck = millis();
|
||||
}
|
||||
|
||||
bool PowerMeterClass::smlReadLoop()
|
||||
{
|
||||
while (inputSerial.available()) {
|
||||
double readVal = 0;
|
||||
unsigned char smlCurrentChar = inputSerial.read();
|
||||
sml_states_t smlCurrentState = smlState(smlCurrentChar);
|
||||
if (smlCurrentState == SML_LISTEND) {
|
||||
for (auto& handler: smlHandlerList) {
|
||||
if (smlOBISCheck(handler.OBIS)) {
|
||||
handler.Fn(readVal);
|
||||
*handler.Arg = readVal;
|
||||
}
|
||||
}
|
||||
} else if (smlCurrentState == SML_FINAL) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ $t('firmwareinfo.FirmwareVersion') }}</th>
|
||||
<td><a :href="'https://github.com/tbnobody/OpenDTU/commits/' + systemStatus.git_hash"
|
||||
<td><a :href="versionInfoUrl"
|
||||
target="_blank" v-tooltip :title="$t('firmwareinfo.FirmwareVersionHint')">
|
||||
{{ systemStatus.git_hash }}
|
||||
</a></td>
|
||||
@ -72,6 +72,12 @@ export default defineComponent({
|
||||
return timestampToString(value, true);
|
||||
};
|
||||
},
|
||||
versionInfoUrl(): string {
|
||||
if (this.systemStatus.git_is_hash) {
|
||||
return 'https://github.com/tbnobody/OpenDTU/commits/' + this.systemStatus.git_hash;
|
||||
}
|
||||
return 'https://github.com/tbnobody/OpenDTU/releases/tag/' + this.systemStatus.git_hash;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -470,6 +470,7 @@
|
||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
||||
"typeHTTP": "HTTP(S) + JSON",
|
||||
"typeSML": "SML (OBIS 16.7.0)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Stromzähler #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Stromzähler #2 (Optional)",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Stromzähler #3 (Optional)",
|
||||
|
||||
@ -470,6 +470,7 @@
|
||||
"typeSDM1ph": "SDM 1 phase (SDM120/220/230)",
|
||||
"typeSDM3ph": "SDM 3 phase (SDM72/630)",
|
||||
"typeHTTP": "HTTP(s) + JSON",
|
||||
"typeSML": "SML (OBIS 16.7.0)",
|
||||
"MqttTopicPowerMeter1": "MQTT topic - Power meter #1",
|
||||
"MqttTopicPowerMeter2": "MQTT topic - Power meter #2",
|
||||
"MqttTopicPowerMeter3": "MQTT topic - Power meter #3",
|
||||
|
||||
@ -9,6 +9,7 @@ export interface SystemStatus {
|
||||
sdkversion: string;
|
||||
config_version: string;
|
||||
git_hash: string;
|
||||
git_is_hash: boolean;
|
||||
resetreason_0: string;
|
||||
resetreason_1: string;
|
||||
cfgsavecount: number;
|
||||
|
||||
@ -215,10 +215,11 @@ export default defineComponent({
|
||||
dataLoading: true,
|
||||
powerMeterConfigList: {} as PowerMeterConfig,
|
||||
powerMeterSourceList: [
|
||||
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
|
||||
{ key: 0, value: this.$t('powermeteradmin.typeMQTT') },
|
||||
{ key: 1, value: this.$t('powermeteradmin.typeSDM1ph') },
|
||||
{ key: 2, value: this.$t('powermeteradmin.typeSDM3ph') },
|
||||
{ key: 3, value: this.$t('powermeteradmin.typeHTTP') },
|
||||
{ key: 4, value: this.$t('powermeteradmin.typeSML') },
|
||||
],
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
|
||||
@ -51,11 +51,13 @@ export default defineComponent({
|
||||
},
|
||||
getUpdateInfo() {
|
||||
// If the left char is a "g" the value is the git hash (remove the "g")
|
||||
this.systemDataList.git_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g' ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
|
||||
this.systemDataList.git_is_hash = this.systemDataList.git_hash?.substring(0, 1) == 'g';
|
||||
this.systemDataList.git_hash = this.systemDataList.git_is_hash ? this.systemDataList.git_hash?.substring(1) : this.systemDataList.git_hash;
|
||||
|
||||
// Handle format "v0.1-5-gabcdefh"
|
||||
if (this.systemDataList.git_hash.lastIndexOf("-") >= 0) {
|
||||
this.systemDataList.git_hash = this.systemDataList.git_hash.substring(this.systemDataList.git_hash.lastIndexOf("-") + 2)
|
||||
this.systemDataList.git_is_hash = true;
|
||||
}
|
||||
|
||||
const fetchUrl = "https://api.github.com/repos/tbnobody/OpenDTU/compare/"
|
||||
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user