diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 515a223e..86522f09 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -27,7 +27,7 @@ using namespace std; static const dataType_t stringDataType = { - "STR",MAX_POS*8,bt_str, ADJ, ' ', 1, MAX_POS, 0 // >= 1 byte character string filled up with space + "STR",MAX_LEN*8,bt_str, ADJ, ' ', 1, 0, 0 // >= 1 byte character string filled up with space }; static const dataType_t pinDataType = { @@ -40,9 +40,9 @@ static const dataType_t uchDataType = { /** the known data field types. */ static const dataType_t dataTypes[] = { - {"IGN",MAX_POS*8,bt_str, IGN|ADJ, 0, 1, MAX_POS, 0}, // >= 1 byte ignored data + {"IGN",MAX_LEN*8,bt_str, IGN|ADJ, 0, 1, 0, 0}, // >= 1 byte ignored data stringDataType, - {"HEX",MAX_POS*8,bt_hexstr, ADJ, 0, 2, 47, 0}, // >= 1 byte hex digit string, usually separated by space, e.g. 0a 1b 2c 3d + {"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) @@ -190,7 +190,8 @@ result_t DataField::create(vector::iterator& it, const vector::iterator end, DataFieldTemplates* templates, DataField*& returnField, const bool isWriteMessage, - const bool isTemplate, const bool isBroadcastOrMasterDestination) + const bool isTemplate, const bool isBroadcastOrMasterDestination, + const unsigned char maxFieldLength) { vector fields; string firstName, firstComment; @@ -312,7 +313,7 @@ result_t DataField::create(vector::iterator& it, if (pos == string::npos) length = 0; // no length specified else { - length = (unsigned char)parseInt(token.substr(pos+1).c_str(), 10, 1, MAX_POS, result); + length = (unsigned char)parseInt(token.substr(pos+1).c_str(), 10, 1, maxFieldLength, result); if (result != RESULT_OK) break; } @@ -364,7 +365,7 @@ result_t SingleDataField::create(const char* typeNameStr, const unsigned char le const PartType partType, int divisor, map values, SingleDataField* &returnField) { - for (size_t i = 0; i < sizeof(dataTypes) / sizeof(dataType_t); i++) { + for (size_t i = 0; i < sizeof(dataTypes) / sizeof(dataType_t); i++) { // TODO use a map const dataType_t* dataType = &dataTypes[i]; if (strcasecmp(typeNameStr, dataType->name) == 0) { unsigned char bitCount = dataType->bitCount; @@ -403,7 +404,7 @@ result_t SingleDataField::create(const char* typeNameStr, const unsigned char le case bt_num: if (values.empty() && (dataType->flags & DAY) != 0) { for (unsigned int i = 0; i < sizeof(dayNames) / sizeof(dayNames[0]); i++) - values[dataType->minValueOrLength + i] = dayNames[i]; + values[dataType->minValue + i] = dayNames[i]; } if (values.empty() || (dataType->flags & LST) == 0) { if (divisor == 0) @@ -432,8 +433,7 @@ result_t SingleDataField::create(const char* typeNameStr, const unsigned char le returnField = new NumberDataField(name, comment, unit, *dataType, partType, byteCount, bitCount, divisor); return RESULT_OK; } - if (values.begin()->first < dataType->minValueOrLength - || values.rbegin()->first > dataType->maxValueOrLength) + if (values.begin()->first < dataType->minValue || values.rbegin()->first > dataType->maxValue) return RESULT_ERR_OUT_OF_RANGE; if (divisor != 0) @@ -484,13 +484,12 @@ result_t SingleDataField::read(const PartType partType, } return RESULT_EMPTY; } - return readRawValue(data, offset, output); } result_t SingleDataField::read(const PartType partType, SymbolString& data, unsigned char offset, - ostringstream& output, OutputFormat outputFormat, + ostringstream& output, OutputFormat outputFormat, signed char outputIndex, bool leadingSeparator, const char* fieldName, signed char fieldIndex) { if (partType != m_partType) @@ -514,18 +513,20 @@ result_t SingleDataField::read(const PartType partType, return RESULT_EMPTY; } - if (leadingSeparator) { - if (outputFormat & OF_JSON) + if (outputFormat & OF_JSON) { + if (leadingSeparator) output << ","; + if (outputIndex>=0 || m_name.empty()) + output << "\n \"" << static_cast(outputIndex<0?0:outputIndex) << "\": {\"name\": \"" << m_name << "\"" << ", \"value\": "; else + output << "\n \"" << m_name << "\": {\"value\": "; + } else { + if (leadingSeparator) output << UI_FIELD_SEPARATOR; + if (outputFormat & OF_VERBOSE) + output << m_name << "="; } - if (outputFormat & OF_JSON) - output << "\n {\"name\": \"" << m_name << "\"" << ", \"value\": "; - else if (outputFormat & OF_VERBOSE) - output << m_name << "="; - result_t result = readSymbols(data, offset, output, outputFormat); if (result != RESULT_OK) return result; @@ -626,7 +627,6 @@ result_t StringDataField::readSymbols(SymbolString& input, const unsigned char b if (baseOffset + m_length > 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; @@ -1158,13 +1158,13 @@ result_t NumberDataField::writeSymbols(istringstream& input, if ((m_dataType.flags & SIG) != 0) { // signed value if ((value & (1 << (m_bitCount - 1))) != 0) { // negative signed value - if (value < m_dataType.minValueOrLength) + if (value < m_dataType.minValue) return RESULT_ERR_OUT_OF_RANGE; // value out of range } - else if (value > m_dataType.maxValueOrLength) + else if (value > m_dataType.maxValue) return RESULT_ERR_OUT_OF_RANGE; // value out of range } - else if (value < m_dataType.minValueOrLength || value > m_dataType.maxValueOrLength) + else if (value < m_dataType.minValue || value > m_dataType.maxValue) return RESULT_ERR_OUT_OF_RANGE; // value out of range } @@ -1194,8 +1194,7 @@ 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.minValueOrLength - || values.rbegin()->first > m_dataType.maxValueOrLength) + if (values.begin()->first < m_dataType.minValue || values.rbegin()->first > m_dataType.maxValue) return RESULT_ERR_INVALID_ARG; // cannot use divisor != 1 for value list field } else @@ -1364,7 +1363,7 @@ result_t DataFieldSet::derive(string name, string comment, return RESULT_ERR_INVALID_ARG; // value list not allowed in set derive bool first = true; for (vector::iterator it = m_fields.begin(); it < m_fields.end(); it++) { - result_t result = (*it)->derive("", first?comment:"", first?unit:"", partType, divisor, values, fields); + result_t result = (*it)->derive("", first?comment:"", first?unit:"", partType, divisor, values, fields); if (result != RESULT_OK) return result; first = false; @@ -1437,19 +1436,23 @@ result_t DataFieldSet::read(const PartType partType, result_t DataFieldSet::read(const PartType partType, SymbolString& data, unsigned char offset, - ostringstream& output, OutputFormat outputFormat, + ostringstream& output, OutputFormat outputFormat, signed char outputIndex, bool leadingSeparator, const char* fieldName, signed char fieldIndex) { bool previousFullByteOffset = true, found = false, findFieldIndex = fieldName != NULL && fieldIndex >= 0; + if (!m_uniqueNames && outputIndex<0) + outputIndex = 0; for (vector::iterator it = m_fields.begin(); it < m_fields.end(); it++) { SingleDataField* field = *it; - if (partType != pt_any && field->getPartType() != partType) + if (partType != pt_any && field->getPartType() != partType) { + if (outputIndex>=0 && !field->isIgnored()) + outputIndex++; continue; - + } if (!previousFullByteOffset && !field->hasFullByteOffset(false)) offset--; - result_t result = field->read(partType, data, offset, output, outputFormat, leadingSeparator, fieldName, fieldIndex); + result_t result = field->read(partType, data, offset, output, outputFormat, outputIndex, leadingSeparator, fieldName, fieldIndex); if (result < RESULT_OK) return result; @@ -1468,6 +1471,8 @@ result_t DataFieldSet::read(const PartType partType, } fieldIndex--; } + if (outputIndex>=0 && !field->isIgnored()) + outputIndex++; } if (!found) { diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index 9caf758c..9082f64e 100644 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -78,7 +78,7 @@ 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. +static const unsigned int OF_JSON = 0x04; //!< JSON format. /** the message part in which a data field is stored. */ enum PartType { @@ -110,19 +110,22 @@ static const unsigned int HCD = 0x200; //!< binary representation is hex convert /** 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 #minValueOrLength for #bt_num) - const unsigned int minValueOrLength; //!< minimum binary value (minimum length of string for @a StringDataField) - const unsigned int maxValueOrLength; //!< maximum binary value (maximum length of string for @a StringDataField) - const short divisorOrFirstBit; //!< #bt_num: divisor (negative for reciprocal) or offset to first bit (if (#bitCount%8)!=0) + 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 binary value (ignored for @a StringDataField) + const unsigned int maxValue; //!< maximum binary 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 + /** * Parse an unsigned int value. * @param str the string to parse. @@ -198,13 +201,15 @@ public: * @param isWriteMessage whether the field is part of a write message (default false). * @param isTemplate true for creating a template @a DataField. * @param isBroadcastOrMasterDestination true if the destination bus address is @a BRODCAST or a master address. + * @param maxFieldLength the maximum allowed length of a single field (default @a MAX_POS). * @return @a RESULT_OK on success, or an error code. * Note: the caller needs to free the created instance. */ static result_t create(vector::iterator& it, const vector::iterator end, DataFieldTemplates* templates, DataField*& returnField, const bool isWriteMessage, - const bool isTemplate, const bool isBroadcastOrMasterDestination); + const bool isTemplate, const bool isBroadcastOrMasterDestination, + const unsigned char maxFieldLength=MAX_POS); /** * Dump the @a string optionally embedded in @a TEXT_SEPARATOR to the output. @@ -287,6 +292,7 @@ public: * @param offset the additional offset to add for reading binary data. * @param output the @a ostringstream to append the formatted value to. * @param outputFormat the @a OutputFormat options to use. + * @param outputIndex the optional index of the field when using an indexed output format, or -1. * @param leadingSeparator whether to prepend a separator before the formatted value. * @param fieldName the optional name of a field to limit the output to. * @param fieldIndex the optional index of the named field to limit the output to, or -1. @@ -296,7 +302,7 @@ public: */ virtual result_t read(const PartType partType, SymbolString& data, unsigned char offset, - ostringstream& output, OutputFormat outputFormat, + ostringstream& output, OutputFormat outputFormat, signed char outputIndex=-1, bool leadingSeparator=false, const char* fieldName=NULL, signed char fieldIndex=-1) = 0; /** @@ -414,7 +420,7 @@ public: // @copydoc virtual result_t read(const PartType partType, SymbolString& data, unsigned char offset, - ostringstream& output, OutputFormat outputFormat, + ostringstream& output, OutputFormat outputFormat, signed char outputIndex=-1, bool leadingSeparator=false, const char* fieldName=NULL, signed char fieldIndex=-1); // @copydoc @@ -738,7 +744,20 @@ public: DataFieldSet(const string name, const string comment, const vector fields) : DataField(name, comment), - m_fields(fields) {} + m_fields(fields) + { + bool uniqueNames = true; + map names; + for (vector::const_iterator it=fields.begin(); it!=fields.end(); it++) { + string name = (*it)->getName(); + if (name.empty() || names.find(name)!=names.end()) { + uniqueNames = false; + break; + } + names[name] = name; + } + m_uniqueNames = uniqueNames; + } /** * Destructor. @@ -791,7 +810,7 @@ public: // @copydoc virtual result_t read(const PartType partType, SymbolString& data, unsigned char offset, - ostringstream& output, OutputFormat outputFormat, + ostringstream& output, OutputFormat outputFormat, signed char outputIndex=-1, bool leadingSeparator=false, const char* fieldName=NULL, signed char fieldIndex=-1); // @copydoc @@ -807,6 +826,9 @@ private: /** the @a vector of @a SingleDataField instances part of this set. */ vector m_fields; + /** whether all fields have a unique name. */ + bool m_uniqueNames; + }; diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 23165d56..064c6e3e 100644 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -119,10 +119,10 @@ int main() {"x,,tth,2", "", "", "", "c"}, {"x,,bdy", "Mon", "10fe07000300", "00", ""}, {"x,,bdy", "Sun", "10fe07000306", "00", ""}, - {"x,,bdy", "", "10fe07000308", "00", "rw"}, + {"x,,bdy", "8", "10fe07000308", "00", "w"}, {"x,,hdy", "Mon", "10fe07000301", "00", ""}, {"x,,hdy", "Sun", "10fe07000307", "00", ""}, - {"x,,hdy", "", "10fe07000308", "00", "rw"}, + {"x,,hdy", "8", "10fe07000308", "00", "w"}, {"x,,bcd", "26", "10feffff0126", "00", ""}, {"x,,bcd", "0", "10feffff0100", "00", ""}, {"x,,bcd", "99", "10feffff0199", "00", ""}, @@ -273,10 +273,11 @@ int main() {"x,,bi3:2,0=off;1=on","1", "10feffff0108", "00", "n"}, {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","x=on ja/nein [Wahrheitswert]", "10feffff0108", "00", "v"}, {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","x=1 ja/nein [Wahrheitswert]", "10feffff0108", "00", "vn"}, - {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n {\"name\": \"x\", \"value\": \"on\"}", "10feffff0108", "00", "j"}, - {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n {\"name\": \"x\", \"value\": \"on\", \"unit\": \"ja/nein\", \"comment\": \"Wahrheitswert\"}", "10feffff0108", "00", "vj"}, - {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n {\"name\": \"x\", \"value\": 1}", "10feffff0108", "00", "nj"}, - {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n {\"name\": \"x\", \"value\": 1, \"unit\": \"ja/nein\", \"comment\": \"Wahrheitswert\"}", "10feffff0108", "00", "vnj"}, + {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n \"x\": {\"value\": \"on\"}", "10feffff0108", "00", "j"}, + {",,bi3:2,0=off;1=on,ja/nein,Wahrheitswert", "\n \"0\": {\"name\": \"\", \"value\": \"on\"}", "10feffff0108", "00", "j"}, + {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n \"x\": {\"value\": \"on\", \"unit\": \"ja/nein\", \"comment\": \"Wahrheitswert\"}", "10feffff0108", "00", "vj"}, + {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n \"x\": {\"value\": 1}", "10feffff0108", "00", "nj"}, + {"x,,bi3:2,0=off;1=on,ja/nein,Wahrheitswert","\n \"x\": {\"value\": 1, \"unit\": \"ja/nein\", \"comment\": \"Wahrheitswert\"}", "10feffff0108", "00", "vnj"}, {"x,,uch,1=test;2=high;3=off;0x10=on","on","10feffff0110", "00", ""}, {"x,s,uch","3","1050ffff00", "0103", ""}, {"x,,d2b,,°C,Aussentemperatur","x=18.004 °C [Aussentemperatur]","10fe0700090112", "00", "v"}, @@ -383,9 +384,9 @@ int main() if (result != RESULT_OK) { cout << " parse \"" << sstr.getDataStr(true, false).substr(0, 2) << "\" error: " << getResultCode(result) << endl; } - result = fields->read(pt_masterData, mstr, 0, output, (verbose?OF_VERBOSE:0)|(numeric?OF_NUMERIC:0)|(json?OF_JSON:0), false); + result = fields->read(pt_masterData, mstr, 0, output, (verbose?OF_VERBOSE:0)|(numeric?OF_NUMERIC:0)|(json?OF_JSON:0), -1, false); if (result >= RESULT_OK) { - result = fields->read(pt_slaveData, sstr, 0, output, (verbose?OF_VERBOSE:0)|(numeric?OF_NUMERIC:0)|(json?OF_JSON:0), !output.str().empty()); + result = fields->read(pt_slaveData, sstr, 0, output, (verbose?OF_VERBOSE:0)|(numeric?OF_NUMERIC:0)|(json?OF_JSON:0), -1, !output.str().empty()); } if (failedRead) if (result >= RESULT_OK)