From 1ba4591943be4cd38e6ca1ffd93f4e41344fdd79 Mon Sep 17 00:00:00 2001 From: Roland Schneider Date: Tue, 22 Apr 2025 22:03:46 +0200 Subject: [PATCH] Issue #34: Provide sml_parser functionality through class SmlParser which provides parser state and cache buffers per instance, opposed to previous static buffers. This allows concurrent handling of multiple smart meter devices with the same program. Issue #32: Provide new function smlOBISServerId to obtain OBIS server id 1-0:0.0.9*255 --- src/sml.cpp | 124 +++++++++++++--- src/sml.h | 131 ++++++++++++++++- test/test/test_56bit/test.cpp | 17 +++ test/test/test_EMH/test.cpp | 17 +++ test/test/test_ESY/test.cpp | 17 +++ test/test/test_SmlParserClass/ehz_bin.h | 118 +++++++++++++++ test/test/test_SmlParserClass/ehz_bin2.h | 118 +++++++++++++++ test/test/test_SmlParserClass/test.cpp | 178 +++++++++++++++++++++++ test/test/test_efr_sgm_c2/test.cpp | 3 + test/test/test_negative/test.cpp | 17 +++ 10 files changed, 711 insertions(+), 29 deletions(-) create mode 100644 test/test/test_SmlParserClass/ehz_bin.h create mode 100644 test/test/test_SmlParserClass/ehz_bin2.h create mode 100644 test/test/test_SmlParserClass/test.cpp diff --git a/src/sml.cpp b/src/sml.cpp index 17dbd63..717ca16 100644 --- a/src/sml.cpp +++ b/src/sml.cpp @@ -5,7 +5,6 @@ #include "smlCrcTable.h" #ifdef SML_DEBUG -char logBuff[200]; #ifdef SML_NATIVE #define SML_LOG(...) \ @@ -40,22 +39,29 @@ char logBuff[200]; } while (0) #endif -#define MAX_LIST_SIZE 80 -#define MAX_TREE_SIZE 10 - -static sml_states_t currentState = SML_START; -static char nodes[MAX_TREE_SIZE]; -static unsigned char currentLevel = 0; -static unsigned short crc = 0xFFFF; -static signed char sc; -static unsigned short crcMine = 0xFFFF; -static unsigned short crcReceived = 0x0000; -static unsigned char len = 4; -static unsigned char listBuffer[MAX_LIST_SIZE]; /* keeps a list - as length + state + data */ -static unsigned char listPos = 0; - -void crc16(unsigned char &byte) +// Define a single static instance to maintain operation of legacy C style global functions +// (e.g. see non-member functions smlState, smlOBISCheck, etc.) +SmlParser SmlParser1; + + +SmlParser::SmlParser() +{ + reset(); +} + +void SmlParser::reset() +{ + currentState = SML_START; + nodes[0] = 0; + currentLevel = 0; + crc = 0xFFFF; + crcMine = 0xFFFF; + crcReceived = 0x0000; + len = 4; + listPos = 0; +} + +void SmlParser::crc16(unsigned char &byte) { #ifdef ARDUINO crc = @@ -65,26 +71,26 @@ void crc16(unsigned char &byte) #endif } -void setState(sml_states_t state, int byteLen) +void SmlParser::setState(sml_states_t state, int byteLen) { currentState = state; len = byteLen; } -void pushListBuffer(unsigned char byte) +void SmlParser::pushListBuffer(unsigned char byte) { if (listPos < MAX_LIST_SIZE) { listBuffer[listPos++] = byte; } } -void reduceList() +void SmlParser::reduceList() { if (currentLevel >= 0 && nodes[currentLevel] > 0) nodes[currentLevel]--; } -void smlNewList(unsigned char size) +void SmlParser::smlNewList(unsigned char size) { reduceList(); if (currentLevel < MAX_TREE_SIZE) @@ -104,7 +110,7 @@ void smlNewList(unsigned char size) } } -void checkMagicByte(unsigned char &byte) +void SmlParser::checkMagicByte(unsigned char &byte) { unsigned int size = 0; while (currentLevel > 0 && nodes[currentLevel] == 0) { @@ -190,6 +196,11 @@ void checkMagicByte(unsigned char &byte) } sml_states_t smlState(unsigned char ¤tByte) +{ + return SmlParser1.smlState(currentByte); +} + +sml_states_t SmlParser::smlState(unsigned char ¤tByte) { unsigned char size; if (len > 0) @@ -296,11 +307,21 @@ sml_states_t smlState(unsigned char ¤tByte) } bool smlOBISCheck(const unsigned char *obis) +{ + return SmlParser1.smlOBISCheck(obis); +} + +bool SmlParser::smlOBISCheck(const unsigned char *obis) { return (memcmp(obis, &listBuffer[2], 6) == 0); } void smlOBISManufacturer(unsigned char *str, int maxSize) +{ + SmlParser1.smlOBISManufacturer(str, maxSize); +} + +void SmlParser::smlOBISManufacturer(unsigned char *str, int maxSize) { int i = 0, pos = 0, size = 0; while (i < listPos) { @@ -312,11 +333,35 @@ void smlOBISManufacturer(unsigned char *str, int maxSize) size = (size > maxSize - 1) ? maxSize : size; memcpy(str, &listBuffer[i + 1], size); str[size + 1] = 0; + break; } i += size + 1; } } +int smlOBISServerId(unsigned char* bytes, int maxSize) +{ + return SmlParser1.smlOBISServerId(bytes, maxSize); +} + +int SmlParser::smlOBISServerId(unsigned char* bytes, int maxSize) +{ + int i = 0, pos = 0, size = 0; + while (i < listPos) { + size = (int)listBuffer[i]; + i++; + pos++; + if (pos == 6) { + /* get serverId at position 6 in list */ + size = (size > maxSize) ? maxSize : size; + memcpy(bytes, &listBuffer[i + 1], size); + return size; + } + i += size + 1; + } + return 0; +} + void smlPow(double &val, signed char &scaler) { if (scaler < 0) { @@ -332,6 +377,11 @@ void smlPow(double &val, signed char &scaler) } void smlOBISByUnit(long long int &val, signed char &scaler, sml_units_t unit) +{ + SmlParser1.smlOBISByUnit(val, scaler, unit); +} + +void SmlParser::smlOBISByUnit(long long int &val, signed char &scaler, sml_units_t unit) { unsigned char i = 0, pos = 0, size = 0, y = 0, skip = 0; sml_states_t type; @@ -373,6 +423,11 @@ void smlOBISByUnit(long long int &val, signed char &scaler, sml_units_t unit) } void smlOBISWh(double &wh) +{ + SmlParser1.smlOBISWh(wh); +} + +void SmlParser::smlOBISWh(double &wh) { long long int val; smlOBISByUnit(val, sc, SML_WATT_HOUR); @@ -381,6 +436,11 @@ void smlOBISWh(double &wh) } void smlOBISW(double &w) +{ + SmlParser1.smlOBISW(w); +} + +void SmlParser::smlOBISW(double &w) { long long int val; smlOBISByUnit(val, sc, SML_WATT); @@ -389,6 +449,11 @@ void smlOBISW(double &w) } void smlOBISVolt(double &v) +{ + SmlParser1.smlOBISVolt(v); +} + +void SmlParser::smlOBISVolt(double &v) { long long int val; smlOBISByUnit(val, sc, SML_VOLT); @@ -397,6 +462,11 @@ void smlOBISVolt(double &v) } void smlOBISAmpere(double &a) +{ + SmlParser1.smlOBISAmpere(a); +} + +void SmlParser::smlOBISAmpere(double &a) { long long int val; smlOBISByUnit(val, sc, SML_AMPERE); @@ -405,6 +475,11 @@ void smlOBISAmpere(double &a) } void smlOBISHertz(double &h) +{ + SmlParser1.smlOBISHertz(h); +} + +void SmlParser::smlOBISHertz(double &h) { long long int val; smlOBISByUnit(val, sc, SML_HERTZ); @@ -413,6 +488,11 @@ void smlOBISHertz(double &h) } void smlOBISDegree(double &d) +{ + SmlParser1.smlOBISDegree(d); +} + +void SmlParser::smlOBISDegree(double &d) { long long int val; smlOBISByUnit(val, sc, SML_DEGREE); diff --git a/src/sml.h b/src/sml.h index cd0f420..fe2eea9 100644 --- a/src/sml.h +++ b/src/sml.h @@ -4,14 +4,13 @@ /*! * \file Parse and interpret an SML (Smart Message Language) message. * - * The library uses shared static memory to store the parsed SML message. - * Use \ref smlState to parse the message byte by byte. - * This will store the parsed data in static memory. + * Use \ref smlState (or \ref SmlParser::smlState) to parse the message byte by byte. + * This will cache the parsed data in the memory provided by the global instance \ref ::SmlParser1. * To access values, check whether the OBIS code you are interested in has just - * been parsed via \ref smlOBISCheck and if so, parse that value using \ref - * smlOBISByUnit to get the data in full precision or one of the convenience - * functions like \ref smlOBISW to get the data directly as double in the - * specified unit. + * been parsed via \ref smlOBISCheck (or \ref SmlParser::smlOBISCheck) and if so, parse that value + * using \ref smlOBISByUnit (or \ref SmlParser::smlOBISByUnit) to get the data in full precision or + * one of the convenience functions like \ref smlOBISW (or \ref SmlParser::smlOBISW) to get + * the data directly as double in the specified unit. */ #include @@ -133,6 +132,15 @@ bool smlOBISCheck(const unsigned char *obis); */ void smlOBISManufacturer(unsigned char *str, int maxSize); +/*! Copies the unique id (server id) of the smart meter device into the + * provided buffer \p bytes, at most \p maxSize bytes. + * Use this after reading OBIS code `{0x01, 0x00, 0x00, 0x00, 0x09, 0xff}` + * i.e. 1-0:0.0.9*255. + * + * \return Returns the number of bytes copied to the buffer. + */ +int smlOBISServerId(unsigned char* bytes, int maxSize); + /*! Copy the value last read into \p val and the read scaler into \p scaler and * ensure that it has unit \p unit . If the unit was assumed wrongly, set \p val * to -1. @@ -160,4 +168,113 @@ void smlOBISHertz(double &h); /*! Convenience function to get a reading in Degrees.*/ void smlOBISDegree(double &d); +/*! Provides parsing functionality for SML messages */ +class SmlParser +{ + public: + SmlParser(); + + /*! Parse a single byte of an SML message and return the status after parsing + * that. */ + sml_states_t smlState(unsigned char &byte); + + /* ! Return whether the 6-character OBIS identifier (binary encoded) matches to + * the last received message. + * If it does, use one of the specific functions to retrieve the value. + * + * Calling this function makes sense after a list was received and parsed, i.e. + * after the state was \ref SML_LISTEND . + * + * OBIS (Object Identification System) identifies the kind of message that was + * received in the format `A-B:C.D.E*F`, the encoding in \p obis is + * an array `{A, B, C, D, E, F}`. + * For example `{0x01, 0x00, 0x01, 0x08, 0x01, 0xff}` encodes `1-0:1.8.1*255`, + which is the electric meter reading. + * If the message matches this particular OBIS identifier, we know that a meter + reading in Wh (Watt hours) was received and we can then read it using \ref + smlOBISWh. + */ + bool smlOBISCheck(const unsigned char *obis); + + /*! Copy the first \p maxSize bytes of the name/identifier of the manufacturer + * into buffer \p str. Use this after reading OBIS code `{0x81, 0x81, 0xc7, + * 0x82, 0x03, 0xff}` i.e. 129-129:199.130.3*255. + */ + void smlOBISManufacturer(unsigned char *str, int maxSize); + + /*! Copies the unique id (server id) of the smart meter device into the + * provided buffer \p bytes, at most \p maxSize bytes. + * Use this after reading OBIS code `{0x01, 0x00, 0x00, 0x00, 0x09, 0xff}` + * i.e. 1-0:0.0.9*255. + * + * \return Returns the number of bytes copied to the buffer. + */ + int smlOBISServerId(unsigned char* bytes, int maxSize); + + /*! Copy the value last read into \p val and the read scaler into \p scaler and + * ensure that it has unit \p unit . If the unit was assumed wrongly, set \p val + * to -1. + * The final value is `val x 10^scaler`. + * + * There are 'convenience' functions that wrap this function to return the + * actual value as double, see below. + * + * \return nothing, but set \p val to -1 in case of an error. + */ + void smlOBISByUnit(long long int &val, signed char &scaler, sml_units_t unit); + + // Be aware that double on Arduino UNO is just 32 bit + + /*! Convenience function to get a reading in Watt hours.*/ + void smlOBISWh(double &wh); + /*! Convenience function to get a reading in Watts.*/ + void smlOBISW(double &w); + /*! Convenience function to get a reading in Volts.*/ + void smlOBISVolt(double &v); + /*! Convenience function to get a reading in Amperes.*/ + void smlOBISAmpere(double &a); + /*! Convenience function to get a reading in Hertz.*/ + void smlOBISHertz(double &h); + /*! Convenience function to get a reading in Degrees.*/ + void smlOBISDegree(double &d); + + /*! Resets the state of the instance.*/ + void reset(); + + private: + void crc16(unsigned char &byte); + void setState(sml_states_t state, int byteLen); + void pushListBuffer(unsigned char byte); + void smlNewList(unsigned char size); + void checkMagicByte(unsigned char &byte); + void reduceList(); + + static const int MAX_LIST_SIZE = 80; + static const int MAX_TREE_SIZE = 10; + + sml_states_t currentState = SML_START; + char nodes[MAX_TREE_SIZE]; + unsigned char currentLevel = 0; + unsigned short crc = 0xFFFF; + signed char sc; + unsigned short crcMine = 0xFFFF; + unsigned short crcReceived = 0x0000; + unsigned char len = 4; + unsigned char listBuffer[MAX_LIST_SIZE]; /* keeps a list + as length + state + data */ + unsigned char listPos = 0; +#ifdef SML_DEBUG + char logBuff[200]; +#endif +}; + +/*! Provides the parsing state and cached values for the + * SML message as passed in through \ref smlState or \ref SmlParser::smlState. + * In case it is necessary to parse the SML messages of two (or more) + * smart meter devices simultaneously, additional instances should be + * provided in the main program and used for the SML messages of the + * other smart meter devices. + */ +extern SmlParser SmlParser1; + #endif diff --git a/test/test/test_56bit/test.cpp b/test/test/test_56bit/test.cpp index acc6775..1dd9afb 100644 --- a/test/test/test_56bit/test.cpp +++ b/test/test/test_56bit/test.cpp @@ -12,11 +12,15 @@ typedef struct { #define MAX_STR_MANUF 5 unsigned char manuf[MAX_STR_MANUF]; +#define MAX_LEN_SERVERID 10 +int serverIdLength = 0; +unsigned char serverId[MAX_LEN_SERVERID]; double SumWh = -2; long long int T1Wh = -2; bool isFinal = false; void Manufacturer() { smlOBISManufacturer(manuf, MAX_STR_MANUF); } +void ServerId() { serverIdLength = smlOBISServerId(serverId, MAX_LEN_SERVERID); } void PowerT1() { signed char sc = 0; @@ -28,6 +32,7 @@ void PowerSum() { smlOBISWh(SumWh); } // clang-format off OBISHandler OBISHandlers[] = { {{ 0x81, 0x81, 0xc7, 0x82, 0x03, 0xff }, &Manufacturer}, /* 129-129:199.130.3*255 */ + {{ 0x01, 0x00, 0x00, 0x00, 0x09, 0xff }, &ServerId}, /* 1- 0: 0. 0.9*255 (ServerId) */ {{ 0x01, 0x00, 0x01, 0x08, 0x01, 0xff }, &PowerT1}, /* 1- 0: 1. 8.1*255 (T1) */ {{ 0x01, 0x00, 0x01, 0x08, 0x00, 0xff }, &PowerSum}, /* 1- 0: 1. 8.0*255 (T1 + T2) */ {{ 0, 0 }} @@ -41,6 +46,7 @@ void setUp(void) unsigned char c; sml_states_t s; manuf[0] = 0; + serverIdLength = 0; for (i = 0; i < ehz_bin_len; ++i) { c = ehz_bin[i]; @@ -61,11 +67,21 @@ void setUp(void) } } +void tearDown(void) { + // intentionally left blank, needed for native hosting in Windows (fixes unity.c undefined reference to 'tearDown') +} + void test_should_return_manufacturer(void) { TEST_ASSERT_EQUAL_STRING("EMH", manuf); } +void test_should_return_serverId(void) +{ + static const unsigned char expectedServerId[]={ 0x06, 0x45, 0x4D, 0x48, 0x01, 0x00, 0x1D, 0x46, 0x15, 0xCA }; + TEST_ASSERT_EQUAL_MEMORY(expectedServerId, serverId, serverIdLength); +} + void test_should_return_t1_56bit(void) { TEST_ASSERT_EQUAL_INT(72057594037927935, T1Wh); @@ -82,6 +98,7 @@ int runUnityTests(void) { UNITY_BEGIN(); RUN_TEST(test_should_return_manufacturer); + RUN_TEST(test_should_return_serverId); RUN_TEST(test_should_return_t1_56bit); RUN_TEST(test_should_return_SumWh); RUN_TEST(test_should_be_final); diff --git a/test/test/test_EMH/test.cpp b/test/test/test_EMH/test.cpp index 6f28352..dd2b539 100644 --- a/test/test/test_EMH/test.cpp +++ b/test/test/test_EMH/test.cpp @@ -12,16 +12,21 @@ typedef struct { #define MAX_STR_MANUF 5 unsigned char manuf[MAX_STR_MANUF]; +#define MAX_LEN_SERVERID 10 +int serverIdLength = 0; +unsigned char serverId[MAX_LEN_SERVERID]; double T1Wh = -2, SumWh = -2; bool isFinal = false; void Manufacturer() { smlOBISManufacturer(manuf, MAX_STR_MANUF); } +void ServerId() { serverIdLength = smlOBISServerId(serverId, MAX_LEN_SERVERID); } void PowerT1() { smlOBISWh(T1Wh); } void PowerSum() { smlOBISWh(SumWh); } // clang-format off OBISHandler OBISHandlers[] = { {{ 0x81, 0x81, 0xc7, 0x82, 0x03, 0xff }, &Manufacturer}, /* 129-129:199.130.3*255 */ + {{ 0x01, 0x00, 0x00, 0x00, 0x09, 0xff }, &ServerId}, /* 1- 0: 0. 0.9*255 (ServerId) */ {{ 0x01, 0x00, 0x01, 0x08, 0x01, 0xff }, &PowerT1}, /* 1- 0: 1. 8.1*255 (T1) */ {{ 0x01, 0x00, 0x01, 0x08, 0x00, 0xff }, &PowerSum}, /* 1- 0: 1. 8.0*255 (T1 + T2) */ {{ 0, 0 }} @@ -35,6 +40,7 @@ void setUp(void) unsigned char c; sml_states_t s; manuf[0] = 0; + serverIdLength = 0; for (i = 0; i < ehz_bin_len; ++i) { c = ehz_bin[i]; @@ -55,11 +61,21 @@ void setUp(void) } } +void tearDown(void) { + // intentionally left blank, needed for native hosting in Windows (fixes unity.c undefined reference to 'tearDown') +} + void test_should_return_manufacturer(void) { TEST_ASSERT_EQUAL_STRING("EMH", manuf); } +void test_should_return_serverId(void) +{ + static const unsigned char expectedServerId[]={ 0x06, 0x45, 0x4D, 0x48, 0x01, 0x00, 0x1D, 0x46, 0x15, 0xCA }; + TEST_ASSERT_EQUAL_MEMORY(expectedServerId, serverId, serverIdLength); +} + void test_should_return_t1(void) { TEST_ASSERT_EQUAL_DOUBLE(12345678.9, T1Wh); } void test_should_return_SumWh(void) @@ -73,6 +89,7 @@ int runUnityTests(void) { UNITY_BEGIN(); RUN_TEST(test_should_return_manufacturer); + RUN_TEST(test_should_return_serverId); RUN_TEST(test_should_return_t1); RUN_TEST(test_should_return_SumWh); RUN_TEST(test_should_be_final); diff --git a/test/test/test_ESY/test.cpp b/test/test/test_ESY/test.cpp index 5eec376..dbf56f4 100644 --- a/test/test/test_ESY/test.cpp +++ b/test/test/test_ESY/test.cpp @@ -12,10 +12,14 @@ typedef struct { #define MAX_STR_MANUF 5 unsigned char manuf[MAX_STR_MANUF]; +#define MAX_LEN_SERVERID 10 +int serverIdLength = 0; +unsigned char serverId[MAX_LEN_SERVERID]; long long int SumWh = -2; bool isFinal = false; void Manufacturer() { smlOBISManufacturer(manuf, MAX_STR_MANUF); } +void ServerId() { serverIdLength = smlOBISServerId(serverId, MAX_LEN_SERVERID); } void PowerSum() { @@ -27,6 +31,7 @@ void PowerSum() // clang-format off OBISHandler OBISHandlers[] = { {{ 0x81, 0x81, 0xc7, 0x82, 0x03, 0xff }, &Manufacturer}, /* 129-129:199.130.3*255 */ + {{ 0x01, 0x00, 0x00, 0x00, 0x09, 0xff }, &ServerId}, // 1-0: 0.0.9 * 255 {{ 0x01, 0x00, 0x01, 0x08, 0x00, 0xff }, &PowerSum}, /* 1- 0: 1. 8.0*255 (T1 + T2) */ {{ 0, 0 }} }; @@ -39,6 +44,7 @@ void setUp(void) unsigned char c; sml_states_t s; manuf[0] = 0; + serverIdLength = 0; for (i = 0; i < data_bin_len; ++i) { #ifdef ARDUINO @@ -64,11 +70,21 @@ void setUp(void) } } +void tearDown(void) { + // intentionally left blank, needed for native hosting in Windows (fixes unity.c undefined reference to 'tearDown') +} + void test_should_return_manufacturer(void) { TEST_ASSERT_EQUAL_STRING("ESY", manuf); } +void test_should_return_serverId(void) +{ + static const unsigned char expectedServerId[]={ 0x09, 0x01, 0x45, 0x53, 0x59, 0x11, 0x03, 0xB5, 0x99, 0xA5 }; + TEST_ASSERT_EQUAL_MEMORY(expectedServerId, serverId, serverIdLength); +} + void test_should_return_SumWh(void) { TEST_ASSERT_EQUAL_INT(29416471626, SumWh); @@ -80,6 +96,7 @@ int runUnityTests(void) { UNITY_BEGIN(); RUN_TEST(test_should_return_manufacturer); + RUN_TEST(test_should_return_serverId); // RUN_TEST(test_should_return_t1); RUN_TEST(test_should_return_SumWh); RUN_TEST(test_should_be_final); diff --git a/test/test/test_SmlParserClass/ehz_bin.h b/test/test/test_SmlParserClass/ehz_bin.h new file mode 100644 index 0000000..7c15840 --- /dev/null +++ b/test/test/test_SmlParserClass/ehz_bin.h @@ -0,0 +1,118 @@ +// clang-format off +// Formatted example data from https://github.com/devZer0/libsml-testing + +const unsigned char ehz_bin[] = { + /* Start */ 0x1b, 0x1b, 0x1b, 0x1b, + /* Version */ 0x01, 0x01, 0x01, 0x01, + + /* List (6 entries) */ 0x76, + /* 1. Transaktion ID */ 0x07, 0x00, 0x0c, 0x04, 0x08, 0x87, 0x2d, + /* 2. Group No */ 0x62, 0x00, + /* 3. Abort On Error */ 0x62, 0x00, + /* 4. List (2 entries) */ 0x72, + /* 1. getOpenResponse */ 0x63, 0x01, 0x01, + /* 2. List (6 entries) */ 0x76, + /* 1. code page */ 0x01, + /* 2. clientid */ 0x01, + /* 3. Transaction ID */ 0x07, 0x00, 0x0c, 0x06, 0x9e, 0x2d, 0x0f, + /* 4. ServerID */ 0x0b, 0x06, 0x45, 0x4d, 0x48, 0x01, 0x00, 0x1d, 0x46, 0x15, 0xca, + /* 5. List name */ 0x01, + /* 6. Time */ 0x01, + /* 5. CRC */ 0x63, 0x2b, 0x8e, + /* 6. END */ 0x00, + + /* List (6 entries) */ 0x76, + /* 1. Transaktion ID */ 0x07, 0x00, 0x0c, 0x04, 0x08, 0x87, 0x2e, + /* 2. Group No */ 0x62, 0x00, + /* 3. Abort On Error */ 0x62, 0x00, + /* 4. Message Body (2) */ 0x72, + /* 1. getListResponse */ 0x63, 0x07, 0x01, + /* 2. List (7) */ 0x77, + /* 1. ClientId */ 0x01, + /* 2. ServerID */ 0x0b, 0x06, 0x45, 0x4d, 0x48, 0x01, 0x00, 0x1d, 0x46, 0x15, 0xca, + /* 3. ?? */ 0x01, + /* 4. List (2) */ 0x72, + /* 1. ?? */ 0x62, 0x01, + /* 2. ?? */ 0x65, 0x06, 0x9e, 0xfa, 0x83, + /* 5. List (7) */ 0x77, + /* 1. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x81, 0x81, 0xc7, 0x82, 0x03, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x01, + /* 5. */ 0x01, + /* 6. */ 0x04, 0x45, 0x4d, 0x48, + /* 7. */ 0x01, + /* 2. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x00, 0x00, 0x09, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x01, + /* 5. */ 0x01, + /* 6. */ 0x0b, 0x06, 0x45, 0x4d, 0x48, 0x01, 0x00, 0x1d, 0x46, 0x15, 0xca, + /* 7. */ 0x01, + /* 3. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x01, 0x08, 0x00, 0xff, + /* 2. */ 0x63, 0x01, 0x82, + /* 3. */ 0x01, + /* 4. */ 0x62, 0x1e, + /* 5. */ 0x52, 0x03, + /* 6. */ 0x56, 0x00, 0x00, 0x00, 0x1C, 0x46, + /* 7. */ 0x01, + /* 4. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x01, 0x08, 0x01, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. Unit */ 0x62, 0x1e, + /* 5. Scaler */ 0x52, 0xff, + /* 6 Value */ 0x59, 0x00, 0x00, 0x00, 0x00, 0x07, 0x5b, 0xcd, 0x15, + /* 7 */ 0x01, + /* 5. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x01, 0x08, 0x02, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x62, 0x1e, + /* 5. */ 0x52, 0x03, + /* 6. */ 0x56, 0x00, 0x00, 0x00, 0x1C, 0x46, + /* 7. */ 0x01, + /* 6. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x0f, 0x07, 0x00, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x62, 0x1b, + /* 5. */ 0x52, 0xff, + /* 6. */ 0x55, 0x00, 0x00, 0x2f, 0x65, + /* 7. */ 0x01, + /* 7. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x81, 0x81, 0xc7, 0x82, 0x05, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x01, + /* 5. */ 0x01, + /* 6. Public Key */ 0x83, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* 7. */ 0x01, + 0x01, + 0x01, + 0x63, 0xb9, 0x3f, + 0x00, + /* List (6) */ 0x76, + /* 1. */ 0x07, 0x00, 0x0c, 0x04, 0x08, 0x87, 0x31, + /* 2. */ 0x62, 0x00, + /* 3. */ 0x62, 0x00, + /* 4. List (2) */ 0x72, + /* 1. */ 0x63, 0x02, 0x01, + /* List (1) */ 0x71, + /* 1. */ 0x01, + /* 5. CRC */ 0x63, 0x6a, 0x53, + /* 6. */ 0x00, + + 0x00, + 0x1b, 0x1b, 0x1b, 0x1b, + 0x1a, 0x01, 0xe8, 0xc6 +}; +const unsigned int ehz_bin_len = 319; + diff --git a/test/test/test_SmlParserClass/ehz_bin2.h b/test/test/test_SmlParserClass/ehz_bin2.h new file mode 100644 index 0000000..a6c4925 --- /dev/null +++ b/test/test/test_SmlParserClass/ehz_bin2.h @@ -0,0 +1,118 @@ +// clang-format off +// Formatted example data from https://github.com/devZer0/libsml-testing + +const unsigned char ehz_bin2[] = { + /* Start */ 0x1b, 0x1b, 0x1b, 0x1b, + /* Version */ 0x01, 0x01, 0x01, 0x01, + + /* List (6 entries) */ 0x76, + /* 1. Transaktion ID */ 0x07, 0x00, 0x0c, 0x04, 0x08, 0x87, 0x2d, + /* 2. Group No */ 0x62, 0x00, + /* 3. Abort On Error */ 0x62, 0x00, + /* 4. List (2 entries) */ 0x72, + /* 1. getOpenResponse */ 0x63, 0x01, 0x01, + /* 2. List (6 entries) */ 0x76, + /* 1. code page */ 0x01, + /* 2. clientid */ 0x01, + /* 3. Transaction ID */ 0x07, 0x00, 0x0c, 0x06, 0x9e, 0x2d, 0x0f, + /* 4. ServerID */ 0x0b, 0x06, 0x45, 0x4d, 0x48, 0x01, 0x00, 0x1d, 0x46, 0x16, 0xca, + /* 5. List name */ 0x01, + /* 6. Time */ 0x01, + /* 5. CRC */ 0x63, 0x2b, 0x8e, + /* 6. END */ 0x00, + + /* List (6 entries) */ 0x76, + /* 1. Transaktion ID */ 0x07, 0x00, 0x0c, 0x04, 0x08, 0x87, 0x2e, + /* 2. Group No */ 0x62, 0x00, + /* 3. Abort On Error */ 0x62, 0x00, + /* 4. Message Body (2) */ 0x72, + /* 1. getListResponse */ 0x63, 0x07, 0x01, + /* 2. List (7) */ 0x77, + /* 1. ClientId */ 0x01, + /* 2. ServerID */ 0x0b, 0x06, 0x45, 0x4d, 0x48, 0x01, 0x00, 0x1d, 0x46, 0x16, 0xca, + /* 3. ?? */ 0x01, + /* 4. List (2) */ 0x72, + /* 1. ?? */ 0x62, 0x01, + /* 2. ?? */ 0x65, 0x06, 0x9e, 0xfa, 0x83, + /* 5. List (7) */ 0x77, + /* 1. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x81, 0x81, 0xc7, 0x82, 0x03, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x01, + /* 5. */ 0x01, + /* 6. */ 0x04, 0x45, 0x4d, 0x48, + /* 7. */ 0x01, + /* 2. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x00, 0x00, 0x09, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x01, + /* 5. */ 0x01, + /* 6. */ 0x0b, 0x06, 0x45, 0x4d, 0x48, 0x01, 0x00, 0x1d, 0x46, 0x16, 0xca, + /* 7. */ 0x01, + /* 3. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x01, 0x08, 0x00, 0xff, + /* 2. */ 0x63, 0x01, 0x82, + /* 3. */ 0x01, + /* 4. */ 0x62, 0x1e, + /* 5. */ 0x52, 0x03, + /* 6. */ 0x56, 0x00, 0x00, 0x00, 0x1C, 0x46, + /* 7. */ 0x01, + /* 4. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x01, 0x08, 0x01, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. Unit */ 0x62, 0x1e, + /* 5. Scaler */ 0x52, 0xff, + /* 6 Value */ 0x59, 0x00, 0x00, 0x00, 0x00, 0x07, 0x5b, 0xcd, 0x15, + /* 7 */ 0x01, + /* 5. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x01, 0x08, 0x02, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x62, 0x1e, + /* 5. */ 0x52, 0x03, + /* 6. */ 0x56, 0x00, 0x00, 0x00, 0x1C, 0x46, + /* 7. */ 0x01, + /* 6. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x01, 0x00, 0x0f, 0x07, 0x00, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x62, 0x1b, + /* 5. */ 0x52, 0xff, + /* 6. */ 0x55, 0x00, 0x00, 0x2f, 0x65, + /* 7. */ 0x01, + /* 7. List (7) */ 0x77, + /* 1. OBIS */ 0x07, 0x81, 0x81, 0xc7, 0x82, 0x05, 0xff, + /* 2. */ 0x01, + /* 3. */ 0x01, + /* 4. */ 0x01, + /* 5. */ 0x01, + /* 6. Public Key */ 0x83, 0x02, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + /* 7. */ 0x01, + 0x01, + 0x01, + 0x63, 0xb9, 0x3f, + 0x00, + /* List (6) */ 0x76, + /* 1. */ 0x07, 0x00, 0x0c, 0x04, 0x08, 0x87, 0x31, + /* 2. */ 0x62, 0x00, + /* 3. */ 0x62, 0x00, + /* 4. List (2) */ 0x72, + /* 1. */ 0x63, 0x02, 0x01, + /* List (1) */ 0x71, + /* 1. */ 0x01, + /* 5. CRC */ 0x63, 0x6a, 0x53, + /* 6. */ 0x00, + + 0x00, + 0x1b, 0x1b, 0x1b, 0x1b, + 0x1a, 0x01, 0x82, 0x44 +}; +const unsigned int ehz_bin2_len = 319; + diff --git a/test/test/test_SmlParserClass/test.cpp b/test/test/test_SmlParserClass/test.cpp new file mode 100644 index 0000000..66e9cfd --- /dev/null +++ b/test/test/test_SmlParserClass/test.cpp @@ -0,0 +1,178 @@ +#include "ehz_bin.h" +#include "ehz_bin2.h" +#include "sml.h" +#include "unity.h" +#ifdef ARDUINO +#include "arduino.h" +#endif + +// Tests two instances of SmlParser with slightly different server ids and +// input stream for the parser instances shifted by value of 'byteStreamShift' to check proper smlState operation. + +class ExtractedData; + +typedef struct { + const unsigned char OBIS[6]; + void (*Handler)(SmlParser& smlParser, ExtractedData& extractedData); +} OBISHandler; + +class ExtractedData +{ + public: + ExtractedData() + { + manuf[0] = 0; + serverIdLength = 0; + t1Wh = -2; + sumWh = -2; + isFinal = false; + } + + static const size_t MAX_STR_MANUF = 5; + unsigned char manuf[MAX_STR_MANUF]; + static const size_t MAX_LEN_SERVERID = 20; + int serverIdLength; + unsigned char serverId[MAX_LEN_SERVERID]; + double t1Wh; + double sumWh; + bool isFinal; +}; + +// SmlParser1 instance already provided by sml parser library. +SmlParser SmlParser2; +ExtractedData extractedData1; +ExtractedData extractedData2; + +void Manufacturer(SmlParser& smlParser, ExtractedData& extractedData) { smlParser.smlOBISManufacturer(extractedData.manuf, ExtractedData::MAX_STR_MANUF); } +void ServerId(SmlParser& smlParser, ExtractedData& extractedData) { extractedData.serverIdLength = smlParser.smlOBISServerId(extractedData.serverId, ExtractedData::MAX_LEN_SERVERID); } +void PowerT1(SmlParser& smlParser, ExtractedData& extractedData) { smlParser.smlOBISWh(extractedData.t1Wh); } +void PowerSum(SmlParser& smlParser, ExtractedData& extractedData) { smlParser.smlOBISWh(extractedData.sumWh); } + +// clang-format off +OBISHandler OBISHandlers[] = { + {{ 0x81, 0x81, 0xc7, 0x82, 0x03, 0xff }, &Manufacturer}, /* 129-129:199.130.3*255 */ + {{ 0x01, 0x00, 0x00, 0x00, 0x09, 0xff }, &ServerId}, /* 1- 0: 0. 0.9*255 (ServerId) */ + {{ 0x01, 0x00, 0x01, 0x08, 0x01, 0xff }, &PowerT1}, /* 1- 0: 1. 8.1*255 (T1) */ + {{ 0x01, 0x00, 0x01, 0x08, 0x00, 0xff }, &PowerSum}, /* 1- 0: 1. 8.0*255 (T1 + T2) */ + {{ 0, 0 }} +}; +// clang-format on + +void setUp(void) +{ + unsigned int byteStreamShift = 20; + sml_states_t s1 = SML_START; + sml_states_t s2 = SML_START; + + for (unsigned int i = 0; i < ehz_bin_len + byteStreamShift; ++i) { + if (i < ehz_bin_len) { + unsigned char c = ehz_bin[i]; + s1 = SmlParser1.smlState(c); + } + if (i >= byteStreamShift) { + unsigned char c = ehz_bin2[i - byteStreamShift]; + s2 = SmlParser2.smlState(c); + } + + if (s1 == SML_LISTEND) { + /* check handlers on last received list */ + int iHandler; + for (iHandler = 0; OBISHandlers[iHandler].Handler != 0 && + !(SmlParser1.smlOBISCheck(OBISHandlers[iHandler].OBIS)); + iHandler++) + ; + if (OBISHandlers[iHandler].Handler != 0) { + OBISHandlers[iHandler].Handler(SmlParser1, extractedData1); + } + } + if (s2 == SML_LISTEND) { + /* check handlers on last received list */ + int iHandler; + for (iHandler = 0; OBISHandlers[iHandler].Handler != 0 && + !(SmlParser2.smlOBISCheck(OBISHandlers[iHandler].OBIS)); + iHandler++) + ; + if (OBISHandlers[iHandler].Handler != 0) { + OBISHandlers[iHandler].Handler(SmlParser2, extractedData2); + } + } + + if (s1 == SML_FINAL) { + extractedData1.isFinal = true; + } + if (s2 == SML_FINAL) { + extractedData2.isFinal = true; + } + } +} + +void tearDown(void) { + // intentionally left blank, needed for native hosting in Windows (fixes unity.c undefined reference to 'tearDown') +} + +void test_should_return_manufacturer(void) +{ + TEST_ASSERT_EQUAL_STRING("EMH", extractedData1.manuf); + TEST_ASSERT_EQUAL_STRING("EMH", extractedData2.manuf); +} + +void test_should_return_serverId(void) +{ + static const unsigned char expectedServerId1[]={ 0x06, 0x45, 0x4D, 0x48, 0x01, 0x00, 0x1D, 0x46, 0x15, 0xCA }; + TEST_ASSERT_EQUAL_MEMORY(expectedServerId1, extractedData1.serverId, extractedData1.serverIdLength); + static const unsigned char expectedServerId2[]={ 0x06, 0x45, 0x4D, 0x48, 0x01, 0x00, 0x1D, 0x46, 0x16, 0xCA }; + TEST_ASSERT_EQUAL_MEMORY(expectedServerId2, extractedData2.serverId, extractedData2.serverIdLength); +} + +void test_should_return_t1(void) +{ + TEST_ASSERT_EQUAL_DOUBLE(12345678.9, extractedData1.t1Wh); + TEST_ASSERT_EQUAL_DOUBLE(12345678.9, extractedData2.t1Wh); +} + +void test_should_return_SumWh(void) +{ + TEST_ASSERT_EQUAL_DOUBLE(7238000, extractedData1.sumWh); + TEST_ASSERT_EQUAL_DOUBLE(7238000, extractedData2.sumWh); +} + +void test_should_be_final(void) +{ + TEST_ASSERT_EQUAL_INT(1, extractedData1.isFinal); + TEST_ASSERT_EQUAL_INT(1, extractedData2.isFinal); +} + +int runUnityTests(void) +{ + UNITY_BEGIN(); + RUN_TEST(test_should_return_manufacturer); + RUN_TEST(test_should_return_serverId); + RUN_TEST(test_should_return_t1); + RUN_TEST(test_should_return_SumWh); + RUN_TEST(test_should_be_final); + return UNITY_END(); +} + +/** + * For native dev-platform or for some embedded frameworks + */ +int main(void) { return runUnityTests(); } + +/** + * For Arduino framework + */ +void setup() +{ +// Wait ~2 seconds before the Unity test runner +// establishes connection with a board Serial interface +#ifdef ARDUINO + delay(2000); +#endif + runUnityTests(); +} +void loop() {} + +/** + * For ESP-IDF framework + */ +void app_main() { runUnityTests(); } diff --git a/test/test/test_efr_sgm_c2/test.cpp b/test/test/test_efr_sgm_c2/test.cpp index 3531b7e..d42ca2d 100644 --- a/test/test/test_efr_sgm_c2/test.cpp +++ b/test/test/test_efr_sgm_c2/test.cpp @@ -51,6 +51,9 @@ void setUp(void) } } +void tearDown(void) { + // intentionally left blank, needed for native hosting in Windows (fixes unity.c undefined reference to 'tearDown') +} void test_should_return_SumWh(void) { diff --git a/test/test/test_negative/test.cpp b/test/test/test_negative/test.cpp index 8e785a5..cca1fce 100644 --- a/test/test/test_negative/test.cpp +++ b/test/test/test_negative/test.cpp @@ -12,16 +12,21 @@ typedef struct { #define MAX_STR_MANUF 5 unsigned char manuf[MAX_STR_MANUF]; +#define MAX_LEN_SERVERID 10 +int serverIdLength = 0; +unsigned char serverId[MAX_LEN_SERVERID]; double T1Wh = -2, SumWh = -2; bool isFinal = false; void Manufacturer() { smlOBISManufacturer(manuf, MAX_STR_MANUF); } +void ServerId() { serverIdLength = smlOBISServerId(serverId, MAX_LEN_SERVERID); } void PowerT1() { smlOBISWh(T1Wh); } void PowerSum() { smlOBISWh(SumWh); } // clang-format off OBISHandler OBISHandlers[] = { {{ 0x81, 0x81, 0xc7, 0x82, 0x03, 0xff }, &Manufacturer}, /* 129-129:199.130.3*255 */ + {{ 0x01, 0x00, 0x00, 0x00, 0x09, 0xff }, &ServerId}, // 1-0: 0.0.9 * 255 {{ 0x01, 0x00, 0x01, 0x08, 0x01, 0xff }, &PowerT1}, /* 1- 0: 1. 8.1*255 (T1) */ {{ 0x01, 0x00, 0x01, 0x08, 0x00, 0xff }, &PowerSum}, /* 1- 0: 1. 8.0*255 (T1 + T2) */ {{ 0, 0 }} @@ -35,6 +40,7 @@ void setUp(void) unsigned char c; sml_states_t s; manuf[0] = 0; + serverIdLength = 0; for (i = 0; i < ehz_bin_len; ++i) { c = ehz_bin[i]; @@ -55,11 +61,21 @@ void setUp(void) } } +void tearDown(void) { + // intentionally left blank, needed for native hosting in Windows (fixes unity.c undefined reference to 'tearDown') +} + void test_should_return_manufacturer(void) { TEST_ASSERT_EQUAL_STRING("EMH", manuf); } +void test_should_return_serverId(void) +{ + static const unsigned char expectedServerId[]={ 0x06, 0x45, 0x4D, 0x48, 0x01, 0x00, 0x1D, 0x46, 0x15, 0xCA }; + TEST_ASSERT_EQUAL_MEMORY(expectedServerId, serverId, serverIdLength); +} + void test_should_return_t1(void) { TEST_ASSERT_EQUAL_DOUBLE(244, T1Wh); } void test_should_return_SumWh(void) { TEST_ASSERT_EQUAL_DOUBLE(-261, SumWh); } @@ -70,6 +86,7 @@ int runUnityTests(void) { UNITY_BEGIN(); RUN_TEST(test_should_return_manufacturer); + RUN_TEST(test_should_return_serverId); RUN_TEST(test_should_return_t1); RUN_TEST(test_should_return_SumWh); RUN_TEST(test_should_be_final);