Linux GNU 11.4.0 Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 9.4% 15 / 0 / 159
Functions: 50.0% 4 / 0 / 8
Branches: 4.8% 5 / 0 / 105

OMCompiler/SimulationRuntime/c/simulation/solver/synchronous.c
Line Branch Exec Source
1 /*
2 * This file belongs to the OpenModelica Run-Time System
3 *
4 * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC), c/o Linköpings
5 * universitet, Department of Computer and Information Science, SE-58183 Linköping, Sweden. All rights
6 * reserved.
7 *
8 * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF THE BSD NEW LICENSE OR THE
9 * AGPL VERSION 3 LICENSE OR THE OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. ANY
10 * USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES RECIPIENT'S
11 * ACCEPTANCE OF THE BSD NEW LICENSE OR THE OSMC PUBLIC LICENSE OR THE AGPL
12 * VERSION 3, ACCORDING TO RECIPIENTS CHOICE.
13 *
14 * The OpenModelica software and the OSMC (Open Source Modelica Consortium) Public License
15 * (OSMC-PL) are obtained from OSMC, either from the above address, from the URLs:
16 * http://www.openmodelica.org or https://github.com/OpenModelica/ or
17 * http://www.ida.liu.se/projects/OpenModelica, and in the OpenModelica distribution. GNU
18 * AGPL version 3 is obtained from: https://www.gnu.org/licenses/licenses.html#GPL. The BSD NEW
19 * License is obtained from: http://www.opensource.org/licenses/BSD-3-Clause.
20 *
21 * This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY
23 * SET FORTH IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF
24 * OSMC-PL.
25 *
26 */
27
28 #include "synchronous.h"
29 #include "epsilon.h"
30 #include "../results/simulation_result.h"
31
32 #ifdef __cplusplus
33 extern "C" {
34 #endif
35
36 /* Function prototypes */
37 void printClocks(BASECLOCK_DATA* baseClocks, int nBaseCllocks);
38 void printSyncTimer(void* data, int stream, void* elemPointer);
39
40 /**
41 * @brief Initialize memory for synchronous functionalities.
42 *
43 * @param data Pointer to data.
44 * @param threadData Pointer to thread data.
45 * @param startTime Start time of simulation.
46 */
47 1 void initSynchronous(DATA* data, threadData_t *threadData, modelica_real startTime)
48 {
49 int i,j;
50 BASECLOCK_DATA* baseClock;
51
52 /* Initialize clocks */
53 1 data->callback->function_initSynchronous(data, threadData);
54
55 /* Error check */
56
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 for(i=0; i<data->modelData->nBaseClocks; i++) {
57 ✗ for(j=0; j<data->simulationInfo->baseClocks[i].nSubClocks; j++) {
58 ✗ assertStreamPrint(threadData, data->simulationInfo->baseClocks[i].subClocks != NULL, "Initialization of synchronous systems failed: baseclocks[%i]->subClocks is NULL!", i);
59 ✗ assertStreamPrint(threadData, data->simulationInfo->baseClocks[i].subClocks[j].solverMethod != NULL, "Continuous clocked systems aren't supported yet.");
60 ✗ assertStreamPrint(threadData, floorRat(data->simulationInfo->baseClocks[i].subClocks[j].shift) >= 0, "Shift of sub-clock is negative. Sub-clocks aren't allowed to fire before base-clock.");
61 }
62 ✗ if (data->simulationInfo->baseClocks[i].isEventClock) { /*event clock*/
63 ✗ for(j=0; j<data->simulationInfo->baseClocks[i].nSubClocks; j++) {
64 ✗ assertStreamPrint(threadData, data->simulationInfo->baseClocks[i].subClocks[j].factor.den == 1, "Factor of sub-clock of event-clock is not an integer, this is not allowed.");
65 }
66 }
67 }
68
69
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 for(i=0; i<data->modelData->nBaseClocks; i++)
70 {
71 ✗ baseClock = &data->simulationInfo->baseClocks[i];
72
73 ✗ data->callback->function_updateSynchronous(data, threadData, i);
74 ✗ if (!baseClock->isEventClock) {
75 // Add base-clock activation time to data->simulationInfo->intvlTimers
76 ✗ SYNC_TIMER timer = (SYNC_TIMER){
77 .base_idx = i,
78 .sub_idx = -1,
79 .type = SYNC_BASE_CLOCK,
80 .activationTime = startTime
81 };
82 ✗ listPushFront(data->simulationInfo->intvlTimers, &timer);
83 }
84 }
85
86 /* Debug print */
87 1 printClocks(data->simulationInfo->baseClocks, data->modelData->nBaseClocks);
88 1 }
89
90 /**
91 * @brief Insert given timer into ordered list of timers.
92 *
93 * Timer with lowest activation time is at the start of the list, last at the end.
94 *
95 * @param list List with timers
96 * @param timer Timer to insert into list.
97 */
98 ✗ static void insertTimer(LIST* list, SYNC_TIMER* timer)
99 {
100 LIST_NODE *it, *prevNode = NULL;
101 ✗ for(it = listFirstNode(list); it; it = listNextNode(it))
102 {
103 ✗ SYNC_TIMER *tmpTimer = listNodeData(it);
104 ✗ if(tmpTimer->activationTime > timer->activationTime)
105 break;
106 prevNode = it;
107 }
108 ✗ if (prevNode) listInsert(list, prevNode, timer);
109 ✗ else listPushFront(list, timer);
110 ✗ }
111
112
113 /**
114 * @brief Check when next clock needs to fire.
115 *
116 * If next activation time is smaller then time on next step reduce step size
117 * to hit activation time of clock exactly.
118 *
119 * @param data Pointer to data.
120 * @param solverInfo Solver info, containing next activation time of clocks.
121 */
122 1 void checkForSynchronous(DATA *data, SOLVER_INFO* solverInfo)
123 {
124
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
1 if (data->simulationInfo->intvlTimers != NULL && listLen(data->simulationInfo->intvlTimers) > 0)
125 {
126 ✗ SYNC_TIMER* nextTimer = (SYNC_TIMER*)listNodeData(listFirstNode(data->simulationInfo->intvlTimers));
127 ✗ double nextTimeStep = solverInfo->currentTime + solverInfo->currentStepSize;
128
129 ✗ if ((nextTimer->activationTime <= nextTimeStep + SYNC_EPS) && (nextTimer->activationTime >= solverInfo->currentTime))
130 {
131 ✗ solverInfo->currentStepSize = nextTimer->activationTime - solverInfo->currentTime;
132 }
133 }
134 1 }
135
136
137 /**
138 * @brief Update base clock and get activation times for all sub-clocks.
139 *
140 * @param data Pointer to data.
141 * @param threadData Pointer to thread data.
142 * @param idx Index of timer to handle.
143 * @param curTime Current activation time.
144 */
145 ✗ modelica_boolean handleBaseClock(DATA* data, threadData_t *threadData, long idx, double curTime)
146 {
147 modelica_boolean frstSubClockIsBaseClock = 0 /* false */;
148
149 /* Special case for event-clocks activated at initialization */
150 ✗ if (data->simulationInfo->initial) {
151 ✗ SYNC_TIMER nextTimer = (SYNC_TIMER){
152 .base_idx = idx,
153 .sub_idx = -1,
154 .type = SYNC_BASE_CLOCK,
155 ✗ .activationTime = data->simulationInfo->startTime};
156 ✗ insertTimer(data->simulationInfo->intvlTimers, &nextTimer);
157 return frstSubClockIsBaseClock;
158 }
159
160 ✗ BASECLOCK_DATA* baseClock = &(data->simulationInfo->baseClocks[idx]);
161 SUBCLOCK_DATA* subClock;
162 SYNC_TIMER nextTimer, firstSubTimer;
163 SYNC_TIMER* nextSubTimer;
164 double nextBaseTime, nextSubTime, absoluteSubTime, activationTime;
165 RATIONAL subTimer;
166 int i;
167
168 /* Update base clock */
169 ✗ baseClock->stats.count++;
170 // Event clocks can't use baseClock->interval
171 ✗ if (baseClock->isEventClock) {
172 ✗ if (baseClock->stats.count > 1) {
173 ✗ baseClock->stats.previousInterval = curTime - baseClock->stats.lastActivationTime;
174 }
175 } else {
176 ✗ baseClock->stats.previousInterval = baseClock->interval;
177 }
178 ✗ baseClock->stats.lastActivationTime = curTime;
179
180 ✗ subClock = &baseClock->subClocks[0];
181 ✗ if (subClock->shift.num == 0 && subClock->factor.num == 1 && subClock->factor.den == 1) {
182 frstSubClockIsBaseClock = 1 /* true */;
183 #if !defined(OMC_MINIMAL_RUNTIME)
184 // Save result before clock tick, then evaluate equations
185 ✗ sim_result.emit(&sim_result, data, threadData);
186 #endif /* #if !defined(OMC_MINIMAL_RUNTIME) */
187 ✗ subClock->stats.count++;
188 ✗ subClock->stats.previousInterval = baseClock->stats.previousInterval;
189 ✗ subClock->stats.lastActivationTime = baseClock->stats.lastActivationTime;
190 ✗ data->callback->function_equationsSynchronous(data, threadData, idx, 0);
191 }
192 ✗ if (!baseClock->isEventClock) {
193 ✗ data->callback->function_updateSynchronous(data, threadData, idx); /* Update interval */
194 ✗ nextBaseTime = curTime + baseClock->interval;
195
196 // Next base clock activation
197 ✗ nextTimer = (SYNC_TIMER){
198 .base_idx = idx,
199 .sub_idx = -1,
200 .type = SYNC_BASE_CLOCK,
201 .activationTime = nextBaseTime};
202 ✗ insertTimer(data->simulationInfo->intvlTimers, &nextTimer);
203 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Activated base-clock %li at time %f", idx, curTime);
204 } else {
205 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Activated event-clock %li at time %f", idx, curTime);
206 }
207
208 // Add sub-clocks to timer that will fire during this base-clock interval.
209 // s = subClock->shift + subClock->stats.count * subClock->factor - (baseClock->stats.count-1);
210 // timer = base.prevTick + s * baseClock->interval
211
212 // Skip first subClock if is equivalent to the baseClock
213 ✗ i = frstSubClockIsBaseClock ? 1 : 0;
214 ✗ for (/* init above */; i < baseClock->nSubClocks; ++i) {
215 ✗ subClock = &baseClock->subClocks[i];
216 ✗ subTimer = addRat(subRat(subClock->shift, int2Rat(baseClock->stats.count-1)), mulRat(int2Rat(subClock->stats.count), subClock->factor));
217 ✗ while (floorRat(subTimer) == 0) {
218 ✗ activationTime = curTime + rat2Real(subTimer)*baseClock->interval;
219 ✗ nextTimer = (SYNC_TIMER){
220 .base_idx = idx,
221 .sub_idx = i,
222 .type = SYNC_SUB_CLOCK,
223 .activationTime = activationTime};
224 ✗ insertTimer(data->simulationInfo->intvlTimers, &nextTimer);
225 ✗ subTimer = addRat(subTimer, subClock->factor);
226 }
227 }
228
229 return frstSubClockIsBaseClock;
230 }
231
232 #if !defined(OMC_MINIMAL_RUNTIME)
233 /**
234 * @brief Handle timer clocks.
235 *
236 * Loop over all timers and check if a timer fired.
237 * If there are no timers return NO_TIMER_FIRED.
238 *
239 * @param data Pointer to data.
240 * @param threadData Pointer to thread data.
241 * @param solverInfo Pointer to solver info.
242 * @return fire_timer_t Return NO_TIMER_FIRED, if there are no fired timers;
243 * TIMER_FIRED, if there is a fired timer;
244 * TIMER_FIRED_EVENT, if there is a fired timer which triggers an event.
245 */
246 2 fire_timer_t handleTimers(DATA* data, threadData_t *threadData, SOLVER_INFO* solverInfo)
247 {
248 int base_idx, sub_idx;
249 double activationTime;
250 modelica_boolean frstSubClockIsBaseClock = 0 /* false */;
251 SYNC_TIMER_TYPE type;
252 SYNC_TIMER* nextTimer;
253 fire_timer_t ret = NO_TIMER_FIRED;
254 SUBCLOCK_DATA* subClock;
255
256
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
2 if (data->simulationInfo->intvlTimers == NULL || listLen(data->simulationInfo->intvlTimers) <= 0) {
257 2 return ret;
258 }
259
260 /* Fire all timers at current time step */
261 ✗ nextTimer = (SYNC_TIMER*)listNodeData(listFirstNode(data->simulationInfo->intvlTimers));
262 ✗ while(nextTimer->activationTime <= solverInfo->currentTime + SYNC_EPS)
263 {
264 ✗ base_idx = nextTimer->base_idx;
265 ✗ sub_idx = nextTimer->sub_idx;
266 ✗ type = nextTimer->type;
267 activationTime = nextTimer->activationTime;
268 ✗ listRemoveFront(data->simulationInfo->intvlTimers);
269 ✗ switch(type)
270 {
271 ✗ case SYNC_BASE_CLOCK:
272 ✗ frstSubClockIsBaseClock = handleBaseClock(data, threadData, base_idx, activationTime);
273 ✗ if (frstSubClockIsBaseClock && data->simulationInfo->baseClocks[base_idx].subClocks[0].holdEvents) {
274 ret = TIMER_FIRED_EVENT;
275 } else {
276 ret = TIMER_FIRED;
277 }
278 break;
279 ✗ case SYNC_SUB_CLOCK:
280 // Save result before clock tick, then evaluate equations
281 ✗ sim_result.emit(&sim_result, data, threadData);
282 ✗ subClock = &data->simulationInfo->baseClocks[base_idx].subClocks[sub_idx];
283 ✗ subClock->stats.count++;
284 ✗ subClock->stats.previousInterval = solverInfo->currentTime - subClock->stats.lastActivationTime;
285 ✗ subClock->stats.lastActivationTime = solverInfo->currentTime;
286 ✗ data->callback->function_equationsSynchronous(data, threadData, base_idx, sub_idx); /* TODO: Fix indices. Now indices for base and sub-clocks */
287 ✗ if (subClock->holdEvents) {
288 ret = TIMER_FIRED_EVENT;
289 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Activated sub-clock (%i,%i) which triggered event at time %f",
290 base_idx, sub_idx, solverInfo->currentTime);
291 } else {
292 ret = TIMER_FIRED;
293 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Activated sub-clock (%i,%i) at time %f",
294 base_idx, sub_idx, solverInfo->currentTime);
295 }
296 break;
297 }
298 ✗ if (listLen(data->simulationInfo->intvlTimers) == 0){
299 break;
300 }
301 ✗ nextTimer = (SYNC_TIMER*)listNodeData(listFirstNode(data->simulationInfo->intvlTimers));
302 }
303 return ret;
304 }
305 #endif /* #if !defined(OMC_MINIMAL_RUNTIME) */
306
307
308 /**
309 * @brief Handle timer clocks and return next time a timer will fire
310 *
311 * Update timers and output when the next timer will fire.
312 * Used for Synchronous features in FMUs.
313 *
314 * @param data data
315 * @param threadData thread data, for errro handling
316 * @param currentTime Current solver timer.
317 * @param nextTimerDefined FALSE if no next timer is defined.
318 * TRUE if a next timer is defined. Then the time is outputted in nextTimerActivationTime.
319 * @param nextTimerActivationTime If nextTimerDefined is true it will contain the next time a timer will fire.
320 * @return int Return 0, if there is no fired timers;
321 * 1, if there is a fired timer;
322 * 2, if there is a fired timer which trigger event;
323 */
324 ✗ int handleTimersFMI(DATA* data, threadData_t *threadData, double currentTime, modelica_boolean *nextTimerDefined, double *nextTimerActivationTime)
325 {
326 int base_idx, sub_idx;
327 double activationTime;
328 modelica_boolean frstSubClockIsBaseClock = 0 /* false */;
329 SYNC_TIMER_TYPE type;
330 SYNC_TIMER* nextTimer;
331 fire_timer_t ret = NO_TIMER_FIRED;
332 SUBCLOCK_DATA* subClock;
333
334 ✗ *nextTimerDefined = FALSE;
335
336 ✗ if (data->simulationInfo->intvlTimers == NULL || listLen(data->simulationInfo->intvlTimers) <= 0) {
337 ✗ return (int) ret;
338 }
339
340 /* Fire all timers at current time step */
341 ✗ nextTimer = (SYNC_TIMER*)listNodeData(listFirstNode(data->simulationInfo->intvlTimers));
342 ✗ while(nextTimer->activationTime <= currentTime + SYNC_EPS)
343 {
344 ✗ base_idx = nextTimer->base_idx;
345 ✗ sub_idx = nextTimer->sub_idx;
346 ✗ type = nextTimer->type;
347 activationTime = nextTimer->activationTime;
348 ✗ listRemoveFront(data->simulationInfo->intvlTimers);
349 ✗ switch(type)
350 {
351 ✗ case SYNC_BASE_CLOCK:
352 ✗ frstSubClockIsBaseClock = handleBaseClock(data, threadData, base_idx, activationTime);
353 ✗ if (frstSubClockIsBaseClock && data->simulationInfo->baseClocks[base_idx].subClocks[0].holdEvents) {
354 ret = TIMER_FIRED_EVENT;
355 } else {
356 ret = TIMER_FIRED;
357 }
358 break;
359 ✗ case SYNC_SUB_CLOCK:
360 ✗ subClock = &data->simulationInfo->baseClocks[base_idx].subClocks[sub_idx];
361 ✗ subClock->stats.count++;
362 ✗ subClock->stats.previousInterval = currentTime - subClock->stats.lastActivationTime;
363 ✗ subClock->stats.lastActivationTime = currentTime;
364 ✗ data->callback->function_equationsSynchronous(data, threadData, base_idx, sub_idx); /* TODO: Fix indices. Now indices for base and sub-clocks */
365 ✗ if (subClock->holdEvents) {
366 ret = TIMER_FIRED_EVENT;
367 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Activated sub-clock (%i,%i) which triggered event at time %f",
368 base_idx, sub_idx, currentTime);
369 } else {
370 ret = TIMER_FIRED;
371 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Activated sub-clock (%i,%i) at time %f",
372 base_idx, sub_idx, currentTime);
373 }
374 break;
375 }
376 ✗ if (listLen(data->simulationInfo->intvlTimers) == 0){
377 break;
378 }
379 ✗ nextTimer = (SYNC_TIMER*)listNodeData(listFirstNode(data->simulationInfo->intvlTimers));
380 }
381 /* Next time a timer will activate, whether or not one fired now. */
382 ✗ if (listLen(data->simulationInfo->intvlTimers) > 0) {
383 ✗ nextTimer = (SYNC_TIMER*)listNodeData(listFirstNode(data->simulationInfo->intvlTimers));
384 ✗ *nextTimerActivationTime = nextTimer->activationTime;
385 ✗ *nextTimerDefined = TRUE;
386 }
387 ✗ return (int) ret;
388 }
389
390 /**
391 * @brief Print all base-clocks and sub-clocks.
392 *
393 * @param baseClocks Pointer to array of size nClocks with base clock data.
394 * @param nBaseClocks Number of base clocks.
395 */
396 1 void printClocks(BASECLOCK_DATA* baseClocks, int nBaseClocks)
397 {
398 int i,j;
399 BASECLOCK_DATA* baseClock;
400 SUBCLOCK_DATA* subClock;
401
402
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if(omc_useStream[OMC_LOG_SYNCHRONOUS]) {
403 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 1, "Initialized synchronous timers.");
404 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Number of base clocks: %i", nBaseClocks);
405 ✗ for(i=0; i<nBaseClocks; i++) {
406 ✗ baseClock = &baseClocks[i];
407 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 1, "Base clock %i", i+1);
408 ✗ if (baseClock->isEventClock) {
409 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "is event clock");
410 ✗ } else if (baseClock->intervalCounter==-1) {
411 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "interval: %e", baseClock->interval);
412 } else {
413 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "intervalCounter/resolution = : %i/%i", baseClock->intervalCounter, baseClock->resolution);
414 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "interval: %e", baseClock->interval);
415 }
416 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "Number of sub-clocks: %i", baseClock->nSubClocks);
417 ✗ for(j=0; j<baseClock->nSubClocks; j++) {
418 ✗ subClock = &baseClock->subClocks[j];
419 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 1, "Sub-clock %i of base clock %i", j+1, i+1);
420 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "shift: "RAT_FMT"/"RAT_FMT, subClock->shift.num, subClock->shift.den);
421 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "factor: "RAT_FMT"/"RAT_FMT, subClock->factor.num, subClock->factor.den);
422 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "solverMethod: %s", strlen(subClock->solverMethod)>0?subClock->solverMethod:"none");
423 ✗ infoStreamPrint(OMC_LOG_SYNCHRONOUS, 0, "holdEvents: %s", subClock->holdEvents?"true":"false");
424 ✗ messageClose(OMC_LOG_SYNCHRONOUS);
425 }
426 ✗ messageClose(OMC_LOG_SYNCHRONOUS);
427 }
428 ✗ messageClose(OMC_LOG_SYNCHRONOUS);
429 }
430 1 }
431
432 /**
433 * @brief Print synchronous timer.
434 *
435 * Prints tuple (base_idx, sub_idx, type, activationTime).
436 *
437 * @param data Void pointer to sync timer element.
438 * Will be casted to SYNC_TIMER*.
439 * @param stream Stream of OMC_LOG_STREAM type.
440 * @param elemPointer Address of element storing this data.
441 */
442 ✗ void printSyncTimer(void* data, int stream, void* elemPointer)
443 {
444 SYNC_TIMER* syncTimerElem = (SYNC_TIMER*) data;
445 ✗ switch (syncTimerElem->type)
446 {
447 ✗ case SYNC_BASE_CLOCK:
448 ✗ infoStreamPrint(stream, 0, "%p: (base_idx :%i, type: %s, activationTime: %e)", elemPointer, syncTimerElem->base_idx, "base-clock", syncTimerElem->activationTime);
449 ✗ break;
450 ✗ case SYNC_SUB_CLOCK:
451 ✗ infoStreamPrint(stream, 0, "%p: (base_idx: %i, sub_idx: %i, type: %s, activationTime: %e)", elemPointer, syncTimerElem->base_idx, syncTimerElem->sub_idx, "sub-clock", syncTimerElem->activationTime);
452 ✗ break;
453
454 ✗ default:
455 ✗ infoStreamPrint(stream, 0, "%p: ERROR: Unknown type", elemPointer);
456 ✗ break;
457 }
458 ✗ }
459
460 #ifdef __cplusplus
461 }
462 #endif
463