Skip to content

Solution for issue #34 "Multiple parsers on one ESP?" and potential solution for issue #32 Parsing of the device id #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 102 additions & 22 deletions src/sml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
#include "smlCrcTable.h"

#ifdef SML_DEBUG
char logBuff[200];

#ifdef SML_NATIVE
#define SML_LOG(...) \
Expand Down Expand Up @@ -40,22 +39,29 @@
} 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()

Check warning on line 47 in src/sml.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/sml.cpp#L47

Member variable 'SmlParser::sc' is not initialized in the constructor.
{
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 =
Expand All @@ -65,26 +71,26 @@
#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)
Expand All @@ -104,7 +110,7 @@
}
}

void checkMagicByte(unsigned char &byte)
void SmlParser::checkMagicByte(unsigned char &byte)
{
unsigned int size = 0;
while (currentLevel > 0 && nodes[currentLevel] == 0) {
Expand Down Expand Up @@ -190,6 +196,11 @@
}

sml_states_t smlState(unsigned char &currentByte)
{
return SmlParser1.smlState(currentByte);
}

sml_states_t SmlParser::smlState(unsigned char &currentByte)
{
unsigned char size;
if (len > 0)
Expand Down Expand Up @@ -296,11 +307,21 @@
}

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) {
Expand All @@ -312,11 +333,35 @@
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);

Check failure on line 357 in src/sml.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/sml.cpp#L357

The `memcpy` family of functions require the developer to validate that the destination buffer is the same size or larger than the source buffer.
return size;
}
i += size + 1;
}
return 0;
}

void smlPow(double &val, signed char &scaler)
{
if (scaler < 0) {
Expand All @@ -332,6 +377,11 @@
}

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;
Expand Down Expand Up @@ -373,6 +423,11 @@
}

void smlOBISWh(double &wh)
{
SmlParser1.smlOBISWh(wh);
}

void SmlParser::smlOBISWh(double &wh)
{
long long int val;
smlOBISByUnit(val, sc, SML_WATT_HOUR);
Expand All @@ -381,6 +436,11 @@
}

void smlOBISW(double &w)
{
SmlParser1.smlOBISW(w);
}

void SmlParser::smlOBISW(double &w)
{
long long int val;
smlOBISByUnit(val, sc, SML_WATT);
Expand All @@ -389,6 +449,11 @@
}

void smlOBISVolt(double &v)
{
SmlParser1.smlOBISVolt(v);
}

void SmlParser::smlOBISVolt(double &v)
{
long long int val;
smlOBISByUnit(val, sc, SML_VOLT);
Expand All @@ -397,6 +462,11 @@
}

void smlOBISAmpere(double &a)
{
SmlParser1.smlOBISAmpere(a);
}

void SmlParser::smlOBISAmpere(double &a)
{
long long int val;
smlOBISByUnit(val, sc, SML_AMPERE);
Expand All @@ -405,6 +475,11 @@
}

void smlOBISHertz(double &h)
{
SmlParser1.smlOBISHertz(h);
}

void SmlParser::smlOBISHertz(double &h)
{
long long int val;
smlOBISByUnit(val, sc, SML_HERTZ);
Expand All @@ -413,6 +488,11 @@
}

void smlOBISDegree(double &d)
{
SmlParser1.smlOBISDegree(d);
}

void SmlParser::smlOBISDegree(double &d)
{
long long int val;
smlOBISByUnit(val, sc, SML_DEGREE);
Expand Down
131 changes: 124 additions & 7 deletions src/sml.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <stdbool.h>
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Loading