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}