Merge remote-tracking branch 'origin/master' into enhanced_device

# Conflicts:
#	src/lib/ebus/device.cpp
This commit is contained in:
john
2019-10-06 14:26:49 +02:00
46 changed files with 887 additions and 359 deletions
Executable → Regular
+25 -11
View File
@@ -647,8 +647,11 @@ size_t SingleDataField::getLength(PartType partType, size_t maxLength) const {
return remainder ? maxLength : m_length;
}
bool SingleDataField::hasFullByteOffset(bool after) const {
bool SingleDataField::hasFullByteOffset(bool after, int16_t& previousFirstBit) const {
if (m_length > 1) {
if (after) {
previousFirstBit = -1;
}
return true;
}
int16_t firstBit;
@@ -658,8 +661,15 @@ bool SingleDataField::hasFullByteOffset(bool after) const {
} else {
firstBit = 0;
}
return (m_dataType->getBitCount() % 8) == 0
|| (after && firstBit + (m_dataType->getBitCount() % 8) >= 8);
bool ret = (m_dataType->getBitCount() % 8) == 0
|| (firstBit == previousFirstBit) || (after && firstBit + (m_dataType->getBitCount() % 8) >= 8);
// std::cout<<(after?"after,":"before,")<<"prev="<<static_cast<unsigned>(previousFirstBit)<<",first="
// <<static_cast<unsigned>(firstBit)<<",length="<<static_cast<unsigned>(m_dataType->getBitCount())
// <<" => "<<(ret?"true":"false")<<"\n";
if (after) {
previousFirstBit = ret ? -1 : firstBit;
}
return ret;
}
size_t SingleDataField::getCount(PartType partType, const char* fieldName) const {
@@ -915,9 +925,10 @@ const DataFieldSet* DataFieldSet::clone() const {
size_t DataFieldSet::getLength(PartType partType, size_t maxLength) const {
size_t length = 0;
bool previousFullByteOffset[] = { true, true, true, true };
int16_t previousFirstBit[] = { -1, -1, -1, -1 };
for (const auto field : m_fields) {
if (field->getPartType() == partType) {
if (!previousFullByteOffset[partType] && !field->hasFullByteOffset(false)) {
if (!previousFullByteOffset[partType] && !field->hasFullByteOffset(false, previousFirstBit[partType])) {
length--;
}
size_t fieldLength = field->getLength(partType, maxLength);
@@ -928,7 +939,7 @@ size_t DataFieldSet::getLength(PartType partType, size_t maxLength) const {
}
length = length + fieldLength;
previousFullByteOffset[partType] = field->hasFullByteOffset(true);
previousFullByteOffset[partType] = field->hasFullByteOffset(true, previousFirstBit[partType]);
}
}
@@ -1010,12 +1021,13 @@ void DataFieldSet::dump(bool prependFieldSeparator, bool asJson, ostream* output
result_t DataFieldSet::read(const SymbolString& data, size_t offset,
const char* fieldName, ssize_t fieldIndex, unsigned int* output) const {
bool previousFullByteOffset = true, found = false, findFieldIndex = fieldIndex >= 0;
int16_t previousFirstBit = -1;
PartType partType = data.isMaster() ? pt_masterData : pt_slaveData;
for (const auto field : m_fields) {
if (field->getPartType() != partType) {
continue;
}
if (!previousFullByteOffset && !field->hasFullByteOffset(false)) {
if (!previousFullByteOffset && !field->hasFullByteOffset(false, previousFirstBit)) {
offset--;
}
result_t result = field->read(data, offset, fieldName, fieldIndex, output);
@@ -1023,7 +1035,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
return result;
}
offset += field->getLength(partType, data.getDataSize()-offset);
previousFullByteOffset = field->hasFullByteOffset(true);
previousFullByteOffset = field->hasFullByteOffset(true, previousFirstBit);
if (result != RESULT_EMPTY) {
found = true;
}
@@ -1049,6 +1061,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
bool leadingSeparator, const char* fieldName, ssize_t fieldIndex,
OutputFormat outputFormat, ssize_t outputIndex, ostream* output) const {
bool previousFullByteOffset = true, found = false, findFieldIndex = fieldIndex >= 0;
int16_t previousFirstBit = -1;
if (outputIndex < 0 && (!m_uniqueNames || ((outputFormat & OF_JSON) && !(outputFormat & OF_NAMES)))) {
outputIndex = 0;
}
@@ -1060,7 +1073,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
}
continue;
}
if (!previousFullByteOffset && !field->hasFullByteOffset(false)) {
if (!previousFullByteOffset && !field->hasFullByteOffset(false, previousFirstBit)) {
offset--;
}
result_t result = field->read(data, offset, leadingSeparator, fieldName, fieldIndex,
@@ -1069,7 +1082,7 @@ result_t DataFieldSet::read(const SymbolString& data, size_t offset,
return result;
}
offset += field->getLength(partType, data.getDataSize()-offset);
previousFullByteOffset = field->hasFullByteOffset(true);
previousFullByteOffset = field->hasFullByteOffset(true, previousFirstBit);
if (result != RESULT_EMPTY) {
found = true;
leadingSeparator = true;
@@ -1099,12 +1112,13 @@ result_t DataFieldSet::write(char separator, size_t offset, istringstream* input
string token;
PartType partType = data->isMaster() ? pt_masterData : pt_slaveData;
bool previousFullByteOffset = true;
int16_t previousFirstBit = -1;
size_t baseOffset = offset;
for (const auto field : m_fields) {
if (field->getPartType() != partType) {
continue;
}
if (!previousFullByteOffset && !field->hasFullByteOffset(false)) {
if (!previousFullByteOffset && !field->hasFullByteOffset(false, previousFirstBit)) {
offset--;
}
result_t result;
@@ -1124,7 +1138,7 @@ result_t DataFieldSet::write(char separator, size_t offset, istringstream* input
return result;
}
offset += fieldLength;
previousFullByteOffset = field->hasFullByteOffset(true);
previousFullByteOffset = field->hasFullByteOffset(true, previousFirstBit);
}
if (usedLength != nullptr) {
+4 -1
View File
@@ -401,10 +401,13 @@ class SingleDataField : public DataField {
/**
* Get whether this field uses a full byte offset.
* @param after @p true to check after consuming the bits, @p false to check before.
* @param previousFirstBit the index to the first bit of the previous field, or -1
* if the previous field used a full byte offset. Will be updated during the call
* when after was true.
* @return @p true if this field uses a full byte offset, @p false if this field
* only consumes a part of a byte and a subsequent field may re-use the same offset.
*/
bool hasFullByteOffset(bool after) const;
bool hasFullByteOffset(bool after, int16_t& previousFirstBit) const;
// @copydoc
size_t getCount(PartType partType = pt_any, const char* fieldName = nullptr) const override;
+1 -1
View File
@@ -696,7 +696,7 @@ result_t NumberDataType::readSymbols(size_t offset, size_t length, const SymbolS
}
}
#endif
if (isnan(val)) {
if (val != val) { // !isnan(val)
if (outputFormat & OF_JSON) {
*output << "null";
} else {
+20 -3
View File
@@ -30,6 +30,9 @@
#ifdef HAVE_LINUX_SERIAL
# include <linux/serial.h>
#endif
#ifdef HAVE_FREEBSD_UFTDI
# include <dev/usb/uftdiio.h>
#endif
#include <errno.h>
#ifdef HAVE_PPOLL
# include <poll.h>
@@ -392,7 +395,7 @@ result_t SerialDevice::open() {
struct termios newSettings;
// open file descriptor
m_fd = ::open(m_name, O_RDWR | O_NOCTTY);
m_fd = ::open(m_name, O_RDWR | O_NOCTTY | O_NDELAY);
if (m_fd < 0) {
return RESULT_ERR_NOTFOUND;
@@ -415,13 +418,24 @@ result_t SerialDevice::open() {
}
#endif
#ifdef HAVE_FREEBSD_UFTDI
int param = 0;
// flush tx/rx and set low latency on uftdi device
if (ioctl(m_fd, UFTDIIOC_GET_LATENCY, &param) == 0) {
ioctl(m_fd, UFTDIIOC_RESET_IO, &param);
param = 1;
ioctl(m_fd, UFTDIIOC_SET_LATENCY, &param);
}
#endif
// save current settings
tcgetattr(m_fd, &m_oldSettings);
// create new settings
memset(&newSettings, 0, sizeof(newSettings));
newSettings.c_cflag |= ((m_enhancedProto ? B115200 : B2400) | CS8 | CLOCAL | CREAD);
cfsetspeed(&newSettings, m_enhancedProto ? B115200 : B2400);
newSettings.c_cflag |= (CS8 | CLOCAL | CREAD);
newSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // non-canonical mode
newSettings.c_iflag |= IGNPAR; // ignore parity errors
newSettings.c_oflag &= ~OPOST;
@@ -434,7 +448,10 @@ result_t SerialDevice::open() {
tcflush(m_fd, TCIFLUSH);
// activate new settings of serial device
tcsetattr(m_fd, TCSAFLUSH, &newSettings);
if (tcsetattr(m_fd, TCSAFLUSH, &newSettings)) {
close();
return RESULT_ERR_DEVICE;
}
// set serial device into blocking mode
fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) & ~O_NONBLOCK);
+1
View File
@@ -22,6 +22,7 @@
#include <unistd.h>
#include <termios.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <iostream>
#include <fstream>
+1 -1
View File
@@ -120,7 +120,7 @@ void FileReader::tolower(string* str) {
static size_t hashFunction(const string& str) {
size_t hash = 0;
for (char c : str) {
for (unsigned char c : str) {
hash = (31 * hash) ^ c;
}
return hash;
Executable → Regular
+82 -55
View File
@@ -45,12 +45,24 @@ using std::endl;
/** the bit mask for the ID length and combined ID bytes in the message key. */
#define ID_LENGTH_AND_IDS_MASK ((7LL << (8 * 7 + 5)) | 0xffffffffLL)
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active read message. */
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active write message. */
#define ID_SOURCE_ACTIVE_WRITE (0x1fLL << (8 * 7))
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active write message. */
/** the bits in the @a ID_SOURCE_MASK for arbitrary source and active read message. */
#define ID_SOURCE_ACTIVE_READ (0x1eLL << (8 * 7))
/**
* the bits in the @a ID_SOURCE_MASK for arbitrary source and active write message
* to a master (same value as ID_SOURCE_ACTIVE_WRITE for now).
*/
#define ID_SOURCE_ACTIVE_WRITE_MASTER (0x1fLL << (8 * 7))
/**
* the bits in the @a ID_SOURCE_MASK for arbitrary source and active read message
* to a master (same value as ID_SOURCE_ACTIVE_WRITE for now).
*/
#define ID_SOURCE_ACTIVE_READ_MASTER (0x1eLL << (8 * 7))
/** special value for invalid message key. */
#define INVALID_KEY 0xffffffffffffffffLL
@@ -79,6 +91,9 @@ static const char* defaultMessageFieldMap[] = { // access level not included in
"*name", "part", "type", "divisor/values", "unit", "comment",
};
/** the m_pollOrder of the last polled message. */
static unsigned int g_lastPollOrder = 0;
extern DataFieldTemplates* getTemplates(const string& filename);
extern result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose,
@@ -99,7 +114,7 @@ Message::Message(const string& circuit, const string& level, const string& name,
m_data(data), m_deleteData(deleteData),
m_pollPriority(pollPriority),
m_usedByCondition(false), m_isScanMessage(false), m_condition(condition),
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollCount(0), m_lastPollTime(0) {
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollOrder(0), m_lastPollTime(0) {
if (circuit == "scan") {
setScanMessage();
m_pollPriority = 0;
@@ -116,7 +131,7 @@ Message::Message(const string& circuit, const string& level, const string& name,
m_data(data), m_deleteData(deleteData),
m_pollPriority(0),
m_usedByCondition(false), m_isScanMessage(true), m_condition(nullptr),
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollCount(0), m_lastPollTime(0) {
m_lastUpdateTime(0), m_lastChangeTime(0), m_pollOrder(0), m_lastPollTime(0) {
}
@@ -158,7 +173,8 @@ uint64_t Message::createKey(const vector<symbol_t>& id, bool isWrite, bool isPas
if (isPassive) {
key |= (uint64_t)getMasterNumber(srcAddress) << (8 * 7); // 0..25
} else {
key |= (isWrite ? 0x1fLL : 0x1eLL) << (8 * 7); // special values for active
key |= isMaster(dstAddress) ? (isWrite ? ID_SOURCE_ACTIVE_WRITE_MASTER : ID_SOURCE_ACTIVE_READ_MASTER)
: (isWrite ? ID_SOURCE_ACTIVE_WRITE : ID_SOURCE_ACTIVE_READ); // special values for active
}
key |= (uint64_t)dstAddress << (8 * 6);
int exp = 5;
@@ -199,7 +215,7 @@ uint64_t Message::createKey(const MasterSymbolString& master, size_t maxIdLength
uint64_t Message::createKey(symbol_t pb, symbol_t sb, bool broadcast) {
uint64_t key = 0;
key |= (broadcast ? 0x1fLL : 0x1eLL) << (8 * 7); // special values for active
key |= broadcast ? ID_SOURCE_ACTIVE_WRITE : ID_SOURCE_ACTIVE_READ; // special values for active
key |= (uint64_t)(broadcast ? BROADCAST : SYN) << (8 * 6);
key |= (uint64_t)pb << (8 * 5);
key |= (uint64_t)sb << (8 * 4);
@@ -596,9 +612,29 @@ bool Message::setPollPriority(size_t priority) {
}
bool ret = m_pollPriority == 0 && usePriority > 0;
m_pollPriority = usePriority;
if (ret || m_pollOrder > g_lastPollOrder+(unsigned int)m_pollPriority) {
// ensure a later increased or newly set priority does not prefer that message before all others
m_pollOrder = g_lastPollOrder+(unsigned int)m_pollPriority;
}
return ret;
}
bool Message::isLessPollWeight(const Message* other) const {
if (m_pollOrder > other->m_pollOrder) {
return true;
}
if (m_pollOrder < other->m_pollOrder) {
return false;
}
if (m_pollPriority > other->m_pollPriority) {
return true;
}
if (m_pollPriority < other->m_pollPriority) {
return false;
}
return m_lastPollTime > other->m_lastPollTime;
}
void Message::setUsedByCondition() {
if (m_usedByCondition) {
return;
@@ -786,26 +822,6 @@ result_t Message::decodeLastDataNumField(const char* fieldName, ssize_t fieldInd
return result;
}
bool Message::isLessPollWeight(const Message* other) const {
size_t tprio = m_pollPriority;
size_t oprio = other->m_pollPriority;
size_t tw = tprio * m_pollCount;
size_t ow = oprio * other->m_pollCount;
if (tw > ow) {
return true;
}
if (tw < ow) {
return false;
}
if (tprio > oprio) {
return true;
}
if (tprio < oprio) {
return false;
}
return m_lastPollTime > other->m_lastPollTime;
}
void Message::dumpHeader(const vector<string>* fieldNames, ostream* output) {
bool first = true;
if (fieldNames == nullptr) {
@@ -1267,6 +1283,7 @@ void ChainedMessage::dumpField(const string& fieldName, bool withConditions, ost
* @param sameIdExtAs the optional @a MasterSymbolString to check for having the same ID.
* @param onlyAvailable true to include only available messages (default true), false to also include messages that
* are currently not available (e.g. due to unresolved or false conditions).
* @return the first available @a Message from the list.
*/
Message* getFirstAvailable(const vector<Message*>& messages, const MasterSymbolString* sameIdExtAs,
const bool onlyAvailable = true) {
@@ -1287,6 +1304,7 @@ Message* getFirstAvailable(const vector<Message*>& messages, const MasterSymbolS
* @param sameIdExtAs the optional @a Message to check for having the same ID.
* @param onlyAvailable true to include only available messages (default true), false to also include messages that
* are currently not available (e.g. due to unresolved or false conditions).
* @return the first available @a Message from the list.
*/
Message* getFirstAvailable(const vector<Message*>& messages, const Message* sameIdExtAs = nullptr,
const bool onlyAvailable = true) {
@@ -2566,6 +2584,14 @@ void MessageMap::findAll(const string& circuit, const string& name, const string
}
}
Message* MessageMap::getFirstAvailableFromIterator(const map<uint64_t, vector<Message*> >::const_iterator& it,
const MasterSymbolString* sameIdExtAs, bool onlyAvailable) const {
if (it != m_messagesByKey.end()) {
return getFirstAvailable(it->second, sameIdExtAs, onlyAvailable);
}
return nullptr;
}
Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination,
bool withRead, bool withWrite, bool withPassive, bool onlyAvailable) const {
if (anyDestination && master.size() >= 5 && master[4] == 0 && master[2] == 0x07 && master[3] == 0x04) {
@@ -2576,6 +2602,7 @@ Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination,
if (baseKey == INVALID_KEY) {
return nullptr;
}
bool isWriteDest = isMaster(master[1]) || master[1] == BROADCAST;
size_t maxIdLength = Message::getKeyLength(baseKey);
for (size_t idLength = maxIdLength; true; idLength--) {
uint64_t key = baseKey;
@@ -2591,44 +2618,39 @@ Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination,
}
}
}
map<uint64_t, vector<Message*> >::const_iterator it;
Message* message;
if (withPassive) {
it = m_messagesByKey.find(key);
if (it != m_messagesByKey.end()) {
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
message = getFirstAvailableFromIterator(m_messagesByKey.find(key), &master, onlyAvailable);
if (message) {
return message;
}
}
if ((key & ID_SOURCE_MASK) != 0) {
key &= ~ID_SOURCE_MASK;
if (withPassive) {
// try again without specific source master
message = getFirstAvailableFromIterator(m_messagesByKey.find(key), &master, onlyAvailable);
if (message) {
return message;
}
}
if ((key & ID_SOURCE_MASK) != 0) {
key &= ~ID_SOURCE_MASK;
it = m_messagesByKey.find(key & ~ID_SOURCE_MASK); // try again without specific source master
if (it != m_messagesByKey.end()) {
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
if (message) {
return message;
}
}
}
} else {
key &= ~ID_SOURCE_MASK;
}
if (withRead) {
it = m_messagesByKey.find(key | ID_SOURCE_ACTIVE_READ); // try again with special value for active read
if (it != m_messagesByKey.end()) {
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
if (message) {
return message;
}
// try again with special value for active read
message = getFirstAvailableFromIterator(
m_messagesByKey.find(key | (isWriteDest ? ID_SOURCE_ACTIVE_READ_MASTER : ID_SOURCE_ACTIVE_READ)),
&master, onlyAvailable);
if (message) {
return message;
}
}
if (withWrite) {
it = m_messagesByKey.find(key | ID_SOURCE_ACTIVE_WRITE); // try again with special value for active write
if (it != m_messagesByKey.end()) {
Message* message = getFirstAvailable(it->second, &master, onlyAvailable);
if (message) {
return message;
}
// try again with special value for active write
message = getFirstAvailableFromIterator(
m_messagesByKey.find(key | (isWriteDest ? ID_SOURCE_ACTIVE_WRITE_MASTER : ID_SOURCE_ACTIVE_WRITE)),
&master, onlyAvailable);
if (message) {
return message;
}
}
if (idLength == 0) {
@@ -2751,11 +2773,16 @@ Message* MessageMap::getNextPoll() {
if (m_pollMessages.empty()) {
return nullptr;
}
lock();
Message* ret = m_pollMessages.top();
m_pollMessages.pop();
ret->m_pollCount++;
if (ret->m_pollOrder > g_lastPollOrder) {
g_lastPollOrder = ret->m_pollOrder;
}
ret->m_pollOrder += (unsigned int)ret->m_pollPriority;
time(&(ret->m_lastPollTime));
m_pollMessages.push(ret); // re-insert at new position
unlock();
return ret;
}
Executable → Regular
+14 -3
View File
@@ -642,8 +642,8 @@ class Message : public AttributedItem {
/** the system time when the message content was last changed, 0 for never. */
time_t m_lastChangeTime;
/** the number of times this messages was already polled for. */
unsigned int m_pollCount;
/** the polling order of this message (roughly number of polls * priority). */
unsigned int m_pollOrder;
/** the system time when this message was last polled for, 0 for never. */
time_t m_lastPollTime;
@@ -1403,13 +1403,24 @@ class MessageMap : public MappedFileReader {
* address), or 0 to ignore.
* @param until the end time to which to add updates (exclusive, also removes messages with unset destination
* address), or 0 to ignore.
* @changedSince true to use the last change time for the since/until range, false to use the last seen time.
* @param changedSince true to use the last change time for the since/until range, false to use the last seen time.
* @param messages the @a deque to which to add the found @a Message instances.
*/
void findAll(const string& circuit, const string& name, const string& levels,
bool completeMatch, bool withRead, bool withWrite, bool withPassive, bool includeEmptyLevel, bool onlyAvailable,
time_t since, time_t until, bool changedSince, deque<Message*>* messages) const;
/**
* Get the first available @a Message from the first map iterator entry.
* @param it the map iterator with list of @a Message instances to check.
* @param sameIdExtAs the optional @a MasterSymbolString to check for having the same ID.
* @param onlyAvailable true to include only available messages (default true), false to also include messages that
* are currently not available (e.g. due to unresolved or false conditions).
* @return the first available @a Message from the first map iterator entry.
*/
Message* getFirstAvailableFromIterator(const map<uint64_t, vector<Message*> >::const_iterator& it,
const MasterSymbolString* sameIdExtAs, bool onlyAvailable) const;
/**
* Find the @a Message instance for the specified master data.
* @param master the @a MasterSymbolString for identifying the @a Message.
+3
View File
@@ -228,6 +228,9 @@ int main() {
{"x,,ttq", "23:45", "10fe0700015f", "00", ""},
{"x,,ttq", "24:00", "10fe07000160", "00", ""},
{"x,,ttq", "-:-", "10fe07000100", "00", ""},
{"x,,ttq,,,,,,ttq", "23:00;05:45", "10fe0700025c17", "00", ""},
{"x,,ttq,,,,,,bi7,,,,,,ttq", "23:00;0;05:45", "10fe0700025c17", "00", ""},
{"x,,ttq,,,,,,bi7,,,,,,ttq", "23:00;1;05:45", "10fe070002dc17", "00", ""},
{"x,,ttq", "", "10fe07000161", "00", "rw"},
{"x,,ttq,2", "", "", "", "c"},
{"x,,ttq,,,,y,,bi7", "23:45;0", "10fe0700015f", "00", ""}, // bit combination
+6 -2
View File
@@ -173,7 +173,11 @@ int main() {
{"w,,x,,,,,,b0,,BI0:1,,,,b1,,BI1:1,,,,b2,,BI2:6,,,,c0,,BI0:1,,,,c1,,BI1:1,,,,c2,,BI2:1", "1;1;1;0;0;0", "ff08b509030e0700", "00", "di" },
{"w,,x,,,,,,b0,,BI0:1,,,,b1,,BI1:1,,,,b2,,BI2:6,,,,c0,,BI0:1,,,,c1,,BI1:1,,,,c2,,BI2:1", "1;0;0;0;0;1", "ff08b509030e0104", "00", "di" },
{"w,,x,,,,,,b0,,BI0:1,,,,b1,,BI1:1,,,,b2,,BI2:6,,,,c0,,BI0:1,,,,c1,,BI1:1,,,,c2,,BI2:1", "0;0;1;0;1;1", "ff08b509030e0406", "00", "di" },
{"*r,cir*cuit#level,na*me,com*ment,ff,75,b509,0d", "", "", "", "" },
{"r,470,ccTimer.Monday,,,15,B515,0002,,,IGN:1,,,,from,,TTM", "", "", "", "M"},
{"w,470,ccTimer.Monday,,,10,B515,0002,from,,TTM", "", "", "", "kM*"},
{"", "19:00", "3115b515020002", "080272", "kd"},
{"", "19:00", "3110b51503000272", "00", "kd"},
{"*r,cir*cuit#level,na*me,com*ment,ff,75,b509,0d", "", "", "", ""},
{"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,UCH", "r,cirCIRCUITcuit,naNAMEme,comCOMMENTment,ff,75,b509,0d0100,field,s,UCH,,,: field=42", "ff75b509030d0100", "012a", "DN"},
{"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,UCH",
// "\"naNAMEme\": {r,cirCIRCUITcuit,naNAMEme,comCOMMENTment,ff,75,b509,0d0100,field,s,UCH,,,: field=42"
@@ -420,7 +424,7 @@ int main() {
result = message->decodeLastData(false, nullptr, -1,
(decodeVerbose?OF_NAMES|OF_UNITS|OF_COMMENTS:0)|(decodeJson?OF_NAMES|OF_JSON:0), &output);
if (result != RESULT_OK) {
cout << " \"" << check[2] << "\" / \"" << check[3] << "\": decode error: "
cout << " \"" << check[2] << "\" / \"" << check[3] << "\": decode error " << (message->isWrite() ? "write: " : "read: ")
<< getResultCode(result) << endl;
error = true;
continue;