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 value of the wrapped file.
122    fn get_str(&self) -> Ev3Result<String> {
123        let mut value = String::new();
124        let mut file = self.file.lock().unwrap();
125        file.seek(SeekFrom::Start(0))?;
126        file.read_to_string(&mut value)?;
127        Ok(value.trim_end().to_owned())
128    }
129
130    /// Sets the value of the wrapped file.
131    /// Returns a `Ev3Result::InternalError` if the file is not writable.
132    fn set_str(&self, value: &str) -> Ev3Result<()> {
133        let mut file = self.file.lock().unwrap();
134        file.seek(SeekFrom::Start(0))?;
135        file.write_all(value.as_bytes())?;
136        Ok(())
137    }
138
139    /// Returns the current value of the wrapped file.
140    /// The value is parsed to the type `T`.
141    /// Returns a `Ev3Result::InternalError` if the current value is not parsable to type `T`.
142    pub fn get<T>(&self) -> Ev3Result<T>
143    where
144        T: std::str::FromStr,
145        <T as std::str::FromStr>::Err: Error,
146    {
147        let value = self.get_str()?;
148        match value.parse::<T>() {
149            Ok(value) => Ok(value),
150            Err(err) => Err(Ev3Error::InternalError {
151                msg: format!("{err}"),
152            }),
153        }
154    }
155
156    /// Sets the value of the wrapped file.
157    /// The value is parsed from the type `T`.
158    /// Returns a `Ev3Result::InternalError` if the file is not writable.
159    pub fn set<T>(&self, value: T) -> Ev3Result<()>
160    where
161        T: std::string::ToString,
162    {
163        self.set_str(&value.to_string())
164    }
165
166    #[inline]
167    /// Sets the value of the wrapped file.
168    /// This function skips the string parsing of the `self.set<T>()` function.
169    /// Returns a `Ev3Result::InternalError` if the file is not writable.
170    pub fn set_str_slice(&self, value: &str) -> Ev3Result<()> {
171        self.set_str(value)
172    }
173
174    /// Returns a string vector representation of the wrapped file.
175    /// The file value is splitted at whitespace's.
176    pub fn get_vec(&self) -> Ev3Result<Vec<String>> {
177        let value = self.get_str()?;
178        let vec = value
179            .split_whitespace()
180            .map(|word| word.to_owned())
181            .collect();
182        Ok(vec)
183    }
184
185    /// Returns a C pointer to the wrapped file.
186    pub fn get_raw_fd(&self) -> RawFd {
187        self.file.lock().unwrap().as_raw_fd()
188    }
189
190    /// Returns the path to the wrapped file.
191    pub fn get_file_path(&self) -> PathBuf {
192        self.file_path.clone()
193    }
194}