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 182
//! A wrapper to a attribute file commonly in the `/sys/class/` directory.
use std::error::Error;
use std::fs::{self, File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::string::String;
use std::sync::{Arc, Mutex};
use crate::{utils::OrErr, Ev3Error, Ev3Result};
/// The root driver path `/sys/class/`.
const ROOT_PATH: &str = "/sys/class/";
/// A wrapper to a attribute file in the `/sys/class/` directory.
#[derive(Debug, Clone)]
pub struct Attribute {
file: Arc<Mutex<File>>,
}
impl Attribute {
/// Create a new `Attribute` instance for the given path.
pub fn from_path(path: &str) -> Ev3Result<Attribute> {
let stat = fs::metadata(&path)?;
let mode = stat.permissions().mode();
// Read permission for group (`ev3dev`)
let readable = mode & 0o040 == 0o040;
let writeable = mode & 0o020 == 0o020;
let file = OpenOptions::new()
.read(readable)
.write(writeable)
.open(&path)?;
Ok(Attribute {
file: Arc::new(Mutex::new(file)),
})
}
/// Create a new `Attribute` instance that wrappes
/// the file `/sys/class/{class_name}/{name}{attribute_name}`.
pub fn from_sys_class(
class_name: &str,
name: &str,
attribute_name: &str,
) -> Ev3Result<Attribute> {
let path = format!("{}{}/{}/{}", ROOT_PATH, class_name, name, attribute_name);
Attribute::from_path(&path)
}
/// Create a new `Attribute` instance by a discriminator attribute.
/// This can be used to manually access driver files or advances features like raw encoder values.
/// To find the correct file, this function iterates over all directories `$d` in `root_path` and
/// checks if the content of `root_path/$d/discriminator_path` equals `discriminator_value`. When a
/// match is found it returns an Attribute for file `root_path/$d/attribute_path`.
///
/// # Example
/// ```no_run
/// use ev3dev_lang_rust::Attribute;
///
/// # fn main() -> ev3dev_lang_rust::Ev3Result<()> {
/// // Get value0 of first connected color sensor.
/// let color_sensor_value = Attribute::from_path_with_discriminator(
/// "/sys/class/lego-sensor",
/// "value0",
/// "driver_name",
/// "lego-ev3-color"
/// )?;
/// println!("value0 of color sensor: {}", color_sensor_value.get::<i32>()?);
///
/// // Get raw rotation count of motor in port `A`.
/// // See https://github.com/ev3dev/ev3dev/wiki/Internals:-ev3dev-stretch for more infomation.
/// let rotation_count = Attribute::from_path_with_discriminator(
/// "/sys/bus/iio/devices",
/// "in_count0_raw",
/// "name",
/// "ev3-tacho"
/// )?;
/// println!("Raw rotation count: {}", rotation_count.get::<i32>()?);
///
/// # Ok(())
/// # }
/// ```
pub fn from_path_with_discriminator(
root_path: &str,
attribute_path: &str,
discriminator_path: &str,
discriminator_value: &str,
) -> Ev3Result<Attribute> {
let paths = fs::read_dir(root_path)?;
for path_result in paths {
let path_buf = path_result?.path();
let current_path = path_buf.to_str().or_err()?;
let discriminator_attribute =
Attribute::from_path(&format!("{}/{}", current_path, discriminator_path))?;
if discriminator_attribute.get::<String>()? == discriminator_value {
return Attribute::from_path(&format!("{}/{}", current_path, attribute_path));
}
}
Err(Ev3Error::InternalError {
msg: format!(
"Attribute `{}` at root path `{}` coult not be found!",
attribute_path, root_path
),
})
}
/// Returns the current value of the wrapped file.
fn get_str(&self) -> Ev3Result<String> {
let mut value = String::new();
let mut file = self.file.lock().unwrap();
file.seek(SeekFrom::Start(0))?;
file.read_to_string(&mut value)?;
Ok(value.trim_end().to_owned())
}
/// Sets the value of the wrapped file.
/// Returns a `Ev3Result::InternalError` if the file is not writable.
fn set_str(&self, value: &str) -> Ev3Result<()> {
let mut file = self.file.lock().unwrap();
file.seek(SeekFrom::Start(0))?;
file.write_all(value.as_bytes())?;
Ok(())
}
/// Returns the current value of the wrapped file.
/// The value is parsed to the type `T`.
/// Returns a `Ev3Result::InternalError` if the current value is not parsable to type `T`.
pub fn get<T>(&self) -> Ev3Result<T>
where
T: std::str::FromStr,
<T as std::str::FromStr>::Err: Error,
{
let value = self.get_str()?;
match value.parse::<T>() {
Ok(value) => Ok(value),
Err(e) => Err(Ev3Error::InternalError {
msg: format!("{}", e),
}),
}
}
/// Sets the value of the wrapped file.
/// The value is parsed from the type `T`.
/// Returns a `Ev3Result::InternalError` if the file is not writable.
pub fn set<T>(&self, value: T) -> Ev3Result<()>
where
T: std::string::ToString,
{
self.set_str(&value.to_string())
}
#[inline]
/// Sets the value of the wrapped file.
/// This function skips the string parsing of the `self.set<T>()` function.
/// Returns a `Ev3Result::InternalError` if the file is not writable.
pub fn set_str_slice(&self, value: &str) -> Ev3Result<()> {
self.set_str(value)
}
/// Returns a string vector representation of the wrapped file.
/// The file value is splitet at whitespaces.
pub fn get_vec(&self) -> Ev3Result<Vec<String>> {
let value = self.get_str()?;
let vec = value
.split_whitespace()
.map(|word| word.to_owned())
.collect();
Ok(vec)
}
/// Returns a C pointer to the wrapped file.
pub fn get_raw_fd(&self) -> RawFd {
self.file.lock().unwrap().as_raw_fd()
}
}