maia_json/
lib.rs

1//! maia-json contains the JSON schemas used by maia-httpd and maia-wasm.
2
3#![warn(missing_docs)]
4
5use serde::{Deserialize, Serialize};
6
7/// API JSON schema.
8///
9/// This JSON schema corresponds to GET requests on `/api`. It contains the
10/// settings of the full Maia SDR system.
11#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
12pub struct Api {
13    /// AD9361 settings.
14    pub ad9361: Ad9361,
15    /// DDC settings.
16    pub ddc: DDCConfigSummary,
17    /// Device geolocation.
18    pub geolocation: DeviceGeolocation,
19    /// IQ recorder settings.
20    pub recorder: Recorder,
21    /// Metadata for the current recording.
22    pub recording_metadata: RecordingMetadata,
23    /// Spectrometer settings.
24    pub spectrometer: Spectrometer,
25    /// System time.
26    pub time: Time,
27    /// Versions information.
28    pub versions: Versions,
29}
30
31/// AD9361 JSON schema.
32///
33/// This JSON schema corresponds to GET and PUT requests on `/api/ad9361`. It
34/// contains the settings of the AD9361.
35#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
36pub struct Ad9361 {
37    /// Sampling frequency in samples per second.
38    pub sampling_frequency: u32,
39    /// Receive RF bandwidth in Hz.
40    pub rx_rf_bandwidth: u32,
41    /// Transmit RF bandwidth in Hz.
42    pub tx_rf_bandwidth: u32,
43    /// Receive LO frequency in Hz.
44    pub rx_lo_frequency: u64,
45    /// Transmit LO frequency in Hz.
46    pub tx_lo_frequency: u64,
47    /// Receive gain in dB.
48    pub rx_gain: f64,
49    /// Receive AGC mode.
50    pub rx_gain_mode: Ad9361GainMode,
51    /// Transmit gain in dB.
52    pub tx_gain: f64,
53}
54
55/// AD9361 PATCH JSON schema.
56///
57/// This JSON schema corresponds to PATCH requests on `/api/ad9361`. It contains
58/// a subset of the settings of the AD9361.
59#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
60pub struct PatchAd9361 {
61    /// Sampling frequency in samples per second.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub sampling_frequency: Option<u32>,
64    /// Receive RF bandwidth in Hz.
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub rx_rf_bandwidth: Option<u32>,
67    /// Transmit RF bandwidth in Hz.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub tx_rf_bandwidth: Option<u32>,
70    /// Receive LO frequency in Hz.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub rx_lo_frequency: Option<u64>,
73    /// Transmit LO frequency in Hz.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub tx_lo_frequency: Option<u64>,
76    /// Receive gain in dB.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub rx_gain: Option<f64>,
79    /// Receive AGC mode.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub rx_gain_mode: Option<Ad9361GainMode>,
82    /// Transmit gain in dB.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub tx_gain: Option<f64>,
85}
86
87#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)]
88/// AD9361 gain control modes.
89///
90/// This enum lists the automatic gain control modes supported by the AD9361.
91pub enum Ad9361GainMode {
92    /// Manual AGC.
93    Manual,
94    /// Fast attack AGC.
95    FastAttack,
96    /// Slow attack AGC.
97    SlowAttack,
98    /// Hybrid AGC.
99    Hybrid,
100}
101
102macro_rules! impl_str_conv {
103    ($ty:ty, $($s:expr => $v:ident),*) => {
104        impl std::str::FromStr for $ty {
105            type Err = ();
106
107            fn from_str(s: &str) -> Result<Self, ()> {
108                Ok(match s {
109                    $(
110                        $s => <$ty>::$v,
111                    )*
112                        _ => return Err(()),
113                })
114            }
115        }
116
117        impl std::fmt::Display for $ty {
118            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
119                write!(f, "{}", match self {
120                    $(
121                        <$ty>::$v => $s,
122                    )*
123                })
124            }
125        }
126    }
127}
128
129impl_str_conv!(Ad9361GainMode,
130               "Manual" => Manual,
131               "Fast attack" => FastAttack,
132               "Slow attack" => SlowAttack,
133               "Hybrid" => Hybrid);
134
135macro_rules! get_fields {
136    ($struct:ident, $x:expr, $($field:ident),*) => {
137        $struct {
138            $(
139                $field: Some($x.$field),
140            )*
141        }
142    }
143}
144
145impl From<Ad9361> for PatchAd9361 {
146    fn from(val: Ad9361) -> PatchAd9361 {
147        get_fields!(
148            PatchAd9361,
149            val,
150            sampling_frequency,
151            rx_rf_bandwidth,
152            tx_rf_bandwidth,
153            rx_lo_frequency,
154            tx_lo_frequency,
155            rx_gain,
156            rx_gain_mode,
157            tx_gain
158        )
159    }
160}
161
162/// Spectrometer JSON schema.
163///
164/// This JSON schema corresponds to GET requests on `/api/spectrometer`. It
165/// contains the settings of the spectrometer (waterfall).
166#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
167pub struct Spectrometer {
168    /// Input source.
169    pub input: SpectrometerInput,
170    /// Input sampling frequency in samples per second (read-only).
171    pub input_sampling_frequency: f64,
172    /// Output sampling frequency in samples per second.
173    pub output_sampling_frequency: f64,
174    /// Number of non-coherent integrations.
175    pub number_integrations: u32,
176    /// FFT size (read-only).
177    pub fft_size: u32,
178    /// Spectrometer mode.
179    pub mode: SpectrometerMode,
180}
181
182/// Spectrometer PATCH JSON schema.
183///
184/// This JSON schema corresponds to PATCH requests on `/api/spectrometer`. It is
185/// used to change the spectrometer rate, by specifying the target output
186/// sampling frequency, or the number of integrations. Since each parameter can
187/// be computed in terms of the other, only one of them should be used in the PATCH request.
188#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
189pub struct PatchSpectrometer {
190    /// Input source.
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub input: Option<SpectrometerInput>,
193    /// Output sampling frequency in samples per second.
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub output_sampling_frequency: Option<f64>,
196    /// Number of non-coherent integrations.
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub number_integrations: Option<u32>,
199    /// Spectrometer mode.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub mode: Option<SpectrometerMode>,
202}
203
204/// Spectrometer input source.
205#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)]
206pub enum SpectrometerInput {
207    /// AD9361 IQ ADC output.
208    AD9361,
209    /// DDC output.
210    DDC,
211}
212
213impl_str_conv!(SpectrometerInput,
214               "AD9361" => AD9361,
215               "DDC" => DDC);
216
217/// Spectrometer mode.
218#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)]
219pub enum SpectrometerMode {
220    /// Power average mode.
221    ///
222    /// The average power over the integration period is computed.
223    Average,
224    /// Peak detect mode.
225    ///
226    /// The maximum (peak) power over the integration period is computed.
227    PeakDetect,
228}
229
230impl_str_conv!(SpectrometerMode,
231               "Average" => Average,
232               "Peak detect" => PeakDetect);
233
234/// DDC design PUT JSON schema.
235///
236/// This JSON schema corresponds to PUT requests on `/api/ddc/design`. It is
237/// used to define design constraints for the DDC and have maia-httpd calculate
238/// suitable FIR filters coefficients using pm-remez.
239#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
240pub struct PutDDCDesign {
241    /// Frequency for the mixer, in Hz.
242    pub frequency: f64,
243    /// Decimation factor for the DDC.
244    pub decimation: u32,
245    /// Transition bandwidth of the DDC output.
246    ///
247    /// This is the fraction (in [0, 1]) of the total output bandwidth that gets
248    /// used as transition bands.
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub transition_bandwidth: Option<f64>,
251    /// Passband ripple.
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub passband_ripple: Option<f64>,
254    /// Stopband attenuation in dB.
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub stopband_attenuation_db: Option<f64>,
257    /// Use 1/f response in the stopband.
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub stopband_one_over_f: Option<bool>,
260}
261
262/// DDC configuration GET JSON schema.
263///
264/// This JSON schema corresponds to GET requests on `/api/ddc/config`. It lists
265/// the configuration of each FIR filter in the DDC, as well as some values
266/// calculated from this configuration.
267#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
268pub struct DDCConfig {
269    /// Indicates whether the DDC is currently enabled.
270    pub enabled: bool,
271    /// Frequency for the mixer, in Hz.
272    pub frequency: f64,
273    /// Total decimation of this DDC configuration.
274    pub decimation: u32,
275    /// Input sampling frequency in samples per second.
276    pub input_sampling_frequency: f64,
277    /// Output sampling frequency in samples per second.
278    pub output_sampling_frequency: f64,
279    /// Maximum input sampling frequency supported by this DDC configuration.
280    pub max_input_sampling_frequency: f64,
281    /// Configuration of the first FIR filter.
282    pub fir1: DDCFIRConfig,
283    /// Configuration of the second FIR filter.
284    ///
285    /// This has the value `None` if the second FIR filter is bypassed.
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub fir2: Option<DDCFIRConfig>,
288    /// Configuration of the third FIR filter.
289    ///
290    /// This has the value `None` if the third FIR filter is bypassed.
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub fir3: Option<DDCFIRConfig>,
293}
294
295/// DDC configuration summary GET JSON schema.
296///
297/// This JSON schema is similar to [`DDCConfig`], but it does not include the
298/// FIR coefficients. It is used for the DDC entry in `/api`.
299#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
300pub struct DDCConfigSummary {
301    /// Indicates whether the DDC is currently enabled.
302    pub enabled: bool,
303    /// Frequency for the mixer, in Hz.
304    pub frequency: f64,
305    /// Total decimation of this DDC configuration.
306    pub decimation: u32,
307    /// Input sampling frequency in samples per second.
308    pub input_sampling_frequency: f64,
309    /// Output sampling frequency in samples per second.
310    pub output_sampling_frequency: f64,
311    /// Maximum input sampling frequency supported by this DDC configuration.
312    pub max_input_sampling_frequency: f64,
313}
314
315macro_rules! ddcconfig_from {
316    ($value:expr, $($field:ident),*) => {
317        DDCConfigSummary {
318            $($field: $value.$field),*
319        }
320    }
321}
322
323impl From<DDCConfig> for DDCConfigSummary {
324    fn from(value: DDCConfig) -> DDCConfigSummary {
325        ddcconfig_from!(
326            value,
327            enabled,
328            frequency,
329            decimation,
330            input_sampling_frequency,
331            output_sampling_frequency,
332            max_input_sampling_frequency
333        )
334    }
335}
336
337/// DDC configuration PUT JSON schema.
338///
339/// This JSON schema corresponds to PUT requests on `/api/ddc/config`. It is
340/// used to set the coefficients for each FIR filter manually, as opposed to
341/// having maia-httpd design a filter satisfying some requirements.
342#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
343pub struct PutDDCConfig {
344    /// Frequency for the mixer, in Hz.
345    pub frequency: f64,
346    /// Configuration of the first FIR filter.
347    pub fir1: DDCFIRConfig,
348    /// Configuration of the second FIR filter.
349    ///
350    /// This has the value `None` if the second FIR filter is bypassed.
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub fir2: Option<DDCFIRConfig>,
353    /// Configuration of the third FIR filter.
354    ///
355    /// This has the value `None` if the third FIR filter is bypassed.
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub fir3: Option<DDCFIRConfig>,
358}
359
360/// DDC configuration PUT JSON schema.
361///
362/// This JSON schema corresponds to PATCH requests on `/api/ddc/config`. It is
363/// used to change the frequency without changing the FIR filter configuration
364#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
365pub struct PatchDDCConfig {
366    /// Frequency for the mixer, in Hz.
367    #[serde(skip_serializing_if = "Option::is_none")]
368    pub frequency: Option<f64>,
369}
370
371/// Configuration of a FIR filter in the DDC.
372#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
373pub struct DDCFIRConfig {
374    /// FIR filter coefficients.
375    pub coefficients: Vec<i32>,
376    /// Decimation factor.
377    pub decimation: u32,
378}
379
380/// IQ recorder JSON schema.
381///
382/// This JSON schema corresponds to GET requests on `/api/recorder`. It contains
383/// the settings of the IQ recorder.
384#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
385pub struct Recorder {
386    /// Current recorder state.
387    pub state: RecorderState,
388    /// Recoder sampling mode.
389    pub mode: RecorderMode,
390    /// Automatically prepend timestamp to file name.
391    pub prepend_timestamp: bool,
392    /// Maximum recording duration (in seconds).
393    pub maximum_duration: f64,
394}
395
396/// IQ recorder PATCH JSON schema.
397///
398/// This JSON schema corresponds to PATCH requests on `/api/recorder`. It is
399/// used to modify the settings of the IQ recorder.
400#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
401pub struct PatchRecorder {
402    /// Command to change the recorder state.
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub state_change: Option<RecorderStateChange>,
405    /// Recorder sampling mode.
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub mode: Option<RecorderMode>,
408    /// Automatically prepend timestamp to file name.
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub prepend_timestamp: Option<bool>,
411    /// Maximum recording duration (in seconds).
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub maximum_duration: Option<f64>,
414}
415
416/// Command to change the IQ recorder state.
417#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Hash)]
418pub enum RecorderStateChange {
419    /// Command the IQ recoder to start recording.
420    Start,
421    /// Command the IQ recorder to stop recording.
422    Stop,
423}
424
425/// IQ recorder sampling mode.
426#[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash)]
427pub enum RecorderMode {
428    /// 8-bit sampling mode.
429    ///
430    /// Only the 8 MSBs of the ADC data or the DDC output are recorded.
431    IQ8bit,
432    /// 12-bit sampling mode.
433    ///
434    /// All the 12 bits of the ADC data, or the 12 MSBs of the
435    /// 16-bit DDC output are recorded.
436    IQ12bit,
437    /// 16-bit sampling mode.
438    ///
439    /// All the 16 bits of the DDC output are recorded. 12-bit ADC data is
440    /// placed on the 12 MSBs.
441    IQ16bit,
442}
443
444impl_str_conv!(RecorderMode,
445               "8 bit IQ" => IQ8bit,
446               "12 bit IQ" => IQ12bit,
447               "16 bit IQ" => IQ16bit);
448
449/// IQ recorder state.
450#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Hash)]
451pub enum RecorderState {
452    /// The IQ recorder is stopped.
453    Stopped,
454    /// The IQ recorder is running.
455    Running,
456    /// The IQ recoder is stopping.
457    Stopping,
458}
459
460/// Geolocation.
461///
462/// This is based on a GeoJSON point, but it is encoded differently in JSON.
463#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
464pub struct Geolocation {
465    /// Latitude in degrees.
466    pub latitude: f64,
467    /// Longitude in degrees.
468    pub longitude: f64,
469    /// Altitude in meters.
470    ///
471    /// The altitude is optional.
472    #[serde(skip_serializing_if = "Option::is_none")]
473    pub altitude: Option<f64>,
474}
475
476/// Recording metadata JSON schema.
477///
478/// This JSON schema corresponds to GET and PUT requests on
479/// `/api/recording/metadata`. It contains the metadata for the current
480/// recording.
481#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
482pub struct RecordingMetadata {
483    /// Recording file name.
484    pub filename: String,
485    /// Recording description.
486    pub description: String,
487    /// Recording author.
488    pub author: String,
489    /// Recording geolocation.
490    ///
491    /// This corresponds to the SigMF "core:geolocation" key. It contains `None`
492    /// if the geolocation is unknown.
493    pub geolocation: DeviceGeolocation,
494}
495
496/// Recording metadata PATCH JSON schema.
497///
498/// This JSON schema corresponds to PATCH and PUT requests on
499/// `/api/recording/metadata`. It is used to modify the metadata for the current
500/// recording.
501#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
502pub struct PatchRecordingMetadata {
503    /// Recording file name.
504    #[serde(skip_serializing_if = "Option::is_none")]
505    pub filename: Option<String>,
506    /// Recording description.
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub description: Option<String>,
509    /// Recording author.
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub author: Option<String>,
512    /// Recording geolocation.
513    ///
514    /// This corresponds to the SigMF "core:geolocation" key. It contains `None`
515    /// inside the `DeviceGeolocation` to remove the geolocation from the
516    /// metadata.
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub geolocation: Option<DeviceGeolocation>,
519}
520
521impl From<RecordingMetadata> for PatchRecordingMetadata {
522    fn from(val: RecordingMetadata) -> PatchRecordingMetadata {
523        get_fields!(
524            PatchRecordingMetadata,
525            val,
526            filename,
527            description,
528            author,
529            geolocation
530        )
531    }
532}
533
534/// System time JSON schema.
535///
536/// This JSON schema corresponds to GET requests on `/api/time`. It contains the
537/// current system time.
538#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
539pub struct Time {
540    /// Number of milliseconds since UNIX timestamp.
541    ///
542    /// This uses the same format as JavaScript `Date.now()`.
543    pub time: f64,
544}
545
546/// System time PATCH JSON schema.
547///
548/// This JSON schema corresponds to GET requests on `/api/time`. It contains the
549/// current system time.
550#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
551pub struct PatchTime {
552    /// Number of milliseconds since UNIX timestamp.
553    ///
554    /// This uses the same format as JavaScript `Date.now()`.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub time: Option<f64>,
557}
558
559impl From<Time> for PatchTime {
560    fn from(val: Time) -> PatchTime {
561        get_fields!(PatchTime, val, time)
562    }
563}
564
565/// Device geolocation JSON schema.
566///
567/// This JSON schema corresponds to GET and PUT requests on
568/// `/api/geolocation`. The GET request contains the current device geolocation,
569/// or `None` if it has never been set or if it has been cleared. The PUT
570/// request sets the current device geolocation, or clears it the request
571/// contains `None`.
572#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
573pub struct DeviceGeolocation {
574    /// Current device geolocation.
575    pub point: Option<Geolocation>,
576}
577
578/// Versions information.
579///
580/// This JSON schema corresponds to GET requests on `/api/versions`.
581#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, Hash)]
582pub struct Versions {
583    /// Firmware version.
584    ///
585    /// This is obtained from the `fw_version` variable in `/etc/libiio.ini`.
586    pub firmware_version: String,
587    /// git version for maia-httpd.
588    ///
589    /// This is the git version of the maia-sdr repository checkout from which
590    /// maia-httpd was built.
591    pub maia_httpd_git: String,
592    /// maia-httpd version.
593    ///
594    /// This is the version of the maia-httpd crate as reported by cargo.
595    pub maia_httpd_version: String,
596    /// maia-hdl version.
597    ///
598    /// This is the version of the maia-hdl IP core as reported by the IP core
599    /// registers.
600    pub maia_hdl_version: String,
601}
602
603/// Error.
604///
605/// This JSON schema is used to report errors to the client. It is used whenever
606/// the API returns an HTTP error code such as 500.
607#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
608pub struct Error {
609    /// HTTP status code.
610    pub http_status_code: u16,
611    /// String describing the error in a human readable form.
612    pub error_description: String,
613    /// Sugested action to perform by the client.
614    pub suggested_action: ErrorAction,
615}
616
617/// Actions for an error.
618///
619/// This enum lists the actions that a client may take to handle an error.
620#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
621pub enum ErrorAction {
622    /// Show a message using the JavaScript `alert()` function.
623    Alert,
624    /// Log the error.
625    Log,
626    /// Ignore the error.
627    Ignore,
628}