Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 47 additions & 125 deletions lib/hooks/pps_ts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uintmax_t> 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;
Expand All @@ -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;
}
};
Expand Down
2 changes: 1 addition & 1 deletion lib/sample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
63 changes: 63 additions & 0 deletions tests/integration/hook-pps_ts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
#
# Integration test for pps_ts hook.
#
# Author: Manuel Pitz <post@cl0.de>
# 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 <<EOF
# seconds.nanoseconds+offset(sequence) sync sine
1788517356.782306674+1.024255e-01(0) 0.00000000000000000 0.00264685783509372
1788517356.884999323+9.981581e-02(1) 5.00000000000000000 0.60350038276657758
1788517356.985179986+9.882144e-02(2) 5.00000000000000000 0.95724907881408772
1788517357.084351027+9.759937e-02(3) 0.00000000000000000 0.94615532027329974
1788517357.182275536+1.025886e-01(4) 0.00000000000000000 0.58580041376441028
1788517357.285092402+9.824974e-02(5) 0.00000000000000000 -0.02014874261232052
1788517357.383639491+1.011173e-01(6) 0.00000000000000000 -0.59666570972198407
1788517357.485462095+1.014006e-01(7) 0.00000000000000000 -0.95776030931804701
1788517357.587346527+9.696400e-02(8) 0.00000000000000000 -0.93989540796406668
1788517357.684528215+1.006499e-01(9) 0.00000000000000000 -0.57427093468242119
1788517357.785542210+9.648715e-02(10) 0.00000000000000000 0.02297431165519980
1788517357.882334633+1.013856e-01(11) 5.00000000000000000 0.59006638467336403
1788517357.984202776+9.787593e-02(12) 5.00000000000000000 0.95545496189635382
1788517358.082479139+1.029714e-01(13) 0.00000000000000000 0.94989711630388352
1788517358.185651330+9.911848e-02(14) 0.00000000000000000 0.56847957605663302
1788517358.285096080+9.691877e-02(15) 0.00000000000000000 -0.02017184747110692
1788517358.382462688+1.005295e-01(16) 0.00000000000000000 -0.59071578581039907
1788517358.483432703+9.899941e-02(17) 0.00000000000000000 -0.95401575986363329
1788517358.582764753+1.026306e-01(18) 0.00000000000000000 -0.94933467267527527
1788517358.685809870+9.775208e-02(19) 0.00000000000000000 -0.56765977550845070
1788517358.783907175+9.807006e-02(20) 0.00000000000000000 0.01270276364811536
1788517358.882326207+1.009989e-01(21) 5.00000000000000000 0.59002364077587799
1788517358.983722150+1.005822e-01(22) 5.00000000000000000 0.95455933367844681
EOF

cat > expect.dat <<EOF
# seconds.nanoseconds+offset(sequence) signal0 signal1
1788517357.000000000+1.983720e+00(11) 5.00000000000000000 0.59006638467336403
1788517357.100000000+1.982079e+00(12) 5.00000000000000000 0.95545496189635382
1788517357.200000000+1.985451e+00(13) 0.00000000000000000 0.94989711630388352
1788517357.300000000+1.984770e+00(14) 0.00000000000000000 0.56847957605663302
1788517357.400000000+1.982015e+00(15) 0.00000000000000000 -0.02017184747110692
1788517357.500000000+1.982992e+00(16) 0.00000000000000000 -0.59071578581039907
1788517357.600000000+1.982432e+00(17) 0.00000000000000000 -0.95401575986363329
1788517357.700000000+1.985395e+00(18) 0.00000000000000000 -0.94933467267527527
1788517357.800000000+1.983562e+00(19) 0.00000000000000000 -0.56765977550845070
1788517357.900000000+1.981977e+00(20) 0.00000000000000000 0.01270276364811536
1788517358.000000000+1.983325e+00(21) 5.00000000000000000 0.59002364077587799
1788517358.100000000+1.984304e+00(22) 5.00000000000000000 0.95455933367844681
EOF

villas hook pps_ts -o signal=signal0 -o threshold=2. -o time_source=sample < input.dat > output.dat
villas compare output.dat expect.dat
2 changes: 1 addition & 1 deletion tests/integration/node-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,4 @@ EOF

villas node config.json

villas compare output.dat expect.dat
villas compare -T output.dat expect.dat
2 changes: 1 addition & 1 deletion tests/integration/node-mapping.sh
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ EOF

villas node -d debug config.json

villas compare output.dat expect.dat
villas compare -T output.dat expect.dat
2 changes: 1 addition & 1 deletion tests/integration/node-multiplexing.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down