From c712014bb90847c27d60bd9c9436839ec4281be6 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 11:39:46 +0100 Subject: [PATCH 01/12] feat: Allow millisecond offset for timeturner Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/api.rs | 5 ++++- src/config.rs | 8 +++++++- src/main.rs | 1 + src/system.rs | 12 +++++++----- static/index.html | 4 +++- static/script.js | 3 +++ 6 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/api.rs b/src/api.rs index 19e58a7..e1a15cb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -303,7 +303,7 @@ mod tests { let new_config_json = serde_json::json!({ "hardwareOffsetMs": 55, "defaultNudgeMs": 2, - "timeturnerOffset": { "hours": 1, "minutes": 2, "seconds": 3, "frames": 4 } + "timeturnerOffset": { "hours": 1, "minutes": 2, "seconds": 3, "frames": 4, "milliseconds": 5 } }); let req = test::TestRequest::post() @@ -315,15 +315,18 @@ mod tests { assert_eq!(resp.hardware_offset_ms, 55); assert_eq!(resp.timeturner_offset.hours, 1); + assert_eq!(resp.timeturner_offset.milliseconds, 5); let final_config = app_state.config.lock().unwrap(); assert_eq!(final_config.hardware_offset_ms, 55); assert_eq!(final_config.timeturner_offset.hours, 1); + assert_eq!(final_config.timeturner_offset.milliseconds, 5); // Test that the file was written assert!(fs::metadata(config_path).is_ok()); let contents = fs::read_to_string(config_path).unwrap(); assert!(contents.contains("hardwareOffsetMs: 55")); assert!(contents.contains("hours: 1")); + assert!(contents.contains("milliseconds: 5")); // Cleanup let _ = fs::remove_file(config_path); diff --git a/src/config.rs b/src/config.rs index 9b2cb5d..16cd774 100644 --- a/src/config.rs +++ b/src/config.rs @@ -19,11 +19,17 @@ pub struct TimeturnerOffset { pub minutes: i64, pub seconds: i64, pub frames: i64, + #[serde(default)] + pub milliseconds: i64, } impl TimeturnerOffset { pub fn is_active(&self) -> bool { - self.hours != 0 || self.minutes != 0 || self.seconds != 0 || self.frames != 0 + self.hours != 0 + || self.minutes != 0 + || self.seconds != 0 + || self.frames != 0 + || self.milliseconds != 0 } } diff --git a/src/main.rs b/src/main.rs index 698eabe..d7d40e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -52,6 +52,7 @@ timeturnerOffset: minutes: 0 seconds: 0 frames: 0 + milliseconds: 0 "#; /// If no `config.yml` exists alongside the binary, write out the default. diff --git a/src/system.rs b/src/system.rs index 1d2ed7d..c3918f6 100644 --- a/src/system.rs +++ b/src/system.rs @@ -57,7 +57,7 @@ pub fn calculate_target_time(frame: &LtcFrame, config: &Config) -> DateTime Result { @@ -177,6 +177,7 @@ mod tests { minutes: 5, seconds: 10, frames: 12, // 12 frames at 25fps is 480ms + milliseconds: 20, }; let target_time = calculate_target_time(&frame, &config); @@ -184,8 +185,8 @@ mod tests { assert_eq!(target_time.hour(), 11); assert_eq!(target_time.minute(), 25); assert_eq!(target_time.second(), 40); - // 480ms - assert_eq!(target_time.nanosecond(), 480_000_000); + // 480ms + 20ms = 500ms + assert_eq!(target_time.nanosecond(), 500_000_000); } #[test] @@ -197,14 +198,15 @@ mod tests { minutes: -5, seconds: -10, frames: -12, // -480ms + milliseconds: -80, }; let target_time = calculate_target_time(&frame, &config); assert_eq!(target_time.hour(), 9); assert_eq!(target_time.minute(), 15); - assert_eq!(target_time.second(), 20); - assert_eq!(target_time.nanosecond(), 0); + assert_eq!(target_time.second(), 19); + assert_eq!(target_time.nanosecond(), 920_000_000); } #[test] diff --git a/static/index.html b/static/index.html index 79bfd80..d5969db 100644 --- a/static/index.html +++ b/static/index.html @@ -50,7 +50,7 @@
- + @@ -58,6 +58,8 @@ + +
diff --git a/static/script.js b/static/script.js index ad9178c..d15d9a6 100644 --- a/static/script.js +++ b/static/script.js @@ -20,6 +20,7 @@ document.addEventListener('DOMContentLoaded', () => { m: document.getElementById('offset-m'), s: document.getElementById('offset-s'), f: document.getElementById('offset-f'), + ms: document.getElementById('offset-ms'), }; const saveConfigButton = document.getElementById('save-config'); const manualSyncButton = document.getElementById('manual-sync'); @@ -84,6 +85,7 @@ document.addEventListener('DOMContentLoaded', () => { offsetInputs.m.value = data.timeturnerOffset.minutes; offsetInputs.s.value = data.timeturnerOffset.seconds; offsetInputs.f.value = data.timeturnerOffset.frames; + offsetInputs.ms.value = data.timeturnerOffset.milliseconds || 0; nudgeValueInput.value = data.defaultNudgeMs; } catch (error) { console.error('Error fetching config:', error); @@ -99,6 +101,7 @@ document.addEventListener('DOMContentLoaded', () => { minutes: parseInt(offsetInputs.m.value, 10) || 0, seconds: parseInt(offsetInputs.s.value, 10) || 0, frames: parseInt(offsetInputs.f.value, 10) || 0, + milliseconds: parseInt(offsetInputs.ms.value, 10) || 0, } }; From fb8088c7041b42d7dc6df22329ef2a0a4de42eb6 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 11:44:59 +0100 Subject: [PATCH 02/12] test: add missing milliseconds field to TimeturnerOffset init Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/sync_logic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync_logic.rs b/src/sync_logic.rs index 4a7fd5c..d54f2cc 100644 --- a/src/sync_logic.rs +++ b/src/sync_logic.rs @@ -348,7 +348,7 @@ mod tests { assert_eq!(get_sync_status(-100, &config), "CLOCK BEHIND"); // Test TIMETURNING status - config.timeturner_offset = TimeturnerOffset { hours: 1, minutes: 0, seconds: 0, frames: 0 }; + config.timeturner_offset = TimeturnerOffset { hours: 1, minutes: 0, seconds: 0, frames: 0, milliseconds: 0 }; assert_eq!(get_sync_status(0, &config), "TIMETURNING"); assert_eq!(get_sync_status(100, &config), "TIMETURNING"); } From 4090fee0a605333e595b23f40fc63156d421aebd Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 11:49:22 +0100 Subject: [PATCH 03/12] test: Restore original config.yml after tests Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/main.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index d7d40e4..1d7e496 100644 --- a/src/main.rs +++ b/src/main.rs @@ -196,18 +196,35 @@ mod tests { use std::fs; use std::path::Path; - /// RAII guard to ensure config file is cleaned up after test. - struct ConfigGuard; + /// RAII guard to manage config file during tests. + /// It saves the original content of `config.yml` if it exists, + /// and restores it when the guard goes out of scope. + /// If the file didn't exist, it's removed. + struct ConfigGuard { + original_content: Option, + } + + impl ConfigGuard { + fn new() -> Self { + Self { + original_content: fs::read_to_string("config.yml").ok(), + } + } + } impl Drop for ConfigGuard { fn drop(&mut self) { - let _ = fs::remove_file("config.yml"); + if let Some(content) = &self.original_content { + fs::write("config.yml", content).expect("Failed to restore config.yml"); + } else { + let _ = fs::remove_file("config.yml"); + } } } #[test] fn test_ensure_config() { - let _guard = ConfigGuard; // Cleanup when _guard goes out of scope. + let _guard = ConfigGuard::new(); // Cleanup when _guard goes out of scope. // --- Test 1: File creation --- // Pre-condition: config.yml does not exist. From 89849c6e0436e54a1ce502816e5f794c168b0e5a Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 11:59:46 +0100 Subject: [PATCH 04/12] refactor: simplify default configuration Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/main.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 1d7e496..7aeeb53 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,9 +42,6 @@ const DEFAULT_CONFIG: &str = r#" # Hardware offset in milliseconds for correcting capture latency. hardwareOffsetMs: 20 -# Default nudge in milliseconds for adjtimex control. -defaultNudgeMs: 2 - # Time-turning offsets. All values are added to the incoming LTC time. # These can be positive or negative. timeturnerOffset: @@ -52,7 +49,6 @@ timeturnerOffset: minutes: 0 seconds: 0 frames: 0 - milliseconds: 0 "#; /// If no `config.yml` exists alongside the binary, write out the default. From f929bacdfd138bf3379f7a0195fa514963c15d47 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 12:10:56 +0100 Subject: [PATCH 05/12] config tweak, --- config.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/config.yml b/config.yml index 470c6c9..8422c84 100644 --- a/config.yml +++ b/config.yml @@ -4,7 +4,9 @@ hardwareOffsetMs: 20 # Time-turning offsets. All values are added to the incoming LTC time. # These can be positive or negative. timeturnerOffset: - hours: 0 - minutes: 0 - seconds: 0 - frames: 0 + hours: 1 + minutes: 2 + seconds: 3 + frames: 4 + milliseconds: 5 +defaultNudgeMs: 2 From fcbd5bd647df391653d479eb87d2c6a6575ede73 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 12:18:24 +0100 Subject: [PATCH 06/12] clarification in this --- src/sync_logic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync_logic.rs b/src/sync_logic.rs index d54f2cc..a0317af 100644 --- a/src/sync_logic.rs +++ b/src/sync_logic.rs @@ -159,7 +159,7 @@ impl LtcState { pub fn get_sync_status(delta_ms: i64, config: &Config) -> &'static str { if config.timeturner_offset.is_active() { - "TIMETURNING" + "TIME LOCK ACTIVE" } else if delta_ms.abs() <= 8 { "IN SYNC" } else if delta_ms > 10 { From c0613c3682a6c2fe066f764d40f801fe83dedf61 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 12:21:54 +0100 Subject: [PATCH 07/12] revert --- src/sync_logic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync_logic.rs b/src/sync_logic.rs index a0317af..d54f2cc 100644 --- a/src/sync_logic.rs +++ b/src/sync_logic.rs @@ -159,7 +159,7 @@ impl LtcState { pub fn get_sync_status(delta_ms: i64, config: &Config) -> &'static str { if config.timeturner_offset.is_active() { - "TIME LOCK ACTIVE" + "TIMETURNING" } else if delta_ms.abs() <= 8 { "IN SYNC" } else if delta_ms > 10 { From 4cb421b3d6e671b7fa22f012b404288b2ad64ede Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 12:25:40 +0100 Subject: [PATCH 08/12] fix: clarify timeturner offset controls with labels Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- static/index.html | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/static/index.html b/static/index.html index d5969db..594d022 100644 --- a/static/index.html +++ b/static/index.html @@ -50,16 +50,29 @@
- - - - - - - - - - + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
From d015794b03bb657ed82dd7f6bac7926a2c00f99f Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 14:18:10 +0100 Subject: [PATCH 09/12] feat: implement auto-sync with periodic clock nudging Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/api.rs | 5 +++ src/config.rs | 3 ++ src/main.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++--- src/ui.rs | 23 +------------- static/index.html | 4 +++ static/script.js | 3 ++ 6 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/api.rs b/src/api.rs index e1a15cb..93a883d 100644 --- a/src/api.rs +++ b/src/api.rs @@ -238,6 +238,7 @@ mod tests { hardware_offset_ms: 10, timeturner_offset: TimeturnerOffset::default(), default_nudge_ms: 2, + auto_sync_enabled: false, })); let log_buffer = Arc::new(Mutex::new(VecDeque::new())); web::Data::new(AppState { @@ -303,6 +304,7 @@ mod tests { let new_config_json = serde_json::json!({ "hardwareOffsetMs": 55, "defaultNudgeMs": 2, + "autoSyncEnabled": true, "timeturnerOffset": { "hours": 1, "minutes": 2, "seconds": 3, "frames": 4, "milliseconds": 5 } }); @@ -314,10 +316,12 @@ mod tests { let resp: Config = test::call_and_read_body_json(&app, req).await; assert_eq!(resp.hardware_offset_ms, 55); + assert_eq!(resp.auto_sync_enabled, true); assert_eq!(resp.timeturner_offset.hours, 1); assert_eq!(resp.timeturner_offset.milliseconds, 5); let final_config = app_state.config.lock().unwrap(); assert_eq!(final_config.hardware_offset_ms, 55); + assert_eq!(final_config.auto_sync_enabled, true); assert_eq!(final_config.timeturner_offset.hours, 1); assert_eq!(final_config.timeturner_offset.milliseconds, 5); @@ -325,6 +329,7 @@ mod tests { assert!(fs::metadata(config_path).is_ok()); let contents = fs::read_to_string(config_path).unwrap(); assert!(contents.contains("hardwareOffsetMs: 55")); + assert!(contents.contains("autoSyncEnabled: true")); assert!(contents.contains("hours: 1")); assert!(contents.contains("milliseconds: 5")); diff --git a/src/config.rs b/src/config.rs index 16cd774..dd37850 100644 --- a/src/config.rs +++ b/src/config.rs @@ -41,6 +41,8 @@ pub struct Config { pub timeturner_offset: TimeturnerOffset, #[serde(default = "default_nudge_ms")] pub default_nudge_ms: i64, + #[serde(default)] + pub auto_sync_enabled: bool, } fn default_nudge_ms() -> i64 { @@ -70,6 +72,7 @@ impl Default for Config { hardware_offset_ms: 0, timeturner_offset: TimeturnerOffset::default(), default_nudge_ms: default_nudge_ms(), + auto_sync_enabled: false, } } } diff --git a/src/main.rs b/src/main.rs index 7aeeb53..516d482 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,11 @@ const DEFAULT_CONFIG: &str = r#" # Hardware offset in milliseconds for correcting capture latency. hardwareOffsetMs: 20 +# Enable automatic clock synchronization. +# When enabled, the system will perform an initial full sync, then periodically +# nudge the clock to keep it aligned with the LTC source. +autoSyncEnabled: false + # Time-turning offsets. All values are added to the incoming LTC time. # These can be positive or negative. timeturnerOffset: @@ -133,11 +138,79 @@ async fn main() { log::info!("🚀 Starting TimeTurner daemon..."); } - // 6️⃣ Set up a LocalSet for the API server and main loop + // 6️⃣ Spawn the auto-sync thread + { + let sync_state = ltc_state.clone(); + let sync_config = config.clone(); + thread::spawn(move || { + // Wait for the first LTC frame to arrive + loop { + if sync_state.lock().unwrap().latest.is_some() { + log::info!("Auto-sync: Initial LTC frame detected."); + break; + } + thread::sleep(std::time::Duration::from_secs(1)); + } + + // Initial sync + { + let state = sync_state.lock().unwrap(); + let config = sync_config.lock().unwrap(); + if config.auto_sync_enabled { + if let Some(frame) = &state.latest { + log::info!("Auto-sync: Performing initial full sync."); + if system::trigger_sync(frame, &config).is_ok() { + log::info!("Auto-sync: Initial sync successful."); + } else { + log::error!("Auto-sync: Initial sync failed."); + } + } + } + } + + thread::sleep(std::time::Duration::from_secs(10)); + + // Main auto-sync loop + loop { + { + let state = sync_state.lock().unwrap(); + let config = sync_config.lock().unwrap(); + + if config.auto_sync_enabled && state.latest.is_some() { + let delta = state.get_ewma_clock_delta(); + let frame = state.latest.as_ref().unwrap(); + + if delta.abs() > 40 { + log::info!("Auto-sync: Delta > 40ms ({}ms), performing full sync.", delta); + if system::trigger_sync(frame, &config).is_ok() { + log::info!("Auto-sync: Full sync successful."); + } else { + log::error!("Auto-sync: Full sync failed."); + } + } else if delta.abs() >= 1 { + // nudge_clock takes microseconds. A positive delta means clock is + // ahead, so we need a negative nudge. + let nudge_us = -delta * 1000; + log::info!("Auto-sync: Delta is {}ms, nudging clock by {}us.", delta, nudge_us); + if system::nudge_clock(nudge_us).is_ok() { + log::info!("Auto-sync: Clock nudge successful."); + } else { + log::error!("Auto-sync: Clock nudge failed."); + } + } + } + } // locks released here + + thread::sleep(std::time::Duration::from_secs(10)); + } + }); + } + + // 7️⃣ Set up a LocalSet for the API server and main loop let local = LocalSet::new(); local .run_until(async move { - // 7️⃣ Spawn the API server thread + // 8️⃣ Spawn the API server thread { let api_state = ltc_state.clone(); let config_clone = config.clone(); @@ -151,7 +224,7 @@ async fn main() { }); } - // 8️⃣ Main logic loop: process frames from serial and update state + // 9️⃣ Main logic loop: process frames from serial and update state let loop_state = ltc_state.clone(); let loop_config = config.clone(); let logic_task = task::spawn_blocking(move || { @@ -172,7 +245,7 @@ async fn main() { } }); - // 9️⃣ Keep main thread alive + // 1️⃣0️⃣ Keep main thread alive if args.command.is_some() { // In daemon mode, wait forever. The logic_task runs in the background. std::future::pending::<()>().await; diff --git a/src/ui.rs b/src/ui.rs index fd7d71b..dfa0023 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -94,28 +94,7 @@ pub fn start_ui( // 6️⃣ sync status wording let sync_status = get_sync_status(cached_delta_ms, &cfg); - // 7️⃣ auto‑sync (same as manual but delayed) - if sync_status != "IN SYNC" && sync_status != "TIMETURNING" { - if let Some(start) = out_of_sync_since { - if start.elapsed() >= Duration::from_secs(5) { - if let Some(frame) = &state.lock().unwrap().latest { - let entry = match system::trigger_sync(frame, &cfg) { - Ok(ts) => format!("🔄 Auto‑synced to LTC: {}", ts), - Err(_) => "❌ Auto‑sync failed".into(), - }; - if logs.len() == 10 { logs.pop_front(); } - logs.push_back(entry); - } - out_of_sync_since = None; - } - } else { - out_of_sync_since = Some(Instant::now()); - } - } else { - out_of_sync_since = None; - } - - // 8️⃣ header & LTC metrics display + // 7️⃣ header & LTC metrics display { let st = state.lock().unwrap(); let opt = st.latest.as_ref(); diff --git a/static/index.html b/static/index.html index 594d022..eb074af 100644 --- a/static/index.html +++ b/static/index.html @@ -49,6 +49,10 @@
+
+ + +
diff --git a/static/script.js b/static/script.js index d15d9a6..22c8dd7 100644 --- a/static/script.js +++ b/static/script.js @@ -15,6 +15,7 @@ document.addEventListener('DOMContentLoaded', () => { }; const hwOffsetInput = document.getElementById('hw-offset'); + const autoSyncCheckbox = document.getElementById('auto-sync-enabled'); const offsetInputs = { h: document.getElementById('offset-h'), m: document.getElementById('offset-m'), @@ -81,6 +82,7 @@ document.addEventListener('DOMContentLoaded', () => { if (!response.ok) throw new Error('Failed to fetch config'); const data = await response.json(); hwOffsetInput.value = data.hardwareOffsetMs; + autoSyncCheckbox.checked = data.autoSyncEnabled; offsetInputs.h.value = data.timeturnerOffset.hours; offsetInputs.m.value = data.timeturnerOffset.minutes; offsetInputs.s.value = data.timeturnerOffset.seconds; @@ -95,6 +97,7 @@ document.addEventListener('DOMContentLoaded', () => { async function saveConfig() { const config = { hardwareOffsetMs: parseInt(hwOffsetInput.value, 10) || 0, + autoSyncEnabled: autoSyncCheckbox.checked, defaultNudgeMs: parseInt(nudgeValueInput.value, 10) || 0, timeturnerOffset: { hours: parseInt(offsetInputs.h.value, 10) || 0, From 9a9702787070ae66ab0fc64733474784e410d4a4 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 14:38:23 +0100 Subject: [PATCH 10/12] fix: remove unused out_of_sync_since variable Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/ui.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ui.rs b/src/ui.rs index dfa0023..b36e9e3 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -34,7 +34,6 @@ pub fn start_ui( terminal::enable_raw_mode().unwrap(); let mut logs: VecDeque = VecDeque::with_capacity(10); - let mut out_of_sync_since: Option = None; let mut last_delta_update = Instant::now() - Duration::from_secs(1); let mut cached_delta_ms: i64 = 0; let mut cached_delta_frames: i64 = 0; From 68dc16344aa8cedabd2f0de6e5405e3c04036a84 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 14:42:33 +0100 Subject: [PATCH 11/12] fix: preserve comments in config.yml when saving Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) --- src/config.rs | 24 ++++++++++++++++++++++-- src/main.rs | 4 ++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index dd37850..974d60b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -78,8 +78,28 @@ impl Default for Config { } pub fn save_config(path: &str, config: &Config) -> Result<(), Box> { - let contents = serde_yaml::to_string(config)?; - fs::write(path, contents)?; + let mut s = String::new(); + s.push_str("# Hardware offset in milliseconds for correcting capture latency.\n"); + s.push_str(&format!("hardwareOffsetMs: {}\n\n", config.hardware_offset_ms)); + + s.push_str("# Enable automatic clock synchronization.\n"); + s.push_str("# When enabled, the system will perform an initial full sync, then periodically\n"); + s.push_str("# nudge the clock to keep it aligned with the LTC source.\n"); + s.push_str(&format!("autoSyncEnabled: {}\n\n", config.auto_sync_enabled)); + + s.push_str("# Default nudge in milliseconds for adjtimex control.\n"); + s.push_str(&format!("defaultNudgeMs: {}\n\n", config.default_nudge_ms)); + + s.push_str("# Time-turning offsets. All values are added to the incoming LTC time.\n"); + s.push_str("# These can be positive or negative.\n"); + s.push_str("timeturnerOffset:\n"); + s.push_str(&format!(" hours: {}\n", config.timeturner_offset.hours)); + s.push_str(&format!(" minutes: {}\n", config.timeturner_offset.minutes)); + s.push_str(&format!(" seconds: {}\n", config.timeturner_offset.seconds)); + s.push_str(&format!(" frames: {}\n", config.timeturner_offset.frames)); + s.push_str(&format!(" milliseconds: {}\n", config.timeturner_offset.milliseconds)); + + fs::write(path, s)?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index 516d482..e265210 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,6 +47,9 @@ hardwareOffsetMs: 20 # nudge the clock to keep it aligned with the LTC source. autoSyncEnabled: false +# Default nudge in milliseconds for adjtimex control. +defaultNudgeMs: 2 + # Time-turning offsets. All values are added to the incoming LTC time. # These can be positive or negative. timeturnerOffset: @@ -54,6 +57,7 @@ timeturnerOffset: minutes: 0 seconds: 0 frames: 0 + milliseconds: 0 "#; /// If no `config.yml` exists alongside the binary, write out the default. From 992720041bc9f59ce806b44091b96c5fc42837e1 Mon Sep 17 00:00:00 2001 From: John Rogers Date: Tue, 29 Jul 2025 14:49:26 +0100 Subject: [PATCH 12/12] updated config with config bodge in tests. --- config.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/config.yml b/config.yml index 8422c84..bf892f4 100644 --- a/config.yml +++ b/config.yml @@ -1,5 +1,13 @@ # Hardware offset in milliseconds for correcting capture latency. -hardwareOffsetMs: 20 +hardwareOffsetMs: 55 + +# Enable automatic clock synchronization. +# When enabled, the system will perform an initial full sync, then periodically +# nudge the clock to keep it aligned with the LTC source. +autoSyncEnabled: true + +# Default nudge in milliseconds for adjtimex control. +defaultNudgeMs: 2 # Time-turning offsets. All values are added to the incoming LTC time. # These can be positive or negative. @@ -9,4 +17,3 @@ timeturnerOffset: seconds: 3 frames: 4 milliseconds: 5 -defaultNudgeMs: 2