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
// Author: Roman Hayn
// MIT License 

use iio::{Buffer, Channel, Context, Device};
use industrial_io as iio;

// Small abstraction for simpler and faster coding down the line
// -> The PlutoSDR uses an ad936x internally
// set ad9361-phy -> altvoltage0 to RX_LO freq.
// set ad9361-phy -> voltage0 to RX_bb / sampling
// cf-ad9361-lpc -> voltage0 is RX0_I
// cf-ad9361-lpc -> voltage1 is RX0_Q
// the device cf-ad9361-lpc supports buffers

// device name definitions
// PHY controls all parameters of the pluto
const DEV_PHY: &str = "ad9361-phy";
const DEV_LPC_RX: &str = "cf-ad9361-lpc";

// TX uses a DDS Signal Generator
const DEV_LPC_TX: &str = "cf-ad9361-dds-core-lpc";

// attribute name definitions
// for rx and/or tx, I and Q samples
const ATTR_XX_I: &str = "voltage0";
const ATTR_XX_Q: &str = "voltage1";

// Shorthand for functions
// Input  -> RX
// Output -> TX
pub const RX: bool = false;
pub const TX: bool = true;

pub struct Pluto {
    pub context: Context,
    pub phy: Device,
    pub rxadc: Device,
    pub txdac: Device,
}

impl Pluto {
    pub fn connect(uri: &str) -> Self {
        let context = iio::Context::from_uri(uri).unwrap();
        let phy = context.find_device(DEV_PHY).unwrap();
        let rxadc = context.find_device(DEV_LPC_RX).unwrap();
        let txdac = context.find_device(DEV_LPC_TX).unwrap();

        println!("[PLUTO-SDR] Initialized:  {}", uri);

        Pluto {
            context,
            phy,
            rxadc,
            txdac,
        }
    }

    // phy -> altvoltage0 -> frequency (RX_LO)
    pub fn set_lo_rx(&self, f_lo: i64) -> Result<(), ()> {
        let res = self.phy.find_channel("altvoltage0", true);
        match res {
            Some(e) => {
                e.attr_write_int("frequency", f_lo).unwrap();
                Ok(())
            }
            None => Err(()),
        }
    }

    // phy -> altvoltage1 -> frequency (TX_LO)
    pub fn set_lo_tx(&self, f_lo: i64) -> Result<(), ()> {
        let res = self.phy.find_channel("altvoltage1", true);
        match res {
            Some(e) => {
                e.attr_write_int("frequency", f_lo).unwrap();
                Ok(())
            }
            None => Err(()),
        }
    }

    // phy -> voltage0 (rx:IN, tx:OUT) -> rf_bandwidth
    pub fn set_rf_bandwidth(&self, f_b: i64, rxtx: bool) -> Result<(), ()> {
        let res = self.phy.find_channel("voltage0", rxtx);
        match res {
            Some(chan) => {
                chan.attr_write_int("rf_bandwidth", f_b).unwrap();
                Ok(())
            }
            None => Err(()),
        }
    }

    // rx hw gain: phy -> voltage0 (IN) -> hardwaregain
    pub fn set_hwgain(&self, g: f64, rxtx: bool) -> Result<(), ()> {
        let res = self.phy.find_channel("voltage0", rxtx);
        match res {
            Some(chan) => {
                // only RX supports auto gain modes
                // set manual mode just in case
                if !rxtx {
                    chan.attr_write_str("gain_control_mode", "manual").unwrap();
                }
                chan.attr_write_float("hardwaregain", g).unwrap();
                Ok(())
            }
            None => Err(()),
        }
    }

    // phy -> voltage0 -> sampling_frequency (docs: same for RX/TX)
    pub fn set_sampling_freq(&self, fs: i64) -> Result<(), ()> {
        let res = self.phy.find_channel("voltage0", false);
        match res {
            Some(chan) => {
                chan.attr_write_int("sampling_frequency", fs).unwrap();
                Ok(())
            }
            None => Err(()),
        }
    }

    pub fn rx_i(&self) -> Channel {
        self.rxadc.find_channel(ATTR_XX_I, false).unwrap()
    }

    pub fn rx_q(&self) -> Channel {
        self.rxadc.find_channel(ATTR_XX_Q, false).unwrap()
    }

    pub fn tx_i(&self) -> Channel {
        self.txdac.find_channel(ATTR_XX_I, true).unwrap()
    }

    pub fn tx_q(&self) -> Channel {
        self.txdac.find_channel(ATTR_XX_Q, true).unwrap()
    }

    // Create a buffer for samples
    pub fn create_buffer_rx(&self, size: usize) -> iio::Result<Buffer> {
        // can not receive with a cyclic buffer
        self.rxadc.create_buffer(size, false)
    }

    pub fn create_buffer_tx(&self, size: usize, cyclic: bool) -> iio::Result<Buffer> {
        self.txdac.create_buffer(size, cyclic)
    }

    // this does not just simply toggle the output
    pub fn toggle_dds(&self, enable: bool) -> () {
        // sets all output channels on/off
        for chan in self.txdac.channels() {
            match chan.id() {
                Some(name) => {
                    if name.contains("altvoltage") {
                        if enable {
                            chan.attr_write_int("raw", 1).unwrap();
                        } else {
                            chan.attr_write_int("raw", 0).unwrap();
                        }
                    }
                }
                None => {}
            }
        }
    }

    pub fn list_dds(&self) -> () {
        for chan in self.txdac.channels() {
            match chan.id() {
                Some(name) => {
                    if name.contains("altvoltage") {
                        println!("-> {}:\n    {:?}", name, chan.attr_read_all().unwrap());
                    }
                }
                None => {}
            }
        }
    }
}