From a82431c529e89f5a7c5d5789a79224ccfb19df3b Mon Sep 17 00:00:00 2001 From: Wolfgang Hottgenroth <wolfgang.hottgenroth@icloud.com> Date: Tue, 3 Nov 2020 08:18:07 +0100 Subject: [PATCH] add libmbus files --- cube/libmbus/mbus-protocol.c | 3861 ++++++++++++++++++++++++++++++++++ cube/libmbus/mbus-protocol.h | 624 ++++++ 2 files changed, 4485 insertions(+) create mode 100644 cube/libmbus/mbus-protocol.c create mode 100644 cube/libmbus/mbus-protocol.h diff --git a/cube/libmbus/mbus-protocol.c b/cube/libmbus/mbus-protocol.c new file mode 100644 index 0000000..45df1be --- /dev/null +++ b/cube/libmbus/mbus-protocol.c @@ -0,0 +1,3861 @@ +//------------------------------------------------------------------------------ +// Copyright (C) 2010-2011, Robert Johansson, Raditex AB +// All rights reserved. +// +// rSCADA +// http://www.rSCADA.se +// info@rscada.se +// +//------------------------------------------------------------------------------ + +#include <assert.h> +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include <mbus/mbus-protocol.h> + +static int parse_debug = 0, debug = 0; +static char error_str[512]; + +#define NITEMS(x) (sizeof(x)/sizeof(x[0])) + +//------------------------------------------------------------------------------ +// internal data +//------------------------------------------------------------------------------ +static mbus_slave_data slave_data[MBUS_MAX_PRIMARY_SLAVES]; + +// +// init event callback +// +void (*_mbus_recv_event)(u_char src_type, const char *buff, size_t len) = NULL; +void (*_mbus_send_event)(u_char src_type, const char *buff, size_t len) = NULL; + +// +// trace callbacks +// +void +mbus_dump_recv_event(u_char src_type, const char *buff, size_t len) +{ + mbus_hex_dump("RECV", buff, len); +} + +void +mbus_dump_send_event(u_char src_type, const char *buff, size_t len) +{ + mbus_hex_dump("SEND", buff, len); +} + +//------------------------------------------------------------------------------ +/// Register a function for receive events. +//------------------------------------------------------------------------------ +void +mbus_register_recv_event(void (*event)(u_char src_type, const char *buff, size_t len)) +{ + _mbus_recv_event = event; +} + +//------------------------------------------------------------------------------ +/// Register a function for send events. +//------------------------------------------------------------------------------ +void +mbus_register_send_event(void (*event)(u_char src_type, const char *buff, size_t len)) +{ + _mbus_send_event = event; +} + +//------------------------------------------------------------------------------ +/// Return a string that contains an the latest error message. +//------------------------------------------------------------------------------ +char * +mbus_error_str() +{ + return error_str; +} + +void +mbus_error_str_set(char *message) +{ + if (message) + { + snprintf(error_str, sizeof(error_str), "%s", message); + } +} + +void +mbus_error_reset() +{ + snprintf(error_str, sizeof(error_str), "no errors"); +} + +//------------------------------------------------------------------------------ +/// Return a pointer to the slave_data register. This register can be used for +/// storing current slave status. +//------------------------------------------------------------------------------ +mbus_slave_data * +mbus_slave_data_get(size_t i) +{ + if (i < MBUS_MAX_PRIMARY_SLAVES) + { + return &slave_data[i]; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +// +// M-Bus FRAME RELATED FUNCTIONS +// +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +/// Allocate an M-bus frame data structure and initialize it according to which +/// frame type is requested. +//------------------------------------------------------------------------------ +mbus_frame * +mbus_frame_new(int frame_type) +{ + mbus_frame *frame = NULL; + + if ((frame = malloc(sizeof(mbus_frame))) != NULL) + { + memset((void *)frame, 0, sizeof(mbus_frame)); + + frame->type = frame_type; + switch (frame->type) + { + case MBUS_FRAME_TYPE_ACK: + + frame->start1 = MBUS_FRAME_ACK_START; + + break; + + case MBUS_FRAME_TYPE_SHORT: + + frame->start1 = MBUS_FRAME_SHORT_START; + frame->stop = MBUS_FRAME_STOP; + + break; + + case MBUS_FRAME_TYPE_CONTROL: + + frame->start1 = MBUS_FRAME_CONTROL_START; + frame->start2 = MBUS_FRAME_CONTROL_START; + frame->length1 = 3; + frame->length2 = 3; + frame->stop = MBUS_FRAME_STOP; + + break; + + case MBUS_FRAME_TYPE_LONG: + + frame->start1 = MBUS_FRAME_LONG_START; + frame->start2 = MBUS_FRAME_LONG_START; + frame->stop = MBUS_FRAME_STOP; + + break; + } + } + + return frame; +} + +//------------------------------------------------------------------------------ +/// Free the memory resources allocated for the M-Bus frame data structure. +//------------------------------------------------------------------------------ +int +mbus_frame_free(mbus_frame *frame) +{ + if (frame) + { + if (frame->next != NULL) + mbus_frame_free(frame->next); + + free(frame); + return 0; + } + return -1; +} + +//------------------------------------------------------------------------------ +/// Caclulate the checksum of the M-Bus frame. Internal. +//------------------------------------------------------------------------------ +u_char +calc_checksum(mbus_frame *frame) +{ + size_t i; + u_char cksum; + + assert(frame != NULL); + switch(frame->type) + { + case MBUS_FRAME_TYPE_SHORT: + + cksum = frame->control; + cksum += frame->address; + + break; + + case MBUS_FRAME_TYPE_CONTROL: + + cksum = frame->control; + cksum += frame->address; + cksum += frame->control_information; + + break; + + case MBUS_FRAME_TYPE_LONG: + + cksum = frame->control; + cksum += frame->address; + cksum += frame->control_information; + + for (i = 0; i < frame->data_size; i++) + { + cksum += frame->data[i]; + } + + break; + + case MBUS_FRAME_TYPE_ACK: + default: + cksum = 0; + } + + return cksum; +} + +//------------------------------------------------------------------------------ +/// Caclulate the checksum of the M-Bus frame. The checksum algorithm is the +/// arithmetic sum of the frame content, without using carry. Which content +/// that is included in the checksum calculation depends on the frame type. +//------------------------------------------------------------------------------ +int +mbus_frame_calc_checksum(mbus_frame *frame) +{ + if (frame) + { + switch (frame->type) + { + case MBUS_FRAME_TYPE_ACK: + case MBUS_FRAME_TYPE_SHORT: + case MBUS_FRAME_TYPE_CONTROL: + case MBUS_FRAME_TYPE_LONG: + frame->checksum = calc_checksum(frame); + + break; + + default: + return -1; + } + } + + return 0; +} + +/// +/// Calculate the values of the lengths fields in the M-Bus frame. Internal. +/// +u_char +calc_length(const mbus_frame *frame) +{ + assert(frame != NULL); + switch(frame->type) + { + case MBUS_FRAME_TYPE_CONTROL: + return 3; + case MBUS_FRAME_TYPE_LONG: + return frame->data_size + 3; + default: + return 0; + } +} + +//------------------------------------------------------------------------------ +/// Calculate the values of the lengths fields in the M-Bus frame. +//------------------------------------------------------------------------------ +int +mbus_frame_calc_length(mbus_frame *frame) +{ + if (frame) + { + frame->length1 = frame->length2 = calc_length(frame); + } + + return 0; +} + +//------------------------------------------------------------------------------ +/// Return the M-Bus frame type +//------------------------------------------------------------------------------ +int +mbus_frame_type(mbus_frame *frame) +{ + if (frame) + { + return frame->type; + } + return -1; +} + +//------------------------------------------------------------------------------ +/// Verify that parsed frame is a valid M-bus frame. +// +// Possible checks: +// +// 1) frame type +// 2) Start/stop bytes +// 3) control field +// 4) length field and actual data size +// 5) checksum +// +//------------------------------------------------------------------------------ +int +mbus_frame_verify(mbus_frame *frame) +{ + u_char checksum; + + if (frame) + { + switch (frame->type) + { + case MBUS_FRAME_TYPE_ACK: + return frame->start1 == MBUS_FRAME_ACK_START; + + case MBUS_FRAME_TYPE_SHORT: + if(frame->start1 != MBUS_FRAME_SHORT_START) + { + snprintf(error_str, sizeof(error_str), "No frame start"); + + return -1; + } + + if ((frame->control != MBUS_CONTROL_MASK_SND_NKE) && + (frame->control != MBUS_CONTROL_MASK_REQ_UD1) && + (frame->control != (MBUS_CONTROL_MASK_REQ_UD1 | MBUS_CONTROL_MASK_FCB)) && + (frame->control != MBUS_CONTROL_MASK_REQ_UD2) && + (frame->control != (MBUS_CONTROL_MASK_REQ_UD2 | MBUS_CONTROL_MASK_FCB))) + { + snprintf(error_str, sizeof(error_str), "Unknown Control Code 0x%.2x", frame->control); + + return -1; + } + + break; + + case MBUS_FRAME_TYPE_CONTROL: + case MBUS_FRAME_TYPE_LONG: + if(frame->start1 != MBUS_FRAME_CONTROL_START || + frame->start2 != MBUS_FRAME_CONTROL_START) + { + snprintf(error_str, sizeof(error_str), "No frame start"); + + return -1; + } + + if ((frame->control != MBUS_CONTROL_MASK_SND_UD) && + (frame->control != (MBUS_CONTROL_MASK_SND_UD | MBUS_CONTROL_MASK_FCB)) && + (frame->control != MBUS_CONTROL_MASK_RSP_UD) && + (frame->control != (MBUS_CONTROL_MASK_RSP_UD | MBUS_CONTROL_MASK_DFC)) && + (frame->control != (MBUS_CONTROL_MASK_RSP_UD | MBUS_CONTROL_MASK_ACD)) && + (frame->control != (MBUS_CONTROL_MASK_RSP_UD | MBUS_CONTROL_MASK_DFC | MBUS_CONTROL_MASK_ACD))) + { + snprintf(error_str, sizeof(error_str), "Unknown Control Code 0x%.2x", frame->control); + + return -1; + } + + if (frame->length1 != frame->length2) + { + snprintf(error_str, sizeof(error_str), "Frame length 1 != 2"); + + return -1; + } + + if (frame->length1 != calc_length(frame)) + { + snprintf(error_str, sizeof(error_str), "Frame length 1 != calc length"); + + return -1; + } + + break; + + default: + snprintf(error_str, sizeof(error_str), "Unknown frame type 0x%.2x", frame->type); + + return -1; + } + + if(frame->stop != MBUS_FRAME_STOP) + { + snprintf(error_str, sizeof(error_str), "No frame stop"); + + return -1; + } + + checksum = calc_checksum(frame); + + if(frame->checksum != checksum) + { + snprintf(error_str, sizeof(error_str), "Invalid checksum (0x%.2x != 0x%.2x)", frame->checksum, checksum); + + return -1; + } + + return 0; + } + + snprintf(error_str, sizeof(error_str), "Got null pointer to frame."); + + return -1; +} + + +//------------------------------------------------------------------------------ +// +// DATA ENCODING, DECODING, AND CONVERSION FUNCTIONS +// +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +/// +/// Encode BCD data +/// +//------------------------------------------------------------------------------ +int +mbus_data_bcd_encode(u_char *bcd_data, size_t bcd_data_size, int value) +{ + int v0, v1, v2, x1, x2; + size_t i; + + v2 = value; + + if (bcd_data) + { + for (i = 0; i < bcd_data_size; i++) + { + v0 = v2; + v1 = (int)(v0 / 10.0); + v2 = (int)(v1 / 10.0); + + x1 = v0 - v1 * 10; + x2 = v1 - v2 * 10; + + bcd_data[bcd_data_size-1-i] = (x2 << 4) | x1; + } + + return 0; + } + + return -1; +} + +//------------------------------------------------------------------------------ +/// +/// Decode BCD data +/// +//------------------------------------------------------------------------------ +long long +mbus_data_bcd_decode(u_char *bcd_data, size_t bcd_data_size) +{ + long long val = 0; + size_t i; + + if (bcd_data) + { + for (i = bcd_data_size; i > 0; i--) + { + val = (val * 10) + ((bcd_data[i-1]>>4) & 0xF); + val = (val * 10) + ( bcd_data[i-1] & 0xF); + } + + return val; + } + + return -1; +} + +//------------------------------------------------------------------------------ +/// +/// Decode INTEGER data +/// +//------------------------------------------------------------------------------ +int +mbus_data_int_decode(u_char *int_data, size_t int_data_size) +{ + int val = 0; + size_t i; + + if (int_data) + { + for (i = int_data_size; i > 0; i--) + { + val = (val << 8) + int_data[i-1]; + } + + return val; + } + + return -1; +} + +long +mbus_data_long_decode(u_char *int_data, size_t int_data_size) +{ + long val = 0; + size_t i; + + if (int_data) + { + for (i = int_data_size; i > 0; i--) + { + val = (val << 8) + int_data[i-1]; + } + + return val; + } + + return -1; +} + +long long +mbus_data_long_long_decode(u_char *int_data, size_t int_data_size) +{ + long long val = 0; + size_t i; + + if (int_data) + { + for (i = int_data_size; i > 0; i--) + { + val = (val << 8) + int_data[i-1]; + } + + return val; + } + + return -1; +} + +//------------------------------------------------------------------------------ +/// +/// Encode INTEGER data (into 'int_data_size' bytes) +/// +//------------------------------------------------------------------------------ +int +mbus_data_int_encode(u_char *int_data, size_t int_data_size, int value) +{ + int i; + + if (int_data) + { + for (i = 0; i < int_data_size; i++) + { + int_data[i] = (value>>(i*8)) & 0xFF; + } + + return 0; + } + + return -1; +} + +//------------------------------------------------------------------------------ +/// +/// Decode float data +/// +/// see also http://en.wikipedia.org/wiki/Single-precision_floating-point_format +/// +//------------------------------------------------------------------------------ +float +mbus_data_float_decode(u_char *float_data) +{ + float val = 0.0f; + long temp = 0, fraction; + int sign,exponent; + size_t i; + + if (float_data) + { + for (i = 4; i > 0; i--) + { + temp = (temp << 8) + float_data[i-1]; + } + + // first bit = sign bit + sign = (temp >> 31) ? -1 : 1; + + // decode 8 bit exponent + exponent = ((temp & 0x7F800000) >> 23) - 127; + + // decode explicit 23 bit fraction + fraction = temp & 0x007FFFFF; + + if ((exponent != -127) && + (exponent != 128)) + { + // normalized value, add bit 24 + fraction |= 0x800000; + } + + // calculate float value + val = (float) sign * fraction * pow(2.0f, -23.0f) * (1 << exponent); + + return val; + } + + return -1.0; +} + +//------------------------------------------------------------------------------ +/// +/// Decode string data. +/// +//------------------------------------------------------------------------------ +void +mbus_data_str_decode(u_char *dst, const u_char *src, size_t len) +{ + size_t i; + + i = 0; + + if (src && dst) + { + dst[len] = '\0'; + while(len > 0) { + dst[i++] = src[--len]; + } + } +} + +//------------------------------------------------------------------------------ +/// +/// Decode binary data. +/// +//------------------------------------------------------------------------------ +void +mbus_data_bin_decode(u_char *dst, const u_char *src, size_t len, size_t max_len) +{ + size_t i, pos; + + i = 0; + pos = 0; + + if (src && dst) + { + while((i < len) && ((pos+3) < max_len)) { + pos += snprintf(&dst[pos], max_len - pos, "%.2X ", src[i]); + i++; + } + + if (pos > 0) + { + // remove last space + pos--; + } + + dst[pos] = '\0'; + } +} + +//------------------------------------------------------------------------------ +/// +/// Decode time data (usable for type f = 4 bytes or type g = 2 bytes) +/// +//------------------------------------------------------------------------------ +void +mbus_data_tm_decode(struct tm *t, u_char *t_data, size_t t_data_size) +{ + if (t && t_data) + { + t->tm_sec = 0; + t->tm_min = 0; + t->tm_hour = 0; + t->tm_mday = 0; + t->tm_mon = 0; + t->tm_year = 0; + t->tm_isdst = 0; + + if (t_data_size == 4) // Type F = Compound CP32: Date and Time + { + if ((t_data[0] & 0x80) == 0) // Time valid ? + { + t->tm_min = t_data[0] & 0x3F; + t->tm_hour = t_data[1] & 0x1F; + t->tm_mday = t_data[2] & 0x1F; + t->tm_mon = (t_data[3] & 0x0F) - 1; + t->tm_year = ((t_data[2] & 0xE0) >> 5) | + ((t_data[3] & 0xF0) >> 1); + t->tm_isdst = (t_data[1] & 0x80) ? 1 : 0; // day saving time + } + } + else if (t_data_size == 2) // Type G: Compound CP16: Date + { + t->tm_mday = t_data[0] & 0x1F; + t->tm_mon = (t_data[1] & 0x0F) - 1; + t->tm_year = ((t_data[0] & 0xE0) >> 5) | + ((t_data[1] & 0xF0) >> 1); + } + } +} + +//------------------------------------------------------------------------------ +/// +/// Generate manufacturer code from 2-byte encoded data +/// +//------------------------------------------------------------------------------ +int +mbus_data_manufacturer_encode(u_char *m_data, u_char *m_code) +{ + int m_val; + + if (m_data == NULL || m_code == NULL) + return -1; + + m_val = ((((int)m_code[0] - 64) & 0x001F) << 10) + + ((((int)m_code[1] - 64) & 0x001F) << 5) + + ((((int)m_code[2] - 64) & 0x001F)); + + mbus_data_int_encode(m_data, 2, m_val); + + return 0; +} + +//------------------------------------------------------------------------------ +/// +/// Generate manufacturer code from 2-byte encoded data +/// +//------------------------------------------------------------------------------ +const char * +mbus_decode_manufacturer(u_char byte1, u_char byte2) +{ + static char m_str[4]; + + int m_id; + + m_str[0] = byte1; + m_str[1] = byte2; + + m_id = mbus_data_int_decode(m_str, 2); + + m_str[0] = (char)(((m_id>>10) & 0x001F) + 64); + m_str[1] = (char)(((m_id>>5) & 0x001F) + 64); + m_str[2] = (char)(((m_id) & 0x001F) + 64); + m_str[3] = 0; + + return m_str; +} + +const char * +mbus_data_product_name(mbus_data_variable_header *header) +{ + static char buff[128]; + unsigned int manufacturer; + + memset(buff, 0, sizeof(buff)); + + if (header) + { + manufacturer = (header->manufacturer[1] << 8) + header->manufacturer[0]; + + if (manufacturer == MBUS_VARIABLE_DATA_MAN_ACW) + { + switch (header->version) + { + case 0x09: + strcpy(buff,"Itron CF Echo 2"); + break; + case 0x0A: + strcpy(buff,"Itron CF 51"); + break; + case 0x0B: + strcpy(buff,"Itron CF 55"); + break; + case 0x0E: + strcpy(buff,"Itron BM +m"); + break; + case 0x0F: + strcpy(buff,"Itron CF 800"); + break; + case 0x14: + strcpy(buff,"Itron CYBLE M-Bus 1.4"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_EFE) + { + switch (header->version) + { + case 0x00: + strcpy(buff, ((header->medium == 0x06) ? "Engelmann WaterStar" : "Engelmann SensoStar 2")); + break; + case 0x01: + strcpy(buff,"Engelmann SensoStar 2C"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_SLB) + { + switch (header->version) + { + case 0x02: + strcpy(buff,"Allmess Megacontrol CF-50"); + break; + case 0x06: + strcpy(buff,"CF Compact / Integral MK MaXX"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_HYD) + { + switch (header->version) + { + case 0x28: + strcpy(buff,"ABB F95 Typ US770"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_LUG) + { + switch (header->version) + { + case 0x02: + strcpy(buff,"Landis & Gyr Ultraheat 2WR5"); + break; + case 0x03: + strcpy(buff,"Landis & Gyr Ultraheat 2WR6"); + break; + case 0x04: + strcpy(buff,"Landis & Gyr Ultraheat UH50"); + break; + case 0x07: + strcpy(buff,"Landis & Gyr Ultraheat T230"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_SVM) + { + switch (header->version) + { + case 0x08: + strcpy(buff,"Elster F2"); + break; + case 0x09: + strcpy(buff,"Kamstrup SVM F22"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_SON) + { + switch (header->version) + { + case 0x0D: + strcpy(buff,"Sontex Supercal 531"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_LSE) + { + switch (header->version) + { + case 0x99: + strcpy(buff,"Siemens WFH21"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_SPX) + { + switch (header->version) + { + case 0x31: + case 0x34: + strcpy(buff,"Sensus PolluTherm"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_ELS) + { + switch (header->version) + { + case 0x02: + strcpy(buff,"Elster TMP-A"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_NZR) + { + switch (header->version) + { + case 0x01: + strcpy(buff,"NZR DHZ 5/63"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_KAM) + { + switch (header->version) + { + case 0x08: + strcpy(buff,"Kamstrup Multical 601"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_EMH) + { + switch (header->version) + { + case 0x00: + strcpy(buff,"EMH DIZ"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_TCH) + { + switch (header->version) + { + case 0x26: + strcpy(buff,"Techem m-bus S"); + break; + } + } + else if (manufacturer == MBUS_VARIABLE_DATA_MAN_ZRM) + { + switch (header->version) + { + case 0x81: + strcpy(buff,"Minol Minocal C2"); + break; + case 0x82: + strcpy(buff,"Minol Minocal WR3"); + break; + } + } + } + + return buff; +} + +//------------------------------------------------------------------------------ +// +// FIXED-LENGTH DATA RECORD FUNCTIONS +// +//------------------------------------------------------------------------------ + + +// +// Value Field Medium/Unit Medium +// hexadecimal Bit 16 Bit 15 Bit 8 Bit 7 +// 0 0 0 0 0 Other +// 1 0 0 0 1 Oil +// 2 0 0 1 0 Electricity +// 3 0 0 1 1 Gas +// 4 0 1 0 0 Heat +// 5 0 1 0 1 Steam +// 6 0 1 1 0 Hot Water +// 7 0 1 1 1 Water +// 8 1 0 0 0 H.C.A. +// 9 1 0 0 1 Reserved +// A 1 0 1 0 Gas Mode 2 +// B 1 0 1 1 Heat Mode 2 +// C 1 1 0 0 Hot Water Mode 2 +// D 1 1 0 1 Water Mode 2 +// E 1 1 1 0 H.C.A. Mode 2 +// F 1 1 1 1 Reserved +// + +/// +/// For fixed-length frames, get a string describing the medium. +/// +const char * +mbus_data_fixed_medium(mbus_data_fixed *data) +{ + static char buff[256]; + + if (data) + { + switch ( (data->cnt1_type&0xC0)>>6 | (data->cnt2_type&0xC0)>>4 ) + { + case 0x00: + snprintf(buff, sizeof(buff), "Other"); + break; + case 0x01: + snprintf(buff, sizeof(buff), "Oil"); + break; + case 0x02: + snprintf(buff, sizeof(buff), "Electricity"); + break; + case 0x03: + snprintf(buff, sizeof(buff), "Gas"); + break; + case 0x04: + snprintf(buff, sizeof(buff), "Heat"); + break; + case 0x05: + snprintf(buff, sizeof(buff), "Steam"); + break; + case 0x06: + snprintf(buff, sizeof(buff), "Hot Water"); + break; + case 0x07: + snprintf(buff, sizeof(buff), "Water"); + break; + case 0x08: + snprintf(buff, sizeof(buff), "H.C.A."); + break; + case 0x09: + snprintf(buff, sizeof(buff), "Reserved"); + break; + case 0x0A: + snprintf(buff, sizeof(buff), "Gas Mode 2"); + break; + case 0x0B: + snprintf(buff, sizeof(buff), "Heat Mode 2"); + break; + case 0x0C: + snprintf(buff, sizeof(buff), "Hot Water Mode 2"); + break; + case 0x0D: + snprintf(buff, sizeof(buff), "Water Mode 2"); + break; + case 0x0E: + snprintf(buff, sizeof(buff), "H.C.A. Mode 2"); + break; + case 0x0F: + snprintf(buff, sizeof(buff), "Reserved"); + break; + default: + snprintf(buff, sizeof(buff), "unknown"); + break; + } + + return buff; + } + + return NULL; +} + + +//------------------------------------------------------------------------------ +// Hex code Hex code +//Unit share Unit share +// MSB..LSB MSB..LSB +// Byte 7/8 Byte 7/8 +// h,m,s 000000 00 MJ/h 100000 20 +// D,M,Y 000001 01 MJ/h * 10 100001 21 +// Wh 000010 02 MJ/h * 100 100010 22 +// Wh * 10 000011 03 GJ/h 100011 23 +// Wh * 100 000100 04 GJ/h * 10 100100 24 +// kWh 000101 05 GJ/h * 100 100101 25 +// kWh * 10 000110 06 ml 100110 26 +// kWh * 100 000111 07 ml * 10 100111 27 +// MWh 001000 08 ml * 100 101000 28 +// MWh * 10 001001 09 l 101001 29 +// MWh * 100 001010 0A l * 10 101010 2A +// kJ 001011 0B l * 100 101011 2B +// kJ * 10 001100 0C m3 101100 2C +// kJ * 100 001101 0D m3 * 10 101101 2D +// MJ 001110 0E m3 * 100 101110 2E +// MJ * 10 001111 0F ml/h 101111 2F +// MJ * 100 010000 10 ml/h * 10 110000 30 +// GJ 010001 11 ml/h * 100 110001 31 +// GJ * 10 010010 12 l/h 110010 32 +// GJ * 100 010011 13 l/h * 10 110011 33 +// W 010100 14 l/h * 100 110100 34 +// W * 10 010101 15 m3/h 110101 35 +// W * 100 010110 16 m3/h * 10 110110 36 +// kW 010111 17 m3/h * 100 110111 37 +// kW * 10 011000 18 °C* 10-3 111000 38 +// kW * 100 011001 19 units for HCA 111001 39 +// MW 011010 1A reserved 111010 3A +// MW * 10 011011 1B reserved 111011 3B +// MW * 100 011100 1C reserved 111100 3C +// kJ/h 011101 1D reserved 111101 3D +// kJ/h * 10 011110 1E same but historic 111110 3E +// kJ/h * 100 011111 1F without units 111111 3F +// +//------------------------------------------------------------------------------ +/// +/// For fixed-length frames, get a string describing the unit of the data. +/// +const char * +mbus_data_fixed_unit(int medium_unit_byte) +{ + static char buff[256]; + + switch (medium_unit_byte & 0x3F) + { + case 0x00: + snprintf(buff, sizeof(buff), "h,m,s"); + break; + case 0x01: + snprintf(buff, sizeof(buff), "D,M,Y"); + break; + + case 0x02: + snprintf(buff, sizeof(buff), "Wh"); + break; + case 0x03: + snprintf(buff, sizeof(buff), "10 Wh"); + break; + case 0x04: + snprintf(buff, sizeof(buff), "100 Wh"); + break; + case 0x05: + snprintf(buff, sizeof(buff), "kWh"); + break; + case 0x06: + snprintf(buff, sizeof(buff), "10 kWh"); + break; + case 0x07: + snprintf(buff, sizeof(buff), "100 kWh"); + break; + case 0x08: + snprintf(buff, sizeof(buff), "MWh"); + break; + case 0x09: + snprintf(buff, sizeof(buff), "10 MWh"); + break; + case 0x0A: + snprintf(buff, sizeof(buff), "100 MWh"); + break; + + case 0x0B: + snprintf(buff, sizeof(buff), "kJ"); + break; + case 0x0C: + snprintf(buff, sizeof(buff), "10 kJ"); + break; + case 0x0E: + snprintf(buff, sizeof(buff), "100 kJ"); + break; + case 0x0D: + snprintf(buff, sizeof(buff), "MJ"); + break; + case 0x0F: + snprintf(buff, sizeof(buff), "10 MJ"); + break; + case 0x10: + snprintf(buff, sizeof(buff), "100 MJ"); + break; + case 0x11: + snprintf(buff, sizeof(buff), "GJ"); + break; + case 0x12: + snprintf(buff, sizeof(buff), "10 GJ"); + break; + case 0x13: + snprintf(buff, sizeof(buff), "100 GJ"); + break; + + case 0x14: + snprintf(buff, sizeof(buff), "W"); + break; + case 0x15: + snprintf(buff, sizeof(buff), "10 W"); + break; + case 0x16: + snprintf(buff, sizeof(buff), "100 W"); + break; + case 0x17: + snprintf(buff, sizeof(buff), "kW"); + break; + case 0x18: + snprintf(buff, sizeof(buff), "10 kW"); + break; + case 0x19: + snprintf(buff, sizeof(buff), "100 kW"); + break; + case 0x1A: + snprintf(buff, sizeof(buff), "MW"); + break; + case 0x1B: + snprintf(buff, sizeof(buff), "10 MW"); + break; + case 0x1C: + snprintf(buff, sizeof(buff), "100 MW"); + break; + + case 0x1D: + snprintf(buff, sizeof(buff), "kJ/h"); + break; + case 0x1E: + snprintf(buff, sizeof(buff), "10 kJ/h"); + break; + case 0x1F: + snprintf(buff, sizeof(buff), "100 kJ/h"); + break; + case 0x20: + snprintf(buff, sizeof(buff), "MJ/h"); + break; + case 0x21: + snprintf(buff, sizeof(buff), "10 MJ/h"); + break; + case 0x22: + snprintf(buff, sizeof(buff), "100 MJ/h"); + break; + case 0x23: + snprintf(buff, sizeof(buff), "GJ/h"); + break; + case 0x24: + snprintf(buff, sizeof(buff), "10 GJ/h"); + break; + case 0x25: + snprintf(buff, sizeof(buff), "100 GJ/h"); + break; + + case 0x26: + snprintf(buff, sizeof(buff), "ml"); + break; + case 0x27: + snprintf(buff, sizeof(buff), "10 ml"); + break; + case 0x28: + snprintf(buff, sizeof(buff), "100 ml"); + break; + case 0x29: + snprintf(buff, sizeof(buff), "l"); + break; + case 0x2A: + snprintf(buff, sizeof(buff), "10 l"); + break; + case 0x2B: + snprintf(buff, sizeof(buff), "100 l"); + break; + case 0x2C: + snprintf(buff, sizeof(buff), "m^3"); + break; + case 0x2D: + snprintf(buff, sizeof(buff), "10 m^3"); + break; + case 0x2E: + snprintf(buff, sizeof(buff), "m^3"); + break; + + case 0x2F: + snprintf(buff, sizeof(buff), "ml/h"); + break; + case 0x30: + snprintf(buff, sizeof(buff), "10 ml/h"); + break; + case 0x31: + snprintf(buff, sizeof(buff), "100 ml/h"); + break; + case 0x32: + snprintf(buff, sizeof(buff), "l/h"); + break; + case 0x33: + snprintf(buff, sizeof(buff), "10 l/h"); + break; + case 0x34: + snprintf(buff, sizeof(buff), "100 l/h"); + break; + case 0x35: + snprintf(buff, sizeof(buff), "m^3/h"); + break; + case 0x36: + snprintf(buff, sizeof(buff), "10 m^3/h"); + break; + case 0x37: + snprintf(buff, sizeof(buff), "100 m^3/h"); + break; + + case 0x38: + snprintf(buff, sizeof(buff), "1e-3 °C"); + break; + case 0x39: + snprintf(buff, sizeof(buff), "units for HCA"); + break; + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + snprintf(buff, sizeof(buff), "reserved"); + break; + case 0x3E: + snprintf(buff, sizeof(buff), "reserved but historic"); + break; + case 0x3F: + snprintf(buff, sizeof(buff), "without units"); + break; + default: + snprintf(buff, sizeof(buff), "unknown"); + break; + } + + return buff; +} + +//------------------------------------------------------------------------------ +// +// VARIABLE-LENGTH DATA RECORD FUNCTIONS +// +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// +// Medium Code bin Code hex +// Other 0000 0000 00 +// Oil 0000 0001 01 +// Electricity 0000 0010 02 +// Gas 0000 0011 03 +// Heat (Volume measured at return temperature: outlet) 0000 0100 04 +// Steam 0000 0101 05 +// Hot Water 0000 0110 06 +// Water 0000 0111 07 +// Heat Cost Allocator. 0000 1000 08 +// Compressed Air 0000 1001 09 +// Cooling load meter (Volume measured at return temperature: outlet) 0000 1010 0A +// Cooling load meter (Volume measured at flow temperature: inlet) ♣ 0000 1011 0B +// Heat (Volume measured at flow temperature: inlet) 0000 1100 0C +// Heat / Cooling load meter ♣ 0000 1101 OD +// Bus / System 0000 1110 0E +// Unknown Medium 0000 1111 0F +// Reserved .......... 10 to 15 +// Cold Water 0001 0110 16 +// Dual Water 0001 0111 17 +// Pressure 0001 1000 18 +// A/D Converter 0001 1001 19 +// Reserved .......... 20 to FF +//------------------------------------------------------------------------------ + +/// +/// For variable-length frames, returns a string describing the medium. +/// +const char * +mbus_data_variable_medium_lookup(u_char medium) +{ + static char buff[256]; + + switch (medium) + { + case MBUS_VARIABLE_DATA_MEDIUM_OTHER: + snprintf(buff, sizeof(buff), "Other"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_OIL: + snprintf(buff, sizeof(buff), "Oil"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_ELECTRICITY: + snprintf(buff, sizeof(buff), "Electricity"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_GAS: + snprintf(buff, sizeof(buff), "Gas"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_HEAT_OUT: + snprintf(buff, sizeof(buff), "Heat: Outlet"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_STEAM: + snprintf(buff, sizeof(buff), "Steam"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_HOT_WATER: + snprintf(buff, sizeof(buff), "Hot water"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_WATER: + snprintf(buff, sizeof(buff), "Water"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_HEAT_COST: + snprintf(buff, sizeof(buff), "Heat Cost Allocator"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_COMPR_AIR: + snprintf(buff, sizeof(buff), "Compressed Air"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_COOL_OUT: + snprintf(buff, sizeof(buff), "Cooling load meter: Outlet"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_COOL_IN: + snprintf(buff, sizeof(buff), "Cooling load meter: Inlet"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_HEAT_IN: + snprintf(buff, sizeof(buff), "Heat: Inlet"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_HEAT_COOL: + snprintf(buff, sizeof(buff), "Heat / Cooling load meter"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_BUS: + snprintf(buff, sizeof(buff), "Bus/System"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_UNKNOWN: + snprintf(buff, sizeof(buff), "Unknown Medium"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_COLD_WATER: + snprintf(buff, sizeof(buff), "Cold water"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_DUAL_WATER: + snprintf(buff, sizeof(buff), "Dual water"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_PRESSURE: + snprintf(buff, sizeof(buff), "Pressure"); + break; + + case MBUS_VARIABLE_DATA_MEDIUM_ADC: + snprintf(buff, sizeof(buff), "A/D Converter"); + break; + + case 0x10: // - 0x15 + case 0x20: // - 0xFF + snprintf(buff, sizeof(buff), "Reserved"); + break; + + + // add more ... + default: + snprintf(buff, sizeof(buff), "Unknown medium (0x%.2x)", medium); + break; + } + + return buff; +} + +//------------------------------------------------------------------------------ +/// +/// Lookup the unit description from a VIF field in a data record +/// +//------------------------------------------------------------------------------ +const char * +mbus_unit_prefix(int exp) +{ + static char buff[256]; + + switch (exp) + { + case 0: + buff[0] = 0; + break; + + case -3: + snprintf(buff, sizeof(buff), "m"); + break; + + case -6: + snprintf(buff, sizeof(buff), "my"); + break; + + case 1: + snprintf(buff, sizeof(buff), "10 "); + break; + + case 2: + snprintf(buff, sizeof(buff), "100 "); + break; + + case 3: + snprintf(buff, sizeof(buff), "k"); + break; + + case 4: + snprintf(buff, sizeof(buff), "10 k"); + break; + + case 5: + snprintf(buff, sizeof(buff), "100 k"); + break; + + case 6: + snprintf(buff, sizeof(buff), "M"); + break; + + case 9: + snprintf(buff, sizeof(buff), "T"); + break; + + default: + snprintf(buff, sizeof(buff), "1e%d ", exp); + } + + return buff; +} + +//------------------------------------------------------------------------------ +/// Look up the data lenght from a VIF field in the data record. +/// +/// See the table on page 41 the M-BUS specification. +//------------------------------------------------------------------------------ +u_char +mbus_dif_datalength_lookup(u_char dif) +{ + switch (dif&0x0F) + { + case 0x0: + return 0; + case 0x1: + return 1; + case 0x2: + return 2; + case 0x3: + return 3; + case 0x4: + return 4; + case 0x5: + return 4; + case 0x6: + return 6; + case 0x7: + return 8; + + case 0x8: + return 0; + case 0x9: + return 1; + case 0xA: + return 2; + case 0xB: + return 3; + case 0xC: + return 4; + case 0xD: + // variable data length, + // data length stored in data field + return 0; + case 0xE: + return 6; + case 0xF: + return 8; + + default: // never reached + return 0x00; + + } +} + +//------------------------------------------------------------------------------ +/// Look up the unit from a VIF field in the data record. +/// +/// See section 8.4.3 Codes for Value Information Field (VIF) in the M-BUS spec +//------------------------------------------------------------------------------ +const char * +mbus_vif_unit_lookup(u_char vif) +{ + static char buff[256]; + int n; + + switch (vif & 0x7F) // ignore the extension bit in this selection + { + // E000 0nnn Energy 10(nnn-3) W + case 0x00: + case 0x00+1: + case 0x00+2: + case 0x00+3: + case 0x00+4: + case 0x00+5: + case 0x00+6: + case 0x00+7: + n = (vif & 0x07) - 3; + snprintf(buff, sizeof(buff), "Energy (%sWh)", mbus_unit_prefix(n)); + break; + + // 0000 1nnn Energy 10(nnn)J (0.001kJ to 10000kJ) + case 0x08: + case 0x08+1: + case 0x08+2: + case 0x08+3: + case 0x08+4: + case 0x08+5: + case 0x08+6: + case 0x08+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Energy (%sJ)", mbus_unit_prefix(n)); + + break; + + // E001 1nnn Mass 10(nnn-3) kg 0.001kg to 10000kg + case 0x18: + case 0x18+1: + case 0x18+2: + case 0x18+3: + case 0x18+4: + case 0x18+5: + case 0x18+6: + case 0x18+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Mass (%skg)", mbus_unit_prefix(n-3)); + + break; + + // E010 1nnn Power 10(nnn-3) W 0.001W to 10000W + case 0x28: + case 0x28+1: + case 0x28+2: + case 0x28+3: + case 0x28+4: + case 0x28+5: + case 0x28+6: + case 0x28+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Power (%sW)", mbus_unit_prefix(n-3)); + //snprintf(buff, sizeof(buff), "Power (10^%d W)", n-3); + + break; + + // E011 0nnn Power 10(nnn) J/h 0.001kJ/h to 10000kJ/h + case 0x30: + case 0x30+1: + case 0x30+2: + case 0x30+3: + case 0x30+4: + case 0x30+5: + case 0x30+6: + case 0x30+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Power (%sJ/h)", mbus_unit_prefix(n)); + + break; + + // E001 0nnn Volume 10(nnn-6) m3 0.001l to 10000l + case 0x10: + case 0x10+1: + case 0x10+2: + case 0x10+3: + case 0x10+4: + case 0x10+5: + case 0x10+6: + case 0x10+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Volume (%s m^3)", mbus_unit_prefix(n-6)); + + break; + + // E011 1nnn Volume Flow 10(nnn-6) m3/h 0.001l/h to 10000l/ + case 0x38: + case 0x38+1: + case 0x38+2: + case 0x38+3: + case 0x38+4: + case 0x38+5: + case 0x38+6: + case 0x38+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Volume flow (%s m^3/h)", mbus_unit_prefix(n-6)); + + break; + + // E100 0nnn Volume Flow ext. 10(nnn-7) m3/min 0.0001l/min to 1000l/min + case 0x40: + case 0x40+1: + case 0x40+2: + case 0x40+3: + case 0x40+4: + case 0x40+5: + case 0x40+6: + case 0x40+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Volume flow (%s m^3/min)", mbus_unit_prefix(n-7)); + + break; + + // E100 1nnn Volume Flow ext. 10(nnn-9) m3/s 0.001ml/s to 10000ml/ + case 0x48: + case 0x48+1: + case 0x48+2: + case 0x48+3: + case 0x48+4: + case 0x48+5: + case 0x48+6: + case 0x48+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Volume flow (%s m^3/s)", mbus_unit_prefix(n-9)); + + break; + + // E101 0nnn Mass flow 10(nnn-3) kg/h 0.001kg/h to 10000kg/ + case 0x50: + case 0x50+1: + case 0x50+2: + case 0x50+3: + case 0x50+4: + case 0x50+5: + case 0x50+6: + case 0x50+7: + + n = (vif & 0x07); + snprintf(buff, sizeof(buff), "Mass flow (%s kg/h)", mbus_unit_prefix(n-3)); + + break; + + // E101 10nn Flow Temperature 10(nn-3) °C 0.001°C to 1°C + case 0x58: + case 0x58+1: + case 0x58+2: + case 0x58+3: + + n = (vif & 0x03); + snprintf(buff, sizeof(buff), "Flow temperature (%sdeg C)", mbus_unit_prefix(n-3)); + + break; + + // E101 11nn Return Temperature 10(nn-3) °C 0.001°C to 1°C + case 0x5C: + case 0x5C+1: + case 0x5C+2: + case 0x5C+3: + + n = (vif & 0x03); + snprintf(buff, sizeof(buff), "Return temperature (%sdeg C)", mbus_unit_prefix(n-3)); + + break; + + // E110 10nn Pressure 10(nn-3) bar 1mbar to 1000mbar + case 0x68: + case 0x68+1: + case 0x68+2: + case 0x68+3: + + n = (vif & 0x03); + snprintf(buff, sizeof(buff), "Pressure (%s bar)", mbus_unit_prefix(n-3)); + + break; + + // E010 00nn On Time + // nn = 00 seconds + // nn = 01 minutes + // nn = 10 hours + // nn = 11 days + // E010 01nn Operating Time coded like OnTime + // E111 00nn Averaging Duration coded like OnTime + // E111 01nn Actuality Duration coded like OnTime + case 0x20: + case 0x20+1: + case 0x20+2: + case 0x20+3: + case 0x24: + case 0x24+1: + case 0x24+2: + case 0x24+3: + case 0x70: + case 0x70+1: + case 0x70+2: + case 0x70+3: + case 0x74: + case 0x74+1: + case 0x74+2: + case 0x74+3: + { + int offset; + + if ((vif & 0x7C) == 0x20) + offset = snprintf(buff, sizeof(buff), "On time "); + else if ((vif & 0x7C) == 0x24) + offset = snprintf(buff, sizeof(buff), "Operating time "); + else if ((vif & 0x7C) == 0x70) + offset = snprintf(buff, sizeof(buff), "Averaging Duration "); + else + offset = snprintf(buff, sizeof(buff), "Actuality Duration "); + + switch (vif & 0x03) + { + case 0x00: + snprintf(&buff[offset], sizeof(buff)-offset, "(seconds)"); + break; + case 0x01: + snprintf(&buff[offset], sizeof(buff)-offset, "(minutes)"); + break; + case 0x02: + snprintf(&buff[offset], sizeof(buff)-offset, "(hours)"); + break; + case 0x03: + snprintf(&buff[offset], sizeof(buff)-offset, "(days)"); + break; + } + } + break; + + // E110 110n Time Point + // n = 0 date + // n = 1 time & date + // data type G + // data type F + case 0x6C: + case 0x6C+1: + + if (vif & 0x1) + snprintf(buff, sizeof(buff), "Time Point (time & date)"); + else + snprintf(buff, sizeof(buff), "Time Point (date)"); + + break; + + // E110 00nn Temperature Difference 10(nn-3)K (mK to K) + case 0x60: + case 0x60+1: + case 0x60+2: + case 0x60+3: + + n = (vif & 0x03); + + snprintf(buff, sizeof(buff), "Temperature Difference (%s deg C)", mbus_unit_prefix(n-3)); + + break; + + // E110 01nn External Temperature 10(nn-3) °C 0.001°C to 1°C + case 0x64: + case 0x64+1: + case 0x64+2: + case 0x64+3: + + n = (vif & 0x03); + snprintf(buff, sizeof(buff), "External temperature (%s deg C)", mbus_unit_prefix(n-3)); + + break; + + // E110 1110 Units for H.C.A. dimensionless + case 0x6E: + snprintf(buff, sizeof(buff), "Units for H.C.A."); + break; + + // E110 1111 Reserved + case 0x6F: + snprintf(buff, sizeof(buff), "Reserved"); + break; + + // Custom VIF in the following string: never reached... + case 0x7C: + snprintf(buff, sizeof(buff), "Custom VIF"); + break; + + // Fabrication No + case 0x78: + snprintf(buff, sizeof(buff), "Fabrication number"); + break; + + // Bus Address + case 0x7A: + snprintf(buff, sizeof(buff), "Bus Address"); + break; + + // Manufacturer specific: 7Fh / FF + case 0x7F: + case 0xFF: + snprintf(buff, sizeof(buff), "Manufacturer specific"); + break; + + default: + snprintf(buff, sizeof(buff), "Unknown (VIF=0x%.2X)", vif); + break; + } + + + return buff; +} + + +//------------------------------------------------------------------------------ +// Lookup the error message +// +// See section 6.6 Codes for general application errors in the M-BUS spec +//------------------------------------------------------------------------------ +const char * +mbus_data_error_lookup(int error) +{ + static char buff[256]; + + switch (error) + { + case MBUS_ERROR_DATA_UNSPECIFIED: + snprintf(buff, sizeof(buff), "Unspecified error"); + break; + + case MBUS_ERROR_DATA_UNIMPLEMENTED_CI: + snprintf(buff, sizeof(buff), "Unimplemented CI-Field"); + break; + + case MBUS_ERROR_DATA_BUFFER_TOO_LONG: + snprintf(buff, sizeof(buff), "Buffer too long, truncated"); + break; + + case MBUS_ERROR_DATA_TOO_MANY_RECORDS: + snprintf(buff, sizeof(buff), "Too many records"); + break; + + case MBUS_ERROR_DATA_PREMATURE_END: + snprintf(buff, sizeof(buff), "Premature end of record"); + break; + + case MBUS_ERROR_DATA_TOO_MANY_DIFES: + snprintf(buff, sizeof(buff), "More than 10 DIFE´s"); + break; + + case MBUS_ERROR_DATA_TOO_MANY_VIFES: + snprintf(buff, sizeof(buff), "More than 10 VIFE´s"); + break; + + case MBUS_ERROR_DATA_RESERVED: + snprintf(buff, sizeof(buff), "Reserved"); + break; + + case MBUS_ERROR_DATA_APPLICATION_BUSY: + snprintf(buff, sizeof(buff), "Application busy"); + break; + + case MBUS_ERROR_DATA_TOO_MANY_READOUTS: + snprintf(buff, sizeof(buff), "Too many readouts"); + break; + + default: + snprintf(buff, sizeof(buff), "Unknown error (0x%.2X)", error); + break; + } + + return buff; +} + + +//------------------------------------------------------------------------------ +/// Lookup the unit from the VIB (VIF or VIFE) +// +// Enhanced Identification +// E000 1000 Access Number (transmission count) +// E000 1001 Medium (as in fixed header) +// E000 1010 Manufacturer (as in fixed header) +// E000 1011 Parameter set identification +// E000 1100 Model / Version +// E000 1101 Hardware version # +// E000 1110 Firmware version # +// E000 1111 Software version # +//------------------------------------------------------------------------------ +const char * +mbus_vib_unit_lookup(mbus_value_information_block *vib) +{ + static char buff[256]; + int n; + + if (vib == NULL) + return ""; + + if (vib->vif == 0xFD || vib->vif == 0xFB) // first type of VIF extention: see table 8.4.4 + { + if (vib->nvife == 0) + { + snprintf(buff, sizeof(buff), "Missing VIF extension"); + } + else if (vib->vife[0] == 0x08 || vib->vife[0] == 0x88) + { + // E000 1000 + snprintf(buff, sizeof(buff), "Access Number (transmission count)"); + } + else if (vib->vife[0] == 0x09|| vib->vife[0] == 0x89) + { + // E000 1001 + snprintf(buff, sizeof(buff), "Medium (as in fixed header)"); + } + else if (vib->vife[0] == 0x0A || vib->vife[0] == 0x8A) + { + // E000 1010 + snprintf(buff, sizeof(buff), "Manufacturer (as in fixed header)"); + } + else if (vib->vife[0] == 0x0B || vib->vife[0] == 0x8B) + { + // E000 1010 + snprintf(buff, sizeof(buff), "Parameter set identification"); + } + else if (vib->vife[0] == 0x0C || vib->vife[0] == 0x8C) + { + // E000 1100 + snprintf(buff, sizeof(buff), "Model / Version"); + } + else if (vib->vife[0] == 0x0D || vib->vife[0] == 0x8D) + { + // E000 1100 + snprintf(buff, sizeof(buff), "Hardware version"); + } + else if (vib->vife[0] == 0x0E || vib->vife[0] == 0x8E) + { + // E000 1101 + snprintf(buff, sizeof(buff), "Firmware version"); + } + else if (vib->vife[0] == 0x0F || vib->vife[0] == 0x8F) + { + // E000 1101 + snprintf(buff, sizeof(buff), "Software version"); + } + else if (vib->vife[0] == 0x17 || vib->vife[0] == 0x97) + { + // VIFE = E001 0111 Error flags + snprintf(buff, sizeof(buff), "Error flags"); + } + else if (vib->vife[0] == 0x10) + { + // VIFE = E001 0000 Customer location + snprintf(buff, sizeof(buff), "Customer location"); + } + else if (vib->vife[0] == 0x0C) + { + // E000 1100 Model / Version + snprintf(buff, sizeof(buff), "Model / Version"); + } + else if (vib->vife[0] == 0x11) + { + // VIFE = E001 0001 Customer + snprintf(buff, sizeof(buff), "Customer"); + } + else if (vib->vife[0] == 0x9) + { + // VIFE = E001 0110 Password + snprintf(buff, sizeof(buff), "Password"); + } + else if (vib->vife[0] == 0x0b) + { + // VIFE = E000 1011 Parameter set identification + snprintf(buff, sizeof(buff), "Parameter set identification"); + } + else if ((vib->vife[0] & 0x70) == 0x40) + { + // VIFE = E100 nnnn 10^(nnnn-9) V + n = (vib->vife[0] & 0x0F); + snprintf(buff, sizeof(buff), "%s V", mbus_unit_prefix(n-9)); + } + else if ((vib->vife[0] & 0x70) == 0x50) + { + // VIFE = E101 nnnn 10nnnn-12 A + n = (vib->vife[0] & 0x0F); + snprintf(buff, sizeof(buff), "%s A", mbus_unit_prefix(n-12)); + } + else if ((vib->vife[0] & 0xF0) == 0x70) + { + // VIFE = E111 nnn Reserved + snprintf(buff, sizeof(buff), "Reserved VIF extension"); + } + else + { + snprintf(buff, sizeof(buff), "Unrecongized VIF extension: 0x%.2x", vib->vife[0]); + } + return buff; + } + else if (vib->vif == 0x7C) + { + // custom VIF + snprintf(buff, sizeof(buff), "%s", vib->custom_vif); + return buff; + } + + return mbus_vif_unit_lookup(vib->vif); // no extention, use VIF +} + +//------------------------------------------------------------------------------ +// Decode data and write to string +// +// Data format (for record->data data array) +// +// Length in Bit Code Meaning Code Meaning +// 0 0000 No data 1000 Selection for Readout +// 8 0001 8 Bit Integer 1001 2 digit BCD +// 16 0010 16 Bit Integer 1010 4 digit BCD +// 24 0011 24 Bit Integer 1011 6 digit BCD +// 32 0100 32 Bit Integer 1100 8 digit BCD +// 32 / N 0101 32 Bit Real 1101 variable length +// 48 0110 48 Bit Integer 1110 12 digit BCD +// 64 0111 64 Bit Integer 1111 Special Functions +// +// The Code is stored in record->drh.dib.dif +// +/// +/// Return a string containing the data +/// +// Source: MBDOC48.PDF +// +//------------------------------------------------------------------------------ +const char * +mbus_data_record_decode(mbus_data_record *record) +{ + static char buff[768]; + u_char vif, vife; + + // ignore extension bit + vif = (record->drh.vib.vif & 0x7F); + vife = (record->drh.vib.vife[0] & 0x7F); + + if (record) + { + int val; + float val3; + long long val4; + struct tm time; + + switch (record->drh.dib.dif & 0x0F) + { + case 0x00: // no data + + buff[0] = 0; + + break; + + case 0x01: // 1 byte integer (8 bit) + + val = mbus_data_int_decode(record->data, 1); + + snprintf(buff, sizeof(buff), "%d", val); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 1 byte integer\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + + case 0x02: // 2 byte (16 bit) + + // E110 1100 Time Point (date) + if (vif == 0x6C) + { + mbus_data_tm_decode(&time, record->data, 2); + snprintf(buff, sizeof(buff), "%04d-%02d-%02d", + (time.tm_year + 2000), + (time.tm_mon + 1), + time.tm_mday); + } + else // 2 byte integer + { + val = mbus_data_int_decode(record->data, 2); + snprintf(buff, sizeof(buff), "%d", val); + if (debug) + printf("%s: DIF 0x%.2x was decoded using 2 byte integer\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + } + + break; + + case 0x03: // 3 byte integer (24 bit) + + val = mbus_data_int_decode(record->data, 3); + + snprintf(buff, sizeof(buff), "%d", val); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 3 byte integer\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x04: // 4 byte (32 bit) + + // E110 1101 Time Point (date/time) + // E011 0000 Start (date/time) of tariff + // E111 0000 Date and time of battery change + if ( (vif == 0x6D) || + ((record->drh.vib.vif == 0xFD) && (vife == 0x30)) || + ((record->drh.vib.vif == 0xFD) && (vife == 0x70))) + { + mbus_data_tm_decode(&time, record->data, 4); + snprintf(buff, sizeof(buff), "%04d-%02d-%02dT%02d:%02d:%02d", + (time.tm_year + 2000), + (time.tm_mon + 1), + time.tm_mday, + time.tm_hour, + time.tm_min, + time.tm_sec); + } + else // 4 byte integer + { + val = mbus_data_int_decode(record->data, 4); + snprintf(buff, sizeof(buff), "%d", val); + } + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 4 byte integer\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x05: // 4 Byte Real (32 bit) + + val3 = mbus_data_float_decode(record->data); + + snprintf(buff, sizeof(buff), "%f", val3); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 4 byte Real\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x06: // 6 byte integer (48 bit) + + val4 = mbus_data_long_long_decode(record->data, 6); + + snprintf(buff, sizeof(buff), "%lld", val4); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 6 byte integer\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x07: // 8 byte integer (64 bit) + + val4 = mbus_data_long_long_decode(record->data, 8); + + snprintf(buff, sizeof(buff), "%lld", val4); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 8 byte integer\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + //case 0x08: + + case 0x09: // 2 digit BCD (8 bit) + + val = (int)mbus_data_bcd_decode(record->data, 1); + snprintf(buff, sizeof(buff), "%d", val); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 2 digit BCD\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x0A: // 4 digit BCD (16 bit) + + val = (int)mbus_data_bcd_decode(record->data, 2); + snprintf(buff, sizeof(buff), "%d", val); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 4 digit BCD\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x0B: // 6 digit BCD (24 bit) + + val = (int)mbus_data_bcd_decode(record->data, 3); + snprintf(buff, sizeof(buff), "%d", val); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 6 digit BCD\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x0C: // 8 digit BCD (32 bit) + + val = (int)mbus_data_bcd_decode(record->data, 4); + snprintf(buff, sizeof(buff), "%d", val); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 8 digit BCD\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x0E: // 12 digit BCD (48 bit) + + val4 = mbus_data_bcd_decode(record->data, 6); + snprintf(buff, sizeof(buff), "%lld", val4); + + if (debug) + printf("%s: DIF 0x%.2x was decoded using 12 digit BCD\n", __PRETTY_FUNCTION__, record->drh.dib.dif); + + break; + + case 0x0F: // special functions + + mbus_data_bin_decode(buff, record->data, record->data_len, sizeof(buff)); + break; + + case 0x0D: // variable length + if (record->data_len <= 0xBF) + { + mbus_data_str_decode(buff, record->data, record->data_len); + break; + } + /* FALLTHROUGH */ + + default: + + snprintf(buff, sizeof(buff), "Unknown DIF (0x%.2x)", record->drh.dib.dif); + break; + } + + return buff; + } + + return NULL; +} +//------------------------------------------------------------------------------ +/// Return the unit description for a variable-length data record +//------------------------------------------------------------------------------ +const char * +mbus_data_record_unit(mbus_data_record *record) +{ + static char buff[128]; + + if (record) + { + snprintf(buff, sizeof(buff), "%s", mbus_vib_unit_lookup(&(record->drh.vib))); + + return buff; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +/// Return the value for a variable-length data record +//------------------------------------------------------------------------------ +const char * +mbus_data_record_value(mbus_data_record *record) +{ + static char buff[768]; + + if (record) + { + snprintf(buff, sizeof(buff), "%s", mbus_data_record_decode(record)); + + return buff; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +/// Return a string containing the function description +//------------------------------------------------------------------------------ +const char * +mbus_data_record_function(mbus_data_record *record) +{ + static char buff[128]; + + if (record) + { + switch (record->drh.dib.dif & MBUS_DATA_RECORD_DIF_MASK_FUNCTION) + { + case 0x00: + snprintf(buff, sizeof(buff), "Instantaneous value"); + break; + + case 0x10: + snprintf(buff, sizeof(buff), "Maximum value"); + break; + + case 0x20: + snprintf(buff, sizeof(buff), "Minimum value"); + break; + + case 0x30: + snprintf(buff, sizeof(buff), "Value during error state"); + break; + + default: + snprintf(buff, sizeof(buff), "unknown"); + } + + return buff; + } + + return NULL; +} + + +/// +/// For fixed-length frames, return a string describing the type of value (stored or actual) +/// +const char * +mbus_data_fixed_function(int status) +{ + static char buff[128]; + + snprintf(buff, sizeof(buff), "%s", + (status & MBUS_DATA_FIXED_STATUS_DATE_MASK) == MBUS_DATA_FIXED_STATUS_DATE_STORED ? + "Stored value" : "Actual value" ); + + return buff; +} + +//------------------------------------------------------------------------------ +// +// PARSER FUNCTIONS +// +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +/// PARSE M-BUS frame data structures from binary data. +//------------------------------------------------------------------------------ +int +mbus_parse(mbus_frame *frame, u_char *data, size_t data_size) +{ + size_t i, len; + + if (frame && data && data_size > 0) + { + if (parse_debug) + printf("%s: Attempting to parse binary data [size = %zu]\n", __PRETTY_FUNCTION__, data_size); + + if (parse_debug) + printf("%s: ", __PRETTY_FUNCTION__); + + for (i = 0; i < data_size && parse_debug; i++) + { + printf("%.2X ", data[i] & 0xFF); + } + + if (parse_debug) + printf("\n%s: done.\n", __PRETTY_FUNCTION__); + + switch (data[0]) + { + case MBUS_FRAME_ACK_START: + + // OK, got a valid ack frame, require no more data + frame->start1 = data[0]; + frame->type = MBUS_FRAME_TYPE_ACK; + return 0; + //return MBUS_FRAME_BASE_SIZE_ACK - 1; // == 0 + + case MBUS_FRAME_SHORT_START: + + if (data_size < MBUS_FRAME_BASE_SIZE_SHORT) + { + // OK, got a valid short packet start, but we need more data + return MBUS_FRAME_BASE_SIZE_SHORT - data_size; + } + + if (data_size != MBUS_FRAME_BASE_SIZE_SHORT) + { + snprintf(error_str, sizeof(error_str), "Too much data in frame."); + + // too much data... ? + return -2; + } + + // init frame data structure + frame->start1 = data[0]; + frame->control = data[1]; + frame->address = data[2]; + frame->checksum = data[3]; + frame->stop = data[4]; + + frame->type = MBUS_FRAME_TYPE_SHORT; + + // verify the frame + if (mbus_frame_verify(frame) != 0) + { + return -3; + } + + // successfully parsed data + return 0; + + case MBUS_FRAME_LONG_START: // (also CONTROL) + + if (data_size < 3) + { + // OK, got a valid long/control packet start, but we need + // more data to determine the length + return 3 - data_size; + } + + // init frame data structure + frame->start1 = data[0]; + frame->length1 = data[1]; + frame->length2 = data[2]; + + if (frame->length1 != frame->length2) + { + snprintf(error_str, sizeof(error_str), "Invalid M-Bus frame length."); + + // not a valid M-bus frame + return -2; + } + + // check length of packet: + len = frame->length1; + + if (data_size < (size_t)(MBUS_FRAME_FIXED_SIZE_LONG + len)) + { + // OK, but we need more data + return MBUS_FRAME_FIXED_SIZE_LONG + len - data_size; + } + + if (data_size > (size_t)(MBUS_FRAME_FIXED_SIZE_LONG + len)) + { + snprintf(error_str, sizeof(error_str), "Too much data in frame."); + + // too much data... ? + return -2; + } + + // we got the whole packet, continue parsing + frame->start2 = data[3]; + frame->control = data[4]; + frame->address = data[5]; + frame->control_information = data[6]; + + frame->data_size = len - 3; + for (i = 0; i < frame->data_size; i++) + { + frame->data[i] = data[7 + i]; + } + + frame->checksum = data[data_size-2]; // data[6 + frame->data_size + 1] + frame->stop = data[data_size-1]; // data[6 + frame->data_size + 2] + + if (frame->data_size == 0) + { + frame->type = MBUS_FRAME_TYPE_CONTROL; + } + else + { + frame->type = MBUS_FRAME_TYPE_LONG; + } + + // verify the frame + if (mbus_frame_verify(frame) != 0) + { + return -3; + } + + // successfully parsed data + return 0; + default: + snprintf(error_str, sizeof(error_str), "Invalid M-Bus frame start."); + + // not a valid M-Bus frame header (start byte) + return -4; + } + + } + + snprintf(error_str, sizeof(error_str), "Got null pointer to frame, data or zero data_size."); + + return -1; +} + + +//------------------------------------------------------------------------------ +/// Parse the fixed-length data of a M-Bus frame +//------------------------------------------------------------------------------ +int +mbus_data_fixed_parse(mbus_frame *frame, mbus_data_fixed *data) +{ + if (frame && data) + { + // copy the fixed-length data structure + memcpy((void *)data, (void *)(frame->data), sizeof(mbus_data_fixed)); + + return 0; + } + + return -1; +} + + +//------------------------------------------------------------------------------ +/// Parse the variable-length data of a M-Bus frame +//------------------------------------------------------------------------------ +int +mbus_data_variable_parse(mbus_frame *frame, mbus_data_variable *data) +{ + mbus_data_record *record; + size_t i, j; + + if (frame && data) + { + // parse header + data->nrecords = 0; + data->more_records_follow = 0; + i = sizeof(mbus_data_variable_header); + if(frame->data_size < i) + return -1; + + // first copy the variable data fixed header + memcpy((void *)&(data->header), (void *)(frame->data), i); + + data->record = NULL; + + while (i < frame->data_size) + { + if ((record = mbus_data_record_new()) == NULL) + { + // clean up... + return (-2); + } + + // copy timestamp + memcpy((void *)&(record->timestamp), (void *)&(frame->timestamp), sizeof(time_t)); + + // read and parse DIB (= DIF + DIFE) + + // DIF + record->drh.dib.dif = frame->data[i]; + + if (record->drh.dib.dif == 0x0F || record->drh.dib.dif == 0x1F) + { + if ((record->drh.dib.dif & 0xFF) == 0x1F) + { + data->more_records_follow = 1; + } + + i++; + // just copy the remaining data as it is vendor specific + record->data_len = frame->data_size - i; + for (j = 0; j < record->data_len; j++) + { + record->data[j] = frame->data[i++]; + } + + // append the record and move on to next one + mbus_data_record_append(data, record); + data->nrecords++; + continue; + } + + // calculate length of data record + record->data_len = mbus_dif_datalength_lookup(record->drh.dib.dif); + + // read DIF extensions + record->drh.dib.ndife = 0; + while (frame->data[i] & MBUS_DIB_DIF_EXTENSION_BIT && + record->drh.dib.ndife < NITEMS(record->drh.dib.dife)) + { + u_char dife = frame->data[i+1]; + record->drh.dib.dife[record->drh.dib.ndife] = dife; + + record->drh.dib.ndife++; + i++; + } + i++; + + // read and parse VIB (= VIF + VIFE) + + // VIF + record->drh.vib.vif = frame->data[i]; + + if (record->drh.vib.vif == 0x7C) + { + // variable length VIF in ASCII format + int var_vif_len; + i++; + var_vif_len = frame->data[i++]; + mbus_data_str_decode(record->drh.vib.custom_vif, &(frame->data[i]), var_vif_len); + i += var_vif_len; + } + else + { + // VIFE + record->drh.vib.nvife = 0; + while (frame->data[i] & MBUS_DIB_VIF_EXTENSION_BIT && + record->drh.vib.nvife < NITEMS(record->drh.vib.vife)) + { + u_char vife = frame->data[i+1]; + record->drh.vib.vife[record->drh.vib.nvife] = vife; + + record->drh.vib.nvife++; + i++; + } + i++; + } + + // re-calculate data length, if of variable length type + if ((record->drh.dib.dif & 0x0F) == 0x0D) // flag for variable length data + { + if(frame->data[i] <= 0xBF) + record->data_len = frame->data[i++]; + else if(frame->data[i] >= 0xC0 && frame->data[i] <= 0xCF) + record->data_len = (frame->data[i++] - 0xC0) * 2; + else if(frame->data[i] >= 0xD0 && frame->data[i] <= 0xDF) + record->data_len = (frame->data[i++] - 0xD0) * 2; + else if(frame->data[i] >= 0xE0 && frame->data[i] <= 0xEF) + record->data_len = frame->data[i++] - 0xE0; + else if(frame->data[i] >= 0xF0 && frame->data[i] <= 0xFA) + record->data_len = frame->data[i++] - 0xF0; + } + + // copy data + for (j = 0; j < record->data_len; j++) + { + record->data[j] = frame->data[i++]; + } + + // append the record and move on to next one + mbus_data_record_append(data, record); + data->nrecords++; + } + + return 0; + } + + return -1; +} + +//------------------------------------------------------------------------------ +/// Check the stype of the frame data (error, fixed or variable) and dispatch to the +/// corresponding parser function. +//------------------------------------------------------------------------------ +int +mbus_frame_data_parse(mbus_frame *frame, mbus_frame_data *data) +{ + if (frame && data) + { + if (frame->control_information == MBUS_CONTROL_INFO_ERROR_GENERAL) + { + data->type = MBUS_DATA_TYPE_ERROR; + + if (frame->data_size > 0) + { + data->error = (int) frame->data[0]; + } + else + { + data->error = 0; + } + + return 0; + } + else if (frame->control_information == MBUS_CONTROL_INFO_RESP_FIXED) + { + if (frame->data_size == 0) + { + snprintf(error_str, sizeof(error_str), "Got zero data_size."); + + return -1; + } + + data->type = MBUS_DATA_TYPE_FIXED; + return mbus_data_fixed_parse(frame, &(data->data_fix)); + } + else if (frame->control_information == MBUS_CONTROL_INFO_RESP_VARIABLE) + { + if (frame->data_size == 0) + { + snprintf(error_str, sizeof(error_str), "Got zero data_size."); + + return -1; + } + + data->type = MBUS_DATA_TYPE_VARIABLE; + return mbus_data_variable_parse(frame, &(data->data_var)); + } + else + { + snprintf(error_str, sizeof(error_str), "Unknown control information 0x%.2x", frame->control_information); + + return -1; + } + } + + snprintf(error_str, sizeof(error_str), "Got null pointer to frame or data."); + + return -1; +} + +//------------------------------------------------------------------------------ +/// Pack the M-bus frame into a binary string representation that can be sent +/// on the bus. The binary packet format is different for the different types +/// of M-bus frames. +//------------------------------------------------------------------------------ +int +mbus_frame_pack(mbus_frame *frame, u_char *data, size_t data_size) +{ + size_t i, offset = 0; + + if (frame && data) + { + if (mbus_frame_calc_length(frame) == -1) + { + return -2; + } + + if (mbus_frame_calc_checksum(frame) == -1) + { + return -3; + } + + switch (frame->type) + { + case MBUS_FRAME_TYPE_ACK: + + if (data_size < MBUS_FRAME_ACK_BASE_SIZE) + { + return -4; + } + + data[offset++] = frame->start1; + + return offset; + + case MBUS_FRAME_TYPE_SHORT: + + if (data_size < MBUS_FRAME_SHORT_BASE_SIZE) + { + return -4; + } + + data[offset++] = frame->start1; + data[offset++] = frame->control; + data[offset++] = frame->address; + data[offset++] = frame->checksum; + data[offset++] = frame->stop; + + return offset; + + case MBUS_FRAME_TYPE_CONTROL: + + if (data_size < MBUS_FRAME_CONTROL_BASE_SIZE) + { + return -4; + } + + data[offset++] = frame->start1; + data[offset++] = frame->length1; + data[offset++] = frame->length2; + data[offset++] = frame->start2; + + data[offset++] = frame->control; + data[offset++] = frame->address; + data[offset++] = frame->control_information; + + data[offset++] = frame->checksum; + data[offset++] = frame->stop; + + return offset; + + case MBUS_FRAME_TYPE_LONG: + + if (data_size < frame->data_size + MBUS_FRAME_LONG_BASE_SIZE) + { + return -4; + } + + data[offset++] = frame->start1; + data[offset++] = frame->length1; + data[offset++] = frame->length2; + data[offset++] = frame->start2; + + data[offset++] = frame->control; + data[offset++] = frame->address; + data[offset++] = frame->control_information; + + for (i = 0; i < frame->data_size; i++) + { + data[offset++] = frame->data[i]; + } + + data[offset++] = frame->checksum; + data[offset++] = frame->stop; + + return offset; + + default: + return -5; + } + } + + return -1; +} + + +//------------------------------------------------------------------------------ +/// pack the data stuctures into frame->data +//------------------------------------------------------------------------------ +int +mbus_frame_internal_pack(mbus_frame *frame, mbus_frame_data *frame_data) +{ + mbus_data_record *record; + int j; + + if (frame == NULL || frame_data == NULL) + return -1; + + frame->data_size = 0; + + switch (frame_data->type) + { + case MBUS_DATA_TYPE_ERROR: + + frame->data[frame->data_size++] = (char) frame_data->error; + + break; + + case MBUS_DATA_TYPE_FIXED: + + // + // pack fixed data structure + // + frame->data[frame->data_size++] = frame_data->data_fix.id_bcd[0]; + frame->data[frame->data_size++] = frame_data->data_fix.id_bcd[1]; + frame->data[frame->data_size++] = frame_data->data_fix.id_bcd[2]; + frame->data[frame->data_size++] = frame_data->data_fix.id_bcd[3]; + frame->data[frame->data_size++] = frame_data->data_fix.tx_cnt; + frame->data[frame->data_size++] = frame_data->data_fix.status; + frame->data[frame->data_size++] = frame_data->data_fix.cnt1_type; + frame->data[frame->data_size++] = frame_data->data_fix.cnt2_type; + frame->data[frame->data_size++] = frame_data->data_fix.cnt1_val[0]; + frame->data[frame->data_size++] = frame_data->data_fix.cnt1_val[1]; + frame->data[frame->data_size++] = frame_data->data_fix.cnt1_val[2]; + frame->data[frame->data_size++] = frame_data->data_fix.cnt1_val[3]; + frame->data[frame->data_size++] = frame_data->data_fix.cnt2_val[0]; + frame->data[frame->data_size++] = frame_data->data_fix.cnt2_val[1]; + frame->data[frame->data_size++] = frame_data->data_fix.cnt2_val[2]; + frame->data[frame->data_size++] = frame_data->data_fix.cnt2_val[3]; + + break; + + case MBUS_DATA_TYPE_VARIABLE: + + // + // first pack variable data structure header + // + frame->data[frame->data_size++] = frame_data->data_var.header.id_bcd[0]; + frame->data[frame->data_size++] = frame_data->data_var.header.id_bcd[1]; + frame->data[frame->data_size++] = frame_data->data_var.header.id_bcd[2]; + frame->data[frame->data_size++] = frame_data->data_var.header.id_bcd[3]; + frame->data[frame->data_size++] = frame_data->data_var.header.manufacturer[0]; + frame->data[frame->data_size++] = frame_data->data_var.header.manufacturer[1]; + frame->data[frame->data_size++] = frame_data->data_var.header.version; + frame->data[frame->data_size++] = frame_data->data_var.header.medium; + frame->data[frame->data_size++] = frame_data->data_var.header.access_no; + frame->data[frame->data_size++] = frame_data->data_var.header.status; + frame->data[frame->data_size++] = frame_data->data_var.header.signature[0]; + frame->data[frame->data_size++] = frame_data->data_var.header.signature[1]; + + // + // pack all data records + // + for (record = frame_data->data_var.record; record; record = record->next) + { + // pack DIF + if (parse_debug) + printf("%s: packing DIF [%zu]", __PRETTY_FUNCTION__, frame->data_size); + frame->data[frame->data_size++] = record->drh.dib.dif; + for (j = 0; j < record->drh.dib.ndife; j++) + { + frame->data[frame->data_size++] = record->drh.dib.dife[j]; + } + + // pack VIF + if (parse_debug) + printf("%s: packing VIF [%zu]", __PRETTY_FUNCTION__, frame->data_size); + frame->data[frame->data_size++] = record->drh.vib.vif; + for (j = 0; j < record->drh.vib.nvife; j++) + { + frame->data[frame->data_size++] = record->drh.vib.vife[j]; + } + + // pack data + if (parse_debug) + printf("%s: packing data [%zu : %zu]", __PRETTY_FUNCTION__, frame->data_size, record->data_len); + for (j = 0; j < record->data_len; j++) + { + frame->data[frame->data_size++] = record->data[j]; + } + } + + break; + + default: + return -2; + } + + return 0; +} + +//------------------------------------------------------------------------------ +// +// Print/Dump functions +// +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +/// Switch parse debugging +//------------------------------------------------------------------------------ +void +mbus_parse_set_debug(int debug) +{ + parse_debug = debug; +} + +//------------------------------------------------------------------------------ +/// Dump frame in HEX on standard output +//------------------------------------------------------------------------------ +int +mbus_frame_print(mbus_frame *frame) +{ + mbus_frame *iter; + u_char data_buff[256]; + int len, i; + + if (frame == NULL) + return -1; + + for (iter = frame; iter; iter = iter->next) + { + if ((len = mbus_frame_pack(iter, data_buff, sizeof(data_buff))) == -1) + { + return -2; + } + + printf("%s: Dumping M-Bus frame [type %d, %d bytes]: ", __PRETTY_FUNCTION__, iter->type, len); + for (i = 0; i < len; i++) + { + printf("%.2X ", data_buff[i]); + } + printf("\n"); + } + + return 0; +} + +//------------------------------------------------------------------------------ +/// +/// Print the data part of a frame. +/// +//------------------------------------------------------------------------------ +int +mbus_frame_data_print(mbus_frame_data *data) +{ + if (data) + { + if (data->type == MBUS_DATA_TYPE_ERROR) + { + return mbus_data_error_print(data->error); + } + + if (data->type == MBUS_DATA_TYPE_FIXED) + { + return mbus_data_fixed_print(&(data->data_fix)); + } + + if (data->type == MBUS_DATA_TYPE_VARIABLE) + { + return mbus_data_variable_print(&(data->data_var)); + } + } + + return -1; +} + +//------------------------------------------------------------------------------ +/// Print M-bus frame info to stdout +//------------------------------------------------------------------------------ +int +mbus_data_variable_header_print(mbus_data_variable_header *header) +{ + if (header) + { + printf("%s: ID = %lld\n", __PRETTY_FUNCTION__, + mbus_data_bcd_decode(header->id_bcd, 4)); + + printf("%s: Manufacturer = 0x%.2X%.2X\n", __PRETTY_FUNCTION__, + header->manufacturer[1], header->manufacturer[0]); + + printf("%s: Manufacturer = %s\n", __PRETTY_FUNCTION__, + mbus_decode_manufacturer(header->manufacturer[0], header->manufacturer[1])); + + printf("%s: Version = 0x%.2X\n", __PRETTY_FUNCTION__, header->version); + printf("%s: Medium = %s (0x%.2X)\n", __PRETTY_FUNCTION__, mbus_data_variable_medium_lookup(header->medium), header->medium); + printf("%s: Access # = 0x%.2X\n", __PRETTY_FUNCTION__, header->access_no); + printf("%s: Status = 0x%.2X\n", __PRETTY_FUNCTION__, header->status); + printf("%s: Signature = 0x%.2X%.2X\n", __PRETTY_FUNCTION__, + header->signature[1], header->signature[0]); + + } + + return -1; +} + +int +mbus_data_variable_print(mbus_data_variable *data) +{ + mbus_data_record *record; + size_t j; + + if (data) + { + mbus_data_variable_header_print(&(data->header)); + + for (record = data->record; record; record = record->next) + { + // DIF + printf("DIF = %.2X\n", record->drh.dib.dif); + printf("DIF.Extension = %s\n", (record->drh.dib.dif & MBUS_DIB_DIF_EXTENSION_BIT) ? "Yes":"No"); + printf("DIF.Function = %s\n", (record->drh.dib.dif & 0x30) ? "Minimum value" : "Instantaneous value" ); + printf("DIF.Data = %.2X\n", record->drh.dib.dif & 0x0F); + + // VENDOR SPECIFIC + if (record->drh.dib.dif == 0x0F || record->drh.dib.dif == 0x1F) //MBUS_DIB_DIF_VENDOR_SPECIFIC) + { + printf("%s: VENDOR DATA [size=%zd] = ", __PRETTY_FUNCTION__, record->data_len); + for (j = 0; j < record->data_len; j++) + { + printf("%.2X ", record->data[j]); + } + printf("\n"); + + if (record->drh.dib.dif == 0x1F) + { + printf("%s: More records follow in next telegram\n", __PRETTY_FUNCTION__); + } + continue; + } + + // calculate length of data record + printf("DATA LENGTH = %zd\n", record->data_len); + + // DIFE + for (j = 0; j < record->drh.dib.ndife; j++) + { + u_char dife = record->drh.dib.dife[j]; + + printf("DIFE[%zd] = %.2X\n", j, dife); + printf("DIFE[%zd].Extension = %s\n", j, (dife & MBUS_DIB_DIF_EXTENSION_BIT) ? "Yes" : "No"); + printf("DIFE[%zd].Function = %s\n", j, (dife & 0x30) ? "Minimum value" : "Instantaneous value" ); + printf("DIFE[%zd].Data = %.2X\n", j, dife & 0x0F); + } + + } + } + + return -1; +} + +int +mbus_data_fixed_print(mbus_data_fixed *data) +{ + if (data) + { + printf("%s: ID = %d\n", __PRETTY_FUNCTION__, (int)mbus_data_bcd_decode(data->id_bcd, 4)); + printf("%s: Access # = 0x%.2X\n", __PRETTY_FUNCTION__, data->tx_cnt); + printf("%s: Status = 0x%.2X\n", __PRETTY_FUNCTION__, data->status); + printf("%s: Function = %s\n", __PRETTY_FUNCTION__, mbus_data_fixed_function(data->status)); + + printf("%s: Medium1 = %s\n", __PRETTY_FUNCTION__, mbus_data_fixed_medium(data)); + printf("%s: Unit1 = %s\n", __PRETTY_FUNCTION__, mbus_data_fixed_unit(data->cnt1_type)); + if ((data->status & MBUS_DATA_FIXED_STATUS_FORMAT_MASK) == MBUS_DATA_FIXED_STATUS_FORMAT_BCD) + { + printf("%s: Counter1 = %d\n", __PRETTY_FUNCTION__, (int)mbus_data_bcd_decode(data->cnt1_val, 4)); + } + else + { + printf("%s: Counter1 = %d\n", __PRETTY_FUNCTION__, mbus_data_int_decode(data->cnt1_val, 4)); + } + + printf("%s: Medium2 = %s\n", __PRETTY_FUNCTION__, mbus_data_fixed_medium(data)); + printf("%s: Unit2 = %s\n", __PRETTY_FUNCTION__, mbus_data_fixed_unit(data->cnt2_type)); + if ((data->status & MBUS_DATA_FIXED_STATUS_FORMAT_MASK) == MBUS_DATA_FIXED_STATUS_FORMAT_BCD) + { + printf("%s: Counter2 = %d\n", __PRETTY_FUNCTION__, (int)mbus_data_bcd_decode(data->cnt2_val, 4)); + } + else + { + printf("%s: Counter2 = %d\n", __PRETTY_FUNCTION__, mbus_data_int_decode(data->cnt2_val, 4)); + } + } + + return -1; +} + +void +mbus_hex_dump(const char *label, const char *buff, size_t len) +{ + time_t rawtime; + struct tm * timeinfo; + char timestamp[21]; + size_t i; + + if (label == NULL || buff == NULL) + return; + + time ( &rawtime ); + timeinfo = gmtime ( &rawtime ); + + strftime(timestamp,20,"%Y-%m-%d %H:%M:%S",timeinfo); + fprintf(stderr, "[%s] %s (%03d):", timestamp, label, len); + + for (i = 0; i < len; i++) + { + fprintf(stderr, " %02X", (u_char) buff[i]); + } + + fprintf(stderr, "\n"); +} + +int +mbus_data_error_print(int error) +{ + printf("%s: Error = %d\n", __PRETTY_FUNCTION__, error); + + return -1; +} + +//------------------------------------------------------------------------------ +// +// XML RELATED FUNCTIONS +// +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +/// +/// Encode string to XML +/// +//------------------------------------------------------------------------------ +void +mbus_str_xml_encode(u_char *dst, const u_char *src, size_t max_len) +{ + size_t i, len; + + i = 0; + len = 0; + + if (dst == NULL) + return; + + if (src != NULL) + { + while((len+6) < max_len) + { + if (src[i] == '\0') + { + break; + } + + switch (src[i]) + { + case '&': + len += snprintf(&dst[len], max_len - len, "&"); + break; + case '<': + len += snprintf(&dst[len], max_len - len, "<"); + break; + case '>': + len += snprintf(&dst[len], max_len - len, ">"); + break; + case '"': + len += snprintf(&dst[len], max_len - len, """); + break; + default: + dst[len++] = src[i]; + break; + } + + i++; + } + } + + dst[len] = '\0'; +} + +//------------------------------------------------------------------------------ +/// Generate XML for the variable-length data header +//------------------------------------------------------------------------------ +char * +mbus_data_variable_header_xml(mbus_data_variable_header *header) +{ + static char buff[8192]; + char str_encoded[768]; + size_t len = 0; + int val; + + if (header) + { + len += snprintf(&buff[len], sizeof(buff) - len, " <SlaveInformation>\n"); + + val = (int)mbus_data_bcd_decode(header->id_bcd, 4); + + len += snprintf(&buff[len], sizeof(buff) - len, " <Id>%d</Id>\n", val); + len += snprintf(&buff[len], sizeof(buff) - len, " <Manufacturer>%s</Manufacturer>\n", + mbus_decode_manufacturer(header->manufacturer[0], header->manufacturer[1])); + len += snprintf(&buff[len], sizeof(buff) - len, " <Version>%d</Version>\n", header->version); + + mbus_str_xml_encode(str_encoded, mbus_data_product_name(header), sizeof(str_encoded)); + + len += snprintf(&buff[len], sizeof(buff) - len, " <ProductName>%s</ProductName>\n", str_encoded); + + mbus_str_xml_encode(str_encoded, mbus_data_variable_medium_lookup(header->medium), sizeof(str_encoded)); + + len += snprintf(&buff[len], sizeof(buff) - len, " <Medium>%s</Medium>\n", str_encoded); + len += snprintf(&buff[len], sizeof(buff) - len, " <AccessNumber>%d</AccessNumber>\n", header->access_no); + len += snprintf(&buff[len], sizeof(buff) - len, " <Status>%.2X</Status>\n", header->status); + len += snprintf(&buff[len], sizeof(buff) - len, " <Signature>%.2X%.2X</Signature>\n", header->signature[1], header->signature[0]); + + len += snprintf(&buff[len], sizeof(buff) - len, " </SlaveInformation>\n\n"); + + return buff; + } + + return ""; +} + +//------------------------------------------------------------------------------ +/// Generate XML for a single variable-length data record +//------------------------------------------------------------------------------ +char * +mbus_data_variable_record_xml(mbus_data_record *record, int record_cnt, int frame_cnt, mbus_data_variable_header *header) +{ + static char buff[8192]; + char str_encoded[768]; + size_t len = 0; + struct tm * timeinfo; + char timestamp[21]; + int val; + + if (record) + { + if (frame_cnt >= 0) + { + len += snprintf(&buff[len], sizeof(buff) - len, + " <DataRecord id=\"%d\" frame=\"%d\">\n", + record_cnt, frame_cnt); + } + else + { + len += snprintf(&buff[len], sizeof(buff) - len, + " <DataRecord id=\"%d\">\n", record_cnt); + } + + if (record->drh.dib.dif == 0x0F) // MBUS_DIB_DIF_VENDOR_SPECIFIC + { + len += snprintf(&buff[len], sizeof(buff) - len, + " <Function>Manufacturer specific</Function>\n"); + } + else if (record->drh.dib.dif == 0x1F) + { + len += snprintf(&buff[len], sizeof(buff) - len, + " <Function>More records follow</Function>\n"); + } + else + { + mbus_str_xml_encode(str_encoded, mbus_data_record_function(record), sizeof(str_encoded)); + len += snprintf(&buff[len], sizeof(buff) - len, + " <Function>%s</Function>\n", str_encoded); + + mbus_str_xml_encode(str_encoded, mbus_data_record_unit(record), sizeof(str_encoded)); + len += snprintf(&buff[len], sizeof(buff) - len, + " <Unit>%s</Unit>\n", str_encoded); + } + + mbus_str_xml_encode(str_encoded, mbus_data_record_value(record), sizeof(str_encoded)); + len += snprintf(&buff[len], sizeof(buff) - len, " <Value>%s</Value>\n", str_encoded); + + timeinfo = gmtime ( &(record->timestamp) ); + strftime(timestamp,20,"%Y-%m-%dT%H:%M:%S",timeinfo); + len += snprintf(&buff[len], sizeof(buff) - len, " <Timestamp>%s</Timestamp>\n", timestamp); + + len += snprintf(&buff[len], sizeof(buff) - len, " </DataRecord>\n\n"); + + return buff; + } + + return ""; +} + +//------------------------------------------------------------------------------ +/// Generate XML for variable-length data +//------------------------------------------------------------------------------ +char * +mbus_data_variable_xml(mbus_data_variable *data) +{ + mbus_data_record *record; + char *buff = NULL; + size_t len = 0, buff_size = 8192; + int i; + + if (data) + { + buff = (char*) malloc(buff_size); + + if (buff == NULL) + return NULL; + + len += snprintf(&buff[len], buff_size - len, "<MBusData>\n\n"); + + len += snprintf(&buff[len], buff_size - len, "%s", + mbus_data_variable_header_xml(&(data->header))); + + for (record = data->record, i = 0; record; record = record->next, i++) + { + if ((buff_size - len) < 1024) + { + buff_size *= 2; + buff = (char*) realloc(buff,buff_size); + + if (buff == NULL) + return NULL; + } + + len += snprintf(&buff[len], buff_size - len, "%s", + mbus_data_variable_record_xml(record, i, -1, &(data->header))); + } + len += snprintf(&buff[len], buff_size - len, "</MBusData>\n"); + + return buff; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +/// Generate XML representation of fixed-length frame. +//------------------------------------------------------------------------------ +char * +mbus_data_fixed_xml(mbus_data_fixed *data) +{ + char *buff = NULL; + char str_encoded[256]; + size_t len = 0, buff_size = 8192; + + if (data) + { + buff = (char*) malloc(buff_size); + + if (buff == NULL) + return NULL; + + len += snprintf(&buff[len], buff_size - len, "<MBusData>\n\n"); + + len += snprintf(&buff[len], buff_size - len, " <SlaveInformation>\n"); + len += snprintf(&buff[len], buff_size - len, " <Id>%d</Id>\n", (int)mbus_data_bcd_decode(data->id_bcd, 4)); + + mbus_str_xml_encode(str_encoded, mbus_data_fixed_medium(data), sizeof(str_encoded)); + len += snprintf(&buff[len], buff_size - len, " <Medium>%s</Medium>\n", str_encoded); + + len += snprintf(&buff[len], buff_size - len, " <AccessNumber>%d</AccessNumber>\n", data->tx_cnt); + len += snprintf(&buff[len], buff_size - len, " <Status>%.2X</Status>\n", data->status); + len += snprintf(&buff[len], buff_size - len, " </SlaveInformation>\n\n"); + + len += snprintf(&buff[len], buff_size - len, " <DataRecord id=\"0\">\n"); + + mbus_str_xml_encode(str_encoded, mbus_data_fixed_function(data->status), sizeof(str_encoded)); + len += snprintf(&buff[len], buff_size - len, " <Function>%s</Function>\n", str_encoded); + + mbus_str_xml_encode(str_encoded, mbus_data_fixed_unit(data->cnt1_type), sizeof(str_encoded)); + len += snprintf(&buff[len], buff_size - len, " <Unit>%s</Unit>\n", str_encoded); + if ((data->status & MBUS_DATA_FIXED_STATUS_FORMAT_MASK) == MBUS_DATA_FIXED_STATUS_FORMAT_BCD) + { + len += snprintf(&buff[len], buff_size - len, " <Value>%d</Value>\n", (int)mbus_data_bcd_decode(data->cnt1_val, 4)); + } + else + { + len += snprintf(&buff[len], buff_size - len, " <Value>%d</Value>\n", mbus_data_int_decode(data->cnt1_val, 4)); + } + len += snprintf(&buff[len], buff_size - len, " </DataRecord>\n\n"); + + len += snprintf(&buff[len], buff_size - len, " <DataRecord id=\"1\">\n"); + + mbus_str_xml_encode(str_encoded, mbus_data_fixed_function(data->status), sizeof(str_encoded)); + len += snprintf(&buff[len], buff_size - len, " <Function>%s</Function>\n", str_encoded); + + mbus_str_xml_encode(str_encoded, mbus_data_fixed_unit(data->cnt2_type), sizeof(str_encoded)); + len += snprintf(&buff[len], buff_size - len, " <Unit>%s</Unit>\n", str_encoded); + if ((data->status & MBUS_DATA_FIXED_STATUS_FORMAT_MASK) == MBUS_DATA_FIXED_STATUS_FORMAT_BCD) + { + len += snprintf(&buff[len], buff_size - len, " <Value>%d</Value>\n", (int)mbus_data_bcd_decode(data->cnt2_val, 4)); + } + else + { + len += snprintf(&buff[len], buff_size - len, " <Value>%d</Value>\n", mbus_data_int_decode(data->cnt2_val, 4)); + } + len += snprintf(&buff[len], buff_size - len, " </DataRecord>\n\n"); + + len += snprintf(&buff[len], buff_size - len, "</MBusData>\n"); + + return buff; + } + + return NULL; +} + +//------------------------------------------------------------------------------ +/// Generate XML representation of a general application error. +//------------------------------------------------------------------------------ +char * +mbus_data_error_xml(int error) +{ + char *buff = NULL; + char str_encoded[256]; + size_t len = 0, buff_size = 8192; + + buff = (char*) malloc(buff_size); + + if (buff == NULL) + return NULL; + + len += snprintf(&buff[len], buff_size - len, "<MBusData>\n\n"); + + len += snprintf(&buff[len], buff_size - len, " <SlaveInformation>\n"); + + mbus_str_xml_encode(str_encoded, mbus_data_error_lookup(error), sizeof(str_encoded)); + len += snprintf(&buff[len], buff_size - len, " <Error>%s</Error>\n", str_encoded); + + len += snprintf(&buff[len], buff_size - len, " </SlaveInformation>\n\n"); + + len += snprintf(&buff[len], buff_size - len, "</MBusData>\n"); + + return buff; +} + +//------------------------------------------------------------------------------ +/// Return a string containing an XML representation of the M-BUS frame data. +//------------------------------------------------------------------------------ +char * +mbus_frame_data_xml(mbus_frame_data *data) +{ + if (data) + { + if (data->type == MBUS_DATA_TYPE_ERROR) + { + return mbus_data_error_xml(data->error); + } + + if (data->type == MBUS_DATA_TYPE_FIXED) + { + return mbus_data_fixed_xml(&(data->data_fix)); + } + + if (data->type == MBUS_DATA_TYPE_VARIABLE) + { + return mbus_data_variable_xml(&(data->data_var)); + } + } + + return NULL; +} + + +//------------------------------------------------------------------------------ +/// Return an XML representation of the M-BUS frame. +//------------------------------------------------------------------------------ +char * +mbus_frame_xml(mbus_frame *frame) +{ + mbus_frame_data frame_data; + mbus_frame *iter; + + mbus_data_record *record; + char *buff = NULL; + + size_t len = 0, buff_size = 8192; + int record_cnt = 0, frame_cnt; + + if (frame) + { + if (mbus_frame_data_parse(frame, &frame_data) == -1) + { + mbus_error_str_set("M-bus data parse error."); + return NULL; + } + + if (frame_data.type == MBUS_DATA_TYPE_ERROR) + { + // + // generate XML for error + // + return mbus_data_error_xml(frame_data.error); + } + + if (frame_data.type == MBUS_DATA_TYPE_FIXED) + { + // + // generate XML for fixed data + // + return mbus_data_fixed_xml(&(frame_data.data_fix)); + } + + if (frame_data.type == MBUS_DATA_TYPE_VARIABLE) + { + // + // generate XML for a sequence of variable data frames + // + + buff = (char*) malloc(buff_size); + + if (buff == NULL) + return NULL; + + // include frame counter in XML output if more than one frame + // is available (frame_cnt = -1 => not included in output) + frame_cnt = (frame->next == NULL) ? -1 : 0; + + len += snprintf(&buff[len], buff_size - len, "<MBusData>\n\n"); + + // only print the header info for the first frame (should be + // the same for each frame in a sequence of a multi-telegram + // transfer. + len += snprintf(&buff[len], buff_size - len, "%s", + mbus_data_variable_header_xml(&(frame_data.data_var.header))); + + // loop through all records in the current frame, using a global + // record count as record ID in the XML output + for (record = frame_data.data_var.record; record; record = record->next, record_cnt++) + { + if ((buff_size - len) < 1024) + { + buff_size *= 2; + buff = (char*) realloc(buff,buff_size); + + if (buff == NULL) + return NULL; + } + + len += snprintf(&buff[len], buff_size - len, "%s", + mbus_data_variable_record_xml(record, record_cnt, frame_cnt, &(frame_data.data_var.header))); + } + + // free all records in the list + if (frame_data.data_var.record) + { + mbus_data_record_free(frame_data.data_var.record); + } + + frame_cnt++; + + for (iter = frame->next; iter; iter = iter->next, frame_cnt++) + { + if (mbus_frame_data_parse(iter, &frame_data) == -1) + { + mbus_error_str_set("M-bus variable data parse error."); + return NULL; + } + + // loop through all records in the current frame, using a global + // record count as record ID in the XML output + for (record = frame_data.data_var.record; record; record = record->next, record_cnt++) + { + if ((buff_size - len) < 1024) + { + buff_size *= 2; + buff = (char*) realloc(buff,buff_size); + + if (buff == NULL) + return NULL; + } + + len += snprintf(&buff[len], buff_size - len, "%s", + mbus_data_variable_record_xml(record, record_cnt, frame_cnt, &(frame_data.data_var.header))); + } + + // free all records in the list + if (frame_data.data_var.record) + { + mbus_data_record_free(frame_data.data_var.record); + } + } + + len += snprintf(&buff[len], buff_size - len, "</MBusData>\n"); + + return buff; + } + } + + return NULL; +} + + +//------------------------------------------------------------------------------ +/// Allocate and initialize a new frame data structure +//------------------------------------------------------------------------------ +mbus_frame_data * +mbus_frame_data_new() +{ + mbus_frame_data *data; + + if ((data = (mbus_frame_data *)malloc(sizeof(mbus_frame_data))) == NULL) + { + return NULL; + } + data->data_var.record = NULL; + + return data; +} + + +//----------------------------------------------------------------------------- +/// Free up data associated with a frame data structure +//------------------------------------------------------------------------------ +void +mbus_frame_data_free(mbus_frame_data *data) +{ + if (data) + { + if (data->data_var.record) + { + mbus_data_record_free(data->data_var.record); // free's up the whole list + } + + free(data); + } +} + + + +//------------------------------------------------------------------------------ +/// Allocate and initialize a new variable data record +//------------------------------------------------------------------------------ +mbus_data_record * +mbus_data_record_new() +{ + mbus_data_record *record; + + if ((record = (mbus_data_record *)malloc(sizeof(mbus_data_record))) == NULL) + { + return NULL; + } + + record->next = NULL; + return record; +} + +//------------------------------------------------------------------------------ +/// free up memory associated with a data record and all the subsequent records +/// in its list (apply recursively) +//------------------------------------------------------------------------------ +void +mbus_data_record_free(mbus_data_record *record) +{ + if (record) + { + mbus_data_record *next = record->next; + + free(record); + + if (next) + mbus_data_record_free(next); + } +} + +//------------------------------------------------------------------------------ +/// Return a string containing an XML representation of the M-BUS frame. +//------------------------------------------------------------------------------ +void +mbus_data_record_append(mbus_data_variable *data, mbus_data_record *record) +{ + mbus_data_record *iter; + + if (data && record) + { + if (data->record == NULL) + { + data->record = record; + } + else + { + // find the end of the list + for (iter = data->record; iter->next; iter = iter->next); + + iter->next = record; + } + } +} + +//------------------------------------------------------------------------------ +// Extract the secondary address from an M-Bus frame. The secondary address +// should be a 16 character string comprised of the device ID (4 bytes), +// manufacturer ID (2 bytes), version (1 byte) and medium (1 byte). +//------------------------------------------------------------------------------ +char * +mbus_frame_get_secondary_address(mbus_frame *frame) +{ + static char addr[32]; + mbus_frame_data *data; + long id; + + if (frame == NULL || (data = mbus_frame_data_new()) == NULL) + { + printf("%s: Failed to allocate data structure [%p, %p].\n", __PRETTY_FUNCTION__, (void*)frame, (void*)data); + return NULL; + } + + if (frame->control_information != MBUS_CONTROL_INFO_RESP_VARIABLE) + { + snprintf(error_str, sizeof(error_str), "Non-variable data response (can't get secondary address from response)."); + return NULL; + } + + if (mbus_frame_data_parse(frame, data) == -1) + { + return NULL; + } + + id = (long) mbus_data_bcd_decode(data->data_var.header.id_bcd, 4); + + snprintf(addr, sizeof(addr), "%08lu%02X%02X%02X%02X", + id, + data->data_var.header.manufacturer[0], + data->data_var.header.manufacturer[1], + data->data_var.header.version, + data->data_var.header.medium); + + // free data + mbus_frame_data_free(data); + + return addr; +} + +//------------------------------------------------------------------------------ +// Pack a secondary address string into an mbus frame +//------------------------------------------------------------------------------ +int +mbus_frame_select_secondary_pack(mbus_frame *frame, char *address) +{ + int val, i, j, k; + char tmp[16]; + + if (frame == NULL || address == NULL || strlen(address) != 16) + { + snprintf(error_str, sizeof(error_str), "%s: frame or address arguments are NULL or invalid.", __PRETTY_FUNCTION__); + return -1; + } + + frame->control = MBUS_CONTROL_MASK_SND_UD | MBUS_CONTROL_MASK_DIR_M2S | MBUS_CONTROL_MASK_FCB; + frame->address = 253; // for addressing secondary slaves + frame->control_information = 0x52; // mode 1 + + frame->data_size = 8; + + // parse secondary_addr_str and populate frame->data[0-7] + // ex: secondary_addr_str = "14491001 1057 01 06" + // (excluding the blank spaces) + + strncpy(tmp, &address[14], 2); tmp[2] = 0; + val = strtol(tmp, NULL, 16); + frame->data[7] = val & 0xFF; + + strncpy(tmp, &address[12], 2); tmp[2] = 0; + val = strtol(tmp, NULL, 16); + frame->data[6] = val & 0xFF; + + strncpy(tmp, &address[8], 4); tmp[4] = 0; + val = strtol(tmp, NULL, 16); + frame->data[4] = (val>>8) & 0xFF; + frame->data[5] = val & 0xFF; + + // parse the ID string, allowing for F wildcard characters. + frame->data[0] = 0; + frame->data[1] = 0; + frame->data[2] = 0; + frame->data[3] = 0; + j = 3; k = 1; + for (i = 0; i < 8; i++) + { + if (address[i] == 'F' || address[i] == 'f') + { + frame->data[j] |= 0x0F << (4 * k--); + } + else + { + frame->data[j] |= (0x0F & (address[i] - '0')) << (4 * k--); + } + + if (k < 0) + { + k = 1; j--; + } + } + + return 0; +} diff --git a/cube/libmbus/mbus-protocol.h b/cube/libmbus/mbus-protocol.h new file mode 100644 index 0000000..5fa5298 --- /dev/null +++ b/cube/libmbus/mbus-protocol.h @@ -0,0 +1,624 @@ +//------------------------------------------------------------------------------ +// Copyright (C) 2010-2011, Robert Johansson, Raditex AB +// All rights reserved. +// +// rSCADA +// http://www.rSCADA.se +// info@rscada.se +// +//------------------------------------------------------------------------------ + +/** + * @file mbus-protocol.h + * + * @brief Functions and data structures for M-Bus protocol parsing. + * + */ + +#ifndef _MBUS_PROTOCOL_H_ +#define _MBUS_PROTOCOL_H_ + +#include <stdlib.h> +#include <sys/types.h> +#include <time.h> + +// +// Packet formats: +// +// ACK: size = 1 byte +// +// byte1: ack = 0xE5 +// +// SHORT: size = 5 byte +// +// byte1: start = 0x10 +// byte2: control = ... +// byte3: address = ... +// byte4: chksum = ... +// byte5: stop = 0x16 +// +// CONTROL: size = 9 byte +// +// byte1: start1 = 0x68 +// byte2: length1 = ... +// byte3: length2 = ... +// byte4: start2 = 0x68 +// byte5: control = ... +// byte6: address = ... +// byte7: ctl.info= ... +// byte8: chksum = ... +// byte9: stop = 0x16 +// +// LONG: size = N >= 9 byte +// +// byte1: start1 = 0x68 +// byte2: length1 = ... +// byte3: length2 = ... +// byte4: start2 = 0x68 +// byte5: control = ... +// byte6: address = ... +// byte7: ctl.info= ... +// byte8: data = ... +// ... = ... +// byteN-1: chksum = ... +// byteN: stop = 0x16 +// +// +// + +typedef struct _mbus_frame { + + u_char start1; + u_char length1; + u_char length2; + u_char start2; + u_char control; + u_char address; + u_char control_information; + // variable data field + u_char checksum; + u_char stop; + + u_char data[252]; + size_t data_size; + + int type; + time_t timestamp; + + //mbus_frame_data frame_data; + + void *next; // pointer to next mbus_frame for multi-telegram replies + +} mbus_frame; + +typedef struct _mbus_slave_data { + + int state_fcb; + int state_acd; + +} mbus_slave_data; + +#define NITEMS(x) (sizeof(x)/sizeof(x[0])) + +// +// Supported handle types +// +#define MBUS_HANDLE_TYPE_TCP 0 +#define MBUS_HANDLE_TYPE_SERIAL 1 + +//------------------------------------------------------------------------------ +// MBUS FRAME DATA FORMATS +// + +// DATA RECORDS +#define MBUS_DIB_DIF_EXTENSION_BIT 0x80 +#define MBUS_DIB_VIF_EXTENSION_BIT 0x80 + +typedef struct _mbus_data_information_block { + + u_char dif; + u_char dife[10]; + size_t ndife; + +} mbus_data_information_block; + +typedef struct _mbus_value_information_block { + + u_char vif; + u_char vife[10]; + size_t nvife; + + u_char custom_vif[128]; + +} mbus_value_information_block; + +typedef struct _mbus_data_record_header { + + mbus_data_information_block dib; + mbus_value_information_block vib; + +} mbus_data_record_header; + +typedef struct _mbus_data_record { + + mbus_data_record_header drh; + + u_char data[234]; + size_t data_len; + + time_t timestamp; + + void *next; + +} mbus_data_record; + +// +// HEADER FOR VARIABLE LENGTH DATA FORMAT +// +typedef struct _mbus_data_variable_header { + + //Ident.Nr. Manufr. Version Medium Access No. Status Signature + //4 Byte 2 Byte 1 Byte 1 Byte 1 Byte 1 Byte 2 Byte + + // ex + // 88 63 80 09 82 4D 02 04 15 00 00 00 + + u_char id_bcd[4]; // 88 63 80 09 + u_char manufacturer[2]; // 82 4D + u_char version; // 02 + u_char medium; // 04 + u_char access_no; // 15 + u_char status; // 00 + u_char signature[2]; // 00 00 + +} mbus_data_variable_header; + +// +// VARIABLE LENGTH DATA FORMAT +// +typedef struct _mbus_data_variable { + + mbus_data_variable_header header; + + mbus_data_record *record; + size_t nrecords; + + u_char *data; + size_t data_len; + + u_char more_records_follow; + + // are these needed/used? + u_char mdh; + u_char *mfg_data; + size_t mfg_data_len; + +} mbus_data_variable; + +// +// FIXED LENGTH DATA FORMAT +// +typedef struct _mbus_data_fixed { + + // ex + // 35 01 00 00 counter 2 = 135 l (historic value) + // + // e.g. + // + // 78 56 34 12 identification number = 12345678 + // 0A transmission counter = 0Ah = 10d + // 00 status 00h: counters coded BCD, actual values, no errors + // E9 7E Type&Unit: medium water, unit1 = 1l, unit2 = 1l (same, but historic) + // 01 00 00 00 counter 1 = 1l (actual value) + // 35 01 00 00 counter 2 = 135 l (historic value) + + u_char id_bcd[4]; + u_char tx_cnt; + u_char status; + u_char cnt1_type; + u_char cnt2_type; + u_char cnt1_val[4]; + u_char cnt2_val[4]; + +} mbus_data_fixed; + +// +// ABSTRACT DATA FORMAT (error, fixed or variable length) +// +#define MBUS_DATA_TYPE_FIXED 1 +#define MBUS_DATA_TYPE_VARIABLE 2 +#define MBUS_DATA_TYPE_ERROR 3 + +typedef struct _mbus_frame_data { + + mbus_data_variable data_var; + mbus_data_fixed data_fix; + + int type; + int error; + +} mbus_frame_data; + +// +// HEADER FOR SECONDARY ADDRESSING +// +typedef struct _mbus_data_secondary_address { + + //Ident.Nr. Manufr. Version Medium + //4 Byte 2 Byte 1 Byte 1 Byte + + // ex + // 14 49 10 01 10 57 01 06 + + u_char id_bcd[4]; // 14 49 10 01 + u_char manufacturer[2]; // 10 57 + u_char version; // 01 + u_char medium; // 06 + +} mbus_data_secondary_address; + + +// +// for compatibility with non-gcc compilers: +// +//#ifndef __PRETTY_FUNCTION__ +//#define __PRETTY_FUNCTION__ "libmbus" +//#endif + +//------------------------------------------------------------------------------ +// FRAME types +// +#define MBUS_FRAME_TYPE_ANY 0x00 +#define MBUS_FRAME_TYPE_ACK 0x01 +#define MBUS_FRAME_TYPE_SHORT 0x02 +#define MBUS_FRAME_TYPE_CONTROL 0x03 +#define MBUS_FRAME_TYPE_LONG 0x04 + +#define MBUS_FRAME_ACK_BASE_SIZE 1 +#define MBUS_FRAME_SHORT_BASE_SIZE 5 +#define MBUS_FRAME_CONTROL_BASE_SIZE 9 +#define MBUS_FRAME_LONG_BASE_SIZE 9 + +#define MBUS_FRAME_BASE_SIZE_ACK 1 +#define MBUS_FRAME_BASE_SIZE_SHORT 5 +#define MBUS_FRAME_BASE_SIZE_CONTROL 9 +#define MBUS_FRAME_BASE_SIZE_LONG 9 + +#define MBUS_FRAME_FIXED_SIZE_ACK 1 +#define MBUS_FRAME_FIXED_SIZE_SHORT 5 +#define MBUS_FRAME_FIXED_SIZE_CONTROL 6 +#define MBUS_FRAME_FIXED_SIZE_LONG 6 + +// +// Frame start/stop bits +// +#define MBUS_FRAME_ACK_START 0xE5 +#define MBUS_FRAME_SHORT_START 0x10 +#define MBUS_FRAME_CONTROL_START 0x68 +#define MBUS_FRAME_LONG_START 0x68 +#define MBUS_FRAME_STOP 0x16 + +// +// +// +#define MBUS_MAX_PRIMARY_SLAVES 256 + +// +// Control field +// +#define MBUS_CONTROL_FIELD_DIRECTION 0x07 +#define MBUS_CONTROL_FIELD_FCB 0x06 +#define MBUS_CONTROL_FIELD_ACD 0x06 +#define MBUS_CONTROL_FIELD_FCV 0x05 +#define MBUS_CONTROL_FIELD_DFC 0x05 +#define MBUS_CONTROL_FIELD_F3 0x04 +#define MBUS_CONTROL_FIELD_F2 0x03 +#define MBUS_CONTROL_FIELD_F1 0x02 +#define MBUS_CONTROL_FIELD_F0 0x01 + +#define MBUS_CONTROL_MASK_SND_NKE 0x40 +#define MBUS_CONTROL_MASK_SND_UD 0x53 +#define MBUS_CONTROL_MASK_REQ_UD2 0x5B +#define MBUS_CONTROL_MASK_REQ_UD1 0x5A +#define MBUS_CONTROL_MASK_RSP_UD 0x08 + +#define MBUS_CONTROL_MASK_FCB 0x20 +#define MBUS_CONTROL_MASK_FCV 0x10 + +#define MBUS_CONTROL_MASK_ACD 0x20 +#define MBUS_CONTROL_MASK_DFC 0x10 + +#define MBUS_CONTROL_MASK_DIR 0x40 +#define MBUS_CONTROL_MASK_DIR_M2S 0x40 +#define MBUS_CONTROL_MASK_DIR_S2M 0x00 + +// +// Address field +// +#define MBUS_ADDRESS_BROADCAST_REPLY 0xFE +#define MBUS_ADDRESS_BROADCAST_NOREPLY 0xFF +#define MBUS_ADDRESS_NETWORK_LAYER 0xFD + +// +// Control Information field +// +//Mode 1 Mode 2 Application Definition in +// 51h 55h data send EN1434-3 +// 52h 56h selection of slaves Usergroup July ́93 +// 50h application reset Usergroup March ́94 +// 54h synronize action suggestion +// B8h set baudrate to 300 baud Usergroup July ́93 +// B9h set baudrate to 600 baud Usergroup July ́93 +// BAh set baudrate to 1200 baud Usergroup July ́93 +// BBh set baudrate to 2400 baud Usergroup July ́93 +// BCh set baudrate to 4800 baud Usergroup July ́93 +// BDh set baudrate to 9600 baud Usergroup July ́93 +// BEh set baudrate to 19200 baud suggestion +// BFh set baudrate to 38400 baud suggestion +// B1h request readout of complete RAM content Techem suggestion +// B2h send user data (not standardized RAM write) Techem suggestion +// B3h initialize test calibration mode Usergroup July ́93 +// B4h EEPROM read Techem suggestion +// B6h start software test Techem suggestion +// 90h to 97h codes used for hashing longer recommended + +#define MBUS_CONTROL_INFO_DATA_SEND 0x51 +#define MBUS_CONTROL_INFO_DATA_SEND_MSB 0x55 +#define MBUS_CONTROL_INFO_SELECT_SLAVE 0x52 +#define MBUS_CONTROL_INFO_SELECT_SLAVE_MSB 0x56 +#define MBUS_CONTROL_INFO_APPLICATION_RESET 0x50 +#define MBUS_CONTROL_INFO_SYNC_ACTION 0x54 +#define MBUS_CONTROL_INFO_SET_BAUDRATE_300 0xB8 +#define MBUS_CONTROL_INFO_SET_BAUDRATE_600 0xB9 +#define MBUS_CONTROL_INFO_SET_BAUDRATE_1200 0xBA +#define MBUS_CONTROL_INFO_SET_BAUDRATE_2400 0xBB +#define MBUS_CONTROL_INFO_SET_BAUDRATE_4800 0xBC +#define MBUS_CONTROL_INFO_SET_BAUDRATE_9600 0xBD +#define MBUS_CONTROL_INFO_SET_BAUDRATE_19200 0xBE +#define MBUS_CONTROL_INFO_SET_BAUDRATE_38400 0xBF +#define MBUS_CONTROL_INFO_REQUEST_RAM_READ 0xB1 +#define MBUS_CONTROL_INFO_SEND_USER_DATA 0xB2 +#define MBUS_CONTROL_INFO_INIT_TEST_CALIB 0xB3 +#define MBUS_CONTROL_INFO_EEPROM_READ 0xB4 +#define MBUS_CONTROL_INFO_SW_TEST_START 0xB6 + +//Mode 1 Mode 2 Application Definition in +// 70h report of general application errors Usergroup March 94 +// 71h report of alarm status Usergroup March 94 +// 72h 76h variable data respond EN1434-3 +// 73h 77h fixed data respond EN1434-3 +#define MBUS_CONTROL_INFO_ERROR_GENERAL 0x70 +#define MBUS_CONTROL_INFO_STATUS_ALARM 0x71 + +#define MBUS_CONTROL_INFO_RESP_FIXED 0x73 +#define MBUS_CONTROL_INFO_RESP_FIXED_MSB 0x77 + +#define MBUS_CONTROL_INFO_RESP_VARIABLE 0x72 +#define MBUS_CONTROL_INFO_RESP_VARIABLE_MSB 0x76 + +// +// DATA BITS +// +#define MBUS_DATA_FIXED_STATUS_FORMAT_MASK 0x80 +#define MBUS_DATA_FIXED_STATUS_FORMAT_BCD 0x00 +#define MBUS_DATA_FIXED_STATUS_FORMAT_INT 0x80 +#define MBUS_DATA_FIXED_STATUS_DATE_MASK 0x40 +#define MBUS_DATA_FIXED_STATUS_DATE_STORED 0x40 +#define MBUS_DATA_FIXED_STATUS_DATE_CURRENT 0x00 + + +// +// data record fields +// +#define MBUS_DATA_RECORD_DIF_MASK_INST 0x00 +#define MBUS_DATA_RECORD_DIF_MASK_MIN 0x10 + +#define MBUS_DATA_RECORD_DIF_MASK_TYPE_INT32 0x04 +#define MBUS_DATA_RECORD_DIF_MASK_DATA 0x0F +#define MBUS_DATA_RECORD_DIF_MASK_FUNCTION 0x30 +#define MBUS_DATA_RECORD_DIF_MASK_STORAGE_NO 0x40 +#define MBUS_DATA_RECORD_DIF_MASK_EXTENTION 0x80 +#define MBUS_DATA_RECORD_DIF_MASK_NON_DATA 0xF0 + +// +// GENERAL APPLICATION ERRORS +// +#define MBUS_ERROR_DATA_UNSPECIFIED 0x00 +#define MBUS_ERROR_DATA_UNIMPLEMENTED_CI 0x01 +#define MBUS_ERROR_DATA_BUFFER_TOO_LONG 0x02 +#define MBUS_ERROR_DATA_TOO_MANY_RECORDS 0x03 +#define MBUS_ERROR_DATA_PREMATURE_END 0x04 +#define MBUS_ERROR_DATA_TOO_MANY_DIFES 0x05 +#define MBUS_ERROR_DATA_TOO_MANY_VIFES 0x06 +#define MBUS_ERROR_DATA_RESERVED 0x07 +#define MBUS_ERROR_DATA_APPLICATION_BUSY 0x08 +#define MBUS_ERROR_DATA_TOO_MANY_READOUTS 0x09 + +// +// FIXED DATA FLAGS +// + +// +// VARIABLE DATA FLAGS +// +#define MBUS_VARIABLE_DATA_MEDIUM_OTHER 0x00 +#define MBUS_VARIABLE_DATA_MEDIUM_OIL 0x01 +#define MBUS_VARIABLE_DATA_MEDIUM_ELECTRICITY 0x02 +#define MBUS_VARIABLE_DATA_MEDIUM_GAS 0x03 +#define MBUS_VARIABLE_DATA_MEDIUM_HEAT_OUT 0x04 +#define MBUS_VARIABLE_DATA_MEDIUM_STEAM 0x05 +#define MBUS_VARIABLE_DATA_MEDIUM_HOT_WATER 0x06 +#define MBUS_VARIABLE_DATA_MEDIUM_WATER 0x07 +#define MBUS_VARIABLE_DATA_MEDIUM_HEAT_COST 0x08 +#define MBUS_VARIABLE_DATA_MEDIUM_COMPR_AIR 0x09 +#define MBUS_VARIABLE_DATA_MEDIUM_COOL_OUT 0x0A +#define MBUS_VARIABLE_DATA_MEDIUM_COOL_IN 0x0B +#define MBUS_VARIABLE_DATA_MEDIUM_HEAT_IN 0x0C +#define MBUS_VARIABLE_DATA_MEDIUM_HEAT_COOL 0x0D +#define MBUS_VARIABLE_DATA_MEDIUM_BUS 0x0E +#define MBUS_VARIABLE_DATA_MEDIUM_UNKNOWN 0x0F +#define MBUS_VARIABLE_DATA_MEDIUM_COLD_WATER 0x16 +#define MBUS_VARIABLE_DATA_MEDIUM_DUAL_WATER 0x17 +#define MBUS_VARIABLE_DATA_MEDIUM_PRESSURE 0x18 +#define MBUS_VARIABLE_DATA_MEDIUM_ADC 0x19 + +#define MBUS_VARIABLE_DATA_MAN_ACW 0x0477 +#define MBUS_VARIABLE_DATA_MAN_ABB 0x0442 +#define MBUS_VARIABLE_DATA_MAN_AMT 0x05B4 +#define MBUS_VARIABLE_DATA_MAN_EFE 0x14C5 +#define MBUS_VARIABLE_DATA_MAN_ELS 0x1593 +#define MBUS_VARIABLE_DATA_MAN_EMH 0x15A8 +#define MBUS_VARIABLE_DATA_MAN_HYD 0x2324 +#define MBUS_VARIABLE_DATA_MAN_KAM 0x2C2D +#define MBUS_VARIABLE_DATA_MAN_LSE 0x3265 +#define MBUS_VARIABLE_DATA_MAN_LUG 0x32A7 +#define MBUS_VARIABLE_DATA_MAN_NZR 0x3B52 +#define MBUS_VARIABLE_DATA_MAN_PAD 0x4024 +#define MBUS_VARIABLE_DATA_MAN_QDS 0x4493 +#define MBUS_VARIABLE_DATA_MAN_SLB 0x4D82 +#define MBUS_VARIABLE_DATA_MAN_SON 0x4DEE +#define MBUS_VARIABLE_DATA_MAN_SPX 0x4E18 +#define MBUS_VARIABLE_DATA_MAN_SVM 0x4ECD +#define MBUS_VARIABLE_DATA_MAN_TCH 0x5068 +#define MBUS_VARIABLE_DATA_MAN_ZRM 0x6A4D + +// +// Event callback functions +// +extern void (*_mbus_recv_event)(u_char src_type, const char *buff, size_t len); +extern void (*_mbus_send_event)(u_char src_type, const char *buff, size_t len); + +void mbus_dump_recv_event(u_char src_type, const char *buff, size_t len); +void mbus_dump_send_event(u_char src_type, const char *buff, size_t len); + +// +// Event register functions +// +void mbus_register_recv_event(void (*event)(u_char src_type, const char *buff, size_t len)); +void mbus_register_send_event(void (*event)(u_char src_type, const char *buff, size_t len)); + +// +// variable length records +// +mbus_data_record *mbus_data_record_new(); +void mbus_data_record_free(mbus_data_record *record); +void mbus_data_record_append(mbus_data_variable *data, mbus_data_record *record); + + +// XXX: Add application reset subcodes + +mbus_frame *mbus_frame_new(int frame_type); +int mbus_frame_free(mbus_frame *frame); + +mbus_frame_data *mbus_frame_data_new(); +void mbus_frame_data_free(mbus_frame_data *data); + +// +// +// +int mbus_frame_calc_checksum(mbus_frame *frame); +int mbus_frame_calc_length (mbus_frame *frame); + +// +// Parse/Pack to bin +// +int mbus_parse(mbus_frame *frame, u_char *data, size_t data_size); + +int mbus_data_fixed_parse (mbus_frame *frame, mbus_data_fixed *data); +int mbus_data_variable_parse(mbus_frame *frame, mbus_data_variable *data); + +int mbus_frame_data_parse (mbus_frame *frame, mbus_frame_data *data); + +int mbus_frame_pack(mbus_frame *frame, u_char *data, size_t data_size); + +int mbus_frame_verify(mbus_frame *frame); + +int mbus_frame_internal_pack(mbus_frame *frame, mbus_frame_data *frame_data); + +// +// data parsing +// +const char *mbus_data_record_function(mbus_data_record *record); +const char *mbus_data_fixed_function(int status); + +// +// M-Bus frame data struct access/write functions +// +int mbus_frame_type(mbus_frame *frame); + +// +// Slave status data register. +// +mbus_slave_data *mbus_slave_data_get(size_t i); + +// +// XML generating functions +// +void mbus_str_xml_encode(u_char *dst, const u_char *src, size_t max_len); +char *mbus_data_xml(mbus_frame_data *data); +char *mbus_data_variable_xml(mbus_data_variable *data); +char *mbus_data_fixed_xml(mbus_data_fixed *data); +char *mbus_data_error_xml(int error); +char *mbus_frame_data_xml(mbus_frame_data *data); + +char *mbus_data_variable_header_xml(mbus_data_variable_header *header); + +char *mbus_frame_xml(mbus_frame *frame); + +// +// Debug/dump +// +int mbus_frame_print(mbus_frame *frame); +int mbus_frame_data_print(mbus_frame_data *data); +int mbus_data_fixed_print(mbus_data_fixed *data); +int mbus_data_error_print(int error); +int mbus_data_variable_header_print(mbus_data_variable_header *header); +int mbus_data_variable_print(mbus_data_variable *data); + +char *mbus_error_str(); +void mbus_error_str_set(char *message); +void mbus_error_reset(); + +void mbus_parse_set_debug(int debug); +void mbus_hex_dump(const char *label, const char *buff, size_t len); + +// +// data encode/decode functions +// +int mbus_data_manufacturer_encode(u_char *m_data, u_char *m_code); +const char *mbus_decode_manufacturer(u_char byte1, u_char byte2); +const char *mbus_data_product_name(mbus_data_variable_header *header); + +int mbus_data_bcd_encode(u_char *bcd_data, size_t bcd_data_size, int value); +int mbus_data_int_encode(u_char *int_data, size_t int_data_size, int value); + +long long mbus_data_bcd_decode(u_char *bcd_data, size_t bcd_data_size); +int mbus_data_int_decode(u_char *int_data, size_t int_data_size); +long mbus_data_long_decode(u_char *int_data, size_t int_data_size); +long long mbus_data_long_long_decode(u_char *int_data, size_t int_data_size); + +float mbus_data_float_decode(u_char *float_data); + +void mbus_data_tm_decode(struct tm *t, u_char *t_data, size_t t_data_size); + +void mbus_data_str_decode(u_char *dst, const u_char *src, size_t len); + +void mbus_data_bin_decode(u_char *dst, const u_char *src, size_t len, size_t max_len); + +const char *mbus_data_fixed_medium(mbus_data_fixed *data); +const char *mbus_data_fixed_unit(int medium_unit_byte); +const char *mbus_data_variable_medium_lookup(u_char medium); +const char *mbus_unit_prefix(int exp); + +const char *mbus_data_error_lookup(int error); + +const char *mbus_vib_unit_lookup(mbus_value_information_block *vib); +const char *mbus_vif_unit_lookup(u_char vif); + +u_char mbus_dif_datalength_lookup(u_char dif); + +char *mbus_frame_get_secondary_address(mbus_frame *frame); +int mbus_frame_select_secondary_pack(mbus_frame *frame, char *address); + +#endif /* _MBUS_PROTOCOL_H_ */ +