mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
197 lines
5.1 KiB
C++
197 lines
5.1 KiB
C++
/* The internal datastructure for a sample of simulation data.
|
|
*
|
|
* Author: Steffen Vogel <post@steffenvogel.de>
|
|
* SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <cinttypes>
|
|
#include <cstring>
|
|
|
|
#include <villas/formats/villas_human.hpp>
|
|
#include <villas/sample.hpp>
|
|
#include <villas/signal.hpp>
|
|
#include <villas/timing.hpp>
|
|
#include <villas/utils.hpp>
|
|
|
|
using namespace villas::node;
|
|
|
|
size_t VILLASHumanFormat::sprintLine(char *buf, size_t len,
|
|
const struct Sample *smp) {
|
|
size_t off = 0;
|
|
|
|
if (flags & (int)SampleFlags::HAS_TS_ORIGIN) {
|
|
if (smp->flags & (int)SampleFlags::HAS_TS_ORIGIN) {
|
|
off += snprintf(buf + off, len - off, "%llu",
|
|
(unsigned long long)smp->ts.origin.tv_sec);
|
|
off += snprintf(buf + off, len - off, ".%09llu",
|
|
(unsigned long long)smp->ts.origin.tv_nsec);
|
|
} else
|
|
off += snprintf(buf + off, len - off, "0.0");
|
|
}
|
|
|
|
if (flags & (int)SampleFlags::HAS_OFFSET) {
|
|
if (smp->flags & (int)SampleFlags::HAS_TS_RECEIVED)
|
|
off += snprintf(buf + off, len - off, "%+e",
|
|
time_delta(&smp->ts.origin, &smp->ts.received));
|
|
}
|
|
|
|
if (flags & (int)SampleFlags::HAS_SEQUENCE) {
|
|
if (smp->flags & (int)SampleFlags::HAS_SEQUENCE)
|
|
off += snprintf(buf + off, len - off, "(%" PRIu64 ")", smp->sequence);
|
|
}
|
|
|
|
if (flags & (int)SampleFlags::HAS_DATA) {
|
|
for (unsigned i = 0; i < smp->length; i++) {
|
|
auto sig = smp->signals->getByIndex(i);
|
|
if (!sig)
|
|
break;
|
|
|
|
off += snprintf(buf + off, len - off, "\t");
|
|
off += smp->data[i].printString(sig->type, buf + off, len - off,
|
|
real_precision);
|
|
}
|
|
}
|
|
|
|
off += snprintf(buf + off, len - off, "%c", delimiter);
|
|
|
|
return off;
|
|
}
|
|
|
|
size_t VILLASHumanFormat::sscanLine(const char *buf, size_t len,
|
|
struct Sample *smp) {
|
|
int ret;
|
|
char *end;
|
|
const char *ptr = buf;
|
|
|
|
double offset = 0;
|
|
|
|
smp->flags = 0;
|
|
smp->signals = signals;
|
|
|
|
/* Format: Seconds.NanoSeconds+Offset(SequenceNumber) Value1 Value2 ...
|
|
* RegEx: (\d+(?:\.\d+)?)([-+]\d+(?:\.\d+)?(?:e[+-]?\d+)?)?(?:\((\d+)\))?
|
|
*
|
|
* Please note that only the seconds and at least one value are mandatory
|
|
*/
|
|
|
|
// Mandatory: seconds
|
|
smp->ts.origin.tv_sec = (uint32_t)strtoul(ptr, &end, 10);
|
|
if (ptr == end || *end == delimiter)
|
|
return -1;
|
|
|
|
smp->flags |= (int)SampleFlags::HAS_TS_ORIGIN;
|
|
|
|
// Optional: nano seconds
|
|
if (*end == '.') {
|
|
ptr = end + 1;
|
|
|
|
smp->ts.origin.tv_nsec = (uint32_t)strtoul(ptr, &end, 10);
|
|
if (ptr == end)
|
|
return -3;
|
|
} else
|
|
smp->ts.origin.tv_nsec = 0;
|
|
|
|
// Optional: offset / delay
|
|
if (*end == '+' || *end == '-') {
|
|
ptr = end;
|
|
|
|
offset = strtof(ptr, &end); // offset is ignored for now
|
|
if (ptr != end)
|
|
smp->flags |= (int)SampleFlags::HAS_OFFSET;
|
|
else
|
|
return -4;
|
|
}
|
|
|
|
// Optional: sequence
|
|
if (*end == '(') {
|
|
ptr = end + 1;
|
|
|
|
smp->sequence = strtoul(ptr, &end, 10);
|
|
if (ptr != end)
|
|
smp->flags |= (int)SampleFlags::HAS_SEQUENCE;
|
|
else
|
|
return -5;
|
|
|
|
if (*end == ')')
|
|
end++;
|
|
}
|
|
|
|
unsigned i;
|
|
for (ptr = end + 1, i = 0; i < smp->capacity; ptr = end + 1, i++) {
|
|
if (*end == delimiter)
|
|
goto out;
|
|
|
|
auto sig = signals->getByIndex(i);
|
|
if (!sig)
|
|
goto out;
|
|
|
|
ret = smp->data[i].parseString(sig->type, ptr, &end);
|
|
if (ret || end == ptr) // There are no valid values anymore.
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
if (*end == delimiter)
|
|
end++;
|
|
|
|
smp->length = i;
|
|
if (smp->length > 0)
|
|
smp->flags |= (int)SampleFlags::HAS_DATA;
|
|
|
|
if (smp->flags & (int)SampleFlags::HAS_OFFSET) {
|
|
struct timespec off = time_from_double(offset);
|
|
smp->ts.received = time_add(&smp->ts.origin, &off);
|
|
|
|
smp->flags |= (int)SampleFlags::HAS_TS_RECEIVED;
|
|
}
|
|
|
|
return end - buf;
|
|
}
|
|
|
|
void VILLASHumanFormat::header(FILE *f, const SignalList::Ptr sigs) {
|
|
// Abort if we are not supposed to, or have already printed the header
|
|
if (!print_header || header_printed)
|
|
return;
|
|
|
|
fprintf(f, "# ");
|
|
|
|
if (flags & (int)SampleFlags::HAS_TS_ORIGIN)
|
|
fprintf(f, "seconds.nanoseconds");
|
|
|
|
if (flags & (int)SampleFlags::HAS_OFFSET)
|
|
fprintf(f, "+offset");
|
|
|
|
if (flags & (int)SampleFlags::HAS_SEQUENCE)
|
|
fprintf(f, "(sequence)");
|
|
|
|
if (flags & (int)SampleFlags::HAS_DATA) {
|
|
for (unsigned i = 0; i < sigs->size(); i++) {
|
|
auto sig = sigs->getByIndex(i);
|
|
if (!sig)
|
|
break;
|
|
|
|
if (!sig->name.empty())
|
|
fprintf(f, "\t%s", sig->name.c_str());
|
|
else
|
|
fprintf(f, "\tsignal%u", i);
|
|
|
|
if (!sig->unit.empty())
|
|
fprintf(f, "[%s]", sig->unit.c_str());
|
|
}
|
|
}
|
|
|
|
fprintf(f, "%c", delimiter);
|
|
|
|
LineFormat::header(f, sigs);
|
|
}
|
|
|
|
// Register format
|
|
static char n[] = "villas.human";
|
|
static char d[] = "VILLAS human readable format";
|
|
static LineFormatPlugin<VILLASHumanFormat, n, d,
|
|
(int)SampleFlags::HAS_TS_ORIGIN |
|
|
(int)SampleFlags::HAS_SEQUENCE |
|
|
(int)SampleFlags::HAS_DATA,
|
|
'\n'>
|
|
p;
|