diff --git a/contrib/html/openapi.yaml b/contrib/html/openapi.yaml
index f8fbd219c..26ec93a57 100644
--- a/contrib/html/openapi.yaml
+++ b/contrib/html/openapi.yaml
@@ -2,7 +2,7 @@ openapi: 3.1.0
info:
title: ebusd-http
description: The API that ebusd provides on HTTP port.
- version: "23.2"
+ version: "23.3"
servers:
- url: http://127.0.0.1:8080/
paths:
@@ -145,6 +145,50 @@ paths:
500:
description: General error.
content: { }
+ /templates:
+ get:
+ summary: Get all known field templates for the root.
+ responses:
+ 200:
+ description: Success.
+ content:
+ application/json;charset=utf-8:
+ schema:
+ $ref: '#/components/schemas/Templates'
+ 400:
+ description: Invalid request parameters.
+ content: { }
+ 403:
+ description: User not authorized.
+ content: { }
+ 500:
+ description: General error.
+ content: { }
+ /templates/{path}:
+ get:
+ summary: Get all known field templates for the path.
+ parameters:
+ - name: path
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ 200:
+ description: Success.
+ content:
+ application/json;charset=utf-8:
+ schema:
+ $ref: '#/components/schemas/Templates'
+ 400:
+ description: Invalid request parameters.
+ content: { }
+ 403:
+ description: User not authorized.
+ content: { }
+ 500:
+ description: General error.
+ content: { }
/raw:
get:
summary: Retrieve raw data from grabbed and/or decoded messages.
@@ -392,8 +436,12 @@ components:
type: integer
minimum: 0
condition:
- type: string
- description: the condition string in case of a conditional message (only with full).
+ description: the condition(s) in case of a conditional message (only with full).
+ oneOf:
+ - $ref: '#/components/schemas/Condition'
+ - type: array
+ items:
+ $ref: '#/components/schemas/Condition'
lastup:
$ref: '#/components/schemas/Seconds'
description: the time in UTC seconds of the last update of the message (0
@@ -407,8 +455,12 @@ components:
description: destination master or slave address.
example: 8
id:
- $ref: '#/components/schemas/Symbols'
- description: the message ID composed of PBSB and further master data bytes (only with def).
+ description: the message ID composed of PBSB and further master data bytes (only with def), or an array thereof in case of a chained message.
+ oneOf:
+ - $ref: '#/components/schemas/Symbols'
+ - type: array
+ items:
+ $ref: '#/components/schemas/Symbols'
comment:
type: string
description: the message comment (only with verbose).
@@ -465,10 +517,10 @@ components:
- type: string
- type: number
nullable: true
- FieldDef:
+ FieldTemplate:
+ description: a single field template.
required:
- name
- - slave
- type
- isbits
- length
@@ -477,9 +529,6 @@ components:
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.
@@ -513,6 +562,66 @@ components:
comment:
type: string
description: the field comment.
+ FieldDef:
+ description: a single field definition.
+ allOf:
+ - $ref: '#/components/schemas/FieldTemplate'
+ - type: object
+ required:
+ - slave
+ properties:
+ slave:
+ type: boolean
+ description: whether the field is part of the slave data.
+ Templates:
+ type: array
+ description: list of known field templates.
+ items:
+ oneOf:
+ - $ref: '#/components/schemas/FieldTemplate'
+ - type: object
+ required:
+ - name
+ - sequence
+ properties:
+ name:
+ type: string
+ description: the template set name.
+ sequence:
+ type: array
+ description: the sequence of fields.
+ items:
+ $ref: '#/components/schemas/FieldTemplate'
+ Condition:
+ description: a single condition.
+ required:
+ - name
+ - message
+ properties:
+ name:
+ type: string
+ description: name of the condition.
+ message:
+ type: string
+ description: name of the referenced message.
+ circuit:
+ type: string
+ description: name of the referenced circuit.
+ zz:
+ maximum: 255
+ minimum: 0
+ type: integer
+ description: the circuit slave address.
+ field:
+ type: string
+ description: the field name in the referenced message.
+ value:
+ type: array
+ description: the value ranges for the condition.
+ items:
+ oneOf:
+ - type: number
+ - type: string
DataType:
description: a known field data type.
type: object
diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp
index f78270f65..5995c8ee3 100644
--- a/src/ebusd/main.cpp
+++ b/src/ebusd/main.cpp
@@ -352,7 +352,14 @@ int main(int argc, char* argv[], char* envp[]) {
} else {
logNotice(lf_main, "configuration dump:");
}
+ *out << "{\"datatypes\":[";
+ DataTypeList::getInstance()->dump(s_opt.dumpConfig, true, out);
+ *out << "],\"templates\":[";
+ const auto tmpl = s_scanHelper->getTemplates("");
+ tmpl->dump(s_opt.dumpConfig, out);
+ *out << "],\"messages\":";
s_messageMap->dump(true, s_opt.dumpConfig, out);
+ *out << "}";
if (fout.is_open()) {
fout.close();
}
diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp
index 3d35a3765..5da4dbab8 100644
--- a/src/ebusd/mainloop.cpp
+++ b/src/ebusd/mainloop.cpp
@@ -2254,6 +2254,18 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri
return formatHttpResult(ret, type, ostream);
}
+ if (uri == "/templates" || uri.substr(0, 11) == "/templates/") {
+ *ostream << "[";
+ OutputFormat verbosity = OF_NAMES|OF_JSON|OF_ALL_ATTRS;
+ string name = uri == "/templates" ? "" : uri.substr(11) + "/";
+ const auto tmpl = m_scanHelper->getTemplates(name);
+ tmpl->dump(verbosity, ostream);
+ *ostream << "\n]";
+ type = 6;
+ *connected = false;
+ return formatHttpResult(ret, type, ostream);
+ }
+
if (uri == "/raw") {
time_t since = 0, until = 0;
bool onlyUnknown = false;
diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp
index 9e337fe05..4309cc42c 100644
--- a/src/ebusd/scan.cpp
+++ b/src/ebusd/scan.cpp
@@ -202,6 +202,19 @@ bool ScanHelper::readTemplates(const string relPath, const string extension, boo
return false;
}
+void ScanHelper::dumpTemplates(OutputFormat outputFormat, ostream* output) const {
+ bool prependSeparator = false;
+ for (auto it : m_templatesByPath) {
+ if (prependSeparator) {
+ *output << ",";
+ }
+ const auto templates = it.second;
+ if (templates->dump(outputFormat, output)) {
+ prependSeparator = true;
+ }
+ }
+}
+
result_t ScanHelper::readConfigFiles(const string& relPath, const string& extension, bool recursive,
string* errorDescription) {
vector files, dirs;
diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h
index bc8cecf58..6cb4d6c3c 100644
--- a/src/ebusd/scan.h
+++ b/src/ebusd/scan.h
@@ -158,6 +158,13 @@ class ScanHelper : public Resolver {
*/
bool readTemplates(const string relPath, const string extension, bool available);
+ /**
+ * Dump the loaded @a DataFieldTemplates to the output.
+ * @param outputFormat the @a OutputFormat options.
+ * @param output the @a ostream to dump to.
+ */
+ void dumpTemplates(OutputFormat outputFormat, ostream* output) const;
+
/**
* Read the configuration files from the specified path.
* @param relPath the relative path from which to read the files (without trailing "/").
diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp
index 0e9d38bcd..f463ee70e 100644
--- a/src/lib/ebus/data.cpp
+++ b/src/lib/ebus/data.cpp
@@ -482,7 +482,10 @@ void SingleDataField::dumpPrefix(bool prependFieldSeparator, OutputFormat output
dumpString(prependFieldSeparator, m_name, output);
}
if (outputFormat & OF_JSON) {
- *output << ", \"slave\": " << (m_partType == pt_slaveData ? "true" : "false") << ", ";
+ if (m_partType != pt_any) {
+ *output << ", \"slave\": " << (m_partType == pt_slaveData ? "true" : "false");
+ }
+ *output << ", ";
} else {
*output << FIELD_SEPARATOR;
if (m_partType == pt_masterData) {
@@ -1459,4 +1462,27 @@ const DataField* DataFieldTemplates::get(const string& name) const {
return ref->second;
}
+bool DataFieldTemplates::dump(OutputFormat outputFormat, ostream* output) const {
+ bool prependFieldSeparator = false;
+ for (const auto &it : m_fieldsByName) {
+ const DataField *dataField = it.second;
+ if (outputFormat & OF_JSON) {
+ if (dataField->isSet()) {
+ if (prependFieldSeparator) {
+ *output << ",\n";
+ }
+ *output << "{\"name\":\"" << dataField->getName(-1) << "\", \"sequence\": [";
+ dataField->dump(false, outputFormat, output);
+ *output << "]}";
+ } else {
+ dataField->dump(prependFieldSeparator, outputFormat, output);
+ }
+ } else {
+ dataField->dump(prependFieldSeparator, outputFormat, output);
+ }
+ prependFieldSeparator = true;
+ }
+ return !prependFieldSeparator;
+}
+
} // namespace ebusd
diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h
index f205509a8..810c34884 100755
--- a/src/lib/ebus/data.h
+++ b/src/lib/ebus/data.h
@@ -836,6 +836,14 @@ class DataFieldTemplates : public MappedFileReader {
*/
const DataField* get(const string& name) const;
+ /**
+ * Dump the templates to the output.
+ * @param outputFormat the @a OutputFormat options.
+ * @param output the @a ostream to dump to.
+ * @return true when a template was written to the output.
+ */
+ bool dump(OutputFormat outputFormat, ostream* output) const;
+
private:
/** the known template @a DataField instances by name. */
diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp
index 54a634fa5..1c77a7fd0 100644
--- a/src/lib/ebus/message.cpp
+++ b/src/lib/ebus/message.cpp
@@ -970,9 +970,8 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b
*output << ",\n \"pollprio\": " << setw(0) << dec << getPollPriority();
}
if (isConditional()) {
- *output << ",\n \"condition\": \"";
- m_condition->dump(false, output);
- *output << "\"";
+ *output << ",\n \"condition\": ";
+ m_condition->dumpJson(output);
}
}
if (withData) {
@@ -985,13 +984,8 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b
}
*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 << ",\n \"id\": [";
+ dumpIdsJson(output);
*output << "]";
}
appendAttributes(outputFormat, output);
@@ -1022,6 +1016,15 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b
*output << "\n }";
}
+void Message::dumpIdsJson(ostringstream* output) const {
+ for (auto it = m_id.begin(); it < m_id.end(); it++) {
+ if (it > m_id.begin()) {
+ *output << ", ";
+ }
+ *output << dec << static_cast(*it);
+ }
+}
+
bool Message::setDataHandlerState(int state, bool addBits) {
if (addBits ? state == (m_dataHandlerState&state) : state == m_dataHandlerState) {
return false;
@@ -1296,6 +1299,22 @@ void ChainedMessage::dumpField(const string& fieldName, bool withConditions, Out
}
}
+void ChainedMessage::dumpIdsJson(ostringstream* output) const {
+ for (auto idsit = m_ids.begin(); idsit < m_ids.end(); idsit++) {
+ if (idsit > m_ids.begin()) {
+ *output << ",";
+ }
+ *output << "[";
+ for (auto it = (*idsit).begin(); it < (*idsit).end(); it++) {
+ if (it > (*idsit).begin()) {
+ *output << ", ";
+ }
+ *output << dec << static_cast(*it);
+ }
+ *output << "]";
+ }
+}
+
/**
* Get the first available @a Message from the list.
@@ -1527,6 +1546,27 @@ void SimpleCondition::dump(bool matched, ostream* output) const {
}
}
+void SimpleCondition::dumpJson(ostream* output) const {
+ *output << "{\"name\": \"" << m_refName << "\"";
+ if (!m_circuit.empty()) {
+ *output << ",\"circuit\":\"" << m_circuit << "\"";
+ }
+ *output << ",\"message\":\"" << (m_name.empty() ? "scan" : m_name) << "\"";
+ if (m_dstAddress != SYN) {
+ *output << ",\"zz\":" << dec << static_cast(m_dstAddress);
+ }
+ if (!m_field.empty()) {
+ *output << ",\"field\":\"" << m_field << "\"";
+ }
+ if (m_hasValues) {
+ *output << ",\"value\":[";
+ dumpValuesJson(output);
+ *output << "]";
+ }
+ *output << "}";
+}
+
+
CombinedCondition* SimpleCondition::combineAnd(Condition* other) {
CombinedCondition* ret = new CombinedCondition();
return ret->combineAnd(this)->combineAnd(other);
@@ -1625,6 +1665,17 @@ bool SimpleNumericCondition::checkValue(const Message* message, const string& fi
return false;
}
+void SimpleNumericCondition::dumpValuesJson(ostream* output) const {
+ bool first = true;
+ for (const auto value : m_valueRanges) {
+ if (!first) {
+ *output << ",";
+ }
+ *output << static_cast(value);
+ first = false;
+ }
+}
+
bool SimpleStringCondition::checkValue(const Message* message, const string& field) {
ostringstream output;
@@ -1641,6 +1692,17 @@ bool SimpleStringCondition::checkValue(const Message* message, const string& fie
return false;
}
+void SimpleStringCondition::dumpValuesJson(ostream* output) const {
+ bool first = true;
+ for (const auto value : m_values) {
+ if (!first) {
+ *output << ",";
+ }
+ *output << "\"" << value << "\"";
+ first = false;
+ }
+}
+
void CombinedCondition::dump(bool matched, ostream* output) const {
for (const auto condition : m_conditions) {
@@ -1648,6 +1710,19 @@ void CombinedCondition::dump(bool matched, ostream* output) const {
}
}
+void CombinedCondition::dumpJson(ostream* output) const {
+ *output << "[";
+ bool first = true;
+ for (const auto condition : m_conditions) {
+ if (!first) {
+ *output << ",";
+ }
+ condition->dumpJson(output);
+ first = false;
+ }
+ *output << "]";
+}
+
result_t CombinedCondition::resolve(void (*readMessageFunc)(Message* message), MessageMap* messages,
ostringstream* errorMessage) {
for (const auto condition : m_conditions) {
@@ -2817,7 +2892,7 @@ void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* o
bool first = true;
bool isJson = (outputFormat & OF_JSON) != 0;
if (isJson) {
- *output << (m_addAll ? "[" : "}");
+ *output << (m_addAll ? "[" : "{");
} else {
Message::dumpHeader(nullptr, output);
}
diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h
index fcd60ac9b..7d314670f 100644
--- a/src/lib/ebus/message.h
+++ b/src/lib/ebus/message.h
@@ -601,6 +601,12 @@ class Message : public AttributedItem {
OutputFormat outputFormat, ostringstream* output) const;
protected:
+ /**
+ * Dump the ID(s) to the output in JSON format.
+ * @param output the @a ostringstream to append to.
+ */
+ virtual void dumpIdsJson(ostringstream* output) const;
+
/** the source filename. */
const string m_filename;
@@ -748,6 +754,8 @@ class ChainedMessage : public Message {
result_t prepareMasterPart(size_t index, const char separator, istringstream* input,
MasterSymbolString* master) override;
+ // @copydoc
+ void dumpIdsJson(ostringstream* output) const override;
public:
// @copydoc
@@ -884,6 +892,18 @@ class Condition {
*/
virtual void dump(bool matched, ostream* output) const = 0;
+ /**
+ * Write the condition definition in JSON to the @a ostream.
+ * @param output the @a ostream to append to.
+ */
+ virtual void dumpJson(ostream* output) const = 0;
+
+ /**
+ * Write the values part of the condition definition in JSON to the @a ostream.
+ * @param output the @a ostream to append to.
+ */
+ virtual void dumpValuesJson(ostream* output) const { /* empty on top level*/ };
+
/**
* Combine this condition with another instance using a logical and.
* @param other the @a Condition to combine with.
@@ -951,6 +971,9 @@ class SimpleCondition : public Condition {
// @copydoc
void dump(bool matched, ostream* output) const override;
+ // @copydoc
+ void dumpJson(ostream* output) const override;
+
// @copydoc
CombinedCondition* combineAnd(Condition* other) override;
@@ -1043,6 +1066,9 @@ class SimpleNumericCondition : public SimpleCondition {
// @copydoc
bool checkValue(const Message* message, const string& field) override;
+ // @copydoc
+ void dumpValuesJson(ostream* output) const override;
+
private:
/** the valid value ranges (pairs of from/to inclusive), empty for @a m_message seen check. */
@@ -1084,6 +1110,9 @@ class SimpleStringCondition : public SimpleCondition {
// @copydoc
bool checkValue(const Message* message, const string& field) override;
+ // @copydoc
+ void dumpValuesJson(ostream* output) const override;
+
private:
/** the valid values. */
@@ -1110,6 +1139,9 @@ class CombinedCondition : public Condition {
// @copydoc
void dump(bool matched, ostream* output) const override;
+ // @copydoc
+ void dumpJson(ostream* output) const override;
+
// @copydoc
CombinedCondition* combineAnd(Condition* other) override { m_conditions.push_back(other); return this; }