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);