Communication Library for Autonomous Systems v1.0
Reliable and secure communication library for autonomous vehicle systems
Loading...
Searching...
No Matches
clock.h
Go to the documentation of this file.
1#ifndef CLOCK_H
2#define CLOCK_H
3
4#include <chrono>
5#include <atomic>
6#include <mutex>
7#include <cstdint>
8#include <ostream>
9#include "api/util/debug.h" // For db<> logging
10#include "api/traits.h" // For traits specialization
11#include "api/framework/leaderKeyStorage.h" // For LeaderKeyStorage
12#include "api/network/ethernet.h" // For Ethernet::Address
13
14// --- Placeholder types (These should be defined in appropriate common headers) ---
15// Represents a timestamp, using microseconds since a steady epoch
16using TimestampType = std::chrono::time_point<std::chrono::steady_clock, std::chrono::microseconds>;
17
18// Represents a duration, e.g., for offsets
19using DurationType = std::chrono::microseconds;
20
21// Represents frequency error, unitless (e.g., parts per million or direct ratio)
23
24// Identifier for a PTP leader (e.g., a vehicle ID or MAC address representation)
25// For simplicity, using uint32_t. Adapt as needed.
27const LeaderIdType INVALID_LEADER_ID = 0; // Example invalid ID
28
29// Structure to pass PTP-relevant data (extracted from message headers) to the Clock's activate method
31 LeaderIdType sender_id; // ID of the message sender (potential PTP master)
32 TimestampType ts_tx_at_sender; // Timestamp from the sender's NIC/PTP header (ts_PN equivalent)
33 TimestampType ts_local_rx; // Local hardware timestamp when this node's NIC received the frame
34};
35
36// Structure to store processed PTP message data internally by the Clock
38 TimestampType ts_tx_at_sender = TimestampType::min(); // ts_PN equivalent
39 TimestampType ts_local_rx = TimestampType::min(); // ts_N_prime (local hardware time of reception)
40 DurationType d_tx_calc = DurationType::zero(); // Calculated total propagation delay
41 TimestampType leader_time_at_local_rx_event = TimestampType::min(); // ts_PN + d_TX_calc
42
44};
45// --- End Placeholder types ---
46
47
56class Clock {
57public:
58 enum class State {
60 AWAITING_SECOND_MSG, // Offset acquired, awaiting second message for drift
61 SYNCHRONIZED // Offset and drift acquired
62 };
63
64 // Deleted copy constructor and assignment operator
65 Clock(const Clock&) = delete;
66 Clock& operator=(const Clock&) = delete;
67
68 static Clock& getInstance();
69 void setSelfId(LeaderIdType id);
70
71 // --- Core Public Interface ---
72
75 bool isFullySynchronized() const;
81 void reset();
82
83private:
84 // Private constructor for singleton
85 Clock() :
86 _currentState(State::UNSYNCHRONIZED),
87 _current_offset(DurationType::zero()),
88 _current_drift_fe(0.0),
89 _leader_time_at_last_sync_event(TimestampType::min()),
90 _local_time_at_last_sync_event(TimestampType::min()),
91 _last_sync_local_time(TimestampType::min()),
92 _current_leader_id(INVALID_LEADER_ID),
93 _leader_has_changed_flag(false),
94 _self_id(INVALID_LEADER_ID) {
95 doClearSyncData();
96 db<Clock>(INF) << "Clock: Initialized in UNSYNCHRONIZED state\n";
97 }
98 ~Clock() = default; // Default destructor
99
100 // --- State Machine Actions (called with _mutex held) ---
101 void doClearSyncData();
102 void doProcessFirstLeaderMsg(const PtpRelevantData& msg_data);
103 void doProcessSecondLeaderMsgAndCalcDrift(const PtpRelevantData& msg_data);
104 void doProcessSubsequentLeaderMsg(const PtpRelevantData& msg_data);
105
106
107 // --- Guard Conditions ---
108 bool isLeaderMessageTimedOut() const;
109 bool isMessageFromCurrentLeader(const PtpRelevantData& msg_data) const;
110 bool isLeaderAssigned() const;
111 bool checkAndHandleLeaderChange(LeaderIdType storage_leader_id);
112
113 // --- Member Variables ---
114 std::atomic<State> _currentState; // Made atomic for better performance
115 mutable std::mutex _mutex;
116
117 // PTP Synchronization Data
118 PtpInternalMessageInfo _msg1_data; // Data from the (N-1)th PTP-relevant message
119 PtpInternalMessageInfo _msg2_data; // Data from the (N)th (latest) PTP-relevant message
120
121 DurationType _current_offset; // O = LocalRxTime - (LeaderTxTimeAtSender + PropagationDelay)
122 FrequencyErrorType _current_drift_fe; // Frequency error (drift rate) relative to leader
123
124 TimestampType _leader_time_at_last_sync_event; // Leader's clock value (ts_tx_at_sender + d_tx_calc) at the moment of the last local RX event used for sync
125 TimestampType _local_time_at_last_sync_event; // Local hardware clock value at that same local RX event
126 TimestampType _last_sync_local_time;
127
128 LeaderIdType _current_leader_id;
129 std::atomic<bool> _leader_has_changed_flag; // Made atomic for better performance
130
131 LeaderIdType _self_id;
132
133 // Configuration Constants
134 // Allow up to 10ms cumulative error:
135 // For standard crystal specification worst-case (~20 ppb): 10ms / 20ppb = 500ms
136 static constexpr DurationType MAX_LEADER_SILENCE_INTERVAL = std::chrono::microseconds(500000);
137};
138
146 static Clock instance;
147 return instance;
148}
149
157 std::lock_guard<std::mutex> lock(_mutex);
158 if (_self_id == INVALID_LEADER_ID && id != INVALID_LEADER_ID) {
159 _self_id = id;
160 db<Clock>(INF) << "Clock: Self ID set to " << _self_id << "\n";
161 } else if (id != INVALID_LEADER_ID && _self_id != id && _self_id != INVALID_LEADER_ID) {
162 // If already set and trying to change to a different valid ID
163 db<Clock>(WRN) << "Clock: Attempt to change self ID from " << _self_id
164 << " to " << id << ". Current self ID maintained.\n";
165 } else if (id == _self_id) {
166 // Setting to the same ID, no action needed but maybe log for debugging
167 db<Clock>(TRC) << "Clock: Self ID re-confirmed to " << _self_id << "\n";
168 }
169}
170
178 std::lock_guard<std::mutex> lock(_mutex);
179
180 // Get current leader from storage without holding its mutex
182 {
186 }
187
188 // NEW: Check if this node IS the leader (according to storage)
189 if (_self_id != INVALID_LEADER_ID && _self_id == storage_leader_id) {
190 if (_currentState.load(std::memory_order_acquire) != State::SYNCHRONIZED || _current_leader_id != _self_id) {
191 db<Clock>(INF) << "Clock: This node (" << _self_id << ") is the PTP leader. Forcing SYNCHRONIZED state.\n";
192 _currentState.store(State::SYNCHRONIZED, std::memory_order_release);
193 _current_leader_id = _self_id; // This node is the leader
194 _current_offset = DurationType::zero();
195 _current_drift_fe = 0.0;
196 _local_time_at_last_sync_event = getLocalSteadyHardwareTime();
197 _leader_time_at_last_sync_event = _local_time_at_last_sync_event; // Leader time is its own local time
198 _msg1_data = PtpInternalMessageInfo(); // Clear old sync message data
199 _msg2_data = PtpInternalMessageInfo();
200 _leader_has_changed_flag.store(false, std::memory_order_release);
201 }
202 // As a leader, its clock is authoritative. No further processing of PTP messages for sync.
203 return;
204 }
205
206 // If this node is NOT the leader, proceed with normal PTP logic.
207 // Check and handle leader change (original logic, might set state to UNSYNCHRONIZED)
208 [[maybe_unused]] bool actual_leader_change_occurred = checkAndHandleLeaderChange(storage_leader_id);
209
210 State current_state_local = _currentState.load(std::memory_order_acquire);
212
213 // Main state transitions
214 switch (current_state_local) {
216 if (new_msg_data && isLeaderAssigned() && isMessageFromCurrentLeader(*new_msg_data)) {
218 doProcessFirstLeaderMsg(*new_msg_data);
219 }
220 break;
221
223 if (isLeaderMessageTimedOut()) {
224 db<Clock>(INF) << "[Clock] Leader is timedout!\n";
226 doClearSyncData();
227 } else if (new_msg_data && isMessageFromCurrentLeader(*new_msg_data)) {
229 doProcessSecondLeaderMsgAndCalcDrift(*new_msg_data);
230 }
231 break;
232
234 if (isLeaderMessageTimedOut()) {
235 db<Clock>(INF) << "[Clock] Leader is timedout!\n";
237 doClearSyncData();
238 } else if (new_msg_data && isMessageFromCurrentLeader(*new_msg_data)) {
239 doProcessSubsequentLeaderMsg(*new_msg_data);
240 }
241 break;
242 }
243
244 // Log state transition if it changed
246 db<Clock>(INF) << "Clock: " << current_state_local << " -> " << new_state << "\n";
247 _currentState.store(new_state, std::memory_order_release);
248 }
249}
250
258 std::lock_guard<std::mutex> lock(_mutex);
260 State current_state_local = _currentState.load(std::memory_order_acquire);
261
263 db<Clock>(INF) << "Clock::getSynchronizedTime WARNING: Clock UNSYNCHRONIZED. Returning local hardware time.\n";
264 *is_synchronized = false;
265 return local_hw_now;
266 }
267
269 // Only offset correction: SynchronizedTime = LeaderTimeAtSync + ElapsedLocal
270 // Note: _current_offset = _local_time_at_last_sync_event - _leader_time_at_last_sync_event
271 // So, LeaderTimeAtSync = _local_time_at_last_sync_event - _current_offset
272 // SynchronizedTime = (_local_time_at_last_sync_event - _current_offset) + elapsed_since_last_sync
273 // SynchronizedTime = local_hw_now - _current_offset
274 *is_synchronized = false;
275 return local_hw_now - _current_offset;
276 }
277
278
279 DurationType elapsed_since_last_sync = local_hw_now - _local_time_at_last_sync_event;
280 double leader_increment_double = static_cast<double>(elapsed_since_last_sync.count()) * _current_drift_fe;
281 DurationType leader_increment = std::chrono::microseconds(static_cast<long long>(leader_increment_double));
282
283 // State::SYNCHRONIZED: Apply offset and drift correction
284 // SynchronizedTime = local_hw_now - (_current_offset_at_last_event + drift_correction_for_elapsed_time)
285 // Or, using the state machine's variables:
286 // leader_increment = elapsed_local * (1.0 - current_drift_fe)
287 // sync_time = leader_time_at_last_sync_event + leader_increment
288
289 *is_synchronized = true;
290 return local_hw_now - _current_offset - leader_increment;
291}
292
299inline bool Clock::isFullySynchronized() const {
300 std::lock_guard<std::mutex> lock(_mutex);
301 return _currentState.load(std::memory_order_acquire) == State::SYNCHRONIZED;
302}
303
311 std::lock_guard<std::mutex> lock(_mutex);
312
313 State current_state_local = _currentState.load(std::memory_order_acquire);
314
315 // NEW: If this node is the leader, it's always synchronized.
316 if (_self_id != INVALID_LEADER_ID && _current_leader_id == _self_id) {
318 // This ensures that if somehow the state was different, it's corrected.
319 db<Clock>(INF) << "Clock::getState: This node (" << _self_id << ") is leader. Correcting state to SYNCHRONIZED.\n";
320 _currentState.store(State::SYNCHRONIZED, std::memory_order_release);
321 }
322 return State::SYNCHRONIZED;
323 }
324
325 // Only check timeout for states that expect leader messages (non-leader case)
328
329 if (isLeaderMessageTimedOut()) {
330 db<Clock>(INF) << "Clock: Timeout detected, transitioning to UNSYNCHRONIZED\n";
331 _currentState.store(State::UNSYNCHRONIZED, std::memory_order_release);
332 doClearSyncData();
334 }
335 }
336 return current_state_local;
337}
338
346 std::lock_guard<std::mutex> lock(_mutex);
347 return _current_leader_id;
348}
349
350// Provides the local hardware time (monotonic steady clock)
352 return std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::steady_clock::now());
353}
354
355// Provides the local system time (wall clock, can change)
357 // Since we want to maintain a single timestamp type (steady_clock),
358 // we'll just return the steady clock time
359 // This is a simplification - in a real system you might want to
360 // maintain the relationship between system and steady time
361
362 return getLocalSteadyHardwareTime(); // + Simulation's artificial drift
363}
364
366 return MAX_LEADER_SILENCE_INTERVAL;
367}
368
374inline void Clock::reset() {
375 db<Clock>(INF) << "Clock::reset() called!\n";
376 std::lock_guard<std::mutex> lock(_mutex);
377 _currentState.store(State::UNSYNCHRONIZED, std::memory_order_release);
378 _current_leader_id = INVALID_LEADER_ID;
379 doClearSyncData();
380 _self_id = INVALID_LEADER_ID; // Reset self_id as well for testing
381}
382
383inline void Clock::doClearSyncData() {
384 _msg1_data = PtpInternalMessageInfo();
385 _msg2_data = PtpInternalMessageInfo();
386 _current_offset = DurationType::zero();
387 _current_drift_fe = 0.0;
388 _leader_time_at_last_sync_event = TimestampType::min();
389 _local_time_at_last_sync_event = getLocalSteadyHardwareTime();
390 _leader_has_changed_flag.store(false, std::memory_order_release);
391 db<Clock>(INF) << "Clock: Sync data cleared\n";
392}
393
394inline void Clock::doProcessFirstLeaderMsg(const PtpRelevantData& msg_data) {
396 _last_sync_local_time = now;
397 _msg1_data.ts_tx_at_sender = msg_data.ts_tx_at_sender;
398 _msg1_data.ts_local_rx = msg_data.ts_local_rx;
399
400 _msg1_data.d_tx_calc = std::chrono::microseconds(2);
401
402 _msg1_data.leader_time_at_local_rx_event = _msg1_data.ts_tx_at_sender + _msg1_data.d_tx_calc;
403 _current_offset = _msg1_data.ts_local_rx - _msg1_data.leader_time_at_local_rx_event;
404
405 _leader_time_at_last_sync_event = _msg1_data.leader_time_at_local_rx_event;
406 _local_time_at_last_sync_event = _msg1_data.ts_local_rx;
407 _current_drift_fe = 0.0;
408
409 db<Clock>(INF) << "Clock: Processed first leader message. Offset: "
410 << _current_offset.count() << "us\n";
411}
412
413inline void Clock::doProcessSecondLeaderMsgAndCalcDrift(const PtpRelevantData& msg_data) {
415 _last_sync_local_time = now;
416 _msg2_data.ts_tx_at_sender = msg_data.ts_tx_at_sender;
417 _msg2_data.ts_local_rx = msg_data.ts_local_rx;
418
419 _msg2_data.d_tx_calc = std::chrono::microseconds(2);
420 _msg2_data.leader_time_at_local_rx_event = _msg2_data.ts_tx_at_sender + _msg2_data.d_tx_calc;
421
424 _current_offset = o2;
425
427 // Use effective leader timestamps at local reception events for delta_T
429
430 if (delta_t_leader_effective.count() > 0) {
431 _current_drift_fe = static_cast<FrequencyErrorType>(delta_o.count()) / static_cast<FrequencyErrorType>(delta_t_leader_effective.count());
432 } else {
433 // std::cerr << "Clock WARNING: delta_T_leader_effective is zero or negative during drift calculation. Drift not updated from " << _current_drift_fe << ".\n";
434 }
435
436 _leader_time_at_last_sync_event = _msg2_data.leader_time_at_local_rx_event;
437 _local_time_at_last_sync_event = _msg2_data.ts_local_rx;
438
439 db<Clock>(INF) << "Clock: Processed second leader message. New Offset: "
440 << _current_offset.count() << "us, Drift FE: " << _current_drift_fe << "\n";
441 db<Clock>(INF) << "[Clock] Leader tx timestamps: " << _msg1_data.ts_tx_at_sender.time_since_epoch().count() <<
442 " " << _msg2_data.ts_tx_at_sender.time_since_epoch().count() <<
443 " Local rx timestamps: " << _msg1_data.ts_local_rx.time_since_epoch().count() <<
444 " " << _msg2_data.ts_local_rx.time_since_epoch().count() << "\n";
445}
446
447inline void Clock::doProcessSubsequentLeaderMsg(const PtpRelevantData& msg_data) {
449 _last_sync_local_time = now;
450 // Action for: SYNCHRONIZED --> SYNCHRONIZED
451 _msg1_data = _msg2_data;
452 doProcessSecondLeaderMsgAndCalcDrift(msg_data);
453
454 db<Clock>(INF) << "Clock: Processed subsequent leader message. Updated Offset: "
455 << _current_offset.count() << "us, Updated Drift FE: " << _current_drift_fe << "\n";
456}
457
458inline bool Clock::isLeaderMessageTimedOut() const {
459 // NEW: If this node is the current leader, it doesn't time out on itself.
460 // Note: _current_leader_id and _self_id are read. Callers must hold _mutex.
461 if (_self_id != INVALID_LEADER_ID && _current_leader_id == _self_id) {
462 return false;
463 }
464
465 if (_local_time_at_last_sync_event == TimestampType::min() || !isLeaderAssigned()) {
466 // No sync event yet or no leader, so not "timed out" in the sense of expecting a message
467 return false;
468 }
470 return (local_hw_now - _last_sync_local_time) > MAX_LEADER_SILENCE_INTERVAL;
471}
472
473inline bool Clock::isMessageFromCurrentLeader(const PtpRelevantData& msg_data) const {
474 return msg_data.sender_id == _current_leader_id;
475}
476
477inline bool Clock::isLeaderAssigned() const {
478 return _current_leader_id != INVALID_LEADER_ID;
479}
480
493inline bool Clock::checkAndHandleLeaderChange(LeaderIdType storage_leader_id) {
494 if (_current_leader_id != storage_leader_id) {
495 db<Clock>(INF) << "Clock: Leader changed from " << _current_leader_id
496 << " to " << storage_leader_id << " during activation\n";
497 _current_leader_id = storage_leader_id;
498 _currentState.store(State::UNSYNCHRONIZED, std::memory_order_release);
499 doClearSyncData();
500 return true; // Leader changed
501 }
502 return false; // No change
503}
504
505// Stream operator for Clock::State
506inline std::ostream& operator<<(std::ostream& os, Clock::State state) {
507 switch (state) {
509 return os << "UNSYNCHRONIZED";
511 return os << "AWAITING_SECOND_MSG";
513 return os << "SYNCHRONIZED";
514 default:
515 return os << "UNKNOWN_STATE";
516 }
517}
518
519#endif // CLOCK_H
Thread-safe singleton for PTP clock synchronization.
Definition clock.h:56
State getState()
Get current synchronization state.
Definition clock.h:310
void reset()
Reset the Clock singleton to its initial state (for testing only)
Definition clock.h:374
Clock & operator=(const Clock &)=delete
void setSelfId(LeaderIdType id)
Set the self ID for this clock instance (node's own PTP-relevant ID)
Definition clock.h:156
DurationType getMaxLeaderSilenceInterval() const
Definition clock.h:365
void activate(const PtpRelevantData *new_msg_data)
Activate the state machine with new PTP data.
Definition clock.h:177
State
Definition clock.h:58
TimestampType getLocalSteadyHardwareTime() const
Definition clock.h:351
LeaderIdType getCurrentLeader() const
Get the current PTP leader ID.
Definition clock.h:345
TimestampType getSynchronizedTime(bool *is_synchronized)
Get the current synchronized time.
Definition clock.h:257
TimestampType getLocalSystemTime()
Definition clock.h:356
Clock(const Clock &)=delete
static Clock & getInstance()
Get the singleton instance.
Definition clock.h:145
bool isFullySynchronized() const
Check if clock is fully synchronized.
Definition clock.h:299
static LeaderKeyStorage & getInstance()
Get the singleton instance.
Definition leaderKeyStorage.h:54
double FrequencyErrorType
Definition clock.h:22
uint32_t LeaderIdType
Definition clock.h:26
std::chrono::time_point< std::chrono::steady_clock, std::chrono::microseconds > TimestampType
Definition clock.h:16
std::ostream & operator<<(std::ostream &os, Clock::State state)
Definition clock.h:506
const LeaderIdType INVALID_LEADER_ID
Definition clock.h:27
std::chrono::microseconds DurationType
Definition clock.h:19
@ INF
Definition debug.h:208
Select_Debug<(Traits< T >::debugged &&Traits< Debug >::error)> db(Debug_Error l)
Definition debug.h:166
@ TRC
Definition debug.h:231
@ WRN
Definition debug.h:185
Definition ethernet.h:16
std::uint8_t bytes[MAC_SIZE]
Definition ethernet.h:17
Definition clock.h:37
TimestampType ts_local_rx
Definition clock.h:39
PtpInternalMessageInfo()=default
TimestampType leader_time_at_local_rx_event
Definition clock.h:41
DurationType d_tx_calc
Definition clock.h:40
TimestampType ts_tx_at_sender
Definition clock.h:38
Definition clock.h:30
TimestampType ts_tx_at_sender
Definition clock.h:32
TimestampType ts_local_rx
Definition clock.h:33
LeaderIdType sender_id
Definition clock.h:31