Skip to content

Commit

Permalink
Open Attribute struct for any path
Browse files Browse the repository at this point in the history
This idea comes from issue #14

- Rename `Attribute::new()` to `Attribute::from_sys_class()`
- Add `Attribute::from_path()`
- Add `Attribute::from_path_with_discriminator()`
- Add example for `Attribute::from_path_with_discriminator()`
  • Loading branch information
pixix4 committed Apr 26, 2021
1 parent 584ec6e commit f89a132
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 32 deletions.
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ev3dev-lang-rust"
version = "0.10.1"
version = "0.10.2"
authors = ["Lars Westermann <lars-westermann@live.de>"]

description = "Rust language bindings for ev3dev"
Expand All @@ -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"
]
5 changes: 5 additions & 0 deletions examples/attributes/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[target.armv5te-unknown-linux-gnueabi]
linker = "/usr/bin/arm-linux-gnueabi-gcc"

[term]
color = "always"
7 changes: 7 additions & 0 deletions examples/attributes/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "attributes"
version = "0.1.0"
authors = ["Lars Westermann <lars.westermann@tu-dresden.de>"]

[dependencies]
ev3dev-lang-rust = { path = "../.."}
4 changes: 4 additions & 0 deletions examples/attributes/Makefile
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions examples/attributes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# `Attribute` example

Simple example for `Attribute` usage

```bash
make
# Binary: ../../target/armv5te-unknown-linux-gnueabi/release/attributes
```
33 changes: 33 additions & 0 deletions examples/attributes/src/main.rs
Original file line number Diff line number Diff line change
@@ -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::<i32>()?
);
println!("Raw rotation count: {}", rotation_count.get::<i32>()?);

std::thread::sleep(std::time::Duration::from_secs(1));
}
}
92 changes: 81 additions & 11 deletions src/attriute.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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/";
Expand All @@ -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<Attribute> {
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<Attribute> {
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<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();
Expand Down
23 changes: 14 additions & 9 deletions src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
let port_address = port.address();

Expand All @@ -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::<String>()?.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::<String>()?;
if driver_name_vec.iter().any(|n| &driver_name == n) {
return Ok(name.to_owned());
Expand All @@ -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<String> {
pub fn find_name_by_driver(class_name: &str, driver_name_vec: &[&str]) -> Ev3Result<String> {
let mut names = Driver::find_names_by_driver(class_name, &driver_name_vec)?;

match names.len() {
Expand All @@ -88,15 +88,18 @@ 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<Vec<String>> {
pub fn find_names_by_driver(
class_name: &str,
driver_name_vec: &[&str],
) -> Ev3Result<Vec<String>> {
let paths = fs::read_dir(format!("{}{}", ROOT_PATH, class_name))?;

let mut found_names = Vec::new();
for path in paths {
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::<String>()?;
if driver_name_vec.iter().any(|n| &driver_name == n) {
Expand All @@ -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);
};
};
Expand Down
7 changes: 5 additions & 2 deletions src/findable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
let mut driver_name_vec = Vec::new();
$(
Expand All @@ -54,25 +55,27 @@ 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<Self> {
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)?;

Ok(Self::new(Driver::new($class_name, &name)))
}

/// Extract list of connected 'Self'
#[allow(clippy::vec_init_then_push)]
pub fn list() -> Ev3Result<Vec<Self>> {
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)))
Expand Down
9 changes: 5 additions & 4 deletions src/led.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion src/sensors/compass_sensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
1 change: 0 additions & 1 deletion src/sensors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit f89a132

Please sign in to comment.