From e795ce604aca700b2350e734a59ebc3ca0cfea4d Mon Sep 17 00:00:00 2001 From: John <ebusd@ebusd.eu> Date: Mon, 6 Jan 2025 22:08:49 +0100 Subject: [PATCH] add value range and step support, fix value list validation --- src/lib/ebus/contrib/tem.cpp | 5 +- src/lib/ebus/data.cpp | 70 ++++++- src/lib/ebus/datatype.cpp | 353 +++++++++++++++++++++----------- src/lib/ebus/datatype.h | 59 +++++- src/lib/ebus/test/test_data.cpp | 68 +++++- 5 files changed, 417 insertions(+), 138 deletions(-) diff --git a/src/lib/ebus/contrib/tem.cpp b/src/lib/ebus/contrib/tem.cpp index ff1e22b1..991905cd 100755 --- a/src/lib/ebus/contrib/tem.cpp +++ b/src/lib/ebus/contrib/tem.cpp @@ -135,8 +135,9 @@ result_t TemParamDataType::writeSymbols(const size_t offset, const size_t length value = (grp << 7) | num; // grp in bits 7...11, num in bits 0...6 } } - if (value < getMinValue() || value > getMaxValue()) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range + result_t ret = checkValueRange(value); + if (ret != RESULT_OK) { + return ret; } return writeRawValue(value, offset, length, output, usedLength); } diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 36856978..2f745ad7 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -273,6 +273,7 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas string divisorStr = pluck("divisor", &row); string valuesStr = pluck("values", &row); + string rangeStr = pluck("range", &row); if (divisorStr.empty() && valuesStr.empty()) { divisorStr = pluck("divisor/values", &row); // [divisor|values] if (divisorStr.find('=') != string::npos) { @@ -365,6 +366,57 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas } transform(typeName.begin(), typeName.end(), typeName.begin(), ::toupper); const DataType* dataType = DataTypeList::getInstance()->get(typeName, length == REMAIN_LEN ? 0 : length); + if (dataType && dataType->isNumeric() && !rangeStr.empty()) { + const NumberDataType* numType = reinterpret_cast<const NumberDataType*>(dataType); + if (divisor != 1 && divisor != 0) { + result = numType->derive(divisor, numType->getBitCount(), &numType); + divisor = 1; + } + // either from-to or from-to:step + size_t sepPos = rangeStr.find('-', 1); + string part = rangeStr.substr(0, sepPos); + FileReader::trim(&part); + unsigned int from; + if (result == RESULT_OK) { + result = numType->parseInput(part, &from); + } + unsigned int to = from; + unsigned int inc = 0; + if (result == RESULT_OK) { + part = rangeStr.substr(sepPos+1); + FileReader::trim(&part); + sepPos = part.find(':'); + if (sepPos != string::npos) { + string incStr = part.substr(sepPos + 1); + FileReader::trim(&incStr); + part = part.substr(0, sepPos); + FileReader::trim(&part); + result = numType->parseInput(incStr, &inc); + } + if (result == RESULT_OK) { + result = numType->parseInput(part, &to); + } + } + float ffrom = 0, fto = 0; + if (result == RESULT_OK) { + result = numType->getFloatFromRawValue(from, &ffrom); + } + if (result == RESULT_OK) { + result = numType->getFloatFromRawValue(to, &fto); + } + if (result == RESULT_OK && ffrom > fto) { + result = RESULT_ERR_INVALID_LIST; + } + if (result == RESULT_OK) { + result = numType->derive(from, to, inc, &numType); + }; + if (result != RESULT_OK) { + *errorDescription = "\""+rangeStr+"\" in field "+formatInt(fieldIndex); + result = RESULT_ERR_OUT_OF_RANGE; + break; + } + dataType = numType; + } if (!dataType) { result = RESULT_ERR_NOTFOUND; *errorDescription = "field type "+typeName+" in field "+formatInt(fieldIndex); @@ -512,8 +564,11 @@ result_t SingleDataField::create(const string& name, const map<string, string>& *returnField = new SingleDataField(name, attributes, numType, partType, byteCount); return RESULT_OK; } - if (values->begin()->first < numType->getMinValue() || values->rbegin()->first > numType->getMaxValue()) { - return RESULT_ERR_OUT_OF_RANGE; + for (auto& it : *values) { + result_t ret = numType->checkValueRange(it.first); + if (ret != RESULT_OK) { + return ret; + } } *returnField = new ValueListDataField(name, attributes, numType, partType, byteCount, *values); return RESULT_OK; @@ -775,8 +830,11 @@ result_t ValueListDataField::derive(const string& name, PartType partType, int d } const NumberDataType* num = reinterpret_cast<const NumberDataType*>(m_dataType); if (!values.empty()) { - if (values.begin()->first < num->getMinValue() || values.rbegin()->first > num->getMaxValue()) { - return RESULT_ERR_INVALID_ARG; // cannot use divisor != 1 for value list field + for (auto& it : values) { + result_t ret = num->checkValueRange(it.first); + if (ret != RESULT_OK) { + return RESULT_ERR_INVALID_ARG; + } } fields->push_back(new ValueListDataField(useName, *attributes, num, partType, m_length, values)); @@ -922,8 +980,8 @@ result_t ConstantDataField::readSymbols(const SymbolString& input, size_t offset if (result != RESULT_OK) { return result; } - string value = coutput.str(); - FileReader::trim(&value); + string value = coutput.str(); + FileReader::trim(&value); if (m_verify) { if (value != m_value) { return RESULT_ERR_OUT_OF_RANGE; diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 6a5c62ff..a617bb39 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -700,8 +700,16 @@ bool NumberDataType::dump(OutputFormat outputFormat, size_t length, AppendDiviso ret = true; } } - if (ret && (outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { - *output << ", \"precision\": " << static_cast<unsigned>(getPrecision()); + if ((outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { + if (ret) { + *output << ", \"precision\": " << static_cast<unsigned>(getPrecision()); + } + *output << ", \"min\": "; + getMinMax(false, OF_JSON, output); + *output << ", \"max\": "; + getMinMax(true, OF_JSON, output); + *output << ", \"step\": "; + getStep(OF_JSON, output); } return ret; } @@ -747,23 +755,119 @@ result_t NumberDataType::derive(int divisor, size_t bitCount, const NumberDataTy } else { return RESULT_ERR_INVALID_ARG; } + ostringstream str; + str << m_id << ',' << static_cast<unsigned>(bitCount) << ',' << static_cast<signed>(divisor); + string key = str.str(); + *derived = static_cast<const NumberDataType*>(DataTypeList::getInstance()->get(key)); + if (*derived == nullptr) { + if (m_bitCount < 8) { + *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, + m_firstBit, divisor, m_baseType ? m_baseType : this); + } else { + *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, + m_minValue, m_maxValue, divisor, m_baseType ? m_baseType : this); + } + DataTypeList::getInstance()->add(*derived, key); + } + return RESULT_OK; +} + +result_t NumberDataType::derive(unsigned int min, unsigned int max, unsigned int inc, const NumberDataType** derived) +const { if (m_bitCount < 8) { - *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, - m_firstBit, divisor, m_baseType ? m_baseType : this); - } else { - *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, - m_minValue, m_maxValue, divisor, m_baseType ? m_baseType : this); + return RESULT_ERR_INVALID_ARG; + } + if (min == m_minValue && max == m_maxValue && (inc == 0 || inc == m_incValue)) { + *derived = this; + return RESULT_OK; + } + if (checkValueRange(min) != RESULT_OK || checkValueRange(max) != RESULT_OK) { + return RESULT_ERR_OUT_OF_RANGE; + } + ostringstream str; + str << m_id << ',' << static_cast<unsigned>(m_bitCount) << ',' << static_cast<signed>(m_divisor) + << ',' << static_cast<unsigned>(min)<< ',' << static_cast<unsigned>(max)<< ',' << static_cast<unsigned>(inc); + string key = str.str(); + *derived = static_cast<const NumberDataType*>(DataTypeList::getInstance()->get(key)); + if (*derived == nullptr) { + *derived = new NumberDataType(m_id, m_bitCount, m_flags, m_replacement, + min, max, inc, m_divisor, m_baseType ? m_baseType : this); + DataTypeList::getInstance()->add(*derived, key); } - DataTypeList::getInstance()->addCleanup(*derived); return RESULT_OK; } result_t NumberDataType::getMinMax(bool getMax, const OutputFormat outputFormat, ostream* output) const { - return readFromRawValue(getMax ? m_maxValue : m_minValue, outputFormat, output); + return readFromRawValue(getMax ? m_maxValue : m_minValue, outputFormat, output, true); } result_t NumberDataType::getStep(const OutputFormat outputFormat, ostream* output) const { - return readFromRawValue(hasFlag(EXP) ? floatToUint(1.0f) : 1, outputFormat, output); + return readFromRawValue(m_incValue ? m_incValue : hasFlag(EXP) ? floatToUint(1.0f) : 1, outputFormat, output, true); +} + +result_t NumberDataType::checkValueRange(unsigned int value, bool* pnegative) const { + bool negative; + if (hasFlag(SIG)) { // signed value + unsigned int negBit = 1 << (m_bitCount - 1); + negative = (value & negBit) != 0; + if (hasFlag(EXP)) { + float fval = uintToFloat(value, negative); + if (!isfinite(fval)) { + return RESULT_EMPTY; + } + float cval = uintToFloat(m_minValue, (m_minValue & negBit) != 0); + if (!isfinite(cval)) { + return RESULT_EMPTY; + } + if (fval < cval) { + return RESULT_ERR_OUT_OF_RANGE; + } + cval = uintToFloat(m_maxValue, (m_maxValue & negBit) != 0); + if (!isfinite(cval)) { + return RESULT_EMPTY; + } + if (fval > cval) { + return RESULT_ERR_OUT_OF_RANGE; + } + } else { + if (m_minValue & negBit) { + // negative min + if (negative && value < m_minValue) { + // e.g. SCH val=0xfc=-4 min=0xff=-1 + return RESULT_ERR_OUT_OF_RANGE; + } + } else { + // positive min + if (negative || value < m_minValue) { + // e.g. SCH val=0xfc=-4 min=0x01=+1 + // e.g. SCH val=0x00=0 min=0x01=+1 + return RESULT_ERR_OUT_OF_RANGE; + } + } + if (m_maxValue & negBit) { + // negative max + if (!negative || value > m_maxValue) { + // e.g. SCH val=0x00=0 max=0xff=-1 + // e.g. SCH val=0xff=-1 max=0xfe=-2 + return RESULT_ERR_OUT_OF_RANGE; + } + } else { + // positive max + if (!negative && value > m_maxValue) { + // e.g. SCH val=0x04=+4 max=0x01=+1 + return RESULT_ERR_OUT_OF_RANGE; + } + } + } + } else if (value < m_minValue || value > m_maxValue) { + return RESULT_ERR_OUT_OF_RANGE; + } else { + negative = false; + } + if (pnegative) { + *pnegative = negative; + } + return RESULT_OK; } result_t NumberDataType::readRawValue(size_t offset, size_t length, const SymbolString& input, @@ -830,22 +934,10 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) return RESULT_EMPTY; } - bool negative; - if (hasFlag(SIG)) { // signed value - negative = (value & (1 << (m_bitCount - 1))) != 0; - if (!hasFlag(EXP)) { - if (negative) { // 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 - } else { - negative = false; + bool negative = false; + result_t ret = checkValueRange(value, &negative); + if (ret != RESULT_OK) { + return ret; } int signedValue; if (m_bitCount == 32) { @@ -865,7 +957,7 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) val /= static_cast<float>(m_divisor); } } - *output = static_cast<float>(val); + *output = val; return RESULT_OK; } if (!negative) { @@ -895,7 +987,7 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) } result_t NumberDataType::readFromRawValue(unsigned int value, - OutputFormat outputFormat, ostream* output) const { + OutputFormat outputFormat, ostream* output, bool skipRangeCheck) const { size_t length = (m_bitCount < 8) ? 1 : (m_bitCount/8); // initialize output *output << setw(0) << std::resetiosflags(output->flags()) << dec << std::skipws << setprecision(6); @@ -909,22 +1001,10 @@ result_t NumberDataType::readFromRawValue(unsigned int value, return RESULT_OK; } - bool negative; - if (hasFlag(SIG)) { // signed value - negative = (value & (1 << (m_bitCount - 1))) != 0; - if (!hasFlag(EXP)) { - if (negative) { // 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 - } else { - negative = false; + bool negative = false; + result_t ret = checkValueRange(value, &negative); + if (!skipRangeCheck && ret != RESULT_OK) { + return ret; } int signedValue; if (m_bitCount == 32) { @@ -1055,7 +1135,7 @@ result_t NumberDataType::getRawValueFromFloat(float val, unsigned int* output) c } else { if (m_divisor == 1) { if (hasFlag(SIG)) { - long signedValue = static_cast<long>(val); // TODO static_c? + long signedValue = static_cast<long>(val); if (signedValue < 0 && m_bitCount != 32) { value = (unsigned int)(signedValue + (1 << m_bitCount)); } else { @@ -1091,106 +1171,108 @@ result_t NumberDataType::getRawValueFromFloat(float val, unsigned int* output) c 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 - } + } + result_t ret = checkValueRange(value); + if (ret != RESULT_OK) { + return ret; } *output = value; return RESULT_OK; } -result_t NumberDataType::writeSymbols(size_t offset, size_t length, istringstream* input, - SymbolString* output, size_t* usedLength) const { +result_t NumberDataType::parseInput(const string inputStr, unsigned int* parsedValue) const { unsigned int value; - const string inputStr = input->str(); if (!hasFlag(REQ) && (isIgnored() || inputStr == NULL_VALUE)) { value = m_replacement; // replacement value } else if (inputStr.empty()) { return RESULT_ERR_EOF; // input too short - } else if (hasFlag(EXP)) { // IEEE 754 binary32 - const char* str = inputStr.c_str(); - char* strEnd = nullptr; - double dvalue = strtod(str, &strEnd); - if (strEnd == nullptr || 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; - } - value = floatToUint(static_cast<float>(dvalue)); - if (value == 0xffffffff) { - return RESULT_ERR_INVALID_NUM; - } } else { - const char* str = inputStr.c_str(); - char* strEnd = nullptr; - if (m_divisor == 1) { - if (hasFlag(SIG)) { - long 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 == nullptr || strEnd == str || (*strEnd != 0 && *strEnd != '.')) { - return RESULT_ERR_INVALID_NUM; // invalid value - } - } else { + if (hasFlag(EXP)) { // IEEE 754 binary32 + const char* str = inputStr.c_str(); + char* strEnd = nullptr; double dvalue = strtod(str, &strEnd); - if (strEnd == nullptr || strEnd == str || *strEnd != 0) { + if (errno == ERANGE || strEnd == nullptr || 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); + dvalue /= -m_divisor; + } else if (m_divisor > 1) { + dvalue *= m_divisor; } - if (hasFlag(SIG)) { - if (dvalue < -exp2((8 * static_cast<double>(length)) - 1) - || dvalue >= exp2((8 * static_cast<double>(length)) - 1)) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - if (dvalue < 0 && m_bitCount != 32) { - value = static_cast<int>(dvalue + (1 << m_bitCount)); + value = floatToUint(static_cast<float>(dvalue)); + if (value == 0xffffffff) { + return RESULT_ERR_INVALID_NUM; + } + } else { + unsigned int maxBit = m_bitCount != 32 ? 1 << m_bitCount : 0; + const char* str = inputStr.c_str(); + char* strEnd = nullptr; + if (m_divisor == 1) { + if (hasFlag(SIG)) { + long signedValue = strtol(str, &strEnd, 0); + if (errno == ERANGE || (maxBit && (signedValue < -(maxBit/2L) || signedValue >= maxBit/2L))) { + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + if (signedValue < 0 && m_bitCount != 32) { + value = (unsigned int)(signedValue + maxBit); + } else { + value = (unsigned int)signedValue; + } } else { - value = static_cast<int>(dvalue); + value = (unsigned int)strtoul(str, &strEnd, 0); + if (errno == ERANGE || (maxBit && value >= maxBit)) { + return RESULT_ERR_OUT_OF_RANGE; + } + } + if (strEnd == nullptr || strEnd == str || (*strEnd != 0 && *strEnd != '.')) { + return RESULT_ERR_INVALID_NUM; // invalid value } } else { - if (dvalue < 0.0 || dvalue >= exp2(8 * static_cast<double>(length))) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range + double dvalue = strtod(str, &strEnd); + if (errno == ERANGE || strEnd == nullptr || strEnd == str || *strEnd != 0) { + return RESULT_ERR_INVALID_NUM; // invalid value } - 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 + if (m_divisor < 0) { + dvalue = round(dvalue / -m_divisor); + } else { + dvalue = round(dvalue * m_divisor); + } + if (hasFlag(SIG)) { + double max = exp2(m_bitCount - 1); + if (dvalue < -max || dvalue >= max) { + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + if (dvalue < 0 && m_bitCount != 32) { + value = static_cast<int>(dvalue + (1 << m_bitCount)); + } else { + value = static_cast<int>(dvalue); + } + } else { + if (dvalue < 0.0 || dvalue >= exp2(m_bitCount)) { + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + value = (unsigned int)dvalue; } - } 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 + } + result_t ret = checkValueRange(value); + if (ret != RESULT_OK) { + return ret; } } + *parsedValue = value; + return RESULT_OK; +} +result_t NumberDataType::writeSymbols(size_t offset, size_t length, istringstream* input, + SymbolString* output, size_t* usedLength) const { + unsigned int value; + const string inputStr = input->str(); + result_t ret = parseInput(inputStr, &value); + if (ret != RESULT_OK) { + return ret; + } return writeRawValue(value, offset, length, output, usedLength); } @@ -1207,6 +1289,7 @@ DataTypeList::DataTypeList() { // unsigned decimal in BCD, 0000 - 9999 (fixed length) add(new NumberDataType("PIN", 16, FIX|BCD|REV, 0xffff, 0, 0x9999, 1)); add(new NumberDataType("UCH", 8, 0, 0xff, 0, 0xfe, 1)); // unsigned integer, 0 - 254 + add(new NumberDataType("U1L", 8, REQ, 0, 0, 0xff, 1)); // unsigned 1-byte, 0 - 255 (no replacement) add(new StringDataType("IGN", MAX_LEN*8, IGN|ADJ, 0)); // >= 1 byte ignored data // >= 1 byte character string filled up with 0x00 (null terminated string) add(new StringDataType("NTS", MAX_LEN*8, ADJ, 0)); @@ -1264,6 +1347,7 @@ DataTypeList::DataTypeList() { add(new NumberDataType("HCD:2", 16, HCD|BCD|REQ, 0, 0, 9999, 1)); // unsigned decimal in HCD, 0 - 9999 add(new NumberDataType("HCD:3", 24, HCD|BCD|REQ, 0, 0, 999999, 1)); // unsigned decimal in HCD, 0 - 999999 add(new NumberDataType("SCH", 8, SIG, 0x80, 0x81, 0x7f, 1)); // signed integer, -127 - +127 + add(new NumberDataType("S1L", 8, SIG|REQ, 0, 0x80, 0x7f, 1)); // signed integer, -128 - +127 (no replacement) add(new NumberDataType("D1B", 8, SIG, 0x80, 0x81, 0x7f, 1)); // signed integer, -127 - +127 // unsigned number (fraction 1/2), 0 - 100 (0x00 - 0xc8, replacement 0xff) add(new NumberDataType("D1C", 8, 0, 0xff, 0x00, 0xc8, 2)); @@ -1283,26 +1367,50 @@ DataTypeList::DataTypeList() { add(new NumberDataType("UIN", 16, 0, 0xffff, 0, 0xfffe, 1)); // unsigned integer, 0 - 65534, big endian add(new NumberDataType("UIR", 16, REV, 0xffff, 0, 0xfffe, 1)); + // unsigned integer, 0 - 65535, little endian (no replacement) + add(new NumberDataType("U2L", 16, REQ, 0, 0, 0xffff, 1)); + // unsigned integer, 0 - 65535, big endian (no replacement) + add(new NumberDataType("U2B", 16, REQ|REV, 0, 0, 0xffff, 1)); // signed integer, -32767 - +32767, little endian add(new NumberDataType("SIN", 16, SIG, 0x8000, 0x8001, 0x7fff, 1)); // signed integer, -32767 - +32767, big endian add(new NumberDataType("SIR", 16, SIG|REV, 0x8000, 0x8001, 0x7fff, 1)); + // signed integer, -32768 - +32767, little endian (no replacement) + add(new NumberDataType("S2L", 16, SIG|REQ, 0, 0x8000, 0x7fff, 1)); + // signed integer, -32768 - +32767, big endian (no replacement) + add(new NumberDataType("S2B", 16, SIG|REQ|REV, 0, 0x8000, 0x7fff, 1)); // unsigned 3 bytes int, 0 - 16777214, little endian add(new NumberDataType("U3N", 24, 0, 0xffffff, 0, 0xfffffe, 1)); // unsigned 3 bytes int, 0 - 16777214, big endian add(new NumberDataType("U3R", 24, REV, 0xffffff, 0, 0xfffffe, 1)); + // unsigned 3 bytes int, 0 - 16777215, little endian (no replacement) + add(new NumberDataType("U3L", 24, REQ, 0, 0, 0xffffff, 1)); + // unsigned 3 bytes int, 0 - 16777215, big endian (no replacement) + add(new NumberDataType("U3B", 24, REQ|REV, 0, 0, 0xffffff, 1)); // signed 3 bytes int, -8388607 - +8388607, little endian add(new NumberDataType("S3N", 24, SIG, 0x800000, 0x800001, 0x7fffff, 1)); // signed 3 bytes int, -8388607 - +8388607, big endian add(new NumberDataType("S3R", 24, SIG|REV, 0x800000, 0x800001, 0x7fffff, 1)); + // signed 3 bytes int, -8388608 - +8388607, little endian (no replacement) + add(new NumberDataType("S3L", 24, SIG|REQ, 0, 0x800000, 0x7fffff, 1)); + // signed 3 bytes int, -8388608 - +8388607, big endian (no replacement) + add(new NumberDataType("S3B", 24, SIG|REQ|REV, 0, 0x800000, 0x7fffff, 1)); // unsigned integer, 0 - 4294967294, little endian add(new NumberDataType("ULG", 32, 0, 0xffffffff, 0, 0xfffffffe, 1)); // unsigned integer, 0 - 4294967294, big endian add(new NumberDataType("ULR", 32, REV, 0xffffffff, 0, 0xfffffffe, 1)); // signed integer, -2147483647 - +2147483647, little endian + // unsigned integer, 0 - 4294967295, little endian (no replacement) + add(new NumberDataType("U4L", 32, REQ, 0, 0, 0xffffffff, 1)); + // unsigned integer, 0 - 4294967295, big endian (no replacement) + add(new NumberDataType("U4B", 32, REQ|REV, 0, 0, 0xffffffff, 1)); add(new NumberDataType("SLG", 32, SIG, 0x80000000, 0x80000001, 0x7fffffff, 1)); // signed integer, -2147483647 - +2147483647, big endian add(new NumberDataType("SLR", 32, SIG|REV, 0x80000000, 0x80000001, 0x7fffffff, 1)); + // signed integer, -2147483648 - +2147483647, little endian (no replacement) + add(new NumberDataType("S4L", 32, SIG|REQ, 0, 0x80000000, 0x7fffffff, 1)); + // signed integer, -2147483648 - +2147483647, big endian (no replacement) + add(new NumberDataType("S4B", 32, SIG|REQ|REV, 0, 0x80000000, 0x7fffffff, 1)); add(new NumberDataType("BI0", 7, ADJ|REQ, 0, 0, 1)); // bit 0 (up to 7 bits until bit 6) add(new NumberDataType("BI1", 7, ADJ|REQ, 0, 1, 1)); // bit 1 (up to 7 bits until bit 7) add(new NumberDataType("BI2", 6, ADJ|REQ, 0, 2, 1)); // bit 2 (up to 6 bits until bit 7) @@ -1350,11 +1458,12 @@ void DataTypeList::clear() { m_typesById.clear(); } -result_t DataTypeList::add(const DataType* dataType) { - if (m_typesById.find(dataType->getId()) != m_typesById.end()) { +result_t DataTypeList::add(const DataType* dataType, const string derivedKey) { + string key = derivedKey.empty() ? dataType->getId() : derivedKey; + if (m_typesById.find(key) != m_typesById.end()) { return RESULT_ERR_DUPLICATE_NAME; // duplicate key } - m_typesById[dataType->getId()] = dataType; + m_typesById[key] = dataType; m_cleanupTypes.push_back(dataType); return RESULT_OK; } diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index c5e77a46..9fb2f3a5 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -481,7 +481,25 @@ class NumberDataType : public DataType { NumberDataType(const string& id, size_t bitCount, uint16_t flags, unsigned int replacement, unsigned int minValue, unsigned int maxValue, int divisor, const NumberDataType* baseType = nullptr) - : DataType(id, bitCount, flags|NUM, replacement), m_minValue(minValue), m_maxValue(maxValue), + : DataType(id, bitCount, flags|NUM, replacement), m_minValue(minValue), m_maxValue(maxValue), m_incValue(0), + m_divisor(divisor == 0 ? 1 : divisor), m_precision(calcPrecision(divisor)), m_firstBit(0), m_baseType(baseType) {} + + /** + * Constructs a new instance for multiple of 8 bits with increment value. + * @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 #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 incValue the smallest step value for increment/decrement, or 0 for auto. + * @param divisor the divisor (negative for reciprocal). + * @param baseType the base @a NumberDataType for derived instances, or nullptr. + */ + NumberDataType(const string& id, size_t bitCount, uint16_t flags, unsigned int replacement, + unsigned int minValue, unsigned int maxValue, unsigned int incValue, int divisor, + const NumberDataType* baseType = nullptr) + : DataType(id, bitCount, flags|NUM, replacement), m_minValue(minValue), m_maxValue(maxValue), m_incValue(incValue), m_divisor(divisor == 0 ? 1 : divisor), m_precision(calcPrecision(divisor)), m_firstBit(0), m_baseType(baseType) {} /** @@ -496,7 +514,7 @@ class NumberDataType : public DataType { */ NumberDataType(const string& id, size_t bitCount, uint16_t flags, unsigned int replacement, int16_t firstBit, int divisor, const NumberDataType* baseType = nullptr) - : DataType(id, bitCount, flags|NUM, replacement), m_minValue(0), m_maxValue((1 << bitCount)-1), + : DataType(id, bitCount, flags|NUM, replacement), m_minValue(0), m_maxValue((1 << bitCount)-1), m_incValue(0), m_divisor(divisor == 0 ? 1 : divisor), m_precision(0), m_firstBit(firstBit), m_baseType(baseType) {} /** @@ -527,6 +545,18 @@ class NumberDataType : public DataType { */ virtual result_t derive(int divisor, size_t bitCount, const NumberDataType** derived) const; + /** + * Derive a new @a NumberDataType from this. + * @param min the minimum raw value. + * @param max the minimum raw value. + * @param inc the smallest step value for increment/decrement, or 0 to keep the current increment (or calculate + * automatically). + * @param derived the derived @a NumberDataType, or this if derivation is + * not necessary. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t derive(unsigned int min, unsigned int max, unsigned int inc, const NumberDataType** derived) const; + /** * @return the minimum raw value. */ @@ -546,6 +576,14 @@ class NumberDataType : public DataType { */ result_t getMinMax(bool getMax, const OutputFormat outputFormat, ostream* output) const; + /** + * Check the value against the minimum and maximum value. + * @param value the raw value. + * @param negative optional variable in which to store the negative flag. + * @return @a RESULT_OK on success, or an error code. + */ + result_t checkValueRange(unsigned int value, bool* negative = nullptr) const; + /** * Get the smallest step value for increment/decrement. * @param outputFormat the @a OutputFormat options to use. @@ -598,10 +636,19 @@ class NumberDataType : public DataType { * @param value the numeric raw value. * @param outputFormat the @a OutputFormat options to use. * @param output the ostream to append the formatted value to. + * @param skipRangeCheck whether to skip the value range check. * @return @a RESULT_OK on success, or an error code. */ result_t readFromRawValue(unsigned int value, - OutputFormat outputFormat, ostream* output) const; + OutputFormat outputFormat, ostream* output, bool skipRangeCheck = false) const; + + /** + * Internal method for parsing an input string to the coorresponding raw value. + * @param inputStr the input string to parse the formatted value from. + * @param parsedValue the variable in which to store the parsed raw value. + * @return @a RESULT_OK on success, or an error code. + */ + result_t parseInput(const string inputStr, unsigned int* parsedValue) const; /** * Internal method for writing the numeric raw value to a @a SymbolString. @@ -628,6 +675,9 @@ class NumberDataType : public DataType { /** the maximum raw value. */ const unsigned int m_maxValue; + /** the smallest step value for increment/decrement, or 0 for auto. */ + const unsigned int m_incValue; + /** the divisor (negative for reciprocal). */ const int m_divisor; @@ -680,10 +730,11 @@ class DataTypeList { /** * Adds a @a DataType instance to this map. * @param dataType the @a DataType instance to add. + * @param derivedKey optional speicla key for derived instances. * @return @a RESULT_OK on success, or an error code. * Note: the caller may not free the added instance on success. */ - result_t add(const DataType* dataType); + result_t add(const DataType* dataType, const string derivedKey = ""); /** * Adds a @a DataType instance for later cleanup. diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 1cfb5626..6fce4b91 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -53,15 +53,18 @@ void verify(bool expectFailMatch, string type, string input, class TestReader : public MappedFileReader { public: - TestReader(DataFieldTemplates* templates, bool isSet, bool isMasterDest) + TestReader(DataFieldTemplates* templates, bool isSet, bool isMasterDest, bool withRange) : MappedFileReader::MappedFileReader(true), m_templates(templates), m_isSet(isSet), m_isMasterDest(isMasterDest), - m_fields(nullptr) {} + m_withRange(withRange), m_fields(nullptr) {} result_t getFieldMap(const string& preferLanguage, vector<string>* row, string* errorDescription) const override { if (row->empty()) { row->push_back("*name"); row->push_back("part"); row->push_back("type"); row->push_back("divisor/values"); + if (m_withRange) { + row->push_back("range"); + } row->push_back("unit"); row->push_back("comment"); return RESULT_OK; @@ -86,6 +89,7 @@ class TestReader : public MappedFileReader { const DataFieldTemplates* m_templates; const bool m_isSet; const bool m_isMasterDest; + const bool m_withRange; public: const DataField* m_fields; }; @@ -101,7 +105,7 @@ int main() { // entry: definition, decoded value, master data, slave data, flags // definition: name,part,type[:len][,[divisor|values][,[unit][,[comment]]]] unsigned int baseLine = __LINE__+1; - string checks[][5] = { + string checks[][6] = { {"x,,ign:10", "", "10fe07000a00000000000000000000", "00", ""}, {"x,,ign:*", "", "10fe07000a00000000000000000000", "00", "W"}, {"x,,ign,2", "", "", "", "c"}, @@ -344,6 +348,13 @@ int main() { {"x,,uch,==48", "", "10feffff01ab", "00", "rW"}, {"x,,uch,=48", "", "10feffff0130", "00", ""}, {"x,,uch,==48", "", "10feffff0130", "00", ""}, + {"x,,uch,,1-3", "2", "10feffff0102", "00", "-"}, + {"x,,uch,,1-3", "4", "10feffff0102", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,uch,,1-3", "2", "10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,uch,,0x1-0x3", "4","10feffff0102", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,uch,,0x1-0x3", "2","10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,uch", "\n \"x\": {\"value\": 2}", "10feffff0102", "00", "-jV", "\n { \"name\": \"x\", \"slave\": false, \"type\": \"UCH\", \"isbits\": false, \"isadjustable\": false, \"isignored\": false, \"isreverse\": false, \"length\": 1, \"result\": \"number\", \"min\": 0, \"max\": 254, \"step\": 1, \"unit\": \"\", \"comment\": \"\"}"}, + {"x,,uch,,1-3:2", "\n \"x\": {\"value\": 2}", "10feffff0102", "00", "-jV", "\n { \"name\": \"x\", \"slave\": false, \"type\": \"UCH\", \"isbits\": false, \"isadjustable\": false, \"isignored\": false, \"isreverse\": false, \"length\": 1, \"result\": \"number\", \"min\": 1, \"max\": 3, \"step\": 2, \"unit\": \"\", \"comment\": \"\"}"}, {"x,,sch", "-90", "10feffff01a6", "00", ""}, {"x,,sch", "0", "10feffff0100", "00", ""}, {"x,,sch", "-1", "10feffff01ff", "00", ""}, @@ -352,6 +363,15 @@ int main() { {"x,,sch", "127", "10feffff017f", "00", ""}, {"x,,sch,10", "-9.0", "10feffff01a6", "00", ""}, {"x,,sch,-10", "-900", "10feffff01a6", "00", ""}, + {"x,,sch,,1-3", "2", "10feffff0102", "00", "-"}, + {"x,,sch,,1-500", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,-130-1", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,-127-127", "-", "10feffff0180", "00", "-"}, + {"x,,sch,,-127-128", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,-128-127", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,1-3", "4", "10feffff0102", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,sch,,1-3", "2", "10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,sch,,-3--1", "-4", "10feffff01fe", "00", "-Rw:ERR: argument value out of valid range"}, {"x,,d1b", "-90", "10feffff01a6", "00", ""}, {"x,,d1b", "0", "10feffff0100", "00", ""}, {"x,,d1b", "-1", "10feffff01ff", "00", ""}, @@ -429,6 +449,12 @@ int main() { {"x,,flt", "-", "10feffff020080", "00", ""}, {"x,,flt", "-32.767", "10feffff020180", "00", ""}, {"x,,flt", "32.767", "10feffff02ff7f", "00", ""}, + {"x,,flt,,1-3", "2.000", "10feffff02d007", "00", "-"}, + {"x,,flt,,1-3", "4.000", "10feffff02d007", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,flt,,1-3", "2.000", "10feffff02a00f", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,flt,,-3--1", "-4", "10feffff0230f8", "00", "-Rw:ERR: argument value out of valid range"}, // -4:60f0, -2:30f8 + {"x,,flt,,-3.1--1.0", "-4", "10feffff0230f8", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,flt,,-3.1--1.0", "-2", "10feffff0260f0", "00", "-rW:ERR: argument value out of valid range"}, {"x,,flr", "-0.090", "10feffff02ffa6", "00", ""}, {"x,,flr", "0.000", "10feffff020000", "00", ""}, {"x,,flr", "-0.001", "10feffff02ffff", "00", ""}, @@ -445,6 +471,11 @@ int main() { {"x,,exp", "0.25", "10feffff040000803e", "00", ""}, {"x,,exp", "0.95", "10feffff043333733f", "00", ""}, {"x,,exp", "0.65", "10feffff046666263f", "00", ""}, + {"x,,exp", "0.065", "10feffff04b81e853d", "00", ""}, + {"x,,exp,,0-0.65", "0.65", "10feffff046666263f", "00", "-"}, + {"x,,exp,,0-0.5", "0.65", "10feffff046666263f", "00", "-rw:ERR: argument value out of valid range"}, + {"x,,exp,10,0-0.065", "0.0650000", "10feffff046666263f", "00", "-"}, + {"x,,exp,10,0-0.05", "0.0650000", "10feffff046666263f", "00", "-rw:ERR: argument value out of valid range"}, {"x,,exr", "-0.09", "10feffff04bdb851ec", "00", ""}, {"x,,exr", "0.0", "10feffff0400000000", "00", ""}, {"x,,exr", "-0.001", "10feffff04ba83126f", "00", ""}, @@ -577,6 +608,12 @@ int main() { continue; } string flags = check[4]; + size_t colon = flags.find(':'); + string errStr; + if (colon != string::npos) { + errStr = flags.substr(colon+1); + flags = flags.substr(0, colon); + } bool isSet = flags.find('s') != string::npos; bool testFields = flags.find('F') != string::npos; bool failedCreate = flags.find('c') != string::npos; @@ -584,6 +621,7 @@ int main() { bool failedReadMatch = flags.find('R') != string::npos; bool failedWrite = flags.find('w') != string::npos; bool failedWriteMatch = flags.find('W') != string::npos; + string withDump = check[5]; // optional const char* findName = flags.find('I') == string::npos ? nullptr : "x"; ssize_t findIndex = -1; if (flags.find('i') != string::npos) { @@ -599,6 +637,9 @@ int main() { if (flags.find("vvv") != string::npos) { verbosity |= OF_COMMENTS; } + if (flags.find("V") != string::npos) { + verbosity |= OF_NAMES|OF_UNITS|OF_COMMENTS|OF_ALL_ATTRS; + } if (flags.find('j') != string::npos) { verbosity |= OF_JSON; } @@ -620,7 +661,8 @@ int main() { } continue; } - TestReader reader{templates, isSet, mstr[1] == BROADCAST || isMaster(mstr[1])}; + bool withRange = flags.find('-') != string::npos; + TestReader reader{templates, isSet, mstr[1] == BROADCAST || isMaster(mstr[1]), withRange}; lineNo = 0; dummystr.clear(); dummystr.str("#"); @@ -638,6 +680,9 @@ int main() { if (result == RESULT_OK) { cout << "\"" << check[0] << "\": failed create error: unexpectedly succeeded" << endl; error = true; + } else if (!errStr.empty() && errorDescription != errStr) { + cout << "\"" << check[0] << "\": failed create error: unexpected result \"" << errorDescription << "\" instead of \"" << errStr << "\"" << endl; + error = true; } else { cout << "\"" << check[0] << "\": failed create OK" << endl; } @@ -680,6 +725,10 @@ int main() { cout << " failed read " << fields->getName(-1) << " >" << check[2] << " " << check[3] << "< error: unexpectedly succeeded" << endl; error = true; + } else if (!errStr.empty() && getResultCode(result) != errStr) { + cout << " failed read " << fields->getName(-1) << " >" << check[2] << " " << check[3] + << "< error: unexpected result \""; + cout << getResultCode(result) << "\" instead of \"" << errStr << "\"" << endl; } else { cout << " failed read " << fields->getName(-1) << " >" << check[2] << " " << check[3] << "< OK" << endl; @@ -744,6 +793,10 @@ int main() { cout << " failed write " << fields->getName(-1) << " >" << expectStr << "< error: unexpectedly succeeded" << endl; error = true; + } else if (!errStr.empty() && getResultCode(result) != errStr) { + cout << " failed write " << fields->getName(-1) << " >" + << "< error: unexpected result \""; + cout << getResultCode(result) << "\" instead of \"" << errStr << "\"" << endl; } else { cout << " failed write " << fields->getName(-1) << " >" << expectStr << "< OK" << endl; @@ -760,6 +813,13 @@ int main() { writeMstr.getStr() + " " + writeSstr.getStr()); } } + if (!withDump.empty()) { + output.clear(); + output.str(""); + fields->dump(false, verbosity, &output); + bool match = output.str() == withDump; + verify(false, "dump", withDump, match, withDump, output.str()); + } delete fields; fields = nullptr; }