Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions uhd/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Unreleased

## Changed

* Exposed reading board, rx, and tx sensors

# [0.4.0](https://github.com/samcrow/uhd-rust/tree/uhd-v0.4.0) - 2026-06-09

## Changed
Expand Down
19 changes: 19 additions & 0 deletions uhd/examples/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,25 @@ fn probe_one_usrp(address: &str) -> Result<(), Box<dyn Error>> {
if let Ok(rate) = usrp.get_master_clock_rate(board) {
println!("Master clock rate {}", rate);
}
if let Err(e) = usrp.set_clock_source("gpsdo", board) {
println!("Failed to set clock source to gpsdo. This is expected if hardware doesn't support it: {e:?}");
}
if let Err(e) = usrp.set_time_source("gpsdo", board) {
println!("Failed to set time source to gpsdo. This is expected if hardware doesn't support it: {e:?}");
}
let clock_source = usrp.get_clock_source(board).ok();
if let Some(source) = &clock_source {
println!("Clock source: {}", source);
}
let time_source = usrp.get_time_source(board).ok();
if let Some(source) = &time_source {
println!("Time source: {}", source);
}
for sensor in usrp.get_mboard_sensor_names(board)? {
if let Ok(value) = usrp.get_mboard_sensor(&sensor, board) {
println!("Sensor {}: {}", sensor, value);
}
}
if let Ok(eeprom) = usrp.get_motherboard_eeprom(board) {
let keys = [
"hardware",
Expand Down
2 changes: 1 addition & 1 deletion uhd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub use stream::*;
pub use transmitter::{info::TransmitInfo, metadata::*, streamer::TransmitStreamer};
pub use tune_request::*;
pub use tune_result::TuneResult;
pub use usrp::Usrp;
pub use usrp::{SensorValue, Usrp};
pub use utils::alloc_boxed_slice;
// Common definitions

Expand Down
112 changes: 112 additions & 0 deletions uhd/src/usrp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,16 @@ impl Usrp {
Ok(vector.into())
}

/// Returns the typed value of a motherboard sensor
pub fn get_mboard_sensor(&self, name: &str, mboard: usize) -> Result<SensorValue, Error> {
let name = CString::new(name)?;
let mut sensor = SensorValueHandle::new()?;
check_status(unsafe {
uhd_sys::uhd_usrp_get_mboard_sensor(self.0, name.as_ptr(), mboard as _, &mut sensor.0)
})?;
sensor.value()
}

/// Returns the values stored in the motherboard EEPROM
pub fn get_motherboard_eeprom(&self, mboard: usize) -> Result<MotherboardEeprom, Error> {
let mut eeprom = MotherboardEeprom::default();
Expand Down Expand Up @@ -480,6 +490,16 @@ impl Usrp {
Ok(vector.into())
}

/// Returns the typed value of a receive sensor
pub fn get_rx_sensor(&self, name: &str, channel: usize) -> Result<SensorValue, Error> {
let name = CString::new(name)?;
let mut sensor = SensorValueHandle::new()?;
check_status(unsafe {
uhd_sys::uhd_usrp_get_rx_sensor(self.0, name.as_ptr(), channel as _, &mut sensor.0)
})?;
sensor.value()
}

/// Returns the frequency of a local oscillator
pub fn get_tx_lo_frequency(&self, channel: usize, name: &str) -> Result<f64, Error> {
let name = CString::new(name)?;
Expand Down Expand Up @@ -508,6 +528,16 @@ impl Usrp {
Ok(vector.into())
}

/// Returns the typed value of a transmit sensor
pub fn get_tx_sensor(&self, name: &str, channel: usize) -> Result<SensorValue, Error> {
let name = CString::new(name)?;
let mut sensor = SensorValueHandle::new()?;
check_status(unsafe {
uhd_sys::uhd_usrp_get_tx_sensor(self.0, name.as_ptr(), channel as _, &mut sensor.0)
})?;
sensor.value()
}

/// Opens a stream that can be used to receive samples
pub fn get_rx_stream<I>(
&mut self,
Expand Down Expand Up @@ -810,6 +840,88 @@ impl Drop for Usrp {
}
}

/// A typed sensor reading.
#[derive(Clone, Debug, PartialEq)]
pub enum SensorValue {
Boolean(bool),
Integer(i32),
Real(f64),
String(String),
}

impl std::fmt::Display for SensorValue {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Boolean(value) => value.fmt(formatter),
Self::Integer(value) => value.fmt(formatter),
Self::Real(value) => value.fmt(formatter),
Self::String(value) => value.fmt(formatter),
}
}
}

struct SensorValueHandle(uhd_sys::uhd_sensor_value_handle);

impl SensorValueHandle {
fn new() -> Result<Self, Error> {
let mut handle = ptr::null_mut();
check_status(unsafe { uhd_sys::uhd_sensor_value_make(&mut handle) })?;
Ok(Self(handle))
}

fn value(&self) -> Result<SensorValue, Error> {
use uhd_sys::uhd_sensor_value_data_type_t as data_type;

let mut sensor_type = 0;
check_status(unsafe { uhd_sys::uhd_sensor_value_data_type(self.0, &mut sensor_type) })?;

match sensor_type {
data_type::UHD_SENSOR_VALUE_BOOLEAN => {
let mut value = false;
check_status(unsafe { uhd_sys::uhd_sensor_value_to_bool(self.0, &mut value) })?;
Ok(SensorValue::Boolean(value))
}
data_type::UHD_SENSOR_VALUE_INTEGER => {
let mut value = 0;
check_status(unsafe { uhd_sys::uhd_sensor_value_to_int(self.0, &mut value) })?;
Ok(SensorValue::Integer(value))
}
data_type::UHD_SENSOR_VALUE_REALNUM => {
let mut value = 0.0;
check_status(unsafe { uhd_sys::uhd_sensor_value_to_realnum(self.0, &mut value) })?;
Ok(SensorValue::Real(value))
}
data_type::UHD_SENSOR_VALUE_STRING => {
let value = copy_string(|buffer, length| unsafe {
uhd_sys::uhd_sensor_value_value(self.0, buffer, length as _)
})?;
Ok(SensorValue::String(value))
}
value => Err(Error::Unique(format!(
"Unknown UHD sensor value type: {}",
value
))),
}
}
}

impl Drop for SensorValueHandle {
fn drop(&mut self) {
let _ = unsafe { uhd_sys::uhd_sensor_value_free(&mut self.0) };
}
}

#[cfg(test)]
mod sensor_value_tests {
use super::{SensorValue, SensorValueHandle};

#[test]
fn empty_sensor_is_boolean_false() {
let sensor = SensorValueHandle::new().unwrap();
assert_eq!(sensor.value().unwrap(), SensorValue::Boolean(false));
}
}

// Thread safety: see https://files.ettus.com/manual/page_general.html#general_threading
// All functions associated with the Usrp struct are thread-safe
unsafe impl Send for Usrp {}
Expand Down