core/
camera.rs

1//! Code for the camera actor, which handles camera-related functionalities.
2
3use crate::sauron;
4use crate::{database::insert_image, utils::get_env};
5
6use a8mini_camera_rs::A8Mini;
7use anyhow::anyhow;
8use chrono::Utc;
9use constants::CAMERA_HEARTBEAT_FAILS_PER_RESTART;
10use sqlx::postgres::PgPoolOptions;
11use std::time::Duration;
12use tokio::time;
13use tracing::{debug, error, info};
14
15use peripherals::peripherals::DroneCamera;
16
17/// Starts the camera actor, given the handle to the sauron actor.
18pub async fn start_camera(sauron: sauron::SauronHandle) {
19    // move sauron's handle into camera
20    let mut fail_counter: u32 = 0;
21    tokio::spawn(async move {
22        loop {
23            match run_camera(sauron.clone()).await {
24                Ok(_) => break,
25                Err(e) => {
26                    fail_counter += 1;
27                    error!("Camera thread failed ({} times): {}", fail_counter, e);
28                    info!("Restarting camera thread in 2500 miliseconds...");
29                    tokio::time::sleep(Duration::from_millis(2500)).await;
30                }
31            }
32        }
33    });
34}
35
36/// Internal function which actually runs the camera actor.
37async fn run_camera(sauron: sauron::SauronHandle) -> anyhow::Result<()> {
38    info!("Started Camera Thread");
39    let database_url = get_env(
40        "DATABASE_URL",
41        "postgresql://user:password@localhost:5432/local".to_string(),
42    );
43
44    let pool = PgPoolOptions::new()
45        .max_connections(constants::CONNECTIONS_PER_DATABASE_POOL)
46        .connect(&database_url)
47        .await?;
48
49    let camera = A8Mini::connect().await.unwrap();
50
51    info!("Connected a8mini, verifying connection with heartbeat message...");
52    let mut heartbeat_fail_counter: u64 = 0;
53    while camera.check_heartbeat().await.is_err() {
54        heartbeat_fail_counter += 1;
55        error!(
56            "Camera heartbeat failed {} times. Retrying...",
57            heartbeat_fail_counter
58        );
59        if heartbeat_fail_counter >= CAMERA_HEARTBEAT_FAILS_PER_RESTART {
60            return Err(anyhow!(
61                "Camera heartbeat fail counter exceeded limit ({}).",
62                heartbeat_fail_counter
63            ));
64        }
65    }
66
67    let mut camera_timer = time::interval(Duration::from_millis(constants::CAMERA_INTERVAL_MS));
68
69    loop {
70        // Wait for the interval to tick
71        camera_timer.tick().await;
72
73        let time_before = Utc::now();
74
75        camera.take_photo().await?;
76
77        let time_after = Utc::now();
78        let average_time = (time_before + (time_after - time_before) / 2).timestamp_millis();
79
80        //Save the photo
81        let photo_path = camera
82            .save_latest_photo("./untagged_image_folder".to_string()) // doesn't matter as this will be run in a docker container and everything will be wiped
83            .await?;
84        debug!("Saved photo to {}", photo_path);
85
86        // Add the photo to the sauron's queue
87        let insert_image_promise = insert_image(&pool, &photo_path, &average_time);
88
89        sauron.send_photo(photo_path.clone(), average_time).await;
90        insert_image_promise.await?;
91    }
92}