From db456abb29824499a312fcec8799dac1a030a24a Mon Sep 17 00:00:00 2001 From: john30 Date: Sat, 25 Jun 2016 06:35:07 +0200 Subject: [PATCH] reworked data types --- src/lib/ebus/Makefile.am | 2 + src/lib/ebus/data.cpp | 1191 ++++++++----------------------------- src/lib/ebus/data.h | 378 ++---------- src/lib/ebus/datatype.cpp | 862 +++++++++++++++++++++++++++ src/lib/ebus/datatype.h | 490 +++++++++++++++ 5 files changed, 1649 insertions(+), 1274 deletions(-) create mode 100644 src/lib/ebus/datatype.cpp create mode 100644 src/lib/ebus/datatype.h diff --git a/src/lib/ebus/Makefile.am b/src/lib/ebus/Makefile.am index 0f4a9bf4..c0eb2b11 100644 --- a/src/lib/ebus/Makefile.am +++ b/src/lib/ebus/Makefile.am @@ -8,6 +8,8 @@ libebus_a_SOURCES = result.cpp \ symbol.cpp \ symbol.h \ filereader.h \ + datatype.cpp \ + datatype.h \ data.cpp \ data.h \ device.cpp \ diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index cf8ce74d..59c266c4 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -27,175 +27,115 @@ #include #include #include +#include using namespace std; -static const dataType_t stringDataType = { - "STR",MAX_LEN*8,bt_str, ADJ, ' ', 1, 0, 0 // >= 1 byte character string filled up with space -}; - -static const dataType_t pinDataType = { - "PIN", 16, bt_num, FIX|BCD|REV, 0xffff, 0, 0x9999, 1 // unsigned decimal in BCD, 0000 - 9999 (fixed length) -}; - -static const dataType_t uchDataType = { - "UCH", 8, bt_num, LST, 0xff, 0, 0xfe, 1 // unsigned integer, 0 - 254 -}; - -/** the known data field types. */ -static const dataType_t dataTypes[] = { - {"IGN",MAX_LEN*8,bt_str, IGN|ADJ, 0, 1, 0, 0}, // >= 1 byte ignored data - stringDataType, - {"NTS",MAX_LEN*8,bt_str, ADJ, 0, 1, 0, 0}, // >= 1 byte character string filled up with 0x00 (null terminated string) - {"HEX",MAX_LEN*8,bt_hexstr, ADJ, 0, 2, 47, 0}, // >= 1 byte hex digit string, usually separated by space, e.g. 0a 1b 2c 3d - {"BDA", 32, bt_dat, BCD, 0xff, 10, 10, 0}, // date with weekday in BCD, 01.01.2000 - 31.12.2099 (0x01,0x01,WW,0x00 - 0x31,0x12,WW,0x99, WW is weekday Mon=0x00 - Sun=0x06, replacement 0xff) - {"BDA", 24, bt_dat, BCD, 0xff, 10, 10, 0}, // date in BCD, 01.01.2000 - 31.12.2099 (0x01,0x01,0x00 - 0x31,0x12,0x99, replacement 0xff) - {"HDA", 32, bt_dat, 0, 0xff, 10, 10, 0}, // date with weekday, 01.01.2000 - 31.12.2099 (0x01,0x01,WW,0x00 - 0x1f,0x0c,WW,0x63, WW is weekday Mon=0x01 - Sun=0x07, replacement 0xff) - {"HDA", 24, bt_dat, 0, 0xff, 10, 10, 0}, // date, 01.01.2000 - 31.12.2099 (0x01,0x01,0x00 - 0x1f,0x0c,0x63, replacement 0xff) - {"BTI", 24, bt_tim, BCD|REV|REQ, 0, 8, 8, 0}, // time in BCD, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x59,0x59,0x23) - {"HTI", 24, bt_tim, REQ, 0, 8, 8, 0}, // time, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x17,0x3b,0x3b) - {"VTI", 24, bt_tim, REV, 0x63, 8, 8, 0}, // time, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x3b,0x3b,0x17, replacement 0x63) [Vaillant type] - {"BTM", 16, bt_tim, BCD|REV, 0xff, 5, 5, 0}, // time as hh:mm in BCD, 00:00 - 23:59 (0x00,0x00 - 0x59,0x23, replacement 0xff) - {"HTM", 16, bt_tim, REQ, 0, 5, 5, 0}, // time as hh:mm, 00:00 - 23:59 (0x00,0x00 - 0x17,0x3b) - {"VTM", 16, bt_tim, REV, 0xff, 5, 5, 0}, // time as hh:mm, 00:00 - 23:59 (0x00,0x00 - 0x3b,0x17, replacement 0xff) [Vaillant type] - {"TTM", 8, bt_tim, 0, 0x90, 5, 5, 10}, // truncated time (only multiple of 10 minutes), 00:00 - 24:00 (minutes div 10 + hour * 6 as integer) - {"TTH", 8, bt_tim, 0, 0, 5, 5, 30}, // truncated time (only multiple of 30 minutes), 00:30 - 24:00 (minutes div 30 + hour * 2 as integer) - {"BDY", 8, bt_num, DAY|LST, 0x07, 0, 6, 1}, // weekday, "Mon" - "Sun" (0x00 - 0x06) [eBUS type] - {"HDY", 8, bt_num, DAY|LST, 0x00, 1, 7, 1}, // weekday, "Mon" - "Sun" (0x01 - 0x07) [Vaillant type] - {"BCD", 8, bt_num, BCD|LST, 0xff, 0, 99, 1}, // unsigned decimal in BCD, 0 - 99 - {"BCD", 16, bt_num, BCD|LST, 0xffff, 0, 9999, 1}, // unsigned decimal in BCD, 0 - 9999 - {"BCD", 24, bt_num, BCD|LST, 0xffffff, 0, 999999, 1}, // unsigned decimal in BCD, 0 - 999999 - {"BCD", 32, bt_num, BCD|LST, 0xffffffff, 0, 99999999, 1}, // unsigned decimal in BCD, 0 - 99999999 - {"HCD", 32, bt_num, HCD|BCD|REQ, 0, 0, 99999999, 1}, // unsigned decimal in HCD, 0 - 99999999 - {"HCD", 8, bt_num, HCD|BCD|REQ, 0, 0, 99, 1}, // unsigned decimal in HCD, 0 - 99 - {"HCD", 16, bt_num, HCD|BCD|REQ, 0, 0, 9999, 1}, // unsigned decimal in HCD, 0 - 9999 - {"HCD", 24, bt_num, HCD|BCD|REQ, 0, 0, 999999, 1}, // unsigned decimal in HCD, 0 - 999999 - pinDataType, - uchDataType, - {"SCH", 8, bt_num, SIG, 0x80, 0x81, 0x7f, 1}, // signed integer, -127 - +127 - {"D1B", 8, bt_num, SIG, 0x80, 0x81, 0x7f, 1}, // signed integer, -127 - +127 - {"D1C", 8, bt_num, 0, 0xff, 0x00, 0xc8, 2}, // unsigned number (fraction 1/2), 0 - 100 (0x00 - 0xc8, replacement 0xff) - {"D2B", 16, bt_num, SIG, 0x8000, 0x8001, 0x7fff, 256}, // signed number (fraction 1/256), -127.99 - +127.99 - {"D2C", 16, bt_num, SIG, 0x8000, 0x8001, 0x7fff, 16}, // signed number (fraction 1/16), -2047.9 - +2047.9 - {"FLT", 16, bt_num, SIG, 0x8000, 0x8001, 0x7fff, 1000}, // signed number (fraction 1/1000), -32.767 - +32.767, little endian - {"FLR", 16, bt_num, SIG|REV, 0x8000, 0x8001, 0x7fff, 1000}, // signed number (fraction 1/1000), -32.767 - +32.767, big endian - {"EXP", 32, bt_num, SIG|EXP, 0x7f800000, 0x00000000, 0xffffffff, 1}, // signed number (IEEE 754 binary32: 1 bit sign, 8 bits exponent, 23 bits significand), little endian - {"EXR", 32, bt_num, SIG|EXP|REV,0x7f800000,0x00000000,0xffffffff, 1}, // signed number (IEEE 754 binary32: 1 bit sign, 8 bits exponent, 23 bits significand), big endian - {"UIN", 16, bt_num, LST, 0xffff, 0, 0xfffe, 1}, // unsigned integer, 0 - 65534, little endian - {"UIR", 16, bt_num, LST|REV, 0xffff, 0, 0xfffe, 1}, // unsigned integer, 0 - 65534, big endian - {"SIN", 16, bt_num, SIG, 0x8000, 0x8001, 0x7fff, 1}, // signed integer, -32767 - +32767, little endian - {"SIR", 16, bt_num, SIG|REV, 0x8000, 0x8001, 0x7fff, 1}, // signed integer, -32767 - +32767, big endian - {"ULG", 32, bt_num, LST, 0xffffffff, 0, 0xfffffffe, 1}, // unsigned integer, 0 - 4294967294, little endian - {"ULR", 32, bt_num, LST|REV, 0xffffffff, 0, 0xfffffffe, 1}, // unsigned integer, 0 - 4294967294, big endian - {"SLG", 32, bt_num, SIG, 0x80000000, 0x80000001, 0xffffffff, 1}, // signed integer, -2147483647 - +2147483647, little endian - {"SLR", 32, bt_num, SIG|REV, 0x80000000, 0x80000001, 0xffffffff, 1}, // signed integer, -2147483647 - +2147483647, big endian - {"BI0", 7, bt_num, ADJ|LST|REQ, 0, 0, 0xef, 0}, // bit 0 (up to 7 bits until bit 6) - {"BI1", 7, bt_num, ADJ|LST|REQ, 0, 0, 0x7f, 1}, // bit 1 (up to 7 bits until bit 7) - {"BI2", 6, bt_num, ADJ|LST|REQ, 0, 0, 0x3f, 2}, // bit 2 (up to 6 bits until bit 7) - {"BI3", 5, bt_num, ADJ|LST|REQ, 0, 0, 0x1f, 3}, // bit 3 (up to 5 bits until bit 7) - {"BI4", 4, bt_num, ADJ|LST|REQ, 0, 0, 0x0f, 4}, // bit 4 (up to 4 bits until bit 7) - {"BI5", 3, bt_num, ADJ|LST|REQ, 0, 0, 0x07, 5}, // bit 5 (up to 3 bits until bit 7) - {"BI6", 2, bt_num, ADJ|LST|REQ, 0, 0, 0x03, 6}, // bit 6 (up to 2 bits until bit 7) - {"BI7", 1, bt_num, ADJ|LST|REQ, 0, 0, 0x01, 7}, // bit 7 -}; - -/** the maximum divisor value. */ -#define MAX_DIVISOR 1000000000 - -/** the maximum value for value lists. */ -#define MAX_VALUE (0xFFFFFFFFu) +/* additional flags for @a DataType. */ +static const unsigned int DAY = LAST_DATATYPE_FLAG<<1; //!< forced value list defaulting to week days /** the week day names. */ static const char* dayNames[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; -unsigned int parseInt(const char* str, int base, const unsigned int minValue, const unsigned int maxValue, result_t& result, unsigned int* length) { - char* strEnd = NULL; +/** + * The STR data type: >= 1 byte character string filled up with space. + */ +static StringDataType* stringDataType = new StringDataType("STR", MAX_LEN*8, ADJ, ' '); - unsigned long int ret = strtoul(str, &strEnd, base); +/** + * The PIN data type: unsigned decimal in BCD, 0000 - 9999 (fixed length). + */ +static NumberDataType* pinDataType = new NumberDataType( + "PIN", 16, FIX|BCD|REV, 0xffff, 0, 0x9999, 1 +); - if (strEnd == NULL || strEnd == str || *strEnd != 0) { - result = RESULT_ERR_INVALID_NUM; // invalid value - return 0; +/** + * The UCH data type: unsigned integer, 0 - 254. + */ +static NumberDataType* uchDataType = new NumberDataType( + "UCH", 8, 0, 0xff, 0, 0xfe, 1 +); + +/** the known base @a DataType instances by ID. + * Note: adjustable length types are stored with the ID only, all others are stored by "ID:BITS". */ +static map baseTypes; + +/** + * Register a base @a DataType. + * @baseType the @a DataType to register. + */ +void registerBaseType(DataType* baseType) { + if (!baseType->isAdjustableLength()) { + ostringstream str; + str << baseType->getId() << LENGTH_SEPARATOR << static_cast(baseType->getBitCount()>=8?baseType->getBitCount()/8:baseType->getBitCount()); + baseTypes[str.str()] = baseType; + if (baseTypes.find(baseType->getId()) != baseTypes.end()) { + return; // only store first one as default + } } - - if (minValue > ret || ret > maxValue) { - result = RESULT_ERR_OUT_OF_RANGE; // invalid value - return 0; - } - if (length != NULL) - *length = (unsigned int)(strEnd - str); - - result = RESULT_OK; - return (unsigned int)ret; + baseTypes[baseType->getId()] = baseType; } -int parseSignedInt(const char* str, int base, const int minValue, const int maxValue, result_t& result, unsigned int* length) { - char* strEnd = NULL; - - long int ret = strtol(str, &strEnd, base); - - if (strEnd == NULL || *strEnd != 0) { - result = RESULT_ERR_INVALID_NUM; // invalid value - return 0; - } - - if (minValue > ret || ret > maxValue) { - result = RESULT_ERR_OUT_OF_RANGE; // invalid value - return 0; - } - if (length != NULL) - *length = (unsigned int)(strEnd - str); - - result = RESULT_OK; - return (int)ret; -} - -void printErrorPos(ostream& out, vector::iterator begin, const vector::iterator end, vector::iterator pos, string filename, size_t lineNo, result_t result) -{ - if (pos > begin) - pos--; - out << "Error reading \"" << filename << "\" line " << setw(0) << dec << static_cast(lineNo) << " field " << static_cast(1+pos.base()-begin.base()) << " value \"" << *pos << "\": " << getResultCode(result) << endl; - out << "Erroneous item is here:" << endl; - bool first = true; - int cnt = 0; - while (begin != end) { - if (first) - first = false; - else { - out << FIELD_SEPARATOR; - if (begin <= pos) { - cnt++; - } - } - string item = *begin; - size_t i = item.find(TEXT_SEPARATOR); - if (i != string::npos) { - do { - item.replace(i, 1, TEXT_SEPARATOR_STR TEXT_SEPARATOR_STR); - i = item.find(TEXT_SEPARATOR, i+sizeof(TEXT_SEPARATOR_STR)+sizeof(TEXT_SEPARATOR_STR)); - } while (i != string::npos); - i = 0; - } else { - i = item.find(FIELD_SEPARATOR); - } - if (i!=string::npos) { - out << TEXT_SEPARATOR << item << TEXT_SEPARATOR; - if (begin < pos) - cnt += 2; - else if (begin == pos) - cnt++; - } else { - out << item; - } - if (begin < pos) - cnt += (unsigned int)(item).length(); - - begin++; - } - out << endl; - out << setw(cnt) << " " << setw(0) << "^" << endl; +/** Register the known base data types. */ +bool registerBaseTypes() { + registerBaseType(stringDataType); + registerBaseType(pinDataType); + registerBaseType(uchDataType); + registerBaseType(new StringDataType("IGN", MAX_LEN*8, IGN|ADJ, 0)); // >= 1 byte ignored data + registerBaseType(new StringDataType("NTS", MAX_LEN*8, ADJ, 0)); // >= 1 byte character string filled up with 0x00 (null terminated string) + registerBaseType(new StringDataType("HEX", MAX_LEN*8, ADJ, 0, true)); // >= 1 byte hex digit string, usually separated by space, e.g. 0a 1b 2c 3d + registerBaseType(new DateTimeDataType("BDA", 32, BCD, 0xff, true, 0)); // date with weekday in BCD, 01.01.2000 - 31.12.2099 (0x01,0x01,WW,0x00 - 0x31,0x12,WW,0x99, WW is weekday Mon=0x00 - Sun=0x06, replacement 0xff) + registerBaseType(new DateTimeDataType("BDA", 24, BCD, 0xff, true, 0)); // date in BCD, 01.01.2000 - 31.12.2099 (0x01,0x01,0x00 - 0x31,0x12,0x99, replacement 0xff) + registerBaseType(new DateTimeDataType("HDA", 32, 0, 0xff, true, 0)); // date with weekday, 01.01.2000 - 31.12.2099 (0x01,0x01,WW,0x00 - 0x1f,0x0c,WW,0x63, WW is weekday Mon=0x01 - Sun=0x07, replacement 0xff) + registerBaseType(new DateTimeDataType("HDA", 24, 0, 0xff, true, 0)); // date, 01.01.2000 - 31.12.2099 (0x01,0x01,0x00 - 0x1f,0x0c,0x63, replacement 0xff) + registerBaseType(new DateTimeDataType("DAY", 16, REQ, 0, true, 0)); // date, days since 01.01.1900, 01.01.1900 - 06.06.2079 (0x00,0x00 - 0xff,0xff) + registerBaseType(new DateTimeDataType("BTI", 24, BCD|REV|REQ, 0, false, 0)); // time in BCD, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x59,0x59,0x23) + registerBaseType(new DateTimeDataType("HTI", 24, REQ, 0, false, 0)); // time, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x17,0x3b,0x3b) + registerBaseType(new DateTimeDataType("VTI", 24, REV, 0x63, false, 0)); // time, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x3b,0x3b,0x17, replacement 0x63) [Vaillant type] + registerBaseType(new DateTimeDataType("BTM", 16, BCD|REV, 0xff, false, 0)); // time as hh:mm in BCD, 00:00 - 23:59 (0x00,0x00 - 0x59,0x23, replacement 0xff) + registerBaseType(new DateTimeDataType("HTM", 16, REQ, 0, false, 0)); // time as hh:mm, 00:00 - 23:59 (0x00,0x00 - 0x17,0x3b) + registerBaseType(new DateTimeDataType("VTM", 16, REV, 0xff, false, 0)); // time as hh:mm, 00:00 - 23:59 (0x00,0x00 - 0x3b,0x17, replacement 0xff) [Vaillant type] + registerBaseType(new DateTimeDataType("TTM", 8, 0, 0x90, false, 10)); // truncated time (only multiple of 10 minutes), 00:00 - 24:00 (minutes div 10 + hour * 6 as integer) + registerBaseType(new DateTimeDataType("TTH", 8, 0, 0, false, 30)); // truncated time (only multiple of 30 minutes), 00:30 - 24:00 (minutes div 30 + hour * 2 as integer) + registerBaseType(new NumberDataType("BDY", 8, DAY, 0x07, 0, 6, 1)); // weekday, "Mon" - "Sun" (0x00 - 0x06) [eBUS type] + registerBaseType(new NumberDataType("HDY", 8, DAY, 0x00, 1, 7, 1)); // weekday, "Mon" - "Sun" (0x01 - 0x07) [Vaillant type] + registerBaseType(new NumberDataType("BCD", 8, BCD, 0xff, 0, 99, 1)); // unsigned decimal in BCD, 0 - 99 + registerBaseType(new NumberDataType("BCD", 16, BCD, 0xffff, 0, 9999, 1)); // unsigned decimal in BCD, 0 - 9999 + registerBaseType(new NumberDataType("BCD", 24, BCD, 0xffffff, 0, 999999, 1)); // unsigned decimal in BCD, 0 - 999999 + registerBaseType(new NumberDataType("BCD", 32, BCD, 0xffffffff, 0, 99999999, 1)); // unsigned decimal in BCD, 0 - 99999999 + registerBaseType(new NumberDataType("HCD", 32, HCD|BCD|REQ, 0, 0, 99999999, 1)); // unsigned decimal in HCD, 0 - 99999999 + registerBaseType(new NumberDataType("HCD", 8, HCD|BCD|REQ, 0, 0, 99, 1)); // unsigned decimal in HCD, 0 - 99 + registerBaseType(new NumberDataType("HCD", 16, HCD|BCD|REQ, 0, 0, 9999, 1)); // unsigned decimal in HCD, 0 - 9999 + registerBaseType(new NumberDataType("HCD", 24, HCD|BCD|REQ, 0, 0, 999999, 1)); // unsigned decimal in HCD, 0 - 999999 + registerBaseType(new NumberDataType("SCH", 8, SIG, 0x80, 0x81, 0x7f, 1)); // signed integer, -127 - +127 + registerBaseType(new NumberDataType("D1B", 8, SIG, 0x80, 0x81, 0x7f, 1)); // signed integer, -127 - +127 + registerBaseType(new NumberDataType("D1C", 8, 0, 0xff, 0x00, 0xc8, 2)); // unsigned number (fraction 1/2), 0 - 100 (0x00 - 0xc8, replacement 0xff) + registerBaseType(new NumberDataType("D2B", 16, SIG, 0x8000, 0x8001, 0x7fff, 256)); // signed number (fraction 1/256), -127.99 - +127.99 + registerBaseType(new NumberDataType("D2C", 16, SIG, 0x8000, 0x8001, 0x7fff, 16)); // signed number (fraction 1/16), -2047.9 - +2047.9 + registerBaseType(new NumberDataType("FLT", 16, SIG, 0x8000, 0x8001, 0x7fff, 1000)); // signed number (fraction 1/1000), -32.767 - +32.767, little endian + registerBaseType(new NumberDataType("FLR", 16, SIG|REV, 0x8000, 0x8001, 0x7fff, 1000)); // signed number (fraction 1/1000), -32.767 - +32.767, big endian + registerBaseType(new NumberDataType("EXP", 32, SIG|EXP, 0x7f800000, 0x00000000, 0xffffffff, 1)); // signed number (IEEE 754 binary32: 1 bit sign, 8 bits exponent, 23 bits significand), little endian + registerBaseType(new NumberDataType("EXR", 32, SIG|EXP|REV, 0x7f800000, 0x00000000, 0xffffffff, 1)); // signed number (IEEE 754 binary32: 1 bit sign, 8 bits exponent, 23 bits significand), big endian + registerBaseType(new NumberDataType("UIN", 16, 0, 0xffff, 0, 0xfffe, 1)); // unsigned integer, 0 - 65534, little endian + registerBaseType(new NumberDataType("UIR", 16, REV, 0xffff, 0, 0xfffe, 1)); // unsigned integer, 0 - 65534, big endian + registerBaseType(new NumberDataType("SIN", 16, SIG, 0x8000, 0x8001, 0x7fff, 1)); // signed integer, -32767 - +32767, little endian + registerBaseType(new NumberDataType("SIR", 16, SIG|REV, 0x8000, 0x8001, 0x7fff, 1)); // signed integer, -32767 - +32767, big endian + registerBaseType(new NumberDataType("ULG", 32, 0, 0xffffffff, 0, 0xfffffffe, 1)); // unsigned integer, 0 - 4294967294, little endian + registerBaseType(new NumberDataType("ULR", 32, REV, 0xffffffff, 0, 0xfffffffe, 1)); // unsigned integer, 0 - 4294967294, big endian + registerBaseType(new NumberDataType("SLG", 32, SIG, 0x80000000, 0x80000001, 0xffffffff, 1)); // signed integer, -2147483647 - +2147483647, little endian + registerBaseType(new NumberDataType("SLR", 32, SIG|REV, 0x80000000, 0x80000001, 0xffffffff, 1)); // signed integer, -2147483647 - +2147483647, big endian + registerBaseType(new NumberDataType("BI0", 7, ADJ|REQ, 0, 0, 1)); // bit 0 (up to 7 bits until bit 6) + registerBaseType(new NumberDataType("BI1", 7, ADJ|REQ, 0, 1, 1)); // bit 1 (up to 7 bits until bit 7) + registerBaseType(new NumberDataType("BI2", 6, ADJ|REQ, 0, 2, 1)); // bit 2 (up to 6 bits until bit 7) + registerBaseType(new NumberDataType("BI3", 5, ADJ|REQ, 0, 3, 1)); // bit 3 (up to 5 bits until bit 7) + registerBaseType(new NumberDataType("BI4", 4, ADJ|REQ, 0, 4, 1)); // bit 4 (up to 4 bits until bit 7) + registerBaseType(new NumberDataType("BI5", 3, ADJ|REQ, 0, 5, 1)); // bit 5 (up to 3 bits until bit 7) + registerBaseType(new NumberDataType("BI6", 2, ADJ|REQ, 0, 6, 1)); // bit 6 (up to 2 bits until bit 7) + registerBaseType(new NumberDataType("BI7", 1, ADJ|REQ, 0, 7, 1)); // bit 7 + return true; } +static bool baseTypesRegistered = registerBaseTypes(); result_t DataField::create(vector::iterator& it, const vector::iterator end, @@ -339,15 +279,15 @@ result_t DataField::create(vector::iterator& it, if (result != RESULT_OK) break; } + transform(token.begin(), token.end(), token.begin(), ::toupper); string typeName = token.substr(0, pos); SingleDataField* add = NULL; - result = SingleDataField::create(typeName.c_str(), length, firstType ? name : "", firstType ? comment : "", firstType ? unit : "", partType, divisor, values, add); + result = SingleDataField::create(token.c_str(), typeName.c_str(), length, firstType ? name : "", firstType ? comment : "", firstType ? unit : "", partType, divisor, values, add); if (add != NULL) fields.push_back(add); else if (result == RESULT_OK) result = RESULT_ERR_NOTFOUND; // type not found - } - else { + } else { bool lastType = stream.eof(); result = templ->derive((firstType && lastType) ? name : "", firstType ? comment : "", firstType ? unit : "", partType, divisor, values, fields); } @@ -382,90 +322,68 @@ void DataField::dumpString(ostream& output, const string str, const bool prepend } } -result_t SingleDataField::create(const char* typeNameStr, const unsigned char length, +result_t SingleDataField::create(const char* fullIdStr, const char* idStr, const unsigned char length, const string name, const string comment, const string unit, const PartType partType, int divisor, map values, SingleDataField* &returnField) { - for (size_t i = 0; i < sizeof(dataTypes) / sizeof(dataType_t); i++) { - const dataType_t* dataType = &dataTypes[i]; - if (strcasecmp(typeNameStr, dataType->name) != 0) - continue; - - unsigned char bitCount = dataType->bitCount; - unsigned char byteCount = (unsigned char)((bitCount + 7) / 8); - if ((dataType->flags & ADJ) != 0) { // adjustable length - if ((bitCount % 8) != 0) { - if (length == 0) - bitCount = 1; // default bit count: 1 bit - else if (length <= bitCount) - bitCount = length; - else - return RESULT_ERR_OUT_OF_RANGE; // invalid length - - byteCount = (unsigned char)((bitCount + 7) / 8); - } - else if (length == 0) - byteCount = 1; //default byte count: 1 byte - else if (length <= byteCount || length == REMAIN_LEN) - byteCount = length; - else + DataType* dataType = baseTypes[fullIdStr]; + if (!dataType) { + dataType = baseTypes[idStr]; + if (!dataType) { + return RESULT_ERR_NOTFOUND; + } + if (!dataType->isAdjustableLength()) { + return RESULT_ERR_OUT_OF_RANGE; + } + } + unsigned char bitCount = dataType->getBitCount(); + unsigned char byteCount = (unsigned char)((bitCount + 7) / 8); + if (dataType->isAdjustableLength()) { + // check length + if ((bitCount % 8) != 0) { + if (length == 0) { + bitCount = 1; // default bit count: 1 bit + } else if (length <= bitCount) { + bitCount = length; + } else { return RESULT_ERR_OUT_OF_RANGE; // invalid length + } + byteCount = (unsigned char)((bitCount + 7) / 8); + } else if (length == 0) { + byteCount = 1; //default byte count: 1 byte + } else if (length <= byteCount || length == REMAIN_LEN) { + byteCount = length; + } else { + return RESULT_ERR_OUT_OF_RANGE; // invalid length } - else if (length > 0 && length != byteCount) - continue; // check for another one with same name but different length - - switch (dataType->type) - { - case bt_str: - case bt_hexstr: - case bt_dat: - case bt_tim: - if (divisor != 0 || !values.empty()) - return RESULT_ERR_INVALID_ARG; // cannot set divisor or values for string field - returnField = new StringDataField(name, comment, unit, *dataType, partType, byteCount); - return RESULT_OK; - case bt_num: - if (values.empty() && (dataType->flags & DAY) != 0) { - for (unsigned int i = 0; i < sizeof(dayNames) / sizeof(dayNames[0]); i++) - values[dataType->minValue + i] = dayNames[i]; - } - if (values.empty() || (dataType->flags & LST) == 0) { - if (divisor == 0) - divisor = 1; - - if ((dataType->bitCount % 8) == 0) { - if (divisor < 0) { - if (dataType->divisorOrFirstBit > 1) - return RESULT_ERR_INVALID_ARG; - - if (dataType->divisorOrFirstBit < 0) - divisor *= -dataType->divisorOrFirstBit; - } else if (dataType->divisorOrFirstBit < 0) { - if (divisor > 1) - return RESULT_ERR_INVALID_ARG; - - if (divisor < 0) - divisor *= -dataType->divisorOrFirstBit; - } else - divisor *= dataType->divisorOrFirstBit; - - if (-MAX_DIVISOR > divisor || divisor > MAX_DIVISOR) - return RESULT_ERR_OUT_OF_RANGE; - } - - returnField = new NumberDataField(name, comment, unit, *dataType, partType, byteCount, bitCount, divisor); - return RESULT_OK; - } - if (values.begin()->first < dataType->minValue || values.rbegin()->first > dataType->maxValue) - return RESULT_ERR_OUT_OF_RANGE; - - if (divisor != 0) - return RESULT_ERR_INVALID_ARG; // cannot use divisor != 1 for value list field - //TODO add special field for fixed values (exactly one value in the list of values) - returnField = new ValueListDataField(name, comment, unit, *dataType, partType, byteCount, bitCount, values); + } + if (typeid(*dataType)==typeid(StringDataType) + || typeid(*dataType)==typeid(DateTimeDataType)) { + if (divisor != 0 || !values.empty()) + return RESULT_ERR_INVALID_ARG; // cannot set divisor or values for string field + returnField = new SingleDataField(name, comment, unit, (StringDataType*)dataType, partType, byteCount); + return RESULT_OK; + } + if (typeid(*dataType)==typeid(NumberDataType)) { + NumberDataType* numType = (NumberDataType*)dataType; + if (values.empty() && numType->hasFlag(DAY)) { + for (unsigned int i = 0; i < sizeof(dayNames) / sizeof(dayNames[0]); i++) + values[numType->getMinValue() + i] = dayNames[i]; + } + if (values.empty()) { + result_t result = numType->derive(divisor, bitCount, numType); + if (result!=RESULT_OK) + return result; + returnField = new SingleDataField(name, comment, unit, numType, partType, byteCount); return RESULT_OK; } + if (values.begin()->first < numType->getMinValue() || values.rbegin()->first > numType->getMaxValue()) { + return RESULT_ERR_OUT_OF_RANGE; + } + //TODO add special field for fixed values (exactly one value in the list of values) + returnField = new ValueListDataField(name, comment, unit, numType, partType, byteCount, values); + return RESULT_OK; } return RESULT_ERR_NOTFOUND; } @@ -479,7 +397,12 @@ void SingleDataField::dump(ostream& output) output << "m"; else if (m_partType == pt_slaveData) output << "s"; - dumpString(output, m_dataType.name); + output << FIELD_SEPARATOR; + if (!m_dataType->dump(output, m_length)) { // no divisor appended + output << FIELD_SEPARATOR; // no value list, no divisor + } + dumpString(output, m_unit); + dumpString(output, m_comment); } @@ -502,13 +425,13 @@ result_t SingleDataField::read(const PartType partType, return RESULT_ERR_INVALID_PART; } if (isIgnored() || (fieldName != NULL && (m_name != fieldName || fieldIndex > 0))) { - bool remainder = m_length==REMAIN_LEN && (m_dataType.flags & ADJ)!=0; + bool remainder = m_length==REMAIN_LEN && m_dataType->isAdjustableLength(); if (!remainder && offset + m_length > data.size()) { return RESULT_ERR_INVALID_POS; } return RESULT_EMPTY; } - return readRawValue(data, offset, output); + return m_dataType->readRawValue(data, offset, m_length, output); } result_t SingleDataField::read(const PartType partType, @@ -531,7 +454,7 @@ result_t SingleDataField::read(const PartType partType, return RESULT_ERR_INVALID_PART; } if (isIgnored() || (fieldName != NULL && (m_name != fieldName || fieldIndex > 0))) { - bool remainder = m_length==REMAIN_LEN && (m_dataType.flags & ADJ)!=0; + bool remainder = m_length==REMAIN_LEN && m_dataType->isAdjustableLength(); if (!remainder && offset + m_length > data.size()) { return RESULT_ERR_INVALID_POS; } @@ -593,697 +516,83 @@ result_t SingleDataField::write(istringstream& input, default: return RESULT_ERR_INVALID_PART; } - return writeSymbols(input, offset, data, length); + return writeSymbols(input, (const unsigned char)offset, data, length); } -StringDataField* StringDataField::clone() +result_t SingleDataField::readSymbols(SymbolString& input, + const unsigned char offset, + ostringstream& output, OutputFormat outputFormat) { - return new StringDataField(*this); + return m_dataType->readSymbols(input, offset, m_length, output, outputFormat); } -result_t StringDataField::derive(string name, string comment, +result_t SingleDataField::writeSymbols(istringstream& input, + const unsigned char offset, + SymbolString& output, unsigned char* usedLength) +{ + return m_dataType->writeSymbols(input, offset, m_length, output, usedLength); +} + +SingleDataField* SingleDataField::clone() +{ + return new SingleDataField(*this); +} + +result_t SingleDataField::derive(string name, string comment, string unit, const PartType partType, int divisor, map values, vector& fields) { if (m_partType != pt_any && partType == pt_any) return RESULT_ERR_INVALID_PART; // cannot create a template from a concrete instance - if (divisor != 0 || !values.empty()) - return RESULT_ERR_INVALID_ARG; // cannot set divisor or values for string field + bool numeric = typeid(*m_dataType)==typeid(NumberDataType); + if (!numeric && (divisor != 0 || !values.empty())) + return RESULT_ERR_INVALID_ARG; // cannot set divisor or values for non-numeric field if (name.empty()) name = m_name; if (comment.empty()) comment = m_comment; if (unit.empty()) unit = m_unit; - - fields.push_back(new StringDataField(name, comment, unit, m_dataType, partType, m_length)); - - return RESULT_OK; -} - -bool StringDataField::hasField(const char* fieldName, bool numeric) -{ - return !numeric && (fieldName==NULL || fieldName==m_name); -} - -void StringDataField::dump(ostream& output) -{ - SingleDataField::dump(output); - if ((m_dataType.flags & ADJ) != 0) { - if (m_length==REMAIN_LEN) - output << ":*"; - else - output << ":" << static_cast(m_length); + DataType* dataType = m_dataType; + if (numeric) { + NumberDataType* numType = (NumberDataType*)m_dataType; + result_t result = numType->derive(divisor, 0, numType); + if (result != RESULT_OK) + return result; + dataType = numType; } - output << FIELD_SEPARATOR; // no value list, no divisor - dumpString(output, m_unit); - dumpString(output, m_comment); -} - -result_t StringDataField::readRawValue(SymbolString& input, const unsigned char offset, unsigned int& value) -{ - return RESULT_EMPTY; -} - -result_t StringDataField::readSymbols(SymbolString& input, const unsigned char baseOffset, - ostringstream& output, OutputFormat outputFormat) -{ - size_t start = 0, count = m_length; - int incr = 1; - unsigned char ch, last = 0, hour = 0; - bool terminated = false; - if (count==REMAIN_LEN && input.size()>baseOffset) { - count = input.size()-baseOffset; - } else if (baseOffset + count > input.size()) { - return RESULT_ERR_INVALID_POS; - } - if ((m_dataType.flags & REV) != 0) { // reverted binary representation (most significant byte first) - start = m_length - 1; - incr = -1; - } - - if (outputFormat & OF_JSON) - output << '"'; - for (size_t offset = start, i = 0; i < count; offset += incr, i++) { - if (m_length == 4 && i == 2 && m_dataType.type == bt_dat) - continue; // skip weekday in between - ch = input[baseOffset + offset]; - if ((m_dataType.flags & BCD) != 0 && ((m_dataType.flags & REQ) != 0 || ch != m_dataType.replacement)) { - if ((ch & 0xf0) > 0x90 || (ch & 0x0f) > 0x09) - return RESULT_ERR_OUT_OF_RANGE; // invalid BCD - ch = (unsigned char)((ch >> 4) * 10 + (ch & 0x0f)); - } - switch (m_dataType.type) - { - case bt_hexstr: - if (i > 0) - output << ' '; - output << setw(2) << hex << setfill('0') << static_cast(ch); - break; - case bt_dat: - if ((m_dataType.flags & REQ) == 0 && ch == m_dataType.replacement) { - if (i + 1 != m_length) { - output << NULL_VALUE << "."; - break; - } - else if (last == m_dataType.replacement) { - output << NULL_VALUE; - break; - } - } - if (i + 1 == m_length) - output << (2000 + ch); - else if (ch < 1 || (i == 0 && ch > 31) || (i == 1 && ch > 12)) - return RESULT_ERR_OUT_OF_RANGE; // invalid date - else - output << setw(2) << dec << setfill('0') << static_cast(ch) << "."; - break; - case bt_tim: - if ((m_dataType.flags & REQ) == 0 && ch == m_dataType.replacement) { - if (m_length == 1) { // truncated time - output << NULL_VALUE << ":" << NULL_VALUE; - break; - } - if (i > 0) - output << ":"; - output << NULL_VALUE; - break; - } - if (m_length == 1) { // truncated time - if (i == 0) { - ch = (unsigned char)(ch/(60/m_dataType.divisorOrFirstBit)); // convert to hours - offset -= incr; // repeat for minutes - count++; - } - else - ch = (unsigned char)((ch % (60/m_dataType.divisorOrFirstBit)) * m_dataType.divisorOrFirstBit); // convert to minutes - } - if (i == 0) { - if (ch > 24) - return RESULT_ERR_OUT_OF_RANGE; // invalid hour - hour = ch; - } - else if (ch > 59 || (hour == 24 && ch > 0)) - return RESULT_ERR_OUT_OF_RANGE; // invalid time - if (i > 0) - output << ":"; - output << setw(2) << dec << setfill('0') << static_cast(ch); - break; - default: - if (ch < 0x20) - ch = (unsigned char)m_dataType.replacement; - if (ch == 0x00) - terminated = true; - else if (!terminated) - output << setw(0) << dec << static_cast(ch); - break; - } - last = ch; - } - if (outputFormat & OF_JSON) - output << '"'; - - return RESULT_OK; -} - -result_t StringDataField::writeSymbols(istringstream& input, - unsigned char baseOffset, SymbolString& output, unsigned char* length) -{ - size_t start = 0, count = m_length; - bool remainder = count==REMAIN_LEN && (m_dataType.flags & ADJ)!=0; - int incr = 1; - unsigned int value = 0, last = 0, lastLast = 0; - string token; - - if ((m_dataType.flags & REV) != 0) { // reverted binary representation (most significant byte first) - start = m_length - 1; - incr = -1; - } - if (isIgnored() && (m_dataType.flags & REQ) == 0) { - if (remainder) { - count = 1; - } - for (size_t offset = start, i = 0; i < count; offset += incr, i++) { - output[baseOffset + offset] = (unsigned char)m_dataType.replacement; // fill up with replacement - } - if (length!=NULL) - *length = (unsigned char)count; - return RESULT_OK; - } - result_t result; - size_t i = 0, offset; - for (offset = start; i < count; offset += incr, i++) { - switch (m_dataType.type) - { - case bt_hexstr: - while (!input.eof() && input.peek() == ' ') - input.get(); - if (input.eof()) { // no more digits - value = m_dataType.replacement; // fill up with replacement - } else { - token.clear(); - token.push_back((unsigned char)input.get()); - if (input.eof()) - return RESULT_ERR_INVALID_NUM; // too short hex value - token.push_back((unsigned char)input.get()); - if (input.eof()) - return RESULT_ERR_INVALID_NUM; // too short hex value - - value = parseInt(token.c_str(), 16, 0, 0xff, result); - if (result != RESULT_OK) - return result; // invalid hex value - } - break; - case bt_dat: - if (m_length == 4 && i == 2) - continue; // skip weekday in between - if (input.eof() || !getline(input, token, '.')) - return RESULT_ERR_EOF; // incomplete - if ((m_dataType.flags & REQ) == 0 && strcmp(token.c_str(), NULL_VALUE) == 0) { - value = m_dataType.replacement; - break; - } - value = parseInt(token.c_str(), 10, 0, 2099, result); - if (result != RESULT_OK) { - return result; // invalid date part - } - if (i + 1 == m_length) { - if (m_length == 4) { - // calculate local week day - int y = (value < 100 ? value + 2000 : value) - 1900; - int l = (last==1 || last==2) ? 1 : 0; - int mjd = 14956 + lastLast + (int)((y-l)*365.25) + (int)((last+1+l*12)*30.6001); - int daysSinceSunday = (mjd+3) % 7; // Sun=0 - if ((m_dataType.flags & BCD) != 0) - output[baseOffset + offset - incr] = (unsigned char)((6+daysSinceSunday) % 7); // Sun=0x06 - else - output[baseOffset + offset - incr] = (unsigned char)(daysSinceSunday==0 ? 7 : daysSinceSunday); // Sun=0x07 - } - if (value >= 2000) - value -= 2000; - if (value > 99) - return RESULT_ERR_OUT_OF_RANGE; // invalid year - } - else if (value < 1 || (i == 0 && value > 31) || (i == 1 && value > 12)) - return RESULT_ERR_OUT_OF_RANGE; // invalid date part - break; - case bt_tim: - if (input.eof() || !getline(input, token, LENGTH_SEPARATOR)) - return RESULT_ERR_EOF; // incomplete - if ((m_dataType.flags & REQ) == 0 && strcmp(token.c_str(), NULL_VALUE) == 0) { - value = m_dataType.replacement; - if (m_length == 1) { // truncated time - if (i == 0) { - last = value; - offset -= incr; // repeat for minutes - count++; - continue; - } - if (last != m_dataType.replacement) - return RESULT_ERR_INVALID_NUM; // invalid truncated time minutes - } - break; - } - value = parseInt(token.c_str(), 10, 0, 59, result); - if (result != RESULT_OK) - return result; // invalid time part - if ((i == 0 && value > 24) || (i > 0 && (last == 24 && value > 0) )) - return RESULT_ERR_OUT_OF_RANGE; // invalid time part - if (m_length == 1) { // truncated time - if (i == 0) { - last = value; - offset -= incr; // repeat for minutes - count++; - continue; - } - - if ((value % m_dataType.divisorOrFirstBit) != 0) - return RESULT_ERR_INVALID_NUM; // invalid truncated time minutes - value = (last * 60 + value)/m_dataType.divisorOrFirstBit; - if (value > 24 * 6) - return RESULT_ERR_OUT_OF_RANGE; // invalid time - } - break; - default: - if (input.eof()) { - value = m_dataType.replacement; - } else { - value = input.get(); - if (input.eof() || value < 0x20) - value = m_dataType.replacement; - } - break; - } - if (remainder && input.eof() && i > 0) { - if (value == 0x00 && m_dataType.type == bt_str) { - output[baseOffset + offset] = 0; - offset += incr; - } - break; - } - lastLast = last; - last = value; - if ((m_dataType.flags & BCD) != 0 && ((m_dataType.flags & REQ) != 0 || value != m_dataType.replacement)) { - if (value > 99) - return RESULT_ERR_OUT_OF_RANGE; // invalid BCD - value = ((value / 10) << 4) | (value % 10); - } - if (value > 0xff) - return RESULT_ERR_OUT_OF_RANGE; // value out of range - output[baseOffset + offset] = (unsigned char)value; - } - - if (!remainder && i < count) - return RESULT_ERR_EOF; // input too short - if (length!=NULL) - *length = (unsigned char)((offset-start)*incr); - return RESULT_OK; -} - - -bool NumericDataField::hasFullByteOffset(bool after) -{ - return m_length > 1 || (m_bitCount % 8) == 0 - || (after && m_bitOffset + (m_bitCount % 8) >= 8); -} - -bool NumericDataField::hasField(const char* fieldName, bool numeric) -{ - return numeric && (fieldName==NULL || fieldName==m_name); -} - -void NumericDataField::dump(ostream& output) -{ - SingleDataField::dump(output); - if ((m_dataType.flags & ADJ) != 0) { - if ((m_dataType.bitCount % 8) != 0) - output << ":" << static_cast(m_bitCount); - else - output << ":" << static_cast(m_length); - } - output << FIELD_SEPARATOR; -} - -result_t NumericDataField::readRawValue(SymbolString& input, - unsigned char baseOffset, unsigned int& value) -{ - size_t start = 0, count = m_length; - int incr = 1; - unsigned char ch; - - if (baseOffset + m_length > input.size()) - return RESULT_ERR_INVALID_POS; // not enough data available - - if ((m_dataType.flags & REV) != 0) { // reverted binary representation (most significant byte first) - start = m_length - 1; - incr = -1; - } - - value = 0; - unsigned int exp = 1; - for (size_t offset = start, i = 0; i < count; offset += incr, i++) { - ch = input[baseOffset + offset]; - if ((m_dataType.flags & BCD) != 0) { - if ((m_dataType.flags & REQ) == 0 && ch == (m_dataType.replacement & 0xff)) { - value = m_dataType.replacement; - return RESULT_OK; - } - if ((m_dataType.flags & HCD) == 0) { - if ((ch & 0xf0) > 0x90 || (ch & 0x0f) > 0x09) - return RESULT_ERR_OUT_OF_RANGE; // invalid BCD - - ch = (unsigned char)((ch >> 4) * 10 + (ch & 0x0f)); - } else if (ch > 0x63) - return RESULT_ERR_OUT_OF_RANGE; // invalid HCD - value += ch * exp; - exp *= 100; - } else { - value |= ch * exp; - exp <<= 8; - } - } - - if ((m_dataType.flags & BCD) == 0) { - value >>= m_bitOffset; - if ((m_bitCount % 8) != 0) - value &= (1 << m_bitCount) - 1; - } - - return RESULT_OK; -} - -result_t NumericDataField::writeRawValue(unsigned int value, - unsigned char baseOffset, SymbolString& output, unsigned char* length) -{ - size_t start = 0, count = m_length; - int incr = 1; - unsigned char ch; - - if ((m_dataType.flags & REV) != 0) { // reverted binary representation (most significant byte first) - start = m_length - 1; - incr = -1; - } - - if ((m_dataType.flags & BCD) == 0) { - if ((m_bitCount % 8) != 0 && (value & ~((1 << m_bitCount) - 1)) != 0) - return RESULT_ERR_OUT_OF_RANGE; - - value <<= m_bitOffset; - } - for (size_t offset = start, i = 0, exp = 1; i < count; offset += incr, i++) { - if ((m_dataType.flags & BCD) != 0) { - if ((m_dataType.flags & REQ) == 0 && value == m_dataType.replacement) - ch = m_dataType.replacement & 0xff; - else { - ch = (unsigned char)((value / exp) % 100); - if ((m_dataType.flags & HCD) == 0) - ch = (unsigned char)(((ch / 10) << 4) | (ch % 10)); - } - exp *= 100; - } else { - ch = (value / exp) & 0xff; - exp <<= 8; - } - if (offset == start && (m_bitCount % 8) != 0 && baseOffset + offset < output.size()) - output[baseOffset + offset] |= ch; - else - output[baseOffset + offset] = ch; - } - if (length!=NULL) - *length = m_length; - return RESULT_OK; -} - - -NumberDataField::NumberDataField(const string name, const string comment, - const string unit, const dataType_t dataType, const PartType partType, - const unsigned char length, const unsigned char bitCount, - const int divisor) - : NumericDataField(name, comment, unit, dataType, partType, length, bitCount, - (unsigned char)((dataType.bitCount % 8) == 0 ? 0 : dataType.divisorOrFirstBit)), - m_divisor(divisor), m_precision(0) -{ - if (divisor > 1) - for (unsigned int exp = 1; exp < MAX_DIVISOR; exp *= 10, m_precision++) - if (exp >= (unsigned int)divisor) - break; -} - -NumberDataField* NumberDataField::clone() -{ - return new NumberDataField(*this); -} - -result_t NumberDataField::derive(string name, string comment, - string unit, const PartType partType, - int divisor, map values, - vector& fields) -{ - if (m_partType != pt_any && partType == pt_any) - return RESULT_ERR_INVALID_PART; // cannot create a template from a concrete instance - if (name.empty()) - name = m_name; - if (comment.empty()) - comment = m_comment; - if (unit.empty()) - unit = m_unit; - if (!values.empty()) { - if (divisor != 0 || m_divisor != 1) - return RESULT_ERR_INVALID_ARG; // cannot use divisor != 1 for value list field - - fields.push_back(new ValueListDataField(name, comment, unit, m_dataType, partType, m_length, m_bitCount, values)); - } - else { - if (divisor == 0) - divisor = m_divisor; - else if ((m_dataType.bitCount % 8) == 0) { - if (divisor < 0) { - if (m_divisor > 1) - return RESULT_ERR_INVALID_ARG; - - if (m_divisor < 0) - divisor *= -m_divisor; - } else if (m_divisor < 0) { - if (divisor > 1) - return RESULT_ERR_INVALID_ARG; - - if (divisor < 0) - divisor *= -m_divisor; - } else - divisor *= m_divisor; - - if (-MAX_DIVISOR > divisor || divisor > MAX_DIVISOR) { - return RESULT_ERR_OUT_OF_RANGE; - } - } - fields.push_back(new NumberDataField(name, comment, unit, m_dataType, partType, m_length, m_bitCount, divisor)); - } - return RESULT_OK; -} - -void NumberDataField::dump(ostream& output) -{ - NumericDataField::dump(output); - if ((m_dataType.bitCount % 8) == 0 && m_dataType.divisorOrFirstBit != m_divisor) - output << static_cast(m_divisor / m_dataType.divisorOrFirstBit); - dumpString(output, m_unit); - dumpString(output, m_comment); -} - -result_t NumberDataField::readSymbols(SymbolString& input, const unsigned char baseOffset, - ostringstream& output, OutputFormat outputFormat) -{ - unsigned int value = 0; - int signedValue; - - result_t result = readRawValue(input, baseOffset, value); - if (result != RESULT_OK) - return result; - - output << setw(0) << dec; // initialize output - - if ((m_dataType.flags & REQ) == 0 && value == m_dataType.replacement) { - if (outputFormat & OF_JSON) - output << "null"; - else - output << NULL_VALUE; - return RESULT_OK; - } - - bool negative = (m_dataType.flags & SIG) != 0 && (value & (1 << (m_bitCount - 1))) != 0; - if (m_bitCount == 32) { - if ((m_dataType.flags & EXP) != 0) { // IEEE 754 binary32 - float val; -#ifdef HAVE_DIRECT_FLOAT_FORMAT -# if HAVE_DIRECT_FLOAT_FORMAT == 2 - value = __builtin_bswap32(value); -# endif - unsigned char* pval = (unsigned char*)&value; - val = *((float*)pval); -#else - int exp = (value >> 23) & 0xff; // 8 bits, signed - if (exp == 0) { - val = 0.0; - } else { - exp -= 127; - unsigned int sig = value & ((1 << 23) - 1); - val = (1.0f + (float)(sig / exp2(23))) * (float)exp2(exp); - if (negative) { - val = -val; - } - } -#endif - if (val != 0.0) { - if (m_divisor < 0) { - val *= (float)-m_divisor; - } else if (m_divisor > 1) { - val /= (float)m_divisor; - } - } - if (m_precision != 0) - output << fixed << setprecision(m_precision+6); - else if (val == 0) - output << fixed << setprecision(1); - output << static_cast(val); - return RESULT_OK; - } - if (!negative) { - if (m_divisor < 0) { - output << static_cast((float)value * (float)(-m_divisor)); - } else if (m_divisor <= 1) { - output << static_cast(value); - } else { - output << setprecision(m_precision) - << fixed << static_cast((float)value / (float)m_divisor); - } - return RESULT_OK; - } - signedValue = (int) value; // negative signed value - } - else if (negative) // negative signed value - signedValue = (int) value - (1 << m_bitCount); - else - signedValue = (int) value; - - if (m_divisor < 0) - output << static_cast((float)signedValue * (float)(-m_divisor)); - else if (m_divisor <= 1) { - if ((m_dataType.flags & (FIX|BCD)) == (FIX|BCD)) { - if (outputFormat & OF_JSON) { - output << '"'; - output << setw(m_length * 2) << setfill('0'); - output << static_cast(signedValue) << setw(0); - output << '"'; - return RESULT_OK; - } - output << setw(m_length * 2) << setfill('0'); - } - output << static_cast(signedValue) << setw(0); - } - else - output << setprecision(m_precision) - << fixed << static_cast((float)signedValue / (float)m_divisor); - - return RESULT_OK; -} - -result_t NumberDataField::writeSymbols(istringstream& input, - unsigned char baseOffset, SymbolString& output, unsigned char* length) -{ - unsigned int value; - - const char* str = input.str().c_str(); - if ((m_dataType.flags & REQ) == 0 && (isIgnored() || strcasecmp(str, NULL_VALUE) == 0)) { - value = m_dataType.replacement; // replacement value - } else if (str == NULL || *str == 0) { - return RESULT_ERR_EOF; // input too short - } else if ((m_dataType.flags & EXP) != 0) { // IEEE 754 binary32 - char* strEnd = NULL; - double dvalue = strtod(str, &strEnd); - if (strEnd == NULL || strEnd == str || *strEnd != 0) { - return RESULT_ERR_INVALID_NUM; // invalid value - } - if (m_divisor < 0) - dvalue /= -m_divisor; - else if (m_divisor > 1) - dvalue *= m_divisor; -#ifdef HAVE_DIRECT_FLOAT_FORMAT - float val = (float)dvalue; - unsigned char* pval = (unsigned char*)&val; - value = *((int32_t*)pval); -# if HAVE_DIRECT_FLOAT_FORMAT == 2 - value = __builtin_bswap32(value); -# endif -#else - value = 0; - if (dvalue != 0) { - bool negative = dvalue < 0; - if (negative) { - dvalue = -dvalue; - } - int exp = ilogb(dvalue); - if (exp < -126 || exp > 127) - return RESULT_ERR_INVALID_NUM; // invalid value - dvalue = scalbln(dvalue, -exp) - 1.0; - unsigned int sig = (unsigned int)(dvalue * exp2(23)); - exp += 127; - value = (exp << 23) | sig; - if (negative) { - value |= 0x80000000; - } - } -#endif + if (values.empty()) { + fields.push_back(new SingleDataField(name, comment, unit, dataType, partType, m_length)); } else { - char* strEnd = NULL; - if (m_divisor == 1) { - if ((m_dataType.flags & SIG) != 0) { - long int signedValue = strtol(str, &strEnd, 10); - if (signedValue < 0 && m_bitCount != 32) - value = (unsigned int)(signedValue + (1 << m_bitCount)); - else - value = (unsigned int)signedValue; - } - else - value = (unsigned int)strtoul(str, &strEnd, 10); - if (strEnd == NULL || strEnd == str || (*strEnd != 0 && *strEnd != '.')) - return RESULT_ERR_INVALID_NUM; // invalid value - } else { - char* strEnd = NULL; - double dvalue = strtod(str, &strEnd); - if (strEnd == NULL || strEnd == str || *strEnd != 0) - return RESULT_ERR_INVALID_NUM; // invalid value - if (m_divisor < 0) - dvalue = round(dvalue / -m_divisor); - else - dvalue = round(dvalue * m_divisor); - if ((m_dataType.flags & SIG) != 0) { - if (dvalue < -(1LL << (8 * m_length)) || dvalue >= (1LL << (8 * m_length))) - return RESULT_ERR_OUT_OF_RANGE; // value out of range - if (dvalue < 0 && m_bitCount != 32) - value = (int)(dvalue + (1 << m_bitCount)); - else - value = (int)dvalue; - } else { - if (dvalue < 0.0 || dvalue >= (1LL << (8 * m_length))) - return RESULT_ERR_OUT_OF_RANGE; // value out of range - value = (unsigned int)dvalue; - } - } - - if ((m_dataType.flags & SIG) != 0) { // signed value - if ((value & (1 << (m_bitCount - 1))) != 0) { // negative signed value - if (value < m_dataType.minValue) - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - else if (value > m_dataType.maxValue) - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - else if (value < m_dataType.minValue || value > m_dataType.maxValue) - return RESULT_ERR_OUT_OF_RANGE; // value out of range + fields.push_back(new ValueListDataField(name, comment, unit, (NumberDataType*)dataType, partType, m_length, values)); } + return RESULT_OK; +} - return writeRawValue(value, baseOffset, output, length); +bool SingleDataField::hasField(const char* fieldName, bool numeric) +{ + bool numericType = typeid(*m_dataType)==typeid(NumberDataType); + return numeric==numericType && (fieldName==NULL || fieldName==m_name); +} + +unsigned char SingleDataField::getLength(PartType partType, unsigned char maxLength) +{ + if (partType != m_partType) { + return (unsigned char)0; + } + bool remainder = m_length==REMAIN_LEN && m_dataType->isAdjustableLength(); + return remainder ? maxLength : m_length; +} + +bool SingleDataField::hasFullByteOffset(bool after) +{ + if (m_length > 1 || typeid(*m_dataType) != typeid(NumberDataType)) { + return true; + } + NumberDataType* num = (NumberDataType*)m_dataType; + return (num->getBitCount() % 8) == 0 + || (after && num->getFirstBit() + (num->getBitCount() % 8) >= 8); } @@ -1309,54 +618,64 @@ result_t ValueListDataField::derive(string name, string comment, return RESULT_ERR_INVALID_ARG; // cannot use divisor != 1 for value list field if (!values.empty()) { - if (values.begin()->first < m_dataType.minValue || values.rbegin()->first > m_dataType.maxValue) + NumberDataType* num = (NumberDataType*)m_dataType; + if (values.begin()->first < num->getMinValue() || values.rbegin()->first > num->getMaxValue()) return RESULT_ERR_INVALID_ARG; // cannot use divisor != 1 for value list field } else values = m_values; - fields.push_back(new ValueListDataField(name, comment, unit, m_dataType, partType, m_length, m_bitCount, values)); + fields.push_back(new ValueListDataField(name, comment, unit, (NumberDataType*)m_dataType, partType, m_length, values)); return RESULT_OK; } void ValueListDataField::dump(ostream& output) { - NumericDataField::dump(output); - bool first = true; - for (map::iterator it = m_values.begin(); it != m_values.end(); it++) { - if (first) - first = false; - else - output << VALUE_SEPARATOR; - output << static_cast(it->first) << "=" << it->second; - } + output << setw(0) << dec; // initialize formatting + dumpString(output, m_name, false); + output << FIELD_SEPARATOR; + if (m_partType == pt_masterData) + output << "m"; + else if (m_partType == pt_slaveData) + output << "s"; + output << FIELD_SEPARATOR; + if (!m_dataType->dump(output, m_length)) { // no divisor appended + bool first = true; + for (map::iterator it = m_values.begin(); it != m_values.end(); it++) { + if (first) + first = false; + else + output << VALUE_SEPARATOR; + output << static_cast(it->first) << "=" << it->second; + } + } // TODO else does not fit into CSV format! dumpString(output, m_unit); dumpString(output, m_comment); } -result_t ValueListDataField::readSymbols(SymbolString& input, const unsigned char baseOffset, +result_t ValueListDataField::readSymbols(SymbolString& input, + const unsigned char offset, ostringstream& output, OutputFormat outputFormat) { unsigned int value = 0; - result_t result = readRawValue(input, baseOffset, value); + result_t result = m_dataType->readRawValue(input, offset, m_length, value); if (result != RESULT_OK) return result; - map::iterator it = m_values.find(value); - if (it == m_values.end() && value != m_dataType.replacement) { + if (it == m_values.end() && value != m_dataType->getReplacement()) { // fall back to raw value in input - output << setw(0) << dec << static_cast(value); + output << setw(0) << dec << static_cast(value); return RESULT_OK; } if (it == m_values.end()) { if (outputFormat & OF_JSON) output << "null"; - else if (value == m_dataType.replacement) + else if (value == m_dataType->getReplacement()) output << NULL_VALUE; } else if (outputFormat & OF_NUMERIC) - output << setw(0) << dec << static_cast(value); + output << setw(0) << dec << static_cast(value); else if (outputFormat & OF_JSON) output << '"' << it->second << '"'; else @@ -1365,19 +684,21 @@ result_t ValueListDataField::readSymbols(SymbolString& input, const unsigned cha } result_t ValueListDataField::writeSymbols(istringstream& input, - unsigned char baseOffset, SymbolString& output, unsigned char* length) + const unsigned char offset, + SymbolString& output, unsigned char* usedLength) { + NumberDataType* numType = (NumberDataType*)m_dataType; if (isIgnored()) - return writeRawValue(m_dataType.replacement, baseOffset, output, length); // replacement value + return numType->writeRawValue(numType->getReplacement(), offset, m_length, output, usedLength); // replacement value const char* str = input.str().c_str(); for (map::iterator it = m_values.begin(); it != m_values.end(); it++) if (it->second.compare(str) == 0) - return writeRawValue(it->first, baseOffset, output, length); + return numType->writeRawValue(it->first, offset, m_length, output, usedLength); if (strcasecmp(str, NULL_VALUE) == 0) - return writeRawValue(m_dataType.replacement, baseOffset, output, length); // replacement value + return numType->writeRawValue(numType->getReplacement(), offset, m_length, output, usedLength); // replacement value char* strEnd = NULL; // fall back to raw value in input unsigned int value; @@ -1385,7 +706,7 @@ result_t ValueListDataField::writeSymbols(istringstream& input, if (strEnd == NULL || strEnd == str || (*strEnd != 0 && *strEnd != '.')) return RESULT_ERR_INVALID_NUM; // invalid value if (m_values.find(value) != m_values.end()) - return writeRawValue(value, baseOffset, output, length); + return numType->writeRawValue(value, offset, m_length, output, usedLength); return RESULT_ERR_NOTFOUND; // value assignment not found } @@ -1423,10 +744,10 @@ DataFieldSet* DataFieldSet::getIdentFields() manufacturers[0xc5] = "Weishaupt"; manufacturers[0xfd] = "ebusd.eu"; vector fields; - fields.push_back(new ValueListDataField("MF", "", "", uchDataType, pt_slaveData, 1, 8, manufacturers)); - fields.push_back(new StringDataField("ID", "", "", stringDataType, pt_slaveData, 5)); - fields.push_back(new NumberDataField("SW", "", "", pinDataType, pt_slaveData, 2, 16, 1)); - fields.push_back(new NumberDataField("HW", "", "", pinDataType, pt_slaveData, 2, 16, 1)); + fields.push_back(new ValueListDataField("MF", "", "", uchDataType, pt_slaveData, 1, manufacturers)); + fields.push_back(new SingleDataField("ID", "", "", stringDataType, pt_slaveData, 5)); + fields.push_back(new SingleDataField("SW", "", "", pinDataType, pt_slaveData, 2)); + fields.push_back(new SingleDataField("HW", "", "", pinDataType, pt_slaveData, 2)); s_identFields = new DataFieldSet("ident", "", fields); } return s_identFields; diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index a18f3194..7638bc8f 100644 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -22,6 +22,7 @@ #include "symbol.h" #include "result.h" #include "filereader.h" +#include "datatype.h" #include #include #include @@ -30,140 +31,22 @@ #include /** @file data.h - * Classes, functions, and constants related to decoding/encoding of symbols - * on the eBUS to/from readable values. + * Classes related to defining fields based on data types in symbols on the + * eBUS. * * A @a DataField is either a @a SingleDataField or a list of * @a SingleDataField instances in a @a DataFieldSet. * - * A @a SingleDataField is either a string based one or a numeric one. Due to - * their text nature, date and time fields are also treated as - * @a StringDataField. - * * The basic field types are just @a StringDataField, @a NumberDataField, and - * @a ValueListDataField. The particular eBUS specification types like e.g. - * @a D1C are defined by using one of these basic field types (see #BaseType) - * with certain flags, such as #BCD, #FIX, #REQ, see #dataType_s. - * - * The list of available base types is defined an array of #dataType_s - * structures and can easily be extended if necessary. - * - * Each @a DataField can be converted from a @a SymbolString to an - * @a ostringstream (see @a DataField#read() methods) or vice versa from an - * @a istringstream to a @a SymbolString (see @a DataField#write()). + * @a ValueListDataField. * * The @a DataFieldTemplates allow definition of derived types as well as * combined types based on the list of available base types. It reads the * instances from configuration files by inheriting the @a FileReader template * class. */ - using namespace std; -/** the separator character used between base type name and length (in CSV only). */ -#define LENGTH_SEPARATOR ':' - -/** the replacement string for undefined values (in UI and CSV). */ -#define NULL_VALUE "-" - -/** the separator character used between fields (in UI only). */ -#define UI_FIELD_SEPARATOR ';' - -/** the type for data output format options. */ -typedef int OutputFormat; - -/* the bit flags for @a OutputFormat. */ -static const unsigned int OF_VERBOSE = 0x01; //!< verbose format (names, values, units, and comments). -static const unsigned int OF_NUMERIC = 0x02; //!< numeric format (keep numeric value of value=name pairs). -static const unsigned int OF_JSON = 0x04; //!< JSON format. - -/** the message part in which a data field is stored. */ -enum PartType { - pt_any, //!< stored in any data (master or slave) - pt_masterData, //!< stored in master data - pt_slaveData, //!< stored in slave data -}; - -/** the available base data types. */ -enum BaseType { - bt_str, //!< text string in a @a StringDataField - bt_hexstr, //!< hex digit string in a @a StringDataField - bt_dat, //!< date in a @a StringDataField - bt_tim, //!< time in a @a StringDataField - bt_num, //!< numeric value in a @a NumericDataField -}; - -/* flags for dataType_t. */ -static const unsigned int ADJ = 0x01; //!< adjustable length, bitCount is maximum length -static const unsigned int BCD = 0x02; //!< binary representation is BCD -static const unsigned int REV = 0x04; //!< reverted binary representation (most significant byte first) -static const unsigned int SIG = 0x08; //!< signed value -static const unsigned int LST = 0x10; //!< value list is possible (without applied divisor) -static const unsigned int DAY = 0x20; //!< forced value list defaulting to week days -static const unsigned int IGN = 0x40; //!< ignore value during read and write -static const unsigned int FIX = 0x80; //!< fixed width formatting -static const unsigned int REQ = 0x100;//!< value may not be NULL -static const unsigned int HCD = 0x200; //!< binary representation is hex converted to decimal and interpreted as 2 digits (also requires #BCD) -static const unsigned int EXP = 0x400; //!< exponential numeric representation - -/** The structure for defining data types with their properties. */ -typedef struct dataType_s { - const char* name; //!< data type identifier - const unsigned char bitCount; //!< number of bits (maximum length if #ADJ flag is set, must be multiple of 8 with flag #BCD) - const BaseType type; //!< base data type - const unsigned short flags; //!< flags (like #BCD) - const unsigned int replacement; //!< replacement value (fill-up value for #bt_str / #bt_hexstr, no replacement if equal to #minValue for #bt_num) - const unsigned int minValue; //!< minimum raw value (ignored for @a StringDataField) - const unsigned int maxValue; //!< maximum raw value (ignored for @a StringDataField) - const short divisorOrFirstBit; //!< #bt_num: divisor (negative for reciprocal) or offset to first bit (if (#bitCount%8)!=0) -} dataType_t; - -/** the maximum allowed position within master or slave data. */ -#define MAX_POS 24 - -/** the maximum allowed field length. */ -#define MAX_LEN 31 - -/** the field length indicating remainder of input. */ -#define REMAIN_LEN 255 - -/** - * Parse an unsigned int value. - * @param str the string to parse. - * @param base the numerical base. - * @param minValue the minimum resulting value. - * @param maxValue the maximum resulting value. - * @param result the variable in which to store an error code when parsing failed or the value is out of bounds. - * @param length the optional variable in which to store the number of read characters. - * @return the parsed value. - */ -unsigned int parseInt(const char* str, int base, const unsigned int minValue, const unsigned int maxValue, result_t& result, unsigned int* length=NULL); - -/** - * Parse a signed int value. - * @param str the string to parse. - * @param base the numerical base. - * @param minValue the minimum resulting value. - * @param maxValue the maximum resulting value. - * @param result the variable in which to store an error code when parsing failed or the value is out of bounds. - * @param length the optional variable in which to store the number of read characters. - * @return the parsed value. - */ -int parseSignedInt(const char* str, int base, const int minValue, const int maxValue, result_t& result, unsigned int* length=NULL); - -/** - * Print the error position of the iterator. - * @param out the @a ostream to print to. - * @param begin the iterator to the beginning of the items. - * @param end the iterator to the end of the items. - * @param pos the iterator with the erroneous position. - * @param filename the name of the file being read. - * @param lineNo the current line number in the file being read. - * @param result the result code. - */ -void printErrorPos(ostream& out, vector::iterator begin, const vector::iterator end, vector::iterator pos, string filename, size_t lineNo, result_t result); - - class DataFieldTemplates; class SingleDataField; @@ -349,7 +232,7 @@ public: * @param length the number of symbols in the message part in which the field is stored. */ SingleDataField(const string name, const string comment, - const string unit, const dataType_t dataType, const PartType partType, + const string unit, DataType* dataType, const PartType partType, const unsigned char length) : DataField(name, comment), m_unit(unit), m_dataType(dataType), m_partType(partType), @@ -361,11 +244,12 @@ public: virtual ~SingleDataField() {} // @copydoc - virtual SingleDataField* clone() = 0; + virtual SingleDataField* clone(); /** * Factory method for creating a new @a SingleDataField instance derived from a base type. - * @param typeNameStr the base type name string. + * @param fullIdStr the full ID string (including optional length suffix). + * @param idStr the ID string (excluding optional length suffix). * @param length the base type length, or 0 for default, or @a REMAIN_LEN for remainder within same message part. * @param name the field name. * @param comment the field comment. @@ -377,7 +261,7 @@ public: * @return @a RESULT_OK on success, or an error code. * Note: the caller needs to free the created instance. */ - static result_t create(const char* typeNameStr, const unsigned char length, + static result_t create(const char* fullIdStr, const char* idStr, const unsigned char length, const string name, const string comment, const string unit, const PartType partType, int divisor, map values, SingleDataField* &returnField); @@ -392,7 +276,7 @@ public: * Get whether this field is ignored. * @return whether this field is ignored. */ - bool isIgnored() const { return (m_dataType.flags & IGN) != 0; } + bool isIgnored() const { return m_dataType->isIgnored(); } /** * Get the message part in which the field is stored. @@ -401,13 +285,13 @@ public: PartType getPartType() const { return m_partType; } // @copydoc - virtual unsigned char getLength(PartType partType, unsigned char maxLength=MAX_LEN) { - if (partType != m_partType) { - return (unsigned char)0; - } - bool remainder = m_length==REMAIN_LEN && (m_dataType.flags & ADJ)!=0; - return remainder ? maxLength : m_length; - } + virtual unsigned char getLength(PartType partType, unsigned char maxLength=MAX_LEN); + + // @copydoc + virtual result_t derive(string name, string comment, + string unit, const PartType partType, + int divisor, map values, + vector& fields); /** * Get whether this field uses a full byte offset. @@ -415,11 +299,14 @@ public: * @return true if this field uses a full byte offset, false if this field * only consumes a part of a byte and a subsequent field may re-use the same offset. */ - virtual bool hasFullByteOffset(bool after) { return true; } + virtual bool hasFullByteOffset(bool after); // @copydoc virtual void dump(ostream& output); + // @copydoc + virtual bool hasField(const char* fieldName, bool numeric); + // @copydoc virtual result_t read(const PartType partType, SymbolString& data, unsigned char offset, @@ -438,35 +325,29 @@ public: protected: - /** - * Internal method for reading the numeric raw value from a @a SymbolString. - * @param input the unescaped @a SymbolString to read the binary value from. - * @param offset the offset in the @a SymbolString. - * @param value the variable in which to store the numeric raw value. - * @return @a RESULT_OK on success, or an error code. - */ - virtual result_t readRawValue(SymbolString& input, const unsigned char offset, unsigned int& value) = 0; - /** * Internal method for reading the field from a @a SymbolString. * @param input the unescaped @a SymbolString to read the binary value from. - * @param baseOffset the base offset in the @a SymbolString. + * @param offset the offset in the @a SymbolString. * @param output the ostringstream to append the formatted value to. * @param outputFormat the @a OutputFormat options to use. * @return @a RESULT_OK on success, or an error code. */ - virtual result_t readSymbols(SymbolString& input, const unsigned char baseOffset, - ostringstream& output, OutputFormat outputFormat) = 0; + virtual result_t readSymbols(SymbolString& input, + const unsigned char offset, + ostringstream& output, OutputFormat outputFormat); /** * Internal method for writing the field to a @a SymbolString. * @param input the @a istringstream to parse the formatted value from. * @param offset the offset in the @a SymbolString. * @param output the unescaped @a SymbolString to write the binary value to. - * @param length the variable in which to store the used length in bytes, or NULL. + * @param usedLength the variable in which to store the used length in bytes, or NULL. * @return @a RESULT_OK on success, or an error code. */ - virtual result_t writeSymbols(istringstream& input, const unsigned char offset, SymbolString& output, unsigned char* length) = 0; + virtual result_t writeSymbols(istringstream& input, + const unsigned char offset, + SymbolString& output, unsigned char* usedLength); protected: @@ -474,7 +355,7 @@ protected: const string m_unit; /** the data type definition. */ - const dataType_t m_dataType; + DataType* m_dataType; /** the message part in which the field is stored. */ const PartType m_partType; @@ -485,191 +366,10 @@ protected: }; -/** - * Base class for all string based data fields. - */ -class StringDataField : public SingleDataField -{ -public: - - /** - * Constructs a new instance. - * @param name the field name. - * @param comment the field comment. - * @param unit the value unit. - * @param dataType the data type definition. - * @param partType the message part in which the field is stored. - * @param length the number of symbols in the message part in which the field is stored, or @a REMAIN_LEN for remainder within same message part. - */ - StringDataField(const string name, const string comment, - const string unit, const dataType_t dataType, const PartType partType, - const unsigned char length) - : SingleDataField(name, comment, unit, dataType, partType, length) {} - - /** - * Destructor. - */ - virtual ~StringDataField() {} - - // @copydoc - virtual StringDataField* clone(); - - // @copydoc - virtual result_t derive(string name, string comment, - string unit, const PartType partType, - int divisor, map values, - vector& fields); - - // @copydoc - virtual bool hasField(const char* fieldName, bool numeric); - - // @copydoc - virtual void dump(ostream& output); - -protected: - - // @copydoc - virtual result_t readRawValue(SymbolString& input, const unsigned char offset, unsigned int& value); - - // @copydoc - virtual result_t readSymbols(SymbolString& input, const unsigned char baseOffset, - ostringstream& output, OutputFormat outputFormat); - - // @copydoc - virtual result_t writeSymbols(istringstream& input, const unsigned char offset, SymbolString& output, unsigned char* length); - -}; - - -/** - * Base class for all numeric data fields. - */ -class NumericDataField : public SingleDataField -{ -public: - - /** - * Constructs a new instance. - * @param name the field name. - * @param comment the field comment. - * @param unit the value unit. - * @param dataType the data type definition. - * @param partType the message part in which the field is stored. - * @param length the number of symbols in the message part in which the field is stored. - * @param bitCount the number of bits in the binary value (may be less than @a length * 8). - * @param bitOffset the offset to the first bit in the binary value. - */ - NumericDataField(const string name, const string comment, - const string unit, const dataType_t dataType, const PartType partType, - const unsigned char length, const unsigned char bitCount, const unsigned char bitOffset) - : SingleDataField(name, comment, unit, dataType, partType, length), - m_bitCount(bitCount), m_bitOffset(bitOffset) {} - - /** - * Destructor. - */ - virtual ~NumericDataField() {} - - // @copydoc - virtual NumericDataField* clone() = 0; - - // @copydoc - virtual bool hasFullByteOffset(bool after); - - // @copydoc - virtual bool hasField(const char* fieldName, bool numeric); - - // @copydoc - virtual void dump(ostream& output); - -protected: - - // @copydoc - virtual result_t readRawValue(SymbolString& input, const unsigned char offset, unsigned int& value); - - /** - * Internal method for writing the raw value to a @a SymbolString. - * @param value the raw value to write. - * @param offset the offset in the @a SymbolString. - * @param output the unescaped @a SymbolString to write the binary value to. - * @param length the variable in which to store the used length in bytes, or NULL. - * @return @a RESULT_OK on success, or an error code. - */ - result_t writeRawValue(unsigned int value, const unsigned char offset, SymbolString& output, unsigned char* length=NULL); - - /** the number of bits in the binary value. */ - const unsigned char m_bitCount; - - /** the offset to the first bit in the binary value. */ - const unsigned char m_bitOffset; - -}; - - -/** - * Base class for all numeric data fields with a number representation. - */ -class NumberDataField : public NumericDataField -{ -public: - - /** - * Constructs a new instance. - * @param name the field name. - * @param comment the field comment. - * @param unit the value unit. - * @param dataType the data type definition. - * @param partType the message part in which the field is stored. - * @param length the number of symbols in the message part in which the field is stored. - * @param bitCount the number of bits in the binary value (may be less than @a length * 8). - * @param divisor the extra divisor (negative for reciprocal) to apply on the value, or 1 for none. - */ - NumberDataField(const string name, const string comment, - const string unit, const dataType_t dataType, const PartType partType, - const unsigned char length, const unsigned char bitCount, - const int divisor); - - /** - * Destructor. - */ - virtual ~NumberDataField() {} - - // @copydoc - virtual NumberDataField* clone(); - - // @copydoc - virtual result_t derive(string name, string comment, - string unit, const PartType partType, - int divisor, map values, - vector& fields); - - // @copydoc - virtual void dump(ostream& output); - -protected: - - // @copydoc - virtual result_t readSymbols(SymbolString& input, const unsigned char baseOffset, - ostringstream& output, OutputFormat outputFormat); - - // @copydoc - virtual result_t writeSymbols(istringstream& input, const unsigned char offset, SymbolString& output, unsigned char* length); - -private: - - /** the combined divisor (negative for reciprocal) to apply on the value, or 1 for none. */ - const int m_divisor; - - /** the precision for formatting the value. */ - unsigned char m_precision; - -}; - - /** * A numeric data field with a list of value=text assignments and a string representation. */ -class ValueListDataField : public NumericDataField +class ValueListDataField : public SingleDataField { public: @@ -681,15 +381,12 @@ public: * @param dataType the data type definition. * @param partType the message part in which the field is stored. * @param length the number of symbols in the message part in which the field is stored. - * @param bitCount the number of bits in the binary value (may be less than @a length * 8). * @param values the value=text assignments. */ ValueListDataField(const string name, const string comment, - const string unit, const dataType_t dataType, const PartType partType, - const unsigned char length, const unsigned char bitCount, - const map values) - : NumericDataField(name, comment, unit, dataType, partType, length, bitCount, - (unsigned char)((dataType.bitCount < 8) ? dataType.divisorOrFirstBit : 0)), + const string unit, NumberDataType* dataType, const PartType partType, + const unsigned char length, const map values) + : SingleDataField(name, comment, unit, dataType, partType, length), m_values(values) {} /** @@ -712,11 +409,14 @@ public: protected: // @copydoc - virtual result_t readSymbols(SymbolString& input, const unsigned char baseOffset, + virtual result_t readSymbols(SymbolString& input, + const unsigned char offset, ostringstream& output, OutputFormat outputFormat); // @copydoc - virtual result_t writeSymbols(istringstream& input, const unsigned char offset, SymbolString& output, unsigned char* length); + virtual result_t writeSymbols(istringstream& input, + const unsigned char offset, + SymbolString& output, unsigned char* usedLength); private: diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp new file mode 100644 index 00000000..34565e8d --- /dev/null +++ b/src/lib/ebus/datatype.cpp @@ -0,0 +1,862 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2016 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "datatype.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +unsigned int parseInt(const char* str, int base, const unsigned int minValue, const unsigned int maxValue, result_t& result, unsigned int* length) { + char* strEnd = NULL; + + unsigned long int ret = strtoul(str, &strEnd, base); + + if (strEnd == NULL || strEnd == str || *strEnd != 0) { + result = RESULT_ERR_INVALID_NUM; // invalid value + return 0; + } + + if (minValue > ret || ret > maxValue) { + result = RESULT_ERR_OUT_OF_RANGE; // invalid value + return 0; + } + if (length != NULL) + *length = (unsigned int)(strEnd - str); + + result = RESULT_OK; + return (unsigned int)ret; +} + +int parseSignedInt(const char* str, int base, const int minValue, const int maxValue, result_t& result, unsigned int* length) { + char* strEnd = NULL; + + long int ret = strtol(str, &strEnd, base); + + if (strEnd == NULL || *strEnd != 0) { + result = RESULT_ERR_INVALID_NUM; // invalid value + return 0; + } + + if (minValue > ret || ret > maxValue) { + result = RESULT_ERR_OUT_OF_RANGE; // invalid value + return 0; + } + if (length != NULL) + *length = (unsigned int)(strEnd - str); + + result = RESULT_OK; + return (int)ret; +} + +void printErrorPos(ostream& out, vector::iterator begin, const vector::iterator end, vector::iterator pos, string filename, size_t lineNo, result_t result) +{ + if (pos > begin) + pos--; + out << "Error reading \"" << filename << "\" line " << setw(0) << dec << static_cast(lineNo) << " field " << static_cast(1+pos.base()-begin.base()) << " value \"" << *pos << "\": " << getResultCode(result) << endl; + out << "Erroneous item is here:" << endl; + bool first = true; + int cnt = 0; + while (begin != end) { + if (first) + first = false; + else { + out << FIELD_SEPARATOR; + if (begin <= pos) { + cnt++; + } + } + string item = *begin; + size_t i = item.find(TEXT_SEPARATOR); + if (i != string::npos) { + do { + item.replace(i, 1, TEXT_SEPARATOR_STR TEXT_SEPARATOR_STR); + i = item.find(TEXT_SEPARATOR, i+sizeof(TEXT_SEPARATOR_STR)+sizeof(TEXT_SEPARATOR_STR)); + } while (i != string::npos); + i = 0; + } else { + i = item.find(FIELD_SEPARATOR); + } + if (i!=string::npos) { + out << TEXT_SEPARATOR << item << TEXT_SEPARATOR; + if (begin < pos) + cnt += 2; + else if (begin == pos) + cnt++; + } else { + out << item; + } + if (begin < pos) + cnt += (unsigned int)(item).length(); + + begin++; + } + out << endl; + out << setw(cnt) << " " << setw(0) << "^" << endl; +} + + +bool DataType::dump(ostream& output, const unsigned char length) const +{ + output << m_id; + if (isAdjustableLength()) { + if (length==REMAIN_LEN) + output << ":*"; + else + output << ":" << static_cast(length); + } + output << FIELD_SEPARATOR; + return false; +} + + +result_t StringDataType::readRawValue(SymbolString& input, const unsigned char offset, + const unsigned char length, unsigned int& value) +{ + return RESULT_EMPTY; +} + +result_t StringDataType::readSymbols(SymbolString& input, const unsigned char baseOffset, + const unsigned char length, ostringstream& output, OutputFormat outputFormat) +{ + size_t start = 0, count = length; + int incr = 1; + unsigned char ch; + bool terminated = false; + if (count==REMAIN_LEN && input.size()>baseOffset) { + count = input.size()-baseOffset; + } else if (baseOffset + count > input.size()) { + return RESULT_ERR_INVALID_POS; + } + if (hasFlag(REV)) { // reverted binary representation (most significant byte first) + start = length - 1; + incr = -1; + } + + if (outputFormat & OF_JSON) + output << '"'; + for (size_t offset = start, i = 0; i < count; offset += incr, i++) { + ch = input[baseOffset + offset]; + if (m_isHex) { + if (i > 0) + output << ' '; + output << setw(2) << hex << setfill('0') << static_cast(ch); + } else { + if (ch < 0x20) + ch = (unsigned char)m_replacement; + if (ch == 0x00) + terminated = true; + else if (!terminated) + output << setw(0) << dec << static_cast(ch); + } + } + if (outputFormat & OF_JSON) + output << '"'; + + return RESULT_OK; +} + +result_t StringDataType::writeSymbols(istringstream& input, + unsigned char baseOffset, const unsigned char length, + SymbolString& output, unsigned char* usedLength) +{ + size_t start = 0, count = length; + bool remainder = count==REMAIN_LEN && hasFlag(ADJ); + int incr = 1; + unsigned int value = 0; + string token; + + if (hasFlag(REV)) { // reverted binary representation (most significant byte first) + start = length - 1; + incr = -1; + } + if (isIgnored() && !hasFlag(REQ)) { + if (remainder) { + count = 1; + } + for (size_t offset = start, i = 0; i < count; offset += incr, i++) { + output[baseOffset + offset] = (unsigned char)m_replacement; // fill up with replacement + } + if (usedLength!=NULL) + *usedLength = (unsigned char)count; + return RESULT_OK; + } + result_t result; + size_t i = 0, offset; + for (offset = start; i < count; offset += incr, i++) { + if (m_isHex) { + while (!input.eof() && input.peek() == ' ') + input.get(); + if (input.eof()) { // no more digits + value = m_replacement; // fill up with replacement + } else { + token.clear(); + token.push_back((unsigned char)input.get()); + if (input.eof()) + return RESULT_ERR_INVALID_NUM; // too short hex value + token.push_back((unsigned char)input.get()); + if (input.eof()) + return RESULT_ERR_INVALID_NUM; // too short hex value + + value = parseInt(token.c_str(), 16, 0, 0xff, result); + if (result != RESULT_OK) + return result; // invalid hex value + } + } else { + if (input.eof()) { + value = m_replacement; + } else { + value = input.get(); + if (input.eof() || value < 0x20) + value = m_replacement; + } + } + if (remainder && input.eof() && i > 0) { + if (value == 0x00 && !m_isHex) { + output[baseOffset + offset] = 0; + offset += incr; + } + break; + } + if (value > 0xff) + return RESULT_ERR_OUT_OF_RANGE; // value out of range + output[baseOffset + offset] = (unsigned char)value; + } + + if (!remainder && i < count) + return RESULT_ERR_EOF; // input too short + if (usedLength!=NULL) + *usedLength = (unsigned char)((offset-start)*incr); + return RESULT_OK; +} + + +result_t DateTimeDataType::readRawValue(SymbolString& input, const unsigned char offset, + const unsigned char length, unsigned int& value) +{ + return RESULT_EMPTY; +} + +result_t DateTimeDataType::readSymbols(SymbolString& input, const unsigned char baseOffset, + const unsigned char length, ostringstream& output, OutputFormat outputFormat) +{ + size_t start = 0, count = length; + int incr = 1; + unsigned char ch, last = 0, hour = 0; + if (count==REMAIN_LEN && input.size()>baseOffset) { + count = input.size()-baseOffset; + } else if (baseOffset + count > input.size()) { + return RESULT_ERR_INVALID_POS; + } + if (hasFlag(REV)) { // reverted binary representation (most significant byte first) + start = length - 1; + incr = -1; + } + + if (outputFormat & OF_JSON) + output << '"'; + for (size_t offset = start, i = 0; i < count; offset += incr, i++) { + if (length == 4 && i == 2 && m_isDate) + continue; // skip weekday in between + ch = input[baseOffset + offset]; + if (hasFlag(BCD) && (hasFlag(REQ) || ch != m_replacement)) { + if ((ch & 0xf0) > 0x90 || (ch & 0x0f) > 0x09) + return RESULT_ERR_OUT_OF_RANGE; // invalid BCD + ch = (unsigned char)((ch >> 4) * 10 + (ch & 0x0f)); + } + switch (m_isDate) + { + case true: + if (length == 2) { // number of days since 01.01.1900 + if (i == 0) { + break; + } + /*int days = last*256 + ch + 15020; // 01.01.1900 + int y = days(value < 100 ? value + 2000 : value) - 1900; + int l = (last==1 || last==2) ? 1 : 0; + int mjd = 14956 + lastLast + (int)((y-l)*365.25) + (int)((last+1+l*12)*30.6001); + int daysSinceSunday = (mjd+3) % 7; // Sun=0 + if ((m_dataType.flags & BCD) != 0) + output[baseOffset + offset - incr] = (unsigned char)((6+daysSinceSunday) % 7); // Sun=0x06 + else + output[baseOffset + offset - incr] = (unsigned char)(daysSinceSunday==0 ? 7 : daysSinceSunday); // Sun=0x07*/ + output << setw(2) << dec << setfill('0') << static_cast(ch) << "."; + } + if (!hasFlag(REQ) && ch == m_replacement) { + if (i + 1 != length) { + output << NULL_VALUE << "."; + break; + } + else if (last == m_replacement) { + output << NULL_VALUE; + break; + } + } + if (i + 1 == length) + output << (2000 + ch); + else if (ch < 1 || (i == 0 && ch > 31) || (i == 1 && ch > 12)) + return RESULT_ERR_OUT_OF_RANGE; // invalid date + else + output << setw(2) << dec << setfill('0') << static_cast(ch) << "."; + break; + case false: + if (!hasFlag(REQ) && ch == m_replacement) { + if (length == 1) { // truncated time + output << NULL_VALUE << ":" << NULL_VALUE; + break; + } + if (i > 0) + output << ":"; + output << NULL_VALUE; + break; + } + if (length == 1) { // truncated time + if (i == 0) { + ch = (unsigned char)(ch/(60/m_resolution)); // convert to hours + offset -= incr; // repeat for minutes + count++; + } + else + ch = (unsigned char)((ch % (60/m_resolution)) * m_resolution); // convert to minutes + } + if (i == 0) { + if (ch > 24) + return RESULT_ERR_OUT_OF_RANGE; // invalid hour + hour = ch; + } + else if (ch > 59 || (hour == 24 && ch > 0)) + return RESULT_ERR_OUT_OF_RANGE; // invalid time + if (i > 0) + output << ":"; + output << setw(2) << dec << setfill('0') << static_cast(ch); + break; + } + last = ch; + } + if (outputFormat & OF_JSON) + output << '"'; + + return RESULT_OK; +} + +result_t DateTimeDataType::writeSymbols(istringstream& input, + unsigned char baseOffset, const unsigned char length, + SymbolString& output, unsigned char* usedLength) +{ + size_t start = 0, count = length; + bool remainder = count==REMAIN_LEN && hasFlag(ADJ); + int incr = 1; + unsigned int value = 0, last = 0, lastLast = 0; + string token; + + if (hasFlag(REV)) { // reverted binary representation (most significant byte first) + start = length - 1; + incr = -1; + } + if (isIgnored() && !hasFlag(REQ)) { + if (remainder) { + count = 1; + } + for (size_t offset = start, i = 0; i < count; offset += incr, i++) { + output[baseOffset + offset] = (unsigned char)m_replacement; // fill up with replacement + } + if (usedLength!=NULL) + *usedLength = (unsigned char)count; + return RESULT_OK; + } + result_t result; + size_t i = 0, offset; + for (offset = start; i < count; offset += incr, i++) { + switch (m_isDate) + { + case true: + if (length == 4 && i == 2) + continue; // skip weekday in between + if (input.eof() || !getline(input, token, '.')) + return RESULT_ERR_EOF; // incomplete + if (!hasFlag(REQ) && strcmp(token.c_str(), NULL_VALUE) == 0) { + value = m_replacement; + break; + } + value = parseInt(token.c_str(), 10, 0, 2099, result); + if (result != RESULT_OK) { + return result; // invalid date part + } + if (i + 1 == length) { + if (length == 4) { + // calculate local week day + int y = (value < 100 ? value + 2000 : value) - 1900; + int l = (last==1 || last==2) ? 1 : 0; + int mjd = 14956 + lastLast + (int)((y-l)*365.25) + (int)((last+1+l*12)*30.6001); + int daysSinceSunday = (mjd+3) % 7; // Sun=0 + if (hasFlag(BCD)) + output[baseOffset + offset - incr] = (unsigned char)((6+daysSinceSunday) % 7); // Sun=0x06 + else + output[baseOffset + offset - incr] = (unsigned char)(daysSinceSunday==0 ? 7 : daysSinceSunday); // Sun=0x07 + } + if (value >= 2000) + value -= 2000; + if (value > 99) + return RESULT_ERR_OUT_OF_RANGE; // invalid year + } + else if (value < 1 || (i == 0 && value > 31) || (i == 1 && value > 12)) + return RESULT_ERR_OUT_OF_RANGE; // invalid date part + break; + case false: + if (input.eof() || !getline(input, token, LENGTH_SEPARATOR)) + return RESULT_ERR_EOF; // incomplete + if (!hasFlag(REQ) && strcmp(token.c_str(), NULL_VALUE) == 0) { + value = m_replacement; + if (length == 1) { // truncated time + if (i == 0) { + last = value; + offset -= incr; // repeat for minutes + count++; + continue; + } + if (last != m_replacement) + return RESULT_ERR_INVALID_NUM; // invalid truncated time minutes + } + break; + } + value = parseInt(token.c_str(), 10, 0, 59, result); + if (result != RESULT_OK) + return result; // invalid time part + if ((i == 0 && value > 24) || (i > 0 && (last == 24 && value > 0) )) + return RESULT_ERR_OUT_OF_RANGE; // invalid time part + if (length == 1) { // truncated time + if (i == 0) { + last = value; + offset -= incr; // repeat for minutes + count++; + continue; + } + + if ((value % m_resolution) != 0) + return RESULT_ERR_INVALID_NUM; // invalid truncated time minutes + value = (last * 60 + value)/m_resolution; + if (value > 24 * 6) + return RESULT_ERR_OUT_OF_RANGE; // invalid time + } + break; + } + if (remainder && input.eof() && i > 0) { + if (value == 0x00) { + output[baseOffset + offset] = 0; + offset += incr; + } + break; + } + lastLast = last; + last = value; + if (hasFlag(BCD) && (hasFlag(REQ) || value != m_replacement)) { + if (value > 99) + return RESULT_ERR_OUT_OF_RANGE; // invalid BCD + value = ((value / 10) << 4) | (value % 10); + } + if (value > 0xff) + return RESULT_ERR_OUT_OF_RANGE; // value out of range + output[baseOffset + offset] = (unsigned char)value; + } + + if (!remainder && i < count) + return RESULT_ERR_EOF; // input too short + if (usedLength!=NULL) + *usedLength = (unsigned char)((offset-start)*incr); + return RESULT_OK; +} + + +NumberDataType::NumberDataType(const char* id, const unsigned char bitCount, const unsigned short flags, const unsigned int replacement, + const unsigned int minValue, const unsigned int maxValue, const int divisor) + : DataType(id, bitCount, flags, replacement), m_minValue(minValue), m_maxValue(maxValue), m_divisor(divisor), m_firstBit(0), m_baseType(NULL) +{ + unsigned char precision = 0; + if (divisor > 1) + for (unsigned int exp = 1; exp < MAX_DIVISOR; exp *= 10, precision++) + if (exp >= (unsigned int)divisor) + break; + m_precision = precision; +} + +bool NumberDataType::dump(ostream& output, unsigned char length) const +{ + if (m_bitCount < 8) { + DataType::dump(output, m_bitCount); + } else { + DataType::dump(output, length); + } + if (m_baseType) { + if (m_baseType->m_divisor != m_divisor) { + output << static_cast(m_divisor / m_baseType->m_divisor); + return true; + } + } else if (m_divisor!=1) { + output << static_cast(m_divisor); + return true; + } + return false; +} + +result_t NumberDataType::derive(int divisor, unsigned char bitCount, NumberDataType* &derived) +{ + if (divisor == 0) { + divisor = 1; + } + if (bitCount <= 0) { + bitCount = m_bitCount; + } else if (bitCount!=m_bitCount && (m_bitCount%8)!=0) { // todo check bitCount%8=0 + if (bitCount+m_firstBit>8) { + return RESULT_ERR_OUT_OF_RANGE; + } + } + if (m_divisor != 1) { + if (divisor == 1) { + divisor = m_divisor; + } else if (divisor < 0) { + /*if ((divisor < 0) != (m_divisor < 0) { + return RESULT_ERR_INVALID_ARG; + }*/ + if (m_divisor > 1) + return RESULT_ERR_INVALID_ARG; + + divisor *= -m_divisor; + } else if (m_divisor < 0) { + if (divisor > 1) + return RESULT_ERR_INVALID_ARG; + + divisor *= -m_divisor; + } else + divisor *= m_divisor; + } + if (divisor==m_divisor && bitCount==m_bitCount) { + derived = this; + return RESULT_OK; + } + if (-MAX_DIVISOR > divisor || divisor > MAX_DIVISOR) + return RESULT_ERR_OUT_OF_RANGE; + + if (m_bitCount < 8) + derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, + m_firstBit, divisor); + else + derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, + m_minValue, m_maxValue, divisor); + derived->m_baseType = m_baseType ? m_baseType : this; + return RESULT_OK; +} + +result_t NumberDataType::readRawValue(SymbolString& input, + unsigned char baseOffset, const unsigned char length, + unsigned int& value) +{ + size_t start = 0, count = length; + int incr = 1; + unsigned char ch; + + if (baseOffset + length > input.size()) + return RESULT_ERR_INVALID_POS; // not enough data available + + if (hasFlag(REV)) { // reverted binary representation (most significant byte first) + start = length - 1; + incr = -1; + } + + value = 0; + unsigned int exp = 1; + for (size_t offset = start, i = 0; i < count; offset += incr, i++) { + ch = input[baseOffset + offset]; + if (hasFlag(BCD)) { + if (!hasFlag(REQ) && ch == (m_replacement & 0xff)) { + value = m_replacement; + return RESULT_OK; + } + if (!hasFlag(HCD)) { + if ((ch & 0xf0) > 0x90 || (ch & 0x0f) > 0x09) + return RESULT_ERR_OUT_OF_RANGE; // invalid BCD + + ch = (unsigned char)((ch >> 4) * 10 + (ch & 0x0f)); + } else if (ch > 0x63) + return RESULT_ERR_OUT_OF_RANGE; // invalid HCD + value += ch * exp; + exp *= 100; + } else { + value |= ch * exp; + exp <<= 8; + } + } + + if (m_firstBit > 0) { + value >>= m_firstBit; + } + if (m_bitCount < 8) { + value &= (1 << m_bitCount) - 1; + } + + return RESULT_OK; +} + +result_t NumberDataType::readSymbols(SymbolString& input, + const unsigned char baseOffset, const unsigned char length, + ostringstream& output, OutputFormat outputFormat) +{ + unsigned int value = 0; + int signedValue; + + result_t result = readRawValue(input, baseOffset, length, value); + if (result != RESULT_OK) { + return result; + } + output << setw(0) << dec; // initialize output + + if (!hasFlag(REQ) && value == m_replacement) { + if (outputFormat & OF_JSON) { + output << "null"; + } else { + output << NULL_VALUE; + } + return RESULT_OK; + } + + bool negative = hasFlag(SIG) && (value & (1 << (m_bitCount - 1))) != 0; + if (m_bitCount == 32) { + if (hasFlag(EXP)) { // IEEE 754 binary32 + float val; +#ifdef HAVE_DIRECT_FLOAT_FORMAT +# if HAVE_DIRECT_FLOAT_FORMAT == 2 + value = __builtin_bswap32(value); +# endif + unsigned char* pval = (unsigned char*)&value; + val = *((float*)pval); +#else + int exp = (value >> 23) & 0xff; // 8 bits, signed + if (exp == 0) { + val = 0.0; + } else { + exp -= 127; + unsigned int sig = value & ((1 << 23) - 1); + val = (1.0f + (float)(sig / exp2(23))) * (float)exp2(exp); + if (negative) { + val = -val; + } + } +#endif + if (val != 0.0) { + if (m_divisor < 0) { + val *= (float)-m_divisor; + } else if (m_divisor > 1) { + val /= (float)m_divisor; + } + } + if (m_precision != 0) { + output << fixed << setprecision(m_precision+6); + } else if (val == 0) { + output << fixed << setprecision(1); + } + output << static_cast(val); + return RESULT_OK; + } + if (!negative) { + if (m_divisor < 0) { + output << static_cast((float)value * (float)(-m_divisor)); + } else if (m_divisor <= 1) { + output << static_cast(value); + } else { + output << setprecision(m_precision) + << fixed << static_cast((float)value / (float)m_divisor); + } + return RESULT_OK; + } + signedValue = (int)value; // negative signed value + } else if (negative) { // negative signed value + signedValue = (int)value - (1 << m_bitCount); + } else { + signedValue = (int)value; + } + if (m_divisor < 0) { + output << static_cast((float)signedValue * (float)(-m_divisor)); + } else if (m_divisor <= 1) { + if (hasFlag(FIX) && hasFlag(BCD)) { + if (outputFormat & OF_JSON) { + output << '"'; + output << setw(length * 2) << setfill('0'); + output << static_cast(signedValue) << setw(0); + output << '"'; + return RESULT_OK; + } + output << setw(length * 2) << setfill('0'); + } + output << static_cast(signedValue) << setw(0); + } else { + output << setprecision(m_precision) + << fixed << static_cast((float)signedValue / (float)m_divisor); + } + return RESULT_OK; +} + +result_t NumberDataType::writeRawValue(unsigned int value, + const unsigned char baseOffset, const unsigned char length, + SymbolString& output, unsigned char* usedLength) +{ + size_t start = 0, count = length; + int incr = 1; + unsigned char ch; + + if (m_bitCount < 8 && (value & ~((1 << m_bitCount) - 1)) != 0) { + return RESULT_ERR_OUT_OF_RANGE; + } + if (m_firstBit > 0) { + value <<= m_firstBit; + } + + if (hasFlag(REV)) { // reverted binary representation (most significant byte first) + start = length - 1; + incr = -1; + } + + for (size_t offset = start, i = 0, exp = 1; i < count; offset += incr, i++) { + if (hasFlag(BCD)) { + if (!hasFlag(REQ) && value == m_replacement) + ch = m_replacement & 0xff; + else { + ch = (unsigned char)((value / exp) % 100); + if (!hasFlag(HCD)) + ch = (unsigned char)(((ch / 10) << 4) | (ch % 10)); + } + exp *= 100; + } else { + ch = (value / exp) & 0xff; + exp <<= 8; + } + if (offset == start && (m_bitCount % 8) != 0 && baseOffset + offset < output.size()) + output[baseOffset + offset] |= ch; + else + output[baseOffset + offset] = ch; + } + if (usedLength!=NULL) + *usedLength = length; + return RESULT_OK; +} + +result_t NumberDataType::writeSymbols(istringstream& input, + const unsigned char baseOffset, const unsigned char length, + SymbolString& output, unsigned char* usedLength) +{ + unsigned int value; + + const char* str = input.str().c_str(); + if (!hasFlag(REQ) && (isIgnored() || strcasecmp(str, NULL_VALUE) == 0)) { + value = m_replacement; // replacement value + } else if (str == NULL || *str == 0) { + return RESULT_ERR_EOF; // input too short + } else if (hasFlag(EXP)) { // IEEE 754 binary32 + char* strEnd = NULL; + double dvalue = strtod(str, &strEnd); + if (strEnd == NULL || strEnd == str || *strEnd != 0) { + return RESULT_ERR_INVALID_NUM; // invalid value + } + if (m_divisor < 0) + dvalue /= -m_divisor; + else if (m_divisor > 1) + dvalue *= m_divisor; +#ifdef HAVE_DIRECT_FLOAT_FORMAT + float val = (float)dvalue; + unsigned char* pval = (unsigned char*)&val; + value = *((int32_t*)pval); +# if HAVE_DIRECT_FLOAT_FORMAT == 2 + value = __builtin_bswap32(value); +# endif +#else + value = 0; + if (dvalue != 0) { + bool negative = dvalue < 0; + if (negative) { + dvalue = -dvalue; + } + int exp = ilogb(dvalue); + if (exp < -126 || exp > 127) + return RESULT_ERR_INVALID_NUM; // invalid value + dvalue = scalbln(dvalue, -exp) - 1.0; + unsigned int sig = (unsigned int)(dvalue * exp2(23)); + exp += 127; + value = (exp << 23) | sig; + if (negative) { + value |= 0x80000000; + } + } +#endif + } else { + char* strEnd = NULL; + if (m_divisor == 1) { + if (hasFlag(SIG)) { + long int signedValue = strtol(str, &strEnd, 10); + if (signedValue < 0 && m_bitCount != 32) + value = (unsigned int)(signedValue + (1 << m_bitCount)); + else + value = (unsigned int)signedValue; + } + else + value = (unsigned int)strtoul(str, &strEnd, 10); + if (strEnd == NULL || strEnd == str || (*strEnd != 0 && *strEnd != '.')) + return RESULT_ERR_INVALID_NUM; // invalid value + } else { + char* strEnd = NULL; + double dvalue = strtod(str, &strEnd); + if (strEnd == NULL || strEnd == str || *strEnd != 0) + return RESULT_ERR_INVALID_NUM; // invalid value + if (m_divisor < 0) + dvalue = round(dvalue / -m_divisor); + else + dvalue = round(dvalue * m_divisor); + if (hasFlag(SIG)) { + if (dvalue < -(1LL << (8 * length)) || dvalue >= (1LL << (8 * length))) + return RESULT_ERR_OUT_OF_RANGE; // value out of range + if (dvalue < 0 && m_bitCount != 32) + value = (int)(dvalue + (1 << m_bitCount)); + else + value = (int)dvalue; + } else { + if (dvalue < 0.0 || dvalue >= (1LL << (8 * length))) + return RESULT_ERR_OUT_OF_RANGE; // value out of range + value = (unsigned int)dvalue; + } + } + + if (hasFlag(SIG)) { // signed value + if ((value & (1 << (m_bitCount - 1))) != 0) { // negative signed value + if (value < m_minValue) + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + else if (value > m_maxValue) + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + else if (value < m_minValue || value > m_maxValue) + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + + return writeRawValue(value, baseOffset, length, output, usedLength); +} diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h new file mode 100644 index 00000000..c29a0f96 --- /dev/null +++ b/src/lib/ebus/datatype.h @@ -0,0 +1,490 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2016 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIBEBUS_DATATYPE_H_ +#define LIBEBUS_DATATYPE_H_ + +#include "symbol.h" +#include "result.h" +#include "filereader.h" +#include +#include +#include +#include +#include +#include + +/** @file datatype.h + * Classes, functions, and constants related to decoding/encoding of symbols + * on the eBUS to/from readable values and registry of data types. + * + * A @a DataType is one of @a StringDataType, @a DateTimeDataType, + * @a NumberDataType, or @a BitsDataType. + * + * The particular eBUS specification types like e.g. @a D1C are defined by + * using one of these base data types with certain flags, such as #BCD, #FIX, + * #REQ, see @a DataType. + * + * The list of available base data types is stored in #baseTypes and can easily + * be extended if necessary. + * + * Each @a DataType can be converted from a @a SymbolString to an + * @a ostringstream (see @a DataType#readSymbols() methods) or vice versa from + * an @a istringstream to a @a SymbolString (see @a DataType#writeSymbols()). + */ + +using namespace std; + +/** the separator character used between base type name and length (in CSV only). */ +#define LENGTH_SEPARATOR ':' + +/** the replacement string for undefined values (in UI and CSV). */ +#define NULL_VALUE "-" + +/** the separator character used between fields (in UI only). */ +#define UI_FIELD_SEPARATOR ';' + +/** the maximum allowed position within master or slave data. */ +#define MAX_POS 24 + +/** the maximum allowed field length. */ +#define MAX_LEN 31 + +/** the field length indicating remainder of input. */ +#define REMAIN_LEN 255 + +/** the maximum divisor value. */ +#define MAX_DIVISOR 1000000000 + +/** the maximum value for value lists. */ +#define MAX_VALUE (0xFFFFFFFFu) + + +/** the type for data output format options. */ +typedef int OutputFormat; + +/* the bit flags for @a OutputFormat. */ +static const unsigned int OF_VERBOSE = 0x01; //!< verbose format (names, values, units, and comments). +static const unsigned int OF_NUMERIC = 0x02; //!< numeric format (keep numeric value of value=name pairs). +static const unsigned int OF_JSON = 0x04; //!< JSON format. + +/** the message part in which a data field is stored. */ +enum PartType { + pt_any, //!< stored in any data (master or slave) + pt_masterData, //!< stored in master data + pt_slaveData, //!< stored in slave data +}; + +/* flags for @a DataType. */ +static const unsigned int ADJ = 0x01; //!< adjustable length, bitCount is maximum length +static const unsigned int BCD = 0x02; //!< binary representation is BCD +static const unsigned int REV = 0x04; //!< reverted binary representation (most significant byte first) +static const unsigned int SIG = 0x08; //!< signed value +static const unsigned int IGN = 0x10; //!< ignore value during read and write +static const unsigned int FIX = 0x20; //!< fixed width formatting +static const unsigned int REQ = 0x40;//!< value may not be NULL +static const unsigned int HCD = 0x80; //!< binary representation is hex converted to decimal and interpreted as 2 digits (also requires #BCD) +static const unsigned int EXP = 0x100; //!< exponential numeric representation +static const unsigned int LAST_DATATYPE_FLAG = EXP; //!< the last flag value used by @a DataType and children + + +/** + * Parse an unsigned int value. + * @param str the string to parse. + * @param base the numerical base. + * @param minValue the minimum resulting value. + * @param maxValue the maximum resulting value. + * @param result the variable in which to store an error code when parsing failed or the value is out of bounds. + * @param length the optional variable in which to store the number of read characters. + * @return the parsed value. + */ +unsigned int parseInt(const char* str, int base, const unsigned int minValue, const unsigned int maxValue, result_t& result, unsigned int* length=NULL); + +/** + * Parse a signed int value. + * @param str the string to parse. + * @param base the numerical base. + * @param minValue the minimum resulting value. + * @param maxValue the maximum resulting value. + * @param result the variable in which to store an error code when parsing failed or the value is out of bounds. + * @param length the optional variable in which to store the number of read characters. + * @return the parsed value. + */ +int parseSignedInt(const char* str, int base, const int minValue, const int maxValue, result_t& result, unsigned int* length=NULL); + +/** + * Print the error position of the iterator. + * @param out the @a ostream to print to. + * @param begin the iterator to the beginning of the items. + * @param end the iterator to the end of the items. + * @param pos the iterator with the erroneous position. + * @param filename the name of the file being read. + * @param lineNo the current line number in the file being read. + * @param result the result code. + */ +void printErrorPos(ostream& out, vector::iterator begin, const vector::iterator end, vector::iterator pos, string filename, size_t lineNo, result_t result); + +/** + * Base class for all kinds of data types. + */ +class DataType +{ +public: + + /** + * Constructs a new instance. + * @param id the type identifier. + * @param bitCount the number of bits (maximum length if #ADJ flag is set, must be multiple of 8 with flag #BCD). + * @param flags the combination of flags (like #BCD). + * @param replacement the replacement value (fill-up value for @a StringDataType, no replacement if equal to @a NumberDataType#minValue). + */ + DataType(const char* id, const unsigned char bitCount, const unsigned short flags, const unsigned int replacement) + : m_id(id), m_bitCount(bitCount), m_flags(flags), m_replacement(replacement) {} + + /** + * Destructor. + */ + virtual ~DataType() {} + + /** + * @return the type identifier. + */ + string getId() const { return m_id; } + + /** + * @return the number of bits (maximum length if #ADJ flag is set). + */ + unsigned char getBitCount() const { return m_bitCount; } + + /** + * Check whether a flag is set. + * @param flag the flag to check (like #BCD). + * @return whether the flag is set. + */ + bool hasFlag(const unsigned int flag) const { return (m_flags & flag) != 0; } + + /** + * @return whether this field is ignored. + */ + bool isIgnored() const { return hasFlag(IGN); } + + /** + * @return whether this field has an . + */ + bool isAdjustableLength() const { return hasFlag(ADJ); } + + /** + * @return the replacement value (fill-up value for @a StringDataType, no replacement if equal to @a NumberDataType#minValue). + */ + unsigned int getReplacement() const { return m_replacement; } + + /** + * Dump the type identifier with the specified length and optionally the + * divisor to the output. + * @param output the @a ostream to dump to. + * @param length the number of symbols to read/write. + * @return true when a non-default divisor was written to the output. + */ + virtual bool dump(ostream& output, const unsigned char length) const; + + /** + * Internal method for reading the numeric raw value from a @a SymbolString. + * @param input the unescaped @a SymbolString to read the binary value from. + * @param offset the offset in the @a SymbolString. + * @param length the number of symbols to read. + * @param value the variable in which to store the numeric raw value. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t readRawValue(SymbolString& input, + const unsigned char offset, const unsigned char length, + unsigned int& value) = 0; + + /** + * Internal method for reading the field from a @a SymbolString. + * @param input the unescaped @a SymbolString to read the binary value from. + * @param offset the offset in the @a SymbolString. + * @param length the number of symbols to read. + * @param output the ostringstream to append the formatted value to. + * @param outputFormat the @a OutputFormat options to use. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t readSymbols(SymbolString& input, + const unsigned char offset, const unsigned char length, + ostringstream& output, OutputFormat outputFormat) = 0; + + /** + * Internal method for writing the field to a @a SymbolString. + * @param input the @a istringstream to parse the formatted value from. + * @param offset the offset in the @a SymbolString. + * @param length the number of symbols to write, or @a REMAIN_LEN. + * @param output the unescaped @a SymbolString to write the binary value to. + * @param usedLength the variable in which to store the used length in bytes, or NULL. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t writeSymbols(istringstream& input, + const unsigned char offset, const unsigned char length, + SymbolString& output, unsigned char* usedLength) = 0; + +protected: + + /** the type identifier. */ + const char* m_id; + + /** the number of bits (maximum length if #ADJ flag is set, must be multiple of 8 with flag #BCD). */ + const unsigned char m_bitCount; + + /** the combination of flags (like #BCD). */ + const unsigned short m_flags; + + /** the replacement value (fill-up value for @a StringDataType, no replacement if equal to @a NumberDataType#minValue). */ + const unsigned int m_replacement; + +}; + +/** + * A string based @a DataType. + */ +class StringDataType : public DataType +{ +public: + + /** + * Constructs a new instance. + * @param id the type identifier. + * @param bitCount the number of bits (maximum length if #ADJ flag is set, must be multiple of 8 with flag #BCD). + * @param flags the combination of flags (like #BCD). + * @param replacement the replacement value (fill-up value). + * @param isHex true for hex digits instead of characters. + */ + StringDataType(const char* id, const unsigned char bitCount, const unsigned short flags, + const unsigned int replacement, bool isHex=false) + : DataType(id, bitCount, flags, replacement), m_isHex(isHex) {} + + /** + * Destructor. + */ + virtual ~StringDataType() {} + + // @copydoc + virtual result_t readRawValue(SymbolString& input, + const unsigned char offset, const unsigned char length, + unsigned int& value); + + // @copydoc + virtual result_t readSymbols(SymbolString& input, + const unsigned char offset, const unsigned char length, + ostringstream& output, OutputFormat outputFormat); + + // @copydoc + virtual result_t writeSymbols(istringstream& input, + const unsigned char offset, const unsigned char length, + SymbolString& output, unsigned char* usedLength); + +private: + + /** true for hex digits instead of characters. */ + const bool m_isHex; + +}; + +/** + * A date/time based @a DataType. + */ +class DateTimeDataType : public DataType +{ +public: + + /** + * Constructs a new instance. + * @param id the type identifier. + * @param bitCount the number of bits (maximum length if #ADJ flag is set, must be multiple of 8 with flag #BCD). + * @param flags the combination of flags (like #BCD). + * @param replacement the replacement value. + * @param isDate true for date, false for time. + * @param resolution the the resolution in minutes for time types, or 1. + */ + DateTimeDataType(const char* id, const unsigned char bitCount, const unsigned short flags, const unsigned int replacement, + const bool isDate, const short resolution) + : DataType(id, bitCount, flags, replacement), m_isDate(isDate), m_resolution(resolution) {} + + /** + * Destructor. + */ + virtual ~DateTimeDataType() {} + + /** + * @return true for date, false for time. + */ + bool isDate() const { return m_isDate; } + + /** + * @return the resolution in minutes for time types, or 1. + */ + short getResolution() const { return m_resolution; } + + // @copydoc + virtual result_t readRawValue(SymbolString& input, + const unsigned char offset, const unsigned char length, + unsigned int& value); + + // @copydoc + virtual result_t readSymbols(SymbolString& input, + const unsigned char offset, const unsigned char length, + ostringstream& output, OutputFormat outputFormat); + + // @copydoc + virtual result_t writeSymbols(istringstream& input, + const unsigned char offset, const unsigned char length, + SymbolString& output, unsigned char* usedLength); + +private: + + /** true for date, false for time. */ + const bool m_isDate; + + /** the resolution in minutes for time types, or 1. */ + const short m_resolution; + +}; + + +/** + * Base class for numeric @a DataType. + */ +class NumberDataType : public DataType +{ +public: + + /** + * Constructs a new instance for multiple of 8 bits. + * @param id the type identifier. + * @param bitCount the number of bits (maximum length if #ADJ flag is set, must be multiple of 8 with flag #BCD). + * @param flags the combination of flags (like #BCD). + * @param replacement the replacement value (no replacement if equal to #minValue). + * @param minValue the minimum raw value. + * @param maxValue the maximum raw value. + * @param divisor the divisor (negative for reciprocal). + */ + NumberDataType(const char* id, const unsigned char bitCount, const unsigned short flags, const unsigned int replacement, + const unsigned int minValue, const unsigned int maxValue, const int divisor); + + /** + * Constructs a new instance for less than 8 bits. + * @param id the type identifier. + * @param bitCount the number of bits (maximum length if #ADJ flag is set). + * @param flags the combination of flags (like #ADJ, must not include flag #BCD). + * @param replacement the replacement value (no replacement if zero). + * @param firstBit the offset to the first bit. + * @param divisor the divisor (negative for reciprocal). + */ + NumberDataType(const char* id, const unsigned char bitCount, const unsigned short flags, const unsigned int replacement, + const short firstBit, const int divisor) + : DataType(id, bitCount, flags, replacement), m_minValue(0), m_maxValue((1<