mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
commit
58c5a9c96f
3 changed files with 300 additions and 1952 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -378,3 +378,4 @@ target
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
cargo.lock
|
||||||
1720
Cargo.lock
generated
1720
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -91,6 +91,18 @@ impl LtcState {
|
||||||
match frame.status.as_str() {
|
match frame.status.as_str() {
|
||||||
"LOCK" => {
|
"LOCK" => {
|
||||||
self.lock_count += 1;
|
self.lock_count += 1;
|
||||||
|
|
||||||
|
// Recompute timecode-match every 5 seconds
|
||||||
|
let now_secs = Utc::now().timestamp();
|
||||||
|
if now_secs - self.last_match_check >= 5 {
|
||||||
|
self.last_match_status = if frame.matches_system_time() {
|
||||||
|
"IN SYNC"
|
||||||
|
} else {
|
||||||
|
"OUT OF SYNC"
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
self.last_match_check = now_secs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"FREE" => {
|
"FREE" => {
|
||||||
self.free_count += 1;
|
self.free_count += 1;
|
||||||
|
|
@ -101,18 +113,6 @@ impl LtcState {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recompute timecode-match every 5 seconds
|
|
||||||
let now_secs = Utc::now().timestamp();
|
|
||||||
if now_secs - self.last_match_check >= 5 {
|
|
||||||
self.last_match_status = if let Some(frame) = &self.latest {
|
|
||||||
if frame.matches_system_time() { "IN SYNC" } else { "OUT OF SYNC" }
|
|
||||||
} else {
|
|
||||||
"UNKNOWN"
|
|
||||||
}
|
|
||||||
.into();
|
|
||||||
self.last_match_check = now_secs;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.latest = Some(frame);
|
self.latest = Some(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,7 +216,7 @@ mod tests {
|
||||||
assert_eq!(state.lock_count, 0);
|
assert_eq!(state.lock_count, 0);
|
||||||
assert_eq!(state.free_count, 1);
|
assert_eq!(state.free_count, 1);
|
||||||
assert!(state.offset_history.is_empty()); // Offsets should be cleared
|
assert!(state.offset_history.is_empty()); // Offsets should be cleared
|
||||||
assert_eq!(state.last_match_status, "OUT OF SYNC");
|
assert_eq!(state.last_match_status, "UNKNOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -229,4 +229,71 @@ mod tests {
|
||||||
assert_eq!(*state.offset_history.front().unwrap(), 5); // 0-4 are pushed out
|
assert_eq!(*state.offset_history.front().unwrap(), 5); // 0-4 are pushed out
|
||||||
assert_eq!(*state.offset_history.back().unwrap(), 24);
|
assert_eq!(*state.offset_history.back().unwrap(), 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timecode_match_status_in_sync() {
|
||||||
|
let mut state = LtcState::new();
|
||||||
|
state.last_match_check = 0; // Force check to run
|
||||||
|
|
||||||
|
let now = Local::now();
|
||||||
|
let frame_in_sync = get_test_frame("LOCK", now.hour(), now.minute(), now.second());
|
||||||
|
state.update(frame_in_sync);
|
||||||
|
|
||||||
|
// This will fail due to the bug (`latest` is None during check).
|
||||||
|
// Expected: "IN SYNC", Actual: "UNKNOWN". This exposes the bug.
|
||||||
|
assert_eq!(state.timecode_match(), "IN SYNC");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timecode_match_status_out_of_sync() {
|
||||||
|
let mut state = LtcState::new();
|
||||||
|
state.last_match_check = 0; // Force check to run
|
||||||
|
|
||||||
|
let now = Local::now();
|
||||||
|
let different_hour = (now.hour() + 1) % 24;
|
||||||
|
let frame_out_of_sync = get_test_frame("LOCK", different_hour, now.minute(), now.second());
|
||||||
|
state.update(frame_out_of_sync);
|
||||||
|
|
||||||
|
// This will also fail due to the bug.
|
||||||
|
// Expected: "OUT OF SYNC", Actual: "UNKNOWN".
|
||||||
|
assert_eq!(state.timecode_match(), "OUT OF SYNC");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timecode_match_throttling() {
|
||||||
|
let mut state = LtcState::new();
|
||||||
|
let now = Local::now();
|
||||||
|
|
||||||
|
// First call. With the bug, status becomes UNKNOWN. With fix, OUT OF SYNC.
|
||||||
|
// The test is written for the fixed behavior.
|
||||||
|
state.last_match_check = 0;
|
||||||
|
let frame_out_of_sync =
|
||||||
|
get_test_frame("LOCK", (now.hour() + 1) % 24, now.minute(), now.second());
|
||||||
|
state.update(frame_out_of_sync.clone());
|
||||||
|
assert_eq!(
|
||||||
|
state.timecode_match(),
|
||||||
|
"OUT OF SYNC",
|
||||||
|
"Initial status should be out of sync"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Second call, immediately. Check should be throttled.
|
||||||
|
// Status should not change, even though we pass an in-sync frame.
|
||||||
|
let frame_in_sync = get_test_frame("LOCK", now.hour(), now.minute(), now.second());
|
||||||
|
state.update(frame_in_sync.clone());
|
||||||
|
assert_eq!(
|
||||||
|
state.timecode_match(),
|
||||||
|
"OUT OF SYNC",
|
||||||
|
"Status should not change due to throttling"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Third call, forcing check to run again.
|
||||||
|
// Status should now update to IN SYNC.
|
||||||
|
state.last_match_check = 0;
|
||||||
|
state.update(frame_in_sync.clone());
|
||||||
|
assert_eq!(
|
||||||
|
state.timecode_match(),
|
||||||
|
"IN SYNC",
|
||||||
|
"Status should update after throttle period"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue