Skip to main content

neoradio2/
device.rs

1use crate::{check, ffi, Result};
2use std::os::raw::{c_char, c_int, c_uint};
3
4/// The raw C handle type. `neoradio2_handle` is `#define neoradio2_handle long`
5/// in the C headers, so bindgen inlines it as `c_long` rather than emitting an
6/// alias; we name it here for readability within this crate.
7pub(crate) type Handle = std::os::raw::c_long;
8
9const MAX_DEVS: usize = ffi::NEORADIO2_MAX_DEVS as usize;
10
11fn cstr_to_string(buf: &[c_char]) -> String {
12    let bytes: Vec<u8> = buf
13        .iter()
14        .take_while(|&&c| c != 0)
15        .map(|&c| c as u8)
16        .collect();
17    String::from_utf8_lossy(&bytes).into_owned()
18}
19
20/// Information about a discovered neoRAD-IO2 USB device.
21#[derive(Clone)]
22pub struct DeviceInfo(pub(crate) ffi::Neoradio2DeviceInfo);
23
24impl DeviceInfo {
25    /// Product name (e.g. "neoRAD-IO2-AIN").
26    pub fn name(&self) -> String {
27        cstr_to_string(&self.0.name)
28    }
29    /// Serial string (e.g. "IB0370").
30    pub fn serial(&self) -> String {
31        cstr_to_string(&self.0.serial_str)
32    }
33    /// USB vendor id.
34    pub fn vendor_id(&self) -> i32 {
35        self.0.vendor_id
36    }
37    /// USB product id.
38    pub fn product_id(&self) -> i32 {
39        self.0.product_id
40    }
41}
42
43impl std::fmt::Debug for DeviceInfo {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct("DeviceInfo")
46            .field("name", &self.name())
47            .field("serial", &self.serial())
48            .field("vendor_id", &format_args!("{:#06x}", self.vendor_id()))
49            .field("product_id", &format_args!("{:#06x}", self.product_id()))
50            .finish()
51    }
52}
53
54/// An open connection to a neoRAD-IO2 chain (closed automatically on drop).
55///
56/// `Device` is `Send` but not `Sync`: the underlying C library is not safe for
57/// concurrent calls on the same handle, and blocking mode is process-global.
58/// Note: the underlying C library also keeps process-global state, so using
59/// even separate `Device` values concurrently across threads is not supported.
60pub struct Device {
61    pub(crate) handle: Handle,
62    // `Handle` (`c_long`) is itself `Sync`, so without this marker the compiler
63    // would auto-derive `Sync` for `Device` too. `*const ()` is neither `Send`
64    // nor `Sync`, which suppresses both auto-impls; `Send` is then restored
65    // explicitly below, leaving `Device` correctly `Send` but not `Sync`.
66    _not_sync: std::marker::PhantomData<*const ()>,
67}
68
69// Safe to move a handle between threads; not safe to share (&Device) across them.
70unsafe impl Send for Device {}
71
72impl Device {
73    /// Enumerate attached neoRAD-IO2 USB devices.
74    pub fn find() -> Result<Vec<DeviceInfo>> {
75        let mut buf: [ffi::Neoradio2DeviceInfo; MAX_DEVS] = unsafe { std::mem::zeroed() };
76        let mut count: c_uint = MAX_DEVS as c_uint;
77        check(unsafe { ffi::neoradio2_find(buf.as_mut_ptr(), &mut count) })?;
78        let n = (count as usize).min(MAX_DEVS);
79        Ok(buf[..n].iter().map(|raw| DeviceInfo(*raw)).collect())
80    }
81
82    /// Open a device discovered by [`find`](Device::find).
83    pub fn open(info: &DeviceInfo) -> Result<Device> {
84        let mut raw = info.0;
85        let mut handle: Handle = -1;
86        check(unsafe { ffi::neoradio2_open(&mut handle, &mut raw) })?;
87        Ok(Device {
88            handle,
89            _not_sync: std::marker::PhantomData,
90        })
91    }
92
93    /// Whether the device is currently open.
94    pub fn is_opened(&self) -> Result<bool> {
95        let mut out: c_int = 0;
96        let mut h = self.handle;
97        check(unsafe { ffi::neoradio2_is_opened(&mut h, &mut out) })?;
98        Ok(out != 0)
99    }
100
101    /// Whether the device is currently closed.
102    pub fn is_closed(&self) -> Result<bool> {
103        let mut out: c_int = 0;
104        let mut h = self.handle;
105        check(unsafe { ffi::neoradio2_is_closed(&mut h, &mut out) })?;
106        Ok(out != 0)
107    }
108}
109
110impl Device {
111    /// Identify the device chain.
112    pub fn chain_identify(&self) -> Result<()> {
113        let mut h = self.handle;
114        check(unsafe { ffi::neoradio2_chain_identify(&mut h) })
115    }
116
117    /// Whether the chain has been identified.
118    pub fn chain_is_identified(&self) -> Result<bool> {
119        let mut out: c_int = 0;
120        let mut h = self.handle;
121        check(unsafe { ffi::neoradio2_chain_is_identified(&mut h, &mut out) })?;
122        Ok(out != 0)
123    }
124
125    /// Number of devices in the chain. `identify` re-identifies first.
126    pub fn chain_count(&self, identify: bool) -> Result<i32> {
127        let mut out: c_int = 0;
128        let mut h = self.handle;
129        check(unsafe { ffi::neoradio2_get_chain_count(&mut h, &mut out, identify as c_int) })?;
130        Ok(out)
131    }
132
133    /// Start application firmware on `device`/`bank` (bank is a bitmask).
134    pub fn app_start(&self, device: i32, bank: i32) -> Result<()> {
135        let mut h = self.handle;
136        check(unsafe { ffi::neoradio2_app_start(&mut h, device, bank) })
137    }
138
139    /// Whether application firmware is running on `device`/`bank`.
140    pub fn app_is_started(&self, device: i32, bank: i32) -> Result<bool> {
141        let mut out: c_int = 0;
142        let mut h = self.handle;
143        check(unsafe { ffi::neoradio2_app_is_started(&mut h, device, bank, &mut out) })?;
144        Ok(out != 0)
145    }
146
147    /// Enter the bootloader on `device`/`bank`.
148    pub fn enter_bootloader(&self, device: i32, bank: i32) -> Result<()> {
149        let mut h = self.handle;
150        check(unsafe { ffi::neoradio2_enter_bootloader(&mut h, device, bank) })
151    }
152}
153
154impl Device {
155    /// Serial number (base-10) of `device`/`bank`.
156    pub fn serial_number(&self, device: i32, bank: i32) -> Result<u32> {
157        let mut out: c_uint = 0;
158        let mut h = self.handle;
159        check(unsafe { ffi::neoradio2_get_serial_number(&mut h, device, bank, &mut out) })?;
160        Ok(out)
161    }
162
163    /// Manufacturing date `(year, month, day)`.
164    pub fn manufacturer_date(&self, device: i32, bank: i32) -> Result<(i32, i32, i32)> {
165        let (mut y, mut m, mut d) = (0, 0, 0);
166        let mut h = self.handle;
167        check(unsafe {
168            ffi::neoradio2_get_manufacturer_date(&mut h, device, bank, &mut y, &mut m, &mut d)
169        })?;
170        Ok((y, m, d))
171    }
172
173    /// Firmware version `(major, minor)`.
174    pub fn firmware_version(&self, device: i32, bank: i32) -> Result<(i32, i32)> {
175        let (mut major, mut minor) = (0, 0);
176        let mut h = self.handle;
177        check(unsafe {
178            ffi::neoradio2_get_firmware_version(&mut h, device, bank, &mut major, &mut minor)
179        })?;
180        Ok((major, minor))
181    }
182
183    /// Hardware revision `(major, minor)`.
184    pub fn hardware_revision(&self, device: i32, bank: i32) -> Result<(i32, i32)> {
185        let (mut major, mut minor) = (0, 0);
186        let mut h = self.handle;
187        check(unsafe {
188            ffi::neoradio2_get_hardware_revision(&mut h, device, bank, &mut major, &mut minor)
189        })?;
190        Ok((major, minor))
191    }
192
193    /// Device type code (see [`DeviceType::from_raw`](crate::DeviceType::from_raw)).
194    pub fn device_type(&self, device: i32, bank: i32) -> Result<u32> {
195        let mut out: c_uint = 0;
196        let mut h = self.handle;
197        check(unsafe { ffi::neoradio2_get_device_type(&mut h, device, bank, &mut out) })?;
198        Ok(out)
199    }
200
201    /// Request the PCB serial number (call before [`pcbsn`](Device::pcbsn)).
202    pub fn request_pcbsn(&self, device: i32, bank: i32) -> Result<()> {
203        let mut h = self.handle;
204        check(unsafe { ffi::neoradio2_request_pcbsn(&mut h, device, bank) })
205    }
206
207    /// PCB serial number string.
208    pub fn pcbsn(&self, device: i32, bank: i32) -> Result<String> {
209        let mut buf = [0 as c_char; 17];
210        let mut h = self.handle;
211        check(unsafe { ffi::neoradio2_get_pcbsn(&mut h, device, bank, buf.as_mut_ptr()) })?;
212        Ok(cstr_to_string(&buf))
213    }
214}
215
216use crate::CalType;
217
218impl Device {
219    /// Request a fresh sensor sample for `device`/`bank`.
220    pub fn request_sensor_data(&self, device: i32, bank: i32, cal: CalType) -> Result<()> {
221        let mut h = self.handle;
222        check(unsafe { ffi::neoradio2_request_sensor_data(&mut h, device, bank, cal.as_raw()) })
223    }
224
225    /// Read a single sensor value as `f32`.
226    pub fn read_sensor_float(&self, device: i32, bank: i32) -> Result<f32> {
227        let mut out: f32 = 0.0;
228        let mut h = self.handle;
229        check(unsafe { ffi::neoradio2_read_sensor_float(&mut h, device, bank, &mut out) })?;
230        Ok(out)
231    }
232
233    /// Read the raw sensor sample as up to 64 integer values (device/mode specific).
234    pub fn read_sensor_array(&self, device: i32, bank: i32) -> Result<Vec<i32>> {
235        let mut arr = [0 as c_int; 64];
236        let mut len: c_int = arr.len() as c_int;
237        let mut h = self.handle;
238        check(unsafe {
239            ffi::neoradio2_read_sensor_array(&mut h, device, bank, arr.as_mut_ptr(), &mut len)
240        })?;
241        let n = (len.max(0) as usize).min(arr.len());
242        Ok(arr[..n].to_vec())
243    }
244
245    /// Write sensor/actuator data for `device`/`bank`.
246    pub fn write_sensor(&self, device: i32, bank: i32, data: &[u8]) -> Result<()> {
247        let mut h = self.handle;
248        check(unsafe {
249            ffi::neoradio2_write_sensor(
250                &mut h,
251                device,
252                bank,
253                data.as_ptr() as *mut u8,
254                data.len() as c_int,
255            )
256        })
257    }
258
259    /// Whether the last [`write_sensor`](Device::write_sensor) completed.
260    pub fn write_sensor_successful(&self, device: i32, bank: i32) -> Result<()> {
261        let mut h = self.handle;
262        check(unsafe { ffi::neoradio2_write_sensor_successful(&mut h, device, bank) })
263    }
264}
265
266impl Drop for Device {
267    fn drop(&mut self) {
268        unsafe { ffi::neoradio2_close(&mut self.handle) };
269    }
270}