Skip to main content

flydra2/
connected_camera_manager.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::collections::{BTreeMap, BTreeSet};
5use std::sync::{
6    Arc, Mutex, RwLock,
7    atomic::{AtomicBool, Ordering},
8};
9use tracing::{debug, error, info};
10
11use crate::{CamInfoRow, MyFloat, safe_u8};
12use braid_types::{
13    BuiServerInfo, CamInfo, CamNum, ConnectedCameraSyncState, PtpStamp, PtpSyncConfig, RawCamName,
14    RecentStats, SyncFno, TRIGGERBOX_SYNC_SECONDS, TriggerType,
15};
16
17/// The moment camera synchronization began, captured on both clocks.
18#[derive(Debug, Clone, Copy)]
19pub struct SyncStart {
20    /// Monotonic clock, used to bound the synchronization window.
21    pub instant: std::time::Instant,
22    /// Wall clock at the same moment. With fake sync, each camera's frame
23    /// numbering is anchored to this common epoch via the frame timestamps,
24    /// so that synchronization does not depend on packet arrival order.
25    pub wallclock: chrono::DateTime<chrono::Utc>,
26}
27
28impl SyncStart {
29    pub fn now() -> Self {
30        Self {
31            instant: std::time::Instant::now(),
32            wallclock: chrono::Utc::now(),
33        }
34    }
35}
36
37pub(crate) trait HasCameraList {
38    fn camera_list(&self) -> CameraList;
39}
40
41/// A set of cameras (stored by their CamNum) which is currently connected.
42///
43/// This struct implements PartialEq so multiple sets of cameras can be checked
44/// to see if both groups are identical.
45#[derive(Clone, Debug, PartialEq)]
46pub(crate) struct CameraList {
47    pub(crate) inner: BTreeSet<u8>,
48}
49
50impl CameraList {
51    pub(crate) fn new(cams: &[u8]) -> Self {
52        let inner = cams.iter().copied().collect();
53        Self { inner }
54    }
55}
56
57impl HasCameraList for CameraList {
58    fn camera_list(&self) -> CameraList {
59        self.clone()
60    }
61}
62
63#[derive(Debug)]
64pub struct ConnectedCameraInfo {
65    cam_num: CamNum,
66    raw_cam_name: RawCamName,
67    sync_state: ConnectedCameraSyncState,
68    http_camserver_info: BuiServerInfo,
69    frames_during_sync: u64,
70    _camera_periodic_signal_period_usec: Option<f64>,
71}
72
73impl ConnectedCameraInfo {
74    fn copy_to_caminfo(&self) -> CamInfoRow {
75        CamInfoRow {
76            camn: self.cam_num,
77            cam_id: self.raw_cam_name.as_str().to_string(),
78        }
79    }
80}
81
82#[derive(Debug)]
83struct ConnectedCamerasManagerInner {
84    all_expected_cameras: BTreeSet<RawCamName>,
85    next_cam_num: CamNum,
86    ccis: BTreeMap<RawCamName, ConnectedCameraInfo>,
87    not_yet_connected: BTreeMap<RawCamName, CamNum>,
88    all_expected_cameras_are_present: bool,
89    all_expected_cameras_are_synced: bool,
90    first_frame_arrived: BTreeSet<RawCamName>,
91}
92
93pub trait ConnectedCamCallback: Send {
94    fn on_cam_changed(&self, _: Vec<CamInfo>);
95}
96
97/// keeps track of connected camera state
98///
99/// There should be a single call to `::new()` made in the app. Then, `clone()`
100/// can be called to copy the outer wrapper which links to the actual inner
101/// manager via `Arc<Mutex<_>>`.
102#[derive(Clone)]
103pub struct ConnectedCamerasManager {
104    inner: Arc<RwLock<ConnectedCamerasManagerInner>>,
105    on_cam_change_func: Arc<Mutex<Option<Box<dyn ConnectedCamCallback>>>>,
106    recon: Option<flydra_mvg::FlydraMultiCameraSystem<MyFloat>>,
107    signal_all_cams_present: Arc<AtomicBool>,
108    signal_all_cams_synced: Arc<AtomicBool>,
109    launch_time_ptp: PtpStamp,
110    periodic_signal_period_usec: Option<f64>,
111}
112
113impl HasCameraList for ConnectedCamerasManager {
114    fn camera_list(&self) -> CameraList {
115        let inner: BTreeSet<u8> = self
116            .inner
117            .read()
118            .unwrap()
119            .ccis
120            .values()
121            .map(|cci| cci.cam_num.0)
122            .collect();
123        CameraList { inner }
124    }
125}
126
127impl ConnectedCamerasManager {
128    pub fn new(
129        recon: &Option<flydra_mvg::FlydraMultiCameraSystem<MyFloat>>,
130        all_expected_cameras: BTreeSet<RawCamName>,
131        signal_all_cams_present: Arc<AtomicBool>,
132        signal_all_cams_synced: Arc<AtomicBool>,
133        periodic_signal_period_usec: Option<f64>,
134        predefined_cam_nums: Option<BTreeMap<RawCamName, CamNum>>,
135    ) -> Self {
136        let mut not_yet_connected = BTreeMap::new();
137
138        // pre-reserve cam numbers for cameras in calibration
139        let next_cam_num = if let Some(recon) = recon {
140            for (base_num, cam_name) in recon.cam_names().enumerate() {
141                let raw_cam_name = RawCamName::new(cam_name.to_string());
142                if let Some(predefined_cam_nums) = &predefined_cam_nums
143                    && let Some(predefined_cam_num) = predefined_cam_nums.get(&raw_cam_name)
144                {
145                    not_yet_connected.insert(raw_cam_name, *predefined_cam_num);
146                } else {
147                    let cam_num: CamNum = safe_u8(base_num).into();
148                    not_yet_connected.insert(raw_cam_name, cam_num);
149                }
150            }
151            safe_u8(recon.len())
152        } else {
153            0
154        };
155
156        let launch_time = chrono::Utc::now();
157        let mut launch_time_ptp = PtpStamp::try_from(launch_time).unwrap();
158
159        if let Some(periodic_signal_period_usec) = periodic_signal_period_usec.as_ref() {
160            // This a) rounds to period so that calculation of frame number in
161            // PTP mode are are not at knife edge between .4999 and 0.5001 of
162            // the period and b) subtracts one tick from the launch time to
163            // avoid the case where the first frame of the camera having a
164            // slightly faster clock will result in an impossible negative frame
165            // number.
166            let periodic_signal_period_nsec = (periodic_signal_period_usec * 1000.0) as u64;
167            let n_ticks = launch_time_ptp.get() / periodic_signal_period_nsec - 1;
168            launch_time_ptp = PtpStamp::new(n_ticks * periodic_signal_period_nsec);
169        }
170
171        let launch_time_ptp_utc: chrono::DateTime<chrono::Utc> =
172            launch_time_ptp.clone().try_into().unwrap();
173        let launch_time_ptp_local: chrono::DateTime<chrono::Local> = launch_time_ptp_utc.into();
174        tracing::debug!("launch_time_ptp_local: {launch_time_ptp_local}");
175
176        Self {
177            signal_all_cams_present,
178            signal_all_cams_synced,
179            inner: Arc::new(RwLock::new(ConnectedCamerasManagerInner {
180                all_expected_cameras,
181                next_cam_num: next_cam_num.into(),
182                ccis: BTreeMap::new(),
183                not_yet_connected,
184                all_expected_cameras_are_present: false,
185                all_expected_cameras_are_synced: false,
186                first_frame_arrived: BTreeSet::new(),
187            })),
188            on_cam_change_func: Arc::new(Mutex::new(None)),
189            recon: recon.clone(),
190            launch_time_ptp,
191            periodic_signal_period_usec,
192        }
193    }
194
195    /// The cameras are being (re)synchronized. Clear all inner data and reset camera numbers.
196    pub fn reset_sync_data(&mut self) {
197        info!("Camera manager dropping old cameras and expecting new cameras");
198
199        let mut next_cam_num = { self.inner.read().unwrap().next_cam_num.0 };
200        let mut not_yet_connected = BTreeMap::new();
201
202        // pre-reserve cam numbers for cameras in calibration
203        if let Some(recon) = &self.recon {
204            for cam_name in recon.cam_names() {
205                let cam_num = next_cam_num;
206                next_cam_num = safe_u8(next_cam_num as usize + 1);
207                let raw_cam_name = RawCamName::new(cam_name.to_string());
208                let cam_num: CamNum = cam_num.into();
209                not_yet_connected.insert(raw_cam_name, cam_num);
210            }
211        }
212
213        let old_ccis = {
214            let mut inner = self.inner.write().unwrap();
215            inner.next_cam_num = next_cam_num.into();
216            let old_ccis = std::mem::take(&mut inner.ccis);
217            inner.not_yet_connected = not_yet_connected;
218            old_ccis
219        };
220
221        for cam_info in old_ccis.values() {
222            // This calls self.notify_cam_changed_listeners():
223            self.register_new_camera(
224                &cam_info.raw_cam_name,
225                &cam_info.http_camserver_info,
226                self.periodic_signal_period_usec,
227            )
228            .unwrap();
229        }
230    }
231
232    /// Set callback to be called when connected cameras or their state changes
233    pub fn set_cam_changed_callback(
234        &mut self,
235        f: Box<dyn ConnectedCamCallback>,
236    ) -> Option<Box<dyn ConnectedCamCallback>> {
237        let old = {
238            let mut mutex_guard = self.on_cam_change_func.lock().unwrap();
239            mutex_guard.replace(f)
240        };
241
242        // set new data on initial connect
243        self.notify_cam_changed_listeners();
244        old
245    }
246
247    fn notify_cam_changed_listeners(&self) {
248        let mutex_guard = self.on_cam_change_func.lock().unwrap();
249        let inner_ref: Option<&Box<dyn ConnectedCamCallback>> = mutex_guard.as_ref();
250        if let Some(cb) = inner_ref {
251            let cams = {
252                // scope for read lock on self.inner
253                self.inner
254                    .read()
255                    .unwrap()
256                    .ccis
257                    .values()
258                    .map(|cci| CamInfo {
259                        name: cci.raw_cam_name.clone(),
260                        state: cci.sync_state.clone(),
261                        strand_cam_http_server_info: cci.http_camserver_info.clone(),
262                        recent_stats: RecentStats::default(),
263                    })
264                    .collect()
265            };
266            cb.on_cam_changed(cams)
267        }
268    }
269
270    /// Alternative constructor for use in case of a single camera.
271    ///
272    /// See `new` and `register_new_camera` for the case when multiple cameras
273    /// will be added.
274    pub fn new_single_cam(
275        raw_cam_name: &RawCamName,
276        http_camserver_info: &BuiServerInfo,
277        recon: &Option<flydra_mvg::FlydraMultiCameraSystem<MyFloat>>,
278        camera_periodic_signal_period_usec: Option<f64>,
279    ) -> Self {
280        let signal_all_cams_present = Arc::new(AtomicBool::new(false));
281        let signal_all_cams_synced = Arc::new(AtomicBool::new(false));
282
283        let mut all_expected_cameras = BTreeSet::new();
284        all_expected_cameras.insert(raw_cam_name.clone());
285
286        let this = Self::new(
287            recon,
288            all_expected_cameras,
289            signal_all_cams_present,
290            signal_all_cams_synced,
291            camera_periodic_signal_period_usec,
292            None,
293        );
294        {
295            let raw_cam_name = raw_cam_name.clone();
296
297            let mut inner = this.inner.write().unwrap();
298
299            assert!(
300                !inner.ccis.contains_key(&raw_cam_name),
301                "camera connecting again?"
302            );
303
304            let cam_num = if let Some(pre_existing) = inner.not_yet_connected.remove(&raw_cam_name)
305            {
306                debug!(
307                    "registering camera {}, which is in existing calibration",
308                    raw_cam_name.as_str()
309                );
310                pre_existing
311            } else {
312                debug!(
313                    "registering camera {}, which is not in existing calibration",
314                    raw_cam_name.as_str()
315                );
316                // unknown (and thus un-calibrated) camera
317                let cam_num = inner.next_cam_num;
318                inner.next_cam_num.0 += 1;
319                cam_num
320            };
321
322            inner.ccis.insert(
323                raw_cam_name.clone(),
324                ConnectedCameraInfo {
325                    cam_num,
326                    raw_cam_name,
327                    sync_state: ConnectedCameraSyncState::Unsynchronized,
328                    http_camserver_info: http_camserver_info.clone(),
329                    frames_during_sync: 0,
330                    _camera_periodic_signal_period_usec: camera_periodic_signal_period_usec,
331                },
332            );
333        }
334        this
335    }
336
337    pub fn remove(&mut self, raw_cam_name: &RawCamName) {
338        self.inner.write().unwrap().ccis.remove(raw_cam_name);
339        self.notify_cam_changed_listeners();
340    }
341
342    /// This is called to register a camera when it connects to the mainbrain.
343    ///
344    /// See `new_single_cam` for the case when only a single camera will be
345    /// added.
346    pub fn register_new_camera(
347        &mut self,
348        raw_cam_name: &RawCamName,
349        http_camserver_info: &BuiServerInfo,
350        camera_periodic_signal_period_usec: Option<f64>,
351    ) -> Result<(), &'static str> {
352        if camera_periodic_signal_period_usec != self.periodic_signal_period_usec {
353            return Err(
354                "camera_periodic_signal_period_usec differs from periodic_signal_period_usec.",
355            );
356        }
357        let raw_cam_name = raw_cam_name.clone();
358        let cam_num = {
359            // This scope is for the write lock on self.inner. Keep it minimal.
360            let mut inner = self.inner.write().unwrap();
361
362            if inner.ccis.contains_key(&raw_cam_name) {
363                tracing::error!(
364                    "Camera \"{raw_cam_name}\" has already connected but is attempting to connect again."
365                );
366                return Err("camera already connected");
367            }
368
369            let cam_num = if let Some(pre_existing) = inner.not_yet_connected.remove(&raw_cam_name)
370            {
371                debug!(
372                    "registering camera {}, which is in existing calibration",
373                    raw_cam_name.as_str()
374                );
375                pre_existing
376            } else {
377                if self.recon.is_some() {
378                    tracing::warn!(
379                        "Camera {} connected, but this is not in existing calibration.",
380                        raw_cam_name.as_str()
381                    );
382                }
383                // unknown (and thus un-calibrated) camera
384                let cam_num_inner = inner.next_cam_num;
385                inner.next_cam_num.0 += 1;
386                cam_num_inner
387            };
388
389            inner.ccis.insert(
390                raw_cam_name.clone(),
391                ConnectedCameraInfo {
392                    cam_num,
393                    raw_cam_name: raw_cam_name.clone(),
394                    sync_state: ConnectedCameraSyncState::Unsynchronized,
395                    http_camserver_info: http_camserver_info.clone(),
396                    frames_during_sync: 0,
397                    _camera_periodic_signal_period_usec: camera_periodic_signal_period_usec,
398                },
399            );
400            cam_num
401        };
402        info!(
403            "register_new_camera got camera name \"{}\", \
404            assigned camera number {}",
405            raw_cam_name.as_str(),
406            cam_num
407        );
408        self.notify_cam_changed_listeners();
409        Ok(())
410    }
411
412    /// Register that a new frame was received
413    ///
414    /// Returns synced frame number
415    pub fn got_new_frame_live<F>(
416        &self,
417        packet: &braid_types::FlydraRawUdpPacket,
418        sync_pulse_pause_started_arc: &Arc<RwLock<Option<SyncStart>>>,
419        send_new_frame_offset: F,
420        trigger_cfg: &TriggerType,
421    ) -> Option<SyncFno>
422    where
423        F: FnMut(u64),
424    {
425        let sync_data = match &trigger_cfg {
426            TriggerType::TriggerboxV1(_) => self.got_new_frame_live_triggerbox(
427                packet,
428                sync_pulse_pause_started_arc,
429                TRIGGERBOX_SYNC_SECONDS,
430                None,
431            ),
432            TriggerType::FakeSync(cfg) => self.got_new_frame_live_triggerbox(
433                packet,
434                sync_pulse_pause_started_arc,
435                0,
436                Some(cfg.framerate),
437            ),
438            TriggerType::PtpSync(ptpcfg) => self.got_new_frame_live_ptp(packet, ptpcfg)?,
439            TriggerType::DeviceTimestamp => {
440                todo!();
441            }
442        };
443        self.finish_got_new_frame_live(sync_data, send_new_frame_offset)
444    }
445
446    /// Register that a new frame was received if we are using the triggerbox (or fake sync).
447    ///
448    /// With a real triggerbox (`fake_sync_fps` is `None`), trigger pulses are
449    /// paused during synchronization, so the first frame each camera delivers
450    /// after the pause is unambiguously the same pulse for all cameras. With
451    /// fake sync (`fake_sync_fps` is `Some(fps)`), cameras free-run and a
452    /// frame from every camera is in flight at any moment, so anchoring on
453    /// "first packet to arrive after synchronization began" would race with
454    /// the frame clock: cameras whose in-flight packet arrived just before
455    /// the sync epoch would synchronize one frame later than the rest. To
456    /// avoid this, fake sync anchors each camera's frame numbering to the
457    /// common wall-clock epoch using the frame's acquisition timestamp.
458    fn got_new_frame_live_triggerbox(
459        &self,
460        packet: &braid_types::FlydraRawUdpPacket,
461        sync_pulse_pause_started_arc: &Arc<RwLock<Option<SyncStart>>>,
462        sync_time_min_sec: u64,
463        fake_sync_fps: Option<f64>,
464    ) -> SyncData {
465        assert!(packet.framenumber >= 0);
466
467        let sync_time_min: std::time::Duration = std::time::Duration::from_secs(sync_time_min_sec);
468        let sync_time_max = std::time::Duration::from_secs(TRIGGERBOX_SYNC_SECONDS + 2);
469
470        let raw_cam_name = RawCamName::new(packet.cam_name.clone());
471
472        let cam_frame = packet.framenumber as u64;
473        let mut synced_frame = None;
474        let mut new_frame0 = None;
475        let mut got_frame_during_sync_time = false;
476        let mut do_check_if_all_cameras_present = false;
477        {
478            let inner = self.inner.read().unwrap();
479            if let Some(cci) = inner.ccis.get(&raw_cam_name) {
480                // We know this camera already.
481                use crate::ConnectedCameraSyncState::*;
482                match cci.sync_state {
483                    Unsynchronized => {
484                        do_check_if_all_cameras_present = true;
485                        let sync_pulse_pause_started = sync_pulse_pause_started_arc.read().unwrap();
486                        if let Some(sync_start) = *sync_pulse_pause_started {
487                            let elapsed = sync_start.instant.elapsed();
488                            if sync_time_min < elapsed && elapsed < sync_time_max {
489                                match fake_sync_fps {
490                                    None => {
491                                        // Camera is not synchronized, but we are
492                                        // expecting a sync pulse. Therefore,
493                                        // synchronize the camera now.
494                                        new_frame0 =
495                                            Some(cam_frame - crate::TRIGGERBOX_FIRST_PULSE);
496
497                                        // // `synced_frame` is the first pulsenumber.
498                                        synced_frame = Some(crate::TRIGGERBOX_FIRST_PULSE);
499                                    }
500                                    Some(fps) => {
501                                        // Fake sync: anchor to the wall-clock
502                                        // epoch via the frame's acquisition
503                                        // timestamp (see method docs). Round
504                                        // the elapsed time to whole frame
505                                        // periods.
506                                        let elapsed_wall = packet.cam_received_time.as_f64()
507                                            - strand_datetime_conversion::datetime_to_f64(
508                                                &sync_start.wallclock,
509                                            );
510                                        let k = (elapsed_wall * fps).round();
511                                        let k_max = (sync_time_max.as_secs_f64() * fps).ceil();
512                                        if (0.0..=k_max).contains(&k) {
513                                            let this_synced =
514                                                k as u64 + crate::TRIGGERBOX_FIRST_PULSE;
515                                            if let Some(frame0) = cam_frame.checked_sub(this_synced)
516                                            {
517                                                new_frame0 = Some(frame0);
518                                                synced_frame = Some(this_synced);
519                                            }
520                                            // else: the camera's own frame
521                                            // counter is still too low; a later
522                                            // frame will synchronize it.
523                                        } else if k > k_max {
524                                            // The frame timestamp disagrees
525                                            // wildly with our clock (e.g. a
526                                            // remote camera with unsynchronized
527                                            // host clock). Warn and fall back to
528                                            // first-packet anchoring rather than
529                                            // never synchronizing.
530                                            tracing::warn!(
531                                                "fake sync: frame timestamp of camera \"{}\" is \
532                                                 {elapsed_wall:.3} s from the sync epoch; falling \
533                                                 back to arrival-order synchronization. Are the \
534                                                 host clocks synchronized?",
535                                                raw_cam_name.as_str()
536                                            );
537                                            new_frame0 =
538                                                Some(cam_frame - crate::TRIGGERBOX_FIRST_PULSE);
539                                            synced_frame = Some(crate::TRIGGERBOX_FIRST_PULSE);
540                                        }
541                                        // else (k < 0): frame was acquired
542                                        // before the sync epoch; the next
543                                        // frame will synchronize the camera.
544                                    }
545                                }
546                            } else if std::time::Duration::from_millis(50) < elapsed {
547                                // If we are 50 msec into the pause but we get a
548                                // frame but it hasn't get been sync_time_min,
549                                // we should complain.
550                                got_frame_during_sync_time = true;
551                            }
552                        }
553                    }
554                    Synchronized(frame0) => {
555                        if cam_frame >= frame0 {
556                            // The camera is already synchronized, return synced frame number
557                            let corrected_frame_number = cam_frame - frame0;
558
559                            // if corrected_frame_number > crate::TRIGGERBOX_FIRST_PULSE {
560                            if corrected_frame_number == u64::MAX {
561                                // We have seen a bug in which the frame number is
562                                // `u64::MAX`. This checks if this obviously wrong
563                                // frame number is introduced after the present
564                                // location or before. In any case, if we are
565                                // getting frame numbers like this, clearly we
566                                // cannot track anymore, so panicing here only
567                                // raises the issue slightly earlier.
568                                panic!(
569                                    "Impossible frame number. cam_name: {}, cam_frame: {}, frame0: {}",
570                                    raw_cam_name.as_str(),
571                                    cam_frame,
572                                    frame0,
573                                );
574                            }
575                            //     synced_frame =
576                            //         Some(corrected_frame_number - crate::TRIGGERBOX_FIRST_PULSE);
577                            // }
578                            synced_frame = Some(corrected_frame_number);
579                        }
580                    }
581                };
582            }
583            // If we do not know the camera, it is because we are starting up
584            // (or shutting down and have already removed the camera) and thus
585            // we should ignore this new data.
586        }
587
588        if got_frame_during_sync_time {
589            let frames_during_sync = {
590                // This scope is for the write lock on self.inner. Keep it minimal.
591                let mut inner = self.inner.write().unwrap();
592
593                match inner.ccis.get_mut(&raw_cam_name) {
594                    Some(cci) => {
595                        cci.frames_during_sync += 1;
596                        cci.frames_during_sync
597                    }
598                    None => {
599                        panic!("reached impossible code.");
600                    }
601                }
602            };
603
604            if frames_during_sync > 10 {
605                error!(
606                    "Many frames during sync period. Camera \"{}\" not \
607                       being externally triggered?",
608                    raw_cam_name.as_str()
609                );
610            }
611        }
612        SyncData {
613            new_frame0,
614            raw_cam_name,
615            do_check_if_all_cameras_present,
616            synced_frame,
617        }
618    }
619
620    /// Register that a new frame was received if we are using PTP
621    fn got_new_frame_live_ptp(
622        &self,
623        packet: &braid_types::FlydraRawUdpPacket,
624        ptpcfg: &PtpSyncConfig,
625    ) -> Option<SyncData> {
626        let raw_cam_name = RawCamName::new(packet.cam_name.clone());
627        let cam = raw_cam_name.as_str();
628        let my_span = tracing::span!(tracing::Level::DEBUG, "got_new_frame_live_ptp", cam);
629        let _enter = my_span.enter();
630
631        let inner = self.inner.read().unwrap();
632        if let Some(cci) = inner.ccis.get(&raw_cam_name) {
633            let camera_periodic_signal_period_usec = self
634                .periodic_signal_period_usec
635                .expect("could not get period for PTP sync");
636            if let Some(expected_period) = ptpcfg.periodic_signal_period_usec
637                && approx::relative_ne!(expected_period, camera_periodic_signal_period_usec)
638            {
639                panic!("camera period not set to expected period");
640            }
641            let device_timestamp = PtpStamp::new(
642                packet
643                    .device_timestamp
644                    .expect("could not get device_timestamp for frame"),
645            );
646
647            let device_timestamp_utc: chrono::DateTime<chrono::Utc> =
648                device_timestamp.clone().try_into().unwrap();
649            let device_timestamp_local: chrono::DateTime<chrono::Local> =
650                device_timestamp_utc.into();
651            tracing::trace!("{cam}: device_timestamp_local: {device_timestamp_local}");
652
653            let elapsed_since_launch = if let Some(dur) =
654                device_timestamp.duration_since(&self.launch_time_ptp)
655            {
656                dur
657            } else {
658                tracing::warn!(
659                    "Launch time precedes device timestamp {raw_cam_name}. Is time running backwards?"
660                );
661                // This would happen if time runs backwards. I have not
662                // seen this scenario, but it shouldn't cause a panic.
663                return None;
664            };
665
666            let camera_periodic_signal_period_nsec = camera_periodic_signal_period_usec * 1000.0;
667            let n_periods =
668                elapsed_since_launch.nanos() as f64 / camera_periodic_signal_period_nsec;
669            let raw_fno = n_periods.round() as u64;
670            let device_timestamp_value = device_timestamp.get();
671            tracing::trace!(device_timestamp_value, n_periods, raw_fno);
672            tracing::trace!(
673                "packet.block_id: {:?}, packet.framenumber: {:?}, launch_time_ptp: {:?}",
674                packet.block_id,
675                packet.framenumber,
676                self.launch_time_ptp
677            );
678            tracing::trace!(
679                "elapsed_since_launch: {elapsed_since_launch:?}, camera_periodic_signal_period_nsec: {camera_periodic_signal_period_nsec}, raw_fno: {raw_fno}"
680            );
681
682            let mut do_check_if_all_cameras_present = false;
683
684            let synced_frame = Some(raw_fno);
685            let mut new_frame0 = None;
686            use crate::ConnectedCameraSyncState::*;
687            match &cci.sync_state {
688                Unsynchronized => {
689                    new_frame0 = Some(0);
690                    do_check_if_all_cameras_present = true;
691                }
692                Synchronized(_frame0) => {}
693            }
694
695            Some(SyncData {
696                new_frame0,
697                raw_cam_name,
698                do_check_if_all_cameras_present,
699                synced_frame,
700            })
701        } else {
702            // Camera starting up (or shutting down). Ignore this frame.)
703            None
704        }
705    }
706
707    fn finish_got_new_frame_live<F>(
708        &self,
709        sync_data: SyncData,
710        mut send_new_frame_offset: F,
711    ) -> Option<SyncFno>
712    where
713        F: FnMut(u64),
714    {
715        let SyncData {
716            new_frame0,
717            raw_cam_name,
718            do_check_if_all_cameras_present,
719            synced_frame,
720        } = sync_data;
721        let mut do_check_if_all_cameras_synchronized = false;
722        if let Some(frame0) = new_frame0 {
723            // Perform the book-keeping associated with synchronization.
724            {
725                // This scope is for the write lock on self.inner. Keep it minimal.
726                let mut inner = self.inner.write().unwrap();
727                match inner.ccis.get_mut(&raw_cam_name) {
728                    Some(cci) => {
729                        cci.sync_state = ConnectedCameraSyncState::Synchronized(frame0);
730                    }
731                    None => {
732                        panic!("reached impossible code.");
733                    }
734                }
735            }
736
737            self.notify_cam_changed_listeners();
738
739            // Do notifications associated with synchronization.
740            send_new_frame_offset(frame0);
741            info!(
742                "cam \"{}\" synchronized with frame offset: {}",
743                raw_cam_name.as_str(),
744                frame0,
745            );
746            do_check_if_all_cameras_synchronized = true;
747        }
748
749        if do_check_if_all_cameras_present
750            && !self.inner.read().unwrap().all_expected_cameras_are_present
751        {
752            let mut inner = self.inner.write().unwrap();
753            let i2: &mut ConnectedCamerasManagerInner = &mut inner;
754            if i2.first_frame_arrived.insert(raw_cam_name.clone()) {
755                info!(
756                    "first frame from camera \"{}\" arrived.",
757                    raw_cam_name.as_str()
758                );
759                if i2.first_frame_arrived == i2.all_expected_cameras {
760                    inner.all_expected_cameras_are_present = true;
761                    self.signal_all_cams_present.store(true, Ordering::SeqCst);
762                    info!("All expected cameras connected.");
763                } else {
764                    info!("All expected cameras NOT connected.");
765                }
766            }
767        }
768
769        if do_check_if_all_cameras_synchronized
770            && !self.inner.read().unwrap().all_expected_cameras_are_synced
771        {
772            let mut inner = self.inner.write().unwrap();
773            let i2: &mut ConnectedCamerasManagerInner = &mut inner;
774            // if i2.first_frame_arrived.insert(raw_cam_name.clone()) {
775            //     info!("first frame from camera {} arrived.", raw_cam_name);
776            let mut all_synced = true;
777            for raw_cam_name in i2.all_expected_cameras.iter() {
778                let this_sync = i2
779                    .ccis
780                    .get(raw_cam_name)
781                    .map(|cci| cci.sync_state.is_synchronized())
782                    .unwrap_or(false);
783                if !this_sync {
784                    all_synced = false;
785                    break;
786                }
787            }
788
789            if all_synced {
790                info!("All expected cameras synchronized.");
791                self.signal_all_cams_synced.store(true, Ordering::SeqCst);
792            } else {
793                info!("All expected cameras NOT synchronized.");
794            }
795        }
796
797        synced_frame.map(SyncFno)
798    }
799
800    pub fn get_raw_cam_name(&self, cam_num: CamNum) -> Option<RawCamName> {
801        for cci in self.inner.read().unwrap().ccis.values() {
802            if cci.cam_num == cam_num {
803                return Some(cci.raw_cam_name.clone());
804            }
805        }
806        None
807    }
808
809    pub fn all_raw_cam_names(&self) -> Vec<RawCamName> {
810        self.inner
811            .read()
812            .unwrap()
813            .ccis
814            .values()
815            .map(|cci| cci.raw_cam_name.clone())
816            .collect()
817    }
818
819    pub fn http_camserver_info(&self, raw_cam_name: &RawCamName) -> Option<BuiServerInfo> {
820        self.inner
821            .read()
822            .unwrap()
823            .ccis
824            .get(raw_cam_name)
825            .map(|cci| cci.http_camserver_info.clone())
826    }
827
828    pub fn cam_num(&self, raw_cam_name: &RawCamName) -> Option<CamNum> {
829        let inner = self.inner.read().unwrap();
830        match inner.ccis.get(raw_cam_name) {
831            Some(cci) => Some(cci.cam_num),
832            None => inner.not_yet_connected.get(raw_cam_name).copied(),
833        }
834    }
835
836    pub(crate) fn sample(&self) -> Vec<CamInfoRow> {
837        self.inner
838            .read()
839            .unwrap()
840            .ccis
841            .values()
842            .map(|cci| cci.copy_to_caminfo())
843            .collect()
844    }
845
846    pub fn len(&self) -> usize {
847        self.inner.read().unwrap().ccis.len()
848    }
849
850    pub fn is_empty(&self) -> bool {
851        self.inner.read().unwrap().ccis.is_empty()
852    }
853}
854
855impl std::fmt::Debug for ConnectedCamerasManager {
856    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
857        f.debug_struct("ConnectedCamerasManager")
858            .finish_non_exhaustive()
859    }
860}
861
862#[derive(Debug)]
863struct SyncData {
864    new_frame0: Option<u64>,
865    raw_cam_name: RawCamName,
866    do_check_if_all_cameras_present: bool,
867    synced_frame: Option<u64>,
868}
869
870#[test]
871fn test_camera_list() {
872    let c1 = CameraList::new(&[1, 2, 3, 4]);
873    let c2 = CameraList::new(&[4, 3, 2, 1]);
874    assert_eq!(c1, c2);
875
876    let c1 = CameraList::new(&[1, 2, 3, 4]);
877    let c2 = CameraList::new(&[4, 3, 2]);
878    assert!(c1 != c2);
879
880    let c1 = CameraList::new(&[1, 2, 3, 4]);
881    let c2 = CameraList::new(&[4, 3, 2, 5]);
882    assert!(c1 != c2);
883}