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}