diff --git a/lib/hooks/pps_ts.cpp b/lib/hooks/pps_ts.cpp index 26abfb6dd..523a5a530 100644 --- a/lib/hooks/pps_ts.cpp +++ b/lib/hooks/pps_ts.cpp @@ -17,39 +17,28 @@ namespace node { class PpsTsHook : public SingleSignalHook { protected: - enum Mode { - SIMPLE, - HORIZON, - } mode; + enum class TimeSource { OS, SAMPLE } timeSource; uint64_t lastSequence; - + bool firstSample; double lastValue; double threshold; + bool armSecDetect; - bool isSynced; - bool isLocked; struct timespec tsVirt; - double timeError; // In seconds - double periodEstimate; // In seconds - double periodErrorCompensation; // In seconds - double period; // In seconds + double period; // In seconds uintmax_t cntEdges; uintmax_t cntSmps; - uintmax_t cntSmpsTotal; - unsigned horizonCompensation; - unsigned horizonEstimation; + unsigned currentSecond; std::vector filterWindow; public: PpsTsHook(Path *p, Node *n, int fl, int prio, bool en = true) - : SingleSignalHook(p, n, fl, prio, en), mode(Mode::SIMPLE), - lastSequence(0), lastValue(0), threshold(1.5), isSynced(false), - isLocked(false), timeError(0.0), periodEstimate(0.0), - periodErrorCompensation(0.0), period(0.0), cntEdges(0), cntSmps(0), - cntSmpsTotal(0), horizonCompensation(10), horizonEstimation(10), - currentSecond(0), filterWindow(horizonEstimation + 1, 0) {} + : SingleSignalHook(p, n, fl, prio, en), timeSource(TimeSource::OS), + lastSequence(0), firstSample(false), lastValue(0), threshold(1.5), + armSecDetect(false), tsVirt({0, 0}), period(0.0), cntEdges(0), + cntSmps(0), currentSecond(0) {} void parse(json_t *json) override { int ret; @@ -59,151 +48,84 @@ class PpsTsHook : public SingleSignalHook { SingleSignalHook::parse(json); - const char *mode_str = nullptr; + const char *timeSourceC = nullptr; - double fSmps = 1.0; - ret = json_unpack_ex(json, &err, 0, "{ s?: s, s?: f, s?: F, s?: i, s?: i }", - "mode", &mode_str, "threshold", &threshold, - "expected_smp_rate", &fSmps, "horizon_estimation", - &horizonEstimation, "horizon_compensation", - &horizonCompensation); + ret = json_unpack_ex(json, &err, 0, "{ s?: s, s?: f }", "time_source", + &timeSourceC, "threshold", &threshold); if (ret) throw ConfigError(json, err, "node-config-hook-pps_ts"); - period = 1.0 / fSmps; - currentSecond = time(nullptr); - - if (mode_str) { - if (!strcmp(mode_str, "simple")) - mode = Mode::SIMPLE; - else if (!strcmp(mode_str, "horizon")) - mode = Mode::HORIZON; + if (timeSourceC) { + if (!strcmp(timeSourceC, "sample")) + timeSource = TimeSource::SAMPLE; else - throw ConfigError(json, "node-config-hook-pps_ts-mode", - "Unsupported mode: {}", mode_str); + timeSource = TimeSource::OS; } state = State::PARSED; } villas::node::Hook::Reason process(struct Sample *smp) override { - switch (mode) { - case Mode::SIMPLE: - return processSimple(smp); - - case Mode::HORIZON: - return processHorizon(smp); - - default: - return Reason::ERROR; - } - } - - villas::node::Hook::Reason processSimple(struct Sample *smp) { assert(state == State::STARTED); // Get value of PPS signal float value = smp->data[signalIndex].f; // TODO check if it is really float - // Detect Edge - bool isEdge = lastValue < threshold && value > threshold; - if (isEdge) { - tsVirt.tv_sec = currentSecond + 1; - tsVirt.tv_nsec = 0; - period = 1.0 / cntSmps; - cntSmps = 0; - cntEdges++; - currentSecond = 0; - } else { - struct timespec tsPeriod = time_from_double(period); - tsVirt = time_add(&tsVirt, &tsPeriod); - } - - lastValue = value; - cntSmps++; - - if (!currentSecond && - tsVirt.tv_nsec > - 0.5e9) //take the second somewere in the center of the last second to reduce impact of system clock error - currentSecond = time(nullptr); - - if (cntEdges < 5) + if (!firstSample) { + firstSample = true; + lastValue = value; return Hook::Reason::SKIP_SAMPLE; - - smp->ts.origin = tsVirt; - smp->flags |= (int)SampleFlags::HAS_TS_ORIGIN; - - if ((smp->sequence - lastSequence) > 1) - logger->warn("Samples missed: {} sampled missed", - smp->sequence - lastSequence); - - lastSequence = smp->sequence; - return Hook::Reason::OK; - } - - villas::node::Hook::Reason processHorizon(struct Sample *smp) { - assert(state == State::STARTED); - - // Get value of PPS signal - float value = smp->data[signalIndex].f; // TODO check if it is really float + } // Detect Edge bool isEdge = lastValue < threshold && value > threshold; - lastValue = value; - if (isEdge) { - if (isSynced) { - if (tsVirt.tv_nsec > 0.5e9) - timeError += 1.0 - (tsVirt.tv_nsec / 1.0e9); - else - timeError -= (tsVirt.tv_nsec / 1.0e9); - - filterWindow[cntEdges % filterWindow.size()] = cntSmpsTotal; - // Estimated sample period over last 'horizonEstimation' seconds - unsigned int tmp = - cntEdges < filterWindow.size() ? cntEdges : horizonEstimation; - double cntSmpsAvg = - (cntSmpsTotal - - filterWindow[(cntEdges - tmp) % filterWindow.size()]) / - tmp; - periodEstimate = 1.0 / cntSmpsAvg; - periodErrorCompensation = - timeError / (cntSmpsAvg * horizonCompensation); - period = periodEstimate + periodErrorCompensation; - } else { - tsVirt.tv_sec = time(nullptr); + + if (cntEdges > 0) { + tsVirt.tv_sec = currentSecond + 1; tsVirt.tv_nsec = 0; - isSynced = true; - cntEdges = 0; - cntSmpsTotal = 0; + period = 1.0 / cntSmps; + currentSecond = 0; } cntSmps = 0; cntEdges++; + armSecDetect = true; + } else { + struct timespec tsPeriod = time_from_double(period); + tsVirt = time_add(&tsVirt, &tsPeriod); + } - logger->debug( - "Time Error is: {} periodEstimate {} periodErrorCompensation {}", - timeError, periodEstimate, periodErrorCompensation); + if (armSecDetect) { + long current_nsec = 0; + if (timeSource == TimeSource::OS) + current_nsec = time_now().tv_nsec; + else if (timeSource == TimeSource::SAMPLE) + current_nsec = smp->ts.origin.tv_nsec; + + if (current_nsec > 0.5e9) { + //take the second somewere in the center of the last second to reduce impact of system clock error + if (timeSource == TimeSource::OS) + currentSecond = time_now().tv_sec; + else if (timeSource == TimeSource::SAMPLE) + currentSecond = smp->ts.origin.tv_sec; + armSecDetect = false; + } } + lastValue = value; cntSmps++; - cntSmpsTotal++; - if (cntEdges < 5) + if (cntEdges < 2) return Hook::Reason::SKIP_SAMPLE; - smp->ts.origin = tsVirt; smp->flags |= (int)SampleFlags::HAS_TS_ORIGIN; - struct timespec tsPeriod = time_from_double(period); - tsVirt = time_add(&tsVirt, &tsPeriod); - if ((smp->sequence - lastSequence) > 1) logger->warn("Samples missed: {} sampled missed", smp->sequence - lastSequence); lastSequence = smp->sequence; - return Hook::Reason::OK; } }; diff --git a/lib/sample.cpp b/lib/sample.cpp index e9ad06f30..2974e9536 100644 --- a/lib/sample.cpp +++ b/lib/sample.cpp @@ -206,7 +206,7 @@ int villas::node::sample_cmp(struct Sample *a, struct Sample *b, double epsilon, // Compare timestamp if (flags & (int)SampleFlags::HAS_TS_ORIGIN) { - if (time_delta(&a->ts.origin, &b->ts.origin) > epsilon) { + if (abs(time_delta(&a->ts.origin, &b->ts.origin)) > epsilon) { printf("ts.origin: %f != %f\n", time_to_double(&a->ts.origin), time_to_double(&b->ts.origin)); return 3; diff --git a/tests/integration/hook-pps_ts.sh b/tests/integration/hook-pps_ts.sh new file mode 100755 index 000000000..5d178c3e5 --- /dev/null +++ b/tests/integration/hook-pps_ts.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# Integration test for pps_ts hook. +# +# Author: Manuel Pitz +# SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University +# SPDX-License-Identifier: Apache-2.0 + +set -e + +DIR=$(mktemp -d) +pushd ${DIR} + +function finish { + popd +} +trap finish EXIT + +cat > input.dat < expect.dat < output.dat +villas compare output.dat expect.dat diff --git a/tests/integration/node-hook.sh b/tests/integration/node-hook.sh index af1498add..07221b350 100755 --- a/tests/integration/node-hook.sh +++ b/tests/integration/node-hook.sh @@ -74,4 +74,4 @@ EOF villas node config.json -villas compare output.dat expect.dat +villas compare -T output.dat expect.dat diff --git a/tests/integration/node-mapping.sh b/tests/integration/node-mapping.sh index cc97d1bd4..d09703f50 100755 --- a/tests/integration/node-mapping.sh +++ b/tests/integration/node-mapping.sh @@ -80,4 +80,4 @@ EOF villas node -d debug config.json -villas compare output.dat expect.dat +villas compare -T output.dat expect.dat diff --git a/tests/integration/node-multiplexing.sh b/tests/integration/node-multiplexing.sh index 698bea278..8717eacff 100755 --- a/tests/integration/node-multiplexing.sh +++ b/tests/integration/node-multiplexing.sh @@ -117,7 +117,7 @@ EOF villas node config.json -villas compare output.dat expect_${MODE}.dat +villas compare -T output.dat expect_${MODE}.dat rm output.dat