Skip to main content

braid_run/
callback_handling.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use axum::response::IntoResponse;
5use tracing::{debug, error, warn};
6
7use braid_types::{BraidHttpApiCallback, PerCamSaveData, RawCamName};
8use event_stream_types::TolerantJson;
9use http::StatusCode;
10use strand_cam_bui_types::RecordingPath;
11
12use crate::mainbrain::*;
13
14/// Mirror the camera's image dimensions into the shared state shown in the
15/// browser UI, which uses them to size the camera preview.
16fn update_camera_image_dimensions(
17    app_state: &BraidAppState,
18    raw_cam_name: &RawCamName,
19    current_image_png: &braid_types::PngImageData,
20) {
21    let Some(dimensions) = current_image_png.dimensions() else {
22        warn!(
23            "could not determine image dimensions for camera \"{}\"",
24            raw_cam_name.as_str()
25        );
26        return;
27    };
28    let mut tracker = app_state.shared_store.write().unwrap();
29    if (*tracker)
30        .as_ref()
31        .camera_image_dimensions
32        .get(raw_cam_name)
33        != Some(&dimensions)
34    {
35        tracker.modify(|store| {
36            store
37                .camera_image_dimensions
38                .insert(raw_cam_name.clone(), dimensions);
39        });
40    }
41}
42
43fn start_saving_mp4s_all_cams(app_state: &BraidAppState, start_saving: bool) {
44    let mut tracker = app_state.shared_store.write().unwrap();
45    tracker.modify(|store| {
46        if start_saving {
47            store.fake_mp4_recording_path = Some(RecordingPath::new("".to_string()));
48        } else {
49            store.fake_mp4_recording_path = None;
50        }
51    });
52}
53
54pub(crate) async fn callback_handler(
55    axum::extract::State(app_state): axum::extract::State<crate::mainbrain::BraidAppState>,
56    session_key: axum_token_auth::SessionKey,
57    TolerantJson(payload): TolerantJson<BraidHttpApiCallback>,
58) -> impl IntoResponse {
59    session_key.is_present();
60    let fut = async {
61        use BraidHttpApiCallback::*;
62        match payload {
63            NewCamera(cam_info) => {
64                debug!("got NewCamera {:?}", cam_info.raw_cam_name.as_str());
65                let http_camserver_info = cam_info.http_camserver_info.unwrap();
66                let cam_settings_data = cam_info.cam_settings_data.unwrap();
67                let camera_periodic_signal_period_usec =
68                    cam_info.camera_periodic_signal_period_usec;
69                let mut cam_manager3 = app_state.cam_manager.clone();
70                cam_manager3
71                    .register_new_camera(
72                        &cam_info.raw_cam_name,
73                        &http_camserver_info,
74                        camera_periodic_signal_period_usec,
75                    )
76                    .map_err(|msg| (StatusCode::BAD_REQUEST, msg))?;
77
78                update_camera_image_dimensions(
79                    &app_state,
80                    &cam_info.raw_cam_name,
81                    &cam_info.current_image_png,
82                );
83
84                let mut current_cam_data = app_state.per_cam_data_arc.write().unwrap();
85                if current_cam_data
86                    .insert(
87                        cam_info.raw_cam_name.clone(),
88                        PerCamSaveData {
89                            cam_settings_data: Some(cam_settings_data),
90                            feature_detect_settings: None,
91                            current_image_png: cam_info.current_image_png,
92                        },
93                    )
94                    .is_some()
95                {
96                    panic!("camera {} already known", cam_info.raw_cam_name.as_str());
97                }
98            }
99            UpdateCurrentImage(image_info) => {
100                // new image from camera
101                debug!(
102                    "got new image for camera \"{}\"",
103                    image_info.raw_cam_name.as_str()
104                );
105                update_camera_image_dimensions(
106                    &app_state,
107                    &image_info.raw_cam_name,
108                    &image_info.inner.current_image_png,
109                );
110                let mut current_cam_data = app_state.per_cam_data_arc.write().unwrap();
111                current_cam_data
112                    .get_mut(&image_info.raw_cam_name)
113                    .unwrap()
114                    .current_image_png = image_info.inner.current_image_png;
115            }
116            UpdateCamSettings(cam_settings) => {
117                let mut current_cam_data = app_state.per_cam_data_arc.write().unwrap();
118                current_cam_data
119                    .get_mut(&cam_settings.raw_cam_name)
120                    .unwrap()
121                    .cam_settings_data = Some(cam_settings.inner);
122            }
123            UpdateFeatureDetectSettings(feature_detect_settings) => {
124                let raw_cam_name = feature_detect_settings.raw_cam_name.clone();
125                let do_update = feature_detect_settings
126                    .inner
127                    .current_feature_detect_settings
128                    .do_update_background_model;
129                {
130                    let mut current_cam_data = app_state.per_cam_data_arc.write().unwrap();
131                    current_cam_data
132                        .get_mut(&raw_cam_name)
133                        .unwrap()
134                        .feature_detect_settings = Some(feature_detect_settings.inner);
135                }
136                // Mirror the per-camera background updating state into the
137                // shared state shown in the browser UI.
138                let mut tracker = app_state.shared_store.write().unwrap();
139                if (*tracker)
140                    .as_ref()
141                    .background_model_updating
142                    .get(&raw_cam_name)
143                    != Some(&do_update)
144                {
145                    tracker.modify(|store| {
146                        store
147                            .background_model_updating
148                            .insert(raw_cam_name, do_update);
149                    });
150                }
151            }
152            DoRecordCsvTables(value) => {
153                debug!("got DoRecordCsvTables({})", value);
154                toggle_saving_csv_tables(
155                    value,
156                    app_state.expected_framerate_arc.clone(),
157                    app_state.output_base_dirname.clone(),
158                    app_state.braidz_write_tx_weak.clone(),
159                    app_state.per_cam_data_arc.clone(),
160                    app_state.shared_store.clone(),
161                )
162                .await;
163            }
164            DoRecordMp4Files(start_saving) => {
165                debug!("got DoRecordMp4Files({start_saving})");
166
167                app_state
168                    .strand_cam_http_session_handler
169                    .toggle_saving_mp4_files_all(start_saving)
170                    .await
171                    .map_err(|_e| {
172                        (
173                            StatusCode::INTERNAL_SERVER_ERROR,
174                            "toggle_saving_mp4_files_all failed",
175                        )
176                    })?;
177
178                start_saving_mp4s_all_cams(&app_state, start_saving);
179            }
180            SetExperimentUuid(value) => {
181                debug!("got SetExperimentUuid({})", value);
182                if let Some(braidz_write_tx) = app_state.braidz_write_tx_weak.upgrade() {
183                    // `braidz_write_tx` will be dropped after this scope.
184                    braidz_write_tx
185                        .send(flydra2::SaveToDiskMsg::SetExperimentUuid(value))
186                        .await
187                        .unwrap();
188                }
189            }
190            SetPostTriggerBufferSize(val) => {
191                debug!("got SetPostTriggerBufferSize({val})");
192
193                app_state
194                    .strand_cam_http_session_handler
195                    .set_post_trigger_buffer_all(val)
196                    .await
197                    .map_err(|_e| {
198                        (
199                            StatusCode::INTERNAL_SERVER_ERROR,
200                            "set_post_trigger_buffer_all failed",
201                        )
202                    })?;
203
204                {
205                    let mut tracker = app_state.shared_store.write().unwrap();
206                    tracker.modify(|store| {
207                        store.post_trigger_buffer_size = val;
208                    });
209                }
210            }
211            PostTriggerMp4Recording => {
212                debug!("got PostTriggerMp4Recording");
213
214                let is_saving = {
215                    let tracker = app_state.shared_store.read().unwrap();
216                    (*tracker).as_ref().fake_mp4_recording_path.is_some()
217                };
218
219                if !is_saving {
220                    app_state
221                        .strand_cam_http_session_handler
222                        .initiate_post_trigger_mp4_all()
223                        .await
224                        .map_err(|_e| {
225                            (
226                                StatusCode::INTERNAL_SERVER_ERROR,
227                                "initiate_post_trigger_mp4_all failed",
228                            )
229                        })?;
230
231                    start_saving_mp4s_all_cams(&app_state, true);
232                } else {
233                    debug!("Already saving, not initiating again.");
234                }
235            }
236            DoTakeNewBackgroundImage => {
237                debug!("got DoTakeNewBackgroundImage");
238                app_state
239                    .strand_cam_http_session_handler
240                    .take_new_background_all()
241                    .await
242                    .map_err(|_e| {
243                        (
244                            StatusCode::INTERNAL_SERVER_ERROR,
245                            "take_new_background_all failed",
246                        )
247                    })?;
248            }
249            SetBackgroundUpdating(value) => {
250                debug!("got SetBackgroundUpdating({value})");
251                // Build the updated per-camera configuration from the most
252                // recently received feature detection settings of each camera.
253                // (Collected first so no lock is held across `await`.)
254                let per_cam_cfgs: Vec<(RawCamName, String)> = {
255                    let current_cam_data = app_state.per_cam_data_arc.read().unwrap();
256                    current_cam_data
257                        .iter()
258                        .filter_map(|(cam_name, cam_data)| {
259                            match &cam_data.feature_detect_settings {
260                                Some(settings) => {
261                                    let mut cfg = settings.current_feature_detect_settings.clone();
262                                    cfg.do_update_background_model = value;
263                                    match serde_yaml::to_string(&cfg) {
264                                        Ok(yaml) => Some((cam_name.clone(), yaml)),
265                                        Err(e) => {
266                                            error!(
267                                                "serializing object detection config for \
268                                                \"{}\": {e}",
269                                                cam_name.as_str()
270                                            );
271                                            None
272                                        }
273                                    }
274                                }
275                                None => {
276                                    warn!(
277                                        "not setting background updating for camera \"{}\": \
278                                        no feature detection settings received yet",
279                                        cam_name.as_str()
280                                    );
281                                    None
282                                }
283                            }
284                        })
285                        .collect()
286                };
287                for (cam_name, cfg_yaml) in per_cam_cfgs {
288                    app_state
289                        .strand_cam_http_session_handler
290                        .send_obj_detection_config(&cam_name, cfg_yaml)
291                        .await
292                        .map_err(|_e| {
293                            (
294                                StatusCode::INTERNAL_SERVER_ERROR,
295                                "send_obj_detection_config failed",
296                            )
297                        })?;
298                }
299            }
300            DoQuit => {
301                debug!("got DoQuit");
302                // Initiate the graceful shutdown sequence: stop saving data
303                // and close files, command all cameras to quit, then exit.
304                app_state
305                    .shtdwn_q_tx
306                    .send(())
307                    .await
308                    .map_err(|_e| (StatusCode::INTERNAL_SERVER_ERROR, "shutdown send failed"))?;
309            }
310        }
311        Ok::<_, (StatusCode, &'static str)>(())
312    };
313    fut.await
314}