Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion examples/sml_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
// globals
int sflag = false; // flag to process only a single OBIS data stream
int vflag = false; // verbose flag
sml_workarounds workarounds = 0;


int serial_port_open(const char* device) {
Expand Down Expand Up @@ -88,7 +89,7 @@ void transport_receiver(unsigned char *buffer, size_t buffer_len) {
int i;
// the buffer contains the whole message, with transport escape sequences.
// these escape sequences are stripped here.
sml_file *file = sml_file_parse(buffer + 8, buffer_len - 16);
sml_file *file = sml_file_parse(buffer + 8, buffer_len - 16, workarounds);
// the sml file is parsed now

// this prints some information about the file
Expand Down
3 changes: 2 additions & 1 deletion sml/include/sml/sml_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ typedef struct {

sml_file *sml_file_init();
// parses a SML file.
sml_file *sml_file_parse(unsigned char *buffer, size_t buffer_len);
sml_file *sml_file_parse(unsigned char *buffer, size_t buffer_len,
const sml_workarounds workarounds);
void sml_file_add_message(sml_file *file, sml_message *message);
void sml_file_write(sml_file *file);
void sml_file_free(sml_file *file);
Expand Down
38 changes: 38 additions & 0 deletions sml/include/sml/sml_shared.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,43 @@ typedef int64_t i64;
#define SML_TYPE_NUMBER_32 sizeof(u32)
#define SML_TYPE_NUMBER_64 sizeof(u64)

// A bitmap to configure workarounds.
typedef int16_t sml_workarounds;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would make this uint, so we can safely use all the bits.
i wonder if it should be 32 bits, this library is probably too large for 16 bit machines, otoh. we won't need that many entries anyway...


/*
* Workaround for certain DZG meters that encode the consumption wrongly.
* Affected:
* - Model TODO with serial numbers starting with 6 (in decimal)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

DVS74
https://www.dzg.de/fileadmin/dzg/content/downloads/produkte-zaehler/dvs74/DZG_DVS74-G2_data_sheet.pdf
the other parts of the model I mentioned are just encoding how many things it can measure, afaict

*
* We only get the serial number, not the meter model via SML. Since model
* DWSB.2TH (serial starting with 4) does not exhibit this bug, we can't

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

* apply the workaround automatically.
*
* The value uses a scaler of -2, so e.g. 328.05 should be
* encoded as an unsigned int with 2 bytes (called Unsigned16 in the standard):
* 63 80 25 (0x8025 == 32805 corresponds to 328.05W)
* or as an unsigned int with 3 bytes (not named in the standard):
* 64 00 80 25
* or as a signed int with 3 bytes (not named in the standard):
* 54 00 80 25
* but they encode it as a signed int with 2 bytes (called Integer16 in the standard):
* 53 80 25
* which reads as -32731 corresponding to -327.31W.
*
* Luckily, it doesn't attempt to do any compression on
* negative values, they're always encoded as, e.g.
* 55 ff fe 13 93 (== -126061 -> -1260.61W)
*
* Since we cannot have positive values >= 0x80000000
* (that would be 21474836.48 W, yes, >21MW), we can
* assume that for 1, 2, 3 bytes, if they look signed,
* they really were intended to be unsigned.
*
* Note that this will NOT work if a meter outputs negative
* values compressed as well - but mine doesn't.
*/
#define SML_WORKAROUND_DZG_NEGATIVE 0x0001

// This sml_buffer is used in two different use-cases.
//
// Parsing: the raw data is in the buffer field,
Expand All @@ -74,6 +111,7 @@ typedef struct {
size_t cursor;
int error;
char *error_msg;
sml_workarounds workarounds;
} sml_buffer;

sml_buffer *sml_buffer_init(size_t length);
Expand Down
5 changes: 4 additions & 1 deletion sml/src/sml_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@
// EDL meter must provide at least 250 bytes as a receive buffer
#define SML_FILE_BUFFER_LENGTH 512

sml_file *sml_file_parse(unsigned char *buffer, size_t buffer_len) {
sml_file *sml_file_parse(unsigned char *buffer,
size_t buffer_len,
const sml_workarounds workarounds) {

@r00t- r00t- Nov 14, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i'm a bit confused that our "constructor" is named _parse, but yes, this is the correct place for that code :)

sml_file *file = (sml_file *)malloc(sizeof(sml_file));
*file = (sml_file){.messages = NULL, .messages_len = 0, .buf = NULL};

sml_buffer *buf = sml_buffer_init(buffer_len);
buf->workarounds = workarounds;
memcpy(buf->buffer, buffer, buffer_len);
file->buf = buf;

Expand Down
55 changes: 6 additions & 49 deletions sml/src/sml_list.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,7 @@ sml_list *sml_list_init() {

void sml_list_add(sml_list *list, sml_list *new_entry) { list->next = new_entry; }

struct workarounds {
unsigned int old_dzg_meter : 1;
};

sml_list *sml_list_entry_parse(sml_buffer *buf, struct workarounds *workarounds) {
static const unsigned char dzg_serial_name[] = {1, 0, 96, 1, 0, 255};
static const unsigned char dzg_serial_start[] = {0x0a, 0x01, 'D', 'Z', 'G', 0x00};
// this is "1 DZG 00 60000000" in the hex encoding, see comment below
static const unsigned char dzg_serial_fixed[] =
{0x0a, 0x01, 'D', 'Z', 'G', 0x00, 0x03, 0x93, 0x87, 0x00};
sml_list *sml_list_entry_parse(sml_buffer *buf) {
static const unsigned char dzg_power_name[] = {1, 0, 16, 7, 0, 255};
u8 value_tl, value_len_more;
sml_list *l = NULL;
Expand Down Expand Up @@ -171,43 +162,10 @@ sml_list *sml_list_entry_parse(sml_buffer *buf, struct workarounds *workarounds)
if (sml_buf_has_errors(buf))
goto error;

/*
* Work around DZG meters before serial numbers starting with
* 6 (in decimal) - they encode the consumption wrongly:
* The value uses a scaler of -2, so e.g. 328.05 should be
* encoded as an unsigned int with 2 bytes (called Unsigned16 in the standard):
* 63 80 25 (0x8025 == 32805 corresponds to 328.05W)
* or as an unsigned int with 3 bytes (not named in the standard):
* 64 00 80 25
* or as a signed int with 3 bytes (not named in the standard):
* 54 00 80 25
* but they encode it as a signed int with 2 bytes (called Integer16 in the standard):
* 53 80 25
* which reads as -32731 corresponding to -327.31W.
*
* Luckily, it doesn't attempt to do any compression on
* negative values, they're always encoded as, e.g.
* 55 ff fe 13 93 (== -126061 -> -1260.61W)
*
* Since we cannot have positive values >= 0x80000000
* (that would be 21474836.48 W, yes, >21MW), we can
* assume that for 1, 2, 3 bytes, if they look signed,
* they really were intended to be unsigned.
*
* Note that this will NOT work if a meter outputs negative
* values compressed as well - but mine doesn't.
*/
if (l->obj_name && l->obj_name->len == sizeof(dzg_serial_name) &&
memcmp(l->obj_name->str, dzg_serial_name, sizeof(dzg_serial_name)) == 0 && l->value &&
l->value->type == SML_TYPE_OCTET_STRING &&
l->value->data.bytes->len >= (int)sizeof(dzg_serial_start) &&
memcmp(l->value->data.bytes->str, dzg_serial_start, sizeof(dzg_serial_start)) == 0 &&
memcmp(l->value->data.bytes->str, dzg_serial_fixed, sizeof(dzg_serial_fixed)) < 0) {
workarounds->old_dzg_meter = 1;
} else if (workarounds->old_dzg_meter && l->obj_name &&
l->obj_name->len == sizeof(dzg_power_name) &&
memcmp(l->obj_name->str, dzg_power_name, sizeof(dzg_power_name)) == 0 && l->value &&
(value_len_more == 1 || value_len_more == 2 || value_len_more == 3)) {
if ((buf->workarounds & SML_WORKAROUND_DZG_NEGATIVE) && l->obj_name &&
l->obj_name->len == sizeof(dzg_power_name) &&
memcmp(l->obj_name->str, dzg_power_name, sizeof(dzg_power_name)) == 0 && l->value &&
(value_len_more == 1 || value_len_more == 2 || value_len_more == 3)) {
l->value->type &= ~SML_TYPE_FIELD;
l->value->type |= SML_TYPE_UNSIGNED;
}
Expand All @@ -223,7 +181,6 @@ sml_list *sml_list_entry_parse(sml_buffer *buf, struct workarounds *workarounds)
}

sml_list *sml_list_parse(sml_buffer *buf) {
struct workarounds workarounds = {0};
sml_list *ret = NULL, **pos = &ret;
int elems;

Expand All @@ -239,7 +196,7 @@ sml_list *sml_list_parse(sml_buffer *buf) {
elems = sml_buf_get_next_length(buf);

while (elems > 0) {
*pos = sml_list_entry_parse(buf, &workarounds);
*pos = sml_list_entry_parse(buf);
if (sml_buf_has_errors(buf))
goto error;
pos = &(*pos)->next;
Expand Down
8 changes: 6 additions & 2 deletions sml/src/sml_shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,12 @@ void sml_buf_update_bytes_read(sml_buffer *buf, int bytes) { buf->cursor += byte

sml_buffer *sml_buffer_init(size_t length) {
sml_buffer *buf = (sml_buffer *)malloc(sizeof(sml_buffer));
*buf =
(sml_buffer){.buffer = NULL, .buffer_len = 0, .cursor = 0, .error = 0, .error_msg = NULL};
*buf = (sml_buffer){.buffer = NULL,
.buffer_len = 0,
.cursor = 0,
.error = 0,
.error_msg = NULL,
.workarounds = 0};
buf->buffer = (unsigned char *)malloc(length);
buf->buffer_len = length;
memset(buf->buffer, 0, buf->buffer_len);
Expand Down
3 changes: 2 additions & 1 deletion test/src/sml_file_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ TEST(sml_file, parse_crash_report1) {
// sadly not... just 202 TEST_ASSERT_EQUAL(388, sizeof buffer2);
memcpy(buffer, buffer2, sizeof buffer2);
size_t bytes = 388;
sml_workarounds workarounds = 0;
// bytes and buffer like from sml_transport_read
sml_file *file = sml_file_parse(buffer + 8, bytes - 16);
sml_file *file = sml_file_parse(buffer + 8, bytes - 16, workarounds);
TEST_ASSERT_NOT_NULL(file);
TEST_ASSERT_EQUAL(1, file->messages_len);
sml_file_free(file);
Expand Down