Skip to main content

braid_run/
main.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use clap::Parser;
5use eyre::{self, Result, WrapErr};
6use tracing::debug;
7
8use braid::braid_start;
9use braid_config_data::parse_config_file;
10use braid_types::{BraidCameraConfig, RawCamName, StartCameraBackend, TriggerType};
11use strand_bui_backend_session_types::BuiServerAddrInfo;
12
13mod callback_handling;
14mod mainbrain;
15mod multicam_http_session_handler;
16
17#[derive(Debug, Parser)]
18#[command(author, version, about)]
19struct BraidRunCliArgs {
20    /// Input directory
21    config_file: std::path::PathBuf,
22    /// Flag if logging to console should be disabled.
23    #[arg(short, long)]
24    disable_console: bool,
25}
26
27fn compute_strand_cam_args(
28    camera: &BraidCameraConfig,
29    mainbrain_internal_addr: &BuiServerAddrInfo,
30) -> Result<Vec<String>> {
31    let urls = strand_bui_backend_session::build_urls(mainbrain_internal_addr)?;
32    let url = urls
33        .first()
34        .ok_or_else(|| eyre::eyre!("need at least one URL"))?;
35    let url_string = format!("{url}");
36    let mut args = vec![
37        "--camera-name".into(),
38        camera.name.clone(),
39        "--braid-url".into(),
40        url_string,
41    ];
42    // Tell the merged `strand-cam` executable which backend to load. This is
43    // `None` for remote cameras, whose backend is chosen on the remote machine.
44    if let Some(backend) = camera.start_backend.camera_backend_arg() {
45        args.push("--camera-backend".into());
46        args.push(backend.into());
47    }
48    Ok(args)
49}
50
51fn launch_strand_cam(
52    strand_cam_set: &mut tokio::task::JoinSet<()>,
53    camera: &BraidCameraConfig,
54    mainbrain_internal_addr: &BuiServerAddrInfo,
55) -> Result<()> {
56    // On initial startup strand cam queries for
57    // [braid_types::RemoteCameraInfoResponse] and thus we do not need to
58    // provide much info.
59
60    let braid_run_exe = std::env::current_exe().unwrap();
61    let exe_dir = braid_run_exe
62        .parent()
63        .expect("Executable must be in some directory");
64    #[cfg(target_os = "windows")]
65    let ext = ".exe";
66    #[cfg(not(target_os = "windows"))]
67    let ext = "";
68    let exe = exe_dir.join(format!(
69        "{}{}",
70        camera.start_backend.strand_cam_exe_name().unwrap(),
71        ext
72    ));
73    debug!("strand cam executable name: \"{}\"", exe.display());
74
75    let cam_name = camera.name.clone();
76
77    let mut exec = std::process::Command::new(&exe);
78    let args = compute_strand_cam_args(camera, mainbrain_internal_addr)?;
79    exec.args(&args);
80    debug!("exec: {:?}", exec);
81    let mut obj = exec.spawn().context(format!(
82        "Starting Strand Cam executable \"{}\"",
83        exe.display()
84    ))?;
85
86    let _abort_handle = strand_cam_set.spawn_blocking(move || {
87        let exit_code = obj.wait().unwrap();
88        if !exit_code.success() {
89            tracing::error!(
90                "Strand Cam executable for {cam_name} exited with exit code {:?}",
91                exit_code.code()
92            );
93        } else {
94            debug!("Strand Cam executable done.");
95        }
96    });
97    Ok(())
98}
99
100#[tokio::main]
101async fn main() -> Result<()> {
102    std::panic::set_hook(Box::new(tracing_panic::panic_hook));
103    braid_start("run")?;
104
105    let args = BraidRunCliArgs::parse();
106    let cfg = parse_config_file(&args.config_file).with_context(|| {
107        format!(
108            "when parsing configuration file {}",
109            args.config_file.display()
110        )
111    })?;
112
113    let log_file_name = chrono::Local::now()
114        .format("~/.braid-%Y%m%d_%H%M%S.%f.log")
115        .to_string();
116
117    let log_file_name = std::path::PathBuf::from(shellexpand::full(&log_file_name)?.to_string());
118    // TODO: delete log files older than, e.g. one week.
119
120    let _guard = env_tracing_logger::initiate_logging(Some(&log_file_name), args.disable_console)
121        .map_err(|e| eyre::eyre!("error initiating logging: {e}"))?;
122
123    let version = format!("{} (git {})", env!("CARGO_PKG_VERSION"), env!("GIT_HASH"));
124    tracing::info!("{} {}", "run", version);
125    tracing::debug!("{:?}", cfg);
126
127    let camera_configs = cfg
128        .cameras
129        .iter()
130        .map(|cfg| {
131            let raw_cam_name = RawCamName::new(cfg.name.to_string());
132            (raw_cam_name, cfg.clone())
133        })
134        .collect();
135
136    let trig_cfg = cfg.trigger;
137
138    let (force_camera_sync_mode, software_limit_framerate) = match &trig_cfg {
139        TriggerType::TriggerboxV1(_) => (true, braid_types::StartSoftwareFrameRateLimit::NoChange),
140        TriggerType::FakeSync(cfg) => (
141            false,
142            braid_types::StartSoftwareFrameRateLimit::Enable(cfg.framerate),
143        ),
144        TriggerType::PtpSync(_) | TriggerType::DeviceTimestamp => {
145            (false, braid_types::StartSoftwareFrameRateLimit::NoChange)
146        }
147    };
148    let show_tracking_params = false;
149
150    // let handle = runtime.handle().clone();
151    let all_expected_cameras = cfg
152        .cameras
153        .iter()
154        .map(|x| RawCamName::new(x.name.clone()))
155        .collect();
156
157    let address_string: String = cfg.mainbrain.http_api_server_addr.clone();
158    // Load the persistent secret once: it both mints Braid's self-expiring
159    // access token in `start_listener` and validates it in the auth layer.
160    let persistent_secret = mainbrain::load_persistent_secret(cfg.mainbrain.secret_base64.clone())?;
161    let (listener, mainbrain_server_info) =
162        braid_types::start_listener(&address_string, &persistent_secret).await?;
163    let mainbrain_internal_addr = mainbrain_server_info.clone();
164
165    let cfg_cameras = cfg.cameras;
166    let mut strand_cam_set = tokio::task::JoinSet::new();
167    for camera in cfg_cameras.into_iter() {
168        if camera.start_backend != StartCameraBackend::Remote {
169            launch_strand_cam(&mut strand_cam_set, &camera, &mainbrain_internal_addr)?;
170        } else {
171            tracing::info!(
172                "Not starting remote camera \"{}\". Use args: {}",
173                camera.name,
174                compute_strand_cam_args(&camera, &mainbrain_internal_addr)
175                    .unwrap()
176                    .join(" ")
177            );
178            // Insert dummy future that never completes so that the JoinSet does
179            // not complete.
180            strand_cam_set.spawn(std::future::pending());
181        }
182    }
183
184    debug!("done launching cameras");
185
186    // This runs the whole thing and "blocks". Now wait for everything to end.
187    mainbrain::do_run_forever(
188        show_tracking_params,
189        // Raising the mainbrain thread priority is currently disabled.
190        // cfg.mainbrain.sched_policy_priority,
191        camera_configs,
192        trig_cfg,
193        cfg.mainbrain,
194        persistent_secret,
195        all_expected_cameras,
196        force_camera_sync_mode,
197        software_limit_framerate.clone(),
198        "braid",
199        listener,
200        mainbrain_server_info,
201        strand_cam_set,
202    )
203    .await?;
204
205    debug!("done {}:{}", file!(), line!());
206
207    Ok(())
208}