diff --git a/src/libebus/data.cpp b/src/libebus/data.cpp old mode 100644 new mode 100755 index e300d0a0..a23a1be6 --- a/src/libebus/data.cpp +++ b/src/libebus/data.cpp @@ -30,8 +30,34 @@ namespace libebus { +/** the known data field types. */ +static const dataType_t dataTypes[] = { + {"STR",16, bt_str, ADJ,' ', 1, 16, 0}, // >= 1 byte character string filled up with space + {"HEX",16, bt_hexstr,ADJ, 0, 2, 47, 0}, // >= 1 byte hex digit string, usually separated by space, e.g. 0a 1b 2c 3d + {"BDA", 4, bt_date, BCD, 0, 10, 10, 0}, // date in BCD, 01.01.2000 - 31.12.2099 (0x01,0x01,WW,0x00 - 0x31,0x12,WW,0x99, WW is ignored weekday) + {"BDA", 3, bt_date, BCD, 0, 10, 10, 0}, // date in BCD, 01.01.2000 - 31.12.2099 (0x01,0x01,0x00 - 0x31,0x12,0x99) + //{"HDA", 3, bt_date, 0, 0, 10, 10, 0}, // date, 01.01.2000 - 31.12.2099 (0x01,0x01,0x00 - 0x31,0x12,0x99) + //{"HDA", 4, bt_date, 0, 0, 10, 10, 0}, // date, 01.01.2000 - 31.12.2099 (0x0101WW00 - 0x3112WW99, WW is ignored weekday) + {"BTI", 3, bt_time,BCD|REV,0, 8, 8, 0}, // time in BCD, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x59,0x59,0x23) + {"TTM", 1, bt_time, 0, 0, 5, 5, 0}, // truncated time (only multiple of 10 minutes), 00:00 - 24:00 (minutes div 10 + hour * 6 as integer) + {"BDY", 1, bt_list,BCD|DAY,0, 0, 6, 0}, // weekday, "Mon" - "Sun" + {"HDY", 1, bt_list,BCD|DAY,0, 1, 7, 0}, // weekday, "Mon" - "Sun" // TODO different range + {"BCD", 1, bt_number,BCD|LST,0xff, 0, 0x99, 1}, // unsigned decimal in BCD, 0 - 99 + {"UCH", 1, bt_number, LST, 0xff, 0, 0xff, 1}, // unsigned integer, 0 - 255 + {"SCH", 1, bt_number, SIG, 0x80, 0x80, 0x7f, 1}, // signed integer, -128 - +127 + {"D1B", 1, bt_number, SIG, 0x80, 0x81, 0x7f, 1}, // signed integer, -127 - +127 + {"D1C", 1, bt_number, 0, 0xff, 0x00, 0xc8, 2}, // unsigned number (fraction 1/2), 0 - 100 (0x00 - 0xc8, replacement 0xff) + {"UIN", 2, bt_number, LST, 0xffff, 0, 0xffff, 1}, // unsigned integer, 0 - 65535 + {"SIN", 2, bt_number, SIG, 0x8000, 0x8000, 0x7fff, 1}, // signed integer, -32768 - +32767 + {"FLT", 2, bt_number, SIG, 0x8000, 0x8000, 0x7fff, 1000}, // signed number (fraction 1/1000), -32.768 - +32.767 + {"D2B", 2, bt_number, SIG, 0x8000, 0x8001, 0x7fff, 256}, // signed number (fraction 1/256), -127.99 - +127.99 + {"D2C", 2, bt_number, SIG, 0x8000, 0x8001, 0x7fff, 16}, // signed number (fraction 1/16), -2047.9 - +2047.9 + {"ULG", 4, bt_number, LST, 0xffffffff, 0, 0xffffffff, 1}, // unsigned integer, 0 - 4294967295 + {"SLG", 4, bt_number, SIG, 0x80000000, 0x80000000, 0xffffffff, 1}, // signed integer, -2147483648 - +2147483647 +}; -static const char* days[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; +/** the week day names. */ +static const char* dayNames[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}; DataField* DataField::create(const unsigned char dstAddress, const bool isSetMessage, @@ -44,60 +70,49 @@ DataField* DataField::create(const unsigned char dstAddress, const bool isSetMes if (it == end) return NULL; name = *it++; - if (it == end) + if (it == end || name.size() == 0) return NULL; const char* posStr = (*it++).c_str(); if (it == end) return NULL; - if (posStr[0] >= 'a' && posStr[1] == 0) { - if (posStr[0] == 's') { // slave ACK - // QQ ZZ PB SB NN + Dx + CRC - //offset = 5+command.getMasterDataLength()+1; // skip QQ ZZ PB SB NN Dx CRC - } else if (posStr[0] == 'm') { // master ACK - //offset = 5+command.getMasterDataLength()+3+command.getSlaveDataLength()+1; // skip QQ ZZ PB SB NN Dx CRC ACK NN Dx CRC - } else { - return NULL; // TODO error code: invalid pos definition - } + if (dstAddress == BROADCAST + || isMaster(dstAddress) + || (isSetMessage == true && posStr[0] <= '9') + || posStr[0] == 'm') { // master data + partType = pt_masterData; + baseOffset = 5; // skip QQ ZZ PB SB NN + //len = command.getMasterDataLength(); + if (posStr[0] == 'm') + posStr++; + } else if ((isSetMessage == false && posStr[0] <= '9') + || posStr[0] == 's') { // slave data + baseOffset = 1; + //offset = 5+command.getMasterDataLength()+3; // skip QQ ZZ PB SB NN Dx CRC ACK NN + //len = command.getSlaveDataLength(); + if (posStr[0] == 's') + posStr++; } else { - if (dstAddress == BROADCAST - || isMaster(dstAddress) - || (isSetMessage == true && posStr[0] <= '9') - || posStr[0] == 'm') { // master data - partType = pt_masterData; - baseOffset = 5; // skip QQ ZZ PB SB NN - //len = command.getMasterDataLength(); - if (posStr[0] == 'm') - posStr++; - } else if ((isSetMessage == false && posStr[0] <= '9') - || posStr[0] == 's') { // slave data - baseOffset = 1; - //offset = 5+command.getMasterDataLength()+3; // skip QQ ZZ PB SB NN Dx CRC ACK NN - //len = command.getSlaveDataLength(); - if (posStr[0] == 's') - posStr++; - } else { + return NULL; // TODO error code: invalid pos definition + } + std::string token; + std::istringstream stream(posStr); + while (std::getline(stream, token, '-') != 0) { + if (++offsetCnt > 2) return NULL; // TODO error code: invalid pos definition - } - std::string token; - std::istringstream stream(posStr); - while (std::getline(stream, token, '-') != 0) { - if (++offsetCnt > 2) - return NULL; // TODO error code: invalid pos definition - const char* start = token.c_str(); - char* end = NULL; - int pos = strtoul(start, &end, 10)-1; // 1-based - if (end != start+strlen(start)) - return NULL; // TODO error code: invalid pos definition - if (pos < 0 || baseOffset+pos > maxPos) - return NULL; // TODO error code: invalid pos definition - else if (offsetCnt==1) - offset = baseOffset+pos; - else if (baseOffset+pos >= offset) - length = baseOffset+pos+1-offset; - else { // wrong order e.g. 4-3 - length = offset-(baseOffset+pos+1); - offset = baseOffset+pos; - } + const char* start = token.c_str(); + char* end = NULL; + unsigned int pos = strtoul(start, &end, 10)-1; // 1-based + if (end != start+strlen(start)) + return NULL; // TODO error code: invalid pos definition + if (baseOffset+pos > maxPos) + return NULL; // TODO error code: invalid pos definition + else if (offsetCnt==1) + offset = baseOffset+pos; + else if (baseOffset+pos >= offset) + length = baseOffset+pos+1-offset; + else { // wrong order e.g. 4-3 + length = offset-(baseOffset+pos+1); + offset = baseOffset+pos; } } @@ -130,104 +145,66 @@ DataField* DataField::create(const unsigned char dstAddress, const bool isSetMes comment.clear(); } - if (strcasecmp(typeStr, "STR") == 0) { - if (length == 0) - length = 1; - return new StringDataField(name, partType, offset, length, dt_string, unit, comment); + std::map values; + if (it != end) { + std::istringstream stream(*it++); + while (std::getline(stream, token, ',') != 0) { + const char* start = token.c_str(); + char* end = NULL; + unsigned int id = strtoul(start, &end, 10)-1; // 1-based + if (end == NULL || end == start || *end != '=') + return NULL; // TODO error code: invalid values definition + values[id] = std::string(end+1); + } } - if (strcasecmp(typeStr, "HEX") == 0) { - if (length == 0) - length = 1; - return new StringDataField(name, partType, offset, length, dt_hex, unit, comment); - } - if (strcasecmp(typeStr, "UCH") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 1, dt_uchar, unit, comment, factor, 0xff); - } - if (strcasecmp(typeStr, "SCH") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 1, dt_schar, unit, comment, factor, 0x80); - } - if (strcasecmp(typeStr, "BCD") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 1, dt_bcd, unit, comment, factor, 0xff); // TODO max value 99 - } - if (strcasecmp(typeStr, "D1B") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 1, dt_d1b, unit, comment, factor, 0x80); - } - if (strcasecmp(typeStr, "D1C") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 1, dt_d1c, unit, comment, factor*0.5, 0xff); // TODO max value 100 - } - if (strcasecmp(typeStr, "UIN") == 0) { - if (length != 0 && length != 2) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 2, dt_uint, unit, comment, factor, 0xffff); - } - if (strcasecmp(typeStr, "SIN") == 0) { - if (length != 0 && length != 2) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 2, dt_sint, unit, comment, factor, 0x8000); - } - if (strcasecmp(typeStr, "D2B") == 0) { - if (length != 0 && length != 2) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 2, dt_d2b, unit, comment, factor/256.0, 0x8000); - } - if (strcasecmp(typeStr, "D2C") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 2, dt_d2c, unit, comment, factor/16.0, 0x8000); - } - if (strcasecmp(typeStr, "ULG") == 0) { - if (length != 0 && length != 4) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 4, dt_ulong, unit, comment, factor, 0xffffffff); - } - if (strcasecmp(typeStr, "SLG") == 0) { - if (length != 0 && length != 4) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 4, dt_slong, unit, comment, factor, 0x80000000); - } - if (strcasecmp(typeStr, "FLT") == 0) { - if (length != 0 && length != 2) - return NULL; // TODO error code: invalid pos definition - return new NumericDataField(name, partType, offset, 2, dt_float, unit, comment, factor/1000.0, 0x8000); // TODO replacement - } - if (strcasecmp(typeStr, "BDA") == 0 || strcasecmp(typeStr, "HDA") == 0) { - if (length == 0) - length = 4; - else if (length != 3 && length != 4) - return NULL; // TODO error code: invalid pos definition - return new StringDataField(name, partType, offset, length, dt_date, unit, comment); // TODO better numeric? - } - if (strcasecmp(typeStr, "BDY") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new StringDataField(name, partType, offset, 1, dt_day, unit, comment); // TODO better numeric? - } - if (strcasecmp(typeStr, "BTI") == 0 || strcasecmp(typeStr, "HTI") == 0) { - if (length != 0 && length != 3) - return NULL; // TODO error code: invalid pos definition - return new StringDataField(name, partType, offset, 3, dt_time, unit, comment); // TODO better numeric? - } - if (strcasecmp(typeStr, "TTM") == 0) { - if (length != 0 && length != 1) - return NULL; // TODO error code: invalid pos definition - return new StringDataField(name, partType, offset, 1, dt_tTime, unit, comment); // TODO better numeric? + + for (size_t i = 0; i < sizeof(dataTypes)/sizeof(dataTypes[0]); i++) { + dataType_t dataType = dataTypes[i]; + if (strcasecmp(typeStr, dataType.name) == 0) { + if ((dataType.flags&ADJ) != 0) { + if (length == 0) + length = 1; // minimum length defaults to 1 + else if (length > dataType.numBytes) + return NULL; // invalid length + } + else if (length == 0) + length = dataType.numBytes; + else if (length != dataType.numBytes) + continue; // check for another one with same name but different length + + switch (dataType.type) { + case bt_str: + case bt_hexstr: + case bt_date: // TODO better numeric? + case bt_time: // TODO better numeric? + return new StringDataField(name, partType, offset, length, dataType, unit, comment); + case bt_list: + if (values.empty() == false) { + if (values.begin()->first < dataType.minValueOrLength) + return NULL; // invalid value id + std::map::iterator end = values.end(); + end--; + if (end->first > dataType.maxValueOrLength) + return NULL; // invalid value id + } + else if ((dataType.flags&DAY) != 0) { + for (unsigned int i=0; i 0) @@ -250,82 +227,80 @@ const std::string DataField::parseSymbols(SymbolString& masterData, SymbolString return output.str(); } -bool DataField::formatSymbols(const std::string& value, SymbolString& masterData, SymbolString& slaveData) +bool DataField::write(const std::string& value, SymbolString& masterData, SymbolString& slaveData) { - SymbolString& data = m_partType == pt_masterData ? masterData : slaveData; + SymbolString& output = m_partType == pt_masterData ? masterData : slaveData; switch (m_partType) { case pt_masterData: - break; case pt_slaveData: break; default: return false; // TODO error code } std::istringstream input(value); - if (format(data, input) == false) + if (writeSymbols(input, output) == false) return false; // TODO error code return true; } -bool StringDataField::parse(SymbolString& data, std::ostringstream& output) +bool StringDataField::readSymbols(SymbolString& input, std::ostringstream& output) { - size_t start = m_offset, end = m_offset+m_length; + size_t start = m_offset, end = m_offset + m_length; + int incr = 1; unsigned char ch; - if (end > data.size()) { + if (end > input.size()) return false; // TODO error not enough data available - } - if (m_dataType == dt_time) { // reverse order - for (size_t i = end-1; i >= start; i--) { - ch = data[i]; - if ((ch&0xf0) > 0x90 || (ch&0x0f) > 0x09) - return false; // invalid BCD - if ((i == end-1 && ch > 0x23) || (i != end-1 && ch > 0x59)) - return false; // invalid time - if (i != end-1) - output << ":"; - output << std::setw(2) << std::setfill('0') << std::hex << static_cast(ch); - } - return true; + + if ((m_dataType.flags&REV) != 0) { // reverted binary representation (most significant byte first) + end = start - 1; + start = m_offset + m_length - 1; + incr = -1; } - for (size_t i = start; i < end; i++) { - ch = data[i]; - switch (m_dataType) { - case dt_hex: - if (i != start) - output << " "; + for (size_t pos = start, i = 0; pos != end; pos += incr, i++) { + if (m_length == 4 && i == 2 && m_dataType.type == bt_date) + continue; // skip weekday in between + ch = input[pos]; + if ((m_dataType.flags & BCD) != 0) { + if ((ch & 0xf0) > 0x90 || (ch & 0x0f) > 0x09) + return false; // invalid BCD + ch = (ch >> 4) * 10 + (ch & 0x0f); + } + switch (m_dataType.type) { + case bt_hexstr: + if (i > 0) + output << ' '; output << std::nouppercase << std::setw(2) << std::hex << std::setfill('0') << static_cast(ch); break; - case dt_date: - if (m_length == 4u && i == m_offset+2u) - break; // skip weekday in between - if ((ch&0xf0) > 0x90 || (ch&0x0f) > 0x09) - return false; // invalid BCD - if (i == end-1) - output << std::hex << (0x2000+ch); - else if (ch < 0x01 || (i == start && ch > 0x31) || (i == start+1 && ch > 0x12)) + case bt_date: + if (i + 1 == m_length) + output << (2000+ch); + else if (ch < 1 || (i == 0 && ch > 31) || (i == 1 && ch > 12)) return false; // invalid date else - output << std::setw(2) << std::setfill('0') << std::hex << static_cast(ch) << "."; + output << std::setw(2) << std::setfill('0') << static_cast(ch) << "."; break; - case dt_day: - if (ch < 1 || ch > 7) - return false; // invalid day - output << days[ch-1]; - break; - case dt_tTime: - if (ch/6 > 23) + case bt_time: + if (m_length == 1) { // truncated time + if (ch > 24*6) + return false; // invalid time + output << std::setw(2) << std::setfill('0') << static_cast(ch/6) << ":" + << std::setw(2) << std::setfill('0') << static_cast((ch%6)*10); + break; + } + if (i > 0) + output << ":"; + if ((i == 0 && ch > 23) || (i > 0 && ch > 59)) return false; // invalid time - output << std::setw(2) << std::setfill('0') << static_cast(ch/6) << ":" - << std::setw(2) << std::setfill('0') << static_cast((ch%6)*10); + output << std::setw(2) << std::setfill('0') << static_cast(ch); break; default: if (ch < 0x20) - ch = 0x20; + ch = m_dataType.replacement; output << static_cast(ch); break; } @@ -334,36 +309,24 @@ bool StringDataField::parse(SymbolString& data, std::ostringstream& output) return true; } -bool StringDataField::format(SymbolString& data, std::istringstream& input) +bool StringDataField::writeSymbols(std::istringstream& input, SymbolString& output) { - size_t start = m_offset, end = m_offset+m_length; + size_t start = m_offset, end = m_offset + m_length; + int incr = 1; const char* str; char* strEnd; - unsigned long int value, hours; - unsigned char ch; - + unsigned long int value = 0, minutes = 0; std::string token; - if (m_dataType == dt_time) { // reverse order - for (size_t i = end-1; i >=start; i--) { - if (std::getline(input, token, ':') == 0) - return false; - str = token.c_str(); - strEnd = NULL; - value = strtoul(str, &strEnd, 16); - if (strEnd != str+strlen(str)) - return false; // TODO error code: invalid value - if ((value&0xf0) > 0x90 || (value&0x0f) > 0x09) - return false; // invalid BCD - if ((i == end-1 && value > 0x23) || (i != end-1 && value > 0x59)) - return false; // invalid time - data[i] = (unsigned char)value; - } - return true; + + if ((m_dataType.flags&REV) != 0) { // reverted binary representation (most significant byte first) + end = start - 1; + start = m_offset + m_length - 1; + incr = -1; } - for (size_t i = start; i < end; i++) { - switch (m_dataType) { - case dt_hex: + for (size_t pos = start, i = 0; pos != end; pos += incr, i++) { + switch (m_dataType.type) { + case bt_hexstr: while (input.peek()==' ') input.get(); token.clear(); @@ -379,50 +342,23 @@ bool StringDataField::format(SymbolString& data, std::istringstream& input) value = strtoul(str, &strEnd, 16); if (strEnd != str+strlen(str)) return false; // TODO error code: invalid value - data[i] = (unsigned char)value; break; - case dt_date: - if (m_length == 4u && i == m_offset+2u) - break; // skip weekday in between + case bt_date: + if (m_length == 4 && i == 2) + continue; // skip weekday in between if (std::getline(input, token, '.') == 0) return false; str = token.c_str(); strEnd = NULL; - value = strtoul(str, &strEnd, 16); + value = strtoul(str, &strEnd, 10); if (strEnd != str+strlen(str)) return false; // TODO error code: invalid value - if ((value&0xf0) > 0x90 || (value&0x0f) > 0x09) - return false; // invalid BCD - if (i == end-1) { - if (value>=0x2000) - value -= 0x2000; - if (value>=0x100) - return false; // invalid year - } - else if (value < 1 || (i == start && value > 0x31) || (i == start+1 && value > 0x12)) + if (i + 1 == m_length && value >= 2000) + value -= 2000; + else if (value < 1 || (i == 0 && value > 31) || (i == 1 && value > 12)) return false; // invalid date - data[i] = (unsigned char)value; break; - case dt_day: - str = input.str().c_str(); - for (ch = 0; ch < 7; ch++) - if (strcasecmp(days[ch], str) == 0) - break; - if (ch == 7) - return false; // invalid day - data[i] = ch+1; - break; - case dt_tTime: - if (std::getline(input, token, ':') == 0) - return false; - str = token.c_str(); - strEnd = NULL; - hours = strtoul(str, &strEnd, 10); - if (strEnd != str+strlen(str)) - return false; // TODO error code: invalid value - if (hours > 23) - return false; // invalid time - + case bt_time: if (std::getline(input, token, ':') == 0) return false; str = token.c_str(); @@ -430,146 +366,234 @@ bool StringDataField::format(SymbolString& data, std::istringstream& input) value = strtoul(str, &strEnd, 10); if (strEnd != str+strlen(str)) return false; // TODO error code: invalid value - if (value > 59 || (value%10) != 0) + if (m_length == 1) { // truncated time + if (std::getline(input, token, ':') == 0) + return false; + str = token.c_str(); + strEnd = NULL; + minutes = strtoul(str, &strEnd, 10); + if (strEnd != str+strlen(str)) + return false; // TODO error code: invalid value + if ((minutes % 10) != 0) + return false; // invalid time + value = value*6 + (minutes / 10); + if (value > 24*6) + return false; // invalid time + break; + } + if ((i == 0 && value > 23) || (i > 0 && value > 59)) return false; // invalid time - data[i] = (unsigned char)(hours*6 + value/10); break; default: - ch = input.get(); - if (input.eof() == true || ch < 0x20) - ch = 0x20; - data[i] = ch; + value = input.get(); + if (input.eof() == true || value < 0x20) + value = m_dataType.replacement; break; } + if ((m_dataType.flags & BCD) != 0) { + if (value > 99) + return false; // invalid BCD + value = (value/10)<<4 | (value%10); + } + if (value > 0xff) + return false; + output[pos] = (unsigned char)value; } return true; } -bool NumericDataField::parse(SymbolString& data, std::ostringstream& output) +bool NumericDataField::readRawValue(SymbolString& input, unsigned int& value) { - size_t start = m_offset, end = m_offset+m_length; - unsigned int value = 0; - int signedValue; + size_t start = m_offset, end = m_offset + m_length; + int incr = 1; unsigned char ch; - if (end > data.size()) + if (end > input.size()) return false; // TODO error not enough data available - for (size_t i = start, exp = 1; i < end; i++) { - ch = data[i]; - switch (m_dataType) { - case dt_bcd: - if (ch == m_replacement) { - output << "-"; + if ((m_dataType.flags&REV) != 0) { // reverted binary representation (most significant byte first) + end = start - 1; + start = m_offset + m_length - 1; + incr = -1; + } + + value = 0; + for (size_t pos = start, exp = 1; pos != end; pos += incr) { + ch = input[pos]; + if ((m_dataType.flags & BCD) != 0) { + if (ch == m_dataType.replacement) { + value = m_dataType.replacement; return true; } - else if ((ch&0xf0) > 0x90 || (ch&0x0f) > 0x09) + if ((ch & 0xf0) > 0x90 || (ch & 0x0f) > 0x09) return false; // invalid BCD - else - value |= ((ch>>4)*10 + (ch&0x0f))*exp; + + ch = (ch >> 4) * 10 + (ch & 0x0f); + value += ch*exp; exp = exp*100; - break; - default: + } + else { value |= ch*exp; exp = exp<<8; - break; } } - if (value == m_replacement) { + return true; +} + +bool NumericDataField::writeRawValue(unsigned int value, SymbolString& output) +{ + size_t start = m_offset, end = m_offset + m_length; + int incr = 1; + unsigned char ch; + + if ((m_dataType.flags&REV) != 0) { // reverted binary representation (most significant byte first) + end = start - 1; + start = m_offset + m_length - 1; + incr = -1; + } + + for (size_t pos = start, exp = 1; pos != end; pos += incr) { + if ((m_dataType.flags & BCD) != 0) { + if (value == m_dataType.replacement) + ch = m_dataType.replacement; + else { + ch = (value/exp)%100; + ch = ((ch/10)<<4) | (ch%10); + } + exp = exp*100; + } + else { + ch = (value/exp)&0xff; + exp = exp<<8; + } + output[pos] = ch; + } + + return true; +} + + +bool NumberDataField::readSymbols(SymbolString& input, std::ostringstream& output) +{ + unsigned int value = 0; + int signedValue; + + if (readRawValue(input, value) == false) + return false; + + if (value == m_dataType.replacement) { output << "-"; return true; } - switch (m_dataType) { - case dt_schar: - case dt_d1b: - signedValue = (char)value; - break; - case dt_d2b: - case dt_d2c: - case dt_sint: - signedValue = (short)value; - break; - case dt_slong: - signedValue = (int)value; - break; - case dt_ulong: - if (m_factor == 1.0) - output << std::setprecision(0) << std::fixed << static_cast(value); + if ((m_dataType.flags&SIG) != 0 && (value & (1 << (m_dataType.numBytes*8 - 1))) != 0) // negative signed value + if (m_dataType.numBytes == 4) + signedValue = (int)value; else - output << std::setprecision(3) << std::fixed << static_cast(value * m_factor); - return true; - default: - signedValue = value; - break; + signedValue = (int)value - (1 << (m_dataType.numBytes*8)); + else { + if (m_dataType.numBytes == 4) { + if (m_factor == 1.0) + output << static_cast(value); + else + output << std::setprecision(3) << std::fixed << static_cast(value * m_factor); + return true; + } + + signedValue = (int)value; } + if (m_factor == 1.0) - output << std::setprecision(0) << std::fixed << static_cast(signedValue); + output << static_cast(signedValue); else output << std::setprecision(3) << std::fixed << static_cast(signedValue * m_factor); return true; } -bool NumericDataField::format(SymbolString& data, std::istringstream& input) +bool NumberDataField::writeSymbols(std::istringstream& input, SymbolString& output) { - size_t start = m_offset, end = m_offset+m_length; unsigned int value; - unsigned char ch; const char* str = input.str().c_str(); if (strcasecmp(str, "-") == 0) // replacement value - value = m_replacement; + value = m_dataType.replacement; else { char* strEnd = NULL; - double dvalue = strtod(str, &strEnd); - if (strEnd != str+strlen(str)) - return false; // TODO error code: invalid value - dvalue /= m_factor; - switch (m_dataType) { - case dt_schar: - case dt_sint: - case dt_slong: - case dt_d1b: - case dt_d2b: - case dt_d2c: - if (dvalue < -(1LL<<(8*m_length)) || dvalue >= (1LL<<(8*m_length))) + if (m_factor == 1.0) { + if ((m_dataType.flags&SIG) != 0) { + int signedValue = strtol(str, &strEnd, 10); + if (signedValue < 0 && m_dataType.numBytes != 4) + value = (unsigned int)(signedValue + (1<<(m_dataType.numBytes*8))); + else + value = (unsigned int)signedValue; + } + else + value = strtoul(str, &strEnd, 10); + if (strEnd != str+strlen(str)) return false; // TODO error code: invalid value - break; - default: - // no special handling - if (dvalue < 0.0 || dvalue >= (1LL<<(8*m_length))) + } + else { + char* strEnd = NULL; + double dvalue = strtod(str, &strEnd); + if (strEnd != str+strlen(str)) return false; // TODO error code: invalid value - break; - } - value = (unsigned int)dvalue; - } - - for (size_t i = start, exp = 1; i < end; i++) { - switch (m_dataType) { - case dt_bcd: - if (value == m_replacement) - ch = m_replacement; + dvalue /= m_factor; + if ((m_dataType.flags&SIG) != 0) { + if (dvalue < -(1LL<<(8*m_length)) || dvalue >= (1LL<<(8*m_length))) + return false; // TODO error code: invalid value + if (dvalue < 0 && m_dataType.numBytes != 4) + value = (unsigned int)(dvalue + (1<<(m_dataType.numBytes*8))); + else + value = (unsigned int)dvalue; + } else { - ch = (value/exp)%100; - ch = ((ch/10)<<4) | (ch%10); + if (dvalue < 0.0 || dvalue >= (1LL<<(8*m_length))) + return false; // TODO error code: invalid value + value = (unsigned int)dvalue; } - exp = exp*100; - break; - default: - ch = (value/exp)&0xff; - exp = exp<<8; - break; } - data[i] = ch; } + return writeRawValue(value, output); +} + + +bool ValueListDataField::readSymbols(SymbolString& input, std::ostringstream& output) +{ + unsigned int value = 0; + + if (readRawValue(input, value) == false) + return false; + + if (value == m_dataType.replacement) { + output << "-"; + return true; + } + + std::map::iterator it = m_values.find(value); + if (it == m_values.end()) + return false; + + output << it->second; return true; } +bool ValueListDataField::writeSymbols(std::istringstream& input, SymbolString& output) +{ + std::string str; + input >> str; + + for (std::map::iterator it = m_values.begin(); it != m_values.end(); it++) + if (it->second.compare(str) == 0) + return writeRawValue(it->first, output); + + return false; +} + } //namespace diff --git a/src/libebus/data.h b/src/libebus/data.h old mode 100644 new mode 100755 index 756e584a..bd5d40c1 --- a/src/libebus/data.h +++ b/src/libebus/data.h @@ -23,6 +23,7 @@ #include "symbol.h" #include #include +#include namespace libebus { @@ -30,33 +31,39 @@ namespace libebus /** the message part in which a data field is stored. */ enum PartType { - pt_masterData, // stored in master data - //pt_slaveAck, // stored in slave acknowledge // TODO implement if reasonable - pt_slaveData, // stored in slave data - //pt_masterAck // stored in master acknowledge // TODO implement if reasonable - }; + pt_masterData, // stored in master data + pt_slaveData, // stored in slave data + }; -/** the available data types. */ -enum DataType { - dt_string, // string of characters with fixed length>0, filled up with space - dt_hex, // string of hex digits with fixed length>0, separated by space - dt_bcd, // length: 1 byte, binary coded number, string format: "%d" - dt_uchar, - dt_schar, - dt_uint, - dt_sint, - dt_ulong, - dt_slong, - dt_float, - dt_d1b, - dt_d1c, - dt_d2b, - dt_d2c, - dt_date, // length: 4 bytes (with skipped weekday) or 3 bytes (without weekday), string format: "dd.mm.yyyy" - dt_day, // length: 1 byte, string format: "www" (3 character weekday name) - dt_time, // length: 3 bytes, string format: "hh:mm" - dt_tTime // length: 1 byte, string format: "hh:mm" - }; +/** the available base data types. */ +enum BaseType { + bt_str, // text string in a StringDataField + bt_hexstr, // hex digit string in a StringDataField + bt_date, // date in a StringDataField + bt_time, // time in a StringDataField + bt_list, // numeric list value in a ValueListDataField + bt_number // number value in a NumberDataField +}; + +/** flags for dataType_t. */ +const unsigned int ADJ = 0x01; // adjustable length, numBytes is maximum length +const unsigned int BCD = 0x02; // binary representation is BCD +const unsigned int REV = 0x04; // reverted binary representation (most significant byte first) +const unsigned int SIG = 0x08; // signed value +const unsigned int LST = 0x10; // value list is possible (without applied factor) +const unsigned int DAY = 0x20; // default value list is week days + +/** the structure for defining field types with their properties. */ +typedef struct { + const char* name; // field identifier + const unsigned int numBytes; // number of bytes (maximum length if ADJ flag is set) + const BaseType type; // base data type + const unsigned int flags; // flags (e.g. BCD) + const unsigned int replacement; // replacement value (fill-up value for bt_str/bt_hexstr) + const unsigned int minValueOrLength; // minimum binary value (minimum length of string for StringDataField) + const unsigned int maxValueOrLength; // maximum binary value (maximum length of string for StringDataField) + const unsigned int divisor; // divisor for bt_number values (or 0 for non-numeric) +} dataType_t; /** @@ -71,13 +78,13 @@ public: * @param partType the message part in which the field is stored. * @param offset the offset to the first symbol in 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 dataType the data type. + * @param dataType the data type definition. * @param unit the value unit. * @param comment the field comment. */ DataField(const std::string name, const PartType partType, const unsigned char offset, const unsigned char length, - const DataType dataType, const std::string unit, + const dataType_t dataType, const std::string unit, const std::string comment) : m_name(name), m_partType(partType), m_offset(offset), m_length(length), m_dataType(dataType), m_unit(unit), @@ -98,35 +105,35 @@ public: std::vector::iterator& it, const std::vector::iterator end); /** - * @brief Parses the value from the master or slave @a SymbolString. - * @param masterData the unescaped master data @a SymbolString to parse from. - * @param slaveData the unescaped slave data @a SymbolString to parse from. - * @return the parsed value as string. + * @brief Reads the value from the master or slave @a SymbolString. + * @param masterData the unescaped master data @a SymbolString for reading binary data. + * @param slaveData the unescaped slave data @a SymbolString for reading binary data. + * @return the formatted value as string. */ - const std::string parseSymbols(SymbolString& masterData, SymbolString& slaveData, bool verbose=false); + const std::string read(SymbolString& masterData, SymbolString& slaveData, bool verbose=false); /** - * @brief Formats the value to the master or slave @a SymbolString. - * @param masterData the unescaped master data @a SymbolString to format to. - * @param slaveData the unescaped slave data @a SymbolString to format to. - * @param value the value as string. + * @brief Writes the value to the master or slave @a SymbolString. + * @param masterData the unescaped master data @a SymbolString for writing binary data. + * @param slaveData the unescaped slave data @a SymbolString for writing binary data. + * @param value the formatted value as string. */ - bool formatSymbols(const std::string& value, SymbolString& masterData, SymbolString& slaveData); + bool write(const std::string& value, SymbolString& masterData, SymbolString& slaveData); protected: /** - * @brief Internal method doing the actual parse for the individual data type. - * @param data the unescaped data SymbolString to parse from. - * @param output the ostringstream to append to. + * @brief Internal method for reading the field from a @a SymbolString. + * @param input the unescaped @a SymbolString to read the binary value from. + * @param output the ostringstream to append the formatted value to. * @return true if the value was parsed successfully. */ - virtual bool parse(SymbolString& data, std::ostringstream& output) = 0; + virtual bool readSymbols(SymbolString& input, std::ostringstream& output) = 0; /** - * @brief Internal method doing the actual format for the individual data type. - * @param data the unescaped data SymbolString to format to. - * @param input the istringstream to interpret. + * @brief Internal method for writing the field to a @a SymbolString. + * @param input the istringstream to parse the formatted value from. + * @param output the unescaped @a SymbolString to write the binary value to. * @return true if the value was formatted successfully. */ - virtual bool format(SymbolString& data, std::istringstream& input) = 0; + virtual bool writeSymbols(std::istringstream& input, SymbolString& output) = 0; /** the field name. */ const std::string m_name; @@ -136,9 +143,8 @@ protected: const unsigned char m_offset; /** the number of symbols in the message part in which the field is stored. */ const unsigned char m_length; - /** the data type. */ - const DataType m_dataType; - //std::vector m_valid;//TODO add list of possible values + /** the data type definition. */ + const dataType_t m_dataType; /** the value unit. */ const std::string m_unit; /** the field comment. */ @@ -158,13 +164,13 @@ public: * @param partType the message part in which the field is stored. * @param offset the offset to the first symbol in 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 dataType the data type. + * @param dataType the data type definition. * @param unit the value unit. * @param comment the field comment. */ StringDataField(const std::string name, const PartType partType, const unsigned char offset, const unsigned char length, - const DataType dataType, const std::string unit, + const dataType_t dataType, const std::string unit, const std::string comment) : DataField(name, partType, offset, length, dataType, unit, comment) {} /** @@ -173,8 +179,8 @@ public: virtual ~StringDataField() {} protected: - virtual bool parse(SymbolString& data, std::ostringstream& output); - virtual bool format(SymbolString& data, std::istringstream& input); + virtual bool readSymbols(SymbolString& input, std::ostringstream& output); + virtual bool writeSymbols(std::istringstream& input, SymbolString& output); }; @@ -191,32 +197,113 @@ public: * @param partType the message part in which the field is stored. * @param offset the offset to the first symbol in 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 dataType the data type. + * @param dataType the data type definition. * @param comment the field comment. * @param unit the value unit. - * @param factor the factor to apply on the value. * @param replacement the (binary) replacement value to use if the value is not set. */ NumericDataField(const std::string name, const PartType partType, const unsigned char offset, const unsigned char length, - const DataType dataType, const std::string unit, - const std::string comment, const float factor, - const unsigned int replacement) - : DataField(name, partType, offset, length, dataType, unit, comment), - m_factor(factor), m_replacement(replacement) {} + const dataType_t dataType, const std::string unit, + const std::string comment) + : DataField(name, partType, offset, length, dataType, unit, comment) {} /** * @brief Destructor. */ virtual ~NumericDataField() {} protected: - virtual bool parse(SymbolString& data, std::ostringstream& output); - virtual bool format(SymbolString& data, std::istringstream& input); + /** + * @brief Internal method for reading the raw value from a @a SymbolString. + * @param input the unescaped @a SymbolString to read the binary value from. + * @param value the variable in which to store the raw value. + * @return true if the value was read successfully. + */ + bool readRawValue(SymbolString& input, unsigned int& value); + /** + * @brief Internal method for writing the raw value to a @a SymbolString. + * @param value the raw value to write. + * @param output the unescaped @a SymbolString to write the binary value to. + * @return true if the value was written successfully. + */ + bool writeRawValue(unsigned int value, SymbolString& output); + +}; + +/** + * @brief Base class for all numeric data fields with a number representation. + */ +class NumberDataField : public NumericDataField +{ +public: + /** + * @brief Constructs a new instance. + * @param name the field name. + * @param partType the message part in which the field is stored. + * @param offset the offset to the first symbol in 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 dataType the data type definition. + * @param comment the field comment. + * @param unit the value unit. + * @param replacement the (binary) replacement value to use if the value is not set. + * @param factor the factor to apply on the value. + */ + NumberDataField(const std::string name, const PartType partType, + const unsigned char offset, const unsigned char length, + const dataType_t dataType, const std::string unit, + const std::string comment, const float factor) + : NumericDataField(name, partType, offset, length, dataType, unit, comment), + m_factor(factor / dataType.divisor) {} + /** + * @brief Destructor. + */ + virtual ~NumberDataField() {} + +protected: + virtual bool readSymbols(SymbolString& input, std::ostringstream& output); + virtual bool writeSymbols(std::istringstream& input, SymbolString& output); /** the factor to apply on the value. */ const float m_factor; - /** the (binary) replacement value to use if the value is not set. */ - const unsigned int m_replacement; + +}; + +/** + * @brief A numeric data field with a list of value=text assignments and a string representation. + */ +class ValueListDataField : public NumericDataField +{ +public: + /** + * @brief Constructs a new instance. + * @param name the field name. + * @param partType the message part in which the field is stored. + * @param offset the offset to the first symbol in 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 dataType the data type definition. + * @param comment the field comment. + * @param unit the value unit. + * @param factor the factor to apply on the value. + * @param replacement the (binary) replacement value to use if the value is not set. + * @param values the value=text assignments. + */ + ValueListDataField(const std::string name, const PartType partType, + const unsigned char offset, const unsigned char length, + const dataType_t dataType, const std::string unit, + const std::string comment, const std::map values) + : NumericDataField(name, partType, offset, length, dataType, unit, comment), + m_values(values) {} + /** + * @brief Destructor. + */ + virtual ~ValueListDataField() {} + +protected: + virtual bool readSymbols(SymbolString& input, std::ostringstream& output); + virtual bool writeSymbols(std::istringstream& input, SymbolString& output); + + /** the value=text assignments. */ + std::map m_values; }; diff --git a/src/test/test_data.cpp b/src/test/test_data.cpp old mode 100644 new mode 100755 index 367a45c0..8c8cfae4 --- a/src/test/test_data.cpp +++ b/src/test/test_data.cpp @@ -25,7 +25,6 @@ using namespace libebus; int main () { - //TODO dt_float, //TODO dt_d1b, //TODO dt_d1c, //TODO dt_d2c, @@ -33,23 +32,26 @@ int main () //name;position(s);type;factor;unit;comment // {"temp;1;d2b;;°C;Aussentemperatur","temp=18.004 °C [Aussentemperatur]","10fe070009019258042126100714cc", "00"}, // {"zeit;1;ttm;2;Uhr;","zeit=22:40 Uhr","10feffff0188", "00"}, - {"hex;1-10;hex","53 70 65 69 63 68 65 72 20 20", "10fe07000a53706569636865722020", "00"}, - {"zeit;1;bti","21:04:58","10fe070009580421", "00"}, - {"datum;1;bda","26.10.2014","10fe07000926100714", "00"}, - {"datum;1-3;bda","26.10.2014","10fe070003261014", "00"}, - {"tag;1;bdy","Sun","10fe07000307", "00"}, - {"temp;1;d2b","18.004","10fe0700090112", "00"}, - {"zeit;1;ttm","22:40","10feffff0188", "00"}, - {"bcd;1;bcd","26","10feffff0126", "00"}, - {"bcd;1;bcd","-","10feffff01ff", "00"}, - {"uch;1;uch","38","10feffff0126", "00"}, - {"sch;1;sch","-90","10feffff01a6", "00"}, - {"uin;1;uin","38","10feffff022600", "00"}, - {"sin;1;sin","-90","10feffff02a6ff", "00"}, - {"ulg;1;ulg","38","10feffff0426000000", "00"}, - {"slg;1;slg","-90","10feffff04a6ffffff", "00"}, - {"str;1-9;str","hallo Du!","10feffff0868616c6c6f20447521", "00"}, - {"str;1-9;str","hallo Du ","10feffff0868616c6c6f20447500", "00"}, + {"x;1-10;hex","53 70 65 69 63 68 65 72 20 20", "10fe07000a53706569636865722020", "00"}, + {"x;1;bti","21:04:58","10fe070009580421", "00"}, + {"x;1;bda","26.10.2014","10fe07000926100714", "00"}, + {"x;1-3;bda","26.10.2014","10fe070003261014", "00"}, + {"x;1;hdy","Sun","10fe07000307", "00"}, + {"x;1;bdy","Sun","10fe07000306", "00"}, + {"x;1;d2b","18.004","10fe0700090112", "00"}, + {"x;1;ttm","22:40","10feffff0188", "00"}, + {"x;1;bcd","26","10feffff0126", "00"}, + {"x;1;bcd","-","10feffff01ff", "00"}, + {"x;1;uch","38","10feffff0126", "00"}, + {"x;1;sch","-90","10feffff01a6", "00"}, + {"x;1;d1b","-90","10feffff01a6", "00"}, + {"x;1;uin","38","10feffff022600", "00"}, + {"x;1;sin","-90","10feffff02a6ff", "00"}, + {"x;1;ulg","38","10feffff0426000000", "00"}, + {"x;1;slg","-90","10feffff04a6ffffff", "00"}, + {"x;1;flt","-0.090","10feffff02a6ff", "00"}, + {"x;1-9;str","hallo Du!","10feffff0868616c6c6f20447521", "00"}, + {"x;1-9;str","hallo Du ","10feffff0868616c6c6f20447520", "00"}, }; for (size_t i = 0; i < sizeof(checks)/sizeof(checks[0]); i++) { std::istringstream isstr(checks[i][0]); @@ -72,7 +74,7 @@ int main () std::cout << "create \"" << checks[i][0] << "\" successful" << std::endl; - std::string gotStr = field->parseSymbols(mstr, sstr); + std::string gotStr = field->read(mstr, sstr); if (strcasecmp(gotStr.c_str(), expectStr.c_str()) == 0) std::cout << "parse successful: " << gotStr << std::endl; @@ -82,7 +84,7 @@ int main () SymbolString writeMstr = SymbolString(mstr.getDataStr().substr(0, 10), false); SymbolString writeSstr = SymbolString(sstr.getDataStr().substr(0, 2), false); - if (field->formatSymbols(gotStr, writeMstr, writeSstr) == false) + if (field->write(gotStr, writeMstr, writeSstr) == false) std::cout << "format failed" << std::endl; else { if (mstr == writeMstr && sstr == writeSstr)