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

Expose the workarounds struct as part of public API #133

Closed
wants to merge 2 commits into from
Closed
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
9 changes: 7 additions & 2 deletions 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 Expand Up @@ -161,13 +162,17 @@ int main(int argc, char *argv[]) {
while ((c = getopt(argc, argv, "+hsv")) != -1) {
switch (c) {
case 'h':
printf("usage: %s [-h] [-s] [-v] device\n", argv[0]);
printf("usage: %s [-d] [-h] [-s] [-v] device\n", argv[0]);
printf("device - serial device of connected power meter e.g. /dev/cu.usbserial, or - for stdin\n");
printf("-d - Enable negative values workaround for certain DZG meters\n");
printf("-h - help\n");
printf("-s - process only one OBIS data stream (single)\n");
printf("-v - verbose\n");
exit(0); // exit here
break;
case 'd':
Copy link
Collaborator

Choose a reason for hiding this comment

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

in "production" we should probably not have an extra flag for each workaround, but i guess in an example program and while there is only one, this is ok :)

workarounds |= SML_WORKAROUND_DZG_NEGATIVE;
break;
case 's':
sflag = true;
break;
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
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

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

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) {
Copy link
Collaborator

@r00t- r00t- Nov 14, 2023

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