├── .cargo └── config.toml ├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── docker └── Dockerfile ├── ev3dev_lang_rust_derive ├── Cargo.toml ├── README.md └── src │ └── lib.rs ├── examples ├── button_handlers.rs ├── buttons.rs ├── color-sensor.rs ├── custom-attributes.rs ├── infrared-sensor.rs ├── motors.rs └── screen.rs ├── src ├── attribute.rs ├── brickpi.rs ├── brickpi3.rs ├── device.rs ├── driver.rs ├── ev3.rs ├── ev3_button_functions.rs ├── findable.rs ├── lib.rs ├── motors │ ├── dc_motor_macro.rs │ ├── large_motor.rs │ ├── medium_motor.rs │ ├── mod.rs │ ├── servo_motor_macro.rs │ ├── tacho_motor.rs │ └── tacho_motor_macro.rs ├── power_supply.rs ├── screen.rs ├── sensors │ ├── color_sensor.rs │ ├── compass_sensor.rs │ ├── gyro_sensor.rs │ ├── hi_technic_color_sensor.rs │ ├── infrared_sensor.rs │ ├── ir_seeker_sensor.rs │ ├── light_sensor.rs │ ├── mod.rs │ ├── sensor.rs │ ├── touch_sensor.rs │ └── ultrasonic_sensor.rs ├── sound.rs ├── utils.rs └── wait.rs ├── tests.sh └── tests ├── brickpi.rs ├── brickpi3.rs ├── ev3.rs └── override-driver-path.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.armv5te-unknown-linux-gnueabi] 2 | linker = "/usr/bin/arm-linux-gnueabi-gcc" 3 | 4 | [target.armv5te-unknown-linux-musleabi] 5 | linker = "rust-lld" 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cargo defaults 2 | target 3 | **/*.rs.bk 4 | Cargo.lock 5 | vendor/ 6 | 7 | # IntelliJ IDEA 8 | *.iml 9 | .idea/* 10 | 11 | # VS Code 12 | .vscode/* 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ev3dev-lang-rust" 3 | version = "0.15.0" 4 | edition = "2021" 5 | authors = ["Lars Westermann "] 6 | 7 | description = "Rust language bindings for ev3dev" 8 | keywords = ["ev3", "ev3dev", "lego", "mindstorms"] 9 | categories = ["api-bindings", "embedded", "hardware-support"] 10 | 11 | license = "MIT" 12 | 13 | repository = "https://github.com/pixix4/ev3dev-lang-rust" 14 | readme = "README.md" 15 | 16 | [features] 17 | default = ["ev3"] 18 | screen = ["framebuffer", "image"] 19 | override-driver-path = [] 20 | ev3 = [] 21 | brickpi = [] 22 | brickpi3 = [] 23 | 24 | [dependencies] 25 | ev3dev-lang-rust-derive = { path = "ev3dev_lang_rust_derive", version = "0.10" } 26 | libc = "0.2" 27 | framebuffer = { version = "0.3", optional = true } 28 | image = { version = "0.25", optional = true } 29 | paste = "1.0" 30 | 31 | [workspace] 32 | members = ["ev3dev_lang_rust_derive"] 33 | 34 | [profile.release] 35 | lto = true 36 | strip = "debuginfo" 37 | opt-level = "z" 38 | 39 | [package.metadata.docs.rs] 40 | features = ["ev3", "screen"] 41 | rustdoc-args = ["--cfg", "docsrs"] 42 | 43 | [[example]] 44 | name = "buttons" 45 | required-features = ["ev3"] 46 | 47 | [[example]] 48 | name = "screen" 49 | required-features = ["screen"] 50 | 51 | [[test]] 52 | name = "ev3" 53 | required-features = ["ev3"] 54 | 55 | [[test]] 56 | name = "brickpi" 57 | required-features = ["brickpi"] 58 | 59 | [[test]] 60 | name = "brickpi3" 61 | required-features = ["brickpi3"] 62 | 63 | [[test]] 64 | name = "override-driver-path" 65 | required-features = ["override-driver-path"] 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Lars Westermann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | build: 4 | docker run --rm -v $(PWD):/build -w /build pixix4/ev3dev-rust:latest \ 5 | cargo build --release --examples --target armv5te-unknown-linux-musleabi 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust language bindings for ev3dev 2 | 3 | ![Build](https://github.com/pixix4/ev3dev-lang-rust/workflows/Build/badge.svg) 4 | [![Latest version](https://img.shields.io/crates/v/ev3dev-lang-rust.svg)](https://crates.io/crates/ev3dev-lang-rust) 5 | 6 | ## Notice 7 | 8 | To use this project with the BrickPi platform the corresponding feature has to be enabled. The features `ev3`, `brickpi` and `brickpi3` are mutual exclusive. 9 | ```toml 10 | [dependencies] 11 | ev3dev_lang_rust = { version="0.13.0" default-features=false, features=["brickpi"] } 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```rust 17 | extern crate ev3dev_lang_rust; 18 | 19 | use ev3dev_lang_rust::Ev3Result; 20 | use ev3dev_lang_rust::motors::{LargeMotor, MotorPort}; 21 | use ev3dev_lang_rust::sensors::ColorSensor; 22 | 23 | fn main() -> Ev3Result<()> { 24 | 25 | // Get large motor on port outA. 26 | let large_motor = LargeMotor::get(MotorPort::OutA)?; 27 | 28 | // Set command "run-direct". 29 | large_motor.run_direct()?; 30 | 31 | // Run motor. 32 | large_motor.set_duty_cycle_sp(50)?; 33 | 34 | // Find color sensor. Always returns the first recognized one. 35 | let color_sensor = ColorSensor::find()?; 36 | 37 | // Switch to rgb mode. 38 | color_sensor.set_mode_rgb_raw()?; 39 | 40 | // Get current rgb color tuple. 41 | println!("Current rgb color: {:?}", color_sensor.get_rgb()?); 42 | 43 | Ok(()) 44 | } 45 | ``` 46 | 47 | There is a [template repository](https://github.com/pixix4/ev3dev-lang-rust-template/) that contains all the required configurations for cross-compilation and performance/binary-size optimizations for this "Hello World" example. 48 | 49 | ## Supported features 50 | 51 | - Motors: 52 | - `LargeMotor` [`lego-ev3-l-motor`, `lego-nxt-motor`] 53 | - `MediumMotor` [`lego-ev3-m-motor`] 54 | - `TachoMotor`: Useful wrapper around `LargeMotor` and `MediumMotor` to make common functions easier to use 55 | - Sensors: 56 | - `ColorSensor` [`lego-ev3-color`] 57 | - `CompassSensor` [`ht-nxt-compass`] 58 | - `GyroSensor` [`lego-ev3-gyro`] 59 | - `InfraredSensor` [`lego-ev3-ir`] 60 | - `IrSeekerSensor` [`ht-nxt-ir-seek-v2`] 61 | - `LightSensor` [`lego-nxt-light`] 62 | - `TouchSensor` [`lego-ev3-touch`, `lego-nxt-touch`] 63 | - `UltrasonicSensor` [`lego-ev3-us`, `lego-nxt-us`] 64 | - Utility 65 | - `Button`: Provides access to the integrated buttons on the ev3 brick 66 | - `Led`: Provides access to the integrated led's on the ev3 brick 67 | - `PowerSupply`: Provides access to the power supply information 68 | - `Screen`: Provides access to the integrated display of the ev3 brick 69 | - `sound`: Provides access to the integrated speakers of the ev3 brick 70 | 71 | ## Cross compilation for the ev3 robot - using `musl` toolchain 72 | 73 | 1. Install the `armv5te-musl` toolchain 74 | 75 | ```bash 76 | rustup target add armv5te-unknown-linux-musleabi 77 | ``` 78 | 79 | 2. Create `.cargo/config.toml` with the following content 80 | 81 | ```toml 82 | [build] 83 | target = "armv5te-unknown-linux-musleabi" 84 | 85 | [target.armv5te-unknown-linux-musleabi] 86 | linker = "rust-lld" 87 | ``` 88 | 89 | 3. Build binary 90 | 91 | ```bash 92 | cargo build --release 93 | ``` 94 | 95 | The `--release` flag is optional. However, it can speed up the execution time by a factor of 30. 96 | The target binary is now in `target/armv5te-unknown-linux-musleabi/release/{application_name}`. 97 | 98 | ## Cross compilation for the ev3 robot - using docker 99 | 100 | If you need to cross compile other dependencies (eg. `openssl` or `paho-mqtt`) it is much easier to use a complete cross compile toolchain. For this you can use the provided docker image `pixix4/ev3dev-rust:latest`. 101 | 102 | 1. Setup a docker environment 103 | 104 | 2. Create `.cargo/config.toml` with the following content 105 | 106 | ```toml 107 | [build] 108 | target = "armv5te-unknown-linux-gnueabi" 109 | 110 | [target.armv5te-unknown-linux-gnueabi] 111 | linker = "/usr/bin/arm-linux-gnueabi-gcc" 112 | ``` 113 | 114 | 3. Build binary 115 | 116 | ```bash 117 | docker run --rm -it -v $(pwd):/build -w /build pixix4/ev3dev-rust:latest \ 118 | cargo build --release 119 | ``` 120 | 121 | The `--release` flag is optional. However, it can speed up the execution time by a factor of 30. 122 | The target binary is now in `target/armv5te-unknown-linux-gnueabi/release/{application_name}`. 123 | 124 | If you do this you will notice that each build gets stuck at `Updating crates.io index` for a long time. To speed up this step you can use the vendoring mechanic of cargo. 125 | 126 | ```bash 127 | cargo vendor 128 | ``` 129 | 130 | Execute the above command and add this additional config to `.cargo/config`. 131 | 132 | ```toml 133 | [source.crates-io] 134 | replace-with = "vendored-sources" 135 | 136 | [source.vendored-sources] 137 | directory = "vendor" 138 | ``` 139 | 140 | ## Optimize binary size 141 | 142 | - Enable "fat" link time optimizations and strip debug symbols: 143 | By default rust only performs lto for each crate individually. To enable global lto (which result in a much more aggressive dead code elimination) add the following additional config to your `Cargo.toml`. This also removes additional debug symbols from the binary. With this you can reduce the binary size of the "Hello World" example by more than 90%. 144 | 145 | ```toml 146 | [profile.release] 147 | lto = true 148 | strip = "debuginfo" 149 | ``` 150 | 151 | The strip option requires rust `1.59.0`. If you are using an older version you can do this manually with docker: 152 | 153 | ```bash 154 | # Run in interactive docker shell 155 | docker run -it --rm -v $(PWD):/build/ -w /build pixix4/ev3dev-rust 156 | /usr/bin/arm-linux-gnueabi-strip /build/target/armv5te-unknown-linux-gnueabi/release/{application_name} 157 | 158 | # Run directly (e.g. via Makefile) 159 | docker run --rm -v $(PWD):/build/ -w /build pixix4/ev3dev-rust \ 160 | /usr/bin/arm-linux-gnueabi-strip /build/target/armv5te-unknown-linux-gnueabi/release/{application_name} 161 | ``` 162 | 163 | ## Editor support 164 | 165 | If you have problems with code completion or inline documentation with rust analyzer it may help to enable to following settings: 166 | 167 | ```json 168 | { 169 | "rust-analyzer.cargo.loadOutDirsFromCheck": true, 170 | "rust-analyzer.procMacro.enable": true 171 | } 172 | ``` 173 | 174 | (Example from VSCode `settings.json`) 175 | 176 | ## Docs.rs documentation 177 | 178 | To build the complete documentation (including the `screen` feature) use: 179 | 180 | ```bash 181 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features ev3,screen 182 | ``` 183 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:trixie-slim 2 | 3 | RUN dpkg --add-architecture armel 4 | 5 | RUN apt-get update 6 | 7 | RUN apt-get --yes install curl cmake pkg-config clang g++ g++-arm-linux-gnueabi crossbuild-essential-armel libssl-dev libssl-dev:armel libclang-dev \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | 11 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 12 | 13 | ENV PATH "$PATH:/root/.cargo/bin" 14 | 15 | RUN rustup target add armv5te-unknown-linux-musleabi armv5te-unknown-linux-gnueabi 16 | 17 | ENV PKG_CONFIG_SYSROOT_DIR /usr/arm-linux-gnueabi/ 18 | ENV CC_armv5te_unknown_linux_gnueabi arm-linux-gnueabi-gcc 19 | ENV CXX_armv5te_unknown_linux_gnueabi arm-linux-gnueabi-g++ 20 | -------------------------------------------------------------------------------- /ev3dev_lang_rust_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ev3dev-lang-rust-derive" 3 | version = "0.10.0" 4 | edition = "2021" 5 | authors = ["Lars Westermann "] 6 | 7 | description = "Derive macros for ev3dev_lang_rust" 8 | 9 | license = "MIT" 10 | 11 | repository = "https://github.com/pixix4/ev3dev-lang-rust" 12 | readme = "README.md" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | proc-macro2 = "1.0" 19 | quote = "1.0" 20 | syn = "1.0" 21 | -------------------------------------------------------------------------------- /ev3dev_lang_rust_derive/README.md: -------------------------------------------------------------------------------- 1 | # Derive macros for ev3dev_lang_rust 2 | 3 | [![Build Status](https://travis-ci.org/pixix4/ev3dev-lang-rust.svg?branch=master)](https://travis-ci.org/pixix4/ev3dev-lang-rust) 4 | [![Latest version](https://img.shields.io/crates/v/ev3dev-lang-rust-derive.svg)](https://crates.io/crates/ev3dev-lang-rust-derive) 5 | 6 | This crate provides some derive macros to simplify the codebase. 7 | 8 | The following traits can be automatically derived: 9 | 10 | * `Device` 11 | * `Findable` 12 | * `Motor` 13 | * `TachoMotor` 14 | * `ServoMotor` 15 | * `DcMotor` 16 | * `Sensor` 17 | 18 | The findable derive needs 3 additional attributes. 19 | * `class_name: &str` 20 | * `driver_name: &str` 21 | * `port: dyn ev3dev_lang_rust::Motor` 22 | 23 | ## Example 24 | 25 | The functionallity of the `LargeMotor` struct consists complitly through derives: 26 | 27 | ```rust 28 | #[derive(Debug, Clone, Device, Findable, Motor, TachoMotor)] 29 | #[class_name = "tacho-motor"] 30 | #[driver_name = "lego-ev3-l-motor"] 31 | #[port = "crate::motors::MotorPort"] 32 | pub struct LargeMotor { 33 | driver: Driver, 34 | } 35 | ``` -------------------------------------------------------------------------------- /ev3dev_lang_rust_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate proc_macro2; 3 | extern crate quote; 4 | extern crate syn; 5 | 6 | use proc_macro::TokenStream; 7 | use quote::quote; 8 | 9 | #[proc_macro_derive(Device)] 10 | pub fn device_macro_derive(input: TokenStream) -> TokenStream { 11 | let ast: syn::DeriveInput = syn::parse(input).unwrap(); 12 | 13 | let name = &ast.ident; 14 | 15 | let gen = quote! { 16 | impl Device for #name { 17 | fn get_attribute(&self, name: &str) -> Attribute { 18 | self.driver.get_attribute(name) 19 | } 20 | } 21 | }; 22 | gen.into() 23 | } 24 | 25 | #[proc_macro_derive(Sensor)] 26 | pub fn sensor_macro_derive(input: TokenStream) -> TokenStream { 27 | let ast: syn::DeriveInput = syn::parse(input).unwrap(); 28 | 29 | let name = &ast.ident; 30 | let gen = quote! { 31 | impl Sensor for #name {} 32 | }; 33 | gen.into() 34 | } 35 | -------------------------------------------------------------------------------- /examples/button_handlers.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | 3 | use ev3dev_lang_rust::{Button, Ev3Result}; 4 | 5 | fn main() -> Ev3Result<()> { 6 | let mut button = Button::new()?; 7 | 8 | button.set_change_handler(|pressed_buttons| println!("Pressed buttons: {:?}", pressed_buttons)); 9 | 10 | button.set_up_handler(|is_pressed| println!("Button 'up' is pressed: {}", is_pressed)); 11 | button.set_down_handler(|is_pressed| println!("Button 'down' is pressed: {}", is_pressed)); 12 | button.set_left_handler(|is_pressed| println!("Button 'left' is pressed: {}", is_pressed)); 13 | button.set_right_handler(|is_pressed| println!("Button 'right' is pressed: {}", is_pressed)); 14 | button.set_enter_handler(|is_pressed| println!("Button 'enter' is pressed: {}", is_pressed)); 15 | button.set_backspace_handler(|is_pressed| { 16 | println!("Button 'backspace' is pressed: {}", is_pressed) 17 | }); 18 | button.set_backspace_handler(|is_pressed| { 19 | println!("Button 'backspace' is pressed: {}", is_pressed) 20 | }); 21 | 22 | loop { 23 | button.process(); 24 | std::thread::sleep(std::time::Duration::from_millis(100)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/buttons.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | 3 | use ev3dev_lang_rust::{Button, Ev3Result}; 4 | 5 | fn main() -> Ev3Result<()> { 6 | let button = Button::new()?; 7 | 8 | loop { 9 | button.process(); 10 | 11 | println!( 12 | "{}, {}, {}, {}, {}, {}", 13 | button.is_up(), 14 | button.is_down(), 15 | button.is_left(), 16 | button.is_right(), 17 | button.is_enter(), 18 | button.is_backspace(), 19 | ); 20 | println!("{:?}", button.get_pressed_buttons()); 21 | 22 | std::thread::sleep(std::time::Duration::from_secs(1)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/color-sensor.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | 3 | use ev3dev_lang_rust::sensors::ColorSensor; 4 | use ev3dev_lang_rust::Ev3Result; 5 | 6 | fn main() -> Ev3Result<()> { 7 | let color_sensor = ColorSensor::find()?; 8 | color_sensor.set_mode_rgb_raw()?; 9 | 10 | loop { 11 | println!("{:?}", color_sensor.get_rgb()?); 12 | 13 | std::thread::sleep(std::time::Duration::from_secs(1)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/custom-attributes.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | 3 | use ev3dev_lang_rust::Attribute; 4 | use ev3dev_lang_rust::Ev3Result; 5 | 6 | fn main() -> Ev3Result<()> { 7 | // Get value0 of first connected color sensor. 8 | let color_sensor_value = Attribute::from_path_with_discriminator( 9 | "/sys/class/lego-sensor", 10 | "value0", 11 | "driver_name", 12 | "lego-ev3-color", 13 | )?; 14 | 15 | // Get raw rotation count of motor in port `A`. 16 | // See https://github.com/ev3dev/ev3dev/wiki/Internals:-ev3dev-stretch for more infomation. 17 | let rotation_count = Attribute::from_path_with_discriminator( 18 | "/sys/bus/iio/devices", 19 | "in_count0_raw", 20 | "name", 21 | "ev3-tacho", 22 | )?; 23 | 24 | loop { 25 | println!( 26 | "value0 of color sensor: {}", 27 | color_sensor_value.get::()? 28 | ); 29 | println!("Raw rotation count: {}", rotation_count.get::()?); 30 | 31 | std::thread::sleep(std::time::Duration::from_secs(1)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/infrared-sensor.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | 3 | use ev3dev_lang_rust::sensors::{BeaconSeeker, InfraredSensor, RemoteControl}; 4 | use ev3dev_lang_rust::Ev3Result; 5 | 6 | fn main() -> Ev3Result<()> { 7 | let sensor = InfraredSensor::find()?; 8 | 9 | remote(sensor)?; 10 | // seeker(sensor)?; 11 | 12 | Ok(()) 13 | } 14 | 15 | #[allow(dead_code)] 16 | fn remote(sensor: InfraredSensor) -> Ev3Result<()> { 17 | let remote = RemoteControl::new(sensor, 1)?; 18 | 19 | loop { 20 | remote.process()?; 21 | println!( 22 | "Button: {:?},{:?},{:?},{:?},{:?}", 23 | remote.is_red_up(), 24 | remote.is_red_down(), 25 | remote.is_beacon(), 26 | remote.is_blue_down(), 27 | remote.is_blue_up(), 28 | ); 29 | 30 | std::thread::sleep(std::time::Duration::from_secs(1)); 31 | } 32 | } 33 | 34 | #[allow(dead_code)] 35 | fn seeker(sensor: InfraredSensor) -> Ev3Result<()> { 36 | let seeker = BeaconSeeker::new(sensor, 1)?; 37 | 38 | loop { 39 | println!("Button: {:?}", seeker.get_heading_and_distance()); 40 | 41 | std::thread::sleep(std::time::Duration::from_secs(1)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/motors.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | 3 | use ev3dev_lang_rust::motors::{LargeMotor, MediumMotor, MotorPort, TachoMotor}; 4 | use ev3dev_lang_rust::Ev3Result; 5 | 6 | fn main() -> Ev3Result<()> { 7 | let large_motor = LargeMotor::get(MotorPort::OutA)?; 8 | let medium_motor = MediumMotor::get(MotorPort::OutB)?; 9 | 10 | // Set the initial speed so that the motors will move 11 | large_motor.set_speed_sp(300)?; 12 | medium_motor.set_speed_sp(300)?; 13 | 14 | large_motor.run_to_rel_pos(Some(360))?; 15 | medium_motor.run_to_rel_pos(Some(180))?; 16 | 17 | #[cfg(target_os = "linux")] 18 | large_motor.wait_until_not_moving(None); 19 | 20 | // If it does not matter which exact motor type is used, the wrapper `TachoMotor` can be used. 21 | 22 | let tacho_motor_1 = TachoMotor::get(MotorPort::OutA)?; 23 | let tacho_motor_2 = TachoMotor::get(MotorPort::OutB)?; 24 | 25 | tacho_motor_1.run_to_rel_pos(Some(360))?; 26 | tacho_motor_2.run_to_rel_pos(Some(180))?; 27 | 28 | #[cfg(target_os = "linux")] 29 | tacho_motor_1.wait_until_not_moving(None); 30 | 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /examples/screen.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | extern crate image; 3 | extern crate imageproc; 4 | 5 | use image::Rgb; 6 | 7 | use ev3dev_lang_rust::Screen; 8 | 9 | fn interpolate(a: Rgb, b: Rgb, progress: f32) -> Rgb { 10 | Rgb([ 11 | ((a.0[0] as f32) * (1.0 - progress) + (b.0[0] as f32) * progress) as u8, 12 | ((a.0[1] as f32) * (1.0 - progress) + (b.0[1] as f32) * progress) as u8, 13 | ((a.0[2] as f32) * (1.0 - progress) + (b.0[2] as f32) * progress) as u8, 14 | ]) 15 | } 16 | 17 | fn main() { 18 | let mut screen = Screen::new().unwrap(); 19 | 20 | for x in 10..20 { 21 | for y in 10..20 { 22 | screen.image.put_pixel(x, y, Rgb([0, 0, 0])); 23 | } 24 | } 25 | for x in 30..40 { 26 | for y in 10..20 { 27 | screen.image.put_pixel(x, y, Rgb([255, 0, 0])); 28 | } 29 | } 30 | 31 | for x in 10..20 { 32 | for y in 30..40 { 33 | screen.image.put_pixel(x, y, Rgb([0, 255, 0])); 34 | } 35 | } 36 | for x in 30..40 { 37 | for y in 30..40 { 38 | screen.image.put_pixel(x, y, Rgb([0, 0, 255])); 39 | } 40 | } 41 | 42 | imageproc::drawing::draw_antialiased_line_segment_mut( 43 | &mut screen.image, 44 | (10, 50), 45 | (50, 90), 46 | Rgb([0, 0, 0]), 47 | interpolate, 48 | ); 49 | 50 | imageproc::drawing::draw_filled_circle_mut(&mut screen.image, (100, 50), 40, Rgb([0, 0, 255])); 51 | 52 | screen.update(); 53 | } 54 | -------------------------------------------------------------------------------- /src/attribute.rs: -------------------------------------------------------------------------------- 1 | //! A wrapper to a attribute file commonly in the `/sys/class/` directory. 2 | use std::error::Error; 3 | use std::fs::{self, File, OpenOptions}; 4 | use std::io::{Read, Seek, SeekFrom, Write}; 5 | use std::os::unix::fs::PermissionsExt; 6 | use std::os::unix::io::{AsRawFd, RawFd}; 7 | use std::path::{Path, PathBuf}; 8 | use std::string::String; 9 | use std::sync::{Arc, Mutex}; 10 | 11 | use crate::driver::DRIVER_PATH; 12 | use crate::utils::OrErr; 13 | use crate::{Ev3Error, Ev3Result}; 14 | 15 | /// A wrapper to a attribute file in the `/sys/class/` directory. 16 | #[derive(Debug, Clone)] 17 | pub struct Attribute { 18 | file_path: PathBuf, 19 | file: Arc>, 20 | } 21 | 22 | impl Attribute { 23 | /// Create a new `Attribute` instance for the given path. 24 | pub fn from_path(path: &Path) -> Ev3Result { 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 { 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::()?); 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::()?); 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 { 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::()? == 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 { 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(&self) -> Ev3Result 143 | where 144 | T: std::str::FromStr, 145 | ::Err: Error, 146 | { 147 | let value = self.get_str()?; 148 | match value.parse::() { 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(&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()` 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> { 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 | } 195 | -------------------------------------------------------------------------------- /src/brickpi.rs: -------------------------------------------------------------------------------- 1 | //! EV3 specific features 2 | 3 | use std::fs; 4 | use std::path::Path; 5 | 6 | use crate::driver::DRIVER_PATH; 7 | use crate::utils::OrErr; 8 | use crate::{Attribute, Ev3Result}; 9 | 10 | /// Color type. 11 | pub type Color = u8; 12 | 13 | /// The led's on top of the EV3 brick. 14 | #[derive(Debug, Clone)] 15 | pub struct Led { 16 | led1: Attribute, 17 | led2: Attribute, 18 | } 19 | 20 | impl Led { 21 | /// Led off. 22 | pub const COLOR_OFF: Color = 0; 23 | 24 | /// Led color blue 25 | pub const COLOR_BLUE: Color = 255; 26 | 27 | /// Create a new instance of the `Led` struct. 28 | pub fn new() -> Ev3Result { 29 | let mut led1_name = String::new(); 30 | let mut led2_name = String::new(); 31 | 32 | let paths = fs::read_dir(Path::new(DRIVER_PATH).join("leds"))?; 33 | 34 | for path in paths { 35 | let file_name = path?.file_name(); 36 | let name = file_name.to_str().or_err()?.to_owned(); 37 | 38 | if name.contains("led1") { 39 | led1_name = name; 40 | } else if name.contains("led2") { 41 | led2_name = name; 42 | } 43 | } 44 | 45 | let led1 = Attribute::from_sys_class("leds", led1_name.as_str(), "brightness")?; 46 | let led2 = Attribute::from_sys_class("leds", led2_name.as_str(), "brightness")?; 47 | 48 | Ok(Led { led1, led2 }) 49 | } 50 | 51 | /// Returns the current brightness value of led1. 52 | pub fn get_led1(&self) -> Ev3Result { 53 | self.led1.get() 54 | } 55 | 56 | /// Sets the brightness value of led1. 57 | pub fn set_led1(&self, brightness: Color) -> Ev3Result<()> { 58 | self.led1.set(brightness) 59 | } 60 | 61 | /// Returns the current brightness value of led2. 62 | pub fn get_led2(&self) -> Ev3Result { 63 | self.led2.get() 64 | } 65 | 66 | /// Sets the brightness value of led2. 67 | pub fn set_led2(&self, brightness: Color) -> Ev3Result<()> { 68 | self.led2.set(brightness) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/brickpi3.rs: -------------------------------------------------------------------------------- 1 | //! EV3 specific features 2 | 3 | use std::fs; 4 | use std::path::Path; 5 | 6 | use crate::driver::DRIVER_PATH; 7 | use crate::utils::OrErr; 8 | use crate::{Attribute, Ev3Result}; 9 | 10 | /// Color type. 11 | pub type Color = u8; 12 | 13 | /// The led's on top of the EV3 brick. 14 | #[derive(Debug, Clone)] 15 | pub struct Led { 16 | led: Attribute, 17 | } 18 | 19 | impl Led { 20 | /// Led off. 21 | pub const COLOR_OFF: Color = 0; 22 | 23 | /// Led color blue 24 | pub const COLOR_AMBER: Color = 255; 25 | 26 | /// Create a new instance of the `Led` struct. 27 | pub fn new() -> Ev3Result { 28 | let mut led_name = String::new(); 29 | 30 | let paths = fs::read_dir(Path::new(DRIVER_PATH).join("leds"))?; 31 | 32 | for path in paths { 33 | let file_name = path?.file_name(); 34 | let name = file_name.to_str().or_err()?.to_owned(); 35 | 36 | if name.contains(":brick-status") && name.contains("led0:") { 37 | led_name = name; 38 | } 39 | } 40 | 41 | let led = Attribute::from_sys_class("leds", led_name.as_str(), "brightness")?; 42 | 43 | Ok(Led { led }) 44 | } 45 | 46 | /// Returns the current brightness value of led. 47 | pub fn get_led(&self) -> Ev3Result { 48 | self.led.get() 49 | } 50 | 51 | /// Sets the brightness value of led. 52 | pub fn set_led(&self, brightness: Color) -> Ev3Result<()> { 53 | self.led.set(brightness) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | use crate::{Attribute, Ev3Result}; 2 | 3 | /// The ev3dev device base trait 4 | pub trait Device { 5 | /// Returns the attribute wrapper for an attribute name. 6 | fn get_attribute(&self, name: &str) -> Attribute; 7 | 8 | /// Returns the name of the port that the motor is connected to. 9 | fn get_address(&self) -> Ev3Result { 10 | self.get_attribute("address").get() 11 | } 12 | 13 | /// Sends a command to the device controller. 14 | fn set_command(&self, command: &str) -> Ev3Result<()> { 15 | self.get_attribute("command").set_str_slice(command) 16 | } 17 | 18 | /// Returns a space separated list of commands that are supported by the device controller. 19 | fn get_commands(&self) -> Ev3Result> { 20 | self.get_attribute("commands").get_vec() 21 | } 22 | 23 | /// Returns the name of the driver that provides this device. 24 | fn get_driver_name(&self) -> Ev3Result { 25 | self.get_attribute("driver_name").get() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/driver.rs: -------------------------------------------------------------------------------- 1 | //! Helper struct that manages attributes. 2 | //! It creates an `Attribute` instance if it does not exists or uses a cached one. 3 | 4 | use std::cell::RefCell; 5 | use std::collections::HashMap; 6 | use std::fmt::{self, Debug}; 7 | use std::fs; 8 | use std::path::Path; 9 | use std::string::String; 10 | 11 | use crate::utils::OrErr; 12 | use crate::{Attribute, Ev3Error, Ev3Result, Port}; 13 | 14 | /// The driver path `/sys/class/`. 15 | #[cfg(not(feature = "override-driver-path"))] 16 | pub const DRIVER_PATH: &str = "/sys/class/"; 17 | 18 | /// The driver path that was set with the env variable `EV3DEV_DRIVER_PATH` (default value: `/sys/class/`). 19 | #[cfg(feature = "override-driver-path")] 20 | pub const DRIVER_PATH: &str = get_driver_path(); 21 | 22 | #[cfg(feature = "override-driver-path")] 23 | const fn get_driver_path() -> &'static str { 24 | let path = std::option_env!("EV3DEV_DRIVER_PATH"); 25 | if let Some(path) = path { 26 | path 27 | } else { 28 | "/sys/class/" 29 | } 30 | } 31 | 32 | /// Helper struct that manages attributes. 33 | /// It creates an `Attribute` instance if it does not exists or uses a cached one. 34 | #[derive(Clone)] 35 | pub struct Driver { 36 | class_name: String, 37 | name: String, 38 | attributes: RefCell>, 39 | } 40 | 41 | impl Driver { 42 | /// Returns a new `Driver`. 43 | /// All attributes created by this driver will use the path `/sys/class/{class_name}/{name}`. 44 | pub fn new(class_name: &str, name: &str) -> Driver { 45 | Driver { 46 | class_name: class_name.to_owned(), 47 | name: name.to_owned(), 48 | attributes: RefCell::new(HashMap::new()), 49 | } 50 | } 51 | 52 | /// Returns the name of the device with the given `class_name`, `driver_name` and at the given `port`. 53 | /// 54 | /// Returns `Ev3Error::NotFound` if no such device exists. 55 | pub fn find_name_by_port_and_driver( 56 | class_name: &str, 57 | port: &dyn Port, 58 | driver_name_vec: &[&str], 59 | ) -> Ev3Result { 60 | let port_address = port.address(); 61 | 62 | let paths = fs::read_dir(Path::new(DRIVER_PATH).join(class_name))?; 63 | 64 | for path in paths { 65 | let file_name = path?.file_name(); 66 | let name = file_name.to_str().or_err()?; 67 | 68 | let address = Attribute::from_sys_class(class_name, name, "address")?; 69 | 70 | if address.get::()?.contains(&port_address) { 71 | let driver = Attribute::from_sys_class(class_name, name, "driver_name")?; 72 | let driver_name = driver.get::()?; 73 | if driver_name_vec.iter().any(|n| &driver_name == n) { 74 | return Ok(name.to_owned()); 75 | } 76 | } 77 | } 78 | 79 | Err(Ev3Error::NotConnected { 80 | device: format!("{driver_name_vec:?}"), 81 | port: Some(port_address), 82 | }) 83 | } 84 | 85 | /// Returns the name of the device with the given `class_name`. 86 | /// 87 | /// Returns `Ev3Error::NotFound` if no such device exists. 88 | /// Returns `Ev3Error::MultipleMatches` if more then one matching device exists. 89 | pub fn find_name_by_driver(class_name: &str, driver_name_vec: &[&str]) -> Ev3Result { 90 | let mut names = Driver::find_names_by_driver(class_name, driver_name_vec)?; 91 | 92 | match names.len() { 93 | 0 => Err(Ev3Error::NotConnected { 94 | device: format!("{driver_name_vec:?}"), 95 | port: None, 96 | }), 97 | 1 => Ok(names 98 | .pop() 99 | .expect("Name vector should contains exactly one element")), 100 | _ => Err(Ev3Error::MultipleMatches { 101 | device: format!("{driver_name_vec:?}"), 102 | ports: names, 103 | }), 104 | } 105 | } 106 | 107 | /// Returns the names of the devices with the given `class_name`. 108 | pub fn find_names_by_driver( 109 | class_name: &str, 110 | driver_name_vec: &[&str], 111 | ) -> Ev3Result> { 112 | let paths = fs::read_dir(Path::new(DRIVER_PATH).join(class_name))?; 113 | 114 | let mut found_names = Vec::new(); 115 | for path in paths { 116 | let file_name = path?.file_name(); 117 | let name = file_name.to_str().or_err()?; 118 | 119 | let driver = Attribute::from_sys_class(class_name, name, "driver_name")?; 120 | 121 | let driver_name = driver.get::()?; 122 | if driver_name_vec.iter().any(|n| &driver_name == n) { 123 | found_names.push(name.to_owned()); 124 | } 125 | } 126 | 127 | Ok(found_names) 128 | } 129 | 130 | /// Return the `Attribute` wrapper for the given `attribute_name`. 131 | /// Creates a new one if it does not exist. 132 | pub fn get_attribute(&self, attribute_name: &str) -> Attribute { 133 | let mut attributes = self.attributes.borrow_mut(); 134 | 135 | if !attributes.contains_key(attribute_name) { 136 | if let Ok(v) = Attribute::from_sys_class( 137 | self.class_name.as_ref(), 138 | self.name.as_ref(), 139 | attribute_name, 140 | ) { 141 | attributes.insert(attribute_name.to_owned(), v); 142 | }; 143 | }; 144 | 145 | attributes 146 | .get(attribute_name) 147 | .expect("Internal error in the attribute map") 148 | .clone() 149 | } 150 | } 151 | 152 | impl Debug for Driver { 153 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 154 | write!( 155 | f, 156 | "Driver {{ class_name: {}, name: {} }}", 157 | self.class_name, self.name 158 | ) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/ev3.rs: -------------------------------------------------------------------------------- 1 | //! EV3 specific features 2 | 3 | use std::fs; 4 | 5 | use std::cell::RefCell; 6 | use std::collections::HashMap; 7 | use std::collections::HashSet; 8 | use std::fmt; 9 | use std::fs::File; 10 | use std::os::unix::io::AsRawFd; 11 | use std::path::Path; 12 | use std::rc::Rc; 13 | 14 | use paste::paste; 15 | 16 | use crate::driver::DRIVER_PATH; 17 | use crate::utils::OrErr; 18 | use crate::{Attribute, Ev3Result}; 19 | 20 | /// Color type. 21 | pub type Color = (u8, u8); 22 | 23 | /// The led's on top of the EV3 brick. 24 | #[derive(Debug, Clone)] 25 | pub struct Led { 26 | left_red: Attribute, 27 | left_green: Attribute, 28 | right_red: Attribute, 29 | right_green: Attribute, 30 | } 31 | 32 | impl Led { 33 | /// Led off. 34 | pub const COLOR_OFF: Color = (0, 0); 35 | 36 | /// Led color red 37 | pub const COLOR_RED: Color = (255, 0); 38 | 39 | /// Led color green. 40 | pub const COLOR_GREEN: Color = (0, 255); 41 | 42 | /// Led color amber. 43 | pub const COLOR_AMBER: Color = (255, 255); 44 | 45 | /// Led color orange. 46 | pub const COLOR_ORANGE: Color = (255, 128); 47 | 48 | /// LED color yellow. 49 | pub const COLOR_YELLOW: Color = (25, 255); 50 | 51 | /// Create a new instance of the `Led` struct. 52 | pub fn new() -> Ev3Result { 53 | let mut left_red_name = String::new(); 54 | let mut left_green_name = String::new(); 55 | let mut right_red_name = String::new(); 56 | let mut right_green_name = String::new(); 57 | 58 | let paths = fs::read_dir(Path::new(DRIVER_PATH).join("leds"))?; 59 | 60 | for path in paths { 61 | let file_name = path?.file_name(); 62 | let name = file_name.to_str().or_err()?.to_owned(); 63 | 64 | if name.contains(":brick-status") || name.contains(":ev3dev") { 65 | if name.contains("led0:") || name.contains("left:") { 66 | if name.contains("red:") { 67 | left_red_name = name; 68 | } else if name.contains("green:") { 69 | left_green_name = name 70 | } 71 | } else if name.contains("led1:") || name.contains("right:") { 72 | if name.contains("red:") { 73 | right_red_name = name 74 | } else if name.contains("green:") { 75 | right_green_name = name 76 | } 77 | } 78 | } 79 | } 80 | 81 | let left_red = Attribute::from_sys_class("leds", left_red_name.as_str(), "brightness")?; 82 | let left_green = Attribute::from_sys_class("leds", left_green_name.as_str(), "brightness")?; 83 | let right_red = Attribute::from_sys_class("leds", right_red_name.as_str(), "brightness")?; 84 | let right_green = 85 | Attribute::from_sys_class("leds", right_green_name.as_str(), "brightness")?; 86 | 87 | Ok(Led { 88 | left_red, 89 | left_green, 90 | right_red, 91 | right_green, 92 | }) 93 | } 94 | 95 | /// Returns the current red value of the left led. 96 | fn get_left_red(&self) -> Ev3Result { 97 | self.left_red.get() 98 | } 99 | 100 | /// Sets the red value of the left led. 101 | fn set_left_red(&self, brightness: u8) -> Ev3Result<()> { 102 | self.left_red.set(brightness) 103 | } 104 | 105 | /// Returns the current green value of the left led. 106 | fn get_left_green(&self) -> Ev3Result { 107 | self.left_green.get() 108 | } 109 | 110 | /// Sets the green value of the left led. 111 | fn set_left_green(&self, brightness: u8) -> Ev3Result<()> { 112 | self.left_green.set(brightness) 113 | } 114 | 115 | /// Returns the current red value of the right led. 116 | fn get_right_red(&self) -> Ev3Result { 117 | self.right_red.get() 118 | } 119 | 120 | /// Sets the red value of the right led. 121 | fn set_right_red(&self, brightness: u8) -> Ev3Result<()> { 122 | self.right_red.set(brightness) 123 | } 124 | 125 | /// Returns the current green value of the right led. 126 | fn get_right_green(&self) -> Ev3Result { 127 | self.right_green.get() 128 | } 129 | 130 | /// Sets the green value of the right led. 131 | fn set_right_green(&self, brightness: u8) -> Ev3Result<()> { 132 | self.right_green.set(brightness) 133 | } 134 | 135 | /// Returns the current color value of the left led. 136 | pub fn get_left_color(&self) -> Ev3Result { 137 | let red = self.get_left_red()?; 138 | let green = self.get_left_green()?; 139 | 140 | Ok((red, green)) 141 | } 142 | 143 | /// Sets the color value of the left led. 144 | pub fn set_left_color(&self, color: Color) -> Ev3Result<()> { 145 | self.set_left_red(color.0)?; 146 | self.set_left_green(color.1) 147 | } 148 | 149 | /// Returns the current color value of the right led. 150 | pub fn get_right_color(&self) -> Ev3Result { 151 | let red = self.get_right_red()?; 152 | let green = self.get_right_green()?; 153 | 154 | Ok((red, green)) 155 | } 156 | 157 | /// Sets the color value of the right led. 158 | pub fn set_right_color(&self, color: Color) -> Ev3Result<()> { 159 | self.set_right_red(color.0)?; 160 | self.set_right_green(color.1) 161 | } 162 | 163 | /// Returns the color value of both leds or `None` if they are different. 164 | pub fn get_color(&self) -> Ev3Result> { 165 | let left = self.get_left_color()?; 166 | let right = self.get_right_color()?; 167 | 168 | if left.0 == right.0 && left.1 == right.1 { 169 | Ok(Some(left)) 170 | } else { 171 | Ok(None) 172 | } 173 | } 174 | 175 | /// Sets the color value of both leds. 176 | pub fn set_color(&self, color: Color) -> Ev3Result<()> { 177 | self.set_left_color(color)?; 178 | self.set_right_color(color) 179 | } 180 | } 181 | 182 | const KEY_BUF_LEN: usize = 96; 183 | const EVIOCGKEY: u32 = 2_153_792_792; 184 | 185 | /// Helper struct for ButtonFileHandler. 186 | struct FileMapEntry { 187 | pub file: File, 188 | pub buffer_cache: [u8; KEY_BUF_LEN], 189 | } 190 | // Manually implement Debug cause `buffer_cache` does not implement Debug. 191 | impl fmt::Debug for FileMapEntry { 192 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 193 | f.debug_struct("FileMapEntry") 194 | .field("file", &self.file) 195 | .finish() 196 | } 197 | } 198 | 199 | /// Helper struct for ButtonFileHandler. 200 | #[derive(Debug)] 201 | struct ButtonMapEntry { 202 | pub file_name: String, 203 | pub key_code: u32, 204 | } 205 | 206 | type ButtonChangeHandler = Box)>; 207 | type ButtonHandler = Box; 208 | 209 | /// This implementation depends on the availability of the EVIOCGKEY ioctl 210 | /// to be able to read the button state buffer. See Linux kernel source 211 | /// in /include/uapi/linux/input.h for details. 212 | struct ButtonFileHandler { 213 | file_map: HashMap, 214 | button_map: HashMap, 215 | button_change_handler: Option, 216 | button_handlers: HashMap, 217 | pressed_buttons: HashSet, 218 | } 219 | 220 | impl std::fmt::Debug for ButtonFileHandler { 221 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 222 | f.debug_struct("ButtonFileHandler") 223 | .field("file_map", &self.file_map) 224 | .field("button_map", &self.button_map) 225 | .field( 226 | "button_change_handler", 227 | &self.button_change_handler.is_some(), 228 | ) 229 | .field("button_handlers", &self.button_map.keys()) 230 | .field("pressed_buttons", &self.pressed_buttons) 231 | .finish() 232 | } 233 | } 234 | 235 | impl ButtonFileHandler { 236 | /// Create a new instance. 237 | fn new() -> Self { 238 | ButtonFileHandler { 239 | file_map: HashMap::new(), 240 | button_map: HashMap::new(), 241 | button_change_handler: None, 242 | button_handlers: HashMap::new(), 243 | pressed_buttons: HashSet::new(), 244 | } 245 | } 246 | 247 | /// Add a button to the file handler. 248 | fn add_button(&mut self, name: &str, file_name: &str, key_code: u32) -> Ev3Result<()> { 249 | if !self.file_map.contains_key(file_name) { 250 | let file = File::open(file_name)?; 251 | let buffer_cache = [0u8; KEY_BUF_LEN]; 252 | 253 | self.file_map 254 | .insert(file_name.to_owned(), FileMapEntry { file, buffer_cache }); 255 | } 256 | 257 | self.button_map.insert( 258 | name.to_owned(), 259 | ButtonMapEntry { 260 | file_name: file_name.to_owned(), 261 | key_code, 262 | }, 263 | ); 264 | 265 | Ok(()) 266 | } 267 | 268 | /// Sets an event handler for the given button. 269 | fn set_button_handler(&mut self, name: &str, handler: Option) { 270 | if let Some(listener) = handler { 271 | self.button_handlers.insert(name.to_owned(), listener); 272 | } else { 273 | self.button_handlers.remove(name); 274 | } 275 | } 276 | 277 | /// Sets an event handler for any button state change. 278 | fn set_button_change_handler(&mut self, handler: Option) { 279 | self.button_change_handler = handler; 280 | } 281 | 282 | /// Gets a copy of the currently pressed buttons. 283 | fn get_pressed_buttons(&self) -> HashSet { 284 | self.pressed_buttons.clone() 285 | } 286 | 287 | /// Check if a button is pressed. 288 | fn get_button_state(&self, name: &str) -> bool { 289 | self.pressed_buttons.contains(name) 290 | } 291 | 292 | /// Check for currently pressed buttons. If the new state differs from the 293 | /// old state, call the appropriate button event handlers. 294 | fn process(&mut self) { 295 | for entry in self.file_map.values_mut() { 296 | unsafe { 297 | libc::ioctl( 298 | entry.file.as_raw_fd(), 299 | (EVIOCGKEY as i32).try_into().unwrap(), 300 | &mut entry.buffer_cache, 301 | ); 302 | } 303 | } 304 | 305 | let old_pressed_buttons = self.pressed_buttons.clone(); 306 | self.pressed_buttons.clear(); 307 | 308 | for ( 309 | btn_name, 310 | ButtonMapEntry { 311 | file_name, 312 | key_code, 313 | }, 314 | ) in self.button_map.iter() 315 | { 316 | let buffer = &self.file_map[file_name].buffer_cache; 317 | 318 | if (buffer[(key_code / 8) as usize] & 1 << (key_code % 8)) != 0 { 319 | self.pressed_buttons.insert(btn_name.to_owned()); 320 | } 321 | } 322 | 323 | let difference = old_pressed_buttons.symmetric_difference(&self.pressed_buttons); 324 | 325 | let mut difference_count = 0; 326 | for button in difference { 327 | difference_count += 1; 328 | if self.button_handlers.contains_key(button) { 329 | self.button_handlers[button](self.get_button_state(button)); 330 | } 331 | } 332 | 333 | if difference_count > 0 { 334 | if let Some(ref handler) = self.button_change_handler { 335 | handler(self.get_pressed_buttons()); 336 | } 337 | } 338 | } 339 | } 340 | 341 | /// Ev3 brick button handler. Opens the corresponding `/dev/input` file handlers. 342 | /// 343 | /// This implementation depends on the availability of the EVIOCGKEY ioctl 344 | /// to be able to read the button state buffer. See Linux kernel source 345 | /// in /include/uapi/linux/input.h for details. 346 | /// 347 | /// ```no_run 348 | /// use ev3dev_lang_rust::Button; 349 | /// use std::thread; 350 | /// use std::time::Duration; 351 | /// 352 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 353 | /// let mut button = Button::new()?; 354 | /// 355 | /// button.set_down_handler(|is_pressed| { 356 | /// println!("Is 'down' pressed: {is_pressed}"); 357 | /// }); 358 | /// 359 | /// loop { 360 | /// button.process(); 361 | /// 362 | /// println!("Is 'up' pressed: {}", button.is_up()); 363 | /// println!("Pressed buttons: {:?}", button.get_pressed_buttons()); 364 | /// 365 | /// thread::sleep(Duration::from_millis(100)); 366 | /// } 367 | /// # } 368 | /// ``` 369 | #[derive(Debug, Clone)] 370 | pub struct Button { 371 | button_handler: Rc>, 372 | } 373 | 374 | impl Button { 375 | /// Ev3 brick button handler. Opens the corresponding `/dev/input` file handlers. 376 | pub fn new() -> Ev3Result { 377 | let mut handler = ButtonFileHandler::new(); 378 | 379 | handler.add_button("up", "/dev/input/by-path/platform-gpio_keys-event", 103)?; 380 | handler.add_button("down", "/dev/input/by-path/platform-gpio_keys-event", 108)?; 381 | handler.add_button("left", "/dev/input/by-path/platform-gpio_keys-event", 105)?; 382 | handler.add_button("right", "/dev/input/by-path/platform-gpio_keys-event", 106)?; 383 | handler.add_button("enter", "/dev/input/by-path/platform-gpio_keys-event", 28)?; 384 | handler.add_button( 385 | "backspace", 386 | "/dev/input/by-path/platform-gpio_keys-event", 387 | 14, 388 | )?; 389 | 390 | Ok(Self { 391 | button_handler: Rc::new(RefCell::new(handler)), 392 | }) 393 | } 394 | 395 | /// Check for currently pressed buttons. If the new state differs from the 396 | /// old state, call the appropriate button event handlers. 397 | /// ```no_run 398 | /// use ev3dev_lang_rust::Button; 399 | /// use std::thread; 400 | /// use std::time::Duration; 401 | /// 402 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 403 | /// let mut button = Button::new()?; 404 | /// 405 | /// button.set_down_handler(|is_pressed| { 406 | /// println!("Is 'down' pressed: {is_pressed}"); 407 | /// }); 408 | /// 409 | /// loop { 410 | /// button.process(); 411 | /// 412 | /// println!("Is 'up' pressed: {}", button.is_up()); 413 | /// println!("Pressed buttons: {:?}", button.get_pressed_buttons()); 414 | /// 415 | /// thread::sleep(Duration::from_millis(100)); 416 | /// } 417 | /// # } 418 | /// ``` 419 | pub fn process(&self) { 420 | self.button_handler.borrow_mut().process() 421 | } 422 | 423 | /// Get all pressed buttons by name. 424 | /// 425 | /// ```no_run 426 | /// use ev3dev_lang_rust::Button; 427 | /// use std::thread; 428 | /// use std::time::Duration; 429 | /// 430 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 431 | /// let button = Button::new()?; 432 | /// 433 | /// loop { 434 | /// button.process(); 435 | /// println!("Pressed buttons: {:?}", button.get_pressed_buttons()); 436 | /// thread::sleep(Duration::from_millis(100)); 437 | /// } 438 | /// # } 439 | /// ``` 440 | pub fn get_pressed_buttons(&self) -> HashSet { 441 | self.button_handler.borrow().get_pressed_buttons() 442 | } 443 | 444 | /// Set an event handler, that is called by `process()` if any button state changes. 445 | /// Has a set of all pressed buttons as parameter. 446 | /// 447 | /// ```no_run 448 | /// use ev3dev_lang_rust::Button; 449 | /// use std::thread; 450 | /// use std::time::Duration; 451 | /// 452 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 453 | /// let mut button = Button::new()?; 454 | /// 455 | /// button.set_change_handler(|pressed_buttons| { 456 | /// println!("pressed buttons: {:?}", pressed_buttons); 457 | /// }); 458 | /// 459 | /// loop { 460 | /// button.process(); 461 | /// thread::sleep(Duration::from_millis(100)); 462 | /// } 463 | /// # } 464 | /// ``` 465 | pub fn set_change_handler(&mut self, handler: impl Fn(HashSet) + 'static) { 466 | self.button_handler 467 | .borrow_mut() 468 | .set_button_change_handler(Some(Box::new(handler))) 469 | } 470 | 471 | /// Removes the change event handler. 472 | pub fn remove_change_handler(&mut self) { 473 | self.button_handler 474 | .borrow_mut() 475 | .set_button_change_handler(None) 476 | } 477 | 478 | ev3_button_functions!(up); 479 | ev3_button_functions!(down); 480 | ev3_button_functions!(left); 481 | ev3_button_functions!(right); 482 | ev3_button_functions!(enter); 483 | ev3_button_functions!(backspace); 484 | } 485 | -------------------------------------------------------------------------------- /src/ev3_button_functions.rs: -------------------------------------------------------------------------------- 1 | /// Helper macro to create all necessary functions for a button 2 | #[macro_export] 3 | macro_rules! ev3_button_functions { 4 | ($button_name:ident) => { 5 | paste! { 6 | #[doc = "Check if the `" $button_name "` button is pressed."] 7 | #[doc = "```no_run"] 8 | #[doc = "use ev3dev_lang_rust::Button;"] 9 | #[doc = "use std::thread;"] 10 | #[doc = "use std::time::Duration;"] 11 | #[doc = ""] 12 | #[doc = "# fn main() -> ev3dev_lang_rust::Ev3Result<()> {"] 13 | #[doc = "let button = Button::new()?;"] 14 | #[doc = ""] 15 | #[doc = "loop {"] 16 | #[doc = " button.process();"] 17 | #[doc = " println!(\"Is '" $button_name "' pressed: {}\", button.is_" $button_name "());"] 18 | #[doc = " thread::sleep(Duration::from_millis(100));"] 19 | #[doc = "}"] 20 | #[doc = "# }"] 21 | #[doc = "```"] 22 | pub fn [] (&self) -> bool { 23 | self.button_handler.borrow().get_button_state(stringify!($button_name)) 24 | } 25 | 26 | #[doc = "Set an event handler, that is called by `process()` if the pressed state of the `" $button_name "` button changes."] 27 | #[doc = "```no_run"] 28 | #[doc = "use ev3dev_lang_rust::Button;"] 29 | #[doc = "use std::thread;"] 30 | #[doc = "use std::time::Duration;"] 31 | #[doc = ""] 32 | #[doc = "# fn main() -> ev3dev_lang_rust::Ev3Result<()> {"] 33 | #[doc = "let mut button = Button::new()?;"] 34 | #[doc = ""] 35 | #[doc = "button.set_" $button_name "_handler(|is_pressed| {"] 36 | #[doc = " println!(\"Is '" $button_name "' pressed: {}\", is_pressed);"] 37 | #[doc = "});"] 38 | #[doc = ""] 39 | #[doc = "loop {"] 40 | #[doc = " button.process();"] 41 | #[doc = " thread::sleep(Duration::from_millis(100));"] 42 | #[doc = "}"] 43 | #[doc = "# }"] 44 | #[doc = "```"] 45 | pub fn [](&mut self, handler: impl Fn(bool) + 'static) { 46 | self.button_handler 47 | .borrow_mut() 48 | .set_button_handler(stringify!($button_name), Some(Box::new(handler))); 49 | } 50 | 51 | #[doc = "Removes the event handler of the `" $button_name "` button."] 52 | pub fn [](&mut self) { 53 | self.button_handler 54 | .borrow_mut() 55 | .set_button_handler(stringify!($button_name), None); 56 | } 57 | } 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /src/findable.rs: -------------------------------------------------------------------------------- 1 | /// Helper to create a new `Device` instance. 2 | /// 3 | /// Generates `get()`, `find()` and `list()` methods. Therefore are 5 parameters required: 4 | /// * `class_name: &str` 5 | /// * `driver_name: &str` 6 | /// * `port: dyn ev3dev_lang_rust::Motor` 7 | /// * `debug_name: &str` 8 | /// * `port_prefix: &str` 9 | /// 10 | /// # Example: 11 | /// 12 | /// ```ignore 13 | /// #[derive(Debug, Clone, Device)] 14 | /// pub struct LargeMotor { 15 | /// driver: Driver, 16 | /// } 17 | /// 18 | /// impl LargeMotor { 19 | /// findable!("tacho-motor", "lego-ev3-l-motor", MotorPort), "LargeMotor", "out"; 20 | /// tacho_motor!(); 21 | /// } 22 | /// ``` 23 | #[macro_export] 24 | macro_rules! findable { 25 | ($class_name:expr, [$( $driver_name:expr ),*], $port: ty, $debug_name:expr, $port_prefix:expr) => { 26 | fn map_error(e: Ev3Error) -> Ev3Error { 27 | match e { 28 | e @ Ev3Error::InternalError { .. } => e, 29 | Ev3Error::NotConnected { device: _, port } => Ev3Error::NotConnected { 30 | device: $debug_name.to_owned(), 31 | port, 32 | }, 33 | Ev3Error::MultipleMatches { device: _, ports } => Ev3Error::MultipleMatches { 34 | device: $debug_name.to_owned(), 35 | ports: ports 36 | .iter() 37 | .map(|item| <$port>::format_name(item)) 38 | .collect(), 39 | }, 40 | } 41 | } 42 | 43 | /// Try to get a `Self` on the given port. Returns `None` if port is not used or another device is connected. 44 | #[allow(clippy::vec_init_then_push)] 45 | pub fn get(port: $port) -> Ev3Result { 46 | let mut driver_name_vec = Vec::new(); 47 | $( 48 | driver_name_vec.push($driver_name); 49 | )* 50 | 51 | let name = Driver::find_name_by_port_and_driver($class_name, &port, &driver_name_vec) 52 | .map_err(Self::map_error)?; 53 | 54 | Ok(Self::new(Driver::new($class_name, &name))) 55 | } 56 | 57 | /// Try to find a `Self`. Only returns a motor if their is exactly one connected, `Error::NotFound` otherwise. 58 | #[allow(clippy::vec_init_then_push)] 59 | pub fn find() -> Ev3Result { 60 | let mut driver_name_vec = Vec::new(); 61 | $( 62 | driver_name_vec.push($driver_name); 63 | )* 64 | 65 | let name = 66 | Driver::find_name_by_driver($class_name, &driver_name_vec).map_err(Self::map_error)?; 67 | 68 | Ok(Self::new(Driver::new($class_name, &name))) 69 | } 70 | 71 | /// Extract list of connected 'Self' 72 | #[allow(clippy::vec_init_then_push)] 73 | pub fn list() -> Ev3Result> { 74 | let mut driver_name_vec = Vec::new(); 75 | $( 76 | driver_name_vec.push($driver_name); 77 | )* 78 | 79 | Ok(Driver::find_names_by_driver($class_name, &driver_name_vec)? 80 | .iter() 81 | .map(|name| Self::new(Driver::new($class_name, &name))) 82 | .collect()) 83 | } 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 3 | 4 | //! # Rust language bindings for ev3dev 5 | //! 6 | //! ```no_run 7 | //! extern crate ev3dev_lang_rust; 8 | //! 9 | //! use ev3dev_lang_rust::Ev3Result; 10 | //! use ev3dev_lang_rust::motors::{LargeMotor, MotorPort}; 11 | //! use ev3dev_lang_rust::sensors::ColorSensor; 12 | //! 13 | //! fn main() -> Ev3Result<()> { 14 | //! 15 | //! // Get large motor on port outA. 16 | //! let large_motor = LargeMotor::get(MotorPort::OutA)?; 17 | //! 18 | //! // Set command "run-direct". 19 | //! large_motor.run_direct()?; 20 | //! 21 | //! // Run motor. 22 | //! large_motor.set_duty_cycle_sp(50)?; 23 | //! 24 | //! // Find color sensor. Always returns the first recognized one. 25 | //! let color_sensor = ColorSensor::find()?; 26 | //! 27 | //! // Switch to rgb mode. 28 | //! color_sensor.set_mode_rgb_raw()?; 29 | //! 30 | //! // Get current rgb color tuple. 31 | //! println!("Current rgb color: {:?}", color_sensor.get_rgb()?); 32 | //! 33 | //! Ok(()) 34 | //! } 35 | //! ``` 36 | 37 | #[cfg(feature = "screen")] 38 | extern crate framebuffer; 39 | 40 | #[cfg(feature = "screen")] 41 | extern crate image; 42 | 43 | #[macro_use] 44 | extern crate ev3dev_lang_rust_derive; 45 | extern crate libc; 46 | extern crate paste; 47 | 48 | #[macro_use] 49 | mod findable; 50 | 51 | #[macro_use] 52 | mod ev3_button_functions; 53 | 54 | mod attribute; 55 | pub use attribute::Attribute; 56 | mod driver; 57 | pub use driver::Driver; 58 | #[cfg(feature = "override-driver-path")] 59 | pub use driver::DRIVER_PATH; 60 | mod device; 61 | pub use device::Device; 62 | 63 | mod utils; 64 | pub use utils::{Ev3Error, Ev3Result, Port}; 65 | 66 | pub mod wait; 67 | 68 | pub mod motors; 69 | pub mod sensors; 70 | 71 | #[cfg(feature = "ev3")] 72 | mod ev3; 73 | #[cfg(feature = "ev3")] 74 | pub use ev3::Button; 75 | #[cfg(feature = "ev3")] 76 | pub use ev3::Led; 77 | #[cfg(feature = "ev3")] 78 | mod port_constants { 79 | pub const OUTPUT_A: &str = "outA"; 80 | pub const OUTPUT_B: &str = "outB"; 81 | pub const OUTPUT_C: &str = "outC"; 82 | pub const OUTPUT_D: &str = "outD"; 83 | 84 | pub const INPUT_1: &str = "in1"; 85 | pub const INPUT_2: &str = "in2"; 86 | pub const INPUT_3: &str = "in3"; 87 | pub const INPUT_4: &str = "in4"; 88 | } 89 | 90 | #[cfg(feature = "brickpi")] 91 | mod brickpi; 92 | #[cfg(feature = "brickpi")] 93 | pub use brickpi::Led; 94 | #[cfg(feature = "brickpi")] 95 | mod port_constants { 96 | pub const OUTPUT_A: &str = "serial0-0:MA"; 97 | pub const OUTPUT_B: &str = "serial0-0:MB"; 98 | pub const OUTPUT_C: &str = "serial0-0:MC"; 99 | pub const OUTPUT_D: &str = "serial0-0:MD"; 100 | 101 | pub const INPUT_1: &str = "serial0-0:S1"; 102 | pub const INPUT_2: &str = "serial0-0:S2"; 103 | pub const INPUT_3: &str = "serial0-0:S3"; 104 | pub const INPUT_4: &str = "serial0-0:S4"; 105 | } 106 | 107 | #[cfg(feature = "brickpi3")] 108 | mod brickpi3; 109 | #[cfg(feature = "brickpi3")] 110 | pub use brickpi3::Led; 111 | #[cfg(feature = "brickpi3")] 112 | mod port_constants { 113 | pub const OUTPUT_A: &str = "spi0.1:MA"; 114 | pub const OUTPUT_B: &str = "spi0.1:MB"; 115 | pub const OUTPUT_C: &str = "spi0.1:MC"; 116 | pub const OUTPUT_D: &str = "spi0.1:MD"; 117 | 118 | pub const INPUT_1: &str = "spi0.1:S1"; 119 | pub const INPUT_2: &str = "spi0.1:S2"; 120 | pub const INPUT_3: &str = "spi0.1:S3"; 121 | pub const INPUT_4: &str = "spi0.1:S4"; 122 | } 123 | 124 | pub mod sound; 125 | 126 | mod power_supply; 127 | pub use power_supply::PowerSupply; 128 | 129 | #[cfg(feature = "screen")] 130 | mod screen; 131 | #[cfg(feature = "screen")] 132 | pub use screen::Screen; 133 | -------------------------------------------------------------------------------- /src/motors/dc_motor_macro.rs: -------------------------------------------------------------------------------- 1 | //! The DcMotor provides a uniform interface for using 2 | //! regular DC motors with no fancy controls or feedback. 3 | //! This includes LEGO MINDSTORMS RCX motors and LEGO Power Functions motors. 4 | 5 | /// The DcMotor provides a uniform interface for using 6 | /// regular DC motors with no fancy controls or feedback. 7 | /// This includes LEGO MINDSTORMS RCX motors and LEGO Power Functions motors. 8 | #[macro_export] 9 | macro_rules! dc_motor { 10 | () => { 11 | /// Causes the motor to run until another command is sent. 12 | pub const COMMAND_RUN_FOREVER: &'static str = "run-forever"; 13 | 14 | /// Run the motor for the amount of time specified in `time_sp` 15 | /// and then stops the motor using the command specified by `stop_action`. 16 | pub const COMMAND_RUN_TIMED: &'static str = "run-timed"; 17 | 18 | /// Runs the motor using the duty cycle specified by `duty_cycle_sp`. 19 | /// Unlike other run commands, changing `duty_cycle_sp` while running will take effect immediately. 20 | pub const COMMAND_RUN_DIRECT: &'static str = "run-direct"; 21 | 22 | /// Stop any of the run commands before they are complete using the command specified by `stop_action`. 23 | pub const COMMAND_STOP: &'static str = "stop"; 24 | 25 | /// A positive duty cycle will cause the motor to rotate clockwise. 26 | pub const POLARITY_NORMAL: &'static str = "normal"; 27 | 28 | /// A positive duty cycle will cause the motor to rotate counter-clockwise. 29 | pub const POLARITY_INVERSED: &'static str = "inversed"; 30 | 31 | /// Power is being sent to the motor. 32 | pub const STATE_RUNNING: &'static str = "running"; 33 | 34 | /// The motor is ramping up or down and has not yet reached a pub constant output level. 35 | pub const STATE_RAMPING: &'static str = "ramping"; 36 | 37 | /// Removes power from the motor. The motor will freely coast to a stop. 38 | pub const STOP_ACTION_COAST: &'static str = "coast"; 39 | 40 | /// Removes power from the motor and creates a passive electrical load. 41 | /// This is usually done by shorting the motor terminals together. 42 | /// This load will absorb the energy from the rotation of the motors 43 | /// and cause the motor to stop more quickly than coasting. 44 | pub const STOP_ACTION_BRAKE: &'static str = "brake"; 45 | 46 | /// Returns the current duty cycle of the motor. Units are percent. Values are -100 to 100. 47 | pub fn get_duty_cycle(&self) -> Ev3Result { 48 | self.get_attribute("duty_cycle").get() 49 | } 50 | 51 | /// Returns the current duty cycle setpoint of the motor. Units are in percent. 52 | /// Valid values are -100 to 100. A negative value causes the motor to rotate in reverse. 53 | pub fn get_duty_cycle_sp(&self) -> Ev3Result { 54 | self.get_attribute("duty_cycle_sp").get() 55 | } 56 | 57 | /// Sets the duty cycle setpoint of the motor. Units are in percent. 58 | /// Valid values are -100 to 100. A negative value causes the motor to rotate in reverse. 59 | pub fn set_duty_cycle_sp(&self, duty_cycle_sp: i32) -> Ev3Result<()> { 60 | self.get_attribute("duty_cycle_sp").set(duty_cycle_sp) 61 | } 62 | 63 | /// Returns the current polarity of the motor. 64 | pub fn get_polarity(&self) -> Ev3Result { 65 | self.get_attribute("polarity").get() 66 | } 67 | 68 | /// Sets the polarity of the motor. 69 | pub fn set_polarity(&self, polarity: &str) -> Ev3Result<()> { 70 | self.get_attribute("polarity").set_str_slice(polarity) 71 | } 72 | 73 | /// Returns the current ramp up setpoint. 74 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 75 | /// the motor speed will increase from 0 to 100% of `max_speed` over the span of this setpoint. 76 | /// The actual ramp time is the ratio of the difference between the speed_sp 77 | /// and the current speed and max_speed multiplied by ramp_up_sp. Values must not be negative. 78 | pub fn get_ramp_up_sp(&self) -> Ev3Result { 79 | self.get_attribute("ramp_up_sp").get() 80 | } 81 | 82 | /// Sets the ramp up setpoint. 83 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 84 | /// the motor speed will increase from 0 to 100% of `max_speed` over the span of this setpoint. 85 | /// The actual ramp time is the ratio of the difference between the speed_sp 86 | /// and the current speed and max_speed multiplied by ramp_up_sp. Values must not be negative. 87 | pub fn set_ramp_up_sp(&self, ramp_up_sp: i32) -> Ev3Result<()> { 88 | self.get_attribute("ramp_up_sp").set(ramp_up_sp) 89 | } 90 | 91 | /// Returns the current ramp down setpoint. 92 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 93 | /// the motor speed will decrease from 100% down to 0 of `max_speed` over the span of this setpoint. 94 | /// The actual ramp time is the ratio of the difference between the speed_sp 95 | /// and the current speed and 0 multiplied by ramp_down_sp. Values must not be negative. 96 | pub fn get_ramp_down_sp(&self) -> Ev3Result { 97 | self.get_attribute("ramp_down_sp").get() 98 | } 99 | 100 | /// Sets the ramp down setpoint. 101 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 102 | /// the motor speed will decrease from 100% down to 0 of `max_speed` over the span of this setpoint. 103 | /// The actual ramp time is the ratio of the difference between the speed_sp 104 | /// and the current speed and 0 multiplied by ramp_down_sp. Values must not be negative. 105 | pub fn set_ramp_down_sp(&self, ramp_down_sp: i32) -> Ev3Result<()> { 106 | self.get_attribute("ramp_down_sp").set(ramp_down_sp) 107 | } 108 | 109 | /// Returns a list of state flags. 110 | pub fn get_state(&self) -> Ev3Result> { 111 | self.get_attribute("state").get_vec() 112 | } 113 | 114 | /// Returns the current stop action. 115 | /// The value determines the motors behavior when command is set to stop. 116 | pub fn get_stop_action(&self) -> Ev3Result { 117 | self.get_attribute("stop_action").get() 118 | } 119 | 120 | /// Sets the stop action. 121 | /// The value determines the motors behavior when command is set to stop. 122 | pub fn set_stop_action(&self, stop_action: &str) -> Ev3Result<()> { 123 | self.get_attribute("stop_action").set_str_slice(stop_action) 124 | } 125 | 126 | /// Returns the current amount of time the motor will run when using the run-timed command. 127 | /// Units are in milliseconds. Values must not be negative. 128 | pub fn get_time_sp(&self) -> Ev3Result { 129 | self.get_attribute("time_sp").get() 130 | } 131 | 132 | /// Sets the amount of time the motor will run when using the run-timed command. 133 | /// Units are in milliseconds. Values must not be negative. 134 | pub fn set_time_sp(&self, time_sp: i32) -> Ev3Result<()> { 135 | self.get_attribute("time_sp").set(time_sp) 136 | } 137 | 138 | /// Runs the motor using the duty cycle specified by `duty_cycle_sp`. 139 | /// Unlike other run commands, changing `duty_cycle_sp` while running will take effect immediately. 140 | pub fn run_direct(&self) -> Ev3Result<()> { 141 | self.set_command(Self::COMMAND_RUN_DIRECT) 142 | } 143 | 144 | /// Causes the motor to run until another command is sent. 145 | pub fn run_forever(&self) -> Ev3Result<()> { 146 | self.set_command(Self::COMMAND_RUN_FOREVER) 147 | } 148 | 149 | /// Run the motor for the amount of time specified in `time_sp` 150 | /// and then stops the motor using the command specified by `stop_action`. 151 | pub fn run_timed(&self, time_sp: Option) -> Ev3Result<()> { 152 | if let Some(duration) = time_sp { 153 | let p = duration.as_millis() as i32; 154 | self.set_time_sp(p)?; 155 | } 156 | self.set_command(Self::COMMAND_RUN_TIMED) 157 | } 158 | 159 | /// Stop any of the run commands before they are complete using the command specified by `stop_action`. 160 | pub fn stop(&self) -> Ev3Result<()> { 161 | self.set_command(Self::COMMAND_STOP) 162 | } 163 | 164 | /// Power is being sent to the motor. 165 | pub fn is_running(&self) -> Ev3Result { 166 | Ok(self 167 | .get_state()? 168 | .iter() 169 | .any(|state| state == Self::STATE_RUNNING)) 170 | } 171 | 172 | /// The motor is ramping up or down and has not yet reached a pub constant output level. 173 | pub fn is_ramping(&self) -> Ev3Result { 174 | Ok(self 175 | .get_state()? 176 | .iter() 177 | .any(|state| state == Self::STATE_RAMPING)) 178 | } 179 | }; 180 | } 181 | -------------------------------------------------------------------------------- /src/motors/large_motor.rs: -------------------------------------------------------------------------------- 1 | use super::MotorPort; 2 | use crate::{wait, Attribute, Device, Driver, Ev3Error, Ev3Result}; 3 | use std::time::Duration; 4 | 5 | /// EV3/NXT large servo motor 6 | #[derive(Debug, Clone, Device)] 7 | pub struct LargeMotor { 8 | driver: Driver, 9 | } 10 | 11 | impl LargeMotor { 12 | fn new(driver: Driver) -> Self { 13 | Self { driver } 14 | } 15 | 16 | findable!( 17 | "tacho-motor", 18 | ["lego-ev3-l-motor", "lego-nxt-motor"], 19 | MotorPort, 20 | "LargeMotor", 21 | "out" 22 | ); 23 | tacho_motor!(); 24 | } 25 | -------------------------------------------------------------------------------- /src/motors/medium_motor.rs: -------------------------------------------------------------------------------- 1 | use super::MotorPort; 2 | use crate::{wait, Attribute, Device, Driver, Ev3Error, Ev3Result}; 3 | use std::time::Duration; 4 | 5 | /// EV3 medium servo motor 6 | #[derive(Debug, Clone, Device)] 7 | pub struct MediumMotor { 8 | driver: Driver, 9 | } 10 | 11 | impl MediumMotor { 12 | fn new(driver: Driver) -> Self { 13 | Self { driver } 14 | } 15 | 16 | findable!( 17 | "tacho-motor", 18 | ["lego-ev3-m-motor"], 19 | MotorPort, 20 | "MediumMotor", 21 | "out" 22 | ); 23 | tacho_motor!(); 24 | } 25 | -------------------------------------------------------------------------------- /src/motors/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Container module for motor types 2 | 3 | #[macro_use] 4 | mod dc_motor_macro; 5 | #[macro_use] 6 | mod servo_motor_macro; 7 | #[macro_use] 8 | mod tacho_motor_macro; 9 | 10 | mod large_motor; 11 | pub use self::large_motor::LargeMotor; 12 | 13 | mod medium_motor; 14 | pub use self::medium_motor::MediumMotor; 15 | 16 | mod tacho_motor; 17 | pub use self::tacho_motor::TachoMotor; 18 | 19 | use crate::{port_constants, Port}; 20 | 21 | /// EV3 ports `outA` to `outD` 22 | #[derive(Debug, Copy, Clone)] 23 | pub enum MotorPort { 24 | /// EV3 `outA` port 25 | OutA, 26 | /// EV3 `outB` port 27 | OutB, 28 | /// EV3 `outC` port 29 | OutC, 30 | /// EV3 `outD` port 31 | OutD, 32 | } 33 | 34 | impl MotorPort { 35 | /// Try to format a device name path to a port name. 36 | pub fn format_name(name: &str) -> String { 37 | match name { 38 | "motor0" => MotorPort::OutA.address(), 39 | "motor1" => MotorPort::OutB.address(), 40 | "motor2" => MotorPort::OutC.address(), 41 | "motor3" => MotorPort::OutD.address(), 42 | _ => name.to_owned(), 43 | } 44 | } 45 | } 46 | 47 | impl Port for MotorPort { 48 | fn address(&self) -> String { 49 | match self { 50 | MotorPort::OutA => port_constants::OUTPUT_A.to_owned(), 51 | MotorPort::OutB => port_constants::OUTPUT_B.to_owned(), 52 | MotorPort::OutC => port_constants::OUTPUT_C.to_owned(), 53 | MotorPort::OutD => port_constants::OUTPUT_D.to_owned(), 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/motors/servo_motor_macro.rs: -------------------------------------------------------------------------------- 1 | //! The ServoMotor provides a uniform interface for using hobby type servo motors. 2 | 3 | /// The ServoMotor provides a uniform interface for using hobby type servo motors. 4 | #[macro_export] 5 | macro_rules! servo_motor { 6 | () => { 7 | /// Remove power from the motor. 8 | pub const COMMAND_RUN: &'static str = "run"; 9 | 10 | /// Drive servo to the position set in the position_sp attribute. 11 | pub const COMMAND_FLOAT: &'static str = "float"; 12 | 13 | /// With normal polarity, a positive duty cycle will cause the motor to rotate clockwise. 14 | pub const POLARITY_NORMAL: &'static str = "normal"; 15 | 16 | /// With inversed polarity, a positive duty cycle will cause the motor to rotate counter-clockwise. 17 | pub const POLARITY_INVERSED: &'static str = "inversed"; 18 | 19 | /// Power is being sent to the motor. 20 | pub const STATE_RUNNING: &'static str = "running"; 21 | 22 | /// Returns the current polarity of the motor. 23 | pub fn get_polarity(&self) -> Ev3Result { 24 | self.get_attribute("polarity").get() 25 | } 26 | 27 | /// Sets the polarity of the motor. 28 | pub fn set_polarity(&self, polarity: &str) -> Ev3Result<()> { 29 | self.get_attribute("polarity").set_str_slice(polarity) 30 | } 31 | 32 | /// Returns the current max pulse setpoint. 33 | /// Used to set the pulse size in milliseconds for the signal 34 | /// that tells the servo to drive to the maximum (clockwise) position_sp. 35 | /// Default value is 2400. Valid values are 2300 to 2700. 36 | /// You must write to the position_sp attribute for changes to this attribute to take effect. 37 | pub fn get_max_pulse_sp(&self) -> Ev3Result { 38 | self.get_attribute("max_pulse_sp").get() 39 | } 40 | 41 | /// Sets the max pulse setpoint. 42 | /// Used to set the pulse size in milliseconds for the signal 43 | /// that tells the servo to drive to the maximum (clockwise) position_sp. 44 | /// Default value is 2400. Valid values are 2300 to 2700. 45 | /// You must write to the position_sp attribute for changes to this attribute to take effect. 46 | pub fn set_max_pulse_sp(&self, max_pulse_sp: i32) -> Ev3Result<()> { 47 | self.get_attribute("max_pulse_sp").set(max_pulse_sp) 48 | } 49 | 50 | /// Returns the current mid pulse setpoint. 51 | /// Used to set the pulse size in milliseconds for the signal 52 | /// that tells the servo to drive to the minimum (counter-clockwise) position_sp. 53 | /// Default value is 600. 54 | /// Valid values are 300 to 700. 55 | /// You must write to the position_sp attribute for changes to this attribute to take effect. 56 | pub fn get_mid_pulse_sp(&self) -> Ev3Result { 57 | self.get_attribute("mid_pulse_sp").get() 58 | } 59 | 60 | /// Sets the mid pulse setpoint. 61 | /// Used to set the pulse size in milliseconds for the signal 62 | /// that tells the servo to drive to the minimum (counter-clockwise) position_sp. 63 | /// Default value is 600. 64 | /// Valid values are 300 to 700. 65 | /// You must write to the position_sp attribute for changes to this attribute to take effect. 66 | pub fn set_mid_pulse_sp(&self, max_pulse_sp: i32) -> Ev3Result<()> { 67 | self.get_attribute("mid_pulse_sp").set(max_pulse_sp) 68 | } 69 | 70 | /// Returns the current min pulse setpoint. 71 | /// Used to set the pulse size in milliseconds for the signal 72 | /// that tells the servo to drive to the minimum (counter-clockwise) position_sp. 73 | /// Default value is 600. Valid values are 300 to 700. 74 | /// You must write to the position_sp attribute for changes to this attribute to take effect. 75 | pub fn get_min_pulse_sp(&self) -> Ev3Result { 76 | self.get_attribute("min_pulse_sp").get() 77 | } 78 | /// Sets the min pulse setpoint. 79 | /// Used to set the pulse size in milliseconds for the signal 80 | /// that tells the servo to drive to the minimum (counter-clockwise) position_sp. 81 | /// Default value is 600. Valid values are 300 to 700. 82 | /// You must write to the position_sp attribute for changes to this attribute to take effect. 83 | pub fn set_min_pulse_sp(&self, min_pulse_sp: i32) -> Ev3Result<()> { 84 | self.get_attribute("min_pulse_sp").set(min_pulse_sp) 85 | } 86 | 87 | /// Returns the current target position for the `run-to-abs-pos` and `run-to-rel-pos` commands. Units are in tacho counts. 88 | /// You can use the value returned by `counts_per_rot` to convert tacho counts to/from rotations or degrees. 89 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer). 90 | pub fn get_position_sp(&self) -> Ev3Result { 91 | self.get_attribute("position_sp").get() 92 | } 93 | 94 | /// Sets the target position for the `run-to-abs-pos` and `run-to-rel-pos` commands. 95 | /// Units are in tacho counts. 96 | /// You can use the value returned by `counts_per_rot` to convert tacho counts to/from rotations or degrees. 97 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer). 98 | pub fn set_position_sp(&self, position_sp: i32) -> Ev3Result<()> { 99 | self.get_attribute("position_sp").set(position_sp) 100 | } 101 | 102 | /// Returns the current the rate_sp at which the servo travels from 0 to 100.0% 103 | /// (half of the full range of the servo). 104 | /// Units are in milliseconds. 105 | /// 106 | /// ## Example: 107 | /// 108 | /// Setting the rate_sp to 1000 means that it will take a 180 109 | /// degree servo 2 second to move from 0 to 180 degrees. 110 | /// 111 | /// ## Note: 112 | /// 113 | /// Some servo controllers may not support this in which case 114 | /// reading and writing will fail with -EOPNOTSUPP. 115 | /// In continuous rotation servos, this value will affect the 116 | /// rate_sp at which the speed ramps up or down. 117 | pub fn get_rate_sp(&self) -> Ev3Result { 118 | self.get_attribute("rate_sp").get() 119 | } 120 | 121 | /// Sets the rate_sp at which the servo travels from 0 to 100.0% 122 | /// (half of the full range of the servo). 123 | /// Units are in milliseconds. 124 | /// 125 | /// ## Example: 126 | /// 127 | /// Setting the rate_sp to 1000 means that it will take a 180 128 | /// degree servo 2 second to move from 0 to 180 degrees. 129 | /// 130 | /// ## Note: 131 | /// 132 | /// Some servo controllers may not support this in which case 133 | /// reading and writing will fail with -EOPNOTSUPP. 134 | /// In continuous rotation servos, this value will affect the 135 | /// rate_sp at which the speed ramps up or down. 136 | pub fn set_rate_sp(&self, rate_sp: i32) -> Ev3Result<()> { 137 | self.get_attribute("rate_sp").set(rate_sp) 138 | } 139 | 140 | /// Returns a list of state flags. 141 | pub fn get_state(&self) -> Ev3Result> { 142 | self.get_attribute("state").get_vec() 143 | } 144 | 145 | /// Power is being sent to the motor. 146 | pub fn is_running(&self) -> Ev3Result { 147 | Ok(self 148 | .get_state()? 149 | .iter() 150 | .any(|state| state == Self::STATE_RUNNING)) 151 | } 152 | 153 | /// Drive servo to the position set in the `position_sp` attribute. 154 | pub fn run(&self) -> Ev3Result<()> { 155 | self.set_command(Self::COMMAND_RUN) 156 | } 157 | 158 | /// Remove power from the motor. 159 | pub fn float(&self) -> Ev3Result<()> { 160 | self.set_command(Self::COMMAND_FLOAT) 161 | } 162 | }; 163 | } 164 | -------------------------------------------------------------------------------- /src/motors/tacho_motor.rs: -------------------------------------------------------------------------------- 1 | //! The TachoMotor provides a uniform interface for using motors with positional 2 | //! and directional feedback such as the EV3 and NXT motors. 3 | //! This feedback allows for precise control of the motors. 4 | 5 | use std::time::Duration; 6 | 7 | use crate::{Ev3Error, Ev3Result}; 8 | 9 | use super::{LargeMotor, MediumMotor, MotorPort}; 10 | 11 | #[derive(Debug, Clone)] 12 | enum TachoMotorInner { 13 | LargeMotor { motor: LargeMotor }, 14 | MediumMotor { motor: MediumMotor }, 15 | } 16 | 17 | /// The TachoMotor provides a uniform interface for using motors with positional 18 | /// and directional feedback such as the EV3 and NXT motors. 19 | /// This feedback allows for precise control of the motors. 20 | /// EV3/NXT large servo motor 21 | #[derive(Debug, Clone)] 22 | pub struct TachoMotor { 23 | inner: TachoMotorInner, 24 | } 25 | 26 | impl TachoMotor { 27 | /// Try to get a `Self` on the given port. Returns `None` if port is not used or another device is connected. 28 | pub fn get(port: MotorPort) -> Ev3Result { 29 | let large_motor = LargeMotor::get(port); 30 | if let Ok(motor) = large_motor { 31 | return Ok(TachoMotor { 32 | inner: TachoMotorInner::LargeMotor { motor }, 33 | }); 34 | } 35 | 36 | let medium_motor = MediumMotor::get(port); 37 | if let Ok(motor) = medium_motor { 38 | return Ok(TachoMotor { 39 | inner: TachoMotorInner::MediumMotor { motor }, 40 | }); 41 | } 42 | 43 | Err(Ev3Error::InternalError { 44 | msg: "Could not find a tacho motor at requested port!".to_owned(), 45 | }) 46 | } 47 | 48 | /// Try to find a `Self`. Only returns a motor if their is exactly one connected, `Error::NotFound` otherwise. 49 | pub fn find() -> Ev3Result { 50 | let large_motor = LargeMotor::find(); 51 | if let Ok(motor) = large_motor { 52 | return Ok(TachoMotor { 53 | inner: TachoMotorInner::LargeMotor { motor }, 54 | }); 55 | } 56 | 57 | let medium_motor = MediumMotor::find(); 58 | if let Ok(motor) = medium_motor { 59 | return Ok(TachoMotor { 60 | inner: TachoMotorInner::MediumMotor { motor }, 61 | }); 62 | } 63 | 64 | Err(Ev3Error::InternalError { 65 | msg: "Could not find a tacho motor at requested port!".to_owned(), 66 | }) 67 | } 68 | 69 | /// Extract list of connected 'Self' 70 | pub fn list() -> Ev3Result> { 71 | let large_motor = LargeMotor::list()?; 72 | let medium_motor = MediumMotor::list()?; 73 | 74 | let mut vec = Vec::with_capacity(large_motor.len() + medium_motor.len()); 75 | 76 | vec.extend(large_motor.into_iter().map(|motor| TachoMotor { 77 | inner: TachoMotorInner::LargeMotor { motor }, 78 | })); 79 | 80 | vec.extend(medium_motor.into_iter().map(|motor| TachoMotor { 81 | inner: TachoMotorInner::MediumMotor { motor }, 82 | })); 83 | 84 | Ok(vec) 85 | } 86 | 87 | /// Try to convert this tacho motor to an `LargeMotor`, return `Self` if this fails. 88 | pub fn into_large_motor(self) -> Result { 89 | match self.inner { 90 | TachoMotorInner::LargeMotor { motor } => Ok(motor), 91 | inner @ TachoMotorInner::MediumMotor { motor: _ } => Err(TachoMotor { inner }), 92 | } 93 | } 94 | 95 | /// Try to convert this tacho motor to an `LargeMotor`, return `Self` if this fails. 96 | pub fn into_medium_motor(self) -> Result { 97 | match self.inner { 98 | inner @ TachoMotorInner::LargeMotor { motor: _ } => Err(TachoMotor { inner }), 99 | TachoMotorInner::MediumMotor { motor } => Ok(motor), 100 | } 101 | } 102 | 103 | /// Causes the motor to run until another command is sent. 104 | pub const COMMAND_RUN_FOREVER: &'static str = "run-forever"; 105 | 106 | /// Runs the motor to an absolute position specified by `position_sp` 107 | /// and then stops the motor using the command specified in `stop_action`. 108 | pub const COMMAND_RUN_TO_ABS_POS: &'static str = "run-to-abs-pos"; 109 | 110 | /// Runs the motor to a position relative to the current position value. 111 | /// The new position will be current `position` + `position_sp`. 112 | /// When the new position is reached, the motor will stop using the command specified by `stop_action`. 113 | pub const COMMAND_RUN_TO_REL_POS: &'static str = "run-to-rel-pos"; 114 | 115 | /// Run the motor for the amount of time specified in `time_sp` 116 | /// and then stops the motor using the command specified by `stop_action`. 117 | pub const COMMAND_RUN_TIMED: &'static str = "run-timed"; 118 | 119 | /// Runs the motor using the duty cycle specified by `duty_cycle_sp`. 120 | /// Unlike other run commands, changing `duty_cycle_sp` while running will take effect immediately. 121 | pub const COMMAND_RUN_DIRECT: &'static str = "run-direct"; 122 | 123 | /// Stop any of the run commands before they are complete using the command specified by `stop_action`. 124 | pub const COMMAND_STOP: &'static str = "stop"; 125 | 126 | /// Resets all of the motor parameter attributes to their default values. 127 | /// This will also have the effect of stopping the motor. 128 | pub const COMMAND_RESET: &'static str = "reset"; 129 | 130 | /// A positive duty cycle will cause the motor to rotate clockwise. 131 | pub const POLARITY_NORMAL: &'static str = "normal"; 132 | 133 | /// A positive duty cycle will cause the motor to rotate counter-clockwise. 134 | pub const POLARITY_INVERSED: &'static str = "inversed"; 135 | 136 | /// Power is being sent to the motor. 137 | pub const STATE_RUNNING: &'static str = "running"; 138 | 139 | /// The motor is ramping up or down and has not yet reached a pub constant output level. 140 | pub const STATE_RAMPING: &'static str = "ramping"; 141 | 142 | /// The motor is not turning, but rather attempting to hold a fixed position. 143 | pub const STATE_HOLDING: &'static str = "holding"; 144 | 145 | /// The motor is turning as fast as possible, but cannot reach its `speed_sp`. 146 | pub const STATE_OVERLOADED: &'static str = "overloaded"; 147 | 148 | /// The motor is trying to run but is not turning at all. 149 | pub const STATE_STALLED: &'static str = "stalled"; 150 | 151 | /// Removes power from the motor. The motor will freely coast to a stop. 152 | pub const STOP_ACTION_COAST: &'static str = "coast"; 153 | 154 | /// Removes power from the motor and creates a passive electrical load. 155 | /// This is usually done by shorting the motor terminals together. 156 | /// This load will absorb the energy from the rotation of the motors 157 | /// and cause the motor to stop more quickly than coasting. 158 | pub const STOP_ACTION_BRAKE: &'static str = "brake"; 159 | 160 | /// Causes the motor to actively try to hold the current position. 161 | /// If an external force tries to turn the motor, the motor will “push back” to maintain its position. 162 | pub const STOP_ACTION_HOLD: &'static str = "hold"; 163 | 164 | /// Returns the number of tacho counts in one rotation of the motor. 165 | /// 166 | /// Tacho counts are used by the position and speed attributes, 167 | /// so you can use this value to convert from rotations or degrees to tacho counts. 168 | /// (rotation motors only) 169 | /// 170 | /// # Examples 171 | /// 172 | /// ```no_run 173 | /// use ev3dev_lang_rust::motors::LargeMotor; 174 | /// 175 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 176 | /// // Init a tacho motor. 177 | /// let motor = LargeMotor::find()?; 178 | /// 179 | /// // Get position and count_per_rot as f32. 180 | /// let position = motor.get_position()? as f32; 181 | /// let count_per_rot = motor.get_count_per_rot()? as f32; 182 | /// 183 | /// // Calculate the rotation count. 184 | /// let rotations = position / count_per_rot; 185 | /// 186 | /// println!("The motor did {:.2} rotations", rotations); 187 | /// # Ok(()) 188 | /// # } 189 | /// ``` 190 | pub fn get_count_per_rot(&self) -> Ev3Result { 191 | match self.inner { 192 | TachoMotorInner::LargeMotor { ref motor } => motor.get_count_per_rot(), 193 | TachoMotorInner::MediumMotor { ref motor } => motor.get_count_per_rot(), 194 | } 195 | } 196 | 197 | /// Returns the number of tacho counts in one meter of travel of the motor. 198 | /// 199 | /// Tacho counts are used by the position and speed attributes, 200 | /// so you can use this value to convert from distance to tacho counts. 201 | /// (linear motors only) 202 | pub fn get_count_per_m(&self) -> Ev3Result { 203 | match self.inner { 204 | TachoMotorInner::LargeMotor { ref motor } => motor.get_count_per_m(), 205 | TachoMotorInner::MediumMotor { ref motor } => motor.get_count_per_m(), 206 | } 207 | } 208 | 209 | /// Returns the number of tacho counts in the full travel of the motor. 210 | /// 211 | /// When combined with the count_per_m attribute, 212 | /// you can use this value to calculate the maximum travel distance of the motor. 213 | /// (linear motors only) 214 | pub fn get_full_travel_count(&self) -> Ev3Result { 215 | match self.inner { 216 | TachoMotorInner::LargeMotor { ref motor } => motor.get_full_travel_count(), 217 | TachoMotorInner::MediumMotor { ref motor } => motor.get_full_travel_count(), 218 | } 219 | } 220 | 221 | /// Returns the current duty cycle of the motor. Units are percent. 222 | /// 223 | /// Values are -100 to 100. 224 | /// 225 | /// # Examples 226 | /// 227 | /// ```no_run 228 | /// use ev3dev_lang_rust::motors::LargeMotor; 229 | /// use std::thread; 230 | /// use std::time::Duration; 231 | /// 232 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 233 | /// // Init a tacho motor. 234 | /// let motor = LargeMotor::find()?; 235 | /// 236 | /// // Set the motor command `run-direct` to start rotation. 237 | /// motor.run_direct()?; 238 | /// 239 | /// // Rotate motor forward and wait 5 seconds. 240 | /// motor.set_duty_cycle_sp(50)?; 241 | /// thread::sleep(Duration::from_secs(5)); 242 | /// 243 | /// assert_eq!(motor.get_duty_cycle()?, 50); 244 | /// # Ok(()) 245 | /// # } 246 | pub fn get_duty_cycle(&self) -> Ev3Result { 247 | match self.inner { 248 | TachoMotorInner::LargeMotor { ref motor } => motor.get_duty_cycle(), 249 | TachoMotorInner::MediumMotor { ref motor } => motor.get_duty_cycle(), 250 | } 251 | } 252 | 253 | /// Returns the current duty cycle setpoint of the motor. 254 | /// 255 | /// Units are in percent. 256 | /// Valid values are -100 to 100. A negative value causes the motor to rotate in reverse. 257 | /// 258 | /// # Examples 259 | /// 260 | /// ```no_run 261 | /// use ev3dev_lang_rust::motors::LargeMotor; 262 | /// use std::thread; 263 | /// use std::time::Duration; 264 | /// 265 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 266 | /// // Init a tacho motor. 267 | /// let motor = LargeMotor::find()?; 268 | /// 269 | /// // Rotate motor forward and wait 5 seconds. 270 | /// motor.set_duty_cycle_sp(50)?; 271 | /// thread::sleep(Duration::from_secs(5)); 272 | /// 273 | /// assert_eq!(motor.get_duty_cycle()?, 50); 274 | /// # Ok(()) 275 | /// # } 276 | pub fn get_duty_cycle_sp(&self) -> Ev3Result { 277 | match self.inner { 278 | TachoMotorInner::LargeMotor { ref motor } => motor.get_duty_cycle_sp(), 279 | TachoMotorInner::MediumMotor { ref motor } => motor.get_duty_cycle_sp(), 280 | } 281 | } 282 | 283 | /// Sets the duty cycle setpoint of the motor. 284 | /// 285 | /// Units are in percent. 286 | /// Valid values are -100 to 100. A negative value causes the motor to rotate in reverse. 287 | /// 288 | /// # Examples 289 | /// 290 | /// ```no_run 291 | /// use ev3dev_lang_rust::motors::LargeMotor; 292 | /// use std::thread; 293 | /// use std::time::Duration; 294 | /// 295 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 296 | /// // Init a tacho motor. 297 | /// let motor = LargeMotor::find()?; 298 | /// 299 | /// // Set the motor command `run-direct` to start rotation. 300 | /// motor.run_direct()?; 301 | /// 302 | /// // Rotate motor forward and wait 5 seconds. 303 | /// motor.set_duty_cycle_sp(50)?; 304 | /// thread::sleep(Duration::from_secs(5)); 305 | /// 306 | /// // Rotate motor backward and wait 5 seconds. 307 | /// motor.set_duty_cycle_sp(-50)?; 308 | /// thread::sleep(Duration::from_secs(5)); 309 | /// # Ok(()) 310 | /// # } 311 | pub fn set_duty_cycle_sp(&self, duty_cycle: i32) -> Ev3Result<()> { 312 | match self.inner { 313 | TachoMotorInner::LargeMotor { ref motor } => motor.set_duty_cycle_sp(duty_cycle), 314 | TachoMotorInner::MediumMotor { ref motor } => motor.set_duty_cycle_sp(duty_cycle), 315 | } 316 | } 317 | 318 | /// Returns the current polarity of the motor. 319 | pub fn get_polarity(&self) -> Ev3Result { 320 | match self.inner { 321 | TachoMotorInner::LargeMotor { ref motor } => motor.get_polarity(), 322 | TachoMotorInner::MediumMotor { ref motor } => motor.get_polarity(), 323 | } 324 | } 325 | 326 | /// Sets the polarity of the motor. 327 | pub fn set_polarity(&self, polarity: &str) -> Ev3Result<()> { 328 | match self.inner { 329 | TachoMotorInner::LargeMotor { ref motor } => motor.set_polarity(polarity), 330 | TachoMotorInner::MediumMotor { ref motor } => motor.set_polarity(polarity), 331 | } 332 | } 333 | 334 | /// Returns the current position of the motor in pulses of the rotary encoder. 335 | /// 336 | /// When the motor rotates clockwise, the position will increase. 337 | /// Likewise, rotating counter-clockwise causes the position to decrease. 338 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer) 339 | /// 340 | /// # Examples 341 | /// 342 | /// ```no_run 343 | /// use ev3dev_lang_rust::motors::LargeMotor; 344 | /// 345 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 346 | /// // Init a tacho motor. 347 | /// let motor = LargeMotor::find()?; 348 | /// 349 | /// // Get position and count_per_rot as f32. 350 | /// let position = motor.get_position()? as f32; 351 | /// let count_per_rot = motor.get_count_per_rot()? as f32; 352 | /// 353 | /// // Calculate the rotation count. 354 | /// let rotations: f32 = position / count_per_rot; 355 | /// 356 | /// println!("The motor did {:.2} rotations", rotations); 357 | /// # Ok(()) 358 | /// # } 359 | /// ``` 360 | pub fn get_position(&self) -> Ev3Result { 361 | match self.inner { 362 | TachoMotorInner::LargeMotor { ref motor } => motor.get_position(), 363 | TachoMotorInner::MediumMotor { ref motor } => motor.get_position(), 364 | } 365 | } 366 | 367 | /// Sets the current position of the motor in pulses of the rotary encoder. 368 | /// 369 | /// When the motor rotates clockwise, the position will increase. 370 | /// Likewise, rotating counter-clockwise causes the position to decrease. 371 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer) 372 | /// 373 | /// # Examples 374 | /// 375 | /// ```no_run 376 | /// use ev3dev_lang_rust::motors::LargeMotor; 377 | /// 378 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 379 | /// // Init a tacho motor. 380 | /// let motor = LargeMotor::find()?; 381 | /// 382 | /// motor.set_position(0)?; 383 | /// let position = motor.get_position()?; 384 | /// 385 | /// // If the motor is not moving, the position value 386 | /// // should not change between set and get operation. 387 | /// assert_eq!(position, 0); 388 | /// # Ok(()) 389 | /// # } 390 | /// ``` 391 | pub fn set_position(&self, position: i32) -> Ev3Result<()> { 392 | match self.inner { 393 | TachoMotorInner::LargeMotor { ref motor } => motor.set_position(position), 394 | TachoMotorInner::MediumMotor { ref motor } => motor.set_position(position), 395 | } 396 | } 397 | 398 | /// Returns the proportional pub constant for the position PID. 399 | pub fn get_hold_pid_kp(&self) -> Ev3Result { 400 | match self.inner { 401 | TachoMotorInner::LargeMotor { ref motor } => motor.get_hold_pid_kp(), 402 | TachoMotorInner::MediumMotor { ref motor } => motor.get_hold_pid_kp(), 403 | } 404 | } 405 | 406 | /// Sets the proportional pub constant for the position PID. 407 | pub fn set_hold_pid_kp(&self, kp: f32) -> Ev3Result<()> { 408 | match self.inner { 409 | TachoMotorInner::LargeMotor { ref motor } => motor.set_hold_pid_kp(kp), 410 | TachoMotorInner::MediumMotor { ref motor } => motor.set_hold_pid_kp(kp), 411 | } 412 | } 413 | 414 | /// Returns the integral pub constant for the position PID. 415 | pub fn get_hold_pid_ki(&self) -> Ev3Result { 416 | match self.inner { 417 | TachoMotorInner::LargeMotor { ref motor } => motor.get_hold_pid_ki(), 418 | TachoMotorInner::MediumMotor { ref motor } => motor.get_hold_pid_ki(), 419 | } 420 | } 421 | 422 | /// Sets the integral pub constant for the position PID. 423 | pub fn set_hold_pid_ki(&self, ki: f32) -> Ev3Result<()> { 424 | match self.inner { 425 | TachoMotorInner::LargeMotor { ref motor } => motor.set_hold_pid_ki(ki), 426 | TachoMotorInner::MediumMotor { ref motor } => motor.set_hold_pid_ki(ki), 427 | } 428 | } 429 | 430 | /// Returns the derivative pub constant for the position PID. 431 | pub fn get_hold_pid_kd(&self) -> Ev3Result { 432 | match self.inner { 433 | TachoMotorInner::LargeMotor { ref motor } => motor.get_hold_pid_kd(), 434 | TachoMotorInner::MediumMotor { ref motor } => motor.get_hold_pid_kd(), 435 | } 436 | } 437 | 438 | /// Sets the derivative pub constant for the position PID. 439 | pub fn set_hold_pid_kd(&self, kd: f32) -> Ev3Result<()> { 440 | match self.inner { 441 | TachoMotorInner::LargeMotor { ref motor } => motor.set_hold_pid_kd(kd), 442 | TachoMotorInner::MediumMotor { ref motor } => motor.set_hold_pid_kd(kd), 443 | } 444 | } 445 | 446 | /// Returns the maximum value that is accepted by the `speed_sp` attribute. 447 | /// 448 | /// This value is the speed of the motor at 9V with no load. 449 | /// Note: The actual maximum obtainable speed will be less than this 450 | /// and will depend on battery voltage and mechanical load on the motor. 451 | pub fn get_max_speed(&self) -> Ev3Result { 452 | match self.inner { 453 | TachoMotorInner::LargeMotor { ref motor } => motor.get_max_speed(), 454 | TachoMotorInner::MediumMotor { ref motor } => motor.get_max_speed(), 455 | } 456 | } 457 | 458 | /// Returns the current target position for the `run-to-abs-pos` and `run-to-rel-pos` commands. 459 | /// 460 | /// Units are in tacho counts. 461 | /// You can use the value returned by `counts_per_rot` to convert tacho counts to/from rotations or degrees. 462 | /// 463 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer). 464 | pub fn get_position_sp(&self) -> Ev3Result { 465 | match self.inner { 466 | TachoMotorInner::LargeMotor { ref motor } => motor.get_position_sp(), 467 | TachoMotorInner::MediumMotor { ref motor } => motor.get_position_sp(), 468 | } 469 | } 470 | 471 | /// Sets the target position for the `run-to-abs-pos` and `run-to-rel-pos` commands. 472 | /// 473 | /// Units are in tacho counts. 474 | /// You can use the value returned by `counts_per_rot` to convert tacho counts to/from rotations or degrees. 475 | /// 476 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer). 477 | /// 478 | /// # Examples 479 | /// 480 | /// ```ignore 481 | /// use ev3dev_lang_rust::motors::LargeMotor; 482 | /// use std::thread; 483 | /// use std::time::Duration; 484 | /// 485 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 486 | /// // Init a tacho motor. 487 | /// let motor = LargeMotor::find()?; 488 | /// 489 | /// // Save the current position. 490 | /// let old_position = motor.get_position()?; 491 | /// 492 | /// // Rotate by 100 ticks 493 | /// let position = motor.set_position_sp(100)?; 494 | /// motor.run_to_rel_pos(None)?; 495 | /// 496 | /// // Wait till rotation is finished. 497 | /// motor.wait_until_not_moving(None); 498 | /// 499 | /// // The new position should be 100 ticks larger. 500 | /// let new_position = motor.get_position()?; 501 | /// assert_eq!(old_position + 100, new_position); 502 | /// # Ok(()) 503 | /// # } 504 | /// ``` 505 | pub fn set_position_sp(&self, position_sp: i32) -> Ev3Result<()> { 506 | match self.inner { 507 | TachoMotorInner::LargeMotor { ref motor } => motor.set_position_sp(position_sp), 508 | TachoMotorInner::MediumMotor { ref motor } => motor.set_position_sp(position_sp), 509 | } 510 | } 511 | 512 | /// Returns the current motor speed in tacho counts per second. 513 | /// 514 | /// Note, this is not necessarily degrees (although it is for LEGO motors). 515 | /// Use the `count_per_rot` attribute to convert this value to RPM or deg/sec. 516 | pub fn get_speed(&self) -> Ev3Result { 517 | match self.inner { 518 | TachoMotorInner::LargeMotor { ref motor } => motor.get_speed(), 519 | TachoMotorInner::MediumMotor { ref motor } => motor.get_speed(), 520 | } 521 | } 522 | 523 | /// Returns the target speed in tacho counts per second used for all run-* commands except run-direct. 524 | /// 525 | /// A negative value causes the motor to rotate in reverse 526 | /// with the exception of run-to-*-pos commands where the sign is ignored. 527 | /// Use the `count_per_rot` attribute to convert RPM or deg/sec to tacho counts per second. 528 | /// Use the `count_per_m` attribute to convert m/s to tacho counts per second. 529 | pub fn get_speed_sp(&self) -> Ev3Result { 530 | match self.inner { 531 | TachoMotorInner::LargeMotor { ref motor } => motor.get_speed_sp(), 532 | TachoMotorInner::MediumMotor { ref motor } => motor.get_speed_sp(), 533 | } 534 | } 535 | 536 | /// Sets the target speed in tacho counts per second used for all run-* commands except run-direct. 537 | /// 538 | /// A negative value causes the motor to rotate in reverse 539 | /// with the exception of run-to-*-pos commands where the sign is ignored. 540 | /// Use the `count_per_rot` attribute to convert RPM or deg/sec to tacho counts per second. 541 | /// Use the `count_per_m` attribute to convert m/s to tacho counts per second. 542 | pub fn set_speed_sp(&self, speed_sp: i32) -> Ev3Result<()> { 543 | match self.inner { 544 | TachoMotorInner::LargeMotor { ref motor } => motor.set_speed_sp(speed_sp), 545 | TachoMotorInner::MediumMotor { ref motor } => motor.set_speed_sp(speed_sp), 546 | } 547 | } 548 | 549 | /// Returns the current ramp up setpoint. 550 | /// 551 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 552 | /// the motor speed will increase from 0 to 100% of `max_speed` over the span of this setpoint. 553 | /// The actual ramp time is the ratio of the difference between the speed_sp 554 | /// and the current speed and max_speed multiplied by ramp_up_sp. Values must not be negative. 555 | pub fn get_ramp_up_sp(&self) -> Ev3Result { 556 | match self.inner { 557 | TachoMotorInner::LargeMotor { ref motor } => motor.get_ramp_up_sp(), 558 | TachoMotorInner::MediumMotor { ref motor } => motor.get_ramp_up_sp(), 559 | } 560 | } 561 | 562 | /// Sets the ramp up setpoint. 563 | /// 564 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 565 | /// the motor speed will increase from 0 to 100% of `max_speed` over the span of this setpoint. 566 | /// The actual ramp time is the ratio of the difference between the speed_sp 567 | /// and the current speed and max_speed multiplied by ramp_up_sp. Values must not be negative. 568 | pub fn set_ramp_up_sp(&self, ramp_up_sp: i32) -> Ev3Result<()> { 569 | match self.inner { 570 | TachoMotorInner::LargeMotor { ref motor } => motor.set_ramp_up_sp(ramp_up_sp), 571 | TachoMotorInner::MediumMotor { ref motor } => motor.set_ramp_up_sp(ramp_up_sp), 572 | } 573 | } 574 | 575 | /// Returns the current ramp down setpoint. 576 | /// 577 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 578 | /// the motor speed will decrease from 100% down to 0 of `max_speed` over the span of this setpoint. 579 | /// The actual ramp time is the ratio of the difference between the speed_sp 580 | /// and the current speed and 0 multiplied by ramp_down_sp. Values must not be negative. 581 | pub fn get_ramp_down_sp(&self) -> Ev3Result { 582 | match self.inner { 583 | TachoMotorInner::LargeMotor { ref motor } => motor.get_ramp_down_sp(), 584 | TachoMotorInner::MediumMotor { ref motor } => motor.get_ramp_down_sp(), 585 | } 586 | } 587 | 588 | /// Sets the ramp down setpoint. 589 | /// 590 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 591 | /// the motor speed will decrease from 100% down to 0 of `max_speed` over the span of this setpoint. 592 | /// The actual ramp time is the ratio of the difference between the speed_sp 593 | /// and the current speed and 0 multiplied by ramp_down_sp. Values must not be negative. 594 | pub fn set_ramp_down_sp(&self, ramp_down_sp: i32) -> Ev3Result<()> { 595 | match self.inner { 596 | TachoMotorInner::LargeMotor { ref motor } => motor.set_ramp_down_sp(ramp_down_sp), 597 | TachoMotorInner::MediumMotor { ref motor } => motor.set_ramp_down_sp(ramp_down_sp), 598 | } 599 | } 600 | 601 | /// Returns the proportional pub constant for the speed regulation PID. 602 | pub fn get_speed_pid_kp(&self) -> Ev3Result { 603 | match self.inner { 604 | TachoMotorInner::LargeMotor { ref motor } => motor.get_speed_pid_kp(), 605 | TachoMotorInner::MediumMotor { ref motor } => motor.get_speed_pid_kp(), 606 | } 607 | } 608 | 609 | /// Sets the proportional pub constant for the speed regulation PID. 610 | pub fn set_speed_pid_kp(&self, kp: f32) -> Ev3Result<()> { 611 | match self.inner { 612 | TachoMotorInner::LargeMotor { ref motor } => motor.set_speed_pid_kp(kp), 613 | TachoMotorInner::MediumMotor { ref motor } => motor.set_speed_pid_kp(kp), 614 | } 615 | } 616 | 617 | /// Returns the integral pub constant for the speed regulation PID. 618 | pub fn get_speed_pid_ki(&self) -> Ev3Result { 619 | match self.inner { 620 | TachoMotorInner::LargeMotor { ref motor } => motor.get_speed_pid_ki(), 621 | TachoMotorInner::MediumMotor { ref motor } => motor.get_speed_pid_ki(), 622 | } 623 | } 624 | 625 | /// Sets the integral pub constant for the speed regulation PID. 626 | pub fn set_speed_pid_ki(&self, ki: f32) -> Ev3Result<()> { 627 | match self.inner { 628 | TachoMotorInner::LargeMotor { ref motor } => motor.set_speed_pid_ki(ki), 629 | TachoMotorInner::MediumMotor { ref motor } => motor.set_speed_pid_ki(ki), 630 | } 631 | } 632 | 633 | /// Returns the derivative pub constant for the speed regulation PID. 634 | pub fn get_speed_pid_kd(&self) -> Ev3Result { 635 | match self.inner { 636 | TachoMotorInner::LargeMotor { ref motor } => motor.get_speed_pid_kd(), 637 | TachoMotorInner::MediumMotor { ref motor } => motor.get_speed_pid_kd(), 638 | } 639 | } 640 | 641 | /// Sets the derivative pub constant for the speed regulation PID. 642 | pub fn set_speed_pid_kd(&self, kd: f32) -> Ev3Result<()> { 643 | match self.inner { 644 | TachoMotorInner::LargeMotor { ref motor } => motor.set_speed_pid_kd(kd), 645 | TachoMotorInner::MediumMotor { ref motor } => motor.set_speed_pid_kd(kd), 646 | } 647 | } 648 | 649 | /// Returns a list of state flags. 650 | pub fn get_state(&self) -> Ev3Result> { 651 | match self.inner { 652 | TachoMotorInner::LargeMotor { ref motor } => motor.get_state(), 653 | TachoMotorInner::MediumMotor { ref motor } => motor.get_state(), 654 | } 655 | } 656 | 657 | /// Returns the current stop action. 658 | /// 659 | /// The value determines the motors behavior when command is set to stop. 660 | pub fn get_stop_action(&self) -> Ev3Result { 661 | match self.inner { 662 | TachoMotorInner::LargeMotor { ref motor } => motor.get_stop_action(), 663 | TachoMotorInner::MediumMotor { ref motor } => motor.get_stop_action(), 664 | } 665 | } 666 | 667 | /// Sets the stop action. 668 | /// 669 | /// The value determines the motors behavior when command is set to stop. 670 | pub fn set_stop_action(&self, stop_action: &str) -> Ev3Result<()> { 671 | match self.inner { 672 | TachoMotorInner::LargeMotor { ref motor } => motor.set_stop_action(stop_action), 673 | TachoMotorInner::MediumMotor { ref motor } => motor.set_stop_action(stop_action), 674 | } 675 | } 676 | 677 | /// Returns a list of stop actions supported by the motor controller. 678 | pub fn get_stop_actions(&self) -> Ev3Result> { 679 | match self.inner { 680 | TachoMotorInner::LargeMotor { ref motor } => motor.get_stop_actions(), 681 | TachoMotorInner::MediumMotor { ref motor } => motor.get_stop_actions(), 682 | } 683 | } 684 | 685 | /// Returns the current amount of time the motor will run when using the run-timed command. 686 | /// 687 | /// Units are in milliseconds. Values must not be negative. 688 | pub fn get_time_sp(&self) -> Ev3Result { 689 | match self.inner { 690 | TachoMotorInner::LargeMotor { ref motor } => motor.get_time_sp(), 691 | TachoMotorInner::MediumMotor { ref motor } => motor.get_time_sp(), 692 | } 693 | } 694 | 695 | /// Sets the amount of time the motor will run when using the run-timed command. 696 | /// 697 | /// Units are in milliseconds. Values must not be negative. 698 | pub fn set_time_sp(&self, time_sp: i32) -> Ev3Result<()> { 699 | match self.inner { 700 | TachoMotorInner::LargeMotor { ref motor } => motor.set_time_sp(time_sp), 701 | TachoMotorInner::MediumMotor { ref motor } => motor.set_time_sp(time_sp), 702 | } 703 | } 704 | 705 | /// Runs the motor using the duty cycle specified by `duty_cycle_sp`. 706 | /// 707 | /// Unlike other run commands, changing `duty_cycle_sp` while running will take effect immediately. 708 | pub fn run_direct(&self) -> Ev3Result<()> { 709 | match self.inner { 710 | TachoMotorInner::LargeMotor { ref motor } => motor.run_direct(), 711 | TachoMotorInner::MediumMotor { ref motor } => motor.run_direct(), 712 | } 713 | } 714 | 715 | /// Causes the motor to run until another command is sent. 716 | pub fn run_forever(&self) -> Ev3Result<()> { 717 | match self.inner { 718 | TachoMotorInner::LargeMotor { ref motor } => motor.run_forever(), 719 | TachoMotorInner::MediumMotor { ref motor } => motor.run_forever(), 720 | } 721 | } 722 | 723 | /// Runs the motor to an absolute position specified by `position_sp` 724 | /// 725 | /// and then stops the motor using the command specified in `stop_action`. 726 | pub fn run_to_abs_pos(&self, position_sp: Option) -> Ev3Result<()> { 727 | match self.inner { 728 | TachoMotorInner::LargeMotor { ref motor } => motor.run_to_abs_pos(position_sp), 729 | TachoMotorInner::MediumMotor { ref motor } => motor.run_to_abs_pos(position_sp), 730 | } 731 | } 732 | 733 | /// Runs the motor to a position relative to the current position value. 734 | /// 735 | /// The new position will be current `position` + `position_sp`. 736 | /// When the new position is reached, the motor will stop using the command specified by `stop_action`. 737 | pub fn run_to_rel_pos(&self, position_sp: Option) -> Ev3Result<()> { 738 | match self.inner { 739 | TachoMotorInner::LargeMotor { ref motor } => motor.run_to_rel_pos(position_sp), 740 | TachoMotorInner::MediumMotor { ref motor } => motor.run_to_rel_pos(position_sp), 741 | } 742 | } 743 | 744 | /// Run the motor for the amount of time specified in `time_sp` 745 | /// 746 | /// and then stops the motor using the command specified by `stop_action`. 747 | pub fn run_timed(&self, time_sp: Option) -> Ev3Result<()> { 748 | match self.inner { 749 | TachoMotorInner::LargeMotor { ref motor } => motor.run_timed(time_sp), 750 | TachoMotorInner::MediumMotor { ref motor } => motor.run_timed(time_sp), 751 | } 752 | } 753 | 754 | /// Stop any of the run commands before they are complete using the command specified by `stop_action`. 755 | pub fn stop(&self) -> Ev3Result<()> { 756 | match self.inner { 757 | TachoMotorInner::LargeMotor { ref motor } => motor.stop(), 758 | TachoMotorInner::MediumMotor { ref motor } => motor.stop(), 759 | } 760 | } 761 | 762 | /// Resets all of the motor parameter attributes to their default values. 763 | /// This will also have the effect of stopping the motor. 764 | pub fn reset(&self) -> Ev3Result<()> { 765 | match self.inner { 766 | TachoMotorInner::LargeMotor { ref motor } => motor.reset(), 767 | TachoMotorInner::MediumMotor { ref motor } => motor.reset(), 768 | } 769 | } 770 | 771 | /// Power is being sent to the motor. 772 | pub fn is_running(&self) -> Ev3Result { 773 | match self.inner { 774 | TachoMotorInner::LargeMotor { ref motor } => motor.is_running(), 775 | TachoMotorInner::MediumMotor { ref motor } => motor.is_running(), 776 | } 777 | } 778 | 779 | /// The motor is ramping up or down and has not yet reached a pub constant output level. 780 | pub fn is_ramping(&self) -> Ev3Result { 781 | match self.inner { 782 | TachoMotorInner::LargeMotor { ref motor } => motor.is_ramping(), 783 | TachoMotorInner::MediumMotor { ref motor } => motor.is_ramping(), 784 | } 785 | } 786 | 787 | /// The motor is not turning, but rather attempting to hold a fixed position. 788 | pub fn is_holding(&self) -> Ev3Result { 789 | match self.inner { 790 | TachoMotorInner::LargeMotor { ref motor } => motor.is_holding(), 791 | TachoMotorInner::MediumMotor { ref motor } => motor.is_holding(), 792 | } 793 | } 794 | 795 | /// The motor is turning as fast as possible, but cannot reach its `speed_sp`. 796 | pub fn is_overloaded(&self) -> Ev3Result { 797 | match self.inner { 798 | TachoMotorInner::LargeMotor { ref motor } => motor.is_overloaded(), 799 | TachoMotorInner::MediumMotor { ref motor } => motor.is_overloaded(), 800 | } 801 | } 802 | 803 | /// The motor is trying to run but is not turning at all. 804 | pub fn is_stalled(&self) -> Ev3Result { 805 | match self.inner { 806 | TachoMotorInner::LargeMotor { ref motor } => motor.is_stalled(), 807 | TachoMotorInner::MediumMotor { ref motor } => motor.is_stalled(), 808 | } 809 | } 810 | 811 | /// Wait until condition `cond` returns true or the `timeout` is reached. 812 | /// 813 | /// The condition is checked when to the `state` attribute has changed. 814 | /// If the `timeout` is `None` it will wait an infinite time. 815 | /// 816 | /// # Examples 817 | /// 818 | /// ```no_run 819 | /// use ev3dev_lang_rust::motors::LargeMotor; 820 | /// use std::time::Duration; 821 | /// 822 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 823 | /// // Init a tacho motor. 824 | /// let motor = LargeMotor::find()?; 825 | /// 826 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 827 | /// 828 | /// let cond = || { 829 | /// motor.get_state() 830 | /// .unwrap_or_else(|_| vec![]) 831 | /// .iter() 832 | /// .all(|s| s != LargeMotor::STATE_RUNNING) 833 | /// }; 834 | /// motor.wait(cond, None); 835 | /// 836 | /// println!("Motor has stopped!"); 837 | /// # Ok(()) 838 | /// # } 839 | /// ``` 840 | #[cfg(target_os = "linux")] 841 | pub fn wait(&self, cond: F, timeout: Option) -> bool 842 | where 843 | F: Fn() -> bool, 844 | { 845 | match self.inner { 846 | TachoMotorInner::LargeMotor { ref motor } => motor.wait(cond, timeout), 847 | TachoMotorInner::MediumMotor { ref motor } => motor.wait(cond, timeout), 848 | } 849 | } 850 | 851 | /// Wait while the `state` is in the vector `self.get_state()` or the `timeout` is reached. 852 | /// 853 | /// If the `timeout` is `None` it will wait an infinite time. 854 | /// 855 | /// # Example 856 | /// 857 | /// ```no_run 858 | /// use ev3dev_lang_rust::motors::LargeMotor; 859 | /// use std::time::Duration; 860 | /// 861 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 862 | /// // Init a tacho motor. 863 | /// let motor = LargeMotor::find()?; 864 | /// 865 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 866 | /// 867 | /// motor.wait_while(LargeMotor::STATE_RUNNING, None); 868 | /// 869 | /// println!("Motor has stopped!"); 870 | /// # Ok(()) 871 | /// # } 872 | /// ``` 873 | #[cfg(target_os = "linux")] 874 | pub fn wait_while(&self, state: &str, timeout: Option) -> bool { 875 | match self.inner { 876 | TachoMotorInner::LargeMotor { ref motor } => motor.wait_while(state, timeout), 877 | TachoMotorInner::MediumMotor { ref motor } => motor.wait_while(state, timeout), 878 | } 879 | } 880 | 881 | /// Wait until the `state` is in the vector `self.get_state()` or the `timeout` is reached. 882 | /// 883 | /// If the `timeout` is `None` it will wait an infinite time. 884 | /// 885 | /// # Example 886 | /// 887 | /// ```no_run 888 | /// use ev3dev_lang_rust::motors::LargeMotor; 889 | /// use std::time::Duration; 890 | /// 891 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 892 | /// // Init a tacho motor. 893 | /// let motor = LargeMotor::find()?; 894 | /// 895 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 896 | /// 897 | /// motor.wait_until(LargeMotor::STATE_RUNNING, None); 898 | /// 899 | /// println!("Motor has started!"); 900 | /// # Ok(()) 901 | /// # } 902 | /// ``` 903 | #[cfg(target_os = "linux")] 904 | pub fn wait_until(&self, state: &str, timeout: Option) -> bool { 905 | match self.inner { 906 | TachoMotorInner::LargeMotor { ref motor } => motor.wait_until(state, timeout), 907 | TachoMotorInner::MediumMotor { ref motor } => motor.wait_until(state, timeout), 908 | } 909 | } 910 | 911 | /// Wait until the motor is not moving or the timeout is reached. 912 | /// 913 | /// This is equal to `wait_while(STATE_RUNNING, timeout)`. 914 | /// If the `timeout` is `None` it will wait an infinite time. 915 | /// 916 | /// # Example 917 | /// 918 | /// ```no_run 919 | /// use ev3dev_lang_rust::motors::LargeMotor; 920 | /// use std::time::Duration; 921 | /// 922 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 923 | /// // Init a tacho motor. 924 | /// let motor = LargeMotor::find()?; 925 | /// 926 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 927 | /// 928 | /// motor.wait_until_not_moving(None); 929 | /// 930 | /// println!("Motor has stopped!"); 931 | /// # Ok(()) 932 | /// # } 933 | /// ``` 934 | #[cfg(target_os = "linux")] 935 | pub fn wait_until_not_moving(&self, timeout: Option) -> bool { 936 | match self.inner { 937 | TachoMotorInner::LargeMotor { ref motor } => motor.wait_until_not_moving(timeout), 938 | TachoMotorInner::MediumMotor { ref motor } => motor.wait_until_not_moving(timeout), 939 | } 940 | } 941 | } 942 | 943 | impl From for TachoMotor { 944 | fn from(motor: LargeMotor) -> Self { 945 | Self { 946 | inner: TachoMotorInner::LargeMotor { motor }, 947 | } 948 | } 949 | } 950 | 951 | impl From for TachoMotor { 952 | fn from(motor: MediumMotor) -> Self { 953 | Self { 954 | inner: TachoMotorInner::MediumMotor { motor }, 955 | } 956 | } 957 | } 958 | -------------------------------------------------------------------------------- /src/motors/tacho_motor_macro.rs: -------------------------------------------------------------------------------- 1 | //! The TachoMotor provides a uniform interface for using motors with positional 2 | //! and directional feedback such as the EV3 and NXT motors. 3 | //! This feedback allows for precise control of the motors. 4 | 5 | /// The TachoMotor provides a uniform interface for using motors with positional 6 | /// and directional feedback such as the EV3 and NXT motors. 7 | /// This feedback allows for precise control of the motors. 8 | #[macro_export] 9 | macro_rules! tacho_motor { 10 | () => { 11 | /// Causes the motor to run until another command is sent. 12 | pub const COMMAND_RUN_FOREVER: &'static str = "run-forever"; 13 | 14 | /// Runs the motor to an absolute position specified by `position_sp` 15 | /// and then stops the motor using the command specified in `stop_action`. 16 | pub const COMMAND_RUN_TO_ABS_POS: &'static str = "run-to-abs-pos"; 17 | 18 | /// Runs the motor to a position relative to the current position value. 19 | /// The new position will be current `position` + `position_sp`. 20 | /// When the new position is reached, the motor will stop using the command specified by `stop_action`. 21 | pub const COMMAND_RUN_TO_REL_POS: &'static str = "run-to-rel-pos"; 22 | 23 | /// Run the motor for the amount of time specified in `time_sp` 24 | /// and then stops the motor using the command specified by `stop_action`. 25 | pub const COMMAND_RUN_TIMED: &'static str = "run-timed"; 26 | 27 | /// Runs the motor using the duty cycle specified by `duty_cycle_sp`. 28 | /// Unlike other run commands, changing `duty_cycle_sp` while running will take effect immediately. 29 | pub const COMMAND_RUN_DIRECT: &'static str = "run-direct"; 30 | 31 | /// Stop any of the run commands before they are complete using the command specified by `stop_action`. 32 | pub const COMMAND_STOP: &'static str = "stop"; 33 | 34 | /// Resets all of the motor parameter attributes to their default values. 35 | /// This will also have the effect of stopping the motor. 36 | pub const COMMAND_RESET: &'static str = "reset"; 37 | 38 | /// A positive duty cycle will cause the motor to rotate clockwise. 39 | pub const POLARITY_NORMAL: &'static str = "normal"; 40 | 41 | /// A positive duty cycle will cause the motor to rotate counter-clockwise. 42 | pub const POLARITY_INVERSED: &'static str = "inversed"; 43 | 44 | /// Power is being sent to the motor. 45 | pub const STATE_RUNNING: &'static str = "running"; 46 | 47 | /// The motor is ramping up or down and has not yet reached a pub constant output level. 48 | pub const STATE_RAMPING: &'static str = "ramping"; 49 | 50 | /// The motor is not turning, but rather attempting to hold a fixed position. 51 | pub const STATE_HOLDING: &'static str = "holding"; 52 | 53 | /// The motor is turning as fast as possible, but cannot reach its `speed_sp`. 54 | pub const STATE_OVERLOADED: &'static str = "overloaded"; 55 | 56 | /// The motor is trying to run but is not turning at all. 57 | pub const STATE_STALLED: &'static str = "stalled"; 58 | 59 | /// Removes power from the motor. The motor will freely coast to a stop. 60 | pub const STOP_ACTION_COAST: &'static str = "coast"; 61 | 62 | /// Removes power from the motor and creates a passive electrical load. 63 | /// This is usually done by shorting the motor terminals together. 64 | /// This load will absorb the energy from the rotation of the motors 65 | /// and cause the motor to stop more quickly than coasting. 66 | pub const STOP_ACTION_BRAKE: &'static str = "brake"; 67 | 68 | /// Causes the motor to actively try to hold the current position. 69 | /// If an external force tries to turn the motor, the motor will “push back” to maintain its position. 70 | pub const STOP_ACTION_HOLD: &'static str = "hold"; 71 | 72 | /// Returns the number of tacho counts in one rotation of the motor. 73 | /// 74 | /// Tacho counts are used by the position and speed attributes, 75 | /// so you can use this value to convert from rotations or degrees to tacho counts. 76 | /// (rotation motors only) 77 | /// 78 | /// # Examples 79 | /// 80 | /// ```no_run 81 | /// use ev3dev_lang_rust::motors::LargeMotor; 82 | /// 83 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 84 | /// // Init a tacho motor. 85 | /// let motor = LargeMotor::find()?; 86 | /// 87 | /// // Get position and count_per_rot as f32. 88 | /// let position = motor.get_position()? as f32; 89 | /// let count_per_rot = motor.get_count_per_rot()? as f32; 90 | /// 91 | /// // Calculate the rotation count. 92 | /// let rotations = position / count_per_rot; 93 | /// 94 | /// println!("The motor did {:.2} rotations", rotations); 95 | /// # Ok(()) 96 | /// # } 97 | /// ``` 98 | pub fn get_count_per_rot(&self) -> Ev3Result { 99 | self.get_attribute("count_per_rot").get() 100 | } 101 | 102 | /// Returns the number of tacho counts in one meter of travel of the motor. 103 | /// 104 | /// Tacho counts are used by the position and speed attributes, 105 | /// so you can use this value to convert from distance to tacho counts. 106 | /// (linear motors only) 107 | pub fn get_count_per_m(&self) -> Ev3Result { 108 | self.get_attribute("count_per_m").get() 109 | } 110 | 111 | /// Returns the number of tacho counts in the full travel of the motor. 112 | /// 113 | /// When combined with the count_per_m attribute, 114 | /// you can use this value to calculate the maximum travel distance of the motor. 115 | /// (linear motors only) 116 | pub fn get_full_travel_count(&self) -> Ev3Result { 117 | self.get_attribute("full_travel_count").get() 118 | } 119 | 120 | /// Returns the current duty cycle of the motor. Units are percent. 121 | /// 122 | /// Values are -100 to 100. 123 | /// 124 | /// # Examples 125 | /// 126 | /// ```no_run 127 | /// use ev3dev_lang_rust::motors::LargeMotor; 128 | /// use std::thread; 129 | /// use std::time::Duration; 130 | /// 131 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 132 | /// // Init a tacho motor. 133 | /// let motor = LargeMotor::find()?; 134 | /// 135 | /// // Set the motor command `run-direct` to start rotation. 136 | /// motor.run_direct()?; 137 | /// 138 | /// // Rotate motor forward and wait 5 seconds. 139 | /// motor.set_duty_cycle_sp(50)?; 140 | /// thread::sleep(Duration::from_secs(5)); 141 | /// 142 | /// assert_eq!(motor.get_duty_cycle()?, 50); 143 | /// # Ok(()) 144 | /// # } 145 | pub fn get_duty_cycle(&self) -> Ev3Result { 146 | self.get_attribute("duty_cycle").get() 147 | } 148 | 149 | /// Returns the current duty cycle setpoint of the motor. 150 | /// 151 | /// Units are in percent. 152 | /// Valid values are -100 to 100. A negative value causes the motor to rotate in reverse. 153 | /// 154 | /// # Examples 155 | /// 156 | /// ```no_run 157 | /// use ev3dev_lang_rust::motors::LargeMotor; 158 | /// use std::thread; 159 | /// use std::time::Duration; 160 | /// 161 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 162 | /// // Init a tacho motor. 163 | /// let motor = LargeMotor::find()?; 164 | /// 165 | /// // Rotate motor forward and wait 5 seconds. 166 | /// motor.set_duty_cycle_sp(50)?; 167 | /// thread::sleep(Duration::from_secs(5)); 168 | /// 169 | /// assert_eq!(motor.get_duty_cycle()?, 50); 170 | /// # Ok(()) 171 | /// # } 172 | pub fn get_duty_cycle_sp(&self) -> Ev3Result { 173 | self.get_attribute("duty_cycle_sp").get() 174 | } 175 | 176 | /// Sets the duty cycle setpoint of the motor. 177 | /// 178 | /// Units are in percent. 179 | /// Valid values are -100 to 100. A negative value causes the motor to rotate in reverse. 180 | /// 181 | /// # Examples 182 | /// 183 | /// ```no_run 184 | /// use ev3dev_lang_rust::motors::LargeMotor; 185 | /// use std::thread; 186 | /// use std::time::Duration; 187 | /// 188 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 189 | /// // Init a tacho motor. 190 | /// let motor = LargeMotor::find()?; 191 | /// 192 | /// // Set the motor command `run-direct` to start rotation. 193 | /// motor.run_direct()?; 194 | /// 195 | /// // Rotate motor forward and wait 5 seconds. 196 | /// motor.set_duty_cycle_sp(50)?; 197 | /// thread::sleep(Duration::from_secs(5)); 198 | /// 199 | /// // Rotate motor backward and wait 5 seconds. 200 | /// motor.set_duty_cycle_sp(-50)?; 201 | /// thread::sleep(Duration::from_secs(5)); 202 | /// # Ok(()) 203 | /// # } 204 | pub fn set_duty_cycle_sp(&self, duty_cycle: i32) -> Ev3Result<()> { 205 | self.get_attribute("duty_cycle_sp").set(duty_cycle) 206 | } 207 | 208 | /// Returns the current polarity of the motor. 209 | pub fn get_polarity(&self) -> Ev3Result { 210 | self.get_attribute("polarity").get() 211 | } 212 | 213 | /// Sets the polarity of the motor. 214 | pub fn set_polarity(&self, polarity: &str) -> Ev3Result<()> { 215 | self.get_attribute("polarity").set_str_slice(polarity) 216 | } 217 | 218 | /// Returns the current position of the motor in pulses of the rotary encoder. 219 | /// 220 | /// When the motor rotates clockwise, the position will increase. 221 | /// Likewise, rotating counter-clockwise causes the position to decrease. 222 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer) 223 | /// 224 | /// # Examples 225 | /// 226 | /// ```no_run 227 | /// use ev3dev_lang_rust::motors::LargeMotor; 228 | /// 229 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 230 | /// // Init a tacho motor. 231 | /// let motor = LargeMotor::find()?; 232 | /// 233 | /// // Get position and count_per_rot as f32. 234 | /// let position = motor.get_position()? as f32; 235 | /// let count_per_rot = motor.get_count_per_rot()? as f32; 236 | /// 237 | /// // Calculate the rotation count. 238 | /// let rotations: f32 = position / count_per_rot; 239 | /// 240 | /// println!("The motor did {:.2} rotations", rotations); 241 | /// # Ok(()) 242 | /// # } 243 | /// ``` 244 | pub fn get_position(&self) -> Ev3Result { 245 | self.get_attribute("position").get() 246 | } 247 | 248 | /// Sets the current position of the motor in pulses of the rotary encoder. 249 | /// 250 | /// When the motor rotates clockwise, the position will increase. 251 | /// Likewise, rotating counter-clockwise causes the position to decrease. 252 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer) 253 | /// 254 | /// # Examples 255 | /// 256 | /// ```no_run 257 | /// use ev3dev_lang_rust::motors::LargeMotor; 258 | /// 259 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 260 | /// // Init a tacho motor. 261 | /// let motor = LargeMotor::find()?; 262 | /// 263 | /// motor.set_position(0)?; 264 | /// let position = motor.get_position()?; 265 | /// 266 | /// // If the motor is not moving, the position value 267 | /// // should not change between set and get operation. 268 | /// assert_eq!(position, 0); 269 | /// # Ok(()) 270 | /// # } 271 | /// ``` 272 | pub fn set_position(&self, position: i32) -> Ev3Result<()> { 273 | self.get_attribute("position").set(position) 274 | } 275 | 276 | /// Returns the proportional pub constant for the position PID. 277 | pub fn get_hold_pid_kp(&self) -> Ev3Result { 278 | self.get_attribute("hold_pid/Kp").get() 279 | } 280 | 281 | /// Sets the proportional pub constant for the position PID. 282 | pub fn set_hold_pid_kp(&self, kp: f32) -> Ev3Result<()> { 283 | self.get_attribute("hold_pid/Kp").set(kp) 284 | } 285 | 286 | /// Returns the integral pub constant for the position PID. 287 | pub fn get_hold_pid_ki(&self) -> Ev3Result { 288 | self.get_attribute("hold_pid/Ki").get() 289 | } 290 | 291 | /// Sets the integral pub constant for the position PID. 292 | pub fn set_hold_pid_ki(&self, ki: f32) -> Ev3Result<()> { 293 | self.get_attribute("hold_pid/Ki").set(ki) 294 | } 295 | 296 | /// Returns the derivative pub constant for the position PID. 297 | pub fn get_hold_pid_kd(&self) -> Ev3Result { 298 | self.get_attribute("hold_pid/Kd").get() 299 | } 300 | 301 | /// Sets the derivative pub constant for the position PID. 302 | pub fn set_hold_pid_kd(&self, kd: f32) -> Ev3Result<()> { 303 | self.get_attribute("hold_pid/Kd").set(kd) 304 | } 305 | 306 | /// Returns the maximum value that is accepted by the `speed_sp` attribute. 307 | /// 308 | /// This value is the speed of the motor at 9V with no load. 309 | /// Note: The actual maximum obtainable speed will be less than this 310 | /// and will depend on battery voltage and mechanical load on the motor. 311 | pub fn get_max_speed(&self) -> Ev3Result { 312 | self.get_attribute("max_speed").get() 313 | } 314 | 315 | /// Returns the current target position for the `run-to-abs-pos` and `run-to-rel-pos` commands. 316 | /// 317 | /// Units are in tacho counts. 318 | /// You can use the value returned by `counts_per_rot` to convert tacho counts to/from rotations or degrees. 319 | /// 320 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer). 321 | pub fn get_position_sp(&self) -> Ev3Result { 322 | self.get_attribute("position_sp").get() 323 | } 324 | 325 | /// Sets the target position for the `run-to-abs-pos` and `run-to-rel-pos` commands. 326 | /// 327 | /// Units are in tacho counts. 328 | /// You can use the value returned by `counts_per_rot` to convert tacho counts to/from rotations or degrees. 329 | /// 330 | /// The range is -2,147,483,648 and +2,147,483,647 tachometer counts (32-bit signed integer). 331 | /// 332 | /// # Examples 333 | /// 334 | /// ```ignore 335 | /// use ev3dev_lang_rust::motors::LargeMotor; 336 | /// use std::thread; 337 | /// use std::time::Duration; 338 | /// 339 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 340 | /// // Init a tacho motor. 341 | /// let motor = LargeMotor::find()?; 342 | /// 343 | /// // Save the current position. 344 | /// let old_position = motor.get_position()?; 345 | /// 346 | /// // Rotate by 100 ticks 347 | /// let position = motor.set_position_sp(100)?; 348 | /// motor.run_to_rel_pos(None)?; 349 | /// 350 | /// // Wait till rotation is finished. 351 | /// motor.wait_until_not_moving(None); 352 | /// 353 | /// // The new position should be 100 ticks larger. 354 | /// let new_position = motor.get_position()?; 355 | /// assert_eq!(old_position + 100, new_position); 356 | /// # Ok(()) 357 | /// # } 358 | /// ``` 359 | pub fn set_position_sp(&self, position_sp: i32) -> Ev3Result<()> { 360 | self.get_attribute("position_sp").set(position_sp) 361 | } 362 | 363 | /// Returns the current motor speed in tacho counts per second. 364 | /// 365 | /// Note, this is not necessarily degrees (although it is for LEGO motors). 366 | /// Use the `count_per_rot` attribute to convert this value to RPM or deg/sec. 367 | pub fn get_speed(&self) -> Ev3Result { 368 | self.get_attribute("speed").get() 369 | } 370 | 371 | /// Returns the target speed in tacho counts per second used for all run-* commands except run-direct. 372 | /// 373 | /// A negative value causes the motor to rotate in reverse 374 | /// with the exception of run-to-*-pos commands where the sign is ignored. 375 | /// Use the `count_per_rot` attribute to convert RPM or deg/sec to tacho counts per second. 376 | /// Use the `count_per_m` attribute to convert m/s to tacho counts per second. 377 | pub fn get_speed_sp(&self) -> Ev3Result { 378 | self.get_attribute("speed_sp").get() 379 | } 380 | 381 | /// Sets the target speed in tacho counts per second used for all run-* commands except run-direct. 382 | /// 383 | /// A negative value causes the motor to rotate in reverse 384 | /// with the exception of run-to-*-pos commands where the sign is ignored. 385 | /// Use the `count_per_rot` attribute to convert RPM or deg/sec to tacho counts per second. 386 | /// Use the `count_per_m` attribute to convert m/s to tacho counts per second. 387 | pub fn set_speed_sp(&self, speed_sp: i32) -> Ev3Result<()> { 388 | self.get_attribute("speed_sp").set(speed_sp) 389 | } 390 | 391 | /// Returns the current ramp up setpoint. 392 | /// 393 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 394 | /// the motor speed will increase from 0 to 100% of `max_speed` over the span of this setpoint. 395 | /// The actual ramp time is the ratio of the difference between the speed_sp 396 | /// and the current speed and max_speed multiplied by ramp_up_sp. Values must not be negative. 397 | pub fn get_ramp_up_sp(&self) -> Ev3Result { 398 | self.get_attribute("ramp_up_sp").get() 399 | } 400 | 401 | /// Sets the ramp up setpoint. 402 | /// 403 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 404 | /// the motor speed will increase from 0 to 100% of `max_speed` over the span of this setpoint. 405 | /// The actual ramp time is the ratio of the difference between the speed_sp 406 | /// and the current speed and max_speed multiplied by ramp_up_sp. Values must not be negative. 407 | pub fn set_ramp_up_sp(&self, ramp_up_sp: i32) -> Ev3Result<()> { 408 | self.get_attribute("ramp_up_sp").set(ramp_up_sp) 409 | } 410 | 411 | /// Returns the current ramp down setpoint. 412 | /// 413 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 414 | /// the motor speed will decrease from 100% down to 0 of `max_speed` over the span of this setpoint. 415 | /// The actual ramp time is the ratio of the difference between the speed_sp 416 | /// and the current speed and 0 multiplied by ramp_down_sp. Values must not be negative. 417 | pub fn get_ramp_down_sp(&self) -> Ev3Result { 418 | self.get_attribute("ramp_down_sp").get() 419 | } 420 | 421 | /// Sets the ramp down setpoint. 422 | /// 423 | /// Units are in milliseconds and must be positive. When set to a non-zero value, 424 | /// the motor speed will decrease from 100% down to 0 of `max_speed` over the span of this setpoint. 425 | /// The actual ramp time is the ratio of the difference between the speed_sp 426 | /// and the current speed and 0 multiplied by ramp_down_sp. Values must not be negative. 427 | pub fn set_ramp_down_sp(&self, ramp_down_sp: i32) -> Ev3Result<()> { 428 | self.get_attribute("ramp_down_sp").set(ramp_down_sp) 429 | } 430 | 431 | /// Returns the proportional pub constant for the speed regulation PID. 432 | pub fn get_speed_pid_kp(&self) -> Ev3Result { 433 | self.get_attribute("speed_pid/Kp").get() 434 | } 435 | 436 | /// Sets the proportional pub constant for the speed regulation PID. 437 | pub fn set_speed_pid_kp(&self, kp: f32) -> Ev3Result<()> { 438 | self.get_attribute("speed_pid/Kp").set(kp) 439 | } 440 | 441 | /// Returns the integral pub constant for the speed regulation PID. 442 | pub fn get_speed_pid_ki(&self) -> Ev3Result { 443 | self.get_attribute("speed_pid/Ki").get() 444 | } 445 | 446 | /// Sets the integral pub constant for the speed regulation PID. 447 | pub fn set_speed_pid_ki(&self, ki: f32) -> Ev3Result<()> { 448 | self.get_attribute("speed_pid/Ki").set(ki) 449 | } 450 | 451 | /// Returns the derivative pub constant for the speed regulation PID. 452 | pub fn get_speed_pid_kd(&self) -> Ev3Result { 453 | self.get_attribute("speed_pid/Kd").get() 454 | } 455 | 456 | /// Sets the derivative pub constant for the speed regulation PID. 457 | pub fn set_speed_pid_kd(&self, kd: f32) -> Ev3Result<()> { 458 | self.get_attribute("speed_pid/Kd").set(kd) 459 | } 460 | 461 | /// Returns a list of state flags. 462 | pub fn get_state(&self) -> Ev3Result> { 463 | self.get_attribute("state").get_vec() 464 | } 465 | 466 | /// Returns the current stop action. 467 | /// 468 | /// The value determines the motors behavior when command is set to stop. 469 | pub fn get_stop_action(&self) -> Ev3Result { 470 | self.get_attribute("stop_action").get() 471 | } 472 | 473 | /// Sets the stop action. 474 | /// 475 | /// The value determines the motors behavior when command is set to stop. 476 | pub fn set_stop_action(&self, stop_action: &str) -> Ev3Result<()> { 477 | self.get_attribute("stop_action").set_str_slice(stop_action) 478 | } 479 | 480 | /// Returns a list of stop actions supported by the motor controller. 481 | pub fn get_stop_actions(&self) -> Ev3Result> { 482 | self.get_attribute("stop_actions").get_vec() 483 | } 484 | 485 | /// Returns the current amount of time the motor will run when using the run-timed command. 486 | /// 487 | /// Units are in milliseconds. Values must not be negative. 488 | pub fn get_time_sp(&self) -> Ev3Result { 489 | self.get_attribute("time_sp").get() 490 | } 491 | 492 | /// Sets the amount of time the motor will run when using the run-timed command. 493 | /// 494 | /// Units are in milliseconds. Values must not be negative. 495 | pub fn set_time_sp(&self, time_sp: i32) -> Ev3Result<()> { 496 | self.get_attribute("time_sp").set(time_sp) 497 | } 498 | 499 | /// Runs the motor using the duty cycle specified by `duty_cycle_sp`. 500 | /// 501 | /// Unlike other run commands, changing `duty_cycle_sp` while running will take effect immediately. 502 | pub fn run_direct(&self) -> Ev3Result<()> { 503 | self.set_command(Self::COMMAND_RUN_DIRECT) 504 | } 505 | 506 | /// Causes the motor to run until another command is sent. 507 | pub fn run_forever(&self) -> Ev3Result<()> { 508 | self.set_command(Self::COMMAND_RUN_FOREVER) 509 | } 510 | 511 | /// Runs the motor to an absolute position specified by `position_sp` 512 | /// 513 | /// and then stops the motor using the command specified in `stop_action`. 514 | pub fn run_to_abs_pos(&self, position_sp: Option) -> Ev3Result<()> { 515 | if let Some(p) = position_sp { 516 | self.set_position_sp(p)?; 517 | } 518 | self.set_command(Self::COMMAND_RUN_TO_ABS_POS) 519 | } 520 | 521 | /// Runs the motor to a position relative to the current position value. 522 | /// 523 | /// The new position will be current `position` + `position_sp`. 524 | /// When the new position is reached, the motor will stop using the command specified by `stop_action`. 525 | pub fn run_to_rel_pos(&self, position_sp: Option) -> Ev3Result<()> { 526 | if let Some(p) = position_sp { 527 | self.set_position_sp(p)?; 528 | } 529 | self.set_command(Self::COMMAND_RUN_TO_REL_POS) 530 | } 531 | 532 | /// Run the motor for the amount of time specified in `time_sp` 533 | /// 534 | /// and then stops the motor using the command specified by `stop_action`. 535 | pub fn run_timed(&self, time_sp: Option) -> Ev3Result<()> { 536 | if let Some(duration) = time_sp { 537 | let p = duration.as_millis() as i32; 538 | self.set_time_sp(p)?; 539 | } 540 | self.set_command(Self::COMMAND_RUN_TIMED) 541 | } 542 | 543 | /// Stop any of the run commands before they are complete using the command specified by `stop_action`. 544 | pub fn stop(&self) -> Ev3Result<()> { 545 | self.set_command(Self::COMMAND_STOP) 546 | } 547 | 548 | /// Resets all of the motor parameter attributes to their default values. 549 | /// This will also have the effect of stopping the motor. 550 | pub fn reset(&self) -> Ev3Result<()> { 551 | self.set_command(Self::COMMAND_RESET) 552 | } 553 | 554 | /// Power is being sent to the motor. 555 | pub fn is_running(&self) -> Ev3Result { 556 | Ok(self 557 | .get_state()? 558 | .iter() 559 | .any(|state| state == Self::STATE_RUNNING)) 560 | } 561 | 562 | /// The motor is ramping up or down and has not yet reached a pub constant output level. 563 | pub fn is_ramping(&self) -> Ev3Result { 564 | Ok(self 565 | .get_state()? 566 | .iter() 567 | .any(|state| state == Self::STATE_RAMPING)) 568 | } 569 | 570 | /// The motor is not turning, but rather attempting to hold a fixed position. 571 | pub fn is_holding(&self) -> Ev3Result { 572 | Ok(self 573 | .get_state()? 574 | .iter() 575 | .any(|state| state == Self::STATE_HOLDING)) 576 | } 577 | 578 | /// The motor is turning as fast as possible, but cannot reach its `speed_sp`. 579 | pub fn is_overloaded(&self) -> Ev3Result { 580 | Ok(self 581 | .get_state()? 582 | .iter() 583 | .any(|state| state == Self::STATE_OVERLOADED)) 584 | } 585 | 586 | /// The motor is trying to run but is not turning at all. 587 | pub fn is_stalled(&self) -> Ev3Result { 588 | Ok(self 589 | .get_state()? 590 | .iter() 591 | .any(|state| state == Self::STATE_STALLED)) 592 | } 593 | 594 | /// Wait until condition `cond` returns true or the `timeout` is reached. 595 | /// 596 | /// The condition is checked when to the `state` attribute has changed. 597 | /// If the `timeout` is `None` it will wait an infinite time. 598 | /// 599 | /// # Examples 600 | /// 601 | /// ```no_run 602 | /// use ev3dev_lang_rust::motors::LargeMotor; 603 | /// use std::time::Duration; 604 | /// 605 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 606 | /// // Init a tacho motor. 607 | /// let motor = LargeMotor::find()?; 608 | /// 609 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 610 | /// 611 | /// let cond = || { 612 | /// motor.get_state() 613 | /// .unwrap_or_else(|_| vec![]) 614 | /// .iter() 615 | /// .all(|s| s != LargeMotor::STATE_RUNNING) 616 | /// }; 617 | /// motor.wait(cond, None); 618 | /// 619 | /// println!("Motor has stopped!"); 620 | /// # Ok(()) 621 | /// # } 622 | /// ``` 623 | pub fn wait(&self, cond: F, timeout: Option) -> bool 624 | where 625 | F: Fn() -> bool, 626 | { 627 | let fd = self.get_attribute("state").get_raw_fd(); 628 | wait::wait(fd, cond, timeout) 629 | } 630 | 631 | /// Wait while the `state` is in the vector `self.get_state()` or the `timeout` is reached. 632 | /// 633 | /// If the `timeout` is `None` it will wait an infinite time. 634 | /// 635 | /// # Example 636 | /// 637 | /// ```no_run 638 | /// use ev3dev_lang_rust::motors::LargeMotor; 639 | /// use std::time::Duration; 640 | /// 641 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 642 | /// // Init a tacho motor. 643 | /// let motor = LargeMotor::find()?; 644 | /// 645 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 646 | /// 647 | /// motor.wait_while(LargeMotor::STATE_RUNNING, None); 648 | /// 649 | /// println!("Motor has stopped!"); 650 | /// # Ok(()) 651 | /// # } 652 | /// ``` 653 | pub fn wait_while(&self, state: &str, timeout: Option) -> bool { 654 | let cond = || { 655 | self.get_state() 656 | .unwrap_or_else(|_| vec![]) 657 | .iter() 658 | .all(|s| s != state) 659 | }; 660 | self.wait(cond, timeout) 661 | } 662 | 663 | /// Wait until the `state` is in the vector `self.get_state()` or the `timeout` is reached. 664 | /// 665 | /// If the `timeout` is `None` it will wait an infinite time. 666 | /// 667 | /// # Example 668 | /// 669 | /// ```no_run 670 | /// use ev3dev_lang_rust::motors::LargeMotor; 671 | /// use std::time::Duration; 672 | /// 673 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 674 | /// // Init a tacho motor. 675 | /// let motor = LargeMotor::find()?; 676 | /// 677 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 678 | /// 679 | /// motor.wait_until(LargeMotor::STATE_RUNNING, None); 680 | /// 681 | /// println!("Motor has started!"); 682 | /// # Ok(()) 683 | /// # } 684 | /// ``` 685 | pub fn wait_until(&self, state: &str, timeout: Option) -> bool { 686 | let cond = || { 687 | self.get_state() 688 | .unwrap_or_else(|_| vec![]) 689 | .iter() 690 | .any(|s| s == state) 691 | }; 692 | self.wait(cond, timeout) 693 | } 694 | 695 | /// Wait until the motor is not moving or the timeout is reached. 696 | /// 697 | /// This is equal to `wait_while(STATE_RUNNING, timeout)`. 698 | /// If the `timeout` is `None` it will wait an infinite time. 699 | /// 700 | /// # Example 701 | /// 702 | /// ```no_run 703 | /// use ev3dev_lang_rust::motors::LargeMotor; 704 | /// use std::time::Duration; 705 | /// 706 | /// # fn main() -> ev3dev_lang_rust::Ev3Result<()> { 707 | /// // Init a tacho motor. 708 | /// let motor = LargeMotor::find()?; 709 | /// 710 | /// motor.run_timed(Some(Duration::from_secs(5)))?; 711 | /// 712 | /// motor.wait_until_not_moving(None); 713 | /// 714 | /// println!("Motor has stopped!"); 715 | /// # Ok(()) 716 | /// # } 717 | /// ``` 718 | pub fn wait_until_not_moving(&self, timeout: Option) -> bool { 719 | self.wait_while(Self::STATE_RUNNING, timeout) 720 | } 721 | }; 722 | } 723 | -------------------------------------------------------------------------------- /src/power_supply.rs: -------------------------------------------------------------------------------- 1 | //! An interface to read data from the system’s power_supply class. 2 | //! Uses the built-in legoev3-battery if none is specified. 3 | 4 | use std::{fs, path::Path}; 5 | 6 | use crate::driver::DRIVER_PATH; 7 | use crate::utils::OrErr; 8 | use crate::{Attribute, Device, Driver, Ev3Error, Ev3Result}; 9 | 10 | /// An interface to read data from the system’s power_supply class. 11 | /// Uses the built-in legoev3-battery if none is specified. 12 | #[derive(Debug, Clone, Device)] 13 | pub struct PowerSupply { 14 | driver: Driver, 15 | } 16 | 17 | impl PowerSupply { 18 | /// Create a new instance of `PowerSupply`. 19 | pub fn new() -> Ev3Result { 20 | let paths = fs::read_dir(Path::new(DRIVER_PATH).join("power_supply"))?; 21 | 22 | for path in paths { 23 | let file_name = path?.file_name(); 24 | let name = file_name.to_str().or_err()?; 25 | 26 | if name.contains("ev3-battery") { 27 | return Ok(PowerSupply { 28 | driver: Driver::new("power_supply", name), 29 | }); 30 | } 31 | } 32 | 33 | Err(Ev3Error::NotConnected { 34 | device: "power_supply".to_owned(), 35 | port: None, 36 | }) 37 | } 38 | 39 | /// Returns the battery current in microamps 40 | pub fn get_current_now(&self) -> Ev3Result { 41 | self.get_attribute("current_now").get() 42 | } 43 | 44 | /// Always returns System. 45 | pub fn get_scope(&self) -> Ev3Result { 46 | self.get_attribute("zscope").get() 47 | } 48 | 49 | /// Returns Unknown or Li-ion depending on if the rechargeable battery is present. 50 | pub fn get_technology(&self) -> Ev3Result { 51 | self.get_attribute("technology").get() 52 | } 53 | 54 | /// Always returns Battery. 55 | pub fn get_type(&self) -> Ev3Result { 56 | self.get_attribute("type").get() 57 | } 58 | 59 | /// Returns the nominal “full” battery voltage. The value returned depends on technology. 60 | pub fn get_voltage_max_design(&self) -> Ev3Result { 61 | self.get_attribute("voltage_max_design").get() 62 | } 63 | 64 | /// Returns the nominal “empty” battery voltage. The value returned depends on technology. 65 | pub fn get_voltage_min_design(&self) -> Ev3Result { 66 | self.get_attribute("voltage_min_design").get() 67 | } 68 | 69 | /// Returns the battery voltage in microvolts. 70 | pub fn get_voltage_now(&self) -> Ev3Result { 71 | self.get_attribute("voltage_now").get() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/screen.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "screen")] 2 | use framebuffer::Framebuffer; 3 | 4 | #[cfg(feature = "screen")] 5 | use image::{Rgb, RgbImage}; 6 | 7 | use crate::Ev3Result; 8 | 9 | /// Represents the device screen. 10 | /// Advanced drawing operations can be performed with the `imageproc` crate. 11 | #[cfg(feature = "screen")] 12 | #[derive(Debug)] 13 | pub struct Screen { 14 | /// Direct reference to the framebuffer 15 | pub buffer: Framebuffer, 16 | /// Convenience layer to access the framebuffer 17 | /// For drawing operations the `imageproc` crate can be used. 18 | pub image: RgbImage, 19 | } 20 | 21 | #[cfg(feature = "screen")] 22 | impl Screen { 23 | /// Create a reference to the device screen 24 | pub fn new() -> Ev3Result { 25 | let buffer = Framebuffer::new("/dev/fb0")?; 26 | 27 | let image = RgbImage::from_pixel( 28 | buffer.fix_screen_info.line_length * 8 / buffer.var_screen_info.bits_per_pixel, 29 | buffer.var_screen_info.yres, 30 | Rgb([255, 255, 255]), 31 | ); 32 | 33 | Ok(Self { buffer, image }) 34 | } 35 | 36 | /// Horizontal screen resolution 37 | pub fn xres(&self) -> u32 { 38 | self.buffer.var_screen_info.xres 39 | } 40 | 41 | /// Vertical screen resolution 42 | pub fn yres(&self) -> u32 { 43 | self.buffer.var_screen_info.yres 44 | } 45 | 46 | /// Dimensions of the screen. 47 | pub fn shape(&self) -> (u32, u32) { 48 | (self.xres(), self.yres()) 49 | } 50 | 51 | /// Clears the screen 52 | pub fn clear(&mut self) { 53 | for (_, _, pixel) in self.image.enumerate_pixels_mut() { 54 | *pixel = Rgb([255, 255, 255]); 55 | } 56 | } 57 | 58 | fn update_1bpp(&mut self) { 59 | let mut buffer = vec![0u8; ((self.xres() * self.yres() + 7) / 8) as usize]; 60 | 61 | let mut byte: usize = 0; 62 | let mut bit: u8 = 0x80; 63 | for (_, _, pixel) in self.image.enumerate_pixels() { 64 | let sum = pixel.0[0] as u32 + pixel.0[1] as u32 + pixel.0[2] as u32; 65 | 66 | buffer[byte] |= if sum >= 0x30 { bit } else { 0x00 }; 67 | 68 | bit >>= 1; 69 | if bit == 0 { 70 | byte += 1; 71 | bit = 0x80; 72 | } 73 | } 74 | 75 | self.buffer.write_frame(&buffer); 76 | } 77 | 78 | /// Convert red, green, blue components to a 16-bit 565 RGB value. Components 79 | /// should be values 0 to 255. 80 | fn color565(r: u8, g: u8, b: u8) -> (u8, u8) { 81 | let c = (((r as u16) & 0xF8) << 8) | (((g as u16) & 0xFC) << 3) | ((b as u16) >> 3); 82 | ((c >> 8) as u8, c as u8) 83 | } 84 | 85 | fn update_16bpp(&mut self) { 86 | let mut buffer = vec![0u8; (2 * self.xres() * self.yres()) as usize]; 87 | 88 | let mut byte: usize = 0; 89 | for (_, _, pixel) in self.image.enumerate_pixels() { 90 | let (p1, p2) = Screen::color565(pixel.0[0], pixel.0[1], pixel.0[2]); 91 | buffer[byte] = p1; 92 | buffer[byte + 1] = p2; 93 | byte += 2; 94 | } 95 | 96 | self.buffer.write_frame(&buffer); 97 | } 98 | 99 | fn update_32bpp(&mut self) { 100 | let mut buffer = vec![0u8; (4 * self.xres() * self.yres()) as usize]; 101 | 102 | let mut byte: usize = 1; 103 | for (_, _, pixel) in self.image.enumerate_pixels() { 104 | buffer[byte..(byte + 2)].copy_from_slice(&pixel.0[0..2]); 105 | byte += 4; 106 | } 107 | 108 | self.buffer.write_frame(&buffer); 109 | } 110 | 111 | /// Applies pending changes to the screen. 112 | /// Nothing will be drawn on the screen until this function is called. 113 | pub fn update(&mut self) { 114 | if self.buffer.var_screen_info.bits_per_pixel == 1 { 115 | self.update_1bpp(); 116 | } else if self.buffer.var_screen_info.bits_per_pixel == 16 { 117 | self.update_16bpp(); 118 | } else if self.buffer.var_screen_info.bits_per_pixel == 32 { 119 | self.update_32bpp(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/sensors/color_sensor.rs: -------------------------------------------------------------------------------- 1 | //! LEGO EV3 color sensor. 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{sensor_mode, Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | 6 | /// LEGO EV3 color sensor. 7 | #[derive(Debug, Clone, Device, Sensor)] 8 | pub struct ColorSensor { 9 | driver: Driver, 10 | } 11 | 12 | impl ColorSensor { 13 | fn new(driver: Driver) -> Self { 14 | Self { driver } 15 | } 16 | 17 | findable!( 18 | "lego-sensor", 19 | ["lego-ev3-color"], 20 | SensorPort, 21 | "ColorSensor", 22 | "in" 23 | ); 24 | 25 | sensor_mode!( 26 | "COL-REFLECT", 27 | MODE_COL_REFLECT, 28 | "Reflected light - sets LED color to red", 29 | set_mode_col_reflect, 30 | is_mode_col_reflect 31 | ); 32 | sensor_mode!( 33 | "COL-AMBIENT", 34 | MODE_COL_AMBIENT, 35 | "Ambient light - sets LED color to blue (dimly lit)", 36 | set_mode_col_ambient, 37 | is_mode_col_ambient 38 | ); 39 | sensor_mode!( 40 | "COL-COLOR", 41 | MODE_COL_COLOR, 42 | "Color - sets LED color to white (all LEDs rapidly cycling)", 43 | set_mode_col_color, 44 | is_mode_col_color 45 | ); 46 | sensor_mode!( 47 | "REF-RAW", 48 | MODE_REF_RAW, 49 | "Raw Reflected - sets LED color to red", 50 | set_mode_ref_raw, 51 | is_mode_ref_raw 52 | ); 53 | sensor_mode!( 54 | "RGB-RAW", 55 | MODE_RGB_RAW, 56 | "Raw Color Components - sets LED color to white (all LEDs rapidly cycling)", 57 | set_mode_rgb_raw, 58 | is_mode_rgb_raw 59 | ); 60 | sensor_mode!( 61 | "COL-CAL", 62 | MODE_COL_CAL, 63 | "Calibration ??? - sets LED color to red, flashing every 4 seconds, then goes continuous", 64 | set_mode_col_cal, 65 | is_mode_col_cal 66 | ); 67 | 68 | /// Get the color value for the modes `COL-REFLECT`, `COL-AMBIENT`, `COL-COLOR` and `REF-RAW`. 69 | pub fn get_color(&self) -> Ev3Result { 70 | self.get_value0() 71 | } 72 | 73 | /// Red component of the detected color, in the range 0-1020. 74 | pub fn get_red(&self) -> Ev3Result { 75 | self.get_value0() 76 | } 77 | 78 | /// Green component of the detected color, in the range 0-1020. 79 | pub fn get_green(&self) -> Ev3Result { 80 | self.get_value1() 81 | } 82 | 83 | /// Blue component of the detected color, in the range 0-1020. 84 | pub fn get_blue(&self) -> Ev3Result { 85 | self.get_value2() 86 | } 87 | 88 | /// Red, green and blue components of the detected color, each in the range 0-1020 89 | pub fn get_rgb(&self) -> Ev3Result<(i32, i32, i32)> { 90 | let red = self.get_red()?; 91 | let green = self.get_green()?; 92 | let blue = self.get_blue()?; 93 | 94 | Ok((red, green, blue)) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/sensors/compass_sensor.rs: -------------------------------------------------------------------------------- 1 | //! HiTechnic EV3 / NXT Compass Sensor. () 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | 6 | /// HiTechnic EV3 / NXT Compass Sensor. 7 | #[derive(Debug, Clone, Device, Sensor)] 8 | pub struct CompassSensor { 9 | driver: Driver, 10 | origin: i32, // zero point 11 | } 12 | 13 | impl CompassSensor { 14 | fn new(driver: Driver) -> Self { 15 | Self { driver, origin: 0 } 16 | } 17 | 18 | findable!( 19 | "lego-sensor", 20 | ["ht-nxt-compass"], 21 | SensorPort, 22 | "Compass", 23 | "in" 24 | ); 25 | 26 | /// Command for starting the calibration 27 | pub const COMMAND_START_CALIBRATION: &'static str = "BEGIN-CAL"; 28 | 29 | /// Command for stopping the calibration 30 | pub const COMMAND_STOP_CALIBRATION: &'static str = "END-CAL"; 31 | 32 | // Sensor only have one mode (COMPASS), so setting the mode is not necessary 33 | 34 | /// gets rotation (in degree) from the compass sensor 35 | pub fn get_rotation(&self) -> Ev3Result { 36 | self.get_value0() 37 | } 38 | 39 | /// sets the origin 40 | pub fn set_zero(&mut self) -> Ev3Result<()> { 41 | self.origin = self.get_rotation()?; 42 | Ok(()) 43 | } 44 | 45 | /// calculates the rotation to the origin / the zero point 46 | pub fn get_relative_rotation(&self) -> Ev3Result { 47 | let pos = self.get_rotation()?; 48 | let mut rel_rot = pos - self.origin; 49 | if rel_rot < 0 { 50 | rel_rot += 360; 51 | } 52 | Ok(rel_rot) 53 | } 54 | 55 | /// calibration: 56 | /// start the calibration by start_calibration() 57 | /// turn the robot 360 degrees 58 | /// end the calibration by stop_calibration() 59 | /// attention: if calibration has not finished, the get_rotation method always returns -258 60 | /// 61 | /// starts the calibration 62 | pub fn start_calibration(&self) -> Ev3Result<()> { 63 | self.set_command(Self::COMMAND_START_CALIBRATION) 64 | } 65 | 66 | /// stops the calibration 67 | pub fn stop_calibration(&self) -> Ev3Result<()> { 68 | self.set_command(Self::COMMAND_STOP_CALIBRATION) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/sensors/gyro_sensor.rs: -------------------------------------------------------------------------------- 1 | //! LEGO EV3 gyro sensor. 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{sensor_mode, Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | 6 | /// LEGO EV3 gyro sensor. 7 | #[derive(Debug, Clone, Device, Sensor)] 8 | pub struct GyroSensor { 9 | driver: Driver, 10 | } 11 | 12 | impl GyroSensor { 13 | fn new(driver: Driver) -> Self { 14 | Self { driver } 15 | } 16 | 17 | findable!( 18 | "lego-sensor", 19 | ["lego-ev3-gyro"], 20 | SensorPort, 21 | "GyroSensor", 22 | "in" 23 | ); 24 | sensor_mode!( 25 | "GYRO-ANG", 26 | MODE_GYRO_ANG, 27 | "Angle", 28 | set_mode_gyro_ang, 29 | is_mode_gyro_ang 30 | ); 31 | sensor_mode!( 32 | "GYRO-RATE", 33 | MODE_GYRO_RATE, 34 | "Rotational Speed", 35 | set_mode_gyro_rate, 36 | is_mode_gyro_rate 37 | ); 38 | sensor_mode!( 39 | "GYRO-FAS", 40 | MODE_GYRO_FAS, 41 | "Raw sensor value ???", 42 | set_mode_gyro_fas, 43 | is_mode_gyro_fas 44 | ); 45 | sensor_mode!( 46 | "GYRO-G&A", 47 | MODE_GYRO_G_AND_A, 48 | "Angle and Rotational Speed", 49 | set_mode_gyro_g_and_a, 50 | is_mode_gyro_g_and_a 51 | ); 52 | sensor_mode!( 53 | "GYRO-CAL", 54 | MODE_GYRO_CAL, 55 | "Calibration ???", 56 | set_mode_gyro_cal, 57 | is_mode_gyro_cal 58 | ); 59 | sensor_mode!( 60 | "TILT-RATE", 61 | MODE_TILT_RATE, 62 | "Rotational Speed (2nd axis)", 63 | set_mode_tilt_rate, 64 | is_mode_tilt_rate 65 | ); 66 | sensor_mode!( 67 | "TILT-ANG", 68 | MODE_TILT_ANG, 69 | "Angle (2nd axis)", 70 | set_mode_tilt_ang, 71 | is_mode_tilt_ang 72 | ); 73 | 74 | /// Gets the angle, ranging from -32768 to 32767 75 | /// Fails if it has been set in the wrong mode 76 | pub fn get_angle(&self) -> Ev3Result { 77 | match self.get_mode()?.as_ref() { 78 | GyroSensor::MODE_GYRO_G_AND_A => self.get_value0(), 79 | GyroSensor::MODE_GYRO_ANG => self.get_value0(), 80 | mode => Ev3Result::Err(Ev3Error::InternalError { 81 | msg: format!("Cannot get angle while in {mode} mode"), 82 | // Returns error 83 | }), 84 | } 85 | } 86 | 87 | /// Gets the rotational speed value, ranging from -440 to 440 88 | /// Fails is it has been set in the wrong mode: 89 | /// for example, fails if we ask for rotational speed while in MODE_GYRO_ANG mode 90 | pub fn get_rotational_speed(&self) -> Ev3Result { 91 | match self.get_mode()?.as_ref() { 92 | GyroSensor::MODE_GYRO_RATE => self.get_value0(), 93 | GyroSensor::MODE_GYRO_G_AND_A => self.get_value1(), 94 | mode => Ev3Result::Err(Ev3Error::InternalError { 95 | msg: format!("Cannot get rotational speed while in {mode} mode"), 96 | // Returns error 97 | }), 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/sensors/hi_technic_color_sensor.rs: -------------------------------------------------------------------------------- 1 | //! HiTechnic NXT Color Sensor V2. 2 | 3 | use crate::{sensor_mode, Attribute, Device, Driver, Ev3Error, Ev3Result}; 4 | 5 | use super::{Sensor, SensorPort}; 6 | 7 | /// HiTechnic NXT Color Sensor V2 8 | #[derive(Debug, Clone, Device, Sensor)] 9 | pub struct HiTechnicColorSensor { 10 | driver: Driver, 11 | } 12 | 13 | impl HiTechnicColorSensor { 14 | fn new(driver: Driver) -> Self { 15 | Self { driver } 16 | } 17 | 18 | findable!( 19 | "lego-sensor", 20 | ["ht-nxt-color-v2"], 21 | SensorPort, 22 | "HiTechnicColorSensor", 23 | "in" 24 | ); 25 | 26 | sensor_mode!( 27 | "COL-REFLECT", 28 | MODE_COL_REFLECT, 29 | "Reflected light - sets LED color to red", 30 | set_mode_col_reflect, 31 | is_mode_col_reflect 32 | ); 33 | sensor_mode!( 34 | "COL-AMBIENT", 35 | MODE_COL_AMBIENT, 36 | "Ambient light - sets LED color to blue (dimly lit)", 37 | set_mode_col_ambient, 38 | is_mode_col_ambient 39 | ); 40 | sensor_mode!( 41 | "COL-COLOR", 42 | MODE_COL_COLOR, 43 | "Color - sets LED color to white (all LEDs rapidly cycling)", 44 | set_mode_col_color, 45 | is_mode_col_color 46 | ); 47 | sensor_mode!( 48 | "REF-RAW", 49 | MODE_REF_RAW, 50 | "Raw Reflected - sets LED color to red", 51 | set_mode_ref_raw, 52 | is_mode_ref_raw 53 | ); 54 | sensor_mode!( 55 | "RAW", 56 | MODE_RGB_RAW, 57 | "Raw Color Components - sets LED color to white (all LEDs rapidly cycling)", 58 | set_mode_rgb_raw, 59 | is_mode_rgb_raw 60 | ); 61 | sensor_mode!( 62 | "COL-CAL", 63 | MODE_COL_CAL, 64 | "Calibration ??? - sets LED color to red, flashing every 4 seconds, then goes continuous", 65 | set_mode_col_cal, 66 | is_mode_col_cal 67 | ); 68 | 69 | /// Get the color value for the modes `COL-REFLECT`, `COL-AMBIENT`, `COL-COLOR` and `REF-RAW`. 70 | pub fn get_color(&self) -> Ev3Result { 71 | self.get_value0() 72 | } 73 | 74 | /// Red component of the detected color, in the range 0-1020. 75 | pub fn get_red(&self) -> Ev3Result { 76 | self.get_value0() 77 | } 78 | 79 | /// Green component of the detected color, in the range 0-1020. 80 | pub fn get_green(&self) -> Ev3Result { 81 | self.get_value1() 82 | } 83 | 84 | /// Blue component of the detected color, in the range 0-1020. 85 | pub fn get_blue(&self) -> Ev3Result { 86 | self.get_value2() 87 | } 88 | 89 | /// Red, green, blue and intensity components of the detected color, each in the range 0-1020 90 | pub fn get_rgb_i(&self) -> Ev3Result<(i32, i32, i32, i32)> { 91 | let red = self.value(0)?; 92 | let green = self.value(1)?; 93 | let blue = self.value(2)?; 94 | let i = self.value(3)?; 95 | 96 | Ok((red, green, blue, i)) 97 | } 98 | 99 | /// Utility function that returns the value with a specified index 100 | pub fn value(&self, v: u8) -> Result { 101 | self.get_value(v) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/sensors/infrared_sensor.rs: -------------------------------------------------------------------------------- 1 | //! LEGO EV3 infrared sensor. 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{sensor_mode, Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | use std::cell::RefCell; 6 | use std::collections::HashSet; 7 | use std::fmt; 8 | use std::rc::Rc; 9 | 10 | /// LEGO EV3 infrared sensor. 11 | #[derive(Debug, Clone, Device, Sensor)] 12 | pub struct InfraredSensor { 13 | driver: Driver, 14 | } 15 | 16 | impl InfraredSensor { 17 | fn new(driver: Driver) -> Self { 18 | Self { driver } 19 | } 20 | 21 | findable!( 22 | "lego-sensor", 23 | ["lego-ev3-ir"], 24 | SensorPort, 25 | "InfraredSensor", 26 | "in" 27 | ); 28 | 29 | sensor_mode!( 30 | "IR-PROX", 31 | MODE_IR_PROX, 32 | "Proximity", 33 | set_mode_ir_prox, 34 | is_mode_ir_prox 35 | ); 36 | sensor_mode!( 37 | "IR-SEEK", 38 | MODE_IR_SEEK, 39 | "IR Seeker", 40 | set_mode_ir_seek, 41 | is_mode_ir_seek 42 | ); 43 | sensor_mode!( 44 | "IR-REMOTE", 45 | MODE_IR_REMOTE, 46 | "IR Remote Control", 47 | set_mode_ir_remote, 48 | is_mode_ir_remote 49 | ); 50 | sensor_mode!( 51 | "IR-REM-A", 52 | MODE_IR_REM_A, 53 | "IR Remote Control", 54 | set_mode_ir_rem_a, 55 | is_mode_ir_rem_a 56 | ); 57 | sensor_mode!( 58 | "IR-S-ALT", 59 | MODE_IR_S_ALT, 60 | "Alternate IR Seeker ???", 61 | set_mode_ir_s_alt, 62 | is_mode_ir_s_alt 63 | ); 64 | sensor_mode!( 65 | "IR-CAL", 66 | MODE_IR_CAL, 67 | "Calibration ???", 68 | set_mode_ir_cal, 69 | is_mode_ir_cal 70 | ); 71 | 72 | /// Get the proximity distance, in the range 0-100 (pct). 73 | pub fn get_distance(&self) -> Ev3Result { 74 | self.get_value0() 75 | } 76 | } 77 | 78 | struct RemoteControlHelper { 79 | last_buttons: i32, 80 | pressed_buttons: HashSet, 81 | } 82 | 83 | impl RemoteControlHelper { 84 | fn new() -> RemoteControlHelper { 85 | RemoteControlHelper { 86 | last_buttons: 0, 87 | pressed_buttons: HashSet::new(), 88 | } 89 | } 90 | 91 | fn contains(&self, button: &str) -> bool { 92 | self.pressed_buttons.contains(button) 93 | } 94 | } 95 | 96 | /// Seeks EV3 Remote Controller in beacon mode. 97 | #[derive(Clone)] 98 | pub struct RemoteControl { 99 | sensor: InfraredSensor, 100 | channel: u8, 101 | helper: Rc>, 102 | } 103 | 104 | // Manually implement Debug cause `buffer_cache` does not implement Debug. 105 | impl fmt::Debug for RemoteControl { 106 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 107 | f.debug_struct("RemoteControl") 108 | .field("sensor", &self.sensor) 109 | .field("channel", &self.channel) 110 | .finish() 111 | } 112 | } 113 | 114 | impl RemoteControl { 115 | /// Wrap a InfraredSensor into a BeaconSeeker 116 | pub fn new(sensor: InfraredSensor, channel: u8) -> Ev3Result { 117 | sensor.set_mode_ir_remote()?; 118 | 119 | Ok(RemoteControl { 120 | sensor, 121 | channel: u8::max(1, u8::min(4, channel)) - 1, 122 | helper: Rc::new(RefCell::new(RemoteControlHelper::new())), 123 | }) 124 | } 125 | 126 | /// Checks if `red_up` button is pressed. 127 | pub fn is_red_up(&self) -> bool { 128 | self.helper.borrow().contains("red_up") 129 | } 130 | 131 | /// Checks if `red_down` button is pressed. 132 | pub fn is_red_down(&self) -> bool { 133 | self.helper.borrow().contains("red_down") 134 | } 135 | 136 | /// Checks if `blue_up` button is pressed. 137 | pub fn is_blue_up(&self) -> bool { 138 | self.helper.borrow().contains("blue_up") 139 | } 140 | 141 | /// Checks if `blue_down` button is pressed. 142 | pub fn is_blue_down(&self) -> bool { 143 | self.helper.borrow().contains("blue_down") 144 | } 145 | 146 | /// Checks if `beacon` button is pressed. 147 | pub fn is_beacon(&self) -> bool { 148 | self.helper.borrow().contains("beacon") 149 | } 150 | 151 | /// Check for currently pressed buttons. If the new state differs from the 152 | /// old state, call the appropriate button event handlers. 153 | pub fn process(&self) -> Ev3Result<()> { 154 | let buttons = self.sensor.get_value(self.channel)?; 155 | 156 | let mut helper = self.helper.borrow_mut(); 157 | 158 | if helper.last_buttons != buttons { 159 | helper.last_buttons = buttons; 160 | 161 | helper.pressed_buttons.clear(); 162 | 163 | match buttons { 164 | 1 => { 165 | helper.pressed_buttons.insert("red_up".to_owned()); 166 | } 167 | 2 => { 168 | helper.pressed_buttons.insert("red_down".to_owned()); 169 | } 170 | 3 => { 171 | helper.pressed_buttons.insert("blue_up".to_owned()); 172 | } 173 | 4 => { 174 | helper.pressed_buttons.insert("blue_down".to_owned()); 175 | } 176 | 5 => { 177 | helper.pressed_buttons.insert("red_up".to_owned()); 178 | helper.pressed_buttons.insert("blue_up".to_owned()); 179 | } 180 | 6 => { 181 | helper.pressed_buttons.insert("red_up".to_owned()); 182 | helper.pressed_buttons.insert("blue_down".to_owned()); 183 | } 184 | 7 => { 185 | helper.pressed_buttons.insert("red_down".to_owned()); 186 | helper.pressed_buttons.insert("blue_up".to_owned()); 187 | } 188 | 8 => { 189 | helper.pressed_buttons.insert("red_down".to_owned()); 190 | helper.pressed_buttons.insert("blue_down".to_owned()); 191 | } 192 | 9 => { 193 | helper.pressed_buttons.insert("beacon".to_owned()); 194 | } 195 | 10 => { 196 | helper.pressed_buttons.insert("red_up".to_owned()); 197 | helper.pressed_buttons.insert("red_down".to_owned()); 198 | } 199 | 11 => { 200 | helper.pressed_buttons.insert("blue_up".to_owned()); 201 | helper.pressed_buttons.insert("blue_down".to_owned()); 202 | } 203 | _ => {} 204 | } 205 | } 206 | Ok(()) 207 | } 208 | } 209 | 210 | /// Seeks EV3 Remote Controller in beacon mode. 211 | #[derive(Debug, Clone)] 212 | pub struct BeaconSeeker { 213 | sensor: InfraredSensor, 214 | channel: u8, 215 | } 216 | 217 | impl BeaconSeeker { 218 | /// Wrap a InfraredSensor into a BeaconSeeker 219 | pub fn new(sensor: InfraredSensor, channel: u8) -> Ev3Result { 220 | sensor.set_mode_ir_seek()?; 221 | 222 | Ok(BeaconSeeker { 223 | sensor, 224 | channel: u8::max(1, u8::min(4, channel)) - 1, 225 | }) 226 | } 227 | 228 | /// Returns heading (-25, 25) to the beacon on the given channel. 229 | pub fn get_heading(&self) -> Ev3Result { 230 | self.sensor.get_value(self.channel * 2) 231 | } 232 | 233 | /// Returns distance (0, 100) to the beacon on the given channel. 234 | /// Returns -128 when beacon is not found. 235 | pub fn get_distance(&self) -> Ev3Result { 236 | self.sensor.get_value(self.channel * 2 + 1) 237 | } 238 | 239 | /// Returns heading and distance to the beacon on the given channel as a 240 | /// tuple. 241 | pub fn get_heading_and_distance(&self) -> Ev3Result<(i32, i32)> { 242 | Ok(( 243 | self.sensor.get_value(self.channel * 2)?, 244 | self.sensor.get_value(self.channel * 2 + 1)?, 245 | )) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/sensors/ir_seeker_sensor.rs: -------------------------------------------------------------------------------- 1 | //! HiTechnic EV3 / NXT Infrared Sensor. () 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{sensor_mode, Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | 6 | /// HiTechnic EV3 / NXT Infrared Sensor. 7 | #[derive(Debug, Clone, Device, Sensor)] 8 | pub struct IrSeekerSensor { 9 | driver: Driver, 10 | } 11 | 12 | impl IrSeekerSensor { 13 | fn new(driver: Driver) -> Self { 14 | Self { driver } 15 | } 16 | 17 | findable!( 18 | "lego-sensor", 19 | ["ht-nxt-ir-seek-v2"], 20 | SensorPort, 21 | "IrSeeker", 22 | "in" 23 | ); 24 | 25 | sensor_mode!( 26 | "AC", 27 | MODE_AC, 28 | "Sensor mode alternating current -> filters the infrared signal of the hitechnic ball -> only shows direction", 29 | set_mode_ac, 30 | is_mode_ac 31 | ); 32 | sensor_mode!( 33 | "DC", 34 | MODE_DC, 35 | "Sensor mode direct current -> reacts on all infrared signals, sun infrared signal included -> only shows direction", 36 | set_mode_dc, 37 | is_mode_dc 38 | ); 39 | sensor_mode!( 40 | "AC-ALL", 41 | MODE_AC_ALL, 42 | "Sensor mode alternating current -> shows direction (value0) and values of each of the five sensors", 43 | set_mode_ac_all, 44 | is_mode_ac_all 45 | ); 46 | sensor_mode!( 47 | "DC-ALL", 48 | MODE_DC_ALL, 49 | "Sensor mode direct current -> shows direction (value0) and values of each of the five sensors", 50 | set_mode_dc_all, 51 | is_mode_dc_all 52 | ); 53 | 54 | /// gets direction of incoming ir light (calculated by the sensor) 55 | pub fn get_ir_direction(&self) -> Ev3Result { 56 | self.get_value0() 57 | } 58 | 59 | /// gets the values of the five sensors of the HiTechnic IR Seeker (only works if dc_all or ac_all mode is activated) 60 | pub fn get_raw_values(&self) -> Ev3Result<[i32; 5]> { 61 | let val1 = self.get_value1()?; 62 | let val2 = self.get_value2()?; 63 | let val3 = self.get_value3()?; 64 | let val4 = self.get_value4()?; 65 | let val5 = self.get_value5()?; 66 | Ok([val1, val2, val3, val4, val5]) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/sensors/light_sensor.rs: -------------------------------------------------------------------------------- 1 | //! LEGO EV3 light sensor. 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{sensor_mode, Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | use std::cell::Cell; 6 | 7 | /// LEGO EV3 light sensor. 8 | #[derive(Debug, Clone, Device, Sensor)] 9 | pub struct LightSensor { 10 | driver: Driver, 11 | reflect_scale: Cell>, 12 | ambient_scale: Cell>, 13 | } 14 | 15 | impl LightSensor { 16 | fn new(driver: Driver) -> Self { 17 | Self { 18 | driver, 19 | reflect_scale: Cell::new(None), 20 | ambient_scale: Cell::new(None), 21 | } 22 | } 23 | 24 | findable!( 25 | "lego-sensor", 26 | ["lego-nxt-light"], 27 | SensorPort, 28 | "LightSensor", 29 | "in" 30 | ); 31 | 32 | sensor_mode!( 33 | "REFLECT", 34 | MODE_REFLECT, 35 | "Reflected light. LED on", 36 | set_mode_reflect, 37 | is_mode_reflect 38 | ); 39 | sensor_mode!( 40 | "AMBIENT", 41 | MODE_AMBIENT, 42 | "Ambient light. LED off", 43 | set_mode_ambient, 44 | is_mode_ambient 45 | ); 46 | 47 | /// A measurement of the light intensity, unscaled. 48 | pub fn get_light_intensity(&self) -> Ev3Result { 49 | self.get_value0() 50 | } 51 | 52 | /// A measurement of the reflected light intensity, as a percentage. 53 | pub fn get_reflected_light_intensity(&self) -> Ev3Result { 54 | let scale_field = self.reflect_scale.get(); 55 | let scale = match scale_field { 56 | Some(s) => s, 57 | None => { 58 | let decimals = self.get_decimals()?; 59 | let s = 10f32.powi(-decimals); 60 | self.reflect_scale.set(Some(s)); 61 | s 62 | } 63 | }; 64 | 65 | Ok((self.get_value0()? as f32) * scale) 66 | } 67 | 68 | /// A measurement of the ambient light intensity, as a percentage. 69 | pub fn get_ambient_light_intensity(&self) -> Ev3Result { 70 | let scale_field = self.ambient_scale.get(); 71 | let scale = match scale_field { 72 | Some(s) => s, 73 | None => { 74 | let decimals = self.get_decimals()?; 75 | let s = 10f32.powi(-decimals); 76 | self.ambient_scale.set(Some(s)); 77 | s 78 | } 79 | }; 80 | 81 | Ok((self.get_value0()? as f32) * scale) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/sensors/mod.rs: -------------------------------------------------------------------------------- 1 | //! # Container module for sensor types 2 | 3 | mod sensor; 4 | pub use self::sensor::Sensor; 5 | 6 | mod color_sensor; 7 | pub use self::color_sensor::ColorSensor; 8 | 9 | mod hi_technic_color_sensor; 10 | pub use self::hi_technic_color_sensor::HiTechnicColorSensor; 11 | 12 | mod ir_seeker_sensor; 13 | pub use self::ir_seeker_sensor::IrSeekerSensor; 14 | 15 | mod compass_sensor; 16 | pub use self::compass_sensor::CompassSensor; 17 | 18 | mod light_sensor; 19 | pub use self::light_sensor::LightSensor; 20 | 21 | mod gyro_sensor; 22 | pub use self::gyro_sensor::GyroSensor; 23 | 24 | mod infrared_sensor; 25 | pub use self::infrared_sensor::BeaconSeeker; 26 | pub use self::infrared_sensor::InfraredSensor; 27 | pub use self::infrared_sensor::RemoteControl; 28 | 29 | mod touch_sensor; 30 | pub use self::touch_sensor::TouchSensor; 31 | 32 | mod ultrasonic_sensor; 33 | pub use self::ultrasonic_sensor::UltrasonicSensor; 34 | 35 | use crate::{port_constants, Port}; 36 | 37 | /// EV3 ports `in1` to `in4` 38 | #[derive(Debug, Copy, Clone)] 39 | pub enum SensorPort { 40 | /// EV3 `in1` port 41 | In1, 42 | /// EV3 `in2` port 43 | In2, 44 | /// EV3 `in3` port 45 | In3, 46 | /// EV3 `in4` port 47 | In4, 48 | } 49 | 50 | impl SensorPort { 51 | /// Try to format a device name path to a port name. 52 | pub fn format_name(name: &str) -> String { 53 | match name { 54 | "sensor0" => SensorPort::In1.address(), 55 | "sensor1" => SensorPort::In2.address(), 56 | "sensor2" => SensorPort::In3.address(), 57 | "sensor3" => SensorPort::In4.address(), 58 | _ => name.to_owned(), 59 | } 60 | } 61 | } 62 | 63 | impl Port for SensorPort { 64 | fn address(&self) -> String { 65 | match self { 66 | SensorPort::In1 => port_constants::INPUT_1.to_owned(), 67 | SensorPort::In2 => port_constants::INPUT_2.to_owned(), 68 | SensorPort::In3 => port_constants::INPUT_3.to_owned(), 69 | SensorPort::In4 => port_constants::INPUT_4.to_owned(), 70 | } 71 | } 72 | } 73 | 74 | #[macro_export] 75 | /// Add a sensor mode constant with getter and setter 76 | macro_rules! sensor_mode { 77 | ($value:expr, $const_name:ident, $docstring:expr, $setter:ident, $getter:ident) => { 78 | #[doc = $docstring] 79 | pub const $const_name: &'static str = $value; 80 | 81 | #[doc = $docstring] 82 | pub fn $setter(&self) -> Ev3Result<()> { 83 | self.set_mode(Self::$const_name) 84 | } 85 | 86 | #[doc = $docstring] 87 | pub fn $getter(&self) -> Ev3Result { 88 | Ok(self.get_mode()? == Self::$const_name) 89 | } 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /src/sensors/sensor.rs: -------------------------------------------------------------------------------- 1 | //! Common utility functions for sensors. 2 | 3 | use crate::{Device, Ev3Result}; 4 | 5 | /// Common utility functions for sensors. 6 | pub trait Sensor: Device { 7 | /// Reading the file will give the unscaled raw values in the `value` attributes. 8 | /// Use `bin_data_format`, `num_values` and the individual sensor documentation to determine how to interpret the data. 9 | fn get_bin_data(&self) -> Ev3Result { 10 | self.get_attribute("bin_data").get() 11 | } 12 | 13 | /// Returns the format of the values in `bin_data` for the current mode. Possible values are: 14 | // * u8: Unsigned 8-bit integer (byte) 15 | // * s8: Signed 8-bit integer (sbyte) 16 | // * u16: Unsigned 16-bit integer (ushort) 17 | // * s16: Signed 16-bit integer (short) 18 | // * s16_be: Signed 16-bit integer, big endian 19 | // * s32: Signed 32-bit integer (int) 20 | // * s32_be: Signed 32-bit integer, big endian 21 | // * float: IEEE 754 32-bit floating point (float) 22 | fn get_bin_data_format(&self) -> Ev3Result { 23 | self.get_attribute("bin_data_format").get() 24 | } 25 | 26 | /// Returns the number of decimal places for the values in the `value` attributes of the current mode. 27 | fn get_decimals(&self) -> Ev3Result { 28 | self.get_attribute("decimals").get() 29 | } 30 | 31 | /// Returns the firmware version of the sensor if available. 32 | /// Currently only NXT/I2C sensors support this. 33 | fn get_fw_version(&self) -> Ev3Result { 34 | self.get_attribute("fw_version").get() 35 | } 36 | 37 | /// Returns the current mode. 38 | /// See the individual sensor documentation for a description of the modes available for each type of sensor. 39 | fn get_mode(&self) -> Ev3Result { 40 | self.get_attribute("mode").get() 41 | } 42 | 43 | /// Sets the sensor to that mode. 44 | /// See the individual sensor documentation for a description of the modes available for each type of sensor. 45 | fn set_mode(&self, mode: &str) -> Ev3Result<()> { 46 | self.get_attribute("mode").set_str_slice(mode) 47 | } 48 | 49 | /// Returns a list of the valid modes for the sensor. 50 | fn get_modes(&self) -> Ev3Result> { 51 | self.get_attribute("modes").get_vec() 52 | } 53 | 54 | /// Returns the number of `value` attributes that will return a valid value for the current mode. 55 | fn get_num_values(&self) -> Ev3Result { 56 | self.get_attribute("num_values").get() 57 | } 58 | 59 | /// Returns the polling period of the sensor in milliseconds. 60 | /// Returns `-EOPNOTSUPP` if changing polling is not supported. 61 | /// Note: Setting poll_ms too high can cause the input port auto detection to fail. 62 | /// If this happens, use the mode attribute of the port to force the port to `nxt-i2c mode`. Values must not be negative. 63 | fn get_poll_ms(&self) -> Ev3Result { 64 | self.get_attribute("poll_ms").get() 65 | } 66 | 67 | /// Sets the polling period of the sensor in milliseconds. 68 | /// Setting to 0 disables polling. 69 | /// Note: Setting poll_ms too high can cause the input port auto detection to fail. 70 | /// If this happens, use the mode attribute of the port to force the port to `nxt-i2c mode`. Values must not be negative. 71 | fn set_poll_ms(&self, poll_ms: i32) -> Ev3Result<()> { 72 | self.get_attribute("poll_ms").set(poll_ms) 73 | } 74 | 75 | /// Returns the units of the measured value for the current mode. May return empty string if units are unknown. 76 | fn get_units(&self) -> Ev3Result { 77 | self.get_attribute("units").get() 78 | } 79 | 80 | /// Returns the current `value{index}` value if available. 81 | fn get_value(&self, index: u8) -> Ev3Result { 82 | use crate::Ev3Error; 83 | match index { 84 | 0 => self.get_value0(), 85 | 1 => self.get_value1(), 86 | 2 => self.get_value2(), 87 | 3 => self.get_value3(), 88 | 4 => self.get_value4(), 89 | 5 => self.get_value5(), 90 | 6 => self.get_value6(), 91 | 7 => self.get_value7(), 92 | _ => Ev3Result::Err(Ev3Error::InternalError { 93 | msg: format!("Sensor value index {index} is out of bounds [0, 7]"), 94 | }), 95 | } 96 | } 97 | 98 | /// Returns the current `value0` value if available. 99 | fn get_value0(&self) -> Ev3Result { 100 | self.get_attribute("value0").get() 101 | } 102 | 103 | /// Returns the current `value1` value if available. 104 | fn get_value1(&self) -> Ev3Result { 105 | self.get_attribute("value1").get() 106 | } 107 | 108 | /// Returns the current `value2` value if available. 109 | fn get_value2(&self) -> Ev3Result { 110 | self.get_attribute("value2").get() 111 | } 112 | 113 | /// Returns the current `value3` value if available. 114 | fn get_value3(&self) -> Ev3Result { 115 | self.get_attribute("value3").get() 116 | } 117 | 118 | /// Returns the current `value4` value if available. 119 | fn get_value4(&self) -> Ev3Result { 120 | self.get_attribute("value4").get() 121 | } 122 | 123 | /// Returns the current `value5` value if available. 124 | fn get_value5(&self) -> Ev3Result { 125 | self.get_attribute("value5").get() 126 | } 127 | 128 | /// Returns the current `value6` value if available. 129 | fn get_value6(&self) -> Ev3Result { 130 | self.get_attribute("value6").get() 131 | } 132 | 133 | /// Returns the current `value7` value if available. 134 | fn get_value7(&self) -> Ev3Result { 135 | self.get_attribute("value7").get() 136 | } 137 | 138 | /// Returns a space delimited string representing sensor-specific text values. Returns `-EOPNOTSUPP` if a sensor does not support text values. 139 | fn get_text_value(&self) -> Ev3Result { 140 | self.get_attribute("text_value").get() 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/sensors/touch_sensor.rs: -------------------------------------------------------------------------------- 1 | //! Touch Sensor 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | 6 | /// Touch Sensor 7 | #[derive(Debug, Clone, Device, Sensor)] 8 | pub struct TouchSensor { 9 | driver: Driver, 10 | } 11 | 12 | impl TouchSensor { 13 | fn new(driver: Driver) -> Self { 14 | Self { driver } 15 | } 16 | 17 | findable!( 18 | "lego-sensor", 19 | ["lego-ev3-touch", "lego-nxt-touch"], 20 | SensorPort, 21 | "TouchSensor", 22 | "in" 23 | ); 24 | 25 | /// Button state 26 | pub const MODE_TOUCH: &'static str = "TOUCH"; 27 | 28 | /// A boolean indicating whether the current touch sensor is being pressed. 29 | pub fn get_pressed_state(&self) -> Ev3Result { 30 | Ok(self.get_value0()? != 0) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/sensors/ultrasonic_sensor.rs: -------------------------------------------------------------------------------- 1 | //! LEGO EV3 ultrasonic sensor 2 | 3 | use super::{Sensor, SensorPort}; 4 | use crate::{sensor_mode, Attribute, Device, Driver, Ev3Error, Ev3Result}; 5 | use std::cell::Cell; 6 | 7 | /// LEGO EV3 ultrasonic sensor. 8 | #[derive(Debug, Clone, Device, Sensor)] 9 | pub struct UltrasonicSensor { 10 | driver: Driver, 11 | cm_scale: Cell>, 12 | in_scale: Cell>, 13 | } 14 | 15 | impl UltrasonicSensor { 16 | fn new(driver: Driver) -> Self { 17 | Self { 18 | driver, 19 | cm_scale: Cell::new(None), 20 | in_scale: Cell::new(None), 21 | } 22 | } 23 | 24 | findable!( 25 | "lego-sensor", 26 | ["lego-ev3-us", "lego-nxt-us"], 27 | SensorPort, 28 | "UltrasonicSensor", 29 | "in" 30 | ); 31 | 32 | sensor_mode!( 33 | "US-DIST-CM", 34 | MODE_US_DIST_CM, 35 | "Continuous measurement - sets LEDs on, steady. Units in centimeters. Distance (0-2550)", 36 | set_mode_us_dist_cm, 37 | is_mode_us_dist_cm 38 | ); 39 | sensor_mode!( 40 | "US-DIST-IN", 41 | MODE_US_DIST_IN, 42 | "Continuous measurement - sets LEDs on, steady. Units in inches. Distance (0-1003)", 43 | set_mode_us_dist_in, 44 | is_mode_us_dist_in 45 | ); 46 | sensor_mode!( 47 | "US-LISTEN", 48 | MODE_US_LISTEN, 49 | "Listen - sets LEDs on, blinking. Presence (0-1) #", 50 | set_mode_us_listen, 51 | is_mode_us_listen 52 | ); 53 | sensor_mode!( 54 | "US-SI-CM", 55 | MODE_US_SI_CM, 56 | "Single measurement - LEDs on momentarily when mode is set, then off. Units in centimeters. Distance (0-2550)", 57 | set_mode_us_si_cm, 58 | is_mode_us_si_cm 59 | ); 60 | sensor_mode!( 61 | "US-SI-IN", 62 | MODE_US_SI_IN, 63 | "Single measurement - LEDs on momentarily when mode is set, then off. Units in inches. Distance (0-1003)", 64 | set_mode_us_si_in, 65 | is_mode_us_si_in 66 | ); 67 | sensor_mode!( 68 | "US-DC-CM", 69 | MODE_US_DC_CM, 70 | "??? - sets LEDs on, steady. Units in centimeters. Distance (0-2550)", 71 | set_mode_us_dc_cm, 72 | is_mode_us_dc_cm 73 | ); 74 | sensor_mode!( 75 | "US-DC-IN", 76 | MODE_US_DC_IN, 77 | "??? - sets LEDs on, steady. Units in inches. Distance (0-1003)", 78 | set_mode_us_dc_in, 79 | is_mode_us_dc_in 80 | ); 81 | 82 | /// Measurement of the distance detected by the sensor, unscaled. 83 | pub fn get_distance(&self) -> Ev3Result { 84 | self.get_value0() 85 | } 86 | 87 | /// Measurement of the distance detected by the sensor, in centimeters. 88 | pub fn get_distance_centimeters(&self) -> Ev3Result { 89 | let scale_field = self.cm_scale.get(); 90 | let scale = match scale_field { 91 | Some(s) => s, 92 | None => { 93 | let decimals = self.get_decimals()?; 94 | let s = 10f32.powi(-decimals); 95 | self.cm_scale.set(Some(s)); 96 | s 97 | } 98 | }; 99 | 100 | Ok((self.get_value0()? as f32) * scale) 101 | } 102 | 103 | /// Measurement of the distance detected by the sensor, in inches. 104 | pub fn get_distance_inches(&self) -> Ev3Result { 105 | let scale_field = self.in_scale.get(); 106 | let scale = match scale_field { 107 | Some(s) => s, 108 | None => { 109 | let decimals = self.get_decimals()?; 110 | let s = 10f32.powi(-decimals); 111 | self.in_scale.set(Some(s)); 112 | s 113 | } 114 | }; 115 | 116 | Ok((self.get_value0()? as f32) * scale) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/sound.rs: -------------------------------------------------------------------------------- 1 | //! Sound-related functions. It can beep, play wav files, or convert text to 2 | //! speech. 3 | //! 4 | //! Note that all methods of the module spawn system processes and return 5 | //! `std::process::Child` objects. The methods are asynchronous (they return 6 | //! immediately after child process was spawned, without waiting for its 7 | //! completion), but you can call wait() on the returned result. 8 | //! 9 | //! # Examples 10 | //! ```no_run 11 | //! # use ev3dev_lang_rust::Ev3Result; 12 | //! use ev3dev_lang_rust::sound; 13 | //! 14 | //! # fn main() -> Ev3Result<()> { 15 | //! // Play "bark.wav", return immediately: 16 | //! sound::play("bark.wav")?; 17 | //! 18 | //! // Introduce yourself, wait for completion: 19 | //! sound::speak("Hello, I am Robot")?.wait()?; 20 | //! # Ok(()) 21 | //! # } 22 | //! ``` 23 | 24 | use crate::{Ev3Error, Ev3Result}; 25 | use std::ffi::OsStr; 26 | use std::process::{Child, Command, Stdio}; 27 | 28 | /// Call beep command. 29 | /// 30 | /// # Example 31 | /// ```no_run 32 | /// # use ev3dev_lang_rust::Ev3Result; 33 | /// use ev3dev_lang_rust::sound; 34 | /// 35 | /// # fn main() -> Ev3Result<()> { 36 | /// sound::beep()?.wait()?; 37 | /// # Ok(()) 38 | /// # } 39 | /// ``` 40 | pub fn beep() -> Ev3Result { 41 | Ok(Command::new("/usr/bin/beep") 42 | .stdout(Stdio::null()) 43 | .spawn()?) 44 | } 45 | 46 | /// Call beep command with the provided arguments. 47 | /// 48 | /// See `beep man page`_ and google `linux beep music`_ for inspiration. 49 | /// * `beep man page`: 50 | /// * `linux beep music`: 51 | /// 52 | /// # Example 53 | /// ```no_run 54 | /// # use ev3dev_lang_rust::Ev3Result; 55 | /// use ev3dev_lang_rust::sound; 56 | /// 57 | /// # fn main() -> Ev3Result<()> { 58 | /// sound::beep_args(&[""])?.wait()?; 59 | /// # Ok(()) 60 | /// # } 61 | /// ``` 62 | pub fn beep_args(args: I) -> Ev3Result 63 | where 64 | I: IntoIterator, 65 | S: AsRef, 66 | { 67 | Ok(Command::new("/usr/bin/beep") 68 | .args(args) 69 | .stdout(Stdio::null()) 70 | .spawn()?) 71 | } 72 | 73 | /// Play tone sequence. The tone_sequence parameter is a list of tuples, 74 | /// where each tuple contains up to three numbers. The first number is 75 | /// frequency in Hz, the second is duration in milliseconds, and the third 76 | /// is delay in milliseconds between this and the next tone in the 77 | /// sequence. 78 | /// 79 | /// # Example 80 | /// ```no_run 81 | /// # use ev3dev_lang_rust::Ev3Result; 82 | /// use ev3dev_lang_rust::sound; 83 | /// 84 | /// # fn main() -> Ev3Result<()> { 85 | /// sound::tone(466.0, 500)?.wait()?; 86 | /// # Ok(()) 87 | /// # } 88 | pub fn tone(frequency: f32, duration: i32) -> Ev3Result { 89 | beep_args(vec![format!("-f {frequency}"), format!("-l {duration}")]) 90 | } 91 | 92 | /// Play tone sequence. The tone_sequence parameter is a list of tuples, 93 | /// where each tuple contains up to three numbers. The first number is 94 | /// frequency in Hz, the second is duration in milliseconds, and the third 95 | /// is delay in milliseconds between this and the next tone in the 96 | /// sequence. 97 | /// 98 | /// # Example 99 | /// ```no_run 100 | /// # use ev3dev_lang_rust::Ev3Result; 101 | /// use ev3dev_lang_rust::sound; 102 | /// 103 | /// # fn main() -> Ev3Result<()> { 104 | /// sound::tone_sequence( 105 | /// &[ 106 | /// (392.00, 350, 100), (392.00, 350, 100), (392.00, 350, 100), (311.1, 250, 100), 107 | /// (466.20, 025, 100), (392.00, 350, 100), (311.10, 250, 100), (466.2, 025, 100), 108 | /// (392.00, 700, 100), (587.32, 350, 100), (587.32, 350, 100), 109 | /// (587.32, 350, 100), (622.26, 250, 100), (466.20, 025, 100), 110 | /// (369.99, 350, 100), (311.10, 250, 100), (466.20, 025, 100), (392.00, 700, 100), 111 | /// (784.00, 350, 100), (392.00, 250, 100), (392.00, 025, 100), (784.00, 350, 100), 112 | /// (739.98, 250, 100), (698.46, 025, 100), (659.26, 025, 100), 113 | /// (622.26, 025, 100), (659.26, 050, 400), (415.30, 025, 200), (554.36, 350, 100), 114 | /// (523.25, 250, 100), (493.88, 025, 100), (466.16, 025, 100), (440.00, 025, 100), 115 | /// (466.16, 050, 400), (311.13, 025, 200), (369.99, 350, 100), 116 | /// (311.13, 250, 100), (392.00, 025, 100), (466.16, 350, 100), (392.00, 250, 100), 117 | /// (466.16, 025, 100), (587.32, 700, 100), (784.00, 350, 100), (392.00, 250, 100), 118 | /// (392.00, 025, 100), (784.00, 350, 100), (739.98, 250, 100), (698.46, 025, 100), 119 | /// (659.26, 025, 100), (622.26, 025, 100), (659.26, 050, 400), (415.30, 025, 200), 120 | /// (554.36, 350, 100), (523.25, 250, 100), (493.88, 025, 100), 121 | /// (466.16, 025, 100), (440.00, 025, 100), (466.16, 050, 400), (311.13, 025, 200), 122 | /// (392.00, 350, 100), (311.13, 250, 100), (466.16, 025, 100), 123 | /// (392.00, 300, 150), (311.13, 250, 100), (466.16, 025, 100), (392.00, 700, 0) 124 | /// ] 125 | /// )?.wait()?; 126 | /// # Ok(()) 127 | /// # } 128 | pub fn tone_sequence(sequence: &[(f32, i32, i32)]) -> Ev3Result { 129 | let tones: Vec = sequence 130 | .iter() 131 | .map(|(frequency, duration, delay)| { 132 | vec![ 133 | format!("-f {frequency}"), 134 | format!("-l {duration}"), 135 | format!("-D {delay}"), 136 | ] 137 | }) 138 | .collect::>>() 139 | .join(&["-n".to_owned()][..]); 140 | 141 | beep_args(tones) 142 | } 143 | 144 | /// Play wav file 145 | pub fn play(wav_file: &str) -> Ev3Result { 146 | Ok(Command::new("/usr/bin/aplay") 147 | .arg("-q") 148 | .arg("-Dplug:dmix") 149 | .arg(wav_file) 150 | .stdout(Stdio::null()) 151 | .spawn()?) 152 | } 153 | 154 | /// Speak the given text aloud. 155 | pub fn speak(text: &str) -> Ev3Result { 156 | let espeak = Command::new("/usr/bin/espeak") 157 | .args(["--stdout", "-a", "200", "-s", "130", text]) 158 | .stdout(Stdio::piped()) 159 | .spawn()?; 160 | 161 | Ok(Command::new("/usr/bin/aplay") 162 | .arg("-q") 163 | .arg("-Dplug:dmix") 164 | .stdin(espeak.stdout.ok_or(Ev3Error::InternalError { 165 | msg: "`espeak` pipe to `aplay` could not be created!".to_owned(), 166 | })?) 167 | .stdout(Stdio::null()) 168 | .spawn()?) 169 | } 170 | 171 | /// Get the main channel name or 'Playback' if not available. 172 | fn get_channels() -> Ev3Result> { 173 | let out = String::from_utf8( 174 | Command::new("/usr/bin/amixer") 175 | .arg("scontrols") 176 | .output()? 177 | .stdout, 178 | )?; 179 | 180 | let mut channels: Vec = out 181 | .split('\n') 182 | .filter_map(|line| { 183 | let vol_start = line.find('\'').unwrap_or(0) + 1; 184 | let vol_end = line.rfind('\'').unwrap_or(1); 185 | 186 | if vol_start >= vol_end { 187 | None 188 | } else { 189 | Some(line[vol_start..vol_end].to_owned()) 190 | } 191 | }) 192 | .collect(); 193 | 194 | if channels.is_empty() { 195 | channels.push("Playback".to_owned()); 196 | } 197 | 198 | Ok(channels) 199 | } 200 | 201 | /// Sets the sound volume to the given percentage [0-100] by calling 202 | /// `amixer -q set %`. 203 | pub fn set_volume_channel(volume: i32, channel: &str) -> Ev3Result<()> { 204 | Command::new("/usr/bin/amixer") 205 | .args(["-q", "set", channel, &format!("{volume}%")]) 206 | .stdout(Stdio::null()) 207 | .spawn()? 208 | .wait()?; 209 | 210 | Ok(()) 211 | } 212 | 213 | /// Sets the sound volume to the given percentage [0-100] by calling 214 | /// `amixer -q set %`. 215 | /// It tries to determine the default channel 216 | /// by running `amixer scontrols`. If that fails as well, it uses the 217 | /// `Playback` channel, as that is the only channel on the EV3. 218 | pub fn set_volume(volume: i32) -> Ev3Result<()> { 219 | for channel in get_channels()? { 220 | set_volume_channel(volume, &channel)?; 221 | } 222 | Ok(()) 223 | } 224 | 225 | /// Gets the current sound volume by parsing the output of 226 | /// `amixer get `. 227 | pub fn get_volume_channel(channel: &str) -> Ev3Result { 228 | let out = String::from_utf8( 229 | Command::new("/usr/bin/amixer") 230 | .args(["get", channel]) 231 | .output()? 232 | .stdout, 233 | )?; 234 | 235 | let vol_start = out.find('[').unwrap_or(0) + 1; 236 | let vol_end = out.find("%]").unwrap_or(1); 237 | let vol = &out[vol_start..vol_end].parse::()?; 238 | 239 | Ok(*vol) 240 | } 241 | 242 | /// Gets the current sound volume by parsing the output of 243 | /// `amixer get `. 244 | /// It tries to determine the default channel 245 | /// by running `amixer scontrols`. If that fails as well, it uses the 246 | /// `Playback` channel, as that is the only channel on the EV3. 247 | pub fn get_volume() -> Ev3Result { 248 | get_volume_channel(&get_channels()?[0]) 249 | } 250 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utility things. 2 | 3 | use std::{error::Error, fmt}; 4 | 5 | /// Helper `Result` type for easy access. 6 | pub type Ev3Result = Result; 7 | 8 | /// Custom error type for internal errors. 9 | #[derive(Debug)] 10 | pub enum Ev3Error { 11 | /// Internal error with error `msg`. 12 | InternalError { 13 | /// Original error message. 14 | msg: String, 15 | }, 16 | /// No matching device found. 17 | NotConnected { 18 | /// Corresponding device 19 | device: String, 20 | /// Device was expected to be on this port (None if no port was specified) 21 | port: Option, 22 | }, 23 | /// More than one matching device found. 24 | MultipleMatches { 25 | /// Corresponding device 26 | device: String, 27 | /// Devices of the requested type were found on this ports. 28 | ports: Vec, 29 | }, 30 | } 31 | 32 | impl fmt::Display for Ev3Error { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | match self { 35 | Ev3Error::InternalError { msg } => write!(f, "InternalError: {msg}!"), 36 | Ev3Error::NotConnected { device, port } => { 37 | write!(f, "'{device}' not connected at port {port:?}!") 38 | } 39 | Ev3Error::MultipleMatches { device, ports } => { 40 | write!(f, "Multiple '{device}' connected at ports {ports:?}!") 41 | } 42 | } 43 | } 44 | } 45 | 46 | impl Error for Ev3Error {} 47 | 48 | impl From for Ev3Error { 49 | fn from(err: std::io::Error) -> Self { 50 | Ev3Error::InternalError { 51 | msg: format!("{err}"), 52 | } 53 | } 54 | } 55 | 56 | impl From for Ev3Error { 57 | fn from(err: std::string::FromUtf8Error) -> Self { 58 | Ev3Error::InternalError { 59 | msg: format!("{err}"), 60 | } 61 | } 62 | } 63 | 64 | impl From for Ev3Error { 65 | fn from(err: std::num::ParseIntError) -> Self { 66 | Ev3Error::InternalError { 67 | msg: format!("{err}"), 68 | } 69 | } 70 | } 71 | 72 | #[cfg(feature = "screen")] 73 | impl From for Ev3Error { 74 | fn from(err: framebuffer::FramebufferError) -> Self { 75 | Ev3Error::InternalError { 76 | msg: format!("{:?}", err), 77 | } 78 | } 79 | } 80 | 81 | /// EV3 ports 82 | pub trait Port { 83 | /// Returns the name of the port. 84 | fn address(&self) -> String; 85 | } 86 | 87 | /// Helper trait to convert an option to an error. 88 | /// Polyfill for the `Try` trait until it is stable. 89 | pub trait OrErr { 90 | /// Consumes the `Option` and returns an `Ev3Result`. 91 | fn or_err(self) -> Ev3Result; 92 | } 93 | 94 | impl OrErr for Option { 95 | fn or_err(self) -> Ev3Result { 96 | self.ok_or(Ev3Error::InternalError { 97 | msg: "Cannot unwrap option".to_owned(), 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/wait.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for cpu efficient `wait` commands. 2 | //! Uses the `libc::epoll_wait` that only works on linux systems. 3 | 4 | #[cfg(target_os = "linux")] 5 | use libc; 6 | use std::os::unix::io::RawFd; 7 | use std::time::{Duration, Instant}; 8 | 9 | /// Wait for until a condition `cond` is `true` or the `timeout` is reached. 10 | /// If the `timeout` is `None` it will wait an infinite time. 11 | /// The condition is checked when the `file` has changed. 12 | /// 13 | /// # Arguments 14 | /// * `file` - Listen to changes in this file 15 | /// * `cond` - Condition that should become true 16 | /// * `timeout` - Maximal timeout to wait for the condition or file changes 17 | /// 18 | /// # Example 19 | /// ``` 20 | /// use std::fs::File; 21 | /// use std::os::unix::io::AsRawFd; 22 | /// use std::time::Duration; 23 | /// 24 | /// use ev3dev_lang_rust::wait; 25 | /// 26 | /// if let Ok(file) = File::open("...") { 27 | /// let cond = || { 28 | /// // ... 29 | /// true 30 | /// }; 31 | /// let timeout = Duration::from_millis(2000); 32 | /// 33 | /// wait::wait(file.as_raw_fd(), cond, Some(timeout)); 34 | /// } 35 | /// ``` 36 | pub fn wait(fd: RawFd, cond: F, timeout: Option) -> bool 37 | where 38 | F: Fn() -> bool, 39 | { 40 | if cond() { 41 | return true; 42 | } 43 | 44 | let start = Instant::now(); 45 | 46 | let mut t = timeout; 47 | 48 | loop { 49 | let wait_timeout = match t { 50 | Some(duration) => duration.as_millis() as i32, 51 | None => -1, 52 | }; 53 | wait_file_changes(fd, wait_timeout); 54 | 55 | if let Some(duration) = timeout { 56 | let elapsed = start.elapsed(); 57 | if elapsed >= duration { 58 | return false; 59 | } 60 | t = Some(duration - elapsed); 61 | } 62 | 63 | if cond() { 64 | return true; 65 | } 66 | } 67 | } 68 | 69 | /// Wrapper for `libc::epoll_wait` 70 | #[cfg(target_os = "linux")] 71 | fn wait_file_changes(fd: RawFd, timeout: i32) -> bool { 72 | let mut buf: [libc::epoll_event; 10] = [libc::epoll_event { events: 0, u64: 0 }; 10]; 73 | 74 | let result = unsafe { 75 | libc::epoll_wait( 76 | fd, 77 | buf.as_mut_ptr() as *mut libc::epoll_event, 78 | buf.len() as i32, 79 | timeout, 80 | ) as i32 81 | }; 82 | 83 | result > 0 84 | } 85 | 86 | /// Stub method for non linux os's 87 | #[cfg(not(target_os = "linux"))] 88 | fn wait_file_changes(_fd: RawFd, _timeout: i32) -> bool { 89 | std::thread::sleep(Duration::from_millis(100)); 90 | false 91 | } 92 | -------------------------------------------------------------------------------- /tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cargo test 4 | cargo test --package ev3dev-lang-rust --test ev3 --no-default-features --features ev3 5 | cargo test --package ev3dev-lang-rust --test brickpi --no-default-features --features brickpi 6 | cargo test --package ev3dev-lang-rust --test brickpi3 --no-default-features --features brickpi3 7 | EV3DEV_DRIVER_PATH="/test/path" cargo test --package ev3dev-lang-rust --test override-driver-path --features override-driver-path 8 | -------------------------------------------------------------------------------- /tests/brickpi.rs: -------------------------------------------------------------------------------- 1 | use ev3dev_lang_rust::{motors::MotorPort, sensors::SensorPort, Port}; 2 | 3 | extern crate ev3dev_lang_rust; 4 | 5 | #[test] 6 | fn test_input_port_mapping() { 7 | assert_eq!(SensorPort::In1.address(), "serial0-0:S1".to_string()); 8 | assert_eq!(SensorPort::In2.address(), "serial0-0:S2".to_string()); 9 | assert_eq!(SensorPort::In3.address(), "serial0-0:S3".to_string()); 10 | assert_eq!(SensorPort::In4.address(), "serial0-0:S4".to_string()); 11 | } 12 | 13 | #[test] 14 | fn test_output_port_mapping() { 15 | assert_eq!(MotorPort::OutA.address(), "serial0-0:MA".to_string()); 16 | assert_eq!(MotorPort::OutB.address(), "serial0-0:MB".to_string()); 17 | assert_eq!(MotorPort::OutC.address(), "serial0-0:MC".to_string()); 18 | assert_eq!(MotorPort::OutD.address(), "serial0-0:MD".to_string()); 19 | } 20 | -------------------------------------------------------------------------------- /tests/brickpi3.rs: -------------------------------------------------------------------------------- 1 | use ev3dev_lang_rust::{motors::MotorPort, sensors::SensorPort, Port}; 2 | 3 | extern crate ev3dev_lang_rust; 4 | 5 | #[test] 6 | fn test_input_port_mapping() { 7 | assert_eq!(SensorPort::In1.address(), "spi0.1:S1".to_string()); 8 | assert_eq!(SensorPort::In2.address(), "spi0.1:S2".to_string()); 9 | assert_eq!(SensorPort::In3.address(), "spi0.1:S3".to_string()); 10 | assert_eq!(SensorPort::In4.address(), "spi0.1:S4".to_string()); 11 | } 12 | 13 | #[test] 14 | fn test_output_port_mapping() { 15 | assert_eq!(MotorPort::OutA.address(), "spi0.1:MA".to_string()); 16 | assert_eq!(MotorPort::OutB.address(), "spi0.1:MB".to_string()); 17 | assert_eq!(MotorPort::OutC.address(), "spi0.1:MC".to_string()); 18 | assert_eq!(MotorPort::OutD.address(), "spi0.1:MD".to_string()); 19 | } 20 | -------------------------------------------------------------------------------- /tests/ev3.rs: -------------------------------------------------------------------------------- 1 | use ev3dev_lang_rust::{motors::MotorPort, sensors::SensorPort, Port}; 2 | 3 | extern crate ev3dev_lang_rust; 4 | 5 | #[test] 6 | fn test_input_port_mapping() { 7 | assert_eq!(SensorPort::In1.address(), "in1".to_string()); 8 | assert_eq!(SensorPort::In2.address(), "in2".to_string()); 9 | assert_eq!(SensorPort::In3.address(), "in3".to_string()); 10 | assert_eq!(SensorPort::In4.address(), "in4".to_string()); 11 | } 12 | 13 | #[test] 14 | fn test_output_port_mapping() { 15 | assert_eq!(MotorPort::OutA.address(), "outA".to_string()); 16 | assert_eq!(MotorPort::OutB.address(), "outB".to_string()); 17 | assert_eq!(MotorPort::OutC.address(), "outC".to_string()); 18 | assert_eq!(MotorPort::OutD.address(), "outD".to_string()); 19 | } 20 | -------------------------------------------------------------------------------- /tests/override-driver-path.rs: -------------------------------------------------------------------------------- 1 | extern crate ev3dev_lang_rust; 2 | 3 | #[test] 4 | fn test_output_port_mapping() { 5 | let driver_path = ev3dev_lang_rust::DRIVER_PATH; 6 | assert_eq!(driver_path, "/test/path"); 7 | } 8 | --------------------------------------------------------------------------------