Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
8fd68da
fix(sample): Change ts compare to absolute compare
Sep 4, 2026
1f577b8
test(pps_ts): Added test for hook pps_ts and fix time source for hook
Sep 4, 2026
d17ea85
test(nodes): Change integration test to ignore timestamps in compare
Sep 4, 2026
469f54c
fix(pps_ts): Cleanup code and make sure variables are initialized
Sep 4, 2026
9427173
fix(api): Restart temp cofnigfile needs .json at the end for corret c…
Sep 13, 2026
1414e1b
fix(websocket): Use previous connection state in CLOSED callback
steffen-vogel-opalrt Aug 7, 2026
0f0949f
fix(file): Disambiguate duplicated in.epoch key in node details string
steffen-vogel-opalrt Aug 7, 2026
b8f686d
fix(dumper): Bound copy of socket path into sun_path
steffen-vogel-opalrt Aug 7, 2026
89f5d48
fix(can): Bound copy of interface name into ifr_name
steffen-vogel-opalrt Aug 7, 2026
db33e89
fix(path): Add missing commas in json_pack format string
steffen-vogel-opalrt Aug 7, 2026
22e2d10
style(utils): Fix typo in tokenize() variable name
steffen-vogel-opalrt Aug 7, 2026
f4db274
fix(utils): Handle vasprintf/realloc failure in vstrcatf
steffen-vogel-opalrt Aug 7, 2026
2198f98
fix(api): Correct capabilities request file and description spelling
steffen-vogel-opalrt Aug 7, 2026
acff091
fix(python): Avoid mutable default, init child, use self.config in Node
steffen-vogel-opalrt Aug 7, 2026
4d6b0d4
fix(python): Only join started threads in communicate()
steffen-vogel-opalrt Aug 7, 2026
6e0f416
fix(python): Assign stripped string in VillasHuman.loads()
steffen-vogel-opalrt Aug 7, 2026
de4f72f
fix(tools): Correct several issues in tc-netem.sh
steffen-vogel-opalrt Aug 7, 2026
0e56af8
fix(tools): Remove stray exits and fix debug output in tc-netem2.sh
steffen-vogel-opalrt Aug 7, 2026
d6ef2b7
fix(tools): Include .h headers in format-all.sh glob
steffen-vogel-opalrt Aug 7, 2026
0a6fde8
fix(tools): Return explicit success and fix exit code in pre-commit hook
steffen-vogel-opalrt Aug 7, 2026
ca26bc9
fix(tools): Quote $@ in villas-helper.sh wrapper
steffen-vogel-opalrt Aug 7, 2026
e05fccb
fix(tools): Port villas-api.sh to the VILLASnode API v2
steffen-vogel-opalrt Aug 7, 2026
baccd34
fix(shmem-client): Show required RNAME argument in usage line
steffen-vogel-opalrt Aug 7, 2026
2aea172
fix(packaging): Report correct parameter and fix typos in deps.sh
steffen-vogel-opalrt Aug 7, 2026
9d30eb4
fix(lua): Correct SampleFlags bit-shift comments in test hook
steffen-vogel-opalrt Aug 7, 2026
bd87717
fix(tools): Correct comment typos, author email and whitelist in hwde…
steffen-vogel-opalrt Aug 7, 2026
25646a4
fix: Correct various spelling mistakes in comments and messages
steffen-vogel-opalrt Aug 7, 2026
4da29ff
fix: Correct spelling mistakes in infiniband node and ip_device
steffen-vogel-opalrt Aug 7, 2026
2402e38
fix: Correct 'snd' -> 'and' in file header comments
steffen-vogel-opalrt Aug 7, 2026
28003dc
fix(tools): Correct spelling in integration-tests.sh output
steffen-vogel-opalrt Aug 7, 2026
e32b0a3
fix: Remove duplicated 'the' in config example and OpenAPI descriptions
steffen-vogel-opalrt Aug 7, 2026
b34f3b5
fix(villas-pipe): Add missing closing quote in RuntimeError format st…
steffen-vogel-opalrt Aug 7, 2026
7668df3
fix: Correct spelling in plugin descriptions and CLI help
steffen-vogel-opalrt Aug 7, 2026
1d9d9eb
fix(test_rtt): Correct inverted strcmp logic and max-mode in parseMode
steffen-vogel-opalrt Aug 7, 2026
8d2d8cb
fix(hooks): Read correctly-spelled start/end_frequency in pmu_dft
steffen-vogel-opalrt Aug 7, 2026
4630c7b
fix(villas-signal): Enable and document -p, -w, -L, -H options
steffen-vogel-opalrt Aug 7, 2026
6a6bd66
fix(villas-test-config): Parse advertised -d debug-level option
steffen-vogel-opalrt Aug 7, 2026
564cd3e
fix(openapi): Add enabled and correct port default in http schema
steffen-vogel-opalrt Aug 8, 2026
6186e23
fix(openapi): Align zeromq and websocket schemas with parsed options
steffen-vogel-opalrt Aug 8, 2026
ed0330e
fix(examples): Correct spelling in redis and ngsi example comments
steffen-vogel-opalrt Aug 8, 2026
63f6c15
fix(examples): Use correct 'samples' option in skip_first example com…
steffen-vogel-opalrt Aug 8, 2026
a9278ca
fix: Correct spelling typos in comments and log messages
steffen-vogel-opalrt Aug 8, 2026
eb0e754
fix(api): Remove duplicated 'with' in UUID error messages
steffen-vogel-opalrt Aug 8, 2026
5bc3a64
fix(hooks): Use correct ConfigError ID in moving-average hook
steffen-vogel-opalrt Aug 8, 2026
2d3f1a6
fix(hooks): Initialize optional json_unpack outputs in digest hook
steffen-vogel-opalrt Aug 8, 2026
e65ecd9
fix(hooks): Initialize optional 'mode' output in gate hook
steffen-vogel-opalrt Aug 8, 2026
a058497
fix(pre-commit): Update black-pre-commit-mirror to version 24.10.0
stv0g Aug 9, 2026
48aa7fa
fix(style): Fix code formatting with clang-format
stv0g Aug 9, 2026
2ab1d8b
fix(style): Merge strings into same line
stv0g Sep 7, 2026
e4f2da2
chore(master): Release 1.2.1
github-actions[bot] Sep 7, 2026
1f4ddbe
chore(master): Release 1.2.2
github-actions[bot] Sep 7, 2026
d29d252
fix(openapi): Use OpenAPI 3.1.1 with JSON Schema Draft 07 dialect
pjungkamp May 18, 2026
427014d
fix(redocly): Fix linter configuration
pjungkamp May 18, 2026
2e7a312
feat(editorconfig): Add yaml configuration
pjungkamp May 18, 2026
6692d26
feat(openapi): Make redocly configuration more strict
pjungkamp May 18, 2026
cd35a00
fix(hook-pmu_dft): Fix configuration typos
pjungkamp Aug 10, 2026
14415d5
refactor(openapi): Rewrite all schema files
pjungkamp Jun 2, 2026
0a17dc0
feat(node): Introduce json-schema-validator for bundled schemas
pjungkamp Jul 15, 2026
74f763e
feat(clangd): Add compilation database path to build dir
pjungkamp Aug 7, 2026
e5c1fbc
feat(config): Validate configuration against JSON schema
pjungkamp Aug 7, 2026
97607e1
feat(gdb): Add gdbinit for nlohmann::json pretty printing
pjungkamp Aug 9, 2026
9e5490b
fix(tool): Catch std::exception instead of std::runtime_error
pjungkamp Aug 9, 2026
9cb4a1a
fix(node): Remove enabled and initial_sequenceno from configuration
pjungkamp Aug 9, 2026
ccb71c4
fix(tests): Fix invalid test and example configurations
pjungkamp Aug 9, 2026
d62edaf
fix(tests): Reap left-over children from integration tests
pjungkamp Aug 9, 2026
1849e7a
feat(config): Allow JSON files with comments
pjungkamp Aug 9, 2026
c7f6a1f
chore(docker): Bump fedore-minimal container to Fedora 43
pjungkamp Aug 9, 2026
97bf608
fix(docker): Add workaround for local/lib libraries
pjungkamp Aug 9, 2026
63f48f3
feat(nix): Use gcc14Stdenv for devShell.default
pjungkamp Aug 10, 2026
f5e5828
chore(hooks): Remove enabled setting for hooks
pjungkamp Aug 10, 2026
a4e88bf
fix(super_node): Remove unused broken code
pjungkamp Aug 10, 2026
4e0cb8c
fix(format-raw): Remove unnecessary exception
pjungkamp Aug 10, 2026
3777695
fix(hook-limit_value): Fix memory corruption
pjungkamp Aug 10, 2026
c726cdd
fix(hook-pmu): Allow explicit none window configuration
pjungkamp Aug 10, 2026
9bff529
fix(config): Always allow integers where floats are allowed
pjungkamp Aug 10, 2026
45f065d
fix(node-loopback): Fix use of undefined macro
pjungkamp Aug 10, 2026
db91736
fix(typo): Fix typo in "frequency"
stv0g Sep 7, 2026
028ecf5
fix(openapi): Fix OpenAPI generation
stv0g Sep 7, 2026
4b0e7b8
feat: Add grid2op-timeseries-converter script for chronics file gener…
steffen-vogel-opalrt Aug 2, 2026
dc4f622
chore(master): Release 1.3.0
github-actions[bot] Sep 7, 2026
e2f211d
chore(master): Release 1.3.0
github-actions[bot] Sep 7, 2026
b6b1655
fix(ci): Add version to Nix builds
stv0g Sep 9, 2026
f351ffb
chore(master): Release 1.3.1
github-actions[bot] Sep 9, 2026
3833f33
docs(schema): Update pps_ts schema to fit the hook
Sep 13, 2026
956899c
Merge branch 'master' into hook-pps-ts-tests
windrad6 Sep 13, 2026
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
21 changes: 0 additions & 21 deletions doc/openapi/components/schemas/hook-pps_ts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,11 @@ properties:
type: string
const: pps_ts

mode:
type: string
enum:
- simple
- horizon
default: simple
description: "The synchronization mode. The `horizon` mode is currently no recommended to use as it is not fully tested."

threshold:
type: number
default: 1.5
description: "The signal level threshold of the PPS signal which is used to detect an edge."

expected_smp_rate:
type: number
default: 1.0
description: "The expected sampling rate of the input signal. Only important for a faster initialization."

horizon_estimation:
type: integer
default: 10

horizon_compensation:
type: integer
default: 10

priority:
default: 99
$ref: ./shared-hook-priority.yaml
Expand Down
6 changes: 4 additions & 2 deletions lib/api/requests/restart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include <cstring>

#include <villas/api/request.hpp>
#include <villas/api/response.hpp>
#include <villas/api/session.hpp>
Expand Down Expand Up @@ -62,8 +64,8 @@ class RestartRequest : public Request {
if (json_is_string(json_config))
configUri = json_string_value(json_config);
else if (json_is_object(json_config)) {
char configUriBuf[] = "villas-node.json.XXXXXX";
int configFd = mkstemp(configUriBuf);
char configUriBuf[] = "villas-node.XXXXXX.json";
int configFd = mkstemps(configUriBuf, strlen(".json"));

FILE *configFile = fdopen(configFd, "w+");

Expand Down
174 changes: 48 additions & 126 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)
: SingleSignalHook(p, n, fl, prio), 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) {}
PpsTsHook(Path *p, Node *n, int fl, int prio, bool en = true)
: SingleSignalHook(p, n, fl, prio), 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
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