Skip to main content

ev3dev_lang_rust/
attribute.rs

1//! A wrapper to a attribute file commonly in the `/sys/class/` directory.
2use std::error::Error;
3use std::fs::{self, File, OpenOptions};
4use std::io::{Read, Seek, SeekFrom, Write};
5use std::os::unix::fs::PermissionsExt;
6use std::os::unix::io::{AsRawFd, RawFd};
7use std::path::{Path, PathBuf};
8use std::string::String;
9use std::sync::{Arc, Mutex};
10
11use crate::driver::DRIVER_PATH;
12use crate::utils::OrErr;
13use crate::{Ev3Error, Ev3Result};
14
15/// A wrapper to a attribute file in the `/sys/class/` directory.
16#[derive(Debug, Clone)]
17pub struct Attribute {
18    file_path: PathBuf,
19    file: Arc<Mutex<File>>,
20}
21
22impl Attribute {
23    /// Create a new `Attribute` instance for the given path.
24    pub fn from_path(path: &Path) -> Ev3Result<Attribute> {
25        let stat = fs::metadata(path)?;
26
27        let mode = stat.permissions().mode();
28
29        // Read permission for group (`ev3dev`)
30        let readable = mode & 0o040 == 0o040;
31        let writeable = mode & 0o020 == 0o020;
32
33        let file = OpenOptions::new()
34            .read(readable)
35            .write(writeable)
36            .open(path)?;
37
38        Ok(Attribute {
39            file_path: PathBuf::from(path),
40            file: Arc::new(Mutex::new(file)),
41        })
42    }
43
44    /// Create a new `Attribute` instance that wrap's
45    /// the file `/sys/class/{class_name}/{name}{attribute_name}`.
46    pub fn from_sys_class(
47        class_name: &str,
48        name: &str,
49        attribute_name: &str,
50    ) -> Ev3Result<Attribute> {
51        let path = Path::new(DRIVER_PATH)
52            .join(class_name)
53            .join(name)
54            .join(attribute_name);
55        Attribute::from_path(path.as_ref())
56    }
57
58    /// Create a new `Attribute` instance by a discriminator attribute.
59    /// This can be used to manually access driver files or advances features like raw encoder values.
60    /// To find the correct file, this function iterates over all directories `$d` in `driver_path` and
61    /// checks if the content of `driver_path/$d/discriminator_path` equals `discriminator_value`. When a
62    /// match is found it returns an Attribute for file `driver_path/$d/attribute_path`.
63    ///
64    /// # Example
65    /// ```no_run
66    /// use ev3dev_lang_rust::Attribute;
67    ///
68    /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> {
69    /// // Get value0 of first connected color sensor.
70    /// let color_sensor_value = Attribute::from_path_with_discriminator(
71    ///     "/sys/class/lego-sensor",
72    ///     "value0",
73    ///     "driver_name",
74    ///     "lego-ev3-color"
75    /// )?;
76    /// println!("value0 of color sensor: {}", color_sensor_value.get::<i32>()?);
77    ///
78    /// // Get raw rotation count of motor in port `A`.
79    /// // See https://github.com/ev3dev/ev3dev/wiki/Internals:-ev3dev-stretch for more information.
80    /// let rotation_count = Attribute::from_path_with_discriminator(
81    ///     "/sys/bus/iio/devices",
82    ///     "in_count0_raw",
83    ///     "name",
84    ///     "ev3-tacho"
85    /// )?;
86    /// println!("Raw rotation count: {}", rotation_count.get::<i32>()?);
87    ///
88    /// # Ok(())
89    /// # }
90    /// ```
91    pub fn from_path_with_discriminator(
92        driver_path: &str,
93        attribute_path: &str,
94        discriminator_path: &str,
95        discriminator_value: &str,
96    ) -> Ev3Result<Attribute> {
97        let paths = fs::read_dir(driver_path)?;
98
99        for path_result in paths {
100            let path_buf = path_result?.path();
101            let current_path = path_buf.to_str().or_err()?;
102
103            let discriminator_attribute = Attribute::from_path(&PathBuf::from(format!(
104                "{current_path}/{discriminator_path}"
105            )))?;
106
107            if discriminator_attribute.get::<String>()? == discriminator_value {
108                return Attribute::from_path(&PathBuf::from(format!(
109                    "{current_path}/{attribute_path}"
110                )));
111            }
112        }
113
114        Err(Ev3Error::InternalError {
115            msg: format!(
116                "Attribute `{attribute_path}` at driver path `{driver_path}` could not be found!"
117            ),
118        })
119    }
120
121    /// Returns the current content of the wrapped file.
122    pub fn get_bytes(&self) -> Ev3Result<Vec<u8>> {
123        let mut value: Vec<u8> = Vec::new();
124        let mut file = self.file.lock().unwrap();
125        file.seek(SeekFrom::Start(0))?;
126        file.read_to_end(&mut value)?;
127        Ok(value)
128    }
129
130    /// Returns the current value of the wrapped file.
131    fn get_str(&self) -> Ev3Result<String> {
132        let mut value = String::new();
133        let mut file = self.file.lock().unwrap();
134        file.seek(SeekFrom::Start(0))?;
135        file.read_to_string(&mut value)?;
136        Ok(value.trim_end().to_owned())
137    }
138
139    /// Sets the value of the wrapped file.
140    /// Returns a `Ev3Result::InternalError` if the file is not writable.
141    fn set_str(&self, value: &str) -> Ev3Result<()> {
142        let mut file = self.file.lock().unwrap();
143        file.seek(SeekFrom::Start(0))?;
144        file.write_all(value.as_bytes())?;
145        Ok(())
146    }
147
148    /// Returns the current value of the wrapped file.
149    /// The value is parsed to the type `T`.
150    /// Returns a `Ev3Result::InternalError` if the current value is not parsable to type `T`.
151    pub fn get<T>(&self) -> Ev3Result<T>
152    where
153        T: std::str::FromStr,
154        <T as std::str::FromStr>::Err: Error,
155    {
156        let value = self.get_str()?;
157        match value.parse::<T>() {
158            Ok(value) => Ok(value),
159            Err(err) => Err(Ev3Error::InternalError {
160                msg: format!("{err}"),
161            }),
162        }
163    }
164
165    /// Sets the value of the wrapped file.
166    /// The value is parsed from the type `T`.
167    /// Returns a `Ev3Result::InternalError` if the file is not writable.
168    pub fn set<T>(&self, value: T) -> Ev3Result<()>
169    where
170        T: std::string::ToString,
171    {
172        self.set_str(&value.to_string())
173    }
174
175    #[inline]
176    /// Sets the value of the wrapped file.
177    /// This function skips the string parsing of the `self.set<T>()` function.
178    /// Returns a `Ev3Result::InternalError` if the file is not writable.
179    pub fn set_str_slice(&self, value: &str) -> Ev3Result<()> {
180        self.set_str(value)
181    }
182
183    /// Returns a string vector representation of the wrapped file.
184    /// The file value is splitted at whitespace's.
185    pub fn get_vec(&self) -> Ev3Result<Vec<String>> {
186        let value = self.get_str()?;
187        let vec = value
188            .split_whitespace()
189            .map(|word| word.to_owned())
190            .collect();
191        Ok(vec)
192    }
193
194    /// Returns a C pointer to the wrapped file.
195    pub fn get_raw_fd(&self) -> RawFd {
196        self.file.lock().unwrap().as_raw_fd()
197    }
198
199    /// Returns the path to the wrapped file.
200    pub fn get_file_path(&self) -> PathBuf {
201        self.file_path.clone()
202    }
203}