diff --git a/Cargo.toml b/Cargo.toml index 4c5bfbb..5181e04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ev3dev-lang-rust" -version = "0.10.1" +version = "0.10.2" authors = ["Lars Westermann "] description = "Rust language bindings for ev3dev" @@ -24,8 +24,9 @@ image = { version = "0.23.8", optional = true } [workspace] members = [ "ev3dev_lang_rust_derive", - "examples/color-sensor", + "examples/attributes", "examples/buttons", - "examples/screen", - "examples/infrared-sensor" + "examples/color-sensor", + "examples/infrared-sensor", + "examples/screen" ] diff --git a/examples/attributes/.cargo/config b/examples/attributes/.cargo/config new file mode 100644 index 0000000..66536f3 --- /dev/null +++ b/examples/attributes/.cargo/config @@ -0,0 +1,5 @@ +[target.armv5te-unknown-linux-gnueabi] +linker = "/usr/bin/arm-linux-gnueabi-gcc" + +[term] +color = "always" diff --git a/examples/attributes/Cargo.toml b/examples/attributes/Cargo.toml new file mode 100644 index 0000000..7ca0df2 --- /dev/null +++ b/examples/attributes/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "attributes" +version = "0.1.0" +authors = ["Lars Westermann "] + +[dependencies] +ev3dev-lang-rust = { path = "../.."} diff --git a/examples/attributes/Makefile b/examples/attributes/Makefile new file mode 100644 index 0000000..010cda2 --- /dev/null +++ b/examples/attributes/Makefile @@ -0,0 +1,4 @@ +.PHONY: all clean + +all: + docker run --rm -v $(PWD)/../../:/ev3dev-lang-rust/ -w /ev3dev-lang-rust/examples/attributes pixix4/ev3dev-rust cargo build --release --target armv5te-unknown-linux-gnueabi diff --git a/examples/attributes/README.md b/examples/attributes/README.md new file mode 100644 index 0000000..4641276 --- /dev/null +++ b/examples/attributes/README.md @@ -0,0 +1,8 @@ +# `Attribute` example + +Simple example for `Attribute` usage + +```bash +make +# Binary: ../../target/armv5te-unknown-linux-gnueabi/release/attributes +``` \ No newline at end of file diff --git a/examples/attributes/src/main.rs b/examples/attributes/src/main.rs new file mode 100644 index 0000000..84cbcfe --- /dev/null +++ b/examples/attributes/src/main.rs @@ -0,0 +1,33 @@ +extern crate ev3dev_lang_rust; + +use ev3dev_lang_rust::Attribute; +use ev3dev_lang_rust::Ev3Result; + +fn main() -> 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", + )?; + + // 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", + )?; + + loop { + println!( + "value0 of color sensor: {}", + color_sensor_value.get::()? + ); + println!("Raw rotation count: {}", rotation_count.get::()?); + + std::thread::sleep(std::time::Duration::from_secs(1)); + } +} diff --git a/src/attriute.rs b/src/attriute.rs index f752cee..b5e64e0 100644 --- a/src/attriute.rs +++ b/src/attriute.rs @@ -1,4 +1,4 @@ -//! A wrapper to a attribute file in the `/sys/class/` directory. +//! 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}; @@ -7,7 +7,7 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::string::String; use std::sync::{Arc, Mutex}; -use crate::{Ev3Error, Ev3Result}; +use crate::{utils::OrErr, Ev3Error, Ev3Result}; /// The root driver path `/sys/class/`. const ROOT_PATH: &str = "/sys/class/"; @@ -19,28 +19,98 @@ pub struct Attribute { } impl Attribute { - /// Create a new `Attribute` instance that wrappes - /// the file `/sys/class/{class_name}/{name}{attribute_name}`. - pub fn new(class_name: &str, name: &str, attribute_name: &str) -> Ev3Result { - let filename = format!("{}{}/{}/{}", ROOT_PATH, class_name, name, attribute_name); - - let stat = fs::metadata(&filename)?; + /// Create a new `Attribute` instance for the given path. + pub fn from_path(path: &str) -> Ev3Result { + let stat = fs::metadata(&path)?; let mode = stat.permissions().mode(); - let readable = mode & 256 == 256; - let writeable = mode & 128 == 128; + // Read permission for group (`ev3dev`) + let readable = mode & 0o040 == 0o040; + let writeable = mode & 0o020 == 0o020; let file = OpenOptions::new() .read(readable) .write(writeable) - .open(&filename)?; + .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 { + 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::()?); + /// + /// // 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::()?); + /// + /// # Ok(()) + /// # } + /// ``` + pub fn from_path_with_discriminator( + root_path: &str, + attribute_path: &str, + discriminator_path: &str, + discriminator_value: &str, + ) -> Ev3Result { + 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::()? == 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 { let mut value = String::new(); diff --git a/src/driver.rs b/src/driver.rs index 2694210..a137975 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -38,7 +38,7 @@ impl Driver { pub fn find_name_by_port_and_driver( class_name: &str, port: &dyn Port, - driver_name_vec: &Vec<&str>, + driver_name_vec: &[&str], ) -> Ev3Result { let port_address = port.address(); @@ -48,10 +48,10 @@ impl Driver { let file_name = path?.file_name(); let name = file_name.to_str().or_err()?; - let address = Attribute::new(class_name, name, "address")?; + let address = Attribute::from_sys_class(class_name, name, "address")?; if address.get::()?.contains(&port_address) { - let driver = Attribute::new(class_name, name, "driver_name")?; + let driver = Attribute::from_sys_class(class_name, name, "driver_name")?; let driver_name = driver.get::()?; if driver_name_vec.iter().any(|n| &driver_name == n) { return Ok(name.to_owned()); @@ -69,7 +69,7 @@ impl Driver { /// /// Returns `Ev3Error::NotFound` if no such device exists. /// Returns `Ev3Error::MultipleMatches` if more then one matching device exists. - pub fn find_name_by_driver(class_name: &str, driver_name_vec: &Vec<&str>) -> Ev3Result { + pub fn find_name_by_driver(class_name: &str, driver_name_vec: &[&str]) -> Ev3Result { let mut names = Driver::find_names_by_driver(class_name, &driver_name_vec)?; match names.len() { @@ -88,7 +88,10 @@ impl Driver { } /// Returns the names of the devices with the given `class_name`. - pub fn find_names_by_driver(class_name: &str, driver_name_vec: &Vec<&str>) -> Ev3Result> { + pub fn find_names_by_driver( + class_name: &str, + driver_name_vec: &[&str], + ) -> Ev3Result> { let paths = fs::read_dir(format!("{}{}", ROOT_PATH, class_name))?; let mut found_names = Vec::new(); @@ -96,7 +99,7 @@ impl Driver { let file_name = path?.file_name(); let name = file_name.to_str().or_err()?; - let driver = Attribute::new(class_name, name, "driver_name")?; + let driver = Attribute::from_sys_class(class_name, name, "driver_name")?; let driver_name = driver.get::()?; if driver_name_vec.iter().any(|n| &driver_name == n) { @@ -113,9 +116,11 @@ impl Driver { let mut attributes = self.attributes.borrow_mut(); if !attributes.contains_key(attribute_name) { - if let Ok(v) = - Attribute::new(self.class_name.as_ref(), self.name.as_ref(), attribute_name) - { + if let Ok(v) = Attribute::from_sys_class( + self.class_name.as_ref(), + self.name.as_ref(), + attribute_name, + ) { attributes.insert(attribute_name.to_owned(), v); }; }; diff --git a/src/findable.rs b/src/findable.rs index b408462..8482c90 100644 --- a/src/findable.rs +++ b/src/findable.rs @@ -41,6 +41,7 @@ macro_rules! findable { } /// Try to get a `Self` on the given port. Returns `None` if port is not used or another device is connected. + #[allow(clippy::vec_init_then_push)] pub fn get(port: $port) -> Ev3Result { let mut driver_name_vec = Vec::new(); $( @@ -54,12 +55,13 @@ macro_rules! findable { } /// Try to find a `Self`. Only returns a motor if their is exactly one connected, `Error::NotFound` otherwise. + #[allow(clippy::vec_init_then_push)] pub fn find() -> Ev3Result { let mut driver_name_vec = Vec::new(); $( driver_name_vec.push($driver_name); )* - + let name = Driver::find_name_by_driver($class_name, &driver_name_vec).map_err(Self::map_error)?; @@ -67,12 +69,13 @@ macro_rules! findable { } /// Extract list of connected 'Self' + #[allow(clippy::vec_init_then_push)] pub fn list() -> Ev3Result> { let mut driver_name_vec = Vec::new(); $( driver_name_vec.push($driver_name); )* - + Ok(Driver::find_names_by_driver($class_name, &driver_name_vec)? .iter() .map(|name| Self::new(Driver::new($class_name, &name))) diff --git a/src/led.rs b/src/led.rs index 5d68215..7805e24 100644 --- a/src/led.rs +++ b/src/led.rs @@ -65,10 +65,11 @@ impl Led { } } - let left_red = Attribute::new("leds", left_red_name.as_str(), "brightness")?; - let left_green = Attribute::new("leds", left_green_name.as_str(), "brightness")?; - let right_red = Attribute::new("leds", right_red_name.as_str(), "brightness")?; - let right_green = Attribute::new("leds", right_green_name.as_str(), "brightness")?; + let left_red = Attribute::from_sys_class("leds", left_red_name.as_str(), "brightness")?; + let left_green = Attribute::from_sys_class("leds", left_green_name.as_str(), "brightness")?; + let right_red = Attribute::from_sys_class("leds", right_red_name.as_str(), "brightness")?; + let right_green = + Attribute::from_sys_class("leds", right_green_name.as_str(), "brightness")?; Ok(Led { left_red, diff --git a/src/sensors/compass_sensor.rs b/src/sensors/compass_sensor.rs index 1feaf99..f2f1c5f 100644 --- a/src/sensors/compass_sensor.rs +++ b/src/sensors/compass_sensor.rs @@ -15,7 +15,13 @@ impl CompassSensor { Self { driver, origin: 0 } } - findable!("lego-sensor", ["ht-nxt-compass"], SensorPort, "Compass", "in"); + findable!( + "lego-sensor", + ["ht-nxt-compass"], + SensorPort, + "Compass", + "in" + ); /// Command for starting the calibration pub const COMMAND_START_CALIBRATION: &'static str = "BEGIN-CAL"; diff --git a/src/sensors/mod.rs b/src/sensors/mod.rs index 6fdf6c8..2681931 100644 --- a/src/sensors/mod.rs +++ b/src/sensors/mod.rs @@ -72,7 +72,6 @@ impl Port for SensorPort { /// Add a sensor mode constant with getter and setter macro_rules! sensor_mode { ($value:expr, $const_name:ident, $docstring:expr, $setter:ident, $getter:ident) => { - #[doc = $docstring] pub const $const_name: &'static str = $value;