diff --git a/include/villas/formats/opal_asyncip.hpp b/include/villas/formats/opal_asyncip.hpp new file mode 100644 index 000000000..90884c831 --- /dev/null +++ b/include/villas/formats/opal_asyncip.hpp @@ -0,0 +1,93 @@ +/** A custom format for OPAL-RTs AsyncIP example + * + * @file + * @author Steffen Vogel + * @copyright 2014-2021, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include + +#include + +namespace villas { +namespace node { + +/* Forward declarations. */ +struct Sample; + +class OpalAsyncIPFormat : public BinaryFormat { + +protected: + const int MAXSIZE = 64; + + struct Payload { + int16_t dev_id; // (2 bytes) Sender device ID + int32_t msg_id; // (4 bytes) Message ID + int16_t msg_len; // (2 bytes) Message length (data only) + double data[]; // Up to MAXSIZE doubles (8 bytes each) + } __attribute__((packed)); + + int16_t dev_id; + +public: + OpalAsyncIPFormat(int fl, uint8_t did = 0) : + BinaryFormat(fl), + dev_id(did) + { } + + virtual + int sscan(const char *buf, size_t len, size_t *rbytes, struct Sample * const smps[], unsigned cnt); + virtual + int sprint(char *buf, size_t len, size_t *wbytes, const struct Sample * const smps[], unsigned cnt); + + virtual + void parse(json_t *json); +}; + + +class OpalAsyncIPFormatPlugin : public FormatFactory { + +public: + using FormatFactory::FormatFactory; + + virtual + Format * make() + { + return new OpalAsyncIPFormat((int) SampleFlags::HAS_TS_ORIGIN | (int) SampleFlags::HAS_SEQUENCE | (int) SampleFlags::HAS_DATA); + } + + /// Get plugin name + virtual + std::string getName() const + { + return "opal.asyncip"; + } + + /// Get plugin description + virtual + std::string getDescription() const + { + return "OPAL-RTs AsyncIP example format"; + } +}; + +} /* namespace node */ +} /* namespace villas */ diff --git a/include/villas/signal_data.hpp b/include/villas/signal_data.hpp index 75197852c..fbdfef793 100644 --- a/include/villas/signal_data.hpp +++ b/include/villas/signal_data.hpp @@ -63,7 +63,7 @@ union SignalData { } /** Convert signal data from one description/format to another. */ - void cast(enum SignalType type, enum SignalType to); + SignalData cast(enum SignalType type, enum SignalType to) const; /** Set data from double */ void set(enum SignalType type, double val); diff --git a/lib/formats/CMakeLists.txt b/lib/formats/CMakeLists.txt index 052d6fb94..2bf20a3d3 100644 --- a/lib/formats/CMakeLists.txt +++ b/lib/formats/CMakeLists.txt @@ -68,6 +68,7 @@ list(APPEND FORMAT_SRC json.cpp line.cpp msg.cpp + opal_asyncip.cpp raw.cpp value.cpp villas_binary.cpp diff --git a/lib/formats/opal_asyncip.cpp b/lib/formats/opal_asyncip.cpp new file mode 100644 index 000000000..647972c2a --- /dev/null +++ b/lib/formats/opal_asyncip.cpp @@ -0,0 +1,132 @@ +/** A custom format for OPAL-RTs AsyncIP example + * + * @author Steffen Vogel + * @copyright 2014-2021, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include +#include +#include +#include + +using namespace villas; +using namespace villas::node; + +int OpalAsyncIPFormat::sprint(char *buf, size_t len, size_t *wbytes, const struct Sample * const smps[], unsigned cnt) +{ + unsigned i; + auto *ptr = buf; + ssize_t slen = len; + + for (i = 0; i < cnt && ptr - buf < slen; i++) { + auto *pl = (struct Payload *) ptr; + auto *smp = smps[i]; + + auto wlen = smp->length * sizeof(double) + sizeof(struct Payload); + if (wlen > len) + return -1; + + pl->dev_id = htole16(dev_id); + pl->msg_id = htole32(smp->sequence); + pl->msg_len = htole16(smp->length * sizeof(double)); + + for (unsigned j = 0; j < smp->length; j++) { + auto sig = smp->signals->getByIndex(j); + auto d = smp->data[j]; + + d = d.cast(sig->type, SignalType::FLOAT); + d.i = htole64(d.i); + + pl->data[j] = d.f; + } + + ptr += wlen; + } + + if (wbytes) + *wbytes = ptr - buf; + + return i; +} + +int OpalAsyncIPFormat::sscan(const char *buf, size_t len, size_t *rbytes, struct Sample * const smps[], unsigned cnt) +{ + unsigned i; + auto *ptr = buf; + ssize_t slen = len; + + if (len % 8 != 0) + return -1; /* Packet size is invalid: Must be multiple of 4 bytes */ + + for (i = 0; i < cnt && ptr - buf + sizeof(struct Payload) < slen; i++) { + if (len < 8) + return -1; /* Packet size is invalid: Must be multiple of 4 bytes */ + + auto *pl = (struct Payload *) ptr; + auto *smp = smps[i]; + + auto rlen = le16toh(pl->msg_len); + if (len < ptr - buf + rlen + sizeof(struct Payload)) + return -2; + + smp->sequence = le32toh(pl->msg_id); + smp->length = rlen / sizeof(double); + smp->flags = (int) SampleFlags::HAS_SEQUENCE; + smp->signals = signals; + + for (unsigned j = 0; j < MIN(smp->length, smp->capacity); j++) { + auto sig = signals->getByIndex(j); + + SignalData d; + d.f = pl->data[j]; + d.i = le64toh(d.i); + + smp->data[j] = d.cast(SignalType::FLOAT, sig->type); + } + + ptr += rlen + sizeof(struct Payload); + } + + if (rbytes) + *rbytes = ptr - buf; + + return i; +} + +void OpalAsyncIPFormat::parse(json_t *json) +{ + int ret; + json_error_t err; + int did = -1; + + ret = json_unpack_ex(json, &err, 0, "{ s?: i }", + "dev_id", &did + ); + if (ret) + throw ConfigError(json, err, "node-config-format-opal-asyncip", "Failed to parse format configuration"); + + if (did >= 0) + dev_id = did; + + Format::parse(json); +} + +static OpalAsyncIPFormatPlugin p; diff --git a/lib/hooks/cast.cpp b/lib/hooks/cast.cpp index fdc1f9ea1..d5f0bcfe7 100644 --- a/lib/hooks/cast.cpp +++ b/lib/hooks/cast.cpp @@ -109,7 +109,7 @@ public: auto orig_sig = smp->signals->getByIndex(index); auto new_sig = signals->getByIndex(index); - smp->data[index].cast(orig_sig->type, new_sig->type); + smp->data[index] = smp->data[index].cast(orig_sig->type, new_sig->type); } return Reason::OK; diff --git a/lib/hooks/lua.cpp b/lib/hooks/lua.cpp index 90219635c..a1e91e455 100644 --- a/lib/hooks/lua.cpp +++ b/lib/hooks/lua.cpp @@ -164,7 +164,7 @@ void lua_tosignaldata(lua_State *L, union SignalData *data, enum SignalType targ return; } - data->cast(type, targetType); + *data = data->cast(type, targetType); } static diff --git a/lib/signal_data.cpp b/lib/signal_data.cpp index efac78c52..249cee826 100644 --- a/lib/signal_data.cpp +++ b/lib/signal_data.cpp @@ -53,98 +53,95 @@ void SignalData::set(enum SignalType type, double val) } } -void SignalData::cast(enum SignalType from, enum SignalType to) +SignalData SignalData::cast(enum SignalType from, enum SignalType to) const { - if (from == to) /* Nothing to do */ - return; + SignalData n = *this; switch (to) { case SignalType::BOOLEAN: switch(from) { - case SignalType::BOOLEAN: - break; - case SignalType::INTEGER: - this->b = this->i; + n.b = this->i; break; case SignalType::FLOAT: - this->b = this->f; + n.b = this->f; break; case SignalType::COMPLEX: - this->b = std::real(this->z); + n.b = std::real(this->z); break; - default: { } + case SignalType::INVALID: + case SignalType::BOOLEAN: + break; } break; case SignalType::INTEGER: switch(from) { case SignalType::BOOLEAN: - this->i = this->b; - break; - - case SignalType::INTEGER: + n.i = this->b; break; case SignalType::FLOAT: - this->i = this->f; + n.i = this->f; break; case SignalType::COMPLEX: - this->i = std::real(this->z); + n.i = std::real(this->z); break; - default: { } + case SignalType::INVALID: + case SignalType::INTEGER: + break; } break; case SignalType::FLOAT: switch(from) { case SignalType::BOOLEAN: - this->f = this->b; + n.f = this->b; break; case SignalType::INTEGER: - this->f = this->i; - break; - - case SignalType::FLOAT: + n.f = this->i; break; case SignalType::COMPLEX: - this->f = std::real(this->z); + n.f = std::real(this->z); break; - default: { } + case SignalType::INVALID: + case SignalType::FLOAT: + break; } break; case SignalType::COMPLEX: switch(from) { case SignalType::BOOLEAN: - this->z = this->b; + n.z = this->b; break; case SignalType::INTEGER: - this->z = this->i; + n.z = this->i; break; case SignalType::FLOAT: - this->z = this->f; + n.z = this->f; break; + case SignalType::INVALID: case SignalType::COMPLEX: break; - - default: { } } break; default: { } } + + return n; } int SignalData::parseString(enum SignalType type, const char *ptr, char **end) diff --git a/tests/integration/convert.sh b/tests/integration/convert.sh index 89038ece3..86d41d0ca 100755 --- a/tests/integration/convert.sh +++ b/tests/integration/convert.sh @@ -33,7 +33,7 @@ function finish { } trap finish EXIT -FORMATS="villas.human csv tsv json" +FORMATS="villas.human csv tsv json opal.asyncip" villas signal -v5 -n -l20 mixed > input.dat