├── .envrc ├── packages ├── pros-sys │ ├── link │ │ ├── libc.a │ │ ├── libpros.a │ │ ├── v5.ld │ │ ├── v5-hot.ld │ │ └── v5-common.ld │ ├── README.md │ ├── build.rs │ ├── Cargo.toml │ └── src │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── distance.rs │ │ ├── colors.rs │ │ ├── rotation.rs │ │ └── serial.rs ├── pros-sync │ ├── README.md │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── pros-math │ ├── README.md │ ├── src │ │ ├── lib.rs │ │ ├── pid.rs │ │ └── feedforward.rs │ └── Cargo.toml ├── pros-core │ ├── src │ │ ├── allocator │ │ │ ├── mod.rs │ │ │ ├── vexos.rs │ │ │ └── wasm.rs │ │ ├── lib.rs │ │ ├── sync.rs │ │ ├── error.rs │ │ ├── time.rs │ │ └── io │ │ │ └── mod.rs │ ├── README.md │ └── Cargo.toml ├── pros-devices │ ├── src │ │ ├── usd.rs │ │ ├── lib.rs │ │ ├── battery.rs │ │ ├── adi │ │ │ ├── pwm.rs │ │ │ ├── ultrasonic.rs │ │ │ ├── gyro.rs │ │ │ ├── encoder.rs │ │ │ ├── motor.rs │ │ │ ├── potentiometer.rs │ │ │ ├── solenoid.rs │ │ │ ├── switch.rs │ │ │ ├── linetracker.rs │ │ │ ├── digital.rs │ │ │ ├── analog.rs │ │ │ └── mod.rs │ │ ├── smart │ │ │ ├── expander.rs │ │ │ ├── distance.rs │ │ │ ├── rotation.rs │ │ │ ├── gps.rs │ │ │ ├── vision.rs │ │ │ └── link.rs │ │ ├── competition.rs │ │ └── position.rs │ ├── README.md │ └── Cargo.toml ├── pros-panic │ ├── README.md │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── pros-async │ ├── README.md │ ├── src │ │ ├── reactor.rs │ │ ├── executor.rs │ │ └── lib.rs │ └── Cargo.toml └── pros │ ├── examples │ ├── basic.rs │ ├── battery.rs │ ├── screen.rs │ ├── dynamic_peripherals.rs │ ├── imu.rs │ ├── optical.rs │ ├── adi_expander.rs │ ├── adi.rs │ └── accessories.rs │ ├── Cargo.toml │ └── src │ └── lib.rs ├── rustfmt.toml ├── rust-toolchain.toml ├── .pre-commit-config.yaml ├── Cargo.toml ├── .cargo └── config.toml ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── small_issue.md │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── rust.yml ├── flake.nix ├── .vscode └── settings.json ├── armv7a-vexos-eabi.json ├── .devcontainer └── devcontainer.json ├── LICENSE ├── TODO.md ├── .gitignore ├── README.md └── flake.lock /.envrc: -------------------------------------------------------------------------------- 1 | use flake . -------------------------------------------------------------------------------- /packages/pros-sys/link/libc.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexide/pros-rs/HEAD/packages/pros-sys/link/libc.a -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | reorder_imports = true 3 | imports_granularity = "Crate" 4 | -------------------------------------------------------------------------------- /packages/pros-sys/link/libpros.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vexide/pros-rs/HEAD/packages/pros-sys/link/libpros.a -------------------------------------------------------------------------------- /packages/pros-sync/README.md: -------------------------------------------------------------------------------- 1 | # pros-sync 2 | 3 | Synchronous robot code trait for [pros-rs](https://crates.io/crates/pros). 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2024-02-07" 3 | components = ["rust-src"] 4 | targets = ["armv7a-none-eabi"] 5 | -------------------------------------------------------------------------------- /packages/pros-math/README.md: -------------------------------------------------------------------------------- 1 | # pros-math 2 | 3 | Common mathematical formulas and models implemented for [`pros-rs`](https://crates.io/crates/pros). 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/doublify/pre-commit-rust 3 | rev: v1.0 4 | hooks: 5 | - id: fmt 6 | - id: cargo-check -------------------------------------------------------------------------------- /packages/pros-math/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Common mathematical formulas and models implemented for [`pros-rs`](https://crates.io/crates/pros). 2 | 3 | #![no_std] 4 | 5 | pub mod feedforward; 6 | pub mod pid; 7 | -------------------------------------------------------------------------------- /packages/pros-core/src/allocator/mod.rs: -------------------------------------------------------------------------------- 1 | //! Simple allocator using the VEX libc allocation functions in vexos and jemalloc in the sim. 2 | 3 | #[cfg(target_arch = "arm")] 4 | mod vexos; 5 | #[cfg(target_arch = "wasm32")] 6 | mod wasm; 7 | -------------------------------------------------------------------------------- /packages/pros-sys/README.md: -------------------------------------------------------------------------------- 1 | # Pros-sys 2 | 3 | EFI for Rust PROS bindings, used in [pros-rs](https://crates.io/crates/pros) 4 | 5 | ## This project is still very early in development 6 | 7 | Make sure to check out the todo list [(TODO.md)](../TODO.md) 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["packages/*"] 3 | resolver = "2" 4 | 5 | [workspace.lints.rust] 6 | rust_2018_idioms = "warn" 7 | missing_docs = "warn" 8 | unsafe_op_in_unsafe_fn = "warn" 9 | 10 | [workspace.lints.clippy] 11 | missing_const_for_fn = "warn" 12 | -------------------------------------------------------------------------------- /packages/pros-devices/src/usd.rs: -------------------------------------------------------------------------------- 1 | //! USD api. 2 | //! 3 | //! The USD API provides functions for interacting with the SD card slot on the V5 Brain. 4 | 5 | /// Checks if an SD card is installed. 6 | pub fn usd_installed() -> bool { 7 | unsafe { pros_sys::misc::usd_is_installed() == 1 } 8 | } 9 | -------------------------------------------------------------------------------- /packages/pros-panic/README.md: -------------------------------------------------------------------------------- 1 | # pros-panic 2 | 3 | Panic handler implementation for [`pros-rs`](https://crates.io/crates/pros-rs). 4 | Supports printing a backtrace when running in the simulator. 5 | If the `display_panics` feature is enabled, it will also display the panic message on the V5 Brain display. 6 | -------------------------------------------------------------------------------- /packages/pros-async/README.md: -------------------------------------------------------------------------------- 1 | # pros-async 2 | 3 | Tiny async runtime and robot traits for `pros-rs`. 4 | The async executor supports spawning tasks and blocking on futures. 5 | It has a reactor to improve the performance of some futures. 6 | FreeRTOS tasks can still be used, but it is recommended to use only async tasks for performance. 7 | -------------------------------------------------------------------------------- /packages/pros/examples/basic.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use pros::prelude::*; 5 | 6 | #[derive(Default)] 7 | pub struct Robot; 8 | 9 | impl AsyncRobot for Robot { 10 | async fn opcontrol(&mut self) -> Result { 11 | println!("basic example"); 12 | 13 | Ok(()) 14 | } 15 | } 16 | async_robot!(Robot); 17 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "./armv7a-vexos-eabi.json" 3 | 4 | [target.wasm32-unknown-unknown] 5 | rustflags = [ 6 | "-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals", 7 | "-Clink-arg=--shared-memory", 8 | "-Clink-arg=--export-table", 9 | ] 10 | 11 | [unstable] 12 | build-std = ["core", "compiler_builtins", "alloc"] 13 | -------------------------------------------------------------------------------- /packages/pros-sys/build.rs: -------------------------------------------------------------------------------- 1 | use cfg_if::cfg_if; 2 | 3 | fn main() { 4 | cfg_if! { 5 | if #[cfg(not(feature = "no-link"))] { 6 | let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); 7 | 8 | #[cfg(not(feature = "no-link"))] 9 | println!("cargo:rustc-link-search=native={manifest_dir}/link"); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/pros-core/README.md: -------------------------------------------------------------------------------- 1 | # pros-core 2 | Low level core functionality for [`pros-rs`](https://crates.io/crates/pros). 3 | The core crate is used in all other crates in the pros-rs ecosystem. 4 | Included in this crate: 5 | - Global allocator 6 | - Errno handling 7 | - Serial terminal printing 8 | - No-std `Instant`s 9 | - Synchronization primitives 10 | - FreeRTOS task management 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Describe the changes this PR makes. Why should it be merged? 2 | 3 | ## Additional Context 4 | 12 | -------------------------------------------------------------------------------- /packages/pros/examples/battery.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use pros::{devices::battery, prelude::*}; 5 | 6 | #[derive(Default)] 7 | pub struct Robot; 8 | 9 | impl AsyncRobot for Robot { 10 | async fn opcontrol(&mut self) -> Result { 11 | if battery::capacity()? < 20.0 { 12 | println!("Battery is low!"); 13 | } else if battery::temperature()? > 999.0 { 14 | println!("Battery has exploded!"); 15 | } 16 | 17 | Ok(()) 18 | } 19 | } 20 | async_robot!(Robot); 21 | -------------------------------------------------------------------------------- /packages/pros-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros-sys" 3 | version = "0.8.0" 4 | edition = "2021" 5 | description = "EFI for the PROS rust bindings" 6 | keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] 7 | categories = [ 8 | "api-bindings", 9 | "development-tools::ffi", 10 | "external-ffi-bindings", 11 | "no-std", 12 | "science::robotics", 13 | ] 14 | license = "MIT" 15 | repository = "https://github.com/gavin-niederman/pros-rs" 16 | links = "pros" 17 | 18 | [lib] 19 | doctest = false 20 | 21 | [features] 22 | xapi = [] 23 | no-link = [] 24 | 25 | [build-dependencies] 26 | cfg-if = "1.0" 27 | 28 | [dependencies] 29 | cfg-if = "1.0" 30 | -------------------------------------------------------------------------------- /packages/pros-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Low level core functionality for [`pros-rs`](https://crates.io/crates/pros). 2 | //! The core crate is used in all other crates in the pros-rs ecosystem. 3 | //! 4 | //! Included in this crate: 5 | //! - Global allocator: [`pros_alloc`] 6 | //! - Errno handling: [`error`] 7 | //! - Serial terminal printing: [`io`] 8 | //! - No-std [`Instant`](time::Instant)s: [`time`] 9 | //! - Synchronization primitives: [`sync`] 10 | //! - FreeRTOS task management: [`task`] 11 | 12 | #![no_std] 13 | #![feature(error_in_core)] 14 | 15 | extern crate alloc; 16 | 17 | pub mod allocator; 18 | pub mod error; 19 | pub mod io; 20 | pub mod sync; 21 | pub mod task; 22 | pub mod time; 23 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | flake-utils.url = "github:numtide/flake-utils"; 4 | cargo-pros.url = "github:pros-rs/cargo-pros"; 5 | pros-cli-nix.url = "github:BattleCh1cken/pros-cli-nix"; 6 | }; 7 | 8 | outputs = { nixpkgs, flake-utils, cargo-pros, pros-cli-nix, ... }: 9 | (flake-utils.lib.eachDefaultSystem (system: 10 | let 11 | pkgs = nixpkgs.legacyPackages.${system}; 12 | cargo-pros' = cargo-pros.packages.${system}.default; 13 | pros-cli = pros-cli-nix.packages.${system}.default; 14 | in { 15 | devShell = pkgs.mkShell { 16 | buildInputs = with pkgs; [ gcc-arm-embedded-9 cargo-pros' pros-cli ]; 17 | }; 18 | })); 19 | } 20 | -------------------------------------------------------------------------------- /packages/pros-core/src/allocator/vexos.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::{GlobalAlloc, Layout}; 2 | 3 | struct Allocator; 4 | unsafe impl GlobalAlloc for Allocator { 5 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 6 | // SAFETY: caller must ensure that the alignment and size are valid for the given layout 7 | unsafe { pros_sys::memalign(layout.align() as _, layout.size() as _) as *mut u8 } 8 | } 9 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 10 | // SAFETY: caller must ensure that the given ptr can be deallocated 11 | unsafe { pros_sys::free(ptr as *mut core::ffi::c_void) } 12 | } 13 | } 14 | 15 | #[global_allocator] 16 | static ALLOCATOR: Allocator = Allocator; 17 | -------------------------------------------------------------------------------- /packages/pros-math/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros-math" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Commonly used mathematical formulas for pros-rs" 7 | keywords = ["PROS", "Robotics", "vex", "v5"] 8 | categories = [ 9 | "no-std", 10 | "science::robotics", 11 | "Mathematics", 12 | ] 13 | repository = "https://github.com/gavin-niederman/pros-rs" 14 | authors = [ 15 | "pros-rs", 16 | "Gavin Niederman ", 17 | "doinkythederp ", 18 | ] 19 | 20 | [dependencies] 21 | num = { version = "0.4.1", default-features = false } 22 | pros-core = { version = "0.1.0", path = "../pros-core" } 23 | 24 | [lints] 25 | workspace = true 26 | -------------------------------------------------------------------------------- /packages/pros/examples/screen.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::fmt::Write; 5 | 6 | use pros::prelude::*; 7 | 8 | pub struct Robot { 9 | screen: Screen, 10 | } 11 | 12 | impl Robot { 13 | fn new(peripherals: Peripherals) -> Self { 14 | Self { 15 | screen: peripherals.screen, 16 | } 17 | } 18 | } 19 | 20 | impl AsyncRobot for Robot { 21 | async fn opcontrol(&mut self) -> Result { 22 | self.screen.fill(&Rect::new(0, 0, 20, 20), Rgb::RED)?; 23 | self.screen.stroke(&Circle::new(25, 25, 20), Rgb::BLUE)?; 24 | 25 | writeln!(self.screen, "Hello, world.")?; 26 | 27 | Ok(()) 28 | } 29 | } 30 | async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); 31 | -------------------------------------------------------------------------------- /packages/pros-sync/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros-sync" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "`SyncRobot` trait and macro for pros-rs" 7 | keywords = ["PROS", "Robotics", "vex", "v5"] 8 | categories = [ 9 | "no-std", 10 | "science::robotics", 11 | ] 12 | repository = "https://github.com/gavin-niederman/pros-rs" 13 | authors = [ 14 | "pros-rs", 15 | "Gavin Niederman ", 16 | "doinkythederp ", 17 | ] 18 | 19 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 20 | 21 | [dependencies] 22 | pros-core = { version = "0.1.0", path = "../pros-core" } 23 | 24 | [lints] 25 | workspace = true 26 | -------------------------------------------------------------------------------- /packages/pros/examples/dynamic_peripherals.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use pros::prelude::*; 5 | 6 | pub struct Robot { 7 | peripherals: DynamicPeripherals, 8 | } 9 | impl Robot { 10 | fn new(peripherals: Peripherals) -> Self { 11 | Self { 12 | peripherals: DynamicPeripherals::new(peripherals), 13 | } 14 | } 15 | } 16 | impl AsyncRobot for Robot { 17 | async fn opcontrol(&mut self) -> Result { 18 | let motor = Motor::new( 19 | self.peripherals.take_smart_port(10).unwrap(), 20 | Gearset::Green, 21 | false, 22 | )?; 23 | motor.wait_until_stopped().await?; 24 | Ok(()) 25 | } 26 | } 27 | async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "baudrate", 4 | "bitfield", 5 | "centidegrees", 6 | "errno", 7 | "feedforward", 8 | "gearset", 9 | "loopspeed", 10 | "opcontrol", 11 | "vexlink" 12 | ], 13 | "rust-analyzer.check.allTargets": false, 14 | "rust-analyzer.cargo.features": [ 15 | "xapi" 16 | ], 17 | "rust-analyzer.check.targets": [ 18 | "${workspaceFolder}/armv7a-vexos-eabi.json", 19 | "wasm32-unknown-unknown", 20 | ], 21 | "rust-analyzer.showUnlinkedFileNotification": false, 22 | "files.trimTrailingWhitespace": true, 23 | "files.trimFinalNewlines": true, 24 | "files.insertFinalNewline": true, 25 | "files.eol": "\n", 26 | } 27 | -------------------------------------------------------------------------------- /packages/pros-devices/README.md: -------------------------------------------------------------------------------- 1 | # pros-devices 2 | 3 | Functionality for accessing hardware connected to the V5 brain. 4 | 5 | ## Overview 6 | 7 | The V5 brain features 21 RJ9 4p4c connector ports (known as "Smart ports") for communicating with newer V5 peripherals, as well as six 3-wire ports with log-to-digital conversion capability for compatibility with legacy Cortex devices. This module provides access to both smart devices and ADI devices. 8 | 9 | ## Organization 10 | 11 | - `smart` contains abstractions and types for smart port connected ices. 12 | - `adi` contains abstractions for three wire ADI connected devices. 13 | - `battery` provides functions for getting information about the battery. 14 | - `controller` provides types for interacting with the V5 controller. 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/small_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Small issue 3 | about: Report a small problem (e.g. typo) 4 | title: '' 5 | labels: type:bug, repro:needed 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Problem Description 11 | 12 | 13 | ## Screenshots 14 | 15 | 16 | ## Additional information 17 | 27 | -------------------------------------------------------------------------------- /packages/pros/examples/imu.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::time::Duration; 5 | 6 | use pros::prelude::*; 7 | 8 | pub struct Robot { 9 | imu: InertialSensor, 10 | } 11 | impl Robot { 12 | fn new(peripherals: Peripherals) -> Self { 13 | Self { 14 | imu: InertialSensor::new(peripherals.port_1), 15 | } 16 | } 17 | } 18 | 19 | impl AsyncRobot for Robot { 20 | async fn opcontrol(&mut self) -> Result { 21 | self.imu.calibrate().await?; 22 | 23 | loop { 24 | let euler = self.imu.euler()?; 25 | 26 | println!( 27 | "Pitch: {} Roll: {} Yaw: {}", 28 | euler.pitch, euler.roll, euler.yaw 29 | ); 30 | 31 | delay(Duration::from_secs(1)); 32 | } 33 | } 34 | } 35 | 36 | async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); 37 | -------------------------------------------------------------------------------- /packages/pros-async/src/reactor.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::BTreeMap; 2 | use core::task::Waker; 3 | 4 | pub struct Sleepers { 5 | sleepers: BTreeMap, 6 | } 7 | 8 | impl Sleepers { 9 | pub fn push(&mut self, waker: Waker, target: u32) { 10 | self.sleepers.insert(target, waker); 11 | } 12 | 13 | pub fn pop(&mut self) -> Option { 14 | self.sleepers.pop_first().map(|(_, waker)| waker) 15 | } 16 | } 17 | 18 | pub struct Reactor { 19 | pub(crate) sleepers: Sleepers, 20 | } 21 | 22 | impl Reactor { 23 | pub const fn new() -> Self { 24 | Self { 25 | sleepers: Sleepers { 26 | sleepers: BTreeMap::new(), 27 | }, 28 | } 29 | } 30 | 31 | pub fn tick(&mut self) { 32 | if let Some(sleeper) = self.sleepers.pop() { 33 | sleeper.wake() 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /armv7a-vexos-eabi.json: -------------------------------------------------------------------------------- 1 | { 2 | "cpu": "cortex-a9", 3 | "arch": "arm", 4 | "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", 5 | "disable-redzone": true, 6 | "emit-debug-gdb-scripts": false, 7 | "env": "newlib", 8 | "executables": true, 9 | "features": "+thumb2,+neon,+vfp3", 10 | "linker": "arm-none-eabi-gcc", 11 | "linker-flavor": "gcc", 12 | "llvm-target": "armv7a-none-eabi", 13 | "max-atomic-width": 64, 14 | "panic-strategy": "abort", 15 | "post-link-args": { 16 | "gcc": [ 17 | "-nostartfiles", 18 | "-nostdlib", 19 | "-Wl,-Tv5.ld,-Tv5-common.ld,--gc-sections", 20 | "-Wl,--start-group,-lgcc,-lpros,-lc,--end-group" 21 | ] 22 | }, 23 | "relocation-model": "static", 24 | "target-family": "unix", 25 | "target-pointer-width": "32", 26 | "os": "vexos", 27 | "vendor": "vex" 28 | } 29 | -------------------------------------------------------------------------------- /packages/pros-async/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros-async" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "A simple async executor for pros-rs" 7 | keywords = ["PROS", "Robotics", "bindings", "async", "vex", "v5"] 8 | categories = [ 9 | "no-std", 10 | "science::robotics", 11 | "Asynchronous" 12 | ] 13 | repository = "https://github.com/gavin-niederman/pros-rs" 14 | authors = [ 15 | "pros-rs", 16 | "Gavin Niederman ", 17 | "doinkythederp ", 18 | ] 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | async-task = { version = "4.5.0", default-features = false } 24 | pros-core = { version = "0.1.0", path = "../pros-core" } 25 | waker-fn = "1.1.1" 26 | pros-sys = { version = "0.8.0", path = "../pros-sys" } 27 | 28 | [lints] 29 | workspace = true 30 | -------------------------------------------------------------------------------- /packages/pros/examples/optical.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::time::Duration; 5 | 6 | use pros::prelude::*; 7 | 8 | pub struct Robot { 9 | optical: OpticalSensor, 10 | } 11 | impl Robot { 12 | pub fn new(peripherals: Peripherals) -> Self { 13 | Self { 14 | optical: OpticalSensor::new(peripherals.port_1, true).unwrap(), 15 | } 16 | } 17 | } 18 | 19 | impl AsyncRobot for Robot { 20 | async fn opcontrol(&mut self) -> Result { 21 | loop { 22 | println!( 23 | "-----\nHue: {}\nSaturation: {}\nBrightess: {}\nLast Gesture Direction: {:?}\n-----\n", 24 | self.optical.hue()?, 25 | self.optical.saturation()?, 26 | self.optical.brightness()?, 27 | self.optical.last_gesture_direction()? 28 | ); 29 | 30 | delay(Duration::from_millis(10)); 31 | } 32 | } 33 | } 34 | 35 | async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); 36 | -------------------------------------------------------------------------------- /packages/pros-panic/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros-panic" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Panic handler for pros-rs" 7 | keywords = ["PROS", "Robotics", "vex", "v5"] 8 | categories = [ 9 | "no-std", 10 | "science::robotics", 11 | ] 12 | repository = "https://github.com/gavin-niederman/pros-rs" 13 | authors = [ 14 | "pros-rs", 15 | "Gavin Niederman ", 16 | "doinkythederp ", 17 | ] 18 | 19 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 20 | 21 | [dependencies] 22 | pros-core = { version = "0.1.0", path = "../pros-core" } 23 | pros-devices = { version = "0.2.0", path = "../pros-devices", optional = true } 24 | pros-sys = { version = "0.8.0", path = "../pros-sys" } 25 | 26 | [features] 27 | default = ["display_panics"] 28 | 29 | display_panics = ["dep:pros-devices"] 30 | 31 | [lints] 32 | workspace = true 33 | -------------------------------------------------------------------------------- /packages/pros/examples/adi_expander.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::time::Duration; 5 | 6 | use pros::prelude::*; 7 | 8 | pub struct Robot { 9 | encoder: AdiEncoder, 10 | } 11 | impl Robot { 12 | fn new(peripherals: Peripherals) -> Self { 13 | // Create an expander on smart port 1 14 | let expander = AdiExpander::new(peripherals.port_1); 15 | 16 | Self { 17 | // Create an encoder on the expander's A and B ports. 18 | encoder: AdiEncoder::new((expander.adi_a, expander.adi_b), false).unwrap(), 19 | } 20 | } 21 | } 22 | 23 | impl AsyncRobot for Robot { 24 | async fn opcontrol(&mut self) -> Result { 25 | // Read from the encoder every second. 26 | loop { 27 | println!("Encoder position: {}", self.encoder.position()?); 28 | 29 | delay(Duration::from_secs(1)); 30 | } 31 | } 32 | } 33 | async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is not working as expected 4 | title: '' 5 | labels: type:bug, repro:required 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Bug Description 11 | 12 | 13 | ## Code to reproduce 14 | 15 | 16 | ## Expected vs. actual behavior 17 | 18 | 19 | ## Additional information 20 | 31 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust 3 | { 4 | "name": "Rust", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/rust:1-1-bullseye", 7 | "features": { 8 | "ghcr.io/devcontainers/features/github-cli:1": {}, 9 | "ghcr.io/devcontainers-contrib/features/apt-get-packages:1": { 10 | "packages": "build-essential,gcc-arm-none-eabi,clang,gcc-multilib,clangd,libstdc++-arm-none-eabi-newlib" 11 | }, 12 | "ghcr.io/devcontainers/features/python:1": {} 13 | }, 14 | "mounts": [ 15 | { 16 | "source": "devcontainer-cargo-cache-${devcontainerId}", 17 | "target": "/usr/local/cargo", 18 | "type": "volume" 19 | } 20 | ], 21 | "postCreateCommand": "pip install --user pros-cli && rustup component add rust-src && cargo install cargo-pros" 22 | } 23 | -------------------------------------------------------------------------------- /packages/pros-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros-core" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Core functionality for pros-rs" 7 | keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] 8 | categories = [ 9 | "api-bindings", 10 | "no-std", 11 | "science::robotics", 12 | ] 13 | repository = "https://github.com/gavin-niederman/pros-rs" 14 | authors = [ 15 | "pros-rs", 16 | "Gavin Niederman ", 17 | "doinkythederp ", 18 | ] 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | pros-sys = { version = "0.8.0", path = "../pros-sys" } 24 | no_std_io = { version = "0.6.0", features = ["alloc"] } 25 | snafu = { version = "0.8.0", default-features = false, features = [ 26 | "rust_1_61", 27 | "unstable-core-error", 28 | ] } 29 | spin = "0.9.8" 30 | 31 | [target.'cfg(target_arch = "wasm32")'.dependencies] 32 | dlmalloc = { version = "0.2.4", features = ["global"] } 33 | 34 | [lints] 35 | workspace = true 36 | -------------------------------------------------------------------------------- /packages/pros-devices/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros-devices" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "High level device for pros-rs" 7 | keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] 8 | categories = [ 9 | "api-bindings", 10 | "no-std", 11 | "science::robotics", 12 | ] 13 | repository = "https://github.com/gavin-niederman/pros-rs" 14 | authors = [ 15 | "pros-rs", 16 | "Gavin Niederman ", 17 | "doinkythederp ", 18 | ] 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | pros-core = { version = "0.1.0", path = "../pros-core" } 24 | pros-sys = { path = "../pros-sys", version = "0.8.0", features = ["xapi"] } 25 | snafu = { version = "0.8.0", default-features = false, features = [ 26 | "rust_1_61", 27 | "unstable-core-error", 28 | ] } 29 | no_std_io = { version = "0.6.0", features = ["alloc"] } 30 | bitflags = "2.4.2" 31 | 32 | [lints] 33 | workspace = true 34 | 35 | [features] 36 | dangerous_motor_tuning = [] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gavin Niederman 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Propose an idea or new feature 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## What's the motivation for this feature? 11 | 17 | 18 | ## Describe the solution you'd like 19 | 20 | 21 | ## Describe the drawbacks, if any 22 | 29 | 30 | ## Describe the alternative solutions, if any 31 | 32 | 33 | ## Additional context 34 | 39 | -------------------------------------------------------------------------------- /packages/pros/examples/adi.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::time::Duration; 5 | 6 | use pros::prelude::*; 7 | 8 | struct ExampleRobot { 9 | encoder: AdiEncoder, 10 | ultrasonic: AdiUltrasonic, 11 | gyro: AdiGyro, 12 | } 13 | impl ExampleRobot { 14 | pub fn new(peripherals: Peripherals) -> Self { 15 | Self { 16 | encoder: AdiEncoder::new((peripherals.adi_a, peripherals.adi_b), false).unwrap(), 17 | ultrasonic: AdiUltrasonic::new((peripherals.adi_c, peripherals.adi_d)).unwrap(), 18 | gyro: AdiGyro::new(peripherals.adi_e, 1.0).unwrap(), 19 | } 20 | } 21 | } 22 | 23 | impl AsyncRobot for ExampleRobot { 24 | async fn opcontrol(&mut self) -> Result { 25 | self.gyro.zero()?; 26 | self.encoder.zero()?; 27 | 28 | loop { 29 | println!("Encoder position: {:?}", self.encoder.position()); 30 | println!("Ultrasonic distance: {:?}", self.ultrasonic.distance()); 31 | 32 | delay(Duration::from_millis(10)); 33 | } 34 | } 35 | } 36 | 37 | async_robot!( 38 | ExampleRobot, 39 | ExampleRobot::new(Peripherals::take().unwrap()) 40 | ); 41 | -------------------------------------------------------------------------------- /packages/pros-devices/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # pros-devices 2 | //! 3 | //! Functionality for accessing hardware connected to the V5 brain. 4 | //! 5 | //! ## Overview 6 | //! 7 | //! The V5 brain features 21 RJ9 4p4c connector ports (known as "Smart ports") for communicating with newer V5 peripherals, as well as six 3-wire ports with log-to-digital conversion capability for compatibility with legacy Cortex devices. This module provides access to both smart devices and ADI devices. 8 | //! 9 | //! ## Organization 10 | //! 11 | //! - [`smart`] contains abstractions and types for smart port connected devices. 12 | //! - [`adi`] contains abstractions for three wire ADI connected devices. 13 | //! - [`battery`] provides functions for getting information about the currently connected 14 | //! battery. 15 | //! - [`controller`] provides types for interacting with the V5 controller. 16 | 17 | #![no_std] 18 | 19 | extern crate alloc; 20 | 21 | pub mod adi; 22 | pub mod smart; 23 | 24 | pub mod battery; 25 | pub mod color; 26 | pub mod competition; 27 | pub mod controller; 28 | pub mod peripherals; 29 | pub mod position; 30 | pub mod screen; 31 | pub mod usd; 32 | 33 | pub use controller::Controller; 34 | pub use position::Position; 35 | pub use screen::Screen; 36 | -------------------------------------------------------------------------------- /packages/pros-core/src/allocator/wasm.rs: -------------------------------------------------------------------------------- 1 | //! WASM host will call these functions to send values to the program 2 | 3 | extern crate alloc; 4 | 5 | use alloc::{ 6 | alloc::{alloc, dealloc, handle_alloc_error, Layout}, 7 | collections::BTreeMap, 8 | }; 9 | 10 | use dlmalloc::GlobalDlmalloc; 11 | 12 | // no multithreading in wasm 13 | static mut LAYOUTS: BTreeMap<*mut u8, Layout> = BTreeMap::new(); 14 | 15 | #[no_mangle] 16 | extern "C" fn wasm_memalign(alignment: usize, size: usize) -> *mut u8 { 17 | if size == 0 { 18 | return core::ptr::null_mut(); 19 | } 20 | let Ok(layout) = Layout::from_size_align(size, alignment) else { 21 | return core::ptr::null_mut(); 22 | }; 23 | let ptr = unsafe { alloc(layout) }; 24 | if ptr.is_null() { 25 | handle_alloc_error(layout); 26 | } 27 | unsafe { 28 | LAYOUTS.insert(ptr, layout); 29 | } 30 | ptr 31 | } 32 | 33 | #[no_mangle] 34 | extern "C" fn wasm_free(ptr: *mut u8) { 35 | if ptr.is_null() { 36 | return; 37 | } 38 | let layout = unsafe { LAYOUTS.remove(&ptr) }; 39 | if let Some(layout) = layout { 40 | unsafe { dealloc(ptr, layout) }; 41 | } 42 | } 43 | 44 | #[global_allocator] 45 | static ALLOCATOR: GlobalDlmalloc = GlobalDlmalloc; 46 | -------------------------------------------------------------------------------- /packages/pros-devices/src/battery.rs: -------------------------------------------------------------------------------- 1 | //! Utilites for getting information about the robot's battery. 2 | 3 | use pros_core::{bail_on, map_errno}; 4 | use pros_sys::{PROS_ERR, PROS_ERR_F}; 5 | use snafu::Snafu; 6 | 7 | /// Get the robot's battery capacity. 8 | pub fn capacity() -> Result { 9 | Ok(bail_on!(PROS_ERR_F, unsafe { 10 | pros_sys::misc::battery_get_capacity() 11 | })) 12 | } 13 | 14 | /// Get the current temperature of the robot's battery. 15 | pub fn temperature() -> Result { 16 | Ok(bail_on!(PROS_ERR_F, unsafe { 17 | pros_sys::misc::battery_get_temperature() 18 | })) 19 | } 20 | 21 | /// Get the electric current of the robot's battery. 22 | pub fn current() -> Result { 23 | Ok(bail_on!(PROS_ERR, unsafe { 24 | pros_sys::misc::battery_get_current() 25 | })) 26 | } 27 | 28 | /// Get the robot's battery voltage. 29 | pub fn voltage() -> Result { 30 | Ok(bail_on!(PROS_ERR, unsafe { 31 | pros_sys::misc::battery_get_voltage() 32 | })) 33 | } 34 | 35 | #[derive(Debug, Snafu)] 36 | /// Errors that can occur when interacting with the robot's battery. 37 | pub enum BatteryError { 38 | /// Another resource is already using the battery. 39 | ConcurrentAccess, 40 | } 41 | 42 | map_errno! { 43 | BatteryError { 44 | EACCES => Self::ConcurrentAccess, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/pros-sys/link/v5.ld: -------------------------------------------------------------------------------- 1 | /* This stack is used during initialization, but FreeRTOS tasks have their own 2 | stack allocated in BSS or Heap (kernel tasks in FreeRTOS .bss heap; user tasks 3 | in standard heap) */ 4 | _STACK_SIZE = DEFINED(_STACK_SIZE) ? _STACK_SIZE : 0x2000; 5 | 6 | _ABORT_STACK_SIZE = DEFINED(_ABORT_STACK_SIZE) ? _ABORT_STACK_SIZE : 1024; 7 | _SUPERVISOR_STACK_SIZE = DEFINED(_SUPERVISOR_STACK_SIZE) ? _SUPERVISOR_STACK_SIZE : 2048; 8 | _IRQ_STACK_SIZE = DEFINED(_IRQ_STACK_SIZE) ? _IRQ_STACK_SIZE : 1024; 9 | _FIQ_STACK_SIZE = DEFINED(_FIQ_STACK_SIZE) ? _FIQ_STACK_SIZE : 1024; 10 | _UNDEF_STACK_SIZE = DEFINED(_UNDEF_STACK_SIZE) ? _UNDEF_STACK_SIZE : 1024; 11 | 12 | _HEAP_SIZE = DEFINED(_HEAP_SIZE) ? _HEAP_SIZE : 0x02E00000; /* ~48 MB */ 13 | 14 | /* Define Memories in the system */ 15 | start_of_cold_mem = 0x03800000; 16 | _COLD_MEM_SIZE = 0x04800000; 17 | end_of_cold_mem = start_of_cold_mem + _COLD_MEM_SIZE; 18 | 19 | start_of_hot_mem = 0x07800000; 20 | _HOT_MEM_SIZE = 0x00800000; 21 | end_of_hot_mem = start_of_hot_mem + _HOT_MEM_SIZE; 22 | 23 | MEMORY 24 | { 25 | /* user code 72M */ 26 | COLD_MEMORY : ORIGIN = start_of_cold_mem, LENGTH = _COLD_MEM_SIZE /* Just under 19 MB */ 27 | HEAP : ORIGIN = 0x04A00000, LENGTH = _HEAP_SIZE 28 | HOT_MEMORY : ORIGIN = start_of_hot_mem, LENGTH = _HOT_MEM_SIZE /* Just over 8 MB */ 29 | } 30 | 31 | REGION_ALIAS("RAM", COLD_MEMORY); 32 | 33 | ENTRY(vexStartup) 34 | -------------------------------------------------------------------------------- /packages/pros-sys/link/v5-hot.ld: -------------------------------------------------------------------------------- 1 | /* This stack is used during initialization, but FreeRTOS tasks have their own 2 | stack allocated in BSS or Heap (kernel tasks in FreeRTOS .bss heap; user tasks 3 | in standard heap) */ 4 | _STACK_SIZE = DEFINED(_STACK_SIZE) ? _STACK_SIZE : 0x2000; 5 | 6 | _ABORT_STACK_SIZE = DEFINED(_ABORT_STACK_SIZE) ? _ABORT_STACK_SIZE : 1024; 7 | _SUPERVISOR_STACK_SIZE = DEFINED(_SUPERVISOR_STACK_SIZE) ? _SUPERVISOR_STACK_SIZE : 2048; 8 | _IRQ_STACK_SIZE = DEFINED(_IRQ_STACK_SIZE) ? _IRQ_STACK_SIZE : 1024; 9 | _FIQ_STACK_SIZE = DEFINED(_FIQ_STACK_SIZE) ? _FIQ_STACK_SIZE : 1024; 10 | _UNDEF_STACK_SIZE = DEFINED(_UNDEF_STACK_SIZE) ? _UNDEF_STACK_SIZE : 1024; 11 | 12 | _HEAP_SIZE = DEFINED(_HEAP_SIZE) ? _HEAP_SIZE : 0x02E00000; /* ~48 MB */ 13 | 14 | /* Define Memories in the system */ 15 | start_of_cold_mem = 0x03800000; 16 | _COLD_MEM_SIZE = 0x04800000; 17 | end_of_cold_mem = start_of_cold_mem + _COLD_MEM_SIZE; 18 | 19 | start_of_hot_mem = 0x07800000; 20 | _HOT_MEM_SIZE = 0x00800000; 21 | end_of_hot_mem = start_of_hot_mem + _HOT_MEM_SIZE; 22 | 23 | MEMORY 24 | { 25 | /* user code 72M */ 26 | COLD_MEMORY : ORIGIN = start_of_cold_mem, LENGTH = _COLD_MEM_SIZE /* Just under 19 MB */ 27 | HEAP : ORIGIN = 0x04A00000, LENGTH = _HEAP_SIZE 28 | HOT_MEMORY : ORIGIN = start_of_hot_mem, LENGTH = _HOT_MEM_SIZE /* Just over 8 MB */ 29 | } 30 | 31 | REGION_ALIAS("RAM", HOT_MEMORY); 32 | 33 | ENTRY(install_hot_table) 34 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | This is the todo list for the eventual 1.0.0 release of pros-rs 4 | 5 | ## Bindings 6 | 7 | * [X] Basic LCD Printing. 8 | * [X] Buttons 9 | * [X] Pressed buttons 10 | * [X] Button press callback functions 11 | * [X] Multitasking 12 | * [X] Mutexes 13 | * [X] Tasks 14 | * [X] Notifications 15 | * [ ] Motors 16 | * [x] Internal gearsets 17 | * [ ] (Custom) Gear Ratios 18 | * [X] Make Robot Functions Take Self 19 | * [X] PID controllers 20 | * [X] Feedforward loops 21 | * [ ] ADI (3 wire ports) 22 | * [ ] Ext. ADI 23 | * [X] Sensors 24 | * [X] Distance 25 | * [X] GPS 26 | * [x] Inertial (IMU) 27 | * [x] Optical 28 | * [X] Rotational 29 | * [X] Vision 30 | * [X] Controllers 31 | * [X] Controller data 32 | * [x] Controller printing 33 | * [X] Link 34 | * [X] Async runtime 35 | * [X] Returning top level futures 36 | * [X] Reactor 37 | * [ ] More asynchronous APIs 38 | * [ ] MPSC 39 | * [X] Task Locals 40 | 41 | ## API 42 | 43 | * [X] Make more ergonomic start functions. (macros?) 44 | * [X] Consider hiding task priority and stack depth from task API. 45 | 46 | ## Docs 47 | 48 | * [X] Guides for building 49 | * [X] Windows 50 | * [X] Linux 51 | * [X] MacOS 52 | * [ ] Examples in docs and readme 53 | * [ ] More comprehensive documentation in general 54 | 55 | ## Non essential 56 | 57 | * [ ] Drivetrain 58 | * [ ] Xapi bindings 59 | * [ ] LVGL bindings 60 | * [X] Serial bindings (pros-sys) 61 | -------------------------------------------------------------------------------- /packages/pros-sys/src/error.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::{c_int, c_uint}; 2 | 3 | pub const PROS_ERR_BYTE: u8 = u8::MAX; 4 | pub const PROS_ERR_2_BYTE: u16 = u16::MAX; 5 | pub const PROS_ERR: i32 = i32::MAX; 6 | pub const PROS_ERR_F: f64 = f64::INFINITY; 7 | pub const PROS_SUCCESS: c_uint = 1; 8 | 9 | pub const EPERM: c_int = 1; 10 | pub const ENOENT: c_int = 2; 11 | pub const ESRCH: c_int = 3; 12 | pub const EINTR: c_int = 4; 13 | pub const EIO: c_int = 5; 14 | pub const ENXIO: c_int = 6; 15 | pub const E2BIG: c_int = 7; 16 | pub const ENOEXEC: c_int = 8; 17 | pub const EBADF: c_int = 9; 18 | pub const ECHILD: c_int = 10; 19 | pub const EAGAIN: c_int = 11; 20 | pub const ENOMEM: c_int = 12; 21 | pub const EACCES: c_int = 13; 22 | pub const EFAULT: c_int = 14; 23 | pub const ENOTBLK: c_int = 15; 24 | pub const EBUSY: c_int = 16; 25 | pub const EEXIST: c_int = 17; 26 | pub const EXDEV: c_int = 18; 27 | pub const ENODEV: c_int = 19; 28 | pub const ENOTDIR: c_int = 20; 29 | pub const EISDIR: c_int = 21; 30 | pub const EINVAL: c_int = 22; 31 | pub const ENFILE: c_int = 23; 32 | pub const EMFILE: c_int = 24; 33 | pub const ENOTTY: c_int = 25; 34 | pub const ETXTBSY: c_int = 26; 35 | pub const EFBIG: c_int = 27; 36 | pub const ENOSPC: c_int = 28; 37 | pub const ESPIPE: c_int = 29; 38 | pub const EROFS: c_int = 30; 39 | pub const EMLINK: c_int = 31; 40 | pub const EPIPE: c_int = 32; 41 | pub const EDOM: c_int = 33; 42 | pub const ERANGE: c_int = 34; 43 | pub const EHOSTDOWN: c_int = 112; 44 | pub const EBADMSG: c_int = 74; 45 | pub const EADDRINUSE: c_int = 98; 46 | pub const ENOSYS: c_int = 38; 47 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Setup | Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Setup | Toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | profile: minimal 17 | toolchain: nightly-2024-02-07 18 | override: true 19 | 20 | - name: Check 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: check 24 | 25 | fmt: 26 | name: Rustfmt 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Setup | Checkout 30 | uses: actions/checkout@v2 31 | 32 | - name: Setup | Toolchain 33 | uses: actions-rs/toolchain@v1 34 | with: 35 | profile: minimal 36 | toolchain: nightly-2024-02-07 37 | override: true 38 | 39 | - name: Setup | Install Rustfmt 40 | run: rustup component add rustfmt 41 | 42 | - name: Format 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: fmt 46 | args: --all -- --check 47 | 48 | lint: 49 | name: Lint 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v3 53 | - uses: dtolnay/rust-toolchain@nightly 54 | with: 55 | components: clippy 56 | - uses: giraffate/clippy-action@v1 57 | with: 58 | reporter: 'github-pr-check' 59 | github_token: ${{ secrets.GITHUB_TOKEN }} 60 | clippy_flags: --all-targets --all-features 61 | -------------------------------------------------------------------------------- /packages/pros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pros" 3 | version = "0.9.0" 4 | edition = "2021" 5 | description = "Rust bindings for PROS" 6 | keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] 7 | categories = ["os", "api-bindings", "no-std", "science::robotics"] 8 | license = "MIT" 9 | repository = "https://github.com/pros-rs/pros-rs" 10 | readme = "../../README.md" 11 | authors = [ 12 | "pros-rs", 13 | "Gavin Niederman ", 14 | "doinkythederp ", 15 | ] 16 | rust-version = "1.75.0" 17 | 18 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 19 | 20 | [dependencies] 21 | pros-sync = { version = "0.2.0", path = "../pros-sync", optional = true } 22 | pros-async = { version = "0.2.0", path = "../pros-async", optional = true } 23 | pros-devices = { version = "0.2.0", path = "../pros-devices", optional = true } 24 | pros-panic = { version = "0.1.1", path = "../pros-panic", optional = true } 25 | pros-core = { version = "0.1.1", path = "../pros-core", optional = true } 26 | pros-math = { version = "0.1.0", path = "../pros-math", optional = true } 27 | pros-sys = { version = "0.8.0", path = "../pros-sys" } 28 | 29 | [features] 30 | default = ["async", "devices", "panic", "display_panics", "core", "math"] 31 | 32 | core = ["dep:pros-core"] 33 | 34 | async = ["dep:pros-async"] 35 | sync = ["dep:pros-sync"] 36 | 37 | devices = ["dep:pros-devices"] 38 | 39 | math = ["dep:pros-math"] 40 | 41 | panic = ["dep:pros-panic"] 42 | display_panics = ["pros-panic/display_panics"] 43 | 44 | dangerous-motor-tuning = ["pros-devices/dangerous_motor_tuning"] 45 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/pwm.rs: -------------------------------------------------------------------------------- 1 | //! ADI Pulse-width modulation (PWM). 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::PROS_ERR; 5 | 6 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 7 | 8 | /// Generic PWM output ADI device. 9 | #[derive(Debug, Eq, PartialEq)] 10 | pub struct AdiPwmOut { 11 | port: AdiPort, 12 | } 13 | 14 | impl AdiPwmOut { 15 | /// Create a pwm output from an [`AdiPort`]. 16 | pub fn new(port: AdiPort) -> Result { 17 | bail_on!(PROS_ERR, unsafe { 18 | pros_sys::ext_adi_port_set_config( 19 | port.internal_expander_index(), 20 | port.index(), 21 | pros_sys::E_ADI_ANALOG_OUT, 22 | ) 23 | }); 24 | 25 | Ok(Self { port }) 26 | } 27 | 28 | /// Sets the PWM output width. 29 | /// 30 | /// This value is sent over 16ms periods with pulse widths ranging from roughly 31 | /// 0.94mS to 2.03mS. 32 | pub fn set_output(&mut self, value: u8) -> Result<(), AdiError> { 33 | bail_on!(PROS_ERR, unsafe { 34 | pros_sys::ext_adi_port_set_value( 35 | self.port.internal_expander_index(), 36 | self.port.index(), 37 | value as i32, 38 | ) 39 | }); 40 | 41 | Ok(()) 42 | } 43 | } 44 | 45 | impl AdiDevice for AdiPwmOut { 46 | type PortIndexOutput = u8; 47 | 48 | fn port_index(&self) -> Self::PortIndexOutput { 49 | self.port.index() 50 | } 51 | 52 | fn expander_port_index(&self) -> Option { 53 | self.port.expander_index() 54 | } 55 | 56 | fn device_type(&self) -> AdiDeviceType { 57 | AdiDeviceType::PwmOut 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | # General 17 | .DS_Store 18 | .AppleDouble 19 | .LSOverride 20 | 21 | # Icon must end with two \r 22 | Icon 23 | 24 | 25 | # Thumbnails 26 | ._* 27 | 28 | # Files that might appear in the root of a volume 29 | .DocumentRevisions-V100 30 | .fseventsd 31 | .Spotlight-V100 32 | .TemporaryItems 33 | .Trashes 34 | .VolumeIcon.icns 35 | .com.apple.timemachine.donotpresent 36 | 37 | # Directories potentially created on remote AFP share 38 | .AppleDB 39 | .AppleDesktop 40 | Network Trash Folder 41 | Temporary Items 42 | .apdisk 43 | 44 | *~ 45 | 46 | # temporary files which can be created if a process still has a handle open of a deleted file 47 | .fuse_hidden* 48 | 49 | # KDE directory preferences 50 | .directory 51 | 52 | # Linux trash folder which might appear on any partition or disk 53 | .Trash-* 54 | 55 | # .nfs files are created when an open file is removed but is still being accessed 56 | .nfs* 57 | 58 | # Windows thumbnail cache files 59 | Thumbs.db 60 | Thumbs.db:encryptable 61 | ehthumbs.db 62 | ehthumbs_vista.db 63 | 64 | # Dump file 65 | *.stackdump 66 | 67 | # Folder config file 68 | [Dd]esktop.ini 69 | 70 | # Recycle Bin used on file shares 71 | $RECYCLE.BIN/ 72 | 73 | # Windows Installer files 74 | *.cab 75 | *.msi 76 | *.msix 77 | *.msm 78 | *.msp 79 | 80 | # Windows shortcuts 81 | *.lnk 82 | 83 | # Direnv 84 | /.direnv 85 | 86 | # RustRover/IDEA support 87 | .idea -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/ultrasonic.rs: -------------------------------------------------------------------------------- 1 | //! ADI ultrasonic sensor. 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::{ext_adi_ultrasonic_t, PROS_ERR}; 5 | 6 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 7 | 8 | #[derive(Debug, Eq, PartialEq)] 9 | /// Adi ultrasonic sensor. 10 | /// Requires two ports one for pinging, and one for listening for the response. 11 | pub struct AdiUltrasonic { 12 | raw: ext_adi_ultrasonic_t, 13 | port_ping: AdiPort, 14 | port_echo: AdiPort, 15 | } 16 | 17 | impl AdiUltrasonic { 18 | /// Create a new ultrasonic sensor from a ping and echo [`AdiPort`]. 19 | pub fn new(ports: (AdiPort, AdiPort)) -> Result { 20 | let port_ping = ports.0; 21 | let port_echo = ports.1; 22 | 23 | if port_ping.internal_expander_index() != port_echo.internal_expander_index() { 24 | return Err(AdiError::ExpanderPortMismatch); 25 | } 26 | 27 | let raw = bail_on!(PROS_ERR, unsafe { 28 | pros_sys::ext_adi_ultrasonic_init( 29 | port_ping.internal_expander_index(), 30 | port_ping.index(), 31 | port_echo.index(), 32 | ) 33 | }); 34 | 35 | Ok(Self { 36 | raw, 37 | port_ping, 38 | port_echo, 39 | }) 40 | } 41 | 42 | /// Get the distance reading of the ultrasonic sensor in centimeters. 43 | /// 44 | /// Round and/or fluffy objects can cause inaccurate values to be returned. 45 | pub fn distance(&self) -> Result { 46 | Ok(bail_on!(PROS_ERR, unsafe { 47 | pros_sys::ext_adi_ultrasonic_get(self.raw) 48 | }) as u16) 49 | } 50 | } 51 | 52 | impl AdiDevice for AdiUltrasonic { 53 | type PortIndexOutput = (u8, u8); 54 | 55 | fn port_index(&self) -> Self::PortIndexOutput { 56 | (self.port_ping.index(), self.port_echo.index()) 57 | } 58 | 59 | fn expander_port_index(&self) -> Option { 60 | self.port_ping.expander_index() 61 | } 62 | 63 | fn device_type(&self) -> AdiDeviceType { 64 | AdiDeviceType::LegacyUltrasonic 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/gyro.rs: -------------------------------------------------------------------------------- 1 | //! ADI gyro device. 2 | 3 | use core::time::Duration; 4 | 5 | use pros_core::bail_on; 6 | use pros_sys::{ext_adi_gyro_t, PROS_ERR, PROS_ERR_F}; 7 | 8 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 9 | 10 | /// ADI gyro device. 11 | #[derive(Debug, Eq, PartialEq)] 12 | pub struct AdiGyro { 13 | raw: ext_adi_gyro_t, 14 | port: AdiPort, 15 | } 16 | 17 | impl AdiGyro { 18 | /// The time it takes to calibrate an [`AdiGyro`]. 19 | /// 20 | /// The theoretical calibration time is 1024ms, but in practice this seemed to be the 21 | /// actual time that it takes. 22 | pub const CALIBRATION_TIME: Duration = Duration::from_millis(1300); 23 | 24 | /// Create a new gyro from an [`AdiPort`]. 25 | /// 26 | /// If the given port has not previously been configured as a gyro, then this 27 | /// function blocks for a 1300ms calibration period. 28 | pub fn new(port: AdiPort, multiplier: f64) -> Result { 29 | let raw = bail_on!(PROS_ERR, unsafe { 30 | pros_sys::ext_adi_gyro_init(port.internal_expander_index(), port.index(), multiplier) 31 | }); 32 | 33 | Ok(Self { raw, port }) 34 | } 35 | 36 | /// Gets the yaw angle of the gyroscope in degrees. 37 | /// 38 | /// Unless a multiplier is applied to the gyro, the return value will be a whole 39 | /// number representing the number of degrees of rotation. 40 | pub fn angle(&self) -> Result { 41 | Ok(bail_on!(PROS_ERR_F, unsafe { pros_sys::ext_adi_gyro_get(self.raw) }) / 10.0) 42 | } 43 | 44 | /// Reset the current gyro angle to zero degrees. 45 | pub fn zero(&mut self) -> Result<(), AdiError> { 46 | bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_gyro_reset(self.raw) }); 47 | Ok(()) 48 | } 49 | } 50 | 51 | impl AdiDevice for AdiGyro { 52 | type PortIndexOutput = u8; 53 | 54 | fn port_index(&self) -> Self::PortIndexOutput { 55 | self.port.index() 56 | } 57 | 58 | fn expander_port_index(&self) -> Option { 59 | self.port.expander_index() 60 | } 61 | 62 | fn device_type(&self) -> AdiDeviceType { 63 | AdiDeviceType::LegacyGyro 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/pros-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | #![allow(dead_code)] 6 | 7 | pub mod adi; 8 | #[cfg(feature = "xapi")] 9 | pub mod apix; 10 | pub mod colors; 11 | pub mod distance; 12 | pub mod error; 13 | pub mod ext_adi; 14 | pub mod gps; 15 | pub mod imu; 16 | pub mod link; 17 | pub mod misc; 18 | pub mod motor; 19 | pub mod optical; 20 | pub mod rotation; 21 | pub mod rtos; 22 | pub mod screen; 23 | pub mod vision; 24 | 25 | use core::ffi::{c_char, c_int, c_void}; 26 | 27 | pub use adi::*; 28 | pub use colors::*; 29 | pub use distance::*; 30 | pub use error::*; 31 | pub use ext_adi::*; 32 | pub use gps::*; 33 | pub use imu::*; 34 | pub use link::*; 35 | pub use misc::*; 36 | pub use motor::*; 37 | pub use optical::*; 38 | pub use rotation::*; 39 | pub use rtos::*; 40 | pub use screen::*; 41 | #[cfg(feaute = "apix")] 42 | pub use serial::*; 43 | pub use vision::*; 44 | #[cfg(feaute = "apix")] 45 | pub mod serial; 46 | 47 | pub const CLOCKS_PER_SEC: u32 = 1000; 48 | 49 | extern "C" { 50 | #[cfg(not(target_arch = "wasm32"))] 51 | pub fn memalign(alignment: usize, size: usize) -> *mut c_void; 52 | #[cfg(not(target_arch = "wasm32"))] 53 | pub fn free(ptr: *mut c_void); 54 | pub fn __errno() -> *mut i32; 55 | pub fn clock() -> i32; 56 | pub fn puts(s: *const c_char) -> i32; 57 | pub fn exit(code: i32) -> !; 58 | pub fn write(fd: c_int, buf: *const c_void, count: usize) -> isize; 59 | 60 | fn initialize(); 61 | fn opcontrol(); 62 | fn autonomous(); 63 | fn disabled(); 64 | fn competition_initialize(); 65 | } 66 | 67 | #[no_mangle] 68 | unsafe extern "C" fn cpp_opcontrol() { 69 | opcontrol(); 70 | } 71 | #[no_mangle] 72 | unsafe extern "C" fn cpp_autonomous() { 73 | autonomous(); 74 | } 75 | #[no_mangle] 76 | unsafe extern "C" fn cpp_disabled() { 77 | disabled(); 78 | } 79 | #[no_mangle] 80 | unsafe extern "C" fn cpp_competition_initialize() { 81 | competition_initialize(); 82 | } 83 | #[no_mangle] 84 | unsafe extern "C" fn cpp_initialize() { 85 | initialize(); 86 | } 87 | #[no_mangle] 88 | unsafe extern "C" fn task_fn_wrapper(function: task_fn_t, args: *mut c_void) { 89 | function.unwrap()(args); 90 | } 91 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/encoder.rs: -------------------------------------------------------------------------------- 1 | //! ADI encoder device. 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::{ext_adi_encoder_t, PROS_ERR}; 5 | 6 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 7 | use crate::Position; 8 | 9 | /// ADI encoder device. 10 | /// Requires two adi ports. 11 | #[derive(Debug, Eq, PartialEq)] 12 | pub struct AdiEncoder { 13 | raw: ext_adi_encoder_t, 14 | port_top: AdiPort, 15 | port_bottom: AdiPort, 16 | } 17 | 18 | impl AdiEncoder { 19 | /// Create a new encoder from a top and bottom [`AdiPort`]. 20 | /// 21 | /// If using an [`AdiExpander`], both ports must be on the same expander module. 22 | pub fn new(ports: (AdiPort, AdiPort), reverse: bool) -> Result { 23 | let port_top = ports.0; 24 | let port_bottom = ports.1; 25 | 26 | if port_top.internal_expander_index() != port_bottom.internal_expander_index() { 27 | return Err(AdiError::ExpanderPortMismatch); 28 | } 29 | 30 | let raw = bail_on!(PROS_ERR, unsafe { 31 | pros_sys::ext_adi_encoder_init( 32 | port_top.internal_expander_index(), 33 | port_top.index(), 34 | port_bottom.index(), 35 | reverse, 36 | ) 37 | }); 38 | 39 | Ok(Self { 40 | raw, 41 | port_top, 42 | port_bottom, 43 | }) 44 | } 45 | 46 | /// Resets the encoder to zero. 47 | pub fn zero(&mut self) -> Result<(), AdiError> { 48 | bail_on!(PROS_ERR, unsafe { pros_sys::adi_encoder_reset(self.raw) }); 49 | Ok(()) 50 | } 51 | 52 | /// Gets the number of ticks recorded by the encoder. 53 | pub fn position(&self) -> Result { 54 | let degrees = bail_on!(PROS_ERR, unsafe { pros_sys::adi_encoder_get(self.raw) }); 55 | 56 | Ok(Position::from_degrees(degrees as f64)) 57 | } 58 | } 59 | 60 | impl AdiDevice for AdiEncoder { 61 | type PortIndexOutput = (u8, u8); 62 | 63 | fn port_index(&self) -> Self::PortIndexOutput { 64 | (self.port_top.index(), self.port_bottom.index()) 65 | } 66 | 67 | fn expander_port_index(&self) -> Option { 68 | self.port_top.expander_index() 69 | } 70 | 71 | fn device_type(&self) -> AdiDeviceType { 72 | AdiDeviceType::LegacyEncoder 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/motor.rs: -------------------------------------------------------------------------------- 1 | //! ADI motor device. 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::PROS_ERR; 5 | 6 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 7 | 8 | #[derive(Debug, Eq, PartialEq)] 9 | /// Cortex era motor device. 10 | pub struct AdiMotor { 11 | port: AdiPort, 12 | } 13 | 14 | impl AdiMotor { 15 | /// Create a new motor from an [`AdiPort`]. 16 | pub const fn new(port: AdiPort) -> Self { 17 | Self { port } 18 | } 19 | 20 | /// Sets the PWM output of the given motor as an f32 from [-1.0, 1.0]. 21 | pub fn set_output(&mut self, value: f32) -> Result<(), AdiError> { 22 | self.set_raw_output((value * 127.0) as i8) 23 | } 24 | 25 | /// Sets the PWM output of the given motor as an i8 from [-127, 127]. 26 | pub fn set_raw_output(&mut self, value: i8) -> Result<(), AdiError> { 27 | bail_on!(PROS_ERR, unsafe { 28 | pros_sys::ext_adi_motor_set( 29 | self.port.internal_expander_index(), 30 | self.port.index(), 31 | value, 32 | ) 33 | }); 34 | 35 | Ok(()) 36 | } 37 | 38 | /// Returns the last set PWM output of the motor on the given port as an f32 from [-1.0, 1.0]. 39 | pub fn output(&self) -> Result { 40 | Ok(self.raw_output()? as f32 / 127.0) 41 | } 42 | 43 | /// Returns the last set PWM output of the motor on the given port as an i8 from [-127, 127]. 44 | pub fn raw_output(&self) -> Result { 45 | Ok(bail_on!(PROS_ERR, unsafe { 46 | pros_sys::ext_adi_motor_get(self.port.internal_expander_index(), self.port.index()) 47 | }) as i8) 48 | } 49 | 50 | /// Stops the given motor. 51 | pub fn stop(&mut self) -> Result<(), AdiError> { 52 | bail_on!(PROS_ERR, unsafe { 53 | pros_sys::ext_adi_motor_stop(self.port.internal_expander_index(), self.port.index()) 54 | }); 55 | 56 | Ok(()) 57 | } 58 | } 59 | 60 | impl AdiDevice for AdiMotor { 61 | type PortIndexOutput = u8; 62 | 63 | fn port_index(&self) -> Self::PortIndexOutput { 64 | self.port.index() 65 | } 66 | 67 | fn expander_port_index(&self) -> Option { 68 | self.port.expander_index() 69 | } 70 | 71 | fn device_type(&self) -> AdiDeviceType { 72 | AdiDeviceType::LegacyPwm 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/pros-math/src/pid.rs: -------------------------------------------------------------------------------- 1 | //! PID controllers. 2 | //! 3 | //! PID controllers are first created with [`PidController::new`] 4 | //! and then can be utilized by calling [`PidController::update`] repeatedly. 5 | 6 | use core::time::Duration; 7 | 8 | /// A proportional–integral–derivative controller. 9 | /// 10 | /// This controller is used to smoothly move motors to a certain point, 11 | /// and allows for feedback-based power adjustments. This is desirable 12 | /// over just setting the motor power, as it can be tuned to make the 13 | /// motor stop in exactly the right position without overshooting. 14 | #[derive(Debug, Clone, Copy)] 15 | pub struct PidController { 16 | /// Proportional constant. This is multiplied by the error to get the 17 | /// proportional component of the output. 18 | pub kp: f32, 19 | /// Integral constant. This accounts for the past values of the error. 20 | pub ki: f32, 21 | /// Derivative constant. This allows you to change the motor behavior 22 | /// based on the rate of change of the error (predicting future values). 23 | pub kd: f32, 24 | 25 | last_time: pros_core::time::Instant, 26 | last_position: f32, 27 | i: f32, 28 | } 29 | 30 | impl PidController { 31 | /// Create a new PID controller with the given constants. 32 | pub fn new(kp: f32, ki: f32, kd: f32) -> Self { 33 | Self { 34 | kp, 35 | ki, 36 | kd, 37 | last_time: pros_core::time::Instant::now(), 38 | last_position: 0.0, 39 | i: 0.0, 40 | } 41 | } 42 | 43 | /// Update the PID controller with the current setpoint and position. 44 | pub fn update(&mut self, setpoint: f32, position: f32) -> f32 { 45 | let mut delta_time = self.last_time.elapsed(); 46 | if delta_time.is_zero() { 47 | delta_time += Duration::from_micros(1); 48 | } 49 | let error = setpoint - position; 50 | 51 | self.i += error * delta_time.as_secs_f32(); 52 | 53 | let p = self.kp * error; 54 | let i = self.ki * self.i; 55 | 56 | let mut d = (position - self.last_position) / delta_time.as_secs_f32(); 57 | if d.is_nan() { 58 | d = 0.0 59 | } 60 | 61 | let output = p + i + d; 62 | 63 | self.last_position = position; 64 | self.last_time = pros_core::time::Instant::now(); 65 | 66 | output 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/pros-math/src/feedforward.rs: -------------------------------------------------------------------------------- 1 | //! Simple feedforward controller for motors. 2 | //! Computes the voltage to maintain an idealized DC motor in a certain state. 3 | //! Uses this feedforward model: V = Kₛ sign(ω) + Kᵥ ω + Kₐ α 4 | 5 | /// Feedforward controller for motor control. 6 | /// 7 | /// This controller is used to apply feedforward control to achieve desired motor behavior 8 | /// based on velocity and acceleration. 9 | #[derive(Debug, Clone)] 10 | pub struct MotorFeedforwardController { 11 | /// Feedforward constant for static friction compensation. 12 | pub ks: f32, 13 | /// Feedforward constant for velocity compensation. 14 | pub kv: f32, 15 | /// Feedforward constant for acceleration compensation. 16 | pub ka: f32, 17 | /// Feedforward constant for the target acceleration. 18 | pub target_acceleration: f32, 19 | /// Target. 20 | pub target: f32, 21 | } 22 | 23 | impl MotorFeedforwardController { 24 | /// Creates a new [`FeedforwardMotorController`] with the given constants and target. 25 | /// 26 | /// # Arguments 27 | /// 28 | /// * `ks` - Feedforward constant for static friction compensation. 29 | /// * `kv` - Feedforward constant for velocity compensation. 30 | /// * `ka` - Feedforward constant for acceleration compensation. 31 | /// * `target_acceleration` - Feedforward constant for the target acceleration. 32 | /// 33 | /// # Returns 34 | /// 35 | /// A new [`FeedforwardMotorController`]. 36 | pub fn new(ks: f32, kv: f32, ka: f32, target_acceleration: f32) -> Self { 37 | Self { 38 | ks, 39 | kv, 40 | ka, 41 | target_acceleration, 42 | target: 0.0, 43 | } 44 | } 45 | 46 | /// Calculates the control output. 47 | /// 48 | /// # Arguments 49 | /// 50 | /// * `target_acceleration` - The target_acceleration of the system. 51 | /// * `target` - Target. 52 | /// 53 | /// # Returns 54 | /// 55 | /// The control output to apply to the motor. 56 | pub fn calculate(&self, target: f32, target_acceleration: f32) -> f32 { 57 | // Calculate the feedforward component based on velocity and acceleration 58 | let v = self.ks * num::signum(target) + self.kv * target + self.ka * target_acceleration; 59 | 60 | // The output is the feedforward controller (V) 61 | let output = v; 62 | 63 | output 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/pros-devices/src/smart/expander.rs: -------------------------------------------------------------------------------- 1 | //! ADI expander module support. 2 | //! 3 | //! The ADI expander API is similar to that of [`Peripherals`]. 4 | //! A main difference between the two is that ADI expanders can be created safely without returning an option. 5 | //! This is because they require a [`SmartPort`] to be created which can only be created without either peripherals struct unsafely. 6 | 7 | use super::{SmartDevice, SmartDeviceType, SmartPort}; 8 | use crate::adi::AdiPort; 9 | 10 | /// Represents an ADI expander module plugged into a smart port. 11 | /// 12 | /// ADI Expanders allow a smart port to be used as an "adapter" for eight additional ADI slots 13 | /// if all onboard [`AdiPort`]s are used. 14 | /// 15 | /// This struct gives access to [`AdiPort`]s similarly to how [`Peripherals`] works. Ports may 16 | /// be partially moved out of this struct to create devices. 17 | #[derive(Debug, Eq, PartialEq)] 18 | pub struct AdiExpander { 19 | /// ADI port A on the expander. 20 | pub adi_a: AdiPort, 21 | /// ADI port B on the expander. 22 | pub adi_b: AdiPort, 23 | /// ADI Port C on the expander. 24 | pub adi_c: AdiPort, 25 | /// ADI Port D on the expander. 26 | pub adi_d: AdiPort, 27 | /// ADI Port E on the expander. 28 | pub adi_e: AdiPort, 29 | /// ADI Port F on the expander. 30 | pub adi_f: AdiPort, 31 | /// ADI Port G on the expander. 32 | pub adi_g: AdiPort, 33 | /// ADI Port H on the expander. 34 | pub adi_h: AdiPort, 35 | 36 | port: SmartPort, 37 | } 38 | 39 | impl AdiExpander { 40 | /// Create a new expander from a smart port index. 41 | pub fn new(port: SmartPort) -> Self { 42 | unsafe { 43 | Self { 44 | adi_a: AdiPort::new(1, Some(port.index())), 45 | adi_b: AdiPort::new(2, Some(port.index())), 46 | adi_c: AdiPort::new(3, Some(port.index())), 47 | adi_d: AdiPort::new(4, Some(port.index())), 48 | adi_e: AdiPort::new(5, Some(port.index())), 49 | adi_f: AdiPort::new(6, Some(port.index())), 50 | adi_g: AdiPort::new(7, Some(port.index())), 51 | adi_h: AdiPort::new(8, Some(port.index())), 52 | port, 53 | } 54 | } 55 | } 56 | } 57 | 58 | impl SmartDevice for AdiExpander { 59 | fn port_index(&self) -> u8 { 60 | self.port.index() 61 | } 62 | 63 | fn device_type(&self) -> SmartDeviceType { 64 | SmartDeviceType::Adi 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pros-rs 2 | 3 | Opinionated Rust bindings for the [PROS](https://github.com/purduesigbots/pros) library and kernel. 4 | 5 | > [!IMPORTANT] 6 | > 7 | > This project is not currently in development. If this looks interesting to you, check out [`vexide`](https://github.com/vexide/vexide) instead! 8 | 9 | ## Usage 10 | 11 | ## Compiling 12 | 13 | The only dependency of pros-rs outside of Rust is The Arm Gnu Toolchain (arm-none-eabi-gcc). 14 | 15 | Read the installation guide for your OS to see how to get things set up. 16 | 17 | ### Windows 18 | Steps: 19 | 1. Run The Arm Gnu Toolchain [here](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads) 20 | 2. Install the pros cli, instructions are [here](https://pros.cs.purdue.edu/v5/getting-started/windows.html) 21 | 3. Install cargo pros with ``cargo install cargo-pros`` 22 | 23 | To compile the project just run ``cargo pros build``. 24 | 25 | ### Linux 26 | 27 | The steps for getting pros-rs compiling are slightly different based on if you use Nix or not. 28 | 29 | #### With Nix 30 | 31 | The Nix flake contains the Arm GNU Toolchain, cargo pros, and pros-cli. 32 | 33 | There is a ``.envrc`` file included for Nix + Direnv users. 34 | 35 | #### Without Nix 36 | 37 | Install arm-none-eabi-gcc and pros-cli from your package manager of choice. 38 | Cargo pros can be installed with ``cargo install cargo-pros``. 39 | 40 | ### MacOS 41 | 42 | This project depends on the Xcode Command Line Tools. 43 | Chances are that if you develop on MacOS you have them already, but if not you can install them with `xcode-select --install`. 44 | 45 | Most of the other dependencies can easily be installed with Homebrew. 46 | 47 | Install the Arm GNU Toolchain with 48 | `brew install osx-cross/arm/arm-gcc-bin`. 49 | 50 | Install pros-cli with 51 | `brew install purduesigbots/pros/pros-cli`. 52 | 53 | And you are done! Compile the project with `cargo build`. 54 | 55 | ## Compiling for WASM 56 | 57 | To build projects in this repository for WebAssembly, run ``cargo pros build -s`` 58 | This will automatically pass all of the correct arguments to cargo. 59 | 60 | If for some reason you want to do it manually, this is the command: 61 | `cargo build --target wasm32-unknown-unknown -Zbuild-std=std,panic_abort`. 62 | 63 | The extra build-std argument is neccesary because this repository's `.cargo/config.toml` enables build-std but only for core, alloc, and compiler_builtins. WebAssembly does come with `std` but there is [currently](https://github.com/rust-lang/cargo/issues/8733) no way to conditionally enable build-std. 64 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/potentiometer.rs: -------------------------------------------------------------------------------- 1 | //! ADI Potentiometer device. 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::{adi_potentiometer_type_e_t, ext_adi_potentiometer_t, PROS_ERR, PROS_ERR_F}; 5 | 6 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 7 | 8 | #[derive(Debug, Eq, PartialEq)] 9 | /// Analog potentiometer ADI device. 10 | pub struct AdiPotentiometer { 11 | potentiometer_type: AdiPotentiometerType, 12 | raw: ext_adi_potentiometer_t, 13 | port: AdiPort, 14 | } 15 | 16 | impl AdiPotentiometer { 17 | /// Create a new potentiometer from an [`AdiPort`]. 18 | pub fn new(port: AdiPort, potentiometer_type: AdiPotentiometerType) -> Result { 19 | let raw = bail_on!(PROS_ERR, unsafe { 20 | pros_sys::ext_adi_potentiometer_init( 21 | port.internal_expander_index(), 22 | port.index(), 23 | potentiometer_type.into(), 24 | ) 25 | }); 26 | 27 | Ok(Self { 28 | potentiometer_type, 29 | raw, 30 | port, 31 | }) 32 | } 33 | 34 | /// Get the type of ADI potentiometer device. 35 | pub const fn potentiometer_type(&self) -> AdiPotentiometerType { 36 | self.potentiometer_type 37 | } 38 | 39 | /// Gets the current potentiometer angle in degrees. 40 | /// 41 | /// The original potentiometer rotates 250 degrees 42 | /// thus returning an angle between 0-250 degrees. 43 | /// Potentiometer V2 rotates 330 degrees 44 | /// thus returning an angle between 0-330 degrees. 45 | pub fn angle(&self) -> Result { 46 | Ok(bail_on!(PROS_ERR_F, unsafe { 47 | pros_sys::ext_adi_potentiometer_get_angle(self.raw) 48 | }) / 10.0) 49 | } 50 | } 51 | 52 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 53 | #[repr(i32)] 54 | /// The type of potentiometer device. 55 | pub enum AdiPotentiometerType { 56 | /// EDR potentiometer. 57 | PotentiometerEdr = pros_sys::E_ADI_POT_EDR, 58 | /// V2 potentiometer. 59 | PotentiometerV2 = pros_sys::E_ADI_POT_V2, 60 | } 61 | 62 | impl From for adi_potentiometer_type_e_t { 63 | fn from(value: AdiPotentiometerType) -> Self { 64 | value as _ 65 | } 66 | } 67 | 68 | impl AdiDevice for AdiPotentiometer { 69 | type PortIndexOutput = u8; 70 | 71 | fn port_index(&self) -> Self::PortIndexOutput { 72 | self.port.index() 73 | } 74 | 75 | fn expander_port_index(&self) -> Option { 76 | self.port.expander_index() 77 | } 78 | 79 | fn device_type(&self) -> AdiDeviceType { 80 | AdiDeviceType::AnalogIn 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/pros-sys/src/distance.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::*; 2 | 3 | extern "C" { 4 | /** Get the currently measured distance from the sensor in mm 5 | 6 | This function uses the following values of errno when an error state is 7 | reached: 8 | ENXIO - The given value is not within the range of V5 ports (1-21). 9 | ENODEV - The port cannot be configured as an Distance Sensor 10 | 11 | \param port The V5 Distance Sensor port number from 1-21 12 | \return The distance value or PROS_ERR if the operation failed, setting 13 | errno.*/ 14 | pub fn distance_get(port: u8) -> i32; 15 | /** Get the confidence in the distance reading 16 | 17 | This is a value that has a range of 0 to 63. 63 means high confidence, 18 | lower values imply less confidence. Confidence is only available 19 | when distance is > 200mm (the value 10 is returned in this scenario). 20 | 21 | This function uses the following values of errno when an error state is 22 | reached: 23 | ENXIO - The given value is not within the range of V5 ports (1-21). 24 | ENODEV - The port cannot be configured as an Distance Sensor 25 | 26 | \param port The V5 Distance Sensor port number from 1-21 27 | \return The confidence value or PROS_ERR if the operation failed, setting 28 | errno.*/ 29 | pub fn distance_get_confidence(port: u8) -> i32; 30 | /** Get the current guess at relative object size 31 | 32 | This is a value that has a range of 0 to 400. 33 | A 18" x 30" grey card will return a value of approximately 75 34 | in typical room lighting. 35 | 36 | This function uses the following values of errno when an error state is 37 | reached: 38 | ENXIO - The given value is not within the range of V5 ports (1-21). 39 | ENODEV - The port cannot be configured as an Distance Sensor 40 | 41 | \param port The V5 Distance Sensor port number from 1-21 42 | \return The size value or PROS_ERR if the operation failed, setting 43 | errno.*/ 44 | pub fn distance_get_object_size(port: u8) -> i32; 45 | /** Get the current guess at relative object size 46 | 47 | This is a value that has a range of 0 to 400. 48 | A 18" x 30" grey card will return a value of approximately 75 49 | in typical room lighting. 50 | 51 | This function uses the following values of errno when an error state is 52 | reached: 53 | ENXIO - The given value is not within the range of V5 ports (1-21). 54 | ENODEV - The port cannot be configured as an Distance Sensor 55 | 56 | \param port The V5 Distance Sensor port number from 1-21 57 | \return The size value or PROS_ERR if the operation failed, setting 58 | errno.*/ 59 | pub fn distance_get_object_velocity(port: u8) -> c_double; 60 | } 61 | -------------------------------------------------------------------------------- /packages/pros-async/src/executor.rs: -------------------------------------------------------------------------------- 1 | use alloc::{collections::VecDeque, sync::Arc}; 2 | use core::{ 3 | cell::RefCell, 4 | future::Future, 5 | pin::Pin, 6 | sync::atomic::{AtomicBool, Ordering}, 7 | task::{Context, Poll}, 8 | time::Duration, 9 | }; 10 | 11 | use async_task::{Runnable, Task}; 12 | use pros_core::{os_task_local, task::delay}; 13 | use waker_fn::waker_fn; 14 | 15 | use super::reactor::Reactor; 16 | 17 | os_task_local! { 18 | pub(crate) static EXECUTOR: Executor = Executor::new(); 19 | } 20 | 21 | pub(crate) struct Executor { 22 | queue: RefCell>, 23 | pub(crate) reactor: RefCell, 24 | } 25 | 26 | impl !Send for Executor {} 27 | impl !Sync for Executor {} 28 | 29 | impl Executor { 30 | pub const fn new() -> Self { 31 | Self { 32 | queue: RefCell::new(VecDeque::new()), 33 | reactor: RefCell::new(Reactor::new()), 34 | } 35 | } 36 | 37 | pub fn spawn(&'static self, future: impl Future + 'static) -> Task { 38 | // SAFETY: `runnable` will never be moved off this thread or shared with another thread because of the `!Send + !Sync` bounds on `Self`. 39 | // Both `future` and `schedule` are `'static` so they cannot be used after being freed. 40 | // TODO: Make sure that the waker can never be sent off the thread. 41 | let (runnable, task) = unsafe { 42 | async_task::spawn_unchecked(future, |runnable| { 43 | self.queue.borrow_mut().push_back(runnable) 44 | }) 45 | }; 46 | 47 | runnable.schedule(); 48 | 49 | task 50 | } 51 | 52 | pub(crate) fn tick(&self) -> bool { 53 | self.reactor.borrow_mut().tick(); 54 | 55 | let runnable = { 56 | let mut queue = self.queue.borrow_mut(); 57 | queue.pop_front() 58 | }; 59 | match runnable { 60 | Some(runnable) => { 61 | runnable.run(); 62 | true 63 | } 64 | None => false, 65 | } 66 | } 67 | 68 | pub fn block_on(&self, mut task: Task) -> R { 69 | let woken = Arc::new(AtomicBool::new(true)); 70 | 71 | let waker = waker_fn({ 72 | let woken = woken.clone(); 73 | move || woken.store(true, Ordering::Relaxed) 74 | }); 75 | let mut cx = Context::from_waker(&waker); 76 | 77 | loop { 78 | if woken.swap(false, Ordering::Relaxed) { 79 | if let Poll::Ready(output) = Pin::new(&mut task).poll(&mut cx) { 80 | return output; 81 | } 82 | self.tick(); 83 | // there might be another future to poll, so we continue without sleeping 84 | continue; 85 | } 86 | 87 | delay(Duration::from_millis(10)); 88 | self.tick(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/pros-devices/src/smart/distance.rs: -------------------------------------------------------------------------------- 1 | //! Distance sensor device. 2 | //! 3 | //! Pretty much one to one with the PROS C and CPP API, except Result is used instead of ERRNO values. 4 | 5 | use core::ffi::c_double; 6 | 7 | use pros_core::{bail_on, error::PortError}; 8 | use pros_sys::PROS_ERR; 9 | 10 | use super::{SmartDevice, SmartDeviceType, SmartPort}; 11 | 12 | /// A physical distance sensor plugged into a port. 13 | /// Distance sensors can only keep track of one object at a time. 14 | #[derive(Debug, Eq, PartialEq)] 15 | pub struct DistanceSensor { 16 | port: SmartPort, 17 | } 18 | 19 | impl DistanceSensor { 20 | /// Create a new distance sensor from a smart port index. 21 | pub const fn new(port: SmartPort) -> Self { 22 | Self { port } 23 | } 24 | 25 | /// Returns the distance to the object the sensor detects in millimeters. 26 | pub fn distance(&self) -> Result { 27 | Ok(bail_on!(PROS_ERR, unsafe { 28 | pros_sys::distance_get(self.port.index()) 29 | }) as u32) 30 | } 31 | 32 | /// Returns the velocity of the object the sensor detects in m/s 33 | pub fn velocity(&self) -> Result { 34 | // All VEX Distance Sensor functions return PROS_ERR on failure even though 35 | // some return floating point values (not PROS_ERR_F) 36 | Ok(bail_on!(PROS_ERR as c_double, unsafe { 37 | pros_sys::distance_get_object_velocity(self.port.index()) 38 | })) 39 | } 40 | 41 | /// Get the current guess at relative "object size". 42 | /// 43 | /// This is a value that has a range of 0 to 400. A 18" x 30" grey card will return 44 | /// a value of approximately 75 in typical room lighting. 45 | /// 46 | /// This sensor reading is unusual, as it is entirely unitless with the seemingly arbitrary 47 | /// range of 0-400 existing due to VEXCode's [`vex::sizeType`] enum having four variants. It's 48 | /// unknown what the sensor is *actually* measuring here either, so use this data with a grain 49 | /// of salt. 50 | /// 51 | /// [`vex::sizeType`]: https://api.vexcode.cloud/v5/search/sizeType/sizeType/enum 52 | pub fn relative_size(&self) -> Result { 53 | Ok(bail_on!(PROS_ERR, unsafe { 54 | pros_sys::distance_get_object_size(self.port.index()) 55 | }) as u32) 56 | } 57 | 58 | /// Returns the confidence in the distance measurement from 0.0 to 1.0. 59 | pub fn distance_confidence(&self) -> Result { 60 | // 0 -> 63 61 | let confidence = bail_on!(PROS_ERR, unsafe { 62 | pros_sys::distance_get_confidence(self.port.index()) 63 | }) as f64; 64 | 65 | Ok(confidence / 63.0) 66 | } 67 | } 68 | 69 | impl SmartDevice for DistanceSensor { 70 | fn port_index(&self) -> u8 { 71 | self.port.index() 72 | } 73 | 74 | fn device_type(&self) -> SmartDeviceType { 75 | SmartDeviceType::Distance 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/pros/examples/accessories.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | 6 | use alloc::sync::Arc; 7 | use core::time::Duration; 8 | 9 | use pros::{ 10 | core::sync::Mutex, 11 | devices::{ 12 | smart::vision::{LedMode, VisionZeroPoint}, 13 | Controller, 14 | }, 15 | prelude::*, 16 | }; 17 | 18 | struct ExampleRobot { 19 | motor: Arc>, 20 | vision: VisionSensor, 21 | } 22 | impl ExampleRobot { 23 | pub fn new(peripherals: Peripherals) -> Self { 24 | Self { 25 | motor: Arc::new(Mutex::new( 26 | Motor::new(peripherals.port_2, Gearset::Green, Direction::Forward).unwrap(), 27 | )), 28 | vision: VisionSensor::new(peripherals.port_9, VisionZeroPoint::Center).unwrap(), 29 | } 30 | } 31 | } 32 | 33 | impl AsyncRobot for ExampleRobot { 34 | async fn opcontrol(&mut self) -> Result { 35 | let handle = pros::async_runtime::spawn(async { 36 | for _ in 0..5 { 37 | println!("Hello from async!"); 38 | sleep(Duration::from_millis(1000)).await; 39 | } 40 | }); 41 | 42 | handle.await; 43 | 44 | // Create a controller, specifically controller 1. 45 | let controller = Controller::Master; 46 | 47 | self.vision.set_led(LedMode::On(Rgb::new(0, 0, 255))); 48 | 49 | // Spawn a new task that will print whether or not the motor is stopped constantly. 50 | pros_core::task::spawn({ 51 | let motor = Arc::clone(&self.motor); // Obtain a shared reference to our motor to safely share between tasks. 52 | 53 | move || loop { 54 | println!("Motor stopped? {}", motor.lock().velocity() < 2); 55 | 56 | // Sleep the task as to not steal processing time from the OS. 57 | // This should always be done in any loop, including loops in the main task. 58 | // Because this is a real FreeRTOS task this is not the sleep function used elsewhere in this example. 59 | // This sleep function will block the entire task, including the async executor! (There isn't one running here, but there is in the main task.) 60 | delay(Duration::from_millis(Motor::DATA_READ_RATE)); 61 | } 62 | }); 63 | 64 | loop { 65 | // Set the motors output with how far up or down the right joystick is pushed. 66 | // Set output takes a float from -1 to 1 that is scaled to -12 to 12 volts. 67 | self.motor 68 | .lock() 69 | .set_voltage(Motor::MAX_VOLTAGE * controller.state()?.joysticks.right.y)?; 70 | 71 | // println!("pid out {}", pid.update(10.0, motor.position().into_degrees() as f32)); 72 | println!( 73 | "Vision objs {}", 74 | self.vision.nth_largest_object(0)?.middle_x 75 | ); 76 | 77 | // Once again, sleep. 78 | sleep(Duration::from_millis(20)).await; 79 | } 80 | } 81 | } 82 | async_robot!( 83 | ExampleRobot, 84 | ExampleRobot::new(Peripherals::take().unwrap()) 85 | ); 86 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/solenoid.rs: -------------------------------------------------------------------------------- 1 | //! ADI Solenoid Pneumatic Control 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::PROS_ERR; 5 | 6 | use super::{digital::LogicLevel, AdiDevice, AdiDeviceType, AdiError, AdiPort}; 7 | 8 | /// Digital pneumatic solenoid valve. 9 | #[derive(Debug, Eq, PartialEq)] 10 | pub struct AdiSolenoid { 11 | port: AdiPort, 12 | level: LogicLevel, 13 | } 14 | 15 | impl AdiSolenoid { 16 | /// Create an AdiSolenoid. 17 | pub fn new(port: AdiPort) -> Result { 18 | bail_on!(PROS_ERR, unsafe { 19 | pros_sys::ext_adi_port_set_config( 20 | port.internal_expander_index(), 21 | port.index(), 22 | pros_sys::E_ADI_DIGITAL_OUT, 23 | ) 24 | }); 25 | 26 | Ok(Self { 27 | port, 28 | level: LogicLevel::Low, 29 | }) 30 | } 31 | 32 | /// Sets the digital logic level of the solenoid. [`LogicLevel::Low`] will close the solenoid, 33 | /// and [`LogicLevel::High`] will open it. 34 | pub fn set_level(&mut self, level: LogicLevel) -> Result<(), AdiError> { 35 | self.level = level; 36 | 37 | bail_on!(PROS_ERR, unsafe { 38 | pros_sys::ext_adi_digital_write( 39 | self.port.internal_expander_index(), 40 | self.port.index(), 41 | level.is_high(), 42 | ) 43 | }); 44 | 45 | Ok(()) 46 | } 47 | 48 | /// Returns the current [`LogicLevel`] of the solenoid's digital output state. 49 | pub const fn level(&self) -> LogicLevel { 50 | self.level 51 | } 52 | 53 | /// Returns `true` if the solenoid is open. 54 | pub const fn is_open(&self) -> LogicLevel { 55 | self.level 56 | } 57 | 58 | /// Returns `true` if the solenoid is closed. 59 | pub const fn is_closed(&self) -> LogicLevel { 60 | self.level 61 | } 62 | 63 | /// Open the solenoid, allowing air pressure through the "open" valve. 64 | pub fn open(&mut self) -> Result<(), AdiError> { 65 | self.set_level(LogicLevel::High) 66 | } 67 | 68 | /// Close the solenoid. 69 | /// 70 | /// - On single-acting solenoids (e.g. SY113-SMO-PM3-F), this will simply block air pressure 71 | /// through the "open" valve. 72 | /// - On double-acting solenoids (e.g. SYJ3120-SMO-M3-F), this will block air pressure through 73 | /// the "open" valve and allow air pressure into the "close" valve. 74 | pub fn close(&mut self) -> Result<(), AdiError> { 75 | self.set_level(LogicLevel::Low) 76 | } 77 | 78 | /// Toggle the solenoid's state between open and closed. 79 | pub fn toggle(&mut self) -> Result<(), AdiError> { 80 | self.set_level(!self.level) 81 | } 82 | } 83 | 84 | impl AdiDevice for AdiSolenoid { 85 | type PortIndexOutput = u8; 86 | 87 | fn port_index(&self) -> Self::PortIndexOutput { 88 | self.port.index() 89 | } 90 | 91 | fn expander_port_index(&self) -> Option { 92 | self.port.expander_index() 93 | } 94 | 95 | fn device_type(&self) -> AdiDeviceType { 96 | AdiDeviceType::DigitalOut 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/switch.rs: -------------------------------------------------------------------------------- 1 | //! ADI Digital Switch 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::PROS_ERR; 5 | 6 | use super::{digital::LogicLevel, AdiDevice, AdiDeviceType, AdiDigitalIn, AdiError, AdiPort}; 7 | 8 | /// Generic digital input ADI device. 9 | #[derive(Debug, Eq, PartialEq)] 10 | pub struct AdiSwitch { 11 | port: AdiPort, 12 | } 13 | 14 | impl AdiSwitch { 15 | /// Create a digital input from an ADI port. 16 | pub fn new(port: AdiPort) -> Result { 17 | bail_on!(PROS_ERR, unsafe { 18 | pros_sys::ext_adi_port_set_config( 19 | port.internal_expander_index(), 20 | port.index(), 21 | pros_sys::E_ADI_DIGITAL_IN, 22 | ) 23 | }); 24 | 25 | Ok(Self { port }) 26 | } 27 | 28 | /// Gets the current logic level of a digital switch. 29 | pub fn level(&self) -> Result { 30 | let value = bail_on!(PROS_ERR, unsafe { 31 | pros_sys::ext_adi_digital_read(self.port.internal_expander_index(), self.port.index()) 32 | }) != 0; 33 | 34 | Ok(match value { 35 | true => LogicLevel::High, 36 | false => LogicLevel::Low, 37 | }) 38 | } 39 | 40 | /// Returrns `true` if the switch is currently being pressed. 41 | /// 42 | /// This is equivalent shorthand to calling `Self::level().is_high()`. 43 | pub fn is_pressed(&self) -> Result { 44 | Ok(self.level()?.is_high()) 45 | } 46 | 47 | /// Returns `true` if the switch has been pressed again since the last time this 48 | /// function was called. 49 | /// 50 | /// # Thread Safety 51 | /// 52 | /// This function is not thread-safe. 53 | /// 54 | /// Multiple tasks polling a single button may return different results under the 55 | /// same circumstances, so only one task should call this function for any given 56 | /// switch. E.g., Task A calls this function for buttons 1 and 2. Task B may call 57 | /// this function for button 3, but should not for buttons 1 or 2. A typical 58 | /// use-case for this function is to call inside opcontrol to detect new button 59 | /// presses, and not in any other tasks. 60 | pub fn was_pressed(&mut self) -> Result { 61 | Ok(bail_on!(PROS_ERR, unsafe { 62 | pros_sys::ext_adi_digital_get_new_press( 63 | self.port.internal_expander_index(), 64 | self.port.index(), 65 | ) 66 | }) != 0) 67 | } 68 | } 69 | 70 | impl From for AdiSwitch { 71 | fn from(device: AdiDigitalIn) -> Self { 72 | Self { 73 | port: unsafe { AdiPort::new(device.port_index(), device.expander_port_index()) }, 74 | } 75 | } 76 | } 77 | 78 | impl AdiDevice for AdiSwitch { 79 | type PortIndexOutput = u8; 80 | 81 | fn port_index(&self) -> Self::PortIndexOutput { 82 | self.port.index() 83 | } 84 | 85 | fn expander_port_index(&self) -> Option { 86 | self.port.expander_index() 87 | } 88 | 89 | fn device_type(&self) -> AdiDeviceType { 90 | AdiDeviceType::DigitalIn 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/pros-devices/src/smart/rotation.rs: -------------------------------------------------------------------------------- 1 | //! Rotation sensor device. 2 | //! 3 | //! Rotation sensors operate on the same [`Position`] type as motors to measure rotation. 4 | 5 | use pros_core::{bail_on, error::PortError}; 6 | use pros_sys::PROS_ERR; 7 | 8 | use super::{SmartDevice, SmartDeviceType, SmartPort}; 9 | use crate::position::Position; 10 | 11 | /// A physical rotation sensor plugged into a port. 12 | #[derive(Debug, Eq, PartialEq)] 13 | pub struct RotationSensor { 14 | port: SmartPort, 15 | /// Whether or not the sensor direction is reversed. 16 | pub reversed: bool, 17 | } 18 | 19 | impl RotationSensor { 20 | /// Creates a new rotation sensor on the given port. 21 | /// Whether or not the sensor should be reversed on creation can be specified. 22 | pub fn new(port: SmartPort, reversed: bool) -> Result { 23 | unsafe { 24 | bail_on!(PROS_ERR, pros_sys::rotation_reset_position(port.index())); 25 | if reversed { 26 | bail_on!( 27 | PROS_ERR, 28 | pros_sys::rotation_set_reversed(port.index(), true) 29 | ); 30 | } 31 | } 32 | 33 | Ok(Self { port, reversed }) 34 | } 35 | 36 | /// Sets the position to zero. 37 | pub fn zero(&mut self) -> Result<(), PortError> { 38 | unsafe { 39 | bail_on!( 40 | PROS_ERR, 41 | pros_sys::rotation_reset_position(self.port.index()) 42 | ); 43 | } 44 | Ok(()) 45 | } 46 | 47 | /// Sets the position. 48 | pub fn set_position(&mut self, position: Position) -> Result<(), PortError> { 49 | unsafe { 50 | bail_on!( 51 | PROS_ERR, 52 | pros_sys::rotation_set_position( 53 | self.port.index(), 54 | (position.into_counts() * 100) as _ 55 | ) 56 | ); 57 | } 58 | Ok(()) 59 | } 60 | 61 | /// Sets whether or not the rotation sensor should be reversed. 62 | pub fn set_reversed(&mut self, reversed: bool) -> Result<(), PortError> { 63 | self.reversed = reversed; 64 | 65 | unsafe { 66 | bail_on!( 67 | PROS_ERR, 68 | pros_sys::rotation_set_reversed(self.port.index(), reversed) 69 | ); 70 | } 71 | Ok(()) 72 | } 73 | 74 | /// Reverses the rotation sensor. 75 | pub fn reverse(&mut self) -> Result<(), PortError> { 76 | self.set_reversed(!self.reversed) 77 | } 78 | 79 | //TODO: See if this is accurate enough or consider switching to get_position function. 80 | /// Gets the current position of the sensor. 81 | pub fn position(&self) -> Result { 82 | Ok(unsafe { 83 | Position::from_degrees( 84 | bail_on!(PROS_ERR, pros_sys::rotation_get_angle(self.port.index())) as f64 / 100.0, 85 | ) 86 | }) 87 | } 88 | } 89 | 90 | impl SmartDevice for RotationSensor { 91 | fn port_index(&self) -> u8 { 92 | self.port.index() 93 | } 94 | 95 | fn device_type(&self) -> SmartDeviceType { 96 | SmartDeviceType::Rotation 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/linetracker.rs: -------------------------------------------------------------------------------- 1 | //! ADI Line Tracker 2 | //! 3 | //! Line trackers read the difference between a black line and a white surface. They can 4 | //! be used to follow a marked path on the ground. 5 | //! 6 | //! # Overview 7 | //! 8 | //! A line tracker consists of an analog infrared light sensor and an infrared LED. 9 | //! It works by illuminating a surface with infrared light; the sensor then picks up 10 | //! the reflected infrared radiation and, based on its intensity, determines the 11 | //! reflectivity of the surface in question. White surfaces will reflect more light 12 | //! than dark surfaces, resulting in their appearing brighter to the sensor. This 13 | //! allows the sensor to detect a dark line on a white background, or a white line on 14 | //! a dark background. 15 | //! 16 | //! # Hardware 17 | //! 18 | //! The Line Tracking Sensor is an analog sensor, and it internally measures values in the 19 | //! range of 0 to 4095 from 0-5V. Darker objects reflect less light, and are indicated by 20 | //! higher numbers. Lighter objects reflect more light, and are indicated by lower numbers. 21 | //! 22 | //! For best results when using the Line Tracking Sensors, it is best to mount the sensors 23 | //! between 1/8 and 1/4 of an inch away from the surface it is measuring. It is also important 24 | //! to keep lighting in the room consistent, so sensors' readings remain accurate. 25 | 26 | use pros_core::bail_on; 27 | use pros_sys::PROS_ERR; 28 | 29 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 30 | 31 | /// Analog line tracker device. 32 | #[derive(Debug, Eq, PartialEq)] 33 | pub struct AdiLineTracker { 34 | port: AdiPort, 35 | } 36 | 37 | impl AdiLineTracker { 38 | /// Create a line tracker on an ADI port. 39 | pub fn new(port: AdiPort) -> Result { 40 | bail_on!(PROS_ERR, unsafe { 41 | pros_sys::ext_adi_port_set_config( 42 | port.internal_expander_index(), 43 | port.index(), 44 | pros_sys::E_ADI_ANALOG_IN, 45 | ) 46 | }); 47 | 48 | Ok(Self { port }) 49 | } 50 | 51 | /// Get the reflectivity factor measured by the sensor. 52 | /// 53 | /// This is returned as a value ranging from [0.0, 1.0]. 54 | pub fn reflectivity(&self) -> Result { 55 | Ok(bail_on!(PROS_ERR, unsafe { 56 | pros_sys::ext_adi_analog_read(self.port.internal_expander_index(), self.port.index()) 57 | }) as f64 58 | / 4095.0) 59 | } 60 | 61 | /// Get the raw reflectivity factor of the sensor. 62 | /// 63 | /// This is a raw 12-bit value from [0, 4095] representing the voltage level from 64 | /// 0-5V measured by the V5 brain's ADC. 65 | pub fn raw_reflectivity(&self) -> Result { 66 | Ok(bail_on!(PROS_ERR, unsafe { 67 | pros_sys::ext_adi_analog_read(self.port.internal_expander_index(), self.port.index()) 68 | }) as u16) 69 | } 70 | } 71 | 72 | impl AdiDevice for AdiLineTracker { 73 | type PortIndexOutput = u8; 74 | 75 | fn port_index(&self) -> Self::PortIndexOutput { 76 | self.port.index() 77 | } 78 | 79 | fn expander_port_index(&self) -> Option { 80 | self.port.expander_index() 81 | } 82 | 83 | fn device_type(&self) -> AdiDeviceType { 84 | AdiDeviceType::AnalogIn 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/pros-devices/src/competition.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for getting what state of the competition the robot is in. 2 | 3 | use pros_sys::misc::{COMPETITION_AUTONOMOUS, COMPETITION_CONNECTED, COMPETITION_DISABLED}; 4 | 5 | // TODO: change this to use PROS' internal version once we switch to PROS 4. 6 | const COMPETITION_SYSTEM: u8 = 1 << 3; 7 | 8 | /// Represents a possible mode that robots can be set in during the competition lifecycle. 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 | pub enum CompetitionMode { 11 | /// The Disabled competition mode. 12 | /// 13 | /// When in disabled mode, voltage commands to motors are disabled. Motors are forcibly 14 | /// locked to the "coast" brake mode and cannot be moved. 15 | /// 16 | /// Robots may be placed into disabled mode at any point in the competition after 17 | /// connecting, but are typically disabled before the autonomous period, between 18 | /// autonomous and opcontrol periods, and following the opcontrol period of a match. 19 | Disabled, 20 | 21 | /// The Autonomous competition mode. 22 | /// 23 | /// When in autonomous mode, all motors and sensors may be accessed, however user 24 | /// input from controller buttons and joysticks is not available to be read. 25 | /// 26 | /// Robots may be placed into autonomous mode at any point in the competition after 27 | /// connecting, but are typically placed into this mode at the start of a match. 28 | Autonomous, 29 | 30 | /// The Opcontrol competition mode. 31 | /// 32 | /// When in opcontrol mode, all device access is available including access to 33 | /// controller joystick values for reading user-input from drive team members. 34 | /// 35 | /// Robots may be placed into opcontrol mode at any point in the competition after 36 | /// connecting, but are typically placed into this mode following the autonomous 37 | /// period. 38 | Opcontrol, 39 | } 40 | 41 | /// Represents a type of system used to control competition state. 42 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 43 | pub enum CompetitionSystem { 44 | /// Competition state is controlled by a VEX Field Controller. 45 | FieldControl, 46 | 47 | /// Competition state is controlled by a VEXnet competition switch. 48 | CompetitionSwitch, 49 | } 50 | 51 | /// Gets the current competition mode, or phase. 52 | pub fn mode() -> CompetitionMode { 53 | let status = unsafe { pros_sys::misc::competition_get_status() }; 54 | 55 | if status & COMPETITION_DISABLED != 0 { 56 | CompetitionMode::Disabled 57 | } else if status & COMPETITION_AUTONOMOUS != 0 { 58 | CompetitionMode::Autonomous 59 | } else { 60 | CompetitionMode::Opcontrol 61 | } 62 | } 63 | 64 | /// Checks if the robot is connected to a competition control system. 65 | pub fn connected() -> bool { 66 | let status = unsafe { pros_sys::misc::competition_get_status() }; 67 | 68 | status & COMPETITION_CONNECTED != 0 69 | } 70 | 71 | /// Gets the type of system currently controlling the robot's competition state, or [`None`] if the robot 72 | /// is not tethered to a competition controller. 73 | pub fn system() -> Option { 74 | let status = unsafe { pros_sys::misc::competition_get_status() }; 75 | 76 | if status & COMPETITION_CONNECTED != 0 { 77 | if status & COMPETITION_SYSTEM == 0 { 78 | Some(CompetitionSystem::FieldControl) 79 | } else { 80 | Some(CompetitionSystem::CompetitionSwitch) 81 | } 82 | } else { 83 | None 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /packages/pros-panic/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Panic handler implementation for [`pros-rs`](https://crates.io/crates/pros-rs). 2 | //! Supports printing a backtrace when running in the simulator. 3 | //! If the `display_panics` feature is enabled, it will also display the panic message on the V5 Brain display. 4 | 5 | #![no_std] 6 | 7 | extern crate alloc; 8 | 9 | use alloc::{format, string::String}; 10 | 11 | use pros_core::eprintln; 12 | #[cfg(feature = "display_panics")] 13 | use pros_devices::Screen; 14 | 15 | #[cfg(target_arch = "wasm32")] 16 | extern "C" { 17 | /// Prints a backtrace to the debug console 18 | fn sim_log_backtrace(); 19 | } 20 | 21 | /// Draw an error box to the screen. 22 | /// 23 | /// This function is internally used by the pros-rs panic handler for displaying 24 | /// panic messages graphically before exiting. 25 | #[cfg(feature = "display_panics")] 26 | fn draw_error( 27 | screen: &mut pros_devices::screen::Screen, 28 | msg: &str, 29 | ) -> Result<(), pros_devices::screen::ScreenError> { 30 | const ERROR_BOX_MARGIN: i16 = 16; 31 | const ERROR_BOX_PADDING: i16 = 16; 32 | const LINE_MAX_WIDTH: usize = 52; 33 | 34 | let error_box_rect = pros_devices::screen::Rect::new( 35 | ERROR_BOX_MARGIN, 36 | ERROR_BOX_MARGIN, 37 | Screen::HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN, 38 | Screen::VERTICAL_RESOLUTION - ERROR_BOX_MARGIN, 39 | ); 40 | 41 | screen.fill(&error_box_rect, pros_devices::color::Rgb::RED)?; 42 | screen.stroke(&error_box_rect, pros_devices::color::Rgb::WHITE)?; 43 | 44 | let mut buffer = String::new(); 45 | let mut line: i16 = 0; 46 | 47 | for (i, character) in msg.char_indices() { 48 | if !character.is_ascii_control() { 49 | buffer.push(character); 50 | } 51 | 52 | if character == '\n' || ((buffer.len() % LINE_MAX_WIDTH == 0) && (i > 0)) { 53 | screen.fill( 54 | &pros_devices::screen::Text::new( 55 | buffer.as_str(), 56 | pros_devices::screen::TextPosition::Point( 57 | ERROR_BOX_MARGIN + ERROR_BOX_PADDING, 58 | ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Screen::LINE_HEIGHT), 59 | ), 60 | pros_devices::screen::TextFormat::Small, 61 | ), 62 | pros_devices::color::Rgb::WHITE, 63 | )?; 64 | 65 | line += 1; 66 | buffer.clear(); 67 | } 68 | } 69 | 70 | screen.fill( 71 | &pros_devices::screen::Text::new( 72 | buffer.as_str(), 73 | pros_devices::screen::TextPosition::Point( 74 | ERROR_BOX_MARGIN + ERROR_BOX_PADDING, 75 | ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Screen::LINE_HEIGHT), 76 | ), 77 | pros_devices::screen::TextFormat::Small, 78 | ), 79 | pros_devices::color::Rgb::WHITE, 80 | )?; 81 | 82 | Ok(()) 83 | } 84 | 85 | #[panic_handler] 86 | /// The panic handler for pros-rs. 87 | pub fn panic(info: &core::panic::PanicInfo<'_>) -> ! { 88 | let current_task = pros_core::task::current(); 89 | 90 | let task_name = current_task.name().unwrap_or_else(|_| "".into()); 91 | 92 | // task 'User Initialization (PROS)' panicked at src/lib.rs:22:1: 93 | // panic message here 94 | let msg = format!("task '{task_name}' {info}"); 95 | 96 | eprintln!("{msg}"); 97 | 98 | unsafe { 99 | #[cfg(feature = "display_panics")] 100 | draw_error(&mut Screen::new(), &msg).unwrap_or_else(|err| { 101 | eprintln!("Failed to draw error message to screen: {err}"); 102 | }); 103 | 104 | #[cfg(target_arch = "wasm32")] 105 | sim_log_backtrace(); 106 | 107 | pros_sys::exit(1); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /packages/pros-core/src/sync.rs: -------------------------------------------------------------------------------- 1 | //! Synchronization types for FreeRTOS tasks. 2 | //! 3 | //! Types implemented here are specifically designed to mimic the standard library. 4 | 5 | use core::{cell::UnsafeCell, fmt::Debug, mem}; 6 | 7 | use crate::error::take_errno; 8 | 9 | /// The basic mutex type. 10 | /// Mutexes are used to share variables between tasks safely. 11 | pub struct Mutex { 12 | pros_mutex: pros_sys::mutex_t, 13 | data: Option>, 14 | } 15 | unsafe impl Send for Mutex {} 16 | unsafe impl Sync for Mutex {} 17 | 18 | impl Mutex { 19 | /// Creates a new mutex. 20 | pub fn new(data: T) -> Self { 21 | let pros_mutex = unsafe { pros_sys::mutex_create() }; 22 | 23 | Self { 24 | pros_mutex, 25 | data: Some(UnsafeCell::new(data)), 26 | } 27 | } 28 | 29 | /// Locks the mutex so that it cannot be locked in another task at the same time. 30 | /// Blocks the current task until the lock is acquired. 31 | pub fn lock(&self) -> MutexGuard<'_, T> { 32 | if !unsafe { pros_sys::mutex_take(self.pros_mutex, pros_sys::TIMEOUT_MAX) } { 33 | panic!("Mutex lock failed: {}", take_errno()); 34 | } 35 | 36 | MutexGuard { mutex: self } 37 | } 38 | 39 | /// Attempts to acquire this lock. This function does not block. 40 | pub fn try_lock(&self) -> Option> { 41 | let success = unsafe { pros_sys::mutex_take(self.pros_mutex, 0) }; 42 | success.then(|| MutexGuard::new(self)) 43 | } 44 | 45 | /// Consumes the mutex and returns the inner data. 46 | pub fn into_inner(mut self) -> T { 47 | let data = mem::take(&mut self.data).unwrap(); 48 | data.into_inner() 49 | } 50 | 51 | /// Gets a mutable reference to the inner data. 52 | pub fn get_mut(&mut self) -> &mut T { 53 | self.data.as_mut().unwrap().get_mut() 54 | } 55 | } 56 | 57 | impl Drop for Mutex { 58 | fn drop(&mut self) { 59 | unsafe { 60 | pros_sys::mutex_delete(self.pros_mutex); 61 | } 62 | } 63 | } 64 | 65 | impl Debug for Mutex 66 | where 67 | T: Debug, 68 | { 69 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 70 | struct Placeholder; 71 | impl Debug for Placeholder { 72 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 73 | f.write_str("") 74 | } 75 | } 76 | 77 | let mut d = f.debug_struct("Mutex"); 78 | match self.try_lock() { 79 | Some(guard) => d.field("data", &&*guard), 80 | None => d.field("data", &Placeholder), 81 | }; 82 | d.finish_non_exhaustive() 83 | } 84 | } 85 | 86 | impl Default for Mutex 87 | where 88 | T: Default, 89 | { 90 | fn default() -> Self { 91 | Self::new(T::default()) 92 | } 93 | } 94 | 95 | impl From for Mutex { 96 | fn from(value: T) -> Self { 97 | Self::new(value) 98 | } 99 | } 100 | 101 | /// Allows the user to access the data from a locked mutex. 102 | /// Dereference to get the inner data. 103 | #[derive(Debug)] 104 | pub struct MutexGuard<'a, T> { 105 | mutex: &'a Mutex, 106 | } 107 | 108 | impl<'a, T> MutexGuard<'a, T> { 109 | const fn new(mutex: &'a Mutex) -> Self { 110 | Self { mutex } 111 | } 112 | } 113 | 114 | impl core::ops::Deref for MutexGuard<'_, T> { 115 | type Target = T; 116 | fn deref(&self) -> &T { 117 | unsafe { &*self.mutex.data.as_ref().unwrap().get() } 118 | } 119 | } 120 | 121 | impl core::ops::DerefMut for MutexGuard<'_, T> { 122 | fn deref_mut(&mut self) -> &mut T { 123 | unsafe { &mut *self.mutex.data.as_ref().unwrap().get() } 124 | } 125 | } 126 | 127 | impl Drop for MutexGuard<'_, T> { 128 | fn drop(&mut self) { 129 | unsafe { 130 | pros_sys::mutex_give(self.mutex.pros_mutex); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /packages/pros-core/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for dealing with errno. 2 | //! 3 | //! Most errors in pros-rs are created by reading the last value of ERRNO. 4 | //! This includes the very generic [`PortError`], which is used for most hardware that gets plugged into a port on a V5 Brain. 5 | //! 6 | //! Most of the contents of this file are not public. 7 | 8 | /// A result type that makes returning errors easier. 9 | pub type Result = core::result::Result>; 10 | 11 | /// Gets the value of errno and sets errno to 0. 12 | pub fn take_errno() -> i32 { 13 | let err = unsafe { *pros_sys::__errno() }; 14 | if err != 0 { 15 | unsafe { *pros_sys::__errno() = 0 }; 16 | } 17 | err 18 | } 19 | 20 | /// Generate an implementation of FromErrno for the given type. 21 | /// 22 | /// Example: 23 | /// ```ignore 24 | /// map_errno! { 25 | /// GpsError { 26 | /// EAGAIN => Self::StillCalibrating, 27 | /// } 28 | /// inherit PortError; 29 | /// } 30 | /// ``` 31 | #[macro_export] 32 | macro_rules! map_errno { 33 | { 34 | $err_ty:ty { $($errno:pat => $err:expr),*$(,)? } 35 | $(inherit $base:ty;)? 36 | } => { 37 | impl $crate::error::FromErrno for $err_ty { 38 | fn from_errno(num: i32) -> Option { 39 | #[allow(unused_imports)] 40 | use pros_sys::error::*; 41 | $( 42 | // if the enum we're inheriting from can handle this errno, return it. 43 | if let Some(err) = <$base as $crate::error::FromErrno>::from_errno(num) { 44 | return Some(err.into()); 45 | } 46 | )? 47 | match num { 48 | $($errno => Some($err),)* 49 | // this function should only be called if errno is set 50 | 0 => panic!("Expected error state in errno, found 0."), 51 | _ => None, 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | /// If errno has an error, return early. 59 | #[macro_export] 60 | macro_rules! bail_errno { 61 | () => {{ 62 | let errno = $crate::error::take_errno(); 63 | if errno != 0 { 64 | let err = $crate::error::FromErrno::from_errno(errno) 65 | .unwrap_or_else(|| panic!("Unknown errno code {errno}")); 66 | return Err(err); 67 | } 68 | }}; 69 | } 70 | 71 | /// Checks if the value is equal to the error state, and if it is, 72 | /// uses the value of errno to create an error and return early. 73 | #[macro_export] 74 | macro_rules! bail_on { 75 | ($err_state:expr, $val:expr) => {{ 76 | let val = $val; 77 | #[allow(clippy::cmp_null)] 78 | if val == $err_state { 79 | let errno = $crate::error::take_errno(); 80 | let err = $crate::error::FromErrno::from_errno(errno) 81 | .unwrap_or_else(|| panic!("Unknown errno code {errno}")); 82 | return Err(err); // where are we using this in a function that doesn't return result? 83 | } 84 | val 85 | }}; 86 | } 87 | use snafu::Snafu; 88 | 89 | /// A trait for converting an errno value into an error type. 90 | pub trait FromErrno { 91 | /// Consume the current `errno` and, if it contains a known error, returns Self. 92 | fn from_errno(num: i32) -> Option 93 | where 94 | Self: Sized; 95 | } 96 | 97 | #[derive(Debug, Snafu)] 98 | /// Generic erros that can take place when using ports on the V5 Brain. 99 | pub enum PortError { 100 | /// The specified port is outside of the allowed range! 101 | PortOutOfRange, 102 | /// The specified port couldn't be configured as the specified type. 103 | PortCannotBeConfigured, 104 | /// The specified port is already being used or is mismatched. 105 | AlreadyInUse, 106 | } 107 | 108 | map_errno!(PortError { 109 | ENXIO => Self::PortOutOfRange, 110 | ENODEV => Self::PortCannotBeConfigured, 111 | EADDRINUSE => Self::AlreadyInUse, 112 | }); 113 | -------------------------------------------------------------------------------- /packages/pros-devices/src/smart/gps.rs: -------------------------------------------------------------------------------- 1 | //! GPS sensor device. 2 | //! 3 | //! A notable differenc between this API and that of PROS 4 | //! is that [`GpsSensor::status`] returns acceleration along with other status data. 5 | 6 | use pros_core::{bail_on, error::PortError, map_errno}; 7 | use pros_sys::{PROS_ERR, PROS_ERR_F}; 8 | use snafu::Snafu; 9 | 10 | use super::{SmartDevice, SmartDeviceType, SmartPort}; 11 | 12 | //TODO: Figure out what all the units are 13 | #[derive(Default, Debug, Clone, Copy, PartialEq)] 14 | /// Represents the data output from a GPS sensor. 15 | pub struct GpsStatus { 16 | /// The x-coordinate of the GPS sensor in meters. 17 | pub x: f64, 18 | /// The y-coordinate of the GPS sensor in meters. 19 | pub y: f64, 20 | /// The pitch of the GPS sensor. 21 | pub pitch: f64, 22 | /// The roll of the GPS sensor. 23 | pub roll: f64, 24 | /// The yaw of the GPS sensor. 25 | pub yaw: f64, 26 | /// The heading of the GPS sensor. 27 | pub heading: f64, 28 | 29 | /// The x-acceleration of the GPS sensor. 30 | pub accel_x: f64, 31 | /// The y-acceleration of the GPS sensor. 32 | pub accel_y: f64, 33 | /// The z-acceleration of the GPS sensor. 34 | pub accel_z: f64, 35 | } 36 | 37 | /// A physical GPS sensor plugged into a port. 38 | #[derive(Debug, Eq, PartialEq)] 39 | pub struct GpsSensor { 40 | port: SmartPort, 41 | } 42 | 43 | impl GpsSensor { 44 | /// Creates a new GPS sensor on the given port. 45 | pub fn new(port: SmartPort) -> Result { 46 | unsafe { 47 | bail_on!( 48 | PROS_ERR, 49 | pros_sys::gps_initialize_full(port.index(), 0.0, 0.0, 0.0, 0.0, 0.0) 50 | ); 51 | } 52 | 53 | Ok(Self { port }) 54 | } 55 | 56 | /// Sets the offset of the GPS sensor, relative to the sensor of turning, in meters. 57 | pub fn set_offset(&mut self, x: f64, y: f64) -> Result<(), GpsError> { 58 | unsafe { 59 | bail_on!(PROS_ERR, pros_sys::gps_set_offset(self.port.index(), x, y)); 60 | } 61 | Ok(()) 62 | } 63 | 64 | /// Gets the possible error of the GPS sensor, in meters. 65 | pub fn rms_error(&self) -> Result { 66 | Ok(unsafe { bail_on!(PROS_ERR_F, pros_sys::gps_get_error(self.port.index())) }) 67 | } 68 | 69 | /// Gets the status of the GPS sensor. 70 | pub fn status(&self) -> Result { 71 | unsafe { 72 | let status = pros_sys::gps_get_status(self.port.index()); 73 | bail_on!(PROS_ERR_F, status.x); 74 | let accel = pros_sys::gps_get_accel(self.port.index()); 75 | bail_on!(PROS_ERR_F, accel.x); 76 | let heading = bail_on!(PROS_ERR_F, pros_sys::gps_get_heading(self.port.index())); 77 | 78 | Ok(GpsStatus { 79 | x: status.x, 80 | y: status.y, 81 | pitch: status.pitch, 82 | roll: status.roll, 83 | yaw: status.yaw, 84 | heading, 85 | 86 | accel_x: accel.x, 87 | accel_y: accel.y, 88 | accel_z: accel.z, 89 | }) 90 | } 91 | } 92 | 93 | /// Zeroes the rotation of the GPS sensor. 94 | pub fn zero_rotation(&mut self) -> Result<(), GpsError> { 95 | unsafe { 96 | bail_on!(PROS_ERR, pros_sys::gps_tare_rotation(self.port.index())); 97 | } 98 | Ok(()) 99 | } 100 | } 101 | 102 | impl SmartDevice for GpsSensor { 103 | fn port_index(&self) -> u8 { 104 | self.port.index() 105 | } 106 | 107 | fn device_type(&self) -> SmartDeviceType { 108 | SmartDeviceType::Gps 109 | } 110 | } 111 | 112 | #[derive(Debug, Snafu)] 113 | /// Errors that can occur when using a GPS sensor. 114 | pub enum GpsError { 115 | /// The GPS sensor is still calibrating. 116 | StillCalibrating, 117 | #[snafu(display("{source}"), context(false))] 118 | /// Generic port related error. 119 | Port { 120 | /// The source of the error. 121 | source: PortError, 122 | }, 123 | } 124 | 125 | map_errno! { 126 | GpsError { 127 | EAGAIN => Self::StillCalibrating, 128 | } 129 | inherit PortError; 130 | } 131 | -------------------------------------------------------------------------------- /packages/pros-devices/src/position.rs: -------------------------------------------------------------------------------- 1 | //! Generic angular position type for motors and sensors. 2 | //! 3 | //! Positions have many conversion functions as well as common operator implementations for ease of use. 4 | 5 | use core::{cmp::Ordering, ops::*}; 6 | 7 | //TODO: Add more unit types to this. 8 | /// Represents an angular position. 9 | #[derive(Clone, Copy, Debug)] 10 | pub enum Position { 11 | /// Degrees of rotation. 12 | Degrees(f64), 13 | /// Counts of full rotations, 360 degrees. 14 | Rotations(f64), 15 | /// Raw encoder ticks. 16 | Counts(i64), 17 | } 18 | 19 | impl Position { 20 | /// Creates a position from a specified number of degrees. 21 | pub const fn from_degrees(position: f64) -> Self { 22 | Self::Degrees(position) 23 | } 24 | 25 | /// Creates a position from a specified number of rotations. 26 | pub const fn from_rotations(position: f64) -> Self { 27 | Self::Rotations(position) 28 | } 29 | 30 | /// Creates a position from a specified number of counts (raw encoder tics). 31 | pub const fn from_counts(position: i64) -> Self { 32 | Self::Counts(position) 33 | } 34 | 35 | /// Converts a position into degrees. 36 | pub fn into_degrees(self) -> f64 { 37 | match self { 38 | Self::Degrees(num) => num, 39 | Self::Rotations(num) => num * 360.0, 40 | Self::Counts(num) => num as f64 * (360.0 / 4096.0), 41 | } 42 | } 43 | 44 | /// Converts a position into rotations. 45 | pub fn into_rotations(self) -> f64 { 46 | match self { 47 | Self::Degrees(num) => num / 360.0, 48 | Self::Rotations(num) => num, 49 | Self::Counts(num) => num as f64 * 4096.0, 50 | } 51 | } 52 | 53 | /// Converts a position into counts (raw encoder ticks). 54 | pub fn into_counts(self) -> i64 { 55 | match self { 56 | Self::Degrees(num) => (num * 4096.0 / 360.0) as i64, 57 | Self::Rotations(num) => (num * 4096.0) as i64, 58 | Self::Counts(num) => num, 59 | } 60 | } 61 | } 62 | 63 | impl Add for Position { 64 | type Output = Self; 65 | 66 | fn add(self, rhs: Self) -> Self::Output { 67 | Self::from_degrees(self.into_degrees() + rhs.into_degrees()) 68 | } 69 | } 70 | 71 | impl AddAssign for Position { 72 | fn add_assign(&mut self, rhs: Self) { 73 | *self = *self + rhs; 74 | } 75 | } 76 | 77 | impl Sub for Position { 78 | type Output = Self; 79 | 80 | fn sub(self, rhs: Self) -> Self::Output { 81 | Self::from_degrees(self.into_degrees() - rhs.into_degrees()) 82 | } 83 | } 84 | 85 | impl SubAssign for Position { 86 | fn sub_assign(&mut self, rhs: Self) { 87 | *self = *self - rhs; 88 | } 89 | } 90 | 91 | impl Mul for Position { 92 | type Output = Self; 93 | 94 | fn mul(self, rhs: Self) -> Self::Output { 95 | Self::from_degrees(self.into_degrees() * rhs.into_degrees()) 96 | } 97 | } 98 | 99 | impl MulAssign for Position { 100 | fn mul_assign(&mut self, rhs: Self) { 101 | *self = *self * rhs; 102 | } 103 | } 104 | 105 | impl Div for Position { 106 | type Output = Self; 107 | 108 | fn div(self, rhs: Self) -> Self::Output { 109 | Self::from_degrees(self.into_degrees() / rhs.into_degrees()) 110 | } 111 | } 112 | 113 | impl DivAssign for Position { 114 | fn div_assign(&mut self, rhs: Self) { 115 | *self = *self / rhs; 116 | } 117 | } 118 | 119 | impl Rem for Position { 120 | type Output = Self; 121 | 122 | fn rem(self, rhs: Self) -> Self::Output { 123 | Self::from_degrees(self.into_degrees() % rhs.into_degrees()) 124 | } 125 | } 126 | 127 | impl RemAssign for Position { 128 | fn rem_assign(&mut self, rhs: Self) { 129 | *self = *self % rhs; 130 | } 131 | } 132 | 133 | impl Neg for Position { 134 | type Output = Self; 135 | 136 | fn neg(self) -> Self::Output { 137 | Self::from_degrees(-self.into_degrees()) 138 | } 139 | } 140 | 141 | impl PartialEq for Position { 142 | fn eq(&self, other: &Self) -> bool { 143 | self.into_degrees() == other.into_degrees() 144 | } 145 | } 146 | 147 | impl PartialOrd for Position { 148 | fn partial_cmp(&self, other: &Self) -> Option { 149 | self.into_degrees().partial_cmp(&other.into_degrees()) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /packages/pros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Pros 2 | //! Opinionated bindings for the PROS library and kernel. 3 | //! Not everything in this library is one to one with the PROS API. 4 | //! 5 | //! Advantages over similar libraries or PROS itself: 6 | //! - Pros-rs has an [`Async executor`](async_runtime) which allows for easy and performant asynchronous code. 7 | //! - Simulation support with [`pros-simulator`](https://crates.io/crates/pros-simulator) and any interface with it (e.g. [`pros-simulator-gui`](https://github.com/pros-rs/pros-simulator-gui)) 8 | //! - Pros-rs is a real crate on crates.io instead of a template, or similar. This allows for dependency management with cargo. 9 | //! 10 | //! # Usage 11 | //! 12 | //! When using pros, you have a few options for how you want to get started. 13 | //! You have two options: `async` and `sync`. 14 | //! When using async, an async executor is started and you can use it to run code asynchronously without any FreeRTOS tasks. 15 | //! When using sync, if you want to run code asynchronously you must create a FreeRTOS task. 16 | //! 17 | //! Here are some examples of both: 18 | //! 19 | //! ```rust 20 | //! // Async 21 | //! use pros::prelude::*; 22 | //! 23 | //! #[derive(Default)] 24 | //! struct Robot; 25 | //! impl AsyncRobot for Robot { 26 | //! async fn opcontrol(&mut self) -> Result { 27 | //! loop { 28 | //! // Do something 29 | //! sleep(Duration::from_millis(20)).await; 30 | //! } 31 | //! } 32 | //! } 33 | //! async_robot!(Robot); 34 | //! ``` 35 | //! 36 | //!```rust 37 | //! // Sync 38 | //! use pros::prelude::*; 39 | //! 40 | //! #[derive(Default)] 41 | //! struct Robot; 42 | //! impl SyncRobot for Robot { 43 | //! fn opcontrol(&mut self) -> Result { 44 | //! loop { 45 | //! // Do something 46 | //! delay(Duration::from_millis(20)); 47 | //! } 48 | //! } 49 | //! } 50 | //! sync_robot!(Robot); 51 | //! ``` 52 | //! 53 | //! You may have noticed the `#[derive(Default)]` attribute on these Robot structs. 54 | //! If you want to learn why, look at the docs for [`pros_async::async_robot`] or [`pros_sync::sync_robot`]. 55 | #![no_std] 56 | 57 | #[cfg(feature = "async")] 58 | pub use pros_async as async_runtime; 59 | #[cfg(feature = "core")] 60 | pub use pros_core as core; 61 | #[cfg(feature = "devices")] 62 | pub use pros_devices as devices; 63 | #[cfg(feature = "math")] 64 | pub use pros_math as math; 65 | #[cfg(feature = "panic")] 66 | pub use pros_panic as panic; 67 | #[cfg(feature = "sync")] 68 | pub use pros_sync as sync; 69 | pub use pros_sys as sys; 70 | 71 | /// Commonly used features of pros-rs. 72 | /// This module is meant to be glob imported. 73 | pub mod prelude { 74 | #[cfg(feature = "async")] 75 | pub use pros_async::{async_robot, block_on, sleep, spawn, AsyncRobot}; 76 | #[cfg(feature = "core")] 77 | pub use pros_core::{ 78 | dbg, eprint, eprintln, 79 | error::{PortError, Result}, 80 | io::{BufRead, Read, Seek, Write}, 81 | print, println, 82 | task::delay, 83 | }; 84 | #[cfg(feature = "devices")] 85 | pub use pros_devices::{ 86 | adi::{ 87 | analog::AdiAnalogIn, 88 | digital::{AdiDigitalIn, AdiDigitalOut}, 89 | encoder::AdiEncoder, 90 | gyro::AdiGyro, 91 | motor::AdiMotor, 92 | potentiometer::{AdiPotentiometer, AdiPotentiometerType}, 93 | pwm::AdiPwmOut, 94 | solenoid::AdiSolenoid, 95 | ultrasonic::AdiUltrasonic, 96 | AdiDevice, AdiPort, 97 | }, 98 | color::Rgb, 99 | controller::Controller, 100 | peripherals::{DynamicPeripherals, Peripherals}, 101 | position::Position, 102 | screen::{Circle, Line, Rect, Screen, Text, TextFormat, TextPosition, TouchState}, 103 | smart::{ 104 | distance::DistanceSensor, 105 | expander::AdiExpander, 106 | gps::GpsSensor, 107 | imu::InertialSensor, 108 | link::{Link, RxLink, TxLink}, 109 | motor::{BrakeMode, Direction, Gearset, Motor, MotorControl}, 110 | optical::OpticalSensor, 111 | rotation::RotationSensor, 112 | vision::VisionSensor, 113 | SmartDevice, SmartPort, 114 | }, 115 | }; 116 | #[cfg(feature = "math")] 117 | pub use pros_math::{feedforward::MotorFeedforwardController, pid::PidController}; 118 | #[cfg(feature = "sync")] 119 | pub use pros_sync::{sync_robot, SyncRobot}; 120 | } 121 | -------------------------------------------------------------------------------- /packages/pros-sync/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Synchronous robot code trait for [pros-rs](https://crates.io/crates/pros). 2 | 3 | #![no_std] 4 | 5 | use pros_core::error::Result; 6 | 7 | /// A trait for robot code that runs without the async executor spun up. 8 | /// This trait isn't recommended. See `AsyncRobot` in [pros-async](https://crates.io/crates/pros-async) for the preferred trait to run robot code. 9 | pub trait SyncRobot { 10 | /// Runs during the operator control period. 11 | /// This function may be called more than once. 12 | /// For that reason, do not use `Peripherals::take` in this function. 13 | fn opcontrol(&mut self) -> Result { 14 | Ok(()) 15 | } 16 | /// Runs during the autonomous period. 17 | fn auto(&mut self) -> Result { 18 | Ok(()) 19 | } 20 | /// Runs continuously during the disabled period. 21 | fn disabled(&mut self) -> Result { 22 | Ok(()) 23 | } 24 | /// Runs once when the competition system is initialized. 25 | fn comp_init(&mut self) -> Result { 26 | Ok(()) 27 | } 28 | } 29 | 30 | #[doc(hidden)] 31 | #[macro_export] 32 | macro_rules! __gen_sync_exports { 33 | ($rbt:ty) => { 34 | pub static mut ROBOT: Option<$rbt> = None; 35 | 36 | #[doc(hidden)] 37 | #[no_mangle] 38 | extern "C" fn opcontrol() { 39 | <$rbt as $crate::SyncRobot>::opcontrol(unsafe { 40 | ROBOT 41 | .as_mut() 42 | .expect("Expected initialize to run before opcontrol") 43 | }) 44 | .unwrap(); 45 | } 46 | 47 | #[doc(hidden)] 48 | #[no_mangle] 49 | extern "C" fn autonomous() { 50 | <$rbt as $crate::SyncRobot>::auto(unsafe { 51 | ROBOT 52 | .as_mut() 53 | .expect("Expected initialize to run before opcontrol") 54 | }) 55 | .unwrap(); 56 | } 57 | 58 | #[doc(hidden)] 59 | #[no_mangle] 60 | extern "C" fn disabled() { 61 | <$rbt as $crate::SyncRobot>::disabled(unsafe { 62 | ROBOT 63 | .as_mut() 64 | .expect("Expected initialize to run before opcontrol") 65 | }) 66 | .unwrap(); 67 | } 68 | 69 | #[doc(hidden)] 70 | #[no_mangle] 71 | extern "C" fn competition_initialize() { 72 | <$rbt as $crate::SyncRobot>::comp_init(unsafe { 73 | ROBOT 74 | .as_mut() 75 | .expect("Expected initialize to run before opcontrol") 76 | }) 77 | .unwrap(); 78 | } 79 | }; 80 | } 81 | 82 | /// Allows your sync robot code to be executed by the pros kernel. 83 | /// If your robot struct implements Default then you can just supply this macro with its type. 84 | /// If not, you can supply an expression that returns your robot type to initialize your robot struct. 85 | /// The code that runs to create your robot struct will run in the initialize function in PROS. 86 | /// 87 | /// Example of using the macro with a struct that implements Default: 88 | /// ```rust 89 | /// use pros::prelude::*; 90 | /// #[derive(Default)] 91 | /// struct ExampleRobot; 92 | /// impl SyncRobot for ExampleRobot { 93 | /// asnyc fn opcontrol(&mut self) -> pros::Result { 94 | /// println!("Hello, world!"); 95 | /// Ok(()) 96 | /// } 97 | /// } 98 | /// sync_robot!(ExampleRobot); 99 | /// ``` 100 | /// 101 | /// Example of using the macro with a struct that does not implement Default: 102 | /// ```rust 103 | /// use pros::prelude::*; 104 | /// struct ExampleRobot { 105 | /// x: i32, 106 | /// } 107 | /// impl SyncRobot for ExampleRobot { 108 | /// async fn opcontrol(&mut self) -> pros::Result { 109 | /// println!("Hello, world! {}", self.x); 110 | /// Ok(()) 111 | /// } 112 | /// } 113 | /// impl ExampleRobot { 114 | /// pub fn new() -> Self { 115 | /// Self { x: 5 } 116 | /// } 117 | /// } 118 | /// sync_robot!(ExampleRobot, ExampleRobot::new()); 119 | #[macro_export] 120 | macro_rules! sync_robot { 121 | ($rbt:ty) => { 122 | $crate::__gen_sync_exports!($rbt); 123 | 124 | #[no_mangle] 125 | extern "C" fn initialize() { 126 | let robot = Default::default(); 127 | unsafe { 128 | ROBOT = Some(robot); 129 | } 130 | } 131 | }; 132 | ($rbt:ty, $init:expr) => { 133 | $crate::__gen_sync_exports!($rbt); 134 | 135 | #[no_mangle] 136 | extern "C" fn initialize() { 137 | let robot = $init; 138 | unsafe { 139 | ROBOT = Some(robot); 140 | } 141 | } 142 | }; 143 | } 144 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/digital.rs: -------------------------------------------------------------------------------- 1 | //! Digital input and output ADI devices 2 | 3 | use pros_core::bail_on; 4 | use pros_sys::PROS_ERR; 5 | 6 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 7 | 8 | /// Represents the logic level of a digital pin. 9 | /// 10 | /// On digital devices, logic levels represent the two possible voltage signals that define 11 | /// the state of a pin. This value is either [`High`] or [`Low`], depending on the intended 12 | /// state of the device. 13 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 14 | pub enum LogicLevel { 15 | /// A high digital signal. 16 | /// 17 | /// ADI ports operate on 3.3V logic, so this value indicates a voltage of 3.3V or above. 18 | High, 19 | 20 | /// The low digital signal. 21 | /// 22 | /// ADI ports operate on 3.3V logic, so this value indicates a voltage below 3.3V. 23 | Low, 24 | } 25 | 26 | impl LogicLevel { 27 | /// Returns `true` if the level is [`High`]. 28 | pub const fn is_high(&self) -> bool { 29 | match self { 30 | Self::High => true, 31 | Self::Low => false, 32 | } 33 | } 34 | 35 | /// Returns `true` if the level is [`Low`]. 36 | pub const fn is_low(&self) -> bool { 37 | match self { 38 | Self::High => false, 39 | Self::Low => true, 40 | } 41 | } 42 | } 43 | 44 | impl core::ops::Not for LogicLevel { 45 | type Output = Self; 46 | 47 | fn not(self) -> Self::Output { 48 | match self { 49 | Self::Low => Self::High, 50 | Self::High => Self::Low, 51 | } 52 | } 53 | } 54 | 55 | /// Generic digital input ADI device. 56 | #[derive(Debug, Eq, PartialEq)] 57 | /// Generic digital input ADI device. 58 | pub struct AdiDigitalIn { 59 | port: AdiPort, 60 | } 61 | 62 | impl AdiDigitalIn { 63 | /// Create a digital input from an ADI port. 64 | pub fn new(port: AdiPort) -> Result { 65 | bail_on!(PROS_ERR, unsafe { 66 | pros_sys::ext_adi_port_set_config( 67 | port.internal_expander_index(), 68 | port.index(), 69 | pros_sys::E_ADI_DIGITAL_IN, 70 | ) 71 | }); 72 | 73 | Ok(Self { port }) 74 | } 75 | 76 | /// Gets the current logic level of a digital input pin. 77 | pub fn level(&self) -> Result { 78 | let value = bail_on!(PROS_ERR, unsafe { 79 | pros_sys::ext_adi_digital_read(self.port.internal_expander_index(), self.port.index()) 80 | }) != 0; 81 | 82 | Ok(match value { 83 | true => LogicLevel::High, 84 | false => LogicLevel::Low, 85 | }) 86 | } 87 | 88 | /// Returns `true` if the digital input's logic level level is [`LogicLevel::High`]. 89 | pub fn is_high(&self) -> Result { 90 | Ok(self.level()?.is_high()) 91 | } 92 | 93 | /// Returns `true` if the digital input's logic level level is [`LogicLevel::Low`]. 94 | pub fn is_low(&self) -> Result { 95 | Ok(self.level()?.is_high()) 96 | } 97 | } 98 | 99 | impl AdiDevice for AdiDigitalIn { 100 | type PortIndexOutput = u8; 101 | 102 | fn port_index(&self) -> Self::PortIndexOutput { 103 | self.port.index() 104 | } 105 | 106 | fn expander_port_index(&self) -> Option { 107 | self.port.expander_index() 108 | } 109 | 110 | fn device_type(&self) -> AdiDeviceType { 111 | AdiDeviceType::DigitalIn 112 | } 113 | } 114 | 115 | /// Generic digital output ADI device. 116 | #[derive(Debug, Eq, PartialEq)] 117 | pub struct AdiDigitalOut { 118 | port: AdiPort, 119 | } 120 | 121 | impl AdiDigitalOut { 122 | /// Create a digital output from an [`AdiPort`]. 123 | pub fn new(port: AdiPort) -> Result { 124 | bail_on!(PROS_ERR, unsafe { 125 | pros_sys::ext_adi_port_set_config( 126 | port.internal_expander_index(), 127 | port.index(), 128 | pros_sys::E_ADI_DIGITAL_OUT, 129 | ) 130 | }); 131 | 132 | Ok(Self { port }) 133 | } 134 | 135 | /// Sets the digital logic level (high or low) of a pin. 136 | pub fn set_level(&mut self, level: LogicLevel) -> Result<(), AdiError> { 137 | bail_on!(PROS_ERR, unsafe { 138 | pros_sys::ext_adi_digital_write( 139 | self.port.internal_expander_index(), 140 | self.port.index(), 141 | level.is_high(), 142 | ) 143 | }); 144 | 145 | Ok(()) 146 | } 147 | 148 | /// Set the digital logic level to [`LogicLevel::High`]. Analagous to 149 | /// [`Self::set_level(LogicLevel::High)`]. 150 | pub fn set_high(&mut self) -> Result<(), AdiError> { 151 | self.set_level(LogicLevel::High) 152 | } 153 | 154 | /// Set the digital logic level to [`LogicLevel::Low`]. Analagous to 155 | /// [`Self::set_level(LogicLevel::Low)`]. 156 | pub fn set_low(&mut self) -> Result<(), AdiError> { 157 | self.set_level(LogicLevel::Low) 158 | } 159 | } 160 | 161 | impl AdiDevice for AdiDigitalOut { 162 | type PortIndexOutput = u8; 163 | 164 | fn port_index(&self) -> Self::PortIndexOutput { 165 | self.port.index() 166 | } 167 | 168 | fn expander_port_index(&self) -> Option { 169 | self.port.expander_index() 170 | } 171 | 172 | fn device_type(&self) -> AdiDeviceType { 173 | AdiDeviceType::DigitalOut 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/analog.rs: -------------------------------------------------------------------------------- 1 | //! ADI Analog Interfaces 2 | //! 3 | //! # Overview 4 | //! 5 | //! Unlike digital ADI devices which can only report a "high" or "low" state, analog 6 | //! ADI devices may report a wide range of values spanning 0-5 volts. These analog 7 | //! voltages readings are then converted into a digital values using the internal 8 | //! Analog-to-Digital Converter (ADC) in the V5 brain. The brain measures analog input 9 | //! using 12-bit values ranging from 0 (0V) to 4095 (5V). 10 | 11 | use pros_core::bail_on; 12 | use pros_sys::PROS_ERR; 13 | 14 | use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; 15 | 16 | /// Generic analog input ADI device. 17 | #[derive(Debug, Eq, PartialEq)] 18 | pub struct AdiAnalogIn { 19 | port: AdiPort, 20 | } 21 | 22 | impl AdiAnalogIn { 23 | /// Create a analog input from an ADI port. 24 | pub fn new(port: AdiPort) -> Result { 25 | bail_on!(PROS_ERR, unsafe { 26 | pros_sys::ext_adi_port_set_config( 27 | port.internal_expander_index(), 28 | port.index(), 29 | pros_sys::E_ADI_ANALOG_IN, 30 | ) 31 | }); 32 | 33 | Ok(Self { port }) 34 | } 35 | 36 | /// Calibrates the analog sensor on the specified channel. 37 | /// 38 | /// This method assumes that the true sensor value is 39 | /// not actively changing at this time and computes an average 40 | /// from approximately 500 samples, 1 ms apart, for a 0.5 s period of calibration. 41 | /// 42 | /// The average value thus calculated is returned and stored for later calls 43 | /// to the value_calibrated and value_calibrated_hr functions. 44 | /// 45 | /// These functions will return the difference between this value and the current 46 | /// sensor value when called. 47 | pub fn calibrate(&mut self) -> Result<(), AdiError> { 48 | bail_on!(PROS_ERR, unsafe { 49 | pros_sys::ext_adi_analog_calibrate( 50 | self.port.internal_expander_index(), 51 | self.port.index(), 52 | ) 53 | }); 54 | 55 | Ok(()) 56 | } 57 | 58 | /// Reads an analog input channel and returns the 12-bit value. 59 | /// 60 | /// # Sensor Compatibility 61 | /// 62 | /// The value returned is undefined if the analog pin has been switched to a different mode. 63 | /// The meaning of the returned value varies depending on the sensor attached. 64 | pub fn value(&self) -> Result { 65 | Ok(bail_on!(PROS_ERR, unsafe { 66 | pros_sys::ext_adi_analog_read(self.port.internal_expander_index(), self.port.index()) 67 | }) as u16) 68 | } 69 | 70 | /// Reads an analog input channel and returns the calculated voltage input (0-5V). 71 | /// 72 | /// # Precision 73 | /// 74 | /// This function has a precision of `5.0/4095.0` volts, as ADC reports 12-bit voltage data 75 | /// on a scale of 0-4095. 76 | /// 77 | /// # Sensor Compatibility 78 | /// 79 | /// The value returned is undefined if the analog pin has been switched to a different mode. 80 | /// The meaning of the returned value varies depending on the sensor attached. 81 | pub fn voltage(&self) -> Result { 82 | Ok(self.value()? as f64 / 4095.0 * 5.0) 83 | } 84 | 85 | /// Reads the calibrated value of an analog input channel. 86 | /// 87 | /// The [`Self::calibrate`] function must be run first on that channel. 88 | /// 89 | /// This function is inappropriate for sensor values intended for integration, 90 | /// as round-off error can accumulate causing drift over time. 91 | /// Use [`Self::high_precision_calibrated_value`] instead. 92 | pub fn calibrated_value(&self) -> Result { 93 | Ok(bail_on!(PROS_ERR, unsafe { 94 | pros_sys::ext_adi_analog_read_calibrated( 95 | self.port.internal_expander_index(), 96 | self.port.index(), 97 | ) 98 | }) as i16) 99 | } 100 | 101 | /// Reads the calibrated value of an analog input channel with enhanced precision. 102 | /// 103 | /// The calibrate function must be run first. 104 | /// 105 | /// This is intended for integrated sensor values such as gyros and accelerometers 106 | /// to reduce drift due to round-off, and should not be used on a sensor such as a 107 | /// line tracker or potentiometer. 108 | /// 109 | /// The value returned actually has 16 bits of "precision", 110 | /// even though the ADC only reads 12 bits, 111 | /// so that errors induced by the average value being 112 | /// between two values come out in the wash when integrated over time. 113 | /// 114 | /// Think of the value as the true value times 16. 115 | pub fn high_precision_calibrated_value(&self) -> Result { 116 | Ok(bail_on!(PROS_ERR, unsafe { 117 | pros_sys::ext_adi_analog_read_calibrated_HR( 118 | self.port.internal_expander_index(), 119 | self.port.index(), 120 | ) 121 | }) as i16) 122 | } 123 | } 124 | 125 | impl AdiDevice for AdiAnalogIn { 126 | type PortIndexOutput = u8; 127 | 128 | fn port_index(&self) -> Self::PortIndexOutput { 129 | self.port.index() 130 | } 131 | 132 | fn expander_port_index(&self) -> Option { 133 | self.port.expander_index() 134 | } 135 | 136 | fn device_type(&self) -> AdiDeviceType { 137 | AdiDeviceType::AnalogIn 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /packages/pros-sys/link/v5-common.ld: -------------------------------------------------------------------------------- 1 | /* Define the sections, and where they are mapped in memory */ 2 | SECTIONS 3 | { 4 | /* This will get stripped out before uploading, but we need to place code 5 | here so we can at least link to it (install_hot_table) */ 6 | .hot_init : { 7 | KEEP (*(.hot_magic)) 8 | KEEP (*(.hot_init)) 9 | } > HOT_MEMORY 10 | 11 | .text : { 12 | KEEP (*(.vectors)) 13 | /* boot data should be exactly 32 bytes long */ 14 | *(.boot_data) 15 | . = 0x20; 16 | *(.boot) 17 | . = ALIGN(64); 18 | *(.freertos_vectors) 19 | *(.text) 20 | *(.text.*) 21 | *(.gnu.linkonce.t.*) 22 | *(.plt) 23 | *(.gnu_warning) 24 | *(.gcc_except_table) 25 | *(.glue_7) 26 | *(.glue_7t) 27 | *(.vfp11_veneer) 28 | *(.ARM.extab) 29 | *(.gnu.linkonce.armextab.*) 30 | } > RAM 31 | 32 | .init : { 33 | KEEP (*(.init)) 34 | } > RAM 35 | 36 | .fini : { 37 | KEEP (*(.fini)) 38 | } > RAM 39 | 40 | .rodata : { 41 | __rodata_start = .; 42 | *(.rodata) 43 | *(.rodata.*) 44 | *(.gnu.linkonce.r.*) 45 | __rodata_end = .; 46 | } > RAM 47 | 48 | .rodata1 : { 49 | __rodata1_start = .; 50 | *(.rodata1) 51 | *(.rodata1.*) 52 | __rodata1_end = .; 53 | } > RAM 54 | 55 | .sdata2 : { 56 | __sdata2_start = .; 57 | *(.sdata2) 58 | *(.sdata2.*) 59 | *(.gnu.linkonce.s2.*) 60 | __sdata2_end = .; 61 | } > RAM 62 | 63 | .sbss2 : { 64 | __sbss2_start = .; 65 | *(.sbss2) 66 | *(.sbss2.*) 67 | *(.gnu.linkonce.sb2.*) 68 | __sbss2_end = .; 69 | } > RAM 70 | 71 | .data : { 72 | __data_start = .; 73 | *(.data) 74 | *(.data.*) 75 | *(.gnu.linkonce.d.*) 76 | *(.jcr) 77 | *(.got) 78 | *(.got.plt) 79 | __data_end = .; 80 | } > RAM 81 | 82 | .data1 : { 83 | __data1_start = .; 84 | *(.data1) 85 | *(.data1.*) 86 | __data1_end = .; 87 | } > RAM 88 | 89 | .got : { 90 | *(.got) 91 | } > RAM 92 | 93 | .ctors : { 94 | __CTOR_LIST__ = .; 95 | ___CTORS_LIST___ = .; 96 | KEEP (*crtbegin.o(.ctors)) 97 | KEEP (*(EXCLUDE_FILE(*crtend.o) .ctors)) 98 | KEEP (*(SORT(.ctors.*))) 99 | KEEP (*(.ctors)) 100 | __CTOR_END__ = .; 101 | ___CTORS_END___ = .; 102 | } > RAM 103 | 104 | .dtors : { 105 | __DTOR_LIST__ = .; 106 | ___DTORS_LIST___ = .; 107 | KEEP (*crtbegin.o(.dtors)) 108 | KEEP (*(EXCLUDE_FILE(*crtend.o) .dtors)) 109 | KEEP (*(SORT(.dtors.*))) 110 | KEEP (*(.dtors)) 111 | __DTOR_END__ = .; 112 | ___DTORS_END___ = .; 113 | } > RAM 114 | 115 | .fixup : { 116 | __fixup_start = .; 117 | *(.fixup) 118 | __fixup_end = .; 119 | } > RAM 120 | 121 | .eh_frame : { 122 | *(.eh_frame) 123 | } > RAM 124 | 125 | .eh_framehdr : { 126 | __eh_framehdr_start = .; 127 | *(.eh_framehdr) 128 | __eh_framehdr_end = .; 129 | } > RAM 130 | 131 | .gcc_except_table : { 132 | *(.gcc_except_table) 133 | } > RAM 134 | 135 | .mmu_tbl (ALIGN(16384)) : { 136 | __mmu_tbl_start = .; 137 | *(.mmu_tbl) 138 | __mmu_tbl_end = .; 139 | } > RAM 140 | 141 | .ARM.exidx : { 142 | __exidx_start = .; 143 | *(.ARM.exidx*) 144 | *(.gnu.linkonce.armexidix.*.*) 145 | __exidx_end = .; 146 | } > RAM 147 | 148 | .preinit_array : { 149 | __preinit_array_start = .; 150 | KEEP (*(SORT(.preinit_array.*))) 151 | KEEP (*(.preinit_array)) 152 | __preinit_array_end = .; 153 | } > RAM 154 | 155 | .init_array : { 156 | __init_array_start = .; 157 | KEEP (*(SORT(.init_array.*))) 158 | KEEP (*(.init_array)) 159 | __init_array_end = .; 160 | } > RAM 161 | 162 | .fini_array : { 163 | __fini_array_start = .; 164 | KEEP (*(SORT(.fini_array.*))) 165 | KEEP (*(.fini_array)) 166 | __fini_array_end = .; 167 | } > RAM 168 | 169 | .ARM.attributes : { 170 | __ARM.attributes_start = .; 171 | *(.ARM.attributes) 172 | __ARM.attributes_end = .; 173 | } > RAM 174 | 175 | .sdata : { 176 | __sdata_start = .; 177 | *(.sdata) 178 | *(.sdata.*) 179 | *(.gnu.linkonce.s.*) 180 | __sdata_end = .; 181 | } > RAM 182 | 183 | .sbss (NOLOAD) : { 184 | __sbss_start = .; 185 | *(.sbss) 186 | *(.sbss.*) 187 | *(.gnu.linkonce.sb.*) 188 | __sbss_end = .; 189 | } > RAM 190 | 191 | .tdata : { 192 | __tdata_start = .; 193 | *(.tdata) 194 | *(.tdata.*) 195 | *(.gnu.linkonce.td.*) 196 | __tdata_end = .; 197 | } > RAM 198 | 199 | .tbss : { 200 | __tbss_start = .; 201 | *(.tbss) 202 | *(.tbss.*) 203 | *(.gnu.linkonce.tb.*) 204 | __tbss_end = .; 205 | } > RAM 206 | 207 | .bss (NOLOAD) : { 208 | __bss_start = .; 209 | *(.bss) 210 | *(.bss.*) 211 | *(.gnu.linkonce.b.*) 212 | *(COMMON) 213 | __bss_end = .; 214 | } > RAM 215 | 216 | _SDA_BASE_ = __sdata_start + ((__sbss_end - __sdata_start) / 2 ); 217 | 218 | _SDA2_BASE_ = __sdata2_start + ((__sbss2_end - __sdata2_start) / 2 ); 219 | 220 | /* Generate Stack and Heap definitions */ 221 | 222 | .heap (NOLOAD) : { 223 | . = ALIGN(16); 224 | _heap = .; 225 | HeapBase = .; 226 | _heap_start = .; 227 | . += _HEAP_SIZE; 228 | _heap_end = .; 229 | HeapLimit = .; 230 | } > HEAP 231 | 232 | .stack (NOLOAD) : { 233 | . = ALIGN(16); 234 | _stack_end = .; 235 | . += _STACK_SIZE; 236 | . = ALIGN(16); 237 | _stack = .; 238 | __stack = _stack; 239 | . = ALIGN(16); 240 | _irq_stack_end = .; 241 | . += _IRQ_STACK_SIZE; 242 | . = ALIGN(16); 243 | __irq_stack = .; 244 | _supervisor_stack_end = .; 245 | . += _SUPERVISOR_STACK_SIZE; 246 | . = ALIGN(16); 247 | __supervisor_stack = .; 248 | _abort_stack_end = .; 249 | . += _ABORT_STACK_SIZE; 250 | . = ALIGN(16); 251 | __abort_stack = .; 252 | _fiq_stack_end = .; 253 | . += _FIQ_STACK_SIZE; 254 | . = ALIGN(16); 255 | __fiq_stack = .; 256 | _undef_stack_end = .; 257 | . += _UNDEF_STACK_SIZE; 258 | . = ALIGN(16); 259 | __undef_stack = .; 260 | } > COLD_MEMORY 261 | 262 | _end = .; 263 | } 264 | -------------------------------------------------------------------------------- /packages/pros-core/src/time.rs: -------------------------------------------------------------------------------- 1 | //! Temporal quantification. 2 | 3 | use core::{ 4 | fmt, 5 | ops::{Add, AddAssign, Sub, SubAssign}, 6 | time::Duration, 7 | }; 8 | 9 | /// Represents a timestamp on a monotonically nondecreasing clock relative to the 10 | /// start of the user program. 11 | /// 12 | /// # Precision 13 | /// This type has a precision of 1 microsecond, and uses [`pros_sys::micros`] internally. 14 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 15 | pub struct Instant(u64); 16 | 17 | impl Instant { 18 | /// Returns an instant corresponding to "now". 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// use pros::time::Instant; 24 | /// 25 | /// let now = Instant::now(); 26 | /// ``` 27 | pub fn now() -> Self { 28 | Self(unsafe { pros_sys::rtos::micros() }) 29 | } 30 | 31 | /// Returns the amount of time elapsed from another instant to this one, 32 | /// or zero duration if that instant is later than this one. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ```no_run 37 | /// use core::time::Duration; 38 | /// use pros::{time::Instant, task::delay}; 39 | /// 40 | /// let now = Instant::now(); 41 | /// delay(Duration::new(1, 0)); 42 | /// let new_now = Instant::now(); 43 | /// println!("{:?}", new_now.duration_since(now)); 44 | /// println!("{:?}", now.duration_since(new_now)); // 0ns 45 | /// ``` 46 | pub fn duration_since(&self, earlier: Instant) -> Duration { 47 | self.checked_duration_since(earlier).unwrap_or_default() 48 | } 49 | 50 | /// Returns the amount of time elapsed from another instant to this one, 51 | /// or None if that instant is later than this one. 52 | /// 53 | /// # Examples 54 | /// 55 | /// ```no_run 56 | /// use core::time::Duration; 57 | /// use pros::{time::Instant, task::delay}; 58 | /// 59 | /// let now = Instant::now(); 60 | /// delay(Duration::new(1, 0)); 61 | /// let new_now = Instant::now(); 62 | /// println!("{:?}", new_now.checked_duration_since(now)); 63 | /// println!("{:?}", now.checked_duration_since(new_now)); // None 64 | /// ``` 65 | pub const fn checked_duration_since(&self, earlier: Instant) -> Option { 66 | if earlier.0 < self.0 { 67 | Some(Duration::from_micros(self.0 - earlier.0)) 68 | } else { 69 | None 70 | } 71 | } 72 | 73 | /// Returns the amount of time elapsed from another instant to this one, 74 | /// or zero duration if that instant is later than this one. 75 | /// 76 | /// # Examples 77 | /// 78 | /// ```no_run 79 | /// use core::time::Duration; 80 | /// use pros::{time::Instant, task::delay}; 81 | /// 82 | /// let instant = Instant::now(); 83 | /// let three_secs = Duration::from_secs(3); 84 | /// delay(three_secs); 85 | /// assert!(instant.elapsed() >= three_secs); 86 | /// ``` 87 | pub fn saturating_duration_since(&self, earlier: Instant) -> Duration { 88 | self.checked_duration_since(earlier).unwrap_or_default() 89 | } 90 | 91 | /// Returns the amount of time elapsed since this instant. 92 | /// 93 | /// # Examples 94 | /// 95 | /// ```no_run 96 | /// use core::time::Duration; 97 | /// use pros::{time::Instant, task::delay}; 98 | /// 99 | /// let instant = Instant::now(); 100 | /// let three_secs = Duration::from_secs(3); 101 | /// delay(three_secs); 102 | /// assert!(instant.elapsed() >= three_secs); 103 | /// ``` 104 | pub fn elapsed(&self) -> Duration { 105 | Instant::now() - *self 106 | } 107 | 108 | /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as 109 | /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` 110 | /// otherwise. 111 | pub fn checked_add(self, rhs: Duration) -> Option { 112 | Some(Self(self.0.checked_add(rhs.as_micros().try_into().ok()?)?)) 113 | } 114 | 115 | /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as 116 | /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` 117 | /// otherwise. 118 | pub fn checked_sub(self, rhs: Duration) -> Option { 119 | Some(Self(self.0.checked_sub(rhs.as_micros().try_into().ok()?)?)) 120 | } 121 | } 122 | 123 | impl Add for Instant { 124 | type Output = Instant; 125 | 126 | /// # Panics 127 | /// 128 | /// This function may panic if the resulting point in time cannot be represented by the 129 | /// underlying data structure. See [`Instant::checked_add`] for a version without panic. 130 | fn add(self, rhs: Duration) -> Self::Output { 131 | self.checked_add(rhs) 132 | .expect("overflow when adding duration to instant") 133 | } 134 | } 135 | 136 | impl AddAssign for Instant { 137 | fn add_assign(&mut self, other: Duration) { 138 | *self = *self + other; 139 | } 140 | } 141 | 142 | impl Sub for Instant { 143 | type Output = Instant; 144 | 145 | fn sub(self, other: Duration) -> Instant { 146 | self.checked_sub(other) 147 | .expect("overflow when subtracting duration from instant") 148 | } 149 | } 150 | 151 | impl SubAssign for Instant { 152 | fn sub_assign(&mut self, other: Duration) { 153 | *self = *self - other; 154 | } 155 | } 156 | 157 | impl Sub for Instant { 158 | type Output = Duration; 159 | 160 | /// Returns the amount of time elapsed from another instant to this one, 161 | /// or zero duration if that instant is later than this one. 162 | fn sub(self, other: Instant) -> Duration { 163 | self.duration_since(other) 164 | } 165 | } 166 | 167 | impl fmt::Debug for Instant { 168 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 169 | self.0.fmt(f) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "cargo-pros": { 4 | "inputs": { 5 | "flake-utils": "flake-utils", 6 | "nixpkgs": "nixpkgs" 7 | }, 8 | "locked": { 9 | "lastModified": 1703362525, 10 | "narHash": "sha256-QPFRaWCDlHKyFu4P4HCsw8pKkJcznup0NIwOjGkkI7g=", 11 | "owner": "pros-rs", 12 | "repo": "cargo-pros", 13 | "rev": "479331022e4a74a62eaa1f776325c690e449f5dd", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "pros-rs", 18 | "repo": "cargo-pros", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-parts": { 23 | "inputs": { 24 | "nixpkgs-lib": "nixpkgs-lib" 25 | }, 26 | "locked": { 27 | "lastModified": 1688254665, 28 | "narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=", 29 | "owner": "hercules-ci", 30 | "repo": "flake-parts", 31 | "rev": "267149c58a14d15f7f81b4d737308421de9d7152", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "id": "flake-parts", 36 | "type": "indirect" 37 | } 38 | }, 39 | "flake-utils": { 40 | "inputs": { 41 | "systems": "systems" 42 | }, 43 | "locked": { 44 | "lastModified": 1692799911, 45 | "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", 46 | "owner": "numtide", 47 | "repo": "flake-utils", 48 | "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "numtide", 53 | "repo": "flake-utils", 54 | "type": "github" 55 | } 56 | }, 57 | "flake-utils_2": { 58 | "inputs": { 59 | "systems": "systems_2" 60 | }, 61 | "locked": { 62 | "lastModified": 1694529238, 63 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 64 | "owner": "numtide", 65 | "repo": "flake-utils", 66 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "numtide", 71 | "repo": "flake-utils", 72 | "type": "github" 73 | } 74 | }, 75 | "nixpkgs": { 76 | "locked": { 77 | "lastModified": 1694062546, 78 | "narHash": "sha256-PiGI4f2BGnZcedP6slLjCLGLRLXPa9+ogGGgVPfGxys=", 79 | "owner": "NixOS", 80 | "repo": "nixpkgs", 81 | "rev": "b200e0df08f80c32974a6108ce431d8a8a5e6547", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "id": "nixpkgs", 86 | "type": "indirect" 87 | } 88 | }, 89 | "nixpkgs-lib": { 90 | "locked": { 91 | "dir": "lib", 92 | "lastModified": 1688049487, 93 | "narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=", 94 | "owner": "NixOS", 95 | "repo": "nixpkgs", 96 | "rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9", 97 | "type": "github" 98 | }, 99 | "original": { 100 | "dir": "lib", 101 | "owner": "NixOS", 102 | "ref": "nixos-unstable", 103 | "repo": "nixpkgs", 104 | "type": "github" 105 | } 106 | }, 107 | "nixpkgs_2": { 108 | "locked": { 109 | "lastModified": 1681358109, 110 | "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", 111 | "owner": "NixOS", 112 | "repo": "nixpkgs", 113 | "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", 114 | "type": "github" 115 | }, 116 | "original": { 117 | "id": "nixpkgs", 118 | "type": "indirect" 119 | } 120 | }, 121 | "nixpkgs_3": { 122 | "locked": { 123 | "lastModified": 1688231357, 124 | "narHash": "sha256-ZOn16X5jZ6X5ror58gOJAxPfFLAQhZJ6nOUeS4tfFwo=", 125 | "owner": "NixOS", 126 | "repo": "nixpkgs", 127 | "rev": "645ff62e09d294a30de823cb568e9c6d68e92606", 128 | "type": "github" 129 | }, 130 | "original": { 131 | "owner": "NixOS", 132 | "ref": "nixos-unstable", 133 | "repo": "nixpkgs", 134 | "type": "github" 135 | } 136 | }, 137 | "pros-cli-nix": { 138 | "inputs": { 139 | "flake-parts": "flake-parts", 140 | "nixpkgs": "nixpkgs_3" 141 | }, 142 | "locked": { 143 | "lastModified": 1691848327, 144 | "narHash": "sha256-bGijqyZb+orLxmY541hUdVJKa44VB6iV2Aviwn5Ji5k=", 145 | "owner": "BattleCh1cken", 146 | "repo": "pros-cli-nix", 147 | "rev": "4c00d8e3c6caa377e67ce0cf84923feba5533f23", 148 | "type": "github" 149 | }, 150 | "original": { 151 | "owner": "BattleCh1cken", 152 | "repo": "pros-cli-nix", 153 | "type": "github" 154 | } 155 | }, 156 | "root": { 157 | "inputs": { 158 | "cargo-pros": "cargo-pros", 159 | "flake-utils": "flake-utils_2", 160 | "nixpkgs": "nixpkgs_2", 161 | "pros-cli-nix": "pros-cli-nix" 162 | } 163 | }, 164 | "systems": { 165 | "locked": { 166 | "lastModified": 1681028828, 167 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 168 | "owner": "nix-systems", 169 | "repo": "default", 170 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 171 | "type": "github" 172 | }, 173 | "original": { 174 | "owner": "nix-systems", 175 | "repo": "default", 176 | "type": "github" 177 | } 178 | }, 179 | "systems_2": { 180 | "locked": { 181 | "lastModified": 1681028828, 182 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 183 | "owner": "nix-systems", 184 | "repo": "default", 185 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 186 | "type": "github" 187 | }, 188 | "original": { 189 | "owner": "nix-systems", 190 | "repo": "default", 191 | "type": "github" 192 | } 193 | } 194 | }, 195 | "root": "root", 196 | "version": 7 197 | } 198 | -------------------------------------------------------------------------------- /packages/pros-async/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Tiny async runtime and robot traits for `pros-rs`. 2 | //! The async executor supports spawning tasks and blocking on futures. 3 | //! It has a reactor to improve the performance of some futures. 4 | //! It is recommended to use the `AsyncRobot` trait to run robot code. 5 | //! FreeRTOS tasks can still be used, but it is recommended to use only async tasks for performance. 6 | 7 | #![no_std] 8 | #![feature(negative_impls)] 9 | 10 | extern crate alloc; 11 | 12 | use core::{future::Future, task::Poll}; 13 | 14 | use async_task::Task; 15 | use executor::EXECUTOR; 16 | use pros_core::error::Result; 17 | 18 | mod executor; 19 | mod reactor; 20 | 21 | /// Runs a future in the background without having to await it 22 | /// To get the the return value you can await a task. 23 | pub fn spawn(future: impl Future + 'static) -> Task { 24 | executor::EXECUTOR.with(|e| e.spawn(future)) 25 | } 26 | 27 | /// Blocks the current task untill a return value can be extracted from the provided future. 28 | /// Does not poll all futures to completion. 29 | pub fn block_on(future: F) -> F::Output { 30 | executor::EXECUTOR.with(|e| e.block_on(spawn(future))) 31 | } 32 | 33 | /// A future that will complete after the given duration. 34 | /// Sleep futures that are closer to completion are prioritized to improve accuracy. 35 | #[derive(Debug)] 36 | pub struct SleepFuture { 37 | target_millis: u32, 38 | } 39 | impl Future for SleepFuture { 40 | type Output = (); 41 | 42 | fn poll( 43 | self: core::pin::Pin<&mut Self>, 44 | cx: &mut core::task::Context<'_>, 45 | ) -> core::task::Poll { 46 | if self.target_millis < unsafe { pros_sys::millis() } { 47 | Poll::Ready(()) 48 | } else { 49 | EXECUTOR.with(|e| { 50 | e.reactor 51 | .borrow_mut() 52 | .sleepers 53 | .push(cx.waker().clone(), self.target_millis) 54 | }); 55 | Poll::Pending 56 | } 57 | } 58 | } 59 | 60 | /// Returns a future that will complete after the given duration. 61 | pub fn sleep(duration: core::time::Duration) -> SleepFuture { 62 | SleepFuture { 63 | target_millis: unsafe { pros_sys::millis() + duration.as_millis() as u32 }, 64 | } 65 | } 66 | 67 | /// A trait for robot code that spins up the pros-rs async executor. 68 | /// This is the preferred trait to run robot code. 69 | pub trait AsyncRobot { 70 | /// Runs during the operator control period. 71 | /// This function may be called more than once. 72 | /// For that reason, do not use `Peripherals::take`in this function. 73 | fn opcontrol(&mut self) -> impl Future { 74 | async { Ok(()) } 75 | } 76 | /// Runs during the autonomous period. 77 | fn auto(&mut self) -> impl Future { 78 | async { Ok(()) } 79 | } 80 | /// Runs continuously during the disabled period. 81 | fn disabled(&mut self) -> impl Future { 82 | async { Ok(()) } 83 | } 84 | /// Runs once when the competition system is initialized. 85 | fn comp_init(&mut self) -> impl Future { 86 | async { Ok(()) } 87 | } 88 | } 89 | 90 | #[doc(hidden)] 91 | #[macro_export] 92 | macro_rules! __gen_async_exports { 93 | ($rbt:ty) => { 94 | pub static mut ROBOT: Option<$rbt> = None; 95 | 96 | #[doc(hidden)] 97 | #[no_mangle] 98 | extern "C" fn opcontrol() { 99 | $crate::block_on(<$rbt as $crate::AsyncRobot>::opcontrol(unsafe { 100 | ROBOT 101 | .as_mut() 102 | .expect("Expected initialize to run before opcontrol") 103 | })) 104 | .unwrap(); 105 | } 106 | 107 | #[doc(hidden)] 108 | #[no_mangle] 109 | extern "C" fn autonomous() { 110 | $crate::block_on(<$rbt as $crate::AsyncRobot>::auto(unsafe { 111 | ROBOT 112 | .as_mut() 113 | .expect("Expected initialize to run before auto") 114 | })) 115 | .unwrap(); 116 | } 117 | 118 | #[doc(hidden)] 119 | #[no_mangle] 120 | extern "C" fn disabled() { 121 | $crate::block_on(<$rbt as $crate::AsyncRobot>::disabled(unsafe { 122 | ROBOT 123 | .as_mut() 124 | .expect("Expected initialize to run before disabled") 125 | })) 126 | .unwrap(); 127 | } 128 | 129 | #[doc(hidden)] 130 | #[no_mangle] 131 | extern "C" fn competition_initialize() { 132 | $crate::block_on(<$rbt as $crate::AsyncRobot>::comp_init(unsafe { 133 | ROBOT 134 | .as_mut() 135 | .expect("Expected initialize to run before comp_init") 136 | })) 137 | .unwrap(); 138 | } 139 | }; 140 | } 141 | 142 | /// Allows your async robot code to be executed by the pros kernel. 143 | /// If your robot struct implements Default then you can just supply this macro with its type. 144 | /// If not, you can supply an expression that returns your robot type to initialize your robot struct. 145 | /// The code that runs to create your robot struct will run in the initialize function in PROS. 146 | /// 147 | /// Example of using the macro with a struct that implements Default: 148 | /// ```rust 149 | /// use pros::prelude::*; 150 | /// #[derive(Default)] 151 | /// struct ExampleRobot; 152 | /// #[async_trait] 153 | /// impl AsyncRobot for ExampleRobot { 154 | /// asnyc fn opcontrol(&mut self) -> pros::Result { 155 | /// println!("Hello, world!"); 156 | /// Ok(()) 157 | /// } 158 | /// } 159 | /// async_robot!(ExampleRobot); 160 | /// ``` 161 | /// 162 | /// Example of using the macro with a struct that does not implement Default: 163 | /// ```rust 164 | /// use pros::prelude::*; 165 | /// struct ExampleRobot { 166 | /// x: i32, 167 | /// } 168 | /// #[async_trait] 169 | /// impl AsyncRobot for ExampleRobot { 170 | /// async fn opcontrol(&mut self) -> pros::Result { 171 | /// println!("Hello, world! {}", self.x); 172 | /// Ok(()) 173 | /// } 174 | /// } 175 | /// impl ExampleRobot { 176 | /// pub fn new() -> Self { 177 | /// Self { x: 5 } 178 | /// } 179 | /// } 180 | /// async_robot!(ExampleRobot, ExampleRobot::new()); 181 | #[macro_export] 182 | macro_rules! async_robot { 183 | ($rbt:ty) => { 184 | $crate::__gen_async_exports!($rbt); 185 | 186 | #[no_mangle] 187 | extern "C" fn initialize() { 188 | let robot = Default::default(); 189 | unsafe { 190 | ROBOT = Some(robot); 191 | } 192 | } 193 | }; 194 | ($rbt:ty, $init:expr) => { 195 | $crate::__gen_async_exports!($rbt); 196 | 197 | #[no_mangle] 198 | extern "C" fn initialize() { 199 | let robot = $init; 200 | unsafe { 201 | ROBOT = Some(robot); 202 | } 203 | } 204 | }; 205 | } 206 | -------------------------------------------------------------------------------- /packages/pros-sys/src/colors.rs: -------------------------------------------------------------------------------- 1 | pub const COLOR_ALICE_BLUE: u32 = 0x00F0F8FF; 2 | pub const COLOR_ANTIQUE_WHITE: u32 = 0x00FAEBD7; 3 | pub const COLOR_AQUA: u32 = 0x0000FFFF; 4 | pub const COLOR_AQUAMARINE: u32 = 0x007FFFD4; 5 | pub const COLOR_AZURE: u32 = 0x00F0FFFF; 6 | pub const COLOR_BEIGE: u32 = 0x00F5F5DC; 7 | pub const COLOR_BISQUE: u32 = 0x00FFE4C4; 8 | pub const COLOR_BLACK: u32 = 0x00000000; 9 | pub const COLOR_BLANCHED_ALMOND: u32 = 0x00FFEBCD; 10 | pub const COLOR_BLUE: u32 = 0x000000FF; 11 | pub const COLOR_BLUE_VIOLET: u32 = 0x008A2BE2; 12 | pub const COLOR_BROWN: u32 = 0x00A52A2A; 13 | pub const COLOR_BURLY_WOOD: u32 = 0x00DEB887; 14 | pub const COLOR_CADET_BLUE: u32 = 0x005F9EA0; 15 | pub const COLOR_CHARTREUSE: u32 = 0x007FFF00; 16 | pub const COLOR_CHOCOLATE: u32 = 0x00D2691E; 17 | pub const COLOR_CORAL: u32 = 0x00FF7F50; 18 | pub const COLOR_CORNFLOWER_BLUE: u32 = 0x006495ED; 19 | pub const COLOR_CORNSILK: u32 = 0x00FFF8DC; 20 | pub const COLOR_CRIMSON: u32 = 0x00DC143C; 21 | pub const COLOR_CYAN: u32 = 0x0000FFFF; 22 | pub const COLOR_DARK_BLUE: u32 = 0x0000008B; 23 | pub const COLOR_DARK_CYAN: u32 = 0x00008B8B; 24 | pub const COLOR_DARK_GOLDENROD: u32 = 0x00B8860B; 25 | pub const COLOR_DARK_GRAY: u32 = 0x00A9A9A9; 26 | pub const COLOR_DARK_GREEN: u32 = 0x00006400; 27 | pub const COLOR_DARK_KHAKI: u32 = 0x00BDB76B; 28 | pub const COLOR_DARK_MAGENTA: u32 = 0x008B008B; 29 | pub const COLOR_DARK_OLIVE_GREEN: u32 = 0x00556B2F; 30 | pub const COLOR_DARK_ORANGE: u32 = 0x00FF8C00; 31 | pub const COLOR_DARK_ORCHID: u32 = 0x009932CC; 32 | pub const COLOR_DARK_RED: u32 = 0x008B0000; 33 | pub const COLOR_DARK_SALMON: u32 = 0x00E9967A; 34 | pub const COLOR_DARK_SEA_GREEN: u32 = 0x008FBC8F; 35 | pub const COLOR_DARK_SLATE_GRAY: u32 = 0x002F4F4F; 36 | pub const COLOR_DARK_TURQUOISE: u32 = 0x0000CED1; 37 | pub const COLOR_DARK_VIOLET: u32 = 0x009400D3; 38 | pub const COLOR_DEEP_PINK: u32 = 0x00FF1493; 39 | pub const COLOR_DEEP_SKY_BLUE: u32 = 0x0000BFFF; 40 | pub const COLOR_DIM_GRAY: u32 = 0x00696969; 41 | pub const COLOR_DODGER_BLUE: u32 = 0x001E90FF; 42 | pub const COLOR_FIRE_BRICK: u32 = 0x00B22222; 43 | pub const COLOR_FLORAL_WHITE: u32 = 0x00FFFAF0; 44 | pub const COLOR_FOREST_GREEN: u32 = 0x00228B22; 45 | pub const COLOR_FUCHSIA: u32 = 0x00FF00FF; 46 | pub const COLOR_GAINSBORO: u32 = 0x00DCDCDC; 47 | pub const COLOR_GHOST_WHITE: u32 = 0x00F8F8FF; 48 | pub const COLOR_GOLD: u32 = 0x00FFD700; 49 | pub const COLOR_GOLDENROD: u32 = 0x00DAA520; 50 | pub const COLOR_GRAY: u32 = 0x00808080; 51 | pub const COLOR_GREEN: u32 = 0x00008000; 52 | pub const COLOR_GREEN_YELLOW: u32 = 0x00ADFF2F; 53 | pub const COLOR_HONEYDEW: u32 = 0x00F0FFF0; 54 | pub const COLOR_HOT_PINK: u32 = 0x00FF69B4; 55 | pub const COLOR_INDIAN_RED: u32 = 0x00CD5C5C; 56 | pub const COLOR_INDIGO: u32 = 0x004B0082; 57 | pub const COLOR_IVORY: u32 = 0x00FFFFF0; 58 | pub const COLOR_KHAKI: u32 = 0x00F0E68C; 59 | pub const COLOR_LAVENDER: u32 = 0x00E6E6FA; 60 | pub const COLOR_LAVENDER_BLUSH: u32 = 0x00FFF0F5; 61 | pub const COLOR_LAWN_GREEN: u32 = 0x007CFC00; 62 | pub const COLOR_LEMON_CHIFFON: u32 = 0x00FFFACD; 63 | pub const COLOR_LIGHT_BLUE: u32 = 0x00ADD8E6; 64 | pub const COLOR_LIGHT_CORAL: u32 = 0x00F08080; 65 | pub const COLOR_LIGHT_CYAN: u32 = 0x00E0FFFF; 66 | pub const COLOR_LIGHT_GOLDENROD_YELLOW: u32 = 0x00FAFAD2; 67 | pub const COLOR_LIGHT_GREEN: u32 = 0x0090EE90; 68 | pub const COLOR_LIGHT_GRAY: u32 = 0x00D3D3D3; 69 | pub const COLOR_LIGHT_PINK: u32 = 0x00FFB6C1; 70 | pub const COLOR_LIGHT_SALMON: u32 = 0x00FFA07A; 71 | pub const COLOR_LIGHT_SEA_GREEN: u32 = 0x0020B2AA; 72 | pub const COLOR_LIGHT_SKY_BLUE: u32 = 0x0087CEFA; 73 | pub const COLOR_LIGHT_SLATE_GRAY: u32 = 0x00778899; 74 | pub const COLOR_LIGHT_STEEL_BLUE: u32 = 0x00B0C4DE; 75 | pub const COLOR_LIGHT_YELLOW: u32 = 0x00FFFFE0; 76 | pub const COLOR_LIME: u32 = 0x0000FF00; 77 | pub const COLOR_LIME_GREEN: u32 = 0x0032CD32; 78 | pub const COLOR_LINEN: u32 = 0x00FAF0E6; 79 | pub const COLOR_MAGENTA: u32 = 0x00FF00FF; 80 | pub const COLOR_MAROON: u32 = 0x00800000; 81 | pub const COLOR_MEDIUM_AQUAMARINE: u32 = 0x0066CDAA; 82 | pub const COLOR_MEDIUM_BLUE: u32 = 0x000000CD; 83 | pub const COLOR_MEDIUM_ORCHID: u32 = 0x00BA55D3; 84 | pub const COLOR_MEDIUM_PURPLE: u32 = 0x009370DB; 85 | pub const COLOR_MEDIUM_SEA_GREEN: u32 = 0x003CB371; 86 | pub const COLOR_MEDIUM_SLATE_BLUE: u32 = 0x007B68EE; 87 | pub const COLOR_MEDIUM_SPRING_GREEN: u32 = 0x0000FA9A; 88 | pub const COLOR_MEDIUM_TURQUOISE: u32 = 0x0048D1CC; 89 | pub const COLOR_MEDIUM_VIOLET_RED: u32 = 0x00C71585; 90 | pub const COLOR_MIDNIGHT_BLUE: u32 = 0x00191970; 91 | pub const COLOR_MINT_CREAM: u32 = 0x00F5FFFA; 92 | pub const COLOR_MISTY_ROSE: u32 = 0x00FFE4E1; 93 | pub const COLOR_MOCCASIN: u32 = 0x00FFE4B5; 94 | pub const COLOR_NAVAJO_WHITE: u32 = 0x00FFDEAD; 95 | pub const COLOR_NAVY: u32 = 0x00000080; 96 | pub const COLOR_OLD_LACE: u32 = 0x00FDF5E6; 97 | pub const COLOR_OLIVE: u32 = 0x00808000; 98 | pub const COLOR_OLIVE_DRAB: u32 = 0x006B8E23; 99 | pub const COLOR_ORANGE: u32 = 0x00FFA500; 100 | pub const COLOR_ORANGE_RED: u32 = 0x00FF4500; 101 | pub const COLOR_ORCHID: u32 = 0x00DA70D6; 102 | pub const COLOR_PALE_GOLDENROD: u32 = 0x00EEE8AA; 103 | pub const COLOR_PALE_GREEN: u32 = 0x0098FB98; 104 | pub const COLOR_PALE_TURQUOISE: u32 = 0x00AFEEEE; 105 | pub const COLOR_PALE_VIOLET_RED: u32 = 0x00DB7093; 106 | pub const COLOR_PAPAY_WHIP: u32 = 0x00FFEFD5; 107 | pub const COLOR_PEACH_PUFF: u32 = 0x00FFDAB9; 108 | pub const COLOR_PERU: u32 = 0x00CD853F; 109 | pub const COLOR_PINK: u32 = 0x00FFC0CB; 110 | pub const COLOR_PLUM: u32 = 0x00DDA0DD; 111 | pub const COLOR_POWDER_BLUE: u32 = 0x00B0E0E6; 112 | pub const COLOR_PURPLE: u32 = 0x00800080; 113 | pub const COLOR_RED: u32 = 0x00FF0000; 114 | pub const COLOR_ROSY_BROWN: u32 = 0x00BC8F8F; 115 | pub const COLOR_ROYAL_BLUE: u32 = 0x004169E1; 116 | pub const COLOR_SADDLE_BROWN: u32 = 0x008B4513; 117 | pub const COLOR_SALMON: u32 = 0x00FA8072; 118 | pub const COLOR_SANDY_BROWN: u32 = 0x00F4A460; 119 | pub const COLOR_SEA_GREEN: u32 = 0x002E8B57; 120 | pub const COLOR_SEASHELL: u32 = 0x00FFF5EE; 121 | pub const COLOR_SIENNA: u32 = 0x00A0522D; 122 | pub const COLOR_SILVER: u32 = 0x00C0C0C0; 123 | pub const COLOR_SKY_BLUE: u32 = 0x0087CEEB; 124 | pub const COLOR_SLATE_BLUE: u32 = 0x006A5ACD; 125 | pub const COLOR_SLATE_GRAY: u32 = 0x00708090; 126 | pub const COLOR_SNOW: u32 = 0x00FFFAFA; 127 | pub const COLOR_SPRING_GREEN: u32 = 0x0000FF7F; 128 | pub const COLOR_STEEL_BLUE: u32 = 0x004682B4; 129 | pub const COLOR_TAN: u32 = 0x00D2B48C; 130 | pub const COLOR_TEAL: u32 = 0x00008080; 131 | pub const COLOR_THISTLE: u32 = 0x00D8BFD8; 132 | pub const COLOR_TOMATO: u32 = 0x00FF6347; 133 | pub const COLOR_TURQUOISE: u32 = 0x0040E0D0; 134 | pub const COLOR_VIOLET: u32 = 0x00EE82EE; 135 | pub const COLOR_WHEAT: u32 = 0x00F5DEB3; 136 | pub const COLOR_WHITE: u32 = 0x00FFFFFF; 137 | pub const COLOR_WHITE_SMOKE: u32 = 0x00F5F5F5; 138 | pub const COLOR_YELLOW: u32 = 0x00FFFF00; 139 | pub const COLOR_YELLOW_GREEN: u32 = 0x009ACD32; 140 | pub const COLOR_DARK_GREY: u32 = COLOR_DARK_GRAY; 141 | pub const COLOR_DARK_SLATE_GREY: u32 = COLOR_DARK_SLATE_GRAY; 142 | pub const COLOR_DIM_GREY: u32 = COLOR_DIM_GRAY; 143 | pub const COLOR_GREY: u32 = COLOR_GRAY; 144 | pub const COLOR_LIGHT_GREY: u32 = COLOR_LIGHT_GRAY; 145 | pub const COLOR_LIGHT_SLATE_GREY: u32 = COLOR_LIGHT_SLATE_GRAY; 146 | pub const COLOR_SLATE_GREY: u32 = COLOR_SLATE_GRAY; 147 | -------------------------------------------------------------------------------- /packages/pros-sys/src/rotation.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_uint; 2 | 3 | pub const ROTATION_MINIMUM_DATA_RATE: c_uint = 5; 4 | 5 | extern "C" { 6 | /** 7 | Reset Rotation Sensor 8 | 9 | Reset the current absolute position to be the same as the 10 | Rotation Sensor angle. 11 | 12 | This function uses the following values of errno when an error state is 13 | reached: 14 | ENXIO - The given value is not within the range of V5 ports (1-21). 15 | ENODEV - The port cannot be configured as an Rotation Sensor 16 | 17 | \param port 18 | The V5 Rotation Sensor port number from 1-21 19 | \return 1 if the operation was successful or PROS_ERR if the operation 20 | failed, setting errno. 21 | */ 22 | pub fn rotation_reset(port: u8) -> i32; 23 | /** 24 | Set the Rotation Sensor's refresh interval in milliseconds. 25 | 26 | The rate may be specified in increments of 5ms, and will be rounded down to 27 | the nearest increment. The minimum allowable refresh rate is 5ms. The default 28 | rate is 10ms. 29 | 30 | This function uses the following values of errno when an error state is 31 | reached: 32 | ENXIO - The given value is not within the range of V5 ports (1-21). 33 | ENODEV - The port cannot be configured as an Rotation Sensor 34 | 35 | \param port 36 | The V5 Rotation Sensor port number from 1-21 37 | \param rate The data refresh interval in milliseconds 38 | \return 1 if the operation was successful or PROS_ERR if the operation 39 | failed, setting errno. 40 | */ 41 | pub fn rotation_set_data_rate(port: u8, rate: u32) -> i32; 42 | /** 43 | Set the Rotation Sensor position reading to a desired rotation value 44 | 45 | This function uses the following values of errno when an error state is 46 | reached: 47 | ENXIO - The given value is not within the range of V5 ports (1-21). 48 | ENODEV - The port cannot be configured as an Rotation Sensor 49 | 50 | \param port 51 | The V5 Rotation Sensor port number from 1-21 52 | \param position 53 | The position in terms of ticks 54 | \return 1 if the operation was successful or PROS_ERR if the operation 55 | failed, setting errno. 56 | */ 57 | pub fn rotation_set_position(port: u8, position: u32) -> i32; 58 | /** 59 | Reset the Rotation Sensor position to 0 60 | 61 | This function uses the following values of errno when an error state is 62 | reached: 63 | ENXIO - The given value is not within the range of V5 ports (1-21). 64 | ENODEV - The port cannot be configured as an Rotation Sensor 65 | 66 | \param port 67 | The V5 Rotation Sensor port number from 1-2 68 | \return 1 if the operation was successful or PROS_ERR if the operation 69 | failed, setting errno. 70 | */ 71 | pub fn rotation_reset_position(port: u8) -> i32; 72 | /** 73 | Get the Rotation Sensor's current position in centidegrees 74 | 75 | This function uses the following values of errno when an error state is 76 | reached: 77 | ENXIO - The given value is not within the range of V5 ports (1-21). 78 | ENODEV - The port cannot be configured as an Rotation Sensor 79 | 80 | \param port 81 | The V5 Rotation Sensor port number from 1-21 82 | \return The position value or PROS_ERR_F if the operation failed, setting 83 | errno. 84 | */ 85 | pub fn rotation_get_position(port: u8) -> i32; 86 | /** 87 | Get the Rotation Sensor's current velocity in centidegrees per second 88 | 89 | This function uses the following values of errno when an error state is 90 | reached: 91 | ENXIO - The given value is not within the range of V5 ports (1-21). 92 | ENODEV - The port cannot be configured as an Rotation Sensor 93 | 94 | \param port 95 | The V5 Rotation Sensor port number from 1-21 96 | \return The velocity value or PROS_ERR_F if the operation failed, setting 97 | errno. 98 | */ 99 | pub fn rotation_get_velocity(port: u8) -> i32; 100 | /** 101 | Get the Rotation Sensor's current angle in centidegrees (0-36000) 102 | 103 | This function uses the following values of errno when an error state is 104 | reached: 105 | ENXIO - The given value is not within the range of V5 ports (1-21). 106 | ENODEV - The port cannot be configured as an Rotation Sensor 107 | 108 | \param port 109 | The V5 Rotation Sensor port number from 1-21 110 | \return The angle value (0-36000) or PROS_ERR_F if the operation failed, setting 111 | errno. 112 | */ 113 | pub fn rotation_get_angle(port: u8) -> i32; 114 | /** 115 | Set the Rotation Sensor's direction reversed flag 116 | 117 | This function uses the following values of errno when an error state is 118 | reached: 119 | ENXIO - The given value is not within the range of V5 ports (1-21). 120 | ENODEV - The port cannot be configured as an Rotation Sensor 121 | 122 | \param port 123 | The V5 Rotation Sensor port number from 1-21 124 | \param value 125 | Determines if the direction of the Rotation Sensor is reversed or not. 126 | 127 | \return 1 if operation succeeded or PROS_ERR if the operation failed, setting 128 | errno. 129 | */ 130 | pub fn rotation_set_reversed(port: u8, value: bool) -> i32; 131 | /** 132 | Reverse the Rotation Sensor's direction 133 | 134 | This function uses the following values of errno when an error state is 135 | reached: 136 | ENXIO - The given value is not within the range of V5 ports (1-21). 137 | ENODEV - The port cannot be configured as an Rotation Sensor 138 | 139 | \param port 140 | The V5 Rotation Sensor port number from 1-21 141 | 142 | \return 1 if the operation was successful or PROS_ERR if the operation 143 | failed, setting errno. 144 | */ 145 | pub fn rotation_reverse(port: u8) -> i32; 146 | /** 147 | Initialize the Rotation Sensor with a reverse flag 148 | 149 | This function uses the following values of errno when an error state is 150 | reached: 151 | ENXIO - The given value is not within the range of V5 ports (1-21). 152 | ENODEV - The port cannot be configured as an Rotation Sensor 153 | 154 | \param port 155 | The V5 Rotation Sensor port number from 1-21 156 | \param reverse_flag 157 | Determines if the Rotation Sensor is reversed or not. 158 | 159 | \return 1 if the operation was successful or PROS_ERR if the operation 160 | failed, setting errno. 161 | */ 162 | pub fn rotation_init_reverse(port: u8, reverse_flag: bool) -> i32; 163 | /** 164 | Get the Rotation Sensor's reversed flag 165 | 166 | This function uses the following values of errno when an error state is 167 | reached: 168 | ENXIO - The given value is not within the range of V5 ports (1-21). 169 | ENODEV - The port cannot be configured as an Rotation Sensor 170 | 171 | \param port 172 | The V5 Rotation Sensor port number from 1-21 173 | 174 | \return Boolean value of if the Rotation Sensor's direction is reversed or not 175 | or PROS_ERR if the operation failed, setting errno. 176 | */ 177 | pub fn rotation_get_reversed(port: u8) -> i32; 178 | } 179 | -------------------------------------------------------------------------------- /packages/pros-devices/src/adi/mod.rs: -------------------------------------------------------------------------------- 1 | //! ADI (Triport) devices on the Vex V5. 2 | 3 | use pros_core::{bail_on, error::PortError, map_errno}; 4 | use pros_sys::{adi_port_config_e_t, E_ADI_ERR, PROS_ERR}; 5 | use snafu::Snafu; 6 | 7 | //TODO: much more in depth module documentation for device modules as well as this module. 8 | pub mod analog; 9 | pub mod digital; 10 | pub mod pwm; 11 | 12 | pub mod encoder; 13 | pub mod gyro; 14 | pub mod linetracker; 15 | pub mod motor; 16 | pub mod potentiometer; 17 | pub mod solenoid; 18 | pub mod switch; 19 | pub mod ultrasonic; 20 | 21 | pub use analog::AdiAnalogIn; 22 | pub use digital::{AdiDigitalIn, AdiDigitalOut}; 23 | pub use encoder::AdiEncoder; 24 | pub use gyro::AdiGyro; 25 | pub use linetracker::AdiLineTracker; 26 | pub use motor::AdiMotor; 27 | pub use potentiometer::AdiPotentiometer; 28 | pub use solenoid::AdiSolenoid; 29 | pub use ultrasonic::AdiUltrasonic; 30 | 31 | /// Represents an ADI (three wire) port on a V5 Brain or V5 Three Wire Expander. 32 | #[derive(Debug, Eq, PartialEq)] 33 | pub struct AdiPort { 34 | /// The index of the port (port number). 35 | /// 36 | /// Ports are indexed starting from 1. 37 | index: u8, 38 | 39 | /// The index of this port's associated [`AdiExpander`]. 40 | /// 41 | /// If this port is not associated with an [`AdiExpander`] it should be set to `None`. 42 | expander_index: Option, 43 | } 44 | 45 | impl AdiPort { 46 | /// Create a new port. 47 | /// 48 | /// # Safety 49 | /// 50 | /// Creating new `AdiPort`s is inherently unsafe due to the possibility of constructing 51 | /// more than one device on the same port index allowing multiple mutable references to 52 | /// the same hardware device. Prefer using [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible. 53 | pub const unsafe fn new(index: u8, expander_index: Option) -> Self { 54 | Self { 55 | index, 56 | expander_index, 57 | } 58 | } 59 | 60 | /// Get the index of the port (port number). 61 | /// 62 | /// Ports are indexed starting from 1. 63 | pub const fn index(&self) -> u8 { 64 | self.index 65 | } 66 | 67 | /// Get the index of this port's associated [`AdiExpander`] smart port, or `None` if this port is not 68 | /// associated with an expander. 69 | pub const fn expander_index(&self) -> Option { 70 | self.expander_index 71 | } 72 | 73 | /// Get the index of this port's associated [`AdiExpander`] smart port, or `pros_sys::adi::INTERNAL_ADI_PORT` 74 | /// if this port is not associated with an expander. 75 | pub(crate) fn internal_expander_index(&self) -> u8 { 76 | self.expander_index 77 | .unwrap_or(pros_sys::adi::INTERNAL_ADI_PORT as u8) 78 | } 79 | 80 | /// Get the type of device this port is currently configured as. 81 | pub fn configured_type(&self) -> Result { 82 | bail_on!(PROS_ERR, unsafe { 83 | pros_sys::ext_adi::ext_adi_port_get_config(self.internal_expander_index(), self.index()) 84 | }) 85 | .try_into() 86 | } 87 | } 88 | 89 | /// Common functionality for a ADI (three-wire) devices. 90 | pub trait AdiDevice { 91 | /// The type that port_index should return. This is usually `u8`, but occasionally `(u8, u8)`. 92 | type PortIndexOutput; 93 | 94 | /// Get the index of the [`AdiPort`] this device is registered on. 95 | /// 96 | /// Ports are indexed starting from 1. 97 | fn port_index(&self) -> Self::PortIndexOutput; 98 | 99 | /// Get the index of the [`AdiPort`] this device is registered on. 100 | /// 101 | /// Ports are indexed starting from 1. 102 | fn expander_port_index(&self) -> Option; 103 | 104 | /// Get the variant of [`AdiDeviceType`] that this device is associated with. 105 | fn device_type(&self) -> AdiDeviceType; 106 | } 107 | 108 | /// Represents a possible type of device that can be registered on a [`AdiPort`]. 109 | #[repr(i32)] 110 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 111 | pub enum AdiDeviceType { 112 | /// Generic analog input. 113 | AnalogIn = pros_sys::adi::E_ADI_ANALOG_IN, 114 | 115 | /// Generic PWM output. 116 | /// 117 | /// This is actually equivalent `pros_sys::adi::E_ADI_ANALOG_OUT`, which is a misnomer. 118 | /// "Analog Out" in reality outputs an 8-bit PWM value. 119 | PwmOut = pros_sys::adi::E_ADI_ANALOG_OUT, 120 | 121 | /// Generic digital input. 122 | DigitalIn = pros_sys::adi::E_ADI_DIGITAL_IN, 123 | 124 | /// Generic digital output. 125 | DigitalOut = pros_sys::adi::E_ADI_DIGITAL_OUT, 126 | 127 | /// Cortex-era yaw-rate gyroscope. 128 | LegacyGyro = pros_sys::adi::E_ADI_LEGACY_GYRO, 129 | 130 | /// Cortex-era servo motor. 131 | LegacyServo = pros_sys::adi::E_ADI_LEGACY_SERVO, 132 | 133 | /// MC29 Controller Output 134 | /// 135 | /// This differs from [`Self::PwmOut`] in that it is specifically designed for controlling 136 | /// legacy ADI motors. Rather than taking a u8 for output, it takes a i8 allowing negative 137 | /// values to be sent for controlling motors in reverse with a nicer API. 138 | LegacyPwm = pros_sys::adi::E_ADI_LEGACY_PWM, 139 | 140 | /// Cortex-era encoder. 141 | LegacyEncoder = pros_sys::E_ADI_LEGACY_ENCODER, 142 | 143 | /// Cortex-era ultrasonic sensor. 144 | LegacyUltrasonic = pros_sys::E_ADI_LEGACY_ULTRASONIC, 145 | } 146 | 147 | impl TryFrom for AdiDeviceType { 148 | type Error = AdiError; 149 | 150 | fn try_from(value: adi_port_config_e_t) -> Result { 151 | bail_on!(E_ADI_ERR, value); 152 | 153 | match value { 154 | pros_sys::E_ADI_ANALOG_IN => Ok(AdiDeviceType::AnalogIn), 155 | pros_sys::E_ADI_ANALOG_OUT => Ok(AdiDeviceType::PwmOut), 156 | pros_sys::E_ADI_DIGITAL_IN => Ok(AdiDeviceType::DigitalIn), 157 | pros_sys::E_ADI_DIGITAL_OUT => Ok(AdiDeviceType::DigitalOut), 158 | 159 | pros_sys::E_ADI_LEGACY_GYRO => Ok(AdiDeviceType::LegacyGyro), 160 | 161 | pros_sys::E_ADI_LEGACY_SERVO => Ok(AdiDeviceType::LegacyServo), 162 | pros_sys::E_ADI_LEGACY_PWM => Ok(AdiDeviceType::LegacyPwm), 163 | 164 | pros_sys::E_ADI_LEGACY_ENCODER => Ok(AdiDeviceType::LegacyEncoder), 165 | pros_sys::E_ADI_LEGACY_ULTRASONIC => Ok(AdiDeviceType::LegacyUltrasonic), 166 | 167 | _ => Err(AdiError::UnknownDeviceType), 168 | } 169 | } 170 | } 171 | 172 | impl From for adi_port_config_e_t { 173 | fn from(value: AdiDeviceType) -> Self { 174 | value as _ 175 | } 176 | } 177 | 178 | #[derive(Debug, Snafu)] 179 | /// Errors that can occur when working with ADI devices. 180 | pub enum AdiError { 181 | /// Another resource is currently trying to access the ADI. 182 | AlreadyInUse, 183 | 184 | /// PROS returned an unrecognized device type. 185 | UnknownDeviceType, 186 | 187 | /// The port specified has not been configured for the device type specified. 188 | PortNotConfigured, 189 | 190 | /// ADI devices may only be initialized from one expander port. 191 | ExpanderPortMismatch, 192 | 193 | /// A given value is not correct, or the buffer is null. 194 | InvalidValue, 195 | 196 | #[snafu(display("{source}"), context(false))] 197 | /// An error occurred while interacting with a port. 198 | Port { 199 | /// The source of the error 200 | source: PortError, 201 | }, 202 | } 203 | 204 | map_errno! { 205 | AdiError { 206 | EACCES => Self::AlreadyInUse, 207 | EADDRINUSE => Self::PortNotConfigured, 208 | EINVAL => Self::InvalidValue, 209 | } 210 | inherit PortError; 211 | } 212 | -------------------------------------------------------------------------------- /packages/pros-core/src/io/mod.rs: -------------------------------------------------------------------------------- 1 | //! Std-like I/O macros and types for use in pros. 2 | //! 3 | //! Implements `println!`, `eprintln!` and `dbg!` on top of the `pros_sys` crate without requiring 4 | //! the use of an allocator. (Modified version of `libc_print` crate) 5 | //! 6 | //! Allows you to use these macros in a #!\[no_std\] context, or in a situation where the 7 | //! traditional Rust streams might not be available (ie: at process shutdown time). 8 | //! 9 | //! ## Usage 10 | //! 11 | //! Exactly as you'd use `println!`, `eprintln!` and `dbg!`. 12 | //! 13 | //! ```rust 14 | //! # use pros::io::*; 15 | //! // Use the default ``-prefixed macros: 16 | //! # fn test1() 17 | //! # { 18 | //! println!("Hello {}!", "stdout"); 19 | //! eprintln!("Hello {}!", "stderr"); 20 | //! let a = 2; 21 | //! let b = dbg!(a * 2) + 1; 22 | //! assert_eq!(b, 5); 23 | //! # } 24 | //! ``` 25 | //! 26 | //! Or you can import aliases to `std` names: 27 | //! 28 | //! ```rust 29 | //! use pros::io::{println, eprintln, dbg}; 30 | //! 31 | //! # fn test2() 32 | //! # { 33 | //! println!("Hello {}!", "stdout"); 34 | //! eprintln!("Hello {}!", "stderr"); 35 | //! let a = 2; 36 | //! let b = dbg!(a * 2) + 1; 37 | //! assert_eq!(b, 5); 38 | //! # } 39 | //! ``` 40 | 41 | // libc_print is licensed under the MIT License: 42 | 43 | // Copyright (c) 2023 Matt Mastracci and contributors 44 | 45 | // Permission is hereby granted, free of charge, to any person obtaining a copy 46 | // of this software and associated documentation files (the "Software"), to deal 47 | // in the Software without restriction, including without limitation the rights 48 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 49 | // copies of the Software, and to permit persons to whom the Software is 50 | // furnished to do so, subject to the following conditions: 51 | 52 | // The above copyright notice and this permission notice shall be included in all 53 | // copies or substantial portions of the Software. 54 | 55 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 56 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 57 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 58 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 59 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 60 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 61 | // SOFTWARE. 62 | #[allow(unused_imports)] 63 | use core::{convert::TryFrom, file, line, stringify}; 64 | 65 | pub use no_std_io::io::*; 66 | 67 | pub use crate::{dbg, eprint, eprintln, print, println}; 68 | 69 | #[doc(hidden)] 70 | #[allow(missing_debug_implementations)] 71 | pub struct __SerialWriter(i32); 72 | 73 | impl core::fmt::Write for __SerialWriter { 74 | #[inline] 75 | fn write_str(&mut self, s: &str) -> core::fmt::Result { 76 | __println(self.0, s) 77 | } 78 | } 79 | 80 | impl __SerialWriter { 81 | #[inline] 82 | pub const fn new(err: bool) -> __SerialWriter { 83 | __SerialWriter(if err { 2 } else { 1 }) 84 | } 85 | 86 | #[inline] 87 | pub fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> core::fmt::Result { 88 | core::fmt::Write::write_fmt(self, args) 89 | } 90 | 91 | #[inline] 92 | pub fn write_str(&mut self, s: &str) -> core::fmt::Result { 93 | __println(self.0, s) 94 | } 95 | 96 | #[inline] 97 | pub fn write_nl(&mut self) -> core::fmt::Result { 98 | __println(self.0, "\n") 99 | } 100 | } 101 | 102 | #[doc(hidden)] 103 | #[inline] 104 | pub fn __println(handle: i32, msg: &str) -> core::fmt::Result { 105 | let msg = msg.as_bytes(); 106 | 107 | let mut written = 0; 108 | while written < msg.len() { 109 | match unsafe { write(handle, &msg[written..]) } { 110 | // Ignore errors 111 | None | Some(0) => break, 112 | Some(res) => written += res, 113 | } 114 | } 115 | 116 | Ok(()) 117 | } 118 | 119 | unsafe fn write(handle: i32, bytes: &[u8]) -> Option { 120 | usize::try_from(unsafe { 121 | pros_sys::write( 122 | handle, 123 | bytes.as_ptr().cast::(), 124 | bytes.len(), 125 | ) 126 | }) 127 | .ok() 128 | } 129 | 130 | /// Macro for printing to the standard output, with a newline. 131 | /// 132 | /// Does not panic on failure to write - instead silently ignores errors. 133 | /// 134 | /// See [`println!`](https://doc.rust-lang.org/std/macro.println.html) for 135 | /// full documentation. 136 | #[macro_export] 137 | macro_rules! println { 138 | () => { $crate::println!("") }; 139 | ($($arg:tt)*) => { 140 | { 141 | #[allow(unused_must_use)] 142 | { 143 | let mut stm = $crate::io::__SerialWriter::new(false); 144 | stm.write_fmt(format_args!($($arg)*)); 145 | stm.write_nl(); 146 | } 147 | } 148 | }; 149 | } 150 | 151 | /// Macro for printing to the standard output. 152 | /// 153 | /// Does not panic on failure to write - instead silently ignores errors. 154 | /// 155 | /// See [`print!`](https://doc.rust-lang.org/std/macro.print.html) for 156 | /// full documentation. 157 | #[macro_export] 158 | macro_rules! print { 159 | ($($arg:tt)*) => { 160 | { 161 | #[allow(unused_must_use)] 162 | { 163 | let mut stm = $crate::io::__SerialWriter::new(false); 164 | stm.write_fmt(format_args!($($arg)*)); 165 | } 166 | } 167 | }; 168 | } 169 | 170 | /// Macro for printing to the standard error, with a newline. 171 | /// 172 | /// Does not panic on failure to write - instead silently ignores errors. 173 | /// 174 | /// See [`eprintln!`](https://doc.rust-lang.org/std/macro.eprintln.html) for 175 | /// full documentation. 176 | #[macro_export] 177 | macro_rules! eprintln { 178 | () => { $crate::eprintln!("") }; 179 | ($($arg:tt)*) => { 180 | { 181 | #[allow(unused_must_use)] 182 | { 183 | let mut stm = $crate::io::__SerialWriter::new(true); 184 | stm.write_fmt(format_args!($($arg)*)); 185 | stm.write_nl(); 186 | } 187 | } 188 | }; 189 | } 190 | 191 | /// Macro for printing to the standard error. 192 | /// 193 | /// Does not panic on failure to write - instead silently ignores errors. 194 | /// 195 | /// See [`eprint!`](https://doc.rust-lang.org/std/macro.eprint.html) for 196 | /// full documentation. 197 | #[macro_export] 198 | macro_rules! eprint { 199 | ($($arg:tt)*) => { 200 | { 201 | #[allow(unused_must_use)] 202 | { 203 | let mut stm = $crate::io::__SerialWriter::new(true); 204 | stm.write_fmt(format_args!($($arg)*)); 205 | } 206 | } 207 | }; 208 | } 209 | 210 | /// Prints and returns the value of a given expression for quick and dirty 211 | /// debugging. 212 | /// 213 | /// An example: 214 | /// 215 | /// ```rust 216 | /// let a = 2; 217 | /// let b = dbg!(a * 2) + 1; 218 | /// // ^-- prints: [src/main.rs:2] a * 2 = 4 219 | /// assert_eq!(b, 5); 220 | /// ``` 221 | /// 222 | /// See [dbg!](https://doc.rust-lang.org/std/macro.dbg.html) for full documentation. 223 | #[macro_export] 224 | macro_rules! dbg { 225 | () => { 226 | $crate::eprintln!("[{}:{}]", $file!(), $line!()) 227 | }; 228 | ($val:expr $(,)?) => { 229 | match $val { 230 | tmp => { 231 | $crate::eprintln!("[{}:{}] {} = {:#?}", file!(), line!(), stringify!($val), &tmp); 232 | tmp 233 | } 234 | } 235 | }; 236 | ($($val:expr),+ $(,)?) => { 237 | ($($crate::dbg!($val)),+,) 238 | }; 239 | } 240 | -------------------------------------------------------------------------------- /packages/pros-sys/src/serial.rs: -------------------------------------------------------------------------------- 1 | //! Contains prototypes for the V5 Generic Serial related functions. 2 | 3 | extern "C" { 4 | /** 5 | Enables generic serial on the given port. 6 | 7 | \note This function must be called before any of the generic serial 8 | functions will work. 9 | 10 | This function uses the following values of errno when an error state is 11 | reached: 12 | EINVAL - The given value is not within the range of V5 ports (1-21). 13 | EACCES - Another resource is currently trying to access the port. 14 | 15 | \param port 16 | The V5 port number from 1-21 17 | 18 | \return 1 if the operation was successful or PROS_ERR if the operation 19 | failed, setting errno. 20 | */ 21 | pub fn serial_enable(port: u8) -> i32; 22 | /** 23 | Sets the baudrate for the serial port to operate at. 24 | 25 | This function uses the following values of errno when an error state is 26 | reached: 27 | EINVAL - The given value is not within the range of V5 ports (1-21). 28 | EACCES - Another resource is currently trying to access the port. 29 | 30 | \param port 31 | The V5 port number from 1-21 32 | \param baudrate 33 | The baudrate to operate at 34 | 35 | \return 1 if the operation was successful or PROS_ERR if the operation 36 | failed, setting errno. 37 | */ 38 | pub fn serial_set_baudrate(port: u8, baudrate: i32) -> i32; 39 | /** 40 | Clears the internal input and output FIFO buffers. 41 | 42 | This can be useful to reset state and remove old, potentially unneeded data 43 | from the input FIFO buffer or to cancel sending any data in the output FIFO 44 | buffer. 45 | 46 | \note This function does not cause the data in the output buffer to be 47 | written, it simply clears the internal buffers. Unlike stdout, generic 48 | serial does not use buffered IO (the FIFO buffers are written as soon 49 | as possible). 50 | 51 | This function uses the following values of errno when an error state is 52 | reached: 53 | EINVAL - The given value is not within the range of V5 ports (1-21). 54 | EACCES - Another resource is currently trying to access the port. 55 | 56 | \param port 57 | The V5 port number from 1-21 58 | 59 | \return 1 if the operation was successful or PROS_ERR if the operation 60 | failed, setting errno. 61 | */ 62 | pub fn serial_flush(port: u8) -> i32; 63 | /** 64 | Returns the number of bytes available to be read in the the port's FIFO 65 | input buffer. 66 | 67 | \note This function does not actually read any bytes, is simply returns the 68 | number of bytes available to be read. 69 | 70 | This function uses the following values of errno when an error state is 71 | reached: 72 | EINVAL - The given value is not within the range of V5 ports (1-21). 73 | EACCES - Another resource is currently trying to access the port. 74 | 75 | \param port 76 | The V5 port number from 1-21 77 | 78 | \return The number of bytes available to be read or PROS_ERR if the operation 79 | failed, setting errno. 80 | */ 81 | pub fn serial_get_read_avail(port: u8) -> i32; 82 | /** 83 | Returns the number of bytes free in the port's FIFO output buffer. 84 | 85 | \note This function does not actually write any bytes, is simply returns the 86 | number of bytes free in the port's buffer. 87 | 88 | This function uses the following values of errno when an error state is 89 | reached: 90 | EINVAL - The given value is not within the range of V5 ports (1-21). 91 | EACCES - Another resource is currently trying to access the port. 92 | 93 | \param port 94 | The V5 port number from 1-21 95 | 96 | \return The number of bytes free or PROS_ERR if the operation failed, 97 | setting errno. 98 | */ 99 | pub fn serial_get_write_free(port: u8) -> i32; 100 | /** 101 | Reads the next byte available in the port's input buffer without removing it. 102 | 103 | This function uses the following values of errno when an error state is 104 | reached: 105 | EINVAL - The given value is not within the range of V5 ports (1-21). 106 | EACCES - Another resource is currently trying to access the port. 107 | 108 | \param port 109 | The V5 port number from 1-21 110 | 111 | \return The next byte available to be read, -1 if none are available, or 112 | PROS_ERR if the operation failed, setting errno. 113 | */ 114 | pub fn serial_peek_byte(port: u8) -> i32; 115 | /** 116 | Reads the next byte available in the port's input buffer. 117 | 118 | This function uses the following values of errno when an error state is 119 | reached: 120 | EINVAL - The given value is not within the range of V5 ports (1-21). 121 | EACCES - Another resource is currently trying to access the port. 122 | 123 | \param port 124 | The V5 port number from 1-21 125 | 126 | \return The next byte available to be read, -1 if none are available, or 127 | PROS_ERR if the operation failed, setting errno. 128 | */ 129 | pub fn serial_read_byte(port: u8) -> i32; 130 | /** 131 | Reads up to the next length bytes from the port's input buffer and places 132 | them in the user supplied buffer. 133 | 134 | \note This function will only return bytes that are currently available to be 135 | read and will not block waiting for any to arrive. 136 | 137 | This function uses the following values of errno when an error state is 138 | reached: 139 | EINVAL - The given value is not within the range of V5 ports (1-21). 140 | EACCES - Another resource is currently trying to access the port. 141 | 142 | \param port 143 | The V5 port number from 1-21 144 | \param buffer 145 | The location to place the data read 146 | \param length 147 | The maximum number of bytes to read 148 | 149 | \return The number of bytes read or PROS_ERR if the operation failed, setting 150 | errno. 151 | */ 152 | pub fn serial_read(port: u8, buffer: *mut u8, length: i32) -> i32; 153 | /** 154 | Write the given byte to the port's output buffer. 155 | 156 | \note Data in the port's output buffer is written to the serial port as soon 157 | as possible on a FIFO basis and can not be done manually by the user. 158 | 159 | This function uses the following values of errno when an error state is 160 | reached: 161 | EINVAL - The given value is not within the range of V5 ports (1-21). 162 | EACCES - Another resource is currently trying to access the port. 163 | EIO - Serious internal write error. 164 | 165 | \param port 166 | The V5 port number from 1-21 167 | \param buffer 168 | The byte to write 169 | 170 | \return The number of bytes written or PROS_ERR if the operation failed, 171 | setting errno. 172 | */ 173 | pub fn serial_write_byte(port: u8, buffer: u8) -> i32; 174 | /** 175 | Writes up to length bytes from the user supplied buffer to the port's output 176 | buffer. 177 | 178 | \note Data in the port's output buffer is written to the serial port as soon 179 | as possible on a FIFO basis and can not be done manually by the user. 180 | 181 | This function uses the following values of errno when an error state is 182 | reached: 183 | EINVAL - The given value is not within the range of V5 ports (1-21). 184 | EACCES - Another resource is currently trying to access the port. 185 | EIO - Serious internal write error. 186 | 187 | \param port 188 | The V5 port number from 1-21 189 | \param buffer 190 | The data to write 191 | \param length 192 | The maximum number of bytes to write 193 | 194 | \return The number of bytes written or PROS_ERR if the operation failed, 195 | setting errno. 196 | */ 197 | pub fn serial_write(port: u8, buffer: *mut u8, length: i32) -> i32; 198 | } 199 | -------------------------------------------------------------------------------- /packages/pros-devices/src/smart/vision.rs: -------------------------------------------------------------------------------- 1 | //! Vision sensor device. 2 | //! 3 | //! Vision sensors take in a zero point at creation. 4 | 5 | extern crate alloc; 6 | use alloc::vec::Vec; 7 | 8 | use pros_core::{bail_errno, bail_on, error::PortError, map_errno}; 9 | use pros_sys::{PROS_ERR, VISION_OBJECT_ERR_SIG}; 10 | use snafu::Snafu; 11 | 12 | use super::{SmartDevice, SmartDeviceType, SmartPort}; 13 | use crate::color::Rgb; 14 | 15 | /// Represents a vision sensor plugged into the vex. 16 | #[derive(Debug, Eq, PartialEq)] 17 | pub struct VisionSensor { 18 | port: SmartPort, 19 | } 20 | 21 | impl VisionSensor { 22 | /// Creates a new vision sensor. 23 | pub fn new(port: SmartPort, zero: VisionZeroPoint) -> Result { 24 | unsafe { 25 | bail_on!( 26 | PROS_ERR, 27 | pros_sys::vision_set_zero_point(port.index(), zero as _) 28 | ); 29 | } 30 | 31 | Ok(Self { port }) 32 | } 33 | 34 | /// Returns the nth largest object seen by the camera. 35 | pub fn nth_largest_object(&self, n: u32) -> Result { 36 | unsafe { pros_sys::vision_get_by_size(self.port.index(), n).try_into() } 37 | } 38 | 39 | /// Returns a list of all objects in order of size (largest to smallest). 40 | pub fn objects(&self) -> Result, VisionError> { 41 | let obj_count = self.num_objects()?; 42 | let mut objects_buf = Vec::with_capacity(obj_count); 43 | 44 | unsafe { 45 | pros_sys::vision_read_by_size( 46 | self.port.index(), 47 | 0, 48 | obj_count as _, 49 | objects_buf.as_mut_ptr(), 50 | ); 51 | } 52 | 53 | bail_errno!(); 54 | 55 | Ok(objects_buf 56 | .into_iter() 57 | .filter_map(|object| object.try_into().ok()) 58 | .collect()) 59 | } 60 | 61 | /// Returns the number of objects seen by the camera. 62 | pub fn num_objects(&self) -> Result { 63 | unsafe { 64 | Ok(bail_on!( 65 | PROS_ERR, 66 | pros_sys::vision_get_object_count(self.port.index()) 67 | ) 68 | .try_into() 69 | .unwrap()) 70 | } 71 | } 72 | 73 | /// Get the current exposure percentage of the vision sensor. The returned result should be within 0.0 to 1.5. 74 | pub fn exposure(&self) -> f32 { 75 | unsafe { (pros_sys::vision_get_exposure(self.port.index()) as f32) * 1.5 / 150.0 } 76 | } 77 | 78 | /// Get the current white balance of the vision sensor. 79 | pub fn current_white_balance(&self) -> Rgb { 80 | unsafe { (pros_sys::vision_get_white_balance(self.port.index()) as u32).into() } 81 | } 82 | 83 | /// Sets the exposure percentage of the vision sensor. Should be between 0.0 and 1.5. 84 | pub fn set_exposure(&mut self, exposure: f32) { 85 | unsafe { 86 | pros_sys::vision_set_exposure(self.port.index(), (exposure * 150.0 / 1.5) as u8); 87 | } 88 | } 89 | 90 | /// Sets the white balance of the vision sensor. 91 | pub fn set_white_balance(&mut self, white_balance: WhiteBalance) { 92 | unsafe { 93 | match white_balance { 94 | WhiteBalance::Auto => pros_sys::vision_set_auto_white_balance(self.port.index(), 1), 95 | WhiteBalance::Rgb(rgb) => { 96 | // Turn off automatic white balance 97 | pros_sys::vision_set_auto_white_balance(self.port.index(), 0); 98 | pros_sys::vision_set_white_balance( 99 | self.port.index(), 100 | >::into(rgb) as i32, 101 | ) 102 | } 103 | }; 104 | } 105 | } 106 | 107 | /// Sets the point that object positions are relative to, in other words where (0, 0) is or the zero point. 108 | pub fn set_zero_point(&mut self, zero: VisionZeroPoint) { 109 | unsafe { 110 | pros_sys::vision_set_zero_point(self.port.index(), zero as _); 111 | } 112 | } 113 | 114 | /// Sets the color of the led. 115 | pub fn set_led(&mut self, mode: LedMode) { 116 | unsafe { 117 | match mode { 118 | LedMode::Off => pros_sys::vision_clear_led(self.port.index()), 119 | LedMode::On(rgb) => pros_sys::vision_set_led( 120 | self.port.index(), 121 | >::into(rgb) as i32, 122 | ), 123 | }; 124 | } 125 | } 126 | } 127 | 128 | impl SmartDevice for VisionSensor { 129 | fn port_index(&self) -> u8 { 130 | self.port.index() 131 | } 132 | 133 | fn device_type(&self) -> SmartDeviceType { 134 | SmartDeviceType::Vision 135 | } 136 | } 137 | 138 | //TODO: figure out how coordinates are done. 139 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] 140 | /// An object detected by the vision sensor 141 | pub struct VisionObject { 142 | /// The offset from the top of the object to the vision center. 143 | pub top: i16, 144 | /// The offset from the left of the object to the vision center. 145 | pub left: i16, 146 | /// The x-coordinate of the middle of the object relative to the vision center. 147 | pub middle_x: i16, 148 | /// The y-coordinate of the middle of the object relative to the vision center. 149 | pub middle_y: i16, 150 | 151 | /// The width of the object. 152 | pub width: i16, 153 | /// The height of the object. 154 | pub height: i16, 155 | } 156 | 157 | impl TryFrom for VisionObject { 158 | type Error = VisionError; 159 | fn try_from(value: pros_sys::vision_object_s_t) -> Result { 160 | if value.signature == VISION_OBJECT_ERR_SIG { 161 | bail_errno!(); 162 | unreachable!("Errno should be non-zero") 163 | } 164 | 165 | Ok(Self { 166 | top: value.top_coord, 167 | left: value.left_coord, 168 | middle_x: value.x_middle_coord, 169 | middle_y: value.y_middle_coord, 170 | width: value.width, 171 | height: value.height, 172 | }) 173 | } 174 | } 175 | 176 | #[repr(u32)] 177 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 178 | /// The zero point of the vision sensor. 179 | /// Vision object coordinates are relative to this point. 180 | pub enum VisionZeroPoint { 181 | /// The zero point will be the top left corner of the vision sensor. 182 | TopLeft, 183 | /// The zero point will be the top right corner of the vision sensor. 184 | Center, 185 | } 186 | 187 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 188 | /// The white balance of the vision sensor. 189 | pub enum WhiteBalance { 190 | /// Provide a specific color to balance the white balance. 191 | Rgb(Rgb), 192 | /// Automatically balance the white balance. 193 | Auto, 194 | } 195 | 196 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 197 | /// The mode of the vision sensor led. 198 | pub enum LedMode { 199 | /// Turn on the led with a certain color. 200 | On(Rgb), 201 | /// Turn off the led. 202 | Off, 203 | } 204 | 205 | #[derive(Debug, Snafu)] 206 | /// Errors that can occur when using a vision sensor. 207 | pub enum VisionError { 208 | /// The camera could not be read. 209 | ReadingFailed, 210 | /// The index specified was higher than the total number of objects seen by the camera. 211 | IndexTooHigh, 212 | /// Port already taken. 213 | PortTaken, 214 | #[snafu(display("{source}"), context(false))] 215 | /// Generic port related error. 216 | Port { 217 | /// The source of the error. 218 | source: PortError, 219 | }, 220 | } 221 | 222 | map_errno! { 223 | VisionError { 224 | EHOSTDOWN => Self::ReadingFailed, 225 | EDOM => Self::IndexTooHigh, 226 | EACCES => Self::PortTaken, 227 | } 228 | inherit PortError; 229 | } 230 | -------------------------------------------------------------------------------- /packages/pros-devices/src/smart/link.rs: -------------------------------------------------------------------------------- 1 | //! Connect to VEXLink radios for robot-to-robot communication. 2 | //! 3 | //! There are two types of links: [`TxLink`] (transmitter radio module) and [`RxLink`] (receiver radio module). 4 | //! both implement a shared trait [`Link`] as well as a no_std version of `Write` and `Read` from [`no_std_io`] respectively. 5 | 6 | use alloc::{ffi::CString, string::String}; 7 | use core::ffi::CStr; 8 | 9 | use no_std_io::io; 10 | use pros_core::{ 11 | bail_errno, bail_on, 12 | error::{FromErrno, PortError}, 13 | map_errno, 14 | }; 15 | use pros_sys::{link_receive, link_transmit, E_LINK_RECEIVER, E_LINK_TRANSMITTER}; 16 | use snafu::Snafu; 17 | 18 | use super::{SmartDevice, SmartDeviceType, SmartPort}; 19 | 20 | /// Types that implement Link can be used to send data to another robot over VEXLink. 21 | pub trait Link: SmartDevice { 22 | /// The identifier of this link. 23 | fn id(&self) -> &CStr; 24 | 25 | /// Check whether this link is connected to another robot. 26 | fn connected(&self) -> bool { 27 | unsafe { pros_sys::link_connected(self.port_index()) } 28 | } 29 | 30 | /// Create a new link ready to send or recieve data. 31 | fn new(port: SmartPort, id: String, vexlink_override: bool) -> Result 32 | where 33 | Self: Sized; 34 | } 35 | 36 | /// A recieving end of a VEXLink connection. 37 | #[derive(Debug)] 38 | pub struct RxLink { 39 | port: SmartPort, 40 | id: CString, 41 | } 42 | 43 | impl RxLink { 44 | /// Get the number of bytes in the incoming buffer. 45 | /// Be aware that the number of incoming bytes can change between when this is called 46 | /// and when you read from the link. 47 | /// If you create a buffer of this size, and then attempt to read into it 48 | /// you may encounter panics or data loss. 49 | pub fn num_incoming_bytes(&self) -> Result { 50 | let num = unsafe { 51 | bail_on!( 52 | pros_sys::PROS_ERR as _, 53 | pros_sys::link_raw_receivable_size(self.port.index()) 54 | ) 55 | }; 56 | 57 | Ok(num) 58 | } 59 | 60 | /// Clear all bytes in the incoming buffer. 61 | /// All data in the incoming will be lost and completely unrecoverable. 62 | pub fn clear_incoming_buf(&self) -> Result<(), LinkError> { 63 | unsafe { 64 | bail_on!( 65 | pros_sys::PROS_ERR as _, 66 | pros_sys::link_clear_receive_buf(self.port.index()) 67 | ) 68 | }; 69 | 70 | Ok(()) 71 | } 72 | 73 | /// Receive data from the link incoming buffer into a buffer. 74 | pub fn receive(&self, buf: &mut [u8]) -> Result { 75 | const PROS_ERR_U32: u32 = pros_sys::PROS_ERR as _; 76 | 77 | match unsafe { link_receive(self.port.index(), buf.as_mut_ptr().cast(), buf.len() as _) } { 78 | PROS_ERR_U32 => { 79 | bail_errno!(); 80 | unreachable!("Expected errno to be set"); 81 | } 82 | 0 => Err(LinkError::Busy), 83 | n => Ok(n), 84 | } 85 | } 86 | } 87 | 88 | impl Link for RxLink { 89 | fn id(&self) -> &CStr { 90 | &self.id 91 | } 92 | fn new(port: SmartPort, id: String, vexlink_override: bool) -> Result { 93 | let id = CString::new(id).unwrap(); 94 | unsafe { 95 | bail_on!( 96 | pros_sys::PROS_ERR as _, 97 | if vexlink_override { 98 | pros_sys::link_init(port.index(), id.as_ptr().cast(), E_LINK_RECEIVER) 99 | } else { 100 | pros_sys::link_init_override(port.index(), id.as_ptr().cast(), E_LINK_RECEIVER) 101 | } 102 | ) 103 | }; 104 | Ok(Self { port, id }) 105 | } 106 | } 107 | 108 | impl SmartDevice for RxLink { 109 | fn port_index(&self) -> u8 { 110 | self.port.index() 111 | } 112 | 113 | fn device_type(&self) -> SmartDeviceType { 114 | SmartDeviceType::Radio 115 | } 116 | } 117 | 118 | impl io::Read for RxLink { 119 | fn read(&mut self, dst: &mut [u8]) -> io::Result { 120 | let bytes_read = self 121 | .receive(dst) 122 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to read from link"))?; 123 | Ok(bytes_read as _) 124 | } 125 | } 126 | 127 | /// A transmitting end of a VEXLink connection. 128 | #[derive(Debug)] 129 | pub struct TxLink { 130 | port: SmartPort, 131 | id: CString, 132 | } 133 | 134 | impl TxLink { 135 | // I have literally no idea what the purpose of this is, 136 | // there is no way to push to the transmission buffer without transmitting it. 137 | /// Get the number of bytes to be sent over this link. 138 | pub fn num_outgoing_bytes(&self) -> Result { 139 | let num = unsafe { 140 | bail_on!( 141 | pros_sys::PROS_ERR as _, 142 | pros_sys::link_raw_transmittable_size(self.port.index()) 143 | ) 144 | }; 145 | 146 | Ok(num) 147 | } 148 | 149 | /// Transmit a buffer of data over the link. 150 | pub fn transmit(&self, buf: &[u8]) -> Result { 151 | const PROS_ERR_U32: u32 = pros_sys::PROS_ERR as _; 152 | 153 | match unsafe { link_transmit(self.port.index(), buf.as_ptr().cast(), buf.len() as _) } { 154 | PROS_ERR_U32 => { 155 | let errno = pros_core::error::take_errno(); 156 | Err(FromErrno::from_errno(errno) 157 | .unwrap_or_else(|| panic!("Unknown errno code {errno}"))) 158 | } 159 | 0 => Err(LinkError::Busy), 160 | n => Ok(n), 161 | } 162 | } 163 | } 164 | 165 | impl io::Write for TxLink { 166 | fn write(&mut self, buf: &[u8]) -> io::Result { 167 | let bytes_written = self 168 | .transmit(buf) 169 | .map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to write to link"))?; 170 | Ok(bytes_written as _) 171 | } 172 | fn flush(&mut self) -> io::Result<()> { 173 | Ok(()) 174 | } 175 | } 176 | 177 | impl Link for TxLink { 178 | fn id(&self) -> &CStr { 179 | &self.id 180 | } 181 | fn new(port: SmartPort, id: String, vexlink_override: bool) -> Result { 182 | let id = CString::new(id).unwrap(); 183 | unsafe { 184 | bail_on!( 185 | pros_sys::PROS_ERR as _, 186 | if vexlink_override { 187 | pros_sys::link_init(port.index(), id.as_ptr().cast(), E_LINK_TRANSMITTER) 188 | } else { 189 | pros_sys::link_init_override( 190 | port.index(), 191 | id.as_ptr().cast(), 192 | E_LINK_TRANSMITTER, 193 | ) 194 | } 195 | ) 196 | }; 197 | Ok(Self { port, id }) 198 | } 199 | } 200 | 201 | impl SmartDevice for TxLink { 202 | fn port_index(&self) -> u8 { 203 | self.port.index() 204 | } 205 | 206 | fn device_type(&self) -> SmartDeviceType { 207 | SmartDeviceType::Radio 208 | } 209 | } 210 | 211 | #[derive(Debug, Snafu)] 212 | /// Errors that can occur when using VEXLink. 213 | pub enum LinkError { 214 | /// No link is connected through the radio. 215 | NoLink, 216 | /// The transmitter buffer is still busy with a previous transmission, and there is no room in the FIFO buffer (queue) to transmit the data. 217 | BufferBusyFull, 218 | /// Invalid data: the data given was a C NULL. 219 | NullData, 220 | /// Protocol error related to start byte, data size, or checksum during a transmission or reception. 221 | Protocol, 222 | /// The link is busy. 223 | Busy, 224 | #[snafu(display("{source}"), context(false))] 225 | /// Generic port related error 226 | Port { 227 | /// The source of the error 228 | source: PortError, 229 | }, 230 | } 231 | 232 | map_errno! { 233 | LinkError { 234 | ENXIO => Self::NoLink, 235 | EBUSY => Self::BufferBusyFull, 236 | EINVAL => Self::NullData, 237 | EBADMSG => Self::Protocol, 238 | } 239 | inherit PortError; 240 | } 241 | --------------------------------------------------------------------------------