diff --git a/docs/api.md b/docs/api.md index 2959c95..83471bb 100644 --- a/docs/api.md +++ b/docs/api.md @@ -8,13 +8,13 @@ This document describes the HTTP API for the NTP Timeturner application. - **`GET /api/status`** - Retrieves the real-time status of the LTC reader and system clock synchronization. + Retrieves the real-time status of the LTC reader and system clock synchronization. The `ltc_timecode` field uses `:` as a separator for non-drop-frame timecode, and `;` for drop-frame timecode between seconds and frames (e.g., `10:20:30;00`). **Example Response:** ```json { "ltc_status": "LOCK", - "ltc_timecode": "10:20:30:00", + "ltc_timecode": "10:20:30;00", "frame_rate": "25.00fps", "system_clock": "10:20:30.005", "system_date": "2025-07-30", diff --git a/src/api.rs b/src/api.rs index 62f21a7..14b0da4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -47,7 +47,11 @@ async fn get_status(data: web::Data) -> impl Responder { let ltc_status = state.latest.as_ref().map_or("(waiting)".to_string(), |f| f.status.clone()); let ltc_timecode = state.latest.as_ref().map_or("…".to_string(), |f| { - format!("{:02}:{:02}:{:02}:{:02}", f.hours, f.minutes, f.seconds, f.frames) + let sep = if f.is_drop_frame { ';' } else { ':' }; + format!( + "{:02}:{:02}:{:02}{}{:02}", + f.hours, f.minutes, f.seconds, sep, f.frames + ) }); let frame_rate = state.latest.as_ref().map_or("…".to_string(), |f| { format!("{:.2}fps", f.frame_rate.to_f64().unwrap_or(0.0)) @@ -291,6 +295,32 @@ mod tests { assert_eq!(resp.hardware_offset_ms, 10); } + #[actix_web::test] + async fn test_get_status_drop_frame() { + let app_state = get_test_app_state(); + // Set state to drop frame + app_state + .ltc_state + .lock() + .unwrap() + .latest + .as_mut() + .unwrap() + .is_drop_frame = true; + + let app = test::init_service( + App::new() + .app_data(app_state.clone()) + .service(get_status), + ) + .await; + + let req = test::TestRequest::get().uri("/api/status").to_request(); + let resp: ApiStatus = test::call_and_read_body_json(&app, req).await; + + assert_eq!(resp.ltc_timecode, "01:02:03;04"); + } + #[actix_web::test] async fn test_get_config() { let app_state = get_test_app_state(); diff --git a/static/script.js b/static/script.js index 2094cc4..6fd3475 100644 --- a/static/script.js +++ b/static/script.js @@ -98,9 +98,11 @@ document.addEventListener('DOMContentLoaded', () => { } // Animate LTC Timecode - only if status is LOCK - if (lastApiData.ltc_status === 'LOCK' && lastApiData.ltc_timecode && lastApiData.ltc_timecode.includes(':') && lastApiData.frame_rate) { - const tcParts = lastApiData.ltc_timecode.split(':'); + if (lastApiData.ltc_status === 'LOCK' && lastApiData.ltc_timecode && lastApiData.ltc_timecode.match(/[:;]/) && lastApiData.frame_rate) { + const separator = lastApiData.ltc_timecode.includes(';') ? ';' : ':'; + const tcParts = lastApiData.ltc_timecode.split(/[:;]/); const frameRate = parseFloat(lastApiData.frame_rate); + if (tcParts.length === 4 && !isNaN(frameRate) && frameRate > 0) { let h = parseInt(tcParts[0], 10); let m = parseInt(tcParts[1], 10); @@ -126,7 +128,7 @@ document.addEventListener('DOMContentLoaded', () => { h %= 24; statusElements.ltcTimecode.textContent = - `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}:${String(f).padStart(2, '0')}`; + `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}${separator}${String(f).padStart(2, '0')}`; } } }