1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! IIO device access.
//!
//! This module is used to control IIO devices, such as the ADI AD9361 driver.

use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use tokio::fs;

/// AD9361 IIO device.
///
/// This struct represents the AD9361 IIO device (ad9361-phy) and can be used to
/// control its attributes.
#[derive(Debug)]
pub struct Ad9361 {
    iio_device_path: PathBuf,
}

macro_rules! iio_getset {
    ($attribute:ident, $filename:expr, $ty_internal:ty, $ty_external:ty) => {
        paste::paste! {
            #[doc = concat!("Returns the value of the `", stringify!($attribute),
                            "` IIO attribute.")]
            pub async fn [<get_ $attribute>](&self) -> Result<$ty_external> {
                fs::read_to_string(self.iio_device_path.join($filename))
                    .await?
                    .trim_end()
                    .parse::<$ty_internal>()
                    .map_err(|_| anyhow::anyhow!(concat!(
                        "failed to parse IIO attribute ", stringify!($attribute))))
                    .map(|x| x.into())
            }

            #[doc = concat!("Sets the value of the `", stringify!($attribute),
                            "` IIO attribute.")]
            pub async fn [<set_ $attribute>](&self, value: $ty_external) -> Result<()> {
                fs::write(
                    self.iio_device_path.join($filename),
                    Into::<$ty_internal>::into(value).to_string().as_bytes(),
                ).await.context(concat!("failed to set IIO attribute ",
                                        stringify!($attribute)))?;
                Ok(())
            }
        }
    };
}

impl Ad9361 {
    /// Opens an AD9361 IIO device.
    ///
    /// This function opens the first IIO device with name ad9361-phy that is
    /// found in the system.
    pub async fn new() -> Result<Ad9361> {
        let iio_device_path = Self::find_iio_device()
            .await?
            .ok_or_else(|| anyhow::anyhow!("ad9361-phy IIO device not found"))?;
        Ok(Ad9361 { iio_device_path })
    }

    async fn find_iio_device() -> Result<Option<PathBuf>> {
        let mut entries = fs::read_dir(Path::new("/sys/bus/iio/devices")).await?;
        while let Some(entry) = entries.next_entry().await? {
            if entry
                .file_name()
                .to_str()
                .ok_or_else(|| anyhow::anyhow!("file name is not valid UTF8"))?
                .starts_with("iio:device")
            {
                let mut path = entry.path();
                path.push("name");
                let this_name = fs::read_to_string(path).await?;
                if this_name == "ad9361-phy\n" {
                    return Ok(Some(entry.path()));
                }
            }
        }
        Ok(None)
    }

    iio_getset!(
        sampling_frequency,
        "in_voltage_sampling_frequency",
        u32,
        u32
    );
    iio_getset!(rx_rf_bandwidth, "in_voltage_rf_bandwidth", u32, u32);
    iio_getset!(tx_rf_bandwidth, "out_voltage_rf_bandwidth", u32, u32);
    iio_getset!(rx_lo_frequency, "out_altvoltage0_RX_LO_frequency", u64, u64);
    iio_getset!(tx_lo_frequency, "out_altvoltage1_TX_LO_frequency", u64, u64);
    iio_getset!(rx_gain, "in_voltage0_hardwaregain", Dbf64, f64);
    iio_getset!(tx_gain, "out_voltage0_hardwaregain", Dbf64, f64);
    iio_getset!(
        rx_gain_mode,
        "in_voltage0_gain_control_mode",
        Ad9361GainMode,
        Ad9361GainMode
    );
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
/// AD9361 gain control modes.
///
/// This enum lists the automatic gain control modes supported by the AD9361.
pub struct Ad9361GainMode(maia_json::Ad9361GainMode);

impl From<maia_json::Ad9361GainMode> for Ad9361GainMode {
    fn from(value: maia_json::Ad9361GainMode) -> Ad9361GainMode {
        Ad9361GainMode(value)
    }
}

impl From<Ad9361GainMode> for maia_json::Ad9361GainMode {
    fn from(value: Ad9361GainMode) -> maia_json::Ad9361GainMode {
        value.0
    }
}

impl std::str::FromStr for Ad9361GainMode {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(match s {
            "manual" => maia_json::Ad9361GainMode::Manual,
            "fast_attack" => maia_json::Ad9361GainMode::FastAttack,
            "slow_attack" => maia_json::Ad9361GainMode::SlowAttack,
            "hybrid" => maia_json::Ad9361GainMode::Hybrid,
            _ => return Err(()),
        }
        .into())
    }
}

impl std::fmt::Display for Ad9361GainMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        write!(
            f,
            "{}",
            match self.0 {
                maia_json::Ad9361GainMode::Manual => "manual",
                maia_json::Ad9361GainMode::FastAttack => "fast_attack",
                maia_json::Ad9361GainMode::SlowAttack => "slow_attack",
                maia_json::Ad9361GainMode::Hybrid => "hybrid",
            }
        )
    }
}

#[derive(Debug, Clone, Copy)]
struct Dbf64(f64);

impl From<f64> for Dbf64 {
    fn from(value: f64) -> Dbf64 {
        Dbf64(value)
    }
}

impl From<Dbf64> for f64 {
    fn from(value: Dbf64) -> f64 {
        value.0
    }
}

impl std::str::FromStr for Dbf64 {
    type Err = <f64 as std::str::FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let db = " dB";
        if !s.ends_with(db) {
            return Err(format!("{s} does not end with 'dB'")
                .parse::<f64>()
                .err()
                .unwrap());
        }
        s[..s.len() - db.len()].parse().map(Dbf64)
    }
}

impl std::fmt::Display for Dbf64 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        self.0.fmt(f)
    }
}