braid_config_data/lib.rs
1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! type definitions for Braid configuration
5use serde::{Deserialize, Serialize};
6
7use braid_types::{BraidCameraConfig, FakeSyncConfig, TriggerType, TriggerboxConfig};
8
9/// The Braid configuration error type.
10#[derive(thiserror::Error, Debug)]
11pub enum Error {
12 #[error("lookup error on variable: {source}")]
13 ShellExpandLookupVarError {
14 #[from]
15 source: shellexpand::LookupError<std::env::VarError>,
16 },
17 #[error("IO error: {source}")]
18 IoError {
19 #[from]
20 source: std::io::Error,
21 },
22 #[error("TOML deserialization error: {source}")]
23 TomlDeError {
24 #[from]
25 source: toml::de::Error,
26 },
27}
28
29type Result<T> = std::result::Result<T, Error>;
30
31/// The default value for [MainbrainConfig::http_api_server_addr].
32pub const DEFAULT_HTTP_API_SERVER_ADDR: &str = "127.0.0.1:0";
33
34fn default_http_api_server_addr() -> String {
35 DEFAULT_HTTP_API_SERVER_ADDR.to_string()
36}
37
38/// The default value for [MainbrainConfig::output_base_dirname].
39pub const DEFAULT_OUTPUT_BASE_DIRNAME: &str = "~/BRAID-DATA";
40
41fn default_output_base_dirname() -> std::path::PathBuf {
42 DEFAULT_OUTPUT_BASE_DIRNAME.into()
43}
44
45fn default_model_server_addr() -> std::net::SocketAddr {
46 braid_types::DEFAULT_MODEL_SERVER_ADDR.parse().unwrap()
47}
48
49fn default_true() -> bool {
50 true
51}
52
53/// Split `path` (which must be a file) into directory and filename component.
54fn split_path<P: AsRef<std::path::Path>>(path: P) -> (std::path::PathBuf, std::path::PathBuf) {
55 let path = path.as_ref();
56 assert!(path.is_file());
57 let mut components = path.components();
58 let filename = components.next_back().unwrap().as_os_str().into();
59 let dirname = components.as_path().into();
60 (dirname, filename)
61}
62
63/// If `path` is relative, make it relative to `dirname`.
64///
65/// `path` must be utf-8 encoded and can start with a tilde, which is expanded
66/// to the home directory.
67fn fixup_relative_path(path: &mut std::path::PathBuf, dirname: &std::path::Path) -> Result<()> {
68 let pathstr = path.as_os_str().to_str().unwrap();
69 let expanded = shellexpand::full(&pathstr)?;
70 *path = std::path::PathBuf::from(expanded.to_string());
71
72 if path.is_relative() {
73 *path = dirname.join(&path);
74 }
75 Ok(())
76}
77
78/// The sub-configuration of [BraidConfig] for `mainbrain` - the central
79/// component of Braid that integrates information from multiple cameras and
80/// performs tracking.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[serde(deny_unknown_fields)]
83pub struct MainbrainConfig {
84 /// Filename of the camera calibration, optional.
85 ///
86 /// Can contain shell variables such as `~`, `$A`, or `${B}`.
87 ///
88 /// If the filename ends with .pymvg or .json, it will be treated as a pymvg
89 /// calibration file. Else it will be treated considered in the flydra XML
90 /// calibration format.
91 pub cal_fname: Option<std::path::PathBuf>,
92 /// Directory where data should be saved. Can contain shell variables.
93 /// Defaults to [DEFAULT_OUTPUT_BASE_DIRNAME].
94 #[serde(default = "default_output_base_dirname")]
95 pub output_base_dirname: std::path::PathBuf,
96 /// Parameters for Kalman filter and data association
97 #[serde(default = "braid_types::default_tracking_params_full_3d")]
98 pub tracking_params: braid_types::TrackingParams,
99 // Raising the mainbrain thread priority is currently disabled.
100 // /// Parameters to potentially raise the mainbrain thread priority.
101 // sched_policy_priority: Option<(i32, i32)>,
102 /// Address of UDP port to send low-latency detection data
103 pub lowlatency_camdata_udp_addr: Option<String>,
104 #[serde(default)]
105 pub lowlatency_camdata_udp_port: u16,
106 /// Address of HTTP port for control API. This is specified in the format
107 /// `IP:PORT` where:
108 ///
109 /// `IP` can be:
110 /// - a numerical IPv4 address:
111 /// - e.g. `1.1.1.1` uses the specific IP
112 /// - `127.0.0.1` for the loopback interface
113 /// - `0.0.0.0` to open the server on all available IPv4 interfaces
114 /// - a numerical IPv6 address:
115 /// - e.g. `[2001:db8:3333:4444:5555:6666:7777:8888]` uses the specific
116 /// IP
117 /// - `[::1]` for the loopback interface
118 /// - `[::]` to open the server on all available IPv6 interfaces
119 /// - a hostname which resolves to an IP address (depending on your DNS
120 /// configuration, resolves to either IPv4 or IPv6):
121 /// - `localhost` resolves to the IP address of the loopback interface
122 /// - e.g. `hostname` for a specific IP address
123 ///
124 /// `PORT` can be:
125 /// - `0` allows the operating system to choose an unassigned port
126 /// dynamically
127 /// - e.g. `1234` uses the specific port
128 ///
129 /// Set to `0.0.0.0:0` to be automatically assigned a public IP address with
130 /// a dynamically assigned port.
131 ///
132 /// Both IPv4 and IPv6 addresses are accepted (see the format details
133 /// above). Giving a non-localhost IP address makes Braid available remotely
134 /// on the network. Remote clients must present an access token to connect.
135 /// Using the unspecified IP address (`0.0.0.0` for IPv4 or `[::]` for IPv6)
136 /// exposes the server on all network interfaces. Using port `0` lets the
137 /// operating system pick a free port.
138 ///
139 /// The default value is set to [DEFAULT_HTTP_API_SERVER_ADDR].
140 #[serde(default = "default_http_api_server_addr")]
141 pub http_api_server_addr: String,
142 /// Address of HTTP port for model server emitting realtime tracking results
143 #[serde(default = "default_model_server_addr")]
144 pub model_server_addr: std::net::SocketAddr,
145 /// Save rows to data2d_distorted where nothing detected (saves timestamps)
146 #[serde(default = "default_true")]
147 pub save_empty_data2d: bool,
148 /// Secret to use for signing HTTP cookies (base64 encoded)
149 pub secret_base64: Option<String>,
150 /// Client networks (CIDR notation, e.g. `100.64.0.0/10` for Tailscale or a
151 /// WireGuard subnet) that are trusted to have already authenticated the
152 /// peer. Requests from these networks are accepted without an access token,
153 /// delegating authentication to the overlay network. Defaults to empty.
154 #[serde(default)]
155 pub trusted_networks: Vec<String>,
156 /// Threshold duration before logging error (msec).
157 ///
158 /// If the image acquisition timestamp precedes the computed trigger
159 /// timestamp, clearly an error has happened. This error must lie in the
160 /// computation of the trigger timestamp. This specifies the threshold error
161 /// at which an error is logged. (The underlying source of such errors
162 /// remains unknown.)
163 pub acquisition_duration_allowed_imprecision_msec: Option<f64>,
164 /// The size of the buffer, in number of messages, used by the channel for
165 /// sending data to disk.
166 #[serde(default = "default_write_buffer_size_num_messages")]
167 pub write_buffer_size_num_messages: usize,
168}
169
170impl std::default::Default for MainbrainConfig {
171 fn default() -> Self {
172 Self {
173 cal_fname: None,
174 output_base_dirname: default_output_base_dirname(),
175 tracking_params: braid_types::default_tracking_params_full_3d(),
176 // Raising the mainbrain thread priority is currently disabled.
177 // sched_policy_priority: None,
178 lowlatency_camdata_udp_addr: None,
179 lowlatency_camdata_udp_port: Default::default(),
180 http_api_server_addr: default_http_api_server_addr(),
181 model_server_addr: default_model_server_addr(),
182 save_empty_data2d: true,
183 secret_base64: None,
184 trusted_networks: Vec::new(),
185 acquisition_duration_allowed_imprecision_msec:
186 braid_types::DEFAULT_ACQUISITION_DURATION_ALLOWED_IMPRECISION_MSEC,
187 write_buffer_size_num_messages: default_write_buffer_size_num_messages(),
188 }
189 }
190}
191
192pub const fn default_write_buffer_size_num_messages() -> usize {
193 10000
194}
195
196/// The Braid configuration format used in [the Braid configuration `TOML`
197/// file](https://strawlab.github.io/strand-braid/braid_configuration_and_launching.html).
198///
199/// This is the new configuration format. Backwards compatibility is maintained
200/// by first attempting to deserialize with this definition.
201///
202/// See the types of each field for sub-configuration values.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(deny_unknown_fields)]
205pub struct BraidConfig {
206 /// Mainbrain configuration
207 #[serde(default = "MainbrainConfig::default")]
208 pub mainbrain: MainbrainConfig,
209 /// Triggerbox configuration.
210 #[serde(default)]
211 pub trigger: TriggerType,
212 pub cameras: Vec<BraidCameraConfig>,
213}
214
215impl From<BraidConfig1> for BraidConfig {
216 fn from(orig: BraidConfig1) -> BraidConfig {
217 let trigger = match orig.trigger {
218 None => TriggerType::FakeSync(FakeSyncConfig::default()),
219 Some(x) => TriggerType::TriggerboxV1(x),
220 };
221 BraidConfig {
222 mainbrain: orig.mainbrain,
223 trigger,
224 cameras: orig.cameras,
225 }
226 }
227}
228
229/// The old configuration format.
230#[derive(Debug, Clone, Serialize, Deserialize)]
231#[serde(deny_unknown_fields)]
232#[doc(hidden)]
233pub struct BraidConfig1 {
234 pub mainbrain: MainbrainConfig,
235 pub trigger: Option<TriggerboxConfig>,
236 pub cameras: Vec<BraidCameraConfig>,
237}
238
239impl BraidConfig {
240 /// For all paths which are relative, make them relative to the
241 /// config file location.
242 fn fixup_relative_paths(&mut self, orig_path: &std::path::Path) -> Result<()> {
243 let (dirname, _orig_path) = split_path(orig_path);
244
245 // fixup self.mainbrain.cal_fname
246 if let Some(cal_fname) = self.mainbrain.cal_fname.as_mut() {
247 fixup_relative_path(cal_fname, &dirname)?;
248 }
249
250 // fixup self.mainbrain.output_base_dirname
251 fixup_relative_path(&mut self.mainbrain.output_base_dirname, &dirname)?;
252
253 // fixup self.cameras.camera_settings_filename
254 for camera_config in self.cameras.iter_mut() {
255 if let Some(ref mut camera_settings_filename) =
256 camera_config.camera_settings_filename.as_mut()
257 {
258 fixup_relative_path(camera_settings_filename, &dirname)?;
259 }
260 }
261
262 Ok(())
263 }
264}
265
266impl std::default::Default for BraidConfig {
267 fn default() -> Self {
268 Self {
269 mainbrain: MainbrainConfig::default(),
270 // This `trigger` field has a different default than
271 // TriggerType::default() in order to show the user (who will query
272 // this with the `braid default-config` command) how to configure
273 // the trigger box.
274 trigger: TriggerType::TriggerboxV1(TriggerboxConfig::default()),
275 cameras: vec![
276 BraidCameraConfig::default_absdiff_config("fake-camera-1".to_string()),
277 BraidCameraConfig::default_absdiff_config("fake-camera-2".to_string()),
278 BraidCameraConfig::default_absdiff_config("fake-camera-3".to_string()),
279 ],
280 }
281 }
282}
283
284/// Parse a `.toml` file and return a [BraidConfig] structure.
285pub fn parse_config_file<P: AsRef<std::path::Path>>(fname: P) -> Result<BraidConfig> {
286 use std::io::Read;
287
288 let mut file = std::fs::File::open(fname.as_ref())?;
289 let mut contents = String::new();
290 file.read_to_string(&mut contents)?;
291 let mut cfg: BraidConfig = match toml::from_str(&contents) {
292 Ok(cfg) => cfg,
293 Err(err_cfg2) => {
294 let cfg1: BraidConfig1 = match toml::from_str(&contents) {
295 Ok(cfg1) => cfg1,
296 Err(err_cfg1) => {
297 tracing::error!(
298 "parsing config file first as BraidConfig failed \
299 and then again as BraidConfig1 failed. The parse error for \
300 BraidConfig1 is: {}\n The original error when parsing \
301 BraidConfig will now be raised.",
302 err_cfg1
303 );
304 return Err(err_cfg2.into());
305 }
306 };
307 BraidConfig::from(cfg1)
308 }
309 };
310 // let mut cfg: BraidConfig = toml::from_str(&contents)?;
311 cfg.fixup_relative_paths(fname.as_ref())?;
312 Ok(cfg)
313}