You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1061 lines
45 KiB

#include <string.h>
#include <stdlib.h>
#include <HardwareSerial.h>
#include <user_interface.h>
#include "../utils/dbg.h"
#include "../utils/scheduler.h"
#include "../config/settings.h"
#include "SML_OBIS_Parser.h"
#ifdef AHOY_SML_OBIS_SUPPORT
// you might use this testwise if you dont have an IR sensor connected to your AHOY-DTU
// #define SML_OBIS_TEST
// at least the size of the largest entry that is of any interest
#define SML_MAX_SERIAL_BUF 32
#ifndef min
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#define SML_ESCAPE_CHAR 0x1b
#define SML_VERSION1_CHAR 0x01
#define SML_MAX_LIST_LAYER 8
#define SML_EXT_LENGTH 0x80
#define SML_OBIS_GRID_POWER_PATH AHOY_HIST_PATH "/grid_power"
#define SML_OBIS_FORMAT_FILE_NAME "%02u_%02u_%04u.bin"
#define SML_MSG_NONE 0
#define SML_MSG_GET_LIST_RSP 0x701
#define OBIS_SIG_YIELD_IN_ALL "\x01\x08\x00"
#define OBIS_SIG_YIELD_OUT_ALL "\x02\x08\x00"
#define OBIS_SIG_POWER_ALL "\x10\x07\x00"
#define OBIS_SIG_POWER_L1 "\x24\x07\x00"
#define OBIS_SIG_POWER_L2 "\x38\x07\x00"
#define OBIS_SIG_POWER_L3 "\x4c\x07\x00"
/* the folloing OBIS objects may not be transmitted by your electricity meter */
#define OBIS_SIG_VOLTAGE_L1 "\x20\x07\x00"
#define OBIS_SIG_VOLTAGE_L2 "\x34\x07\x00"
#define OBIS_SIG_VOLTAGE_L3 "\x48\x07\x00"
#define OBIS_SIG_CURRENT_L1 "\x1f\x07\x00"
#define OBIS_SIG_CURRENT_L2 "\x33\x07\x00"
#define OBIS_SIG_CURRENT_L3 "\x47\x07\x00"
typedef enum _sml_state {
SML_ST_FIND_START_TAG = 0,
SML_ST_FIND_VERSION,
SML_ST_FIND_MSG,
SML_ST_FIND_LIST_ENTRIES,
SML_ST_SKIP_LIST_ENTRY,
SML_ST_FIND_END_TAG,
SML_ST_CHECK_CRC
} sml_state_t;
typedef enum _sml_list_entry_type {
SML_TYPE_OCTET_STRING = 0x00,
SML_TYPE_BOOL = 0x40,
SML_TYPE_INT = 0x50,
SML_TYPE_UINT = 0x60,
SML_TYPE_LIST = 0x70
} sml_list_entry_type_t;
typedef enum _obis_state {
OBIS_ST_NONE = 0,
OBIS_ST_SERIAL_NR,
OBIS_ST_YIELD_IN_ALL,
OBIS_ST_YIELD_OUT_ALL,
OBIS_ST_POWER_ALL,
OBIS_ST_POWER_L1,
OBIS_ST_POWER_L2,
OBIS_ST_POWER_L3,
OBIS_ST_VOLTAGE_L1,
OBIS_ST_VOLTAGE_L2,
OBIS_ST_VOLTAGE_L3,
OBIS_ST_CURRENT_L1,
OBIS_ST_CURRENT_L2,
OBIS_ST_CURRENT_L3,
OBIS_ST_UNKNOWN
} obis_state_t;
static sml_state_t sml_state = SML_ST_FIND_START_TAG;
static uint16_t cur_sml_list_layer;
static unsigned char sml_list_layer_entries [SML_MAX_LIST_LAYER];
static unsigned char sml_serial_buf[SML_MAX_SERIAL_BUF];
static unsigned char *cur_serial_buf = sml_serial_buf;
static uint16 sml_serial_len = 0;
static uint16 sml_skip_len = 0;
static uint32 sml_message = SML_MSG_NONE;
static obis_state_t obis_state = OBIS_ST_NONE;
static int obis_power_all_scale, obis_power_all_value;
/* design: max 16 bit fuer aktuelle Powerwerte */
static int16_t obis_cur_pac;
static uint16_t sml_telegram_crc;
static uint16_t sml_msg_crc;
static bool sml_msg_failure;
static uint16_t obis_cur_pac_cnt;
static uint16_t obis_cur_pac_index;
static int32_t obis_pac_sum;
static uint32_t *obis_timestamp;
static int obis_yield_in_all_scale, obis_yield_out_all_scale;
static uint64_t obis_yield_in_all_value, obis_yield_out_all_value;
static bool sml_trace_obis = false;
static IApp *mApp;
const unsigned char version_seq[] = { SML_VERSION1_CHAR, SML_VERSION1_CHAR, SML_VERSION1_CHAR, SML_VERSION1_CHAR };
const unsigned char esc_seq[] = {SML_ESCAPE_CHAR, SML_ESCAPE_CHAR, SML_ESCAPE_CHAR, SML_ESCAPE_CHAR};
#ifdef SML_OBIS_TEST
static size_t sml_test_telegram_offset;
const unsigned char sml_test_telegram[] = {
0x1b, 0x1b, 0x1b, 0x1b, // Escape sequence
0x01, 0x01, 0x01, 0x01, // Version 1
0x76, // List with 6 enties (1st SML message of this telegram)
0x05, 0x03, 0x2b, 0x18, 0x20,
0x62, 0x00,
0x62, 0x00,
0x72,
0x63, 0x01, 0x01, // Message type: OpenResponse
0x76,
0x01,
0x01,
0x05, 0x01, 0x0e, 0x5d, 0x5b,
0x0b, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01,
0x01,
0x63, 0xea, 0xbf, // msg crc (adjust to your needs)
0x00,
0x76, // List with 6 entries (2. SML mesaage of this telegram)
0x05, 0x03, 0x2b, 0x18, 0x21,
0x62, 0x00,
0x62, 0x00,
0x72,
0x63, 0x07, 0x01, // Message type: GetListResponse
0x77,
0x01,
0x0b, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x07, 0x01, 0x00, 0x62, 0x0a, 0xff, 0xff,
0x72,
0x62, 0x01,
0x65, 0x02, 0x1a, 0x58, 0x7f,
0x7a,
0x77,
0x07, 0x01, 0x00, 0x01, 0x08, 0x00, 0xff, // OBIS: Energy in overall - no tarif
0x65, 0x00, 0x01, 0x01, 0x80,
0x01,
0x62, 0x1e, // "Wh"
0x52, 0xff, // scaler 0.1
0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xc3, 0x05, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x01, 0x08, 0x01, 0xff, // OBIS: Energy in - tarif 1
0x01,
0x01,
0x62, 0x1e, // "Wh"
0x52, 0xff, // scaler 0.1
0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xc3, 0x05, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x01, 0x08, 0x02, 0xff, // OBIS: Energy in - tarif 2
0x01,
0x01,
0x62, 0x1e, // "Wh"
0x52, 0xff, // scaler 0.1
0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x02, 0x08, 0x00, 0xff, // OBIS: energy out overall - no tarif
0x01,
0x01,
0x62, 0x1e, // "Wh"
0x52, 0xff, // scaler 0.1
0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x02, 0x08, 0x01, 0xff, // OBIS: energy out - tarif 1
0x01,
0x01,
0x62, 0x1e, // "Wh"
0x52, 0xff, // scaler 0.1
0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x02, 0x08, 0x02, 0xff, // OBIS: energy out - tarif 2
0x01,
0x01,
0x62, 0x1e, // "Wh"
0x52, 0xff, // scaler 0.1
0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x10, 0x07, 0x00, 0xff, // OBIS: power overall
0x01,
0x01,
0x62, 0x1b, // "W"
0x52, 0x00, // scaler 1
0x55, 0x00, 0x00, 0x00, 0x2a, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x24, 0x07, 0x00, 0xff, // OBIS: power L1
0x01,
0x01,
0x62, 0x1b, // "W"
0x52, 0x00, // scaler 1
0x55, 0x00, 0x00, 0x00, 0x2a, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x38, 0x07, 0x00, 0xff, // OBIS: power L2
0x01,
0x01,
0x62, 0x1b, // "W"
0x52, 0x00, // scaler 1
0x55, 0x00, 0x00, 0x00, 0x00, // value
0x01,
0x77,
0x07, 0x01, 0x00, 0x4c, 0x07, 0x00, 0xff, // OBIS: power L3
0x01,
0x01,
0x62, 0x1b, // "W"
0x52, 0x00, // scaler 1
0x55, 0x00, 0x00, 0x00, 0x00, // value
0x01,
0x01,
0x01,
0x63, 0x43, 0x92, // msg crc (adjust to your needs)
0x00,
0x76, // List with 6 entries (3rd SML message of this telegram)
0x05, 0x03, 0x2b, 0x18, 0x22,
0x62, 0x00,
0x62, 0x00,
0x72,
0x63, 0x02, 0x01, // Message type: CloseResponse
0x71,
0x01,
0x63, 0x86, 0x5b, // msg crc (adjust to your needs)
0x00,
0x1b, 0x1b, 0x1b, 0x1b, // Escape sequence
0x1a, 0x00, 0xd9, 0x66 // 1a + number of fill bytes + CRC16 of telegram (change this to your needs)
};
#endif
//-----------------------------------------------------------------------------
// DIN EN 62056-46, Polynom 0x1021
static const uint16_t sml_crctab[256] = {
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};
//-----------------------------------------------------------------------------
uint16_t sml_init_crc ()
{
return 0xffff;
}
//-----------------------------------------------------------------------------
uint16_t sml_calc_crc (uint16_t crc, unsigned int len, unsigned char *data)
{
while (len--) {
crc = (crc >> 8) ^ sml_crctab[(crc ^ *data++) & 0xff];
}
return crc;
}
//-----------------------------------------------------------------------------
uint16_t sml_finit_crc (uint16_t crc)
{
crc ^= 0xffff;
crc = (crc << 8) | (crc >> 8);
return crc;
}
//-----------------------------------------------------------------------------
void sml_set_trace_obis (bool trace_flag)
{
sml_trace_obis = trace_flag;
}
//-----------------------------------------------------------------------------
void sml_cleanup_history ()
{
time_t time_today;
obis_cur_pac = 0;
obis_cur_pac_cnt = 0;
obis_cur_pac_index = 0;
obis_pac_sum = 0;
if ((time_today = *obis_timestamp)) {
Dir grid_power_dir;
char cur_file_name[sizeof (SML_OBIS_FORMAT_FILE_NAME)];
time_today = gTimezone.toLocal (time_today);
snprintf (cur_file_name, sizeof (cur_file_name), SML_OBIS_FORMAT_FILE_NAME,
day(time_today), month (time_today), year (time_today));
grid_power_dir = LittleFS.openDir (SML_OBIS_GRID_POWER_PATH);
/* design: no dataserver, cleanup old history */
while (grid_power_dir.next()) {
if (grid_power_dir.fileName() != cur_file_name) {
DPRINTLN (DBG_INFO, "Remove file " + grid_power_dir.fileName() +
", Size: " + String (grid_power_dir.fileSize()));
LittleFS.remove (SML_OBIS_GRID_POWER_PATH "/" + grid_power_dir.fileName());
}
}
} else {
DPRINTLN (DBG_WARN, "sml_cleanup_history, no time yet");
}
}
//-----------------------------------------------------------------------------
File sml_open_hist ()
{
time_t time_today;
File file = (File) NULL;
if ((time_today = *obis_timestamp)) {
char file_name[sizeof (SML_OBIS_GRID_POWER_PATH) + sizeof (SML_OBIS_FORMAT_FILE_NAME)];
time_today = gTimezone.toLocal(time_today);
snprintf (file_name, sizeof (file_name), SML_OBIS_GRID_POWER_PATH "/" SML_OBIS_FORMAT_FILE_NAME,
day(time_today), month(time_today), year(time_today));
file = LittleFS.open (file_name, "r");
if (!file) {
DPRINT (DBG_WARN, "sml_open_hist, failed to open ");
DBGPRINTLN (file_name);
}
} else {
DPRINTLN (DBG_WARN, "sml_open_history, no time yet");
}
return file;
}
//-----------------------------------------------------------------------------
void sml_close_hist (File file)
{
if (file) {
file.close ();
}
}
//-----------------------------------------------------------------------------
int sml_find_hist_power (File file, uint16_t index)
{
if (file) {
size_t len;
uint16_t cmp_index = 0; /* init wegen Compilerwarnung */
unsigned char data[4];
while ((len = file.read (data, sizeof (data))) == sizeof (data)) {
cmp_index = data[0] + (data[1] << 8);
if (cmp_index >= index) {
break;
}
// yield(); /* do not do this here: seems to cause hanger */
}
if (len < sizeof (data)) {
if (index == obis_cur_pac_index) {
return sml_get_obis_pac_average ();
}
DPRINTLN (DBG_DEBUG, "sml_find_hist_power(1), cant find " + String (index));
return INT32_MIN;
}
if (cmp_index == index) {
return (int16_t)(data[2] + (data[3] << 8));
}
DPRINTLN (DBG_DEBUG, "sml_find_hist_power(2), cant find " + String (index) + ", found " + String (cmp_index));
file.seek (file.position() - sizeof (data));
} else if ((index == obis_cur_pac_index) && obis_cur_pac_cnt) {
return sml_get_obis_pac_average ();
}
return INT32_MIN;
}
//-----------------------------------------------------------------------------
void sml_setup (IApp *app, uint32_t *timestamp)
{
obis_timestamp = timestamp;
mApp = app;
}
//-----------------------------------------------------------------------------
int16_t sml_get_obis_pac ()
{
return obis_cur_pac;
}
//-----------------------------------------------------------------------------
void sml_handle_obis_state (unsigned char *buf)
{
#ifdef undef
if (sml_trace_obis) {
DPRINTLN(DBG_INFO, "OBIS " + String(buf[0], HEX) + "-" + String(buf[1], HEX) + ":" + String(buf[2], HEX) +
"." + String (buf[3], HEX) + "." + String(buf[4], HEX) + "*" + String(buf[5], HEX));
}
#endif
if (sml_message == SML_MSG_GET_LIST_RSP) {
if (buf[0] == 1) {
if (!memcmp (&buf[2], OBIS_SIG_YIELD_IN_ALL, 3)) {
obis_state = OBIS_ST_YIELD_IN_ALL;
} else if (!memcmp (&buf[2], OBIS_SIG_YIELD_OUT_ALL, 3)) {
obis_state = OBIS_ST_YIELD_OUT_ALL;
} else if (!memcmp (&buf[2], OBIS_SIG_POWER_ALL, 3)) {
obis_state = OBIS_ST_POWER_ALL;
} else if (!memcmp (&buf[2], OBIS_SIG_POWER_L1, 3)) {
obis_state = OBIS_ST_POWER_L1;
} else if (!memcmp (&buf[2], OBIS_SIG_POWER_L2, 3)) {
obis_state = OBIS_ST_POWER_L2;
} else if (!memcmp (&buf[2], OBIS_SIG_POWER_L3, 3)) {
obis_state = OBIS_ST_POWER_L3;
} else if (!memcmp (&buf[2], OBIS_SIG_CURRENT_L1, 3)) {
obis_state = OBIS_ST_CURRENT_L1;
} else if (!memcmp (&buf[2], OBIS_SIG_CURRENT_L2, 3)) {
obis_state = OBIS_ST_CURRENT_L2;
} else if (!memcmp (&buf[2], OBIS_SIG_CURRENT_L3, 3)) {
obis_state = OBIS_ST_CURRENT_L3;
} else if (!memcmp (&buf[2], OBIS_SIG_VOLTAGE_L1, 3)) {
obis_state = OBIS_ST_VOLTAGE_L1;
} else if (!memcmp (&buf[2], OBIS_SIG_VOLTAGE_L2, 3)) {
obis_state = OBIS_ST_VOLTAGE_L2;
} else if (!memcmp (&buf[2], OBIS_SIG_VOLTAGE_L3, 3)) {
obis_state = OBIS_ST_VOLTAGE_L3;
} else {
obis_state = OBIS_ST_UNKNOWN;
}
} else {
obis_state = OBIS_ST_UNKNOWN;
}
}
}
//-----------------------------------------------------------------------------
int64_t sml_obis_get_uint (unsigned char *data, unsigned int len)
{
int64_t value = 0;
if (len > 8) {
DPRINTLN(DBG_WARN, "Int too big");
} else {
unsigned int i;
for (i=0; i<len; i++) {
value = (value << 8) | *data++;
}
}
return value;
}
//-----------------------------------------------------------------------------
int sml_obis_scale_uint (uint64_t value, int scale)
{
if (scale > 0) {
value = value * (10 * scale);
} else if (scale < 0) {
value = value / (10 * -scale);
}
return (int)value;
}
//-----------------------------------------------------------------------------
int64_t sml_obis_get_int (unsigned char *data, unsigned int len)
{
int64_t value = 0;
if (len > 8) {
DPRINTLN(DBG_WARN, "Int too big");
} else {
unsigned int i;
if ((len > 0) && (*data & 0x80)) {
value = -1LL;
}
for (i=0; i<len; i++) {
value = (value << 8) | *data++;
}
}
return value;
}
//-----------------------------------------------------------------------------
int sml_obis_scale_int (int64_t value, int scale)
{
if (scale > 0) {
value = value * (10 * scale);
} else if (scale < 0) {
value = value / (10 * -scale);
}
return (int)value;
}
//-----------------------------------------------------------------------------
int16_t sml_get_obis_pac_average ()
{
int32_t average;
int16_t pac_average = 0;
if (obis_cur_pac_cnt) {
if (obis_pac_sum >= 0) {
average = (obis_pac_sum + (obis_cur_pac_cnt >> 1)) / obis_cur_pac_cnt;
if (average > INT16_MAX) {
pac_average = INT16_MAX;
} else {
pac_average = average;
}
} else {
average = (obis_pac_sum - (obis_cur_pac_cnt >> 1)) / obis_cur_pac_cnt;
if (average < INT16_MIN) {
pac_average = INT16_MIN;
} else {
pac_average = average;
}
}
}
return pac_average;
}
//-----------------------------------------------------------------------------
void sml_handle_obis_pac (int16_t pac)
{
time_t time_today;
obis_cur_pac = pac;
if ((time_today = *obis_timestamp)) {
uint32_t pac_index;
time_today = gTimezone.toLocal (time_today);
pac_index = hour(time_today) * 60 + minute(time_today);
pac_index /= AHOY_PAC_INTERVAL;
if (pac_index != obis_cur_pac_index) {
/* calc average for last interval */
if (obis_cur_pac_cnt) {
int16_t pac_average = sml_get_obis_pac_average();
File file;
char file_name[sizeof (SML_OBIS_GRID_POWER_PATH) + sizeof (SML_OBIS_FORMAT_FILE_NAME)];
snprintf (file_name, sizeof (file_name), SML_OBIS_GRID_POWER_PATH "/" SML_OBIS_FORMAT_FILE_NAME,
day(time_today), month(time_today), year(time_today));
// append last average
if ((file = LittleFS.open (file_name, "a"))) {
unsigned char buf[4];
buf[0] = obis_cur_pac_index & 0xff;
buf[1] = obis_cur_pac_index >> 8;
buf[2] = pac_average & 0xff;
buf[3] = pac_average >> 8;
if (file.write (buf, sizeof (buf)) != sizeof (buf)) {
DPRINTLN (DBG_WARN, "sml_handle_obis_pac, failed_to_write");
} else {
DPRINTLN (DBG_DEBUG, "sml_handle_obis_pac, write to " + String(file_name));
}
file.close ();
} else {
DPRINTLN (DBG_WARN, "sml_handle_obis_pac, failed to open");
}
obis_cur_pac_cnt = 0;
obis_pac_sum = 0;
}
obis_cur_pac_index = pac_index;
}
if ((pac_index >= AHOY_MIN_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL) &&
(pac_index < AHOY_MAX_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL)) {
obis_pac_sum += pac;
obis_cur_pac_cnt++;
} else {
DPRINTLN (DBG_DEBUG, "sml_handle_obis_pac, outside daylight, minutes: " + String (pac_index * AHOY_PAC_INTERVAL));
}
} else {
DPRINTLN (DBG_INFO, "sml_handle_obis_pac, no time2");
}
}
//-----------------------------------------------------------------------------
uint16_t sml_fill_buf (uint16_t len)
{
len = min (sizeof (sml_serial_buf) - (cur_serial_buf - sml_serial_buf) - sml_serial_len, len);
#ifdef SML_OBIS_TEST
size_t partlen;
partlen = min (len, sizeof (sml_test_telegram) - sml_test_telegram_offset);
memcpy (cur_serial_buf + sml_serial_len, &sml_test_telegram[sml_test_telegram_offset], partlen);
sml_serial_len += partlen;
sml_test_telegram_offset += partlen;
if (sml_test_telegram_offset >= sizeof (sml_test_telegram)) {
sml_test_telegram_offset = 0;
}
if (partlen < len) {
memcpy (cur_serial_buf + sml_serial_len, &sml_test_telegram[sml_test_telegram_offset], len - partlen);
sml_serial_len += len - partlen;
sml_test_telegram_offset += len - partlen;
}
#else
if (len) {
len = Serial.readBytes(cur_serial_buf + sml_serial_len, len);
sml_serial_len += len;
}
#endif
return len;
}
//-----------------------------------------------------------------------------
bool sml_get_list_entries (uint16_t layer)
{
bool error = false;
while (!error && sml_serial_len) {
sml_list_entry_type_t type = (sml_list_entry_type_t)(*cur_serial_buf & 0x70);
unsigned char entry_len;
// Acc. to Spec there might be len_info > 2. But does this happen in real life?
// Also: an info_len > 2 could be due to corrupt data. So better break.
uint16 len_info = (*cur_serial_buf & SML_EXT_LENGTH) ? 2 : 1;
#ifdef undef
DPRINT (DBG_INFO, "get_list_entries");
DBGPRINT (", layer " + String (layer));
DBGPRINT (", entries " + String (sml_list_layer_entries[layer]));
DBGPRINT (", type 0x" + String (type, HEX));
DBGPRINT (", len_info " + String (len_info));
DBGPRINTLN (", sml_len " + String (sml_serial_len));
#endif
if (sml_serial_len < len_info) {
if (cur_serial_buf > sml_serial_buf) {
memmove (sml_serial_buf, cur_serial_buf, sml_serial_len);
cur_serial_buf = sml_serial_buf;
}
error = true;
} else {
if (len_info == 2) {
entry_len = (*cur_serial_buf << 4) | (*(cur_serial_buf+1) & 0xf);
} else {
entry_len = *cur_serial_buf & 0x0f; /* bei Listen andere Bedeutung */
}
if ((type == SML_TYPE_LIST) || (sml_serial_len >= entry_len)) {
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, len_info, cur_serial_buf);
if (layer || (sml_list_layer_entries[layer] > 2)) {
sml_msg_crc = sml_calc_crc (sml_msg_crc, len_info, cur_serial_buf);
}
sml_serial_len -= len_info;
if (entry_len && (type != SML_TYPE_LIST)) {
entry_len -= len_info;
}
if (sml_serial_len) {
cur_serial_buf += len_info;
} else {
cur_serial_buf = sml_serial_buf;
}
if (sml_list_layer_entries[layer]) {
switch (type) {
case SML_TYPE_OCTET_STRING:
if ((layer == 4) && (entry_len == 6)) {
sml_handle_obis_state (cur_serial_buf);
}
break;
case SML_TYPE_BOOL:
break;
case SML_TYPE_INT:
if (!layer && (sml_list_layer_entries[layer] == 2)) {
// perhaps there is a creepy smart meter that does send crc as int?
uint16_t rcv_crc = sml_obis_get_int (cur_serial_buf, entry_len);
sml_msg_crc = sml_finit_crc (sml_msg_crc);
if (rcv_crc != sml_msg_crc) {
DPRINTLN(DBG_WARN, "Wrong CRC for msg 0x" + String (sml_message, HEX) +
", 0x" + String (sml_msg_crc, HEX) + " <-> 0x" + String (rcv_crc, HEX));
} else {
sml_msg_failure = 0;
}
} else if (layer == 1) {
if ((sml_message == SML_MSG_NONE) && (sml_list_layer_entries[layer] == 2)) {
sml_message = sml_obis_get_int (cur_serial_buf, entry_len);
}
} else if (layer == 4) {
if (obis_state == OBIS_ST_POWER_ALL) {
if (sml_list_layer_entries[layer] == 3) {
obis_power_all_scale = (int)sml_obis_get_int (cur_serial_buf, entry_len);
} else if (sml_list_layer_entries[layer] == 2) {
obis_power_all_value = (int)sml_obis_get_int (cur_serial_buf, entry_len);
}
} else if (obis_state == OBIS_ST_YIELD_IN_ALL) {
if (sml_list_layer_entries[layer] == 3) {
obis_yield_in_all_scale = (int)sml_obis_get_int (cur_serial_buf, entry_len);
} else if (sml_list_layer_entries[layer] == 2) {
obis_yield_in_all_value = sml_obis_get_int (cur_serial_buf, entry_len);
}
} else if (obis_state == OBIS_ST_YIELD_OUT_ALL) {
if (sml_list_layer_entries[layer] == 3) {
obis_yield_out_all_scale = (int)sml_obis_get_int (cur_serial_buf, entry_len);
} else if (sml_list_layer_entries[layer] == 2) {
obis_yield_out_all_value = sml_obis_get_int (cur_serial_buf, entry_len);
}
}
}
break;
case SML_TYPE_UINT:
if (!layer && (sml_list_layer_entries[layer] == 2)) {
uint16_t rcv_crc = sml_obis_get_uint (cur_serial_buf, entry_len);
sml_msg_crc = sml_finit_crc (sml_msg_crc);
if (rcv_crc != sml_msg_crc) {
DPRINTLN(DBG_WARN, "Wrong CRC for msg 0x" + String (sml_message, HEX) +
", 0x" + String (sml_msg_crc, HEX) + " <-> 0x" + String (rcv_crc, HEX));
} else {
sml_msg_failure = 0;
}
} else if (layer == 1) {
if ((sml_message == SML_MSG_NONE) && (sml_list_layer_entries[layer] == 2)) {
sml_message = sml_obis_get_uint (cur_serial_buf, entry_len);
}
} else if (layer == 4) {
if (obis_state == OBIS_ST_YIELD_IN_ALL) {
if (sml_list_layer_entries[layer] == 2) {
obis_yield_in_all_value = sml_obis_get_uint (cur_serial_buf, entry_len);
}
} else if (obis_state == OBIS_ST_YIELD_OUT_ALL) {
if (sml_list_layer_entries[layer] == 2) {
obis_yield_out_all_value = sml_obis_get_uint (cur_serial_buf, entry_len);
}
}
}
break;
case SML_TYPE_LIST:
if (layer + 1 < SML_MAX_LIST_LAYER) {
sml_list_layer_entries[layer]--;
layer++;
cur_sml_list_layer = layer;
sml_list_layer_entries[layer] = entry_len;
#ifdef undef
DPRINTLN(DBG_INFO, "Open layer " + String(layer) + ", entries " + String(entry_len));
#endif
if (!sml_serial_len) {
error = true;
}
} else {
sml_state = SML_ST_FIND_START_TAG;
return sml_serial_len ? false : true;
}
break;
default:
DPRINT(DBG_WARN, "Ill Element 0x" + String(type, HEX));
DBGPRINTLN(", len " + String (entry_len + len_info));
/* design: aussteigen */
sml_state = SML_ST_FIND_START_TAG;
return sml_serial_len ? false : true;
}
if (type != SML_TYPE_LIST) {
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, entry_len, cur_serial_buf);
if (layer || (sml_list_layer_entries[layer] > 2)) {
sml_msg_crc = sml_calc_crc (sml_msg_crc, entry_len, cur_serial_buf);
}
sml_serial_len -= entry_len;
if (sml_serial_len) {
cur_serial_buf += entry_len;
} else {
cur_serial_buf = sml_serial_buf;
error = true;
}
sml_list_layer_entries[layer]--;
}
}
while (!sml_list_layer_entries[layer]) {
#ifdef undef
DPRINTLN(DBG_INFO, "COMPLETE, layer " + String (layer));
#endif
if (layer) {
layer--;
cur_sml_list_layer = layer;
} else {
sml_state = SML_ST_FIND_MSG;
return sml_serial_len ? false : true;
}
}
} else if (entry_len > sizeof (sml_serial_buf)) {
DPRINTLN (DBG_INFO, "skip " + String (entry_len));
sml_skip_len = entry_len;
sml_state = SML_ST_SKIP_LIST_ENTRY;
return false;
} else {
if (cur_serial_buf > sml_serial_buf) {
memmove (sml_serial_buf, cur_serial_buf, sml_serial_len);
cur_serial_buf = sml_serial_buf;
}
error = true;
}
}
}
return error;
}
//-----------------------------------------------------------------------------
uint16_t sml_parse_stream (uint16 len)
{
bool parse_continue;
uint16_t serial_read;
serial_read = sml_fill_buf (len);
do {
parse_continue = false;
switch (sml_state) {
case SML_ST_FIND_START_TAG:
if (sml_serial_len >= sizeof (esc_seq)) {
unsigned char *last_serial_buf = cur_serial_buf;
if ((cur_serial_buf = (unsigned char *)memmem (cur_serial_buf, sml_serial_len, esc_seq, sizeof (esc_seq)))) {
sml_telegram_crc = sml_init_crc ();
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, sizeof (esc_seq), cur_serial_buf);
sml_serial_len -= cur_serial_buf - last_serial_buf;
sml_serial_len -= sizeof (esc_seq);
if (sml_serial_len) {
cur_serial_buf += sizeof (esc_seq);
parse_continue = true;
} else {
cur_serial_buf = sml_serial_buf;
}
sml_state = SML_ST_FIND_VERSION;
#ifdef undef
DPRINTLN(DBG_INFO, "START_TAG, rest " + String(sml_serial_len));
#endif
} else {
cur_serial_buf = last_serial_buf + sml_serial_len;
last_serial_buf = cur_serial_buf;
/* handle up to last 3 esc chars */
while ((*(cur_serial_buf - 1) == SML_ESCAPE_CHAR)) {
cur_serial_buf--;
}
if ((sml_serial_len = last_serial_buf - cur_serial_buf)) {
memmove (sml_serial_buf, cur_serial_buf, sml_serial_len);
}
cur_serial_buf = sml_serial_buf;
}
} else if (cur_serial_buf > sml_serial_buf) {
memmove (sml_serial_buf, cur_serial_buf, sml_serial_len);
cur_serial_buf = sml_serial_buf;
}
break;
case SML_ST_FIND_VERSION:
if (sml_serial_len >=sizeof (version_seq)) {
if (!memcmp (cur_serial_buf, version_seq, sizeof (version_seq))) {
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, sizeof (version_seq), cur_serial_buf);
sml_msg_failure = 0;
sml_state = SML_ST_FIND_MSG;
#ifdef undef
DPRINTLN(DBG_INFO, "VERSION, rest " + String (sml_serial_len - sizeof (version_seq)));
#endif
} else {
#ifdef undef
DPRINTLN(DBG_INFO, "no VERSION, rest " + String (sml_serial_len - sizeof (version_seq)));
#endif
sml_state = SML_ST_FIND_START_TAG;
}
sml_serial_len -= sizeof (version_seq);
if (sml_serial_len) {
cur_serial_buf += sizeof (version_seq);
parse_continue = true;
} else {
cur_serial_buf = sml_serial_buf;
}
} else if (cur_serial_buf > sml_serial_buf) {
memmove (sml_serial_buf, cur_serial_buf, sml_serial_len);
cur_serial_buf = sml_serial_buf;
}
break;
case SML_ST_FIND_MSG:
if (sml_msg_failure) {
sml_state = SML_ST_FIND_START_TAG;
parse_continue = sml_serial_len ? true : false;
} else if (sml_serial_len) {
if (*cur_serial_buf == 0x1b) {
sml_state = SML_ST_FIND_END_TAG;
parse_continue = true;
} else if ((*cur_serial_buf & 0x70) == 0x70) {
/* todo: extended list on 1st level (does this happen in real life?) */
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, 1, cur_serial_buf);
#ifdef undef
DPRINTLN (DBG_INFO, "TOPLIST 0x" + String(*cur_serial_buf, HEX) + ", rest " + String (sml_serial_len - 1));
#endif
sml_state = SML_ST_FIND_LIST_ENTRIES;
cur_sml_list_layer = 0;
sml_message = SML_MSG_NONE;
sml_msg_failure = 1; // assume corrupt data (crc of this msg must proof ok)
sml_msg_crc = sml_init_crc ();
sml_msg_crc = sml_calc_crc (sml_msg_crc, 1, cur_serial_buf);
obis_state = OBIS_ST_NONE;
sml_list_layer_entries[0] = *cur_serial_buf & 0xf;
sml_serial_len--;
if (sml_serial_len) {
cur_serial_buf++;
parse_continue = true;
} else {
cur_serial_buf = sml_serial_buf;
}
} else if (*cur_serial_buf == 0x00) {
/* fill byte (depends on the size of the telegram) */
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, 1, cur_serial_buf);
sml_serial_len--;
if (sml_serial_len) {
cur_serial_buf++;
parse_continue = true;
} else {
cur_serial_buf = sml_serial_buf;
}
} else {
DPRINTLN(DBG_WARN, "Unexpected 0x" + String(*cur_serial_buf, HEX) + ", rest: " + String (sml_serial_len));
sml_state = SML_ST_FIND_START_TAG;
parse_continue = true;
}
}
break;
case SML_ST_FIND_LIST_ENTRIES:
parse_continue = !sml_get_list_entries (cur_sml_list_layer);
break;
case SML_ST_SKIP_LIST_ENTRY:
if (sml_serial_len) { /* design: keep rcv buf small and skip irrelevant long list entries */
size_t len = min (sml_serial_len, sml_skip_len);
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, len, cur_serial_buf);
if (cur_sml_list_layer || (sml_list_layer_entries[cur_sml_list_layer] > 2)) {
sml_msg_crc = sml_calc_crc (sml_msg_crc, len, cur_serial_buf);
}
sml_serial_len -= len;
if (sml_serial_len) {
cur_serial_buf += len;
parse_continue = true;
} else {
cur_serial_buf = sml_serial_buf;
}
sml_skip_len -= len;
if (!sml_skip_len) {
sml_state = SML_ST_FIND_LIST_ENTRIES;
sml_list_layer_entries[cur_sml_list_layer]--;
while (!sml_list_layer_entries[cur_sml_list_layer]) {
#ifdef undef
DPRINTLN(DBG_INFO, "COMPLETE, layer " + String (cur_sml_list_layer));
#endif
if (cur_sml_list_layer) {
cur_sml_list_layer--;
} else {
sml_state = SML_ST_FIND_MSG;
break;
}
}
}
}
break;
case SML_ST_FIND_END_TAG:
if (sml_serial_len >= sizeof (esc_seq)) {
if (!memcmp (cur_serial_buf, esc_seq, sizeof (esc_seq))) {
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, sizeof (esc_seq), cur_serial_buf);
sml_state = SML_ST_CHECK_CRC;
} else {
DPRINTLN(DBG_WARN, "Missing END_TAG, found 0x" + String (*cur_serial_buf) +
" 0x" + String (*(cur_serial_buf+1)) +
" 0x" + String (*(cur_serial_buf+2)) +
" 0x" + String (*(cur_serial_buf+3)));
sml_state = SML_ST_FIND_START_TAG;
}
sml_serial_len -= sizeof (esc_seq);
if (sml_serial_len) {
cur_serial_buf += sizeof (esc_seq);
parse_continue = true;
} else {
cur_serial_buf = sml_serial_buf;
}
} else if (cur_serial_buf > sml_serial_buf) {
memmove (sml_serial_buf, cur_serial_buf, sml_serial_len);
cur_serial_buf = sml_serial_buf;
}
break;
case SML_ST_CHECK_CRC:
if (sml_serial_len >= 4) {
if (*cur_serial_buf == 0x1a) {
uint16_t calc_crc16, rcv_crc16;
sml_telegram_crc = sml_calc_crc (sml_telegram_crc, 2, cur_serial_buf);
calc_crc16 = sml_finit_crc (sml_telegram_crc);
rcv_crc16 = (*(cur_serial_buf+2) << 8) + *(cur_serial_buf+3);
if (calc_crc16 == rcv_crc16) {
obis_power_all_value = sml_obis_scale_int (obis_power_all_value, obis_power_all_scale);
#ifdef undef
// a bit more verbose info
obis_yield_in_all_value = sml_obis_scale_uint (obis_yield_in_all_value, obis_yield_in_all_scale);
obis_yield_out_all_value = sml_obis_scale_uint (obis_yield_out_all_value, obis_yield_out_all_scale);
DPRINTLN(DBG_INFO, "Power " + String (obis_power_all_value) +
", Yield in " + String (obis_yield_in_all_value) +
", Yield out " + String (obis_yield_out_all_value));
#else
DPRINTLN(DBG_INFO, "Power " + String (obis_power_all_value));
#endif
sml_handle_obis_pac (obis_power_all_value);
} else {
DPRINTLN(DBG_WARN, "CRC ERROR 0x" + String (calc_crc16, HEX) + " <-> 0x" + String (rcv_crc16, HEX));
}
}
sml_state = SML_ST_FIND_START_TAG;
sml_serial_len -= 4;
if (sml_serial_len) {
cur_serial_buf += 4;
parse_continue = true;
} else {
cur_serial_buf = sml_serial_buf;
}
} else if (cur_serial_buf > sml_serial_buf) {
memmove (sml_serial_buf, cur_serial_buf, sml_serial_len);
cur_serial_buf = sml_serial_buf;
}
break;
}
} while (parse_continue);
return serial_read;
}
//-----------------------------------------------------------------------------
void sml_loop ()
{
uint16_t serial_avail;
uint16_t serial_read = 0;
#ifdef SML_OBIS_TEST
uint32_t cur_uptime;
static uint32_t last_uptime;
if (((cur_uptime = mApp->getUptime()) > 30) && (cur_uptime != last_uptime)) {
last_uptime = cur_uptime;
serial_avail = sizeof (sml_test_telegram) >> 2;
} else {
serial_avail = 0;
}
#else
serial_avail = Serial.available();
#endif
if (serial_avail > 0) {
do {
serial_read = sml_parse_stream (serial_avail);
serial_avail -= serial_read;
// yield(); /* unconditionally called this might have a bad effect for TX Retransmit to the Inverter via NRF24L01+ (not quite sure) */
} while (serial_read && serial_avail);
}
}
#endif