diff --git a/contrib/html/swagger.yaml b/contrib/html/swagger.yaml
index 2e35ac4f..c389bd14 100644
--- a/contrib/html/swagger.yaml
+++ b/contrib/html/swagger.yaml
@@ -7,6 +7,7 @@ schemes:
- http
produces:
- 'application/json;charset=utf-8'
+host: 'l:8080'
paths:
/data:
get:
@@ -34,6 +35,8 @@ paths:
- $ref: '#/parameters/fullQuery'
- $ref: '#/parameters/requiredQuery'
- $ref: '#/parameters/writeQuery'
+ - $ref: '#/parameters/rawQuery'
+ - $ref: '#/parameters/defQuery'
- $ref: '#/parameters/userQuery'
- $ref: '#/parameters/secretQuery'
/data/{circuit}:
@@ -64,6 +67,8 @@ paths:
- $ref: '#/parameters/fullQuery'
- $ref: '#/parameters/requiredQuery'
- $ref: '#/parameters/writeQuery'
+ - $ref: '#/parameters/rawQuery'
+ - $ref: '#/parameters/defQuery'
- $ref: '#/parameters/userQuery'
- $ref: '#/parameters/secretQuery'
/data/{circuit}/{message}:
@@ -89,6 +94,8 @@ paths:
- $ref: '#/parameters/fullQuery'
- $ref: '#/parameters/requiredQuery'
- $ref: '#/parameters/writeQuery'
+ - $ref: '#/parameters/rawQuery'
+ - $ref: '#/parameters/defQuery'
- $ref: '#/parameters/userQuery'
- $ref: '#/parameters/secretQuery'
/{file}:
@@ -115,6 +122,7 @@ paths:
- image/jpeg
- image/svg+xml
- 'application/json;charset=utf-8'
+ - 'application/yaml;charset=utf-8'
head:
summary: Retrieve headers for a certain file.
responses:
@@ -157,6 +165,12 @@ definitions:
description: the maximum symbol rate on the bus seen since start.
minimum: 0
example: 167
+ qq:
+ type: integer
+ description: the ebusd master address (only if not readonly).
+ minimum: 0
+ maximum: 255
+ example: 49
reconnects:
type: integer
description: the number of reconnects to the device since start.
@@ -186,39 +200,87 @@ definitions:
- lastup
Circuit:
type: object
- items:
- $ref: '#/definitions/Message'
+ properties:
+ name:
+ type: string
+ description: the circuit name.
+ zz:
+ type: integer
+ description: the circuit slave address.
+ minimum: 0
+ maximum: 255
+ example: 8
+ messages:
+ type: object
+ additionalProperties:
+ $ref: '#/definitions/Message'
Message:
type: object
properties:
name:
type: string
description: the message name.
- lastup:
- type: integer
- description: the time in UTC seconds of the last update of the message.
- minimum: 0
- zz:
- type: string
- description: destination bus address.
- pattern: '[0-9a-f]{2}'
- comment:
- type: string
- description: the message comment (only with verbose).
- fields:
- type: object
- items:
- $ref: '#/definitions/Field'
passive:
type: boolean
description: true when this is a passive message.
write:
type: boolean
description: true for a write message, false for a read message.
+ lastup:
+ type: integer
+ description: the time in UTC seconds of the last update of the message (0 for never).
+ minimum: 0
+ qq:
+ type: integer
+ description: limited source master address (only with def).
+ minimum: 0
+ maximum: 255
+ example: 49
+ zz:
+ type: integer
+ description: destination master or slave address.
+ minimum: 0
+ maximum: 255
+ example: 8
+ id:
+ type: array
+ description: the message ID composed of PBSB and further master data bytes (only with def).
+ items:
+ type: integer
+ minimum: 0
+ maximum: 255
+ comment:
+ type: string
+ description: the message comment (only with verbose).
+ master:
+ type: array
+ description: the last seen master data bytes (only with raw and if available).
+ items:
+ type: integer
+ minimum: 0
+ maximum: 255
+ slave:
+ type: array
+ description: the last seen slave data bytes (only with raw and if available).
+ items:
+ type: integer
+ minimum: 0
+ maximum: 255
+ fields:
+ type: object
+ description: the decoded fields the message is composed of (only if available).
+ additionalProperties:
+ $ref: '#/definitions/Field'
+ fielddefs:
+ type: array
+ description: the field definitions the message is composed of (only with def).
+ items:
+ $ref: '#/definitions/FieldDef'
required:
- - lastup
+ - name
- passive
- write
+ - lastup
Field:
type: object
properties:
@@ -233,12 +295,57 @@ definitions:
comment:
type: string
description: the field comment (only with verbose).
+ FieldDef:
+ type: object
+ properties:
+ name:
+ type: string
+ description: the field name.
+ slave:
+ type: boolean
+ description: whether the field is part of the slave data.
+ type:
+ type: string
+ description: the field type.
+ isbits:
+ type: boolean
+ description: true when the length is in bits.
+ length:
+ type: number
+ description: the field length in bytes (-1 for remainder, number of bits when isbits is true).
+ divisor:
+ type: number
+ description: the extra divisor applied to the raw value (only if applicable).
+ value:
+ type: string
+ description: the constant value (only if applicable).
+ verify:
+ type: boolean
+ description: whether the constant value is verified when decoding (only if applicable).
+ values:
+ type: object
+ description: the raw numeric value to name assignments (only if applicable).
+ additionalProperties:
+ type: string
+ description: the value name.
+ unit:
+ type: string
+ description: the field unit.
+ comment:
+ type: string
+ description: the field comment.
+ required:
+ - name
+ - slave
+ - type
+ - isbits
+ - length
Data:
type: object
properties:
global:
$ref: '#/definitions/Global'
- items:
+ additionalProperties:
$ref: '#/definitions/Circuit'
required:
- global
@@ -282,7 +389,7 @@ parameters:
indexedQuery:
name: indexed
in: query
- description: return field indexes instead of names.
+ description: always return field indexes instead of names.
type: boolean
allowEmptyValue: false
numericQuery:
@@ -315,6 +422,18 @@ parameters:
description: retrieve write messages in addition to read/poll messages.
type: boolean
allowEmptyValue: false
+ rawQuery:
+ name: raw
+ in: query
+ description: include raw master/slave data.
+ type: boolean
+ allowEmptyValue: false
+ defQuery:
+ name: def
+ in: query
+ description: include message and field definition.
+ type: boolean
+ allowEmptyValue: false
userQuery:
name: user
in: query
diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp
index f3e30ced..234f0491 100644
--- a/src/ebusd/bushandler.cpp
+++ b/src/ebusd/bushandler.cpp
@@ -234,7 +234,7 @@ bool decodeType(const DataType* type, const SymbolString& input, size_t length,
first = false;
*output << endl << " ";
ostringstream::pos_type cnt = output->tellp();
- type->dump(length, false, output);
+ type->dump(false, length, false, output);
cnt = output->tellp() - cnt;
while (cnt < 5) {
*output << " ";
diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp
index f9799bb1..afcd3c38 100644
--- a/src/ebusd/mainloop.cpp
+++ b/src/ebusd/mainloop.cpp
@@ -1616,7 +1616,8 @@ result_t MainLoop::executeHelp(ostringstream* ostream) {
}
result_t MainLoop::executeGet(const vector& args, bool* connected, ostringstream* ostream) {
- bool numeric = false, valueName = false, required = false, full = false, withWrite = false;
+ bool numeric = false, valueName = false, required = false, full = false, withWrite = false, raw = false;
+ bool withDefinition = false;
OutputFormat verbosity = OF_NAMES;
size_t argPos = 1;
string uri = args[argPos++];
@@ -1673,6 +1674,10 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri
required = value.length() == 0 || value == "1" || value == "true";
} else if (qname == "write") {
withWrite = value.length() == 0 || value == "1" || value == "true";
+ } else if (qname == "raw") {
+ raw = value.length() == 0 || value == "1" || value == "true";
+ } else if (qname == "def") {
+ withDefinition = value.length() == 0 || value == "1" || value == "true";
} else if (qname == "user") {
user = value;
} else if (qname == "secret") {
@@ -1692,7 +1697,8 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri
time_t maxLastUp = 0;
if (ret == RESULT_OK) {
bool first = true;
- verbosity |= (valueName ? OF_VALUENAME : numeric ? OF_NUMERIC : 0) | OF_JSON | (full ? OF_ALL_ATTRS : 0);
+ verbosity |= (valueName ? OF_VALUENAME : numeric ? OF_NUMERIC : 0) | OF_JSON | (full ? OF_ALL_ATTRS : 0)
+ | (withDefinition ? OF_DEFINTION : 0);
deque messages;
m_messages->findAll(circuit, name, getUserLevels(user), exact, true, withWrite, true, true, true, 0, 0,
&messages);
@@ -1725,15 +1731,16 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri
}
if (message->getCircuit() != lastCircuit) {
if (lastCircuit.length() > 0) {
- *ostream << "\n },";
+ *ostream << "\n }\n },";
}
lastCircuit = message->getCircuit();
*ostream << "\n \"" << lastCircuit << "\": {";
- first = true;
if (full && m_messages->decodeCircuit(lastCircuit, verbosity, ostream)) { // add circuit specific values
- first = false;
+ *ostream << ",";
}
+ *ostream << "\n \"messages\": {";
lastName = "";
+ first = true;
}
name = message->getName();
bool same = name == lastName;
@@ -1741,16 +1748,16 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri
Message* next = *(it+1);
same = next->getCircuit() == lastCircuit && next->getName() == name;
}
- message->decode(!first, same, verbosity, ostream);
+ message->decodeJson(!first, same, raw, verbosity, ostream);
lastName = name;
first = false;
}
if (lastCircuit.length() > 0) {
- *ostream << "\n },";
+ *ostream << "\n }\n },";
}
*ostream << "\n \"global\": {"
- << "\n \"version\": \"" << PACKAGE_VERSION "." REVISION "\"";
+ << "\n \"version\": \"" << PACKAGE_VERSION "." REVISION "\"" << setw(0) << dec;
if (!m_updateCheck.empty()) {
*ostream << ",\n \"updatecheck\": \"" << m_updateCheck << "\"";
}
@@ -1766,10 +1773,13 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri
*ostream << ",\n \"symbolrate\": " << m_busHandler->getSymbolRate()
<< ",\n \"maxsymbolrate\": " << m_busHandler->getMaxSymbolRate();
}
+ if (!m_device->isReadOnly()) {
+ *ostream << ",\n \"qq\": " << static_cast(m_address);
+ }
*ostream << ",\n \"reconnects\": " << m_reconnectCount
<< ",\n \"masters\": " << m_busHandler->getMasterCount()
<< ",\n \"messages\": " << m_messages->size()
- << ",\n \"lastup\": " << setw(0) << dec << static_cast(maxLastUp)
+ << ",\n \"lastup\": " << static_cast(maxLastUp)
<< "\n }"
<< "\n}";
type = 6;
@@ -1802,6 +1812,8 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri
type = 5;
} else if (ext == "json") {
type = 6;
+ } else if (ext == "yaml") {
+ type = 7;
}
}
if (type < 0) {
@@ -1848,6 +1860,9 @@ result_t MainLoop::formatHttpResult(result_t ret, int type, ostringstream* ostre
case 6:
*ostream << "application/json;charset=utf-8";
break;
+ case 7:
+ *ostream << "application/yaml;charset=utf-8";
+ break;
default:
*ostream << "text/html";
break;
diff --git a/src/lib/ebus/contrib/test/test_tem.cpp b/src/lib/ebus/contrib/test/test_tem.cpp
index 1a1fa0a4..91c2ff42 100644
--- a/src/lib/ebus/contrib/test/test_tem.cpp
+++ b/src/lib/ebus/contrib/test/test_tem.cpp
@@ -178,7 +178,7 @@ int main() {
continue;
}
cout << "\"" << check[0] << "\"=\"";
- fields->dump(&cout);
+ fields->dump(false, false, &cout);
cout << "\": create OK" << endl;
ostringstream output;
diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp
index 8e29ccac..75cdfe64 100644
--- a/src/lib/ebus/data.cpp
+++ b/src/lib/ebus/data.cpp
@@ -45,35 +45,6 @@ static const char* defaultTemplateFieldMap[] = {
};
-
-string getDataFieldName(const string& name, bool* supportsLanguage) {
- if (name.find("name") != string::npos || name.find("field") != string::npos) {
- return "name";
- }
- if (name.find("part") != string::npos) {
- return "part";
- }
- if (name.find("type") != string::npos) {
- return "type";
- }
- if (name.find("divisor") != string::npos) {
- if (name.find("values") != string::npos) {
- *supportsLanguage = true;
- return "divisor/values";
- }
- return "divisor";
- }
- *supportsLanguage = true;
- if (name == "values" || name == "unit") {
- return name;
- }
- if (name.find("comment") != string::npos) {
- return "comment";
- }
- return "";
-}
-
-
string AttributedItem::formatInt(size_t value) {
ostringstream stream;
stream << dec << static_cast(value);
@@ -102,8 +73,8 @@ void AttributedItem::dumpString(bool prependFieldSeparator, const string& str, o
}
void AttributedItem::appendJson(bool prependFieldSeparator, const string& name, const string& value,
- bool asString, ostream* output) {
- bool plain = !asString;
+ bool forceString, ostream* output) {
+ bool plain = !forceString && !value.empty();
if (plain) {
plain = value == "false" || value == "true";
if (!plain) {
@@ -120,7 +91,15 @@ void AttributedItem::appendJson(bool prependFieldSeparator, const string& name,
if (plain) {
*output << value;
} else {
- *output << '"' << value << '"';
+ *output << '"';
+ if (value.find_first_of('"') == string::npos) { // check for nested '"'
+ *output << value;
+ } else {
+ string replaced = value;
+ replace(replaced.begin(), replaced.end(), '"', '\'');
+ *output << replaced;
+ }
+ *output << '"';
}
}
@@ -133,8 +112,13 @@ void AttributedItem::mergeAttributes(map* attributes) const {
}
}
-void AttributedItem::dumpAttribute(bool prependFieldSeparator, const string& name, ostream* output) const {
- dumpString(prependFieldSeparator, getAttribute(name), output);
+void AttributedItem::dumpAttribute(bool prependFieldSeparator, bool asJson, const string& name, ostream* output)
+ const {
+ if (asJson) {
+ appendJson(prependFieldSeparator, name, getAttribute(name), false, output);
+ } else {
+ dumpString(prependFieldSeparator, getAttribute(name), output);
+ }
}
bool AttributedItem::appendAttribute(OutputFormat outputFormat, const string& name, bool onlyIfNonEmpty,
@@ -164,10 +148,19 @@ bool AttributedItem::appendAttributes(OutputFormat outputFormat, ostream* output
for (const auto entry : m_attributes) {
ret = true;
if (!entry.second.empty() && entry.first != "unit" && entry.first != "comment") {
+ const string& key = entry.first;
if (outputFormat & OF_JSON) {
- appendJson(true, entry.first, entry.second, false, output);
+ if (key == "zz" || key == "qq") {
+ result_t result = RESULT_EMPTY;
+ size_t addr = parseInt(entry.second.c_str(), 16, 0, 255, &result);
+ if (result == RESULT_OK) {
+ *output << FIELD_SEPARATOR << " \"" << key << "\": " << addr;
+ continue;
+ }
+ }
+ appendJson(true, key, entry.second, false, output);
} else {
- *output << " " << entry.first << "=" << entry.second;
+ *output << " " << key << "=" << entry.second;
}
}
}
@@ -459,27 +452,44 @@ result_t SingleDataField::create(const string& name, const map&
return RESULT_OK;
}
-void SingleDataField::dumpPrefix(ostream* output) const {
+void SingleDataField::dumpPrefix(bool prependFieldSeparator, bool asJson, ostream* output) const {
*output << setw(0) << dec; // initialize formatting
- dumpString(false, m_name, output);
- *output << FIELD_SEPARATOR;
- if (m_partType == pt_masterData) {
- *output << "m";
- } else if (m_partType == pt_slaveData) {
- *output << "s";
+ if (asJson) {
+ if (prependFieldSeparator) {
+ *output << FIELD_SEPARATOR;
+ }
+ *output << "\n {";
+ appendJson(false, "name", m_name, true, output);
+ } else {
+ dumpString(prependFieldSeparator, m_name, output);
}
*output << FIELD_SEPARATOR;
+ if (asJson) {
+ *output << " \"slave\": " << (m_partType == pt_slaveData ? "true" : "false") << ", ";
+ } else {
+ if (m_partType == pt_masterData) {
+ *output << "m";
+ } else if (m_partType == pt_slaveData) {
+ *output << "s";
+ }
+ }
+ if (!asJson) {
+ *output << FIELD_SEPARATOR;
+ }
+ m_dataType->dump(asJson, m_length, true, output);
}
-void SingleDataField::dumpSuffix(ostream* output) const {
- dumpAttribute(true, "unit", output);
- dumpAttribute(true, "comment", output);
+void SingleDataField::dumpSuffix(bool asJson, ostream* output) const {
+ dumpAttribute(true, asJson, "unit", output);
+ dumpAttribute(true, asJson, "comment", output);
+ if (asJson) {
+ *output << "}";
+ }
}
-void SingleDataField::dump(ostream* output) const {
- dumpPrefix(output);
- m_dataType->dump(m_length, true, output);
- dumpSuffix(output);
+void SingleDataField::dump(bool prependFieldSeparator, bool asJson, ostream* output) const {
+ dumpPrefix(prependFieldSeparator, asJson, output);
+ dumpSuffix(asJson, output);
}
result_t SingleDataField::read(const SymbolString& data, size_t offset,
@@ -676,10 +686,19 @@ result_t ValueListDataField::derive(const string& name, PartType partType, int d
return RESULT_OK;
}
-void ValueListDataField::dump(ostream* output) const {
- dumpPrefix(output);
- if (!m_dataType->dump(m_length, true, output)) { // no divisor appended
- bool first = true;
+void ValueListDataField::dump(bool prependFieldSeparator, bool asJson, ostream* output) const {
+ dumpPrefix(prependFieldSeparator, asJson, output);
+ // no divisor appended since it is not allowed for ValueListDataField
+ bool first = true;
+ if (asJson) {
+ *output << ", \"values\": {";
+ for (const auto it : m_values) {
+ appendJson(!first, formatInt(it.first), it.second, true, output); // TODO optimize?
+ first = false;
+ }
+ *output << " }";
+ } else {
+ *output << FIELD_SEPARATOR;
for (const auto it : m_values) {
if (first) {
first = false;
@@ -688,8 +707,8 @@ void ValueListDataField::dump(ostream* output) const {
}
*output << it.first << "=" << it.second;
}
- } // else: impossible since divisor is not allowed for ValueListDataField
- dumpSuffix(output);
+ }
+ dumpSuffix(asJson, output);
}
result_t ValueListDataField::readSymbols(const SymbolString& input, size_t offset,
@@ -783,12 +802,16 @@ result_t ConstantDataField::derive(const string& name, PartType partType, int di
return RESULT_OK;
}
-void ConstantDataField::dump(ostream* output) const {
- dumpPrefix(output);
- if (!m_dataType->dump(m_length, true, output)) { // no divisor appended
- *output << (m_verify?"==":"=") << m_value;
- } // else: impossible since divisor is not allowed for ConstantDataField
- dumpSuffix(output);
+void ConstantDataField::dump(bool prependFieldSeparator, bool asJson, ostream* output) const {
+ dumpPrefix(prependFieldSeparator, asJson, output);
+ // no divisor appended since it is not allowed for ConstantDataField
+ if (asJson) {
+ appendJson(false, "value", m_value, true, output);
+ *output << ", \"verify\":" << (m_verify ? "true" : "false");
+ } else {
+ *output << FIELD_SEPARATOR << (m_verify?"==":"=") << m_value;
+ }
+ dumpSuffix(asJson, output);
}
result_t ConstantDataField::readSymbols(const SymbolString& input, size_t offset,
@@ -953,15 +976,10 @@ bool DataFieldSet::hasField(const char* fieldName, bool numeric) const {
return false;
}
-void DataFieldSet::dump(ostream* output) const {
- bool first = true;
+void DataFieldSet::dump(bool prependFieldSeparator, bool asJson, ostream* output) const {
for (const auto field : m_fields) {
- if (first) {
- first = false;
- } else {
- *output << FIELD_SEPARATOR;
- }
- field->dump(output);
+ field->dump(prependFieldSeparator, asJson, output);
+ prependFieldSeparator = true;
}
}
diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h
index 2e45e00a..24175239 100644
--- a/src/lib/ebus/data.h
+++ b/src/lib/ebus/data.h
@@ -50,14 +50,6 @@ namespace ebusd {
*/
-/**
- * Get the normalized data field name for the given name.
- * @param name the input field name.
- * @param supportsLanguage set to true when the field supports multiple language.
- * @return the normalized data field name, or empty if unknown.
- */
-string getDataFieldName(const string& name, bool* supportsLanguage);
-
class DataFieldTemplates;
class SingleDataField;
@@ -115,11 +107,11 @@ class AttributedItem {
* @param prependFieldSeparator whether to start with a @a FIELD_SEPARATOR.
* @param name the name of the attribute.
* @param value the value of the attribute.
- * @param asString true to force writing as string, false to detect the type from the value.
+ * @param forceString true to force writing as string, false to detect the type from the value.
* @param output the @a ostream to append to.
*/
static void appendJson(bool prependFieldSeparator, const string& name, const string& value,
- bool asString, ostream* output);
+ bool forceString, ostream* output);
/**
* Merge this instance's additional named attributes into the specified attributes.
@@ -130,10 +122,11 @@ class AttributedItem {
/**
* Dump the attribute optionally embedded in @a TEXT_SEPARATOR to the output.
* @param prependFieldSeparator whether to start with a @a FIELD_SEPARATOR.
+ * @param asJson whether to output in JSON format.
* @param name the name of the attribute to dump.
* @param output the @a ostream to dump to.
*/
- void dumpAttribute(bool prependFieldSeparator, const string& name, ostream* output) const;
+ void dumpAttribute(bool prependFieldSeparator, bool asJson, const string& name, ostream* output) const;
/**
* Append the attribute value to the output.
@@ -265,9 +258,11 @@ class DataField : public AttributedItem {
/**
* Dump the field settings to the output.
+ * @param prependFieldSeparator whether to start with a @a FIELD_SEPARATOR.
+ * @param asJson whether to output in JSON format.
* @param output the @a ostream to dump to.
*/
- virtual void dump(ostream* output) const = 0;
+ virtual void dump(bool prependFieldSeparator, bool asJson, ostream* output) const = 0;
/**
* Return whether the field is available.
@@ -404,18 +399,21 @@ class SingleDataField : public DataField {
/**
* Dump the common prefix field settings to the output (name and part type).
+ * @param prependFieldSeparator whether to start with a @a FIELD_SEPARATOR.
+ * @param asJson whether to output in JSON format.
* @param output the @a ostream to dump to.
*/
- void dumpPrefix(ostream* output) const;
+ void dumpPrefix(bool prependFieldSeparator, bool asJson, ostream* output) const;
/**
* Dump the common suffix field settings to the output (optional unit and comment).
+ * @param asJson whether to output in JSON format.
* @param output the @a ostream to dump to.
*/
- void dumpSuffix(ostream* output) const;
+ void dumpSuffix(bool asJson, ostream* output) const;
// @copydoc
- void dump(ostream* output) const override;
+ void dump(bool prependFieldSeparator, bool asJson, ostream* output) const override;
// @copydoc
bool hasField(const char* fieldName, bool numeric) const override;
@@ -501,7 +499,7 @@ class ValueListDataField : public SingleDataField {
vector* fields) const override;
// @copydoc
- void dump(ostream* output) const override;
+ void dump(bool prependFieldSeparator, bool asJson, ostream* output) const override;
protected:
@@ -554,7 +552,7 @@ class ConstantDataField : public SingleDataField {
vector* fields) const override;
// @copydoc
- void dump(ostream* output) const override;
+ void dump(bool prependFieldSeparator, bool asJson, ostream* output) const override;
protected:
@@ -660,7 +658,7 @@ class DataFieldSet : public DataField {
bool hasField(const char* fieldName, bool numeric) const override;
// @copydoc
- void dump(ostream* output) const override;
+ void dump(bool prependFieldSeparator, bool asJson, ostream* output) const override;
// @copydoc
result_t read(const SymbolString& data, size_t offset,
diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp
index c7991d0f..b2c845e9 100644
--- a/src/lib/ebus/datatype.cpp
+++ b/src/lib/ebus/datatype.cpp
@@ -42,17 +42,27 @@ using std::setw;
using std::endl;
-bool DataType::dump(size_t length, bool appendSeparatorDivisor, ostream* output) const {
- *output << m_id;
- if (isAdjustableLength()) {
- if (length == REMAIN_LEN) {
- *output << ":*";
+bool DataType::dump(bool asJson, size_t length, bool appendDivisor, ostream* output) const {
+ if (asJson) {
+ *output << "\"type\": \"" << m_id << "\"" << FIELD_SEPARATOR << " \"isbits\": "
+ << (getBitCount() < 8 ? "true" : "false") << FIELD_SEPARATOR << " \"length\": ";
+ if (isAdjustableLength() && length == REMAIN_LEN) {
+ *output << "-1";
} else {
- *output << ":" << static_cast(length);
+ *output << static_cast(length);
+ }
+ } else {
+ *output << m_id;
+ if (isAdjustableLength()) {
+ if (length == REMAIN_LEN) {
+ *output << ":*";
+ } else {
+ *output << ":" << static_cast(length);
+ }
+ }
+ if (appendDivisor) {
+ *output << FIELD_SEPARATOR;
}
- }
- if (appendSeparatorDivisor) {
- *output << FIELD_SEPARATOR;
}
return false;
}
@@ -502,21 +512,27 @@ size_t NumberDataType::calcPrecision(int divisor) {
return precision;
}
-bool NumberDataType::dump(size_t length, bool appendSeparatorDivisor, ostream* output) const {
+bool NumberDataType::dump(bool asJson, size_t length, bool appendDivisor, ostream* output) const {
if (m_bitCount < 8) {
- DataType::dump(m_bitCount, appendSeparatorDivisor, output);
+ DataType::dump(asJson, m_bitCount, appendDivisor, output);
} else {
- DataType::dump(length, appendSeparatorDivisor, output);
+ DataType::dump(asJson, length, appendDivisor, output);
}
- if (!appendSeparatorDivisor) {
+ if (!appendDivisor) {
return false;
}
if (m_baseType) {
if (m_baseType->m_divisor != m_divisor) {
+ if (asJson) {
+ *output << ", \"divisor\": ";
+ }
*output << (m_divisor / m_baseType->m_divisor);
return true;
}
} else if (m_divisor != 1) {
+ if (asJson) {
+ *output << ", \"divisor\": ";
+ }
*output << m_divisor;
return true;
}
diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h
index 49507270..fe14fbb9 100644
--- a/src/lib/ebus/datatype.h
+++ b/src/lib/ebus/datatype.h
@@ -106,6 +106,9 @@ typedef int OutputFormat;
/** bit flag for @a OutputFormat: include all attributes. */
#define OF_ALL_ATTRS 0x80
+/** bit flag for @a OutputFormat: include message/field definition. */
+#define OF_DEFINTION 0x100
+
/** the message part in which a data field is stored. */
enum PartType {
pt_any, //!< stored in any data (master or slave)
@@ -216,12 +219,13 @@ class DataType {
/**
* Dump the type identifier with the specified length and optionally the
* divisor to the output.
+ * @param asJson whether to output in JSON format.
* @param length the number of symbols to read/write.
- * @param appendSeparatorDivisor whether to append a @a FIELD_SEPARATOR followed by the divisor (if available).
+ * @param appendDivisor whether to append the divisor (if available).
* @param output the @a ostream to dump to.
* @return true when a non-default divisor was written to the output.
*/
- virtual bool dump(size_t length, bool appendSeparatorDivisor, ostream* output) const;
+ virtual bool dump(bool asJson, size_t length, bool appendDivisor, ostream* output) const;
/**
* Internal method for reading the numeric raw value from a @a SymbolString.
@@ -432,7 +436,7 @@ class NumberDataType : public DataType {
static size_t calcPrecision(int divisor);
// @copydoc
- bool dump(size_t length, bool appendSeparatorDivisor, ostream* output) const override;
+ bool dump(bool asJson, size_t length, bool appendDivisor, ostream* output) const override;
/**
* Derive a new @a NumberDataType from this.
diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp
index 562ee0a8..ad2c9af4 100644
--- a/src/lib/ebus/message.cpp
+++ b/src/lib/ebus/message.cpp
@@ -81,29 +81,6 @@ static const char* defaultMessageFieldMap[] = { // access level not included in
extern DataFieldTemplates* getTemplates(const string& filename);
-/**
- * Get the normalized message field name for the given name.
- * @param name the input field name.
- * @param supportsLanguage set to true when the field supports multiple language.
- * @return the normalized message field name, or empty if unknown.
- */
-string getMessageFieldName(const string& name, bool* supportsLanguage) {
- if (name.find("type") != string::npos) {
- return "type";
- }
- if (name == "circuit" || name == "level" || name == "qq" || name == "zz" || name == "pbsb" || name == "id") {
- return name;
- }
- if (name.find("name") != string::npos) {
- return "name";
- }
- *supportsLanguage = true;
- if (name.find("comment") == 0) {
- return "comment";
- }
- return "";
-}
-
Message::Message(const string& circuit, const string& level, const string& name,
bool isWrite, bool isPassive, const map& attributes,
@@ -921,18 +898,32 @@ void Message::dumpField(const string& fieldName, bool withConditions, ostream* o
return;
}
if (fieldName == "fields") {
- m_data->dump(output);
+ m_data->dump(false, false, output);
return;
}
- dumpAttribute(false, fieldName, output);
+ dumpAttribute(false, false, fieldName, output);
}
-void Message::decode(bool leadingSeparator, bool appendDirection, OutputFormat outputFormat, ostringstream* output)
- const {
+void addData(const SymbolString& data, ostringstream* output) {
+ if (data.size() == 0) {
+ return;
+ }
+ *output << ",\n \"" << (data.isMaster() ? "master" : "slave") << "\": [";
+ for (size_t pos = 0; pos < data.size(); pos++) {
+ if (pos > 0) {
+ *output << ", ";
+ }
+ *output << dec << static_cast(data[pos]);
+ }
+ *output << "]";
+}
+
+void Message::decodeJson(bool leadingSeparator, bool appendDirection, bool addRaw, OutputFormat outputFormat,
+ ostringstream* output) const {
if (leadingSeparator) {
*output << ",";
}
- *output << "\n \"" << getName();
+ *output << "\n \"" << getName();
if (appendDirection) {
if (isPassive()) {
*output << "-u";
@@ -940,27 +931,54 @@ void Message::decode(bool leadingSeparator, bool appendDirection, OutputFormat o
*output << "-w";
}
}
+ bool withDefinition = (outputFormat & OF_DEFINTION) != 0;
*output << "\": {"
- << "\n \"name\": \"" << getName() << "\""
- << ",\n \"lastup\": " << setw(0) << dec << static_cast(getLastUpdateTime());
- if (getLastUpdateTime() != 0) {
- *output << ",\n \"zz\": \"" << setfill('0') << setw(2) << hex << static_cast(getDstAddress()) << "\"";
+ << "\n \"name\": \"" << getName() << "\""
+ << ",\n \"passive\": " << (isPassive() ? "true" : "false")
+ << ",\n \"write\": " << (isWrite() ? "true" : "false")
+ << ",\n \"lastup\": " << setw(0) << dec << m_lastUpdateTime;
+ bool hasData = getLastUpdateTime() != 0;
+ if (hasData || withDefinition) {
+ if (withDefinition && m_srcAddress != SYN) {
+ *output << ",\n \"qq\": " << dec << static_cast(m_srcAddress);
+ }
+ *output << ",\n \"zz\": " << dec << static_cast(m_dstAddress);
+ if (withDefinition) {
+ *output << ",\n \"id\": [" << dec;
+ for (auto it = m_id.begin(); it < m_id.end(); it++) {
+ if (it > m_id.begin()) {
+ *output << ", ";
+ }
+ *output << dec << static_cast(*it);
+ }
+ *output << "]";
+ }
appendAttributes(OF_JSON | outputFormat, output);
- size_t pos = (size_t)output->tellp();
- *output << ",\n \"fields\": {";
- result_t dret = decodeLastData(false, NULL, -1, outputFormat, output);
- if (dret == RESULT_OK) {
- *output << "\n }";
- } else {
- string prefix = output->str().substr(0, pos);
- output->str("");
- output->clear(); // remove written fields
- *output << prefix << ",\n \"decodeerror\": \"" << getResultCode(dret) << "\"";
+ if (hasData) {
+ if (addRaw) {
+ addData(m_lastMasterData, output);
+ addData(m_lastSlaveData, output);
+ *output << dec;
+ }
+ size_t pos = (size_t)output->tellp();
+ *output << ",\n \"fields\": {";
+ result_t dret = decodeLastData(false, NULL, -1, outputFormat, output);
+ if (dret == RESULT_OK) {
+ *output << "\n }";
+ } else {
+ string prefix = output->str().substr(0, pos);
+ output->str("");
+ output->clear(); // remove written fields
+ *output << prefix << ",\n \"decodeerror\": \"" << getResultCode(dret) << "\"";
+ }
+ }
+ if (withDefinition) {
+ *output << ",\n \"fielddefs\": [";
+ m_data->dump(false, true, output);
+ *output << "\n ]";
}
}
- *output << ",\n \"passive\": " << (isPassive() ? "true" : "false")
- << ",\n \"write\": " << (isWrite() ? "true" : "false")
- << "\n }";
+ *output << "\n }";
}
@@ -2091,6 +2109,16 @@ result_t MessageMap::readFromFile(const string& filename, bool verbose, mapempty()) {
+ AttributedItem::pluck("suffix", defaults);
+ m_circuitData[circuit] = new AttributedItem(name, *defaults);
+ }
+ }
+ }
if (result == RESULT_OK) {
const string file = getRelativePath(filename);
m_loadedFileInfos[file].m_hash = *hash;
@@ -2542,7 +2570,7 @@ bool MessageMap::decodeCircuit(const string& circuit, OutputFormat outputFormat,
return false;
}
if (outputFormat & OF_JSON) {
- *output << "\"name\": \"" << it->second->getName() << "\"";
+ *output << "\n \"name\": \"" << it->second->getName() << "\"";
} else {
*output << it->second->getName() << "=";
}
diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h
index 25949522..4e24500b 100644
--- a/src/lib/ebus/message.h
+++ b/src/lib/ebus/message.h
@@ -552,11 +552,12 @@ class Message : public AttributedItem {
* Decode the message from the last stored data in JSON format.
* @param leadingSeparator whether to prepend a separator before the first value.
* @param appendDirection whether to append the direction to the name key (for passive and write).
+ * @param addRaw whether to add the raw symbols as well.
* @param outputFormat the @a OutputFormat options to use.
* @param output the @a ostringstream to append the decoded value(s) to.
*/
- virtual void decode(bool leadingSeparator, bool appendDirection, OutputFormat outputFormat, ostringstream* output)
- const;
+ virtual void decodeJson(bool leadingSeparator, bool appendDirection, bool addRaw, OutputFormat outputFormat,
+ ostringstream* output) const;
protected:
/** the optional circuit name. */
diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp
index 1089f74a..3149b652 100644
--- a/src/lib/ebus/test/test_data.cpp
+++ b/src/lib/ebus/test/test_data.cpp
@@ -609,7 +609,7 @@ int main() {
continue;
}
cout << "\"" << check[0] << "\"=\"";
- fields->dump(&cout);
+ fields->dump(false, false, &cout);
cout << "\": create OK" << endl;
ostringstream output;
diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp
index ddb64938..80f25620 100644
--- a/src/lib/ebus/test/test_message.cpp
+++ b/src/lib/ebus/test/test_message.cpp
@@ -145,6 +145,25 @@ int main() {
{"r,,x,,,,,6a00,,,UCH,10,bar,,Bit6,,BI6:1,0=B60;1=B61,,,Bit7,,BI7:1,0=B70;1=B71", "1.9;B60;B70", "ff08b509030d6a00", "02133f", "d" },
{"*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"
+ "\n"
+ " \"naNAMEme\": {\n"
+ " \"name\": \"naNAMEme\",\n"
+ " \"passive\": false,\n"
+ " \"write\": false,\n"
+ " \"lastup\": *,\n"
+ " \"qq\": 255,\n"
+ " \"zz\": 117,\n"
+ " \"id\": [181, 9, 13, 1, 0],\n"
+ " \"fields\": {\n"
+ " \"0\": {\"name\": \"field\", \"value\": 42}\n"
+ " },\n"
+ " \"fielddefs\": [\n"
+ " { \"name\": \"field\", \"slave\": true, \"type\": \"UCH\", \"isbits\": false, \"length\": 1, \"unit\": \"\", \"comment\": \"\"}\n"
+ " ]\n"
+ " }: \n"
+ " \"field\": {\"value\": 42}", "ff75b509030d0100", "012a", "jN"},
};
templates = new DataFieldTemplates();
unsigned int lineNo = 0;
@@ -349,8 +368,23 @@ int main() {
message->storeLastData(*mstrs[index], *sstrs[index]);
}
ostringstream output;
- if (withMessageDump && !decodeJson) {
- message->dump(NULL, true, &output);
+ if (withMessageDump) {
+ if (decodeJson) {
+ message->decodeJson(false, false, false, OF_JSON|OF_DEFINTION, &output);
+ string str = output.str();
+ size_t start = str.find("\"lastup\": ");
+ if (start != string::npos) {
+ start += 10;
+ size_t end = str.find(",", start);
+ if (end != string::npos) {
+ str = str.substr(0, start)+"*"+str.substr(end);
+ }
+ }
+ output.str("");
+ output << str;
+ } else {
+ message->dump(NULL, true, &output);
+ }
output << ": ";
}
result = message->decodeLastData(false, NULL, -1,
@@ -362,6 +396,7 @@ int main() {
continue;
}
cout << " \"" << check[2] << "\" / \"" << check[3] << "\": decode OK" << endl;
+ string outStr = output.str();
bool match = inputStr == output.str();
verify(false, "decode", check[2] + "/" + check[3], match, inputStr, output.str());
if (checkUpdateTime || checkSameChangeTime) {