diff --git a/src/plugins/SML_OBIS_Parser.cpp b/src/plugins/SML_OBIS_Parser.cpp index 93d11265a..ab3e1ebfb 100644 --- a/src/plugins/SML_OBIS_Parser.cpp +++ b/src/plugins/SML_OBIS_Parser.cpp @@ -1,1060 +1,1061 @@ -#include -#include -#include -#include -#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 -1; - } - 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 -1; -} - -//----------------------------------------------------------------------------- -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 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 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 +#include +#include +#include +#include +#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 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 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