Browse Source

add SML

pull/1155/head
DanielR92 2 years ago
parent
commit
584ca7945d
  1. 2
      src/config/settings.h
  2. 405
      src/plugins/zeroExport/SML.cpp
  3. 106
      src/plugins/zeroExport/SML.h
  4. 42
      src/plugins/zeroExport/SMLCRCTable.h
  5. 3
      src/plugins/zeroExport/zeroExport.h
  6. 6
      src/web/html/setup.html
  7. 8
      src/web/web.h

2
src/config/settings.h

@ -146,7 +146,7 @@ typedef struct {
unsigned long lastTime; // tic toc
double max_power;
bool two_percent; // ask if not go lower then 2%
char tibber_pw[ZEXPORT_ADDR_LEN]; // needed for tibber QWGH-ED12
char tibber_pw[10]; // needed for tibber QWGH-ED12
} cfgzeroExport_t;
#endif

405
src/plugins/zeroExport/SML.cpp

@ -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 &currentByte)
{
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
src/plugins/zeroExport/SML.h

@ -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
src/plugins/zeroExport/SMLCRCTable.h

@ -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

3
src/plugins/zeroExport/zeroExport.h

@ -7,6 +7,7 @@
#include <string.h>
#include "AsyncJson.h"
#include "SML.h"
template <class HMSYSTEM>
class ZeroExport {
@ -73,6 +74,8 @@ class ZeroExport {
String responseBody = httpClient.getString();
DynamicJsonDocument json(2048);
DeserializationError err = deserializeJson(json, responseBody);
sml_state sml = smlState(httpClient.getStream());
smlOBISManufacturer
// Parse succeeded?
if (err) {

6
src/web/html/setup.html

@ -312,13 +312,11 @@
<div class="col-8 col-sm-3">Enable zero export</div>
<div class="col-4 col-sm-9"><input type="checkbox" name="en_zeroexport"/></div>
<p>Please select your favorite query interface:</p>
<input type="radio" id="html" name="device" value=0>Shelly<br>
<input type="radio" id="css" name="device" value=1>Tibber<br>
<input type="radio" id="javascript" name="device" value=2>Volkszähler
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Monitor IP: </div>
<div class="col-12 col-sm-9"><input type="text" name="monitor_url" maxlength="100">A JSON-Format is required to work properly.<br>
HICHI: http://IP_Address/cm?cmnd=status%208<br>
Shelly: http://IP_Address/status</div>
@ -1135,7 +1133,7 @@
getAjax("/api/inverter/list", parseZeroIv);
for(var i of [["monitor_url", "monitor_url"], ["power_avg", "power_avg"], ["count_avg", "count_avg"], ["json_path", "json_path"], ["max_power", "max_power"]])
for(var i of [["monitor_url", "monitor_url"], ["power_avg", "power_avg"], ["count_avg", "count_avg"], ["json_path", "json_path"], ["max_power", "max_power"], ["tibber_pw", "tibber_pw"]])
if(null != obj[i[1]])
document.getElementsByName(i[0])[0].value = obj[i[1]];

8
src/web/web.h

@ -581,12 +581,20 @@ class Web {
addr.toCharArray(mConfig->plugin.zexport.monitor_url, ZEXPORT_ADDR_LEN);
} else
mConfig->plugin.zexport.monitor_url[0] = '\0';
if (request->arg("json_path") != "") {
String addr = request->arg("json_path");
addr.trim();
addr.toCharArray(mConfig->plugin.zexport.json_path, ZEXPORT_ADDR_LEN);
} else
mConfig->plugin.zexport.json_path[0] = '\0';
if (request->arg("tibber_pw") != "") {
String addr = request->arg("tibber_pw");
addr.trim();
addr.toCharArray(mConfig->plugin.zexport.tibber_pw, 10);
} else
mConfig->plugin.zexport.tibber_pw[0] = '\0';
#endif
// serial console

Loading…
Cancel
Save