Skip to content
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

iec62056: Initial include of iec62056 module #19168

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

bergzand
Copy link
Member

Contribution description

This PR adds a new iec62056 module for reading and parsing smart energy meter data. Main focus at this point is the iec62056-21 sub-specification for local exchange of smart meter data.

The module contributed here can be used as basis for reading DSMR compatible meters exposing a P1 port (IEC62056-21 mode D). I think it can also be used to parse messages from German meters with infrared communication (Mode A through C)

Testing procedure

A test application is provided. Test data is gathered from the local smart meter available here.

Issues/PRs references

None

@bergzand bergzand added the Type: new feature The issue requests / The PR implemements a new feature for RIOT label Jan 18, 2023
@github-actions github-actions bot added Area: doc Area: Documentation Area: sys Area: System Area: tests Area: tests and testing framework labels Jan 18, 2023
@bergzand bergzand force-pushed the pr/iec62056_21 branch 5 times, most recently from 9005a2b to 78a80f9 Compare January 19, 2023 14:53
@benpicco benpicco added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Feb 16, 2023
@riot-ci
Copy link

riot-ci commented Feb 16, 2023

Murdock results

FAILED

78a80f9 iec62056: Add test application

Success Failures Total Runtime
4222 0 6879 06m:52s

Artifacts

Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a quick look

#define ENABLE_DEBUG 0
#include "debug.h"

#define MIN(a, b) (a > b ? b : a)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#define MIN(a, b) (a > b ? b : a)
#include "macros/utils.h"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I swear that didn't exist yet back when I wrote this code :)

time->tm_sec = scn_u32_dec(dataset->value + 10, 2);
time->tm_isdst = dataset->value[12] == 'S';

mktime(time); /* Normalize just in case */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mktime(time); /* Normalize just in case */
rtc_tm_normalize(time); /* Normalize just in case */

would be a bit cheaper - but I'm not sure if we should really normalize the data here, I wouldn't expect the device to output 'unnormalized' data and normalizing invalid data will just create a very hard to find error. (It's easier to recognize garbage timestamps than timestamps that are somewhat off)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I see what you mean. To be honest I really don't trust smart meters to always output valid timestamp data, hence my idea to normalize it. But maybe this is a case of garbage in -> garbage out.

#include "iec62056.h"
#include "iec62056/obis.h"

#define MAX(a, b) (a > b ? a : b)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#define MAX(a, b) (a > b ? a : b)
#include "macros/utils.h"```

int res = 0;
char previous_separator = '\0';

memset(obis, 255, sizeof(iec62056_obis_t));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why 255 instead of 0?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to iec62056, the value 0 in an obis is valid data. 255 as value is either reserved, manufacturer specific or signals that the field is unused (in the case of value group F)

Comment on lines +24 to +30
#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this file is empty, why have it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was useful once, but now it is just there for the doxygen grouping. I'll rework it a bit and remove the file

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced with a doc.txt in sys/iec62056 for some documentation and a doxygen group.

* @returns negative when parsing failed, zero on success
*/
int iec62056_21_dataset_parse_register_to_decfrac(const iec62056_21_dataset_t *dataset,
int64_t *mantissa, int64_t *scaling);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really get 128 bit values from this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From IEC62056-21, on the length of the data:

Value: 32 printable characters maximum with the exception of (, ), *, / and !. For decimal values, only points (not commas) shall be used and shall be counted as characters.

Values can be large here, but I don't know if those appear in the wild, I'm severely lacking real world test cases to be honest.

Comment on lines +98 to +103
static inline iec62056_obis_t iec62056_obis_init(uint8_t a, uint8_t b, uint8_t c, uint8_t d,
uint8_t e,
uint8_t f)
{
return (iec62056_obis_t){ a, b, c, d, e, f };
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function seems a bit superfluous

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's mostly a convenience function for initializing the object. I clarified that in the documentation. Let me know if the function should be removed.

return res;
}

*value = (double)mantissa * pow(10, scaling);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*value = (double)mantissa * pow(10, scaling);
*value = (float)mantissa * powf(10, scaling);

is single precision not enough?
A iec62056_21_dataset_parse_register_to_float() might be handy if we don't want to pull in soft-float on e.g. Cortex-M4F.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a iec62056_21_dataset_parse_register_to_float().

int iec62056_21_dataset_parse_register_to_decfrac(const iec62056_21_dataset_t *dataset,
int64_t *mantissa, int64_t *scaling)
{
/* parse a string with format '001234.567*kWh' to components */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be made a bit more reusable by moving the number parsing part (01234.567) to the fmt module - or just by using sscanf() for the number

Comment on lines +72 to +75
IEC62056_21_STATE_OBIS,
IEC62056_21_STATE_NEXT,
IEC62056_21_STATE_COSEM_OBJECT,
IEC62056_21_STATE_LASTLINE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments would be nice

@bergzand bergzand removed the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Aug 11, 2023
#include <time.h>
#include <math.h>

#include "checksum/ucrc16.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not seem to be used.

const char *telegram, size_t len, bool *skip)
{
/* find the end of the obis ID */
const char *obj = memchr(telegram, '(', len);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const char *obj = memchr(telegram, '(', len);
const char *obj = memchr(telegram, '(', len);

bool iec62056_obis_equal(const iec62056_obis_t *first, const iec62056_obis_t *second,
bool include_channel)
{
return (first->a == second->a) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return (first->a == second->a) &&
return (first->a == second->a) &&

*
* IEC 62056-21 functions for direct local data exchange of electricity metering.
*
* Function implement protocol telegram parsing.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Function implement protocol telegram parsing.
* Function implement protocol telegram parsing.

uint8_t c; /**< Group C: Abstract or physical data item identifier */
uint8_t d; /**< Group D: Type or result of processing of the physical quantity */
uint8_t e; /**< Group E: Further processing or classification */
uint8_t f; /**< Group F: Identifies historical values of data */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint8_t f; /**< Group F: Identifies historical values of data */
uint8_t f; /**< Group F: Identifies historical values of data */

* @brief OBIS identifier struct
*/
typedef struct {
uint8_t a; /**< Group A: media/energy type */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uint8_t a; /**< Group A: media/energy type */
uint8_t a; /**< Group A: Media/energy type */

Or, remove capital from the other group names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: doc Area: Documentation Area: sys Area: System Area: tests Area: tests and testing framework Type: new feature The issue requests / The PR implemements a new feature for RIOT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants