maia_httpd/
app.rs

1//! maia-httpd application.
2//!
3//! This module contains a top-level structure [`App`] that represents the whole
4//! maia-httpd application and a structure [`AppState`] that contains the
5//! application state.
6
7use crate::{
8    args::Args,
9    fpga::{InterruptHandler, IpCore},
10    httpd::{self, RecorderFinishWaiter, RecorderState},
11    iio::Ad9361,
12    spectrometer::{Spectrometer, SpectrometerConfig},
13};
14use anyhow::Result;
15use std::sync::{Arc, Mutex};
16use tokio::sync::broadcast;
17
18/// maia-httpd application.
19///
20/// This struct represents the maia-sdr application. It owns the different
21/// objects of which the application is formed, and runs them concurrently.
22#[derive(Debug)]
23pub struct App {
24    httpd: httpd::Server,
25    interrupt_handler: InterruptHandler,
26    recorder_finish: RecorderFinishWaiter,
27    spectrometer: Spectrometer,
28}
29
30impl App {
31    /// Creates a new application.
32    #[tracing::instrument(name = "App::new", level = "debug")]
33    pub async fn new(args: &Args) -> Result<App> {
34        // Initialize and build application state
35        let (ip_core, interrupt_handler) = IpCore::take().await?;
36        let ip_core = std::sync::Mutex::new(ip_core);
37        let ad9361 = tokio::sync::Mutex::new(Ad9361::new().await?);
38        let recorder = RecorderState::new(&ad9361, &ip_core).await?;
39        let state = AppState(Arc::new(State {
40            ad9361,
41            ip_core,
42            geolocation: std::sync::Mutex::new(None),
43            recorder,
44            spectrometer_config: Default::default(),
45        }));
46        // Initialize spectrometer sample rate and mode
47        state.spectrometer_config().set_samp_rate_mode(
48            state.ad9361().lock().await.get_sampling_frequency().await? as f32,
49            state.ip_core().lock().unwrap().spectrometer_mode(),
50        );
51
52        // Build application objects
53
54        let (waterfall_sender, _) = broadcast::channel(16);
55        let spectrometer = Spectrometer::new(
56            state.clone(),
57            interrupt_handler.waiter_spectrometer(),
58            waterfall_sender.clone(),
59        );
60
61        let recorder_finish =
62            RecorderFinishWaiter::new(state.clone(), interrupt_handler.waiter_recorder());
63
64        let httpd = httpd::Server::new(
65            args.listen,
66            args.listen_https,
67            args.ssl_cert.as_ref(),
68            args.ssl_key.as_ref(),
69            args.ca_cert.as_ref(),
70            state,
71            waterfall_sender,
72        )
73        .await?;
74
75        Ok(App {
76            httpd,
77            interrupt_handler,
78            recorder_finish,
79            spectrometer,
80        })
81    }
82
83    /// Runs the application.
84    ///
85    /// This only returns if one of the objects that form the application fails.
86    #[tracing::instrument(name = "App::run", level = "debug", skip_all)]
87    pub async fn run(self) -> Result<()> {
88        tokio::select! {
89            ret = self.httpd.run() => ret,
90            ret = self.interrupt_handler.run() => ret,
91            ret = self.recorder_finish.run() => ret,
92            ret = self.spectrometer.run() => ret,
93        }
94    }
95}
96
97/// Application state.
98///
99/// This struct contains the application state that needs to be shared between
100/// different modules, such as different Axum handlers in the HTTP server. The
101/// struct behaves as an `Arc<...>`. It is cheaply clonable and clones represent
102/// a reference to a shared object.
103#[derive(Debug, Clone)]
104pub struct AppState(Arc<State>);
105
106#[derive(Debug)]
107struct State {
108    ad9361: tokio::sync::Mutex<Ad9361>,
109    ip_core: Mutex<IpCore>,
110    geolocation: Mutex<Option<maia_json::Geolocation>>,
111    recorder: RecorderState,
112    spectrometer_config: SpectrometerConfig,
113}
114
115impl AppState {
116    /// Gives access to the [`Ad9361`] object of the application.
117    pub fn ad9361(&self) -> &tokio::sync::Mutex<Ad9361> {
118        &self.0.ad9361
119    }
120
121    /// Gives access to the [`IpCore`] object of the application.
122    pub fn ip_core(&self) -> &Mutex<IpCore> {
123        &self.0.ip_core
124    }
125
126    /// Gives access to the current geolocation of the device.
127    ///
128    /// The geolocation is `None` if it has never been set or if it has been
129    /// cleared, or a valid [`Geolocation`](maia_json::Geolocation) otherwise.
130    pub fn geolocation(&self) -> &Mutex<Option<maia_json::Geolocation>> {
131        &self.0.geolocation
132    }
133
134    /// Gives access to the [`RecorderState`] object of the application.
135    pub fn recorder(&self) -> &RecorderState {
136        &self.0.recorder
137    }
138
139    /// Gives access to the [`SpectrometerConfig`] object of the application.
140    pub fn spectrometer_config(&self) -> &SpectrometerConfig {
141        &self.0.spectrometer_config
142    }
143
144    /// Returns the AD9361 sampling frequency.
145    pub async fn ad9361_samp_rate(&self) -> Result<f64> {
146        Ok(self.ad9361().lock().await.get_sampling_frequency().await? as f64)
147    }
148}