From 5c4a19e186f7c9606c0b175932069fd79b0e79d9 Mon Sep 17 00:00:00 2001 From: Kyle McNally Date: Tue, 16 Jun 2026 10:03:02 +0100 Subject: [PATCH] fix: sync camera clock to local wall-clock time The Baichuan protocol has no timezone field, cameras store whatever time they receive and display it directly. Local wall-clock time is the correct value to send, matching what manufacturer apps do. Sending UTC caused the camera display to show the wrong time for anyone outside UTC. Uses the system TZ (via TZ env var or /etc/localtime, DST-aware). Falls back to UTC if the local offset cannot be determined. Users who want UTC can set TZ=UTC on the host. Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 1 + Cargo.toml | 1 + README.md | 1 + docs/configuration.md | 2 +- src/common/camthread.rs | 26 ++++++++++++++++++++------ 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 685b1446..b8feb3a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2099,6 +2099,7 @@ dependencies = [ "serde", "serde_json", "tikv-jemallocator", + "time", "tokio", "tokio-stream", "tokio-util", diff --git a/Cargo.toml b/Cargo.toml index 8b582b71..30ba266d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "io-util", "tracing", "signal"] } tokio-stream = "0.1.18" +time = { version = "0.3", features = ["local-offset"] } tokio-util = { version = "0.7.18", features = ["full", "tracing"] } toml = "1.0.7" uuid = { version = "1.22.0", features = ["v4"] } diff --git a/README.md b/README.md index c6dafef6..b2c3937d 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ merged, plus additional improvements: - Low-latency RTSP mode with frame draining, live-mode AppSrc, and reduced buffers - Docker images published to GHCR instead of Docker Hub - Updated dependencies and Debian Trixie base image +- Use local timezone when using `update_time` to set camera time **Inherited features from upstream:** diff --git a/docs/configuration.md b/docs/configuration.md index 4e66b2a7..d86ae90a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -55,7 +55,7 @@ enable_low_latency = false # reduce stream delay (default: false) # Diagnostics debug = false # dump raw XML from camera print_format = "None" # "None", "Human", or "Xml" for status messages -update_time = false # force camera clock sync on connect +update_time = false # force camera clock sync on connect (uses local wall-clock time) # NVR channel (for multi-camera NVRs) channel_id = 0 # 0-indexed camera on NVR diff --git a/src/common/camthread.rs b/src/common/camthread.rs index 6254ab9c..2cd2e424 100644 --- a/src/common/camthread.rs +++ b/src/common/camthread.rs @@ -194,7 +194,11 @@ impl Drop for NeoCamThread { } } -async fn update_camera_time(camera: &BcCamera, name: &str, update_time: bool) -> AnyResult<()> { +async fn update_camera_time( + camera: &BcCamera, + name: &str, + update_time: bool, +) -> AnyResult<()> { let cam_time = camera.get_time().await?; let mut update = false; if let Some(time) = cam_time { @@ -207,11 +211,21 @@ async fn update_camera_time(camera: &BcCamera, name: &str, update_time: bool) -> log::warn!("{}: Camera has no time set, Updating", name); } if update { - use std::time::SystemTime; - let new_time = SystemTime::now(); - - log::info!("{}: Setting time to {:?}", name, new_time); - match camera.set_time(new_time.into()).await { + use time::{OffsetDateTime, UtcOffset}; + + let utc_now = OffsetDateTime::now_utc(); + let offset = UtcOffset::local_offset_at(utc_now).unwrap_or(UtcOffset::UTC); + let local = utc_now.to_offset(offset); + log::info!( + "{}: Setting camera time to local time: {} {}", + name, + local, + offset + ); + // Strip the offset so the camera stores wall-clock local time as-is + let new_time = local.replace_offset(UtcOffset::UTC); + + match camera.set_time(new_time).await { Ok(_) => { let cam_time = camera.get_time().await?; if let Some(time) = cam_time {