diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1da706c5..bef8863e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,7 +14,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly + toolchain: nightly-2024-02-07 override: true - name: Check @@ -33,7 +33,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly + toolchain: nightly-2024-02-07 override: true - name: Setup | Install Rustfmt @@ -57,4 +57,4 @@ jobs: with: reporter: 'github-pr-check' github_token: ${{ secrets.GITHUB_TOKEN }} - clippy_flags: --all-targets --all-features \ No newline at end of file + clippy_flags: --all-targets --all-features diff --git a/CHANGELOG.md b/CHANGELOG.md index c2510a2a..3ebeeaf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,75 @@ Before releasing: ### Added +- `object_size` method on `DistanceSensor` for getting a guess at an object's relative size. + +### Fixed + +### Changed + +### Removed + +## [0.8.0] + +### Added +- Added feedforward motor controllers (#80) +- Lightly document all APIs with missing documentation. (#70) +- Added `Debug`, `Copy`, and `Clone` derives for common structs (#70) +- Screen drawing API. (#81) +- Added screen field to `Peripherals` and `DynamicPeripherals::take_screen` method. (#81) +- Added `AdiSolenoid`, a wrapper over `AdiDigitalOut` for actuating SMC pneumatic solenoids. (#61) +- Added `AdiSwitch`, another `AdiDigitalOut` wrapper that abstracts bumper switches and limit switches. (#61) +- Added `AdiLineTracker` for abstracting the EDR line tracker sensor. + +### Fixed + +- Fix error handling and error type variats in ADI bindings +- Fix `AsynRobot` only running opcontrol +- Properly handle `EADDRINUSE` return for smart port errors (**Breaking Change**) (#97) + +### Changed + +- Re-exported printing macros from `pros::io`. (#82) +- Applied several lints to improve code quality. (#70) +- Updated to PROS version 4. (**Breaking Change**) (#81) +- Moved `vision::Rgb` into its own `color.rs` file. (**Breaking Change**) (#81) +- The VEXOS target has been updated to improve file size and floating point operation speed. (#81) +- `Peripherals::new()` is no longer const (**Breaking Change) (#81) +- Updated panic handler to print to the brain display as well as over serial (#81) +- Refactors digital and analog ADI input/output. (**Breaking Change**) (#61) + - Adds LogicLevel rather than bools for controlling digital devices. + - Adds 0-5V voltage getters and setters for analog ADI. + - Changed analog getters and setters to use `u16` data. +- Changed `AdiPotentiometer` to return degrees rather than tenth degrees (**Breaking Change**) (#61). + - Renamed `AdiPotentiometer::value` to `AdiPotentiometer::angle`. +- Refactors `AdiMotor` to match the smart motor APIs, having output/raw output getters/setters. +- Renamed `AdiUltrasonic::value` to `AdiUltrasonic::distance` (**Breaking Change**) (#61). +- Renamed `AdiEncoder::value` to `AdiEncoder::position` (**Breaking Change**) (#61). +- Repurposed `AdiAnalogOut` as `AdiPwmOut` to correct match port output. (**Breaking Change**) (#90). +- Moved most device-related constants into their associated struct `impl` (**Breaking Change**) (#98). +- Renamed IMU_RESET_TIMEOUT to `InertialSensor::CALIBRATION_TIMEOUT` (**Breaking Change**) (#98). +- Repurposed the `pros` crate as a metapackage without any code of its own. (**Breaking Change**) (#86) +- Split the pros-rs into several small subcrates. (**Breaking Change**) (#86) + - `pros-async` with the async executor and robot trait. + - `pros-devices` for device bindings. + - `pros-sync` for the sync robot trait. + - `pros-core` with basic abstractions over `pros-sys` needed to compile a program to the brain. + - `pros-math` with commonly used controllers and other mathematical models. + - `pros-panic` for the panic handler implementation. + +### Removed + +- LVGL bindings (pros-sys) and colors (pros). (**Breaking Change**) (#81) +- LLEMU/lcd bindings. (**Breaking Change**) (#81) +- Re-exported printing macros from `pros::io`. (#82) +- Applied several lints to improve code quality. (#70) +- Removed the confusingly named `write`, `ewrite`, `writeln`, and `ewriteln` macros. (**Breaking Change**) (#82) +- Removed AdiDigitalIn::new_press, instead swapping it for AdiSwitch::was_pressed. (**Breaking Change**) (#61) + +## [0.7.0] + +### Added + - `SmartPort` struct for device access. (#34) - `SmartDevice` trait for common functionality across smart port devices. (#34) - Methods to get a device's port number as well as determine if the device is plugged in or not. (#34) @@ -36,7 +105,7 @@ Before releasing: - All ADI device bindings (#55) - `LocalKey` now has `Cell`/`RefCell`-specific methods for setting and taking values. (#42) - `Peripherals` and `DynamicPeripherals` structs to ensure that you have only registered one device on a given smart or ADI port. (#53) -- `object_size` method on `DistanceSensor` for getting a guess at an object's relative size. +- Support for ADI Expander modules with `AdiExpander`. (#63) ### Fixed @@ -44,12 +113,15 @@ Before releasing: - Fixed error handling in IMU sensor bindings. (#37) - Fixed errors in doctests and examples throughout the crate. (#37) - Fixed Missing ERRNO and ADI config variants in pros-sys (#55) +- Fixed incorrect error handling with `InertialSensor::status`. (#65) +- `Controller::status` now handles errors by returning `Result`. (**Breaking Change**) (#74) ### Changed - Overhauled the `competition` module with more straightforward getters for competition state. (#38) (**Breaking Change**) - LLEMU-related macros have been prefixed with `llemu_` (e.g. `llemu_println`). (**Breaking Change**) (#30) - Added `Debug`, `Copy`, and `Clone` derives for common structs (#37) +- Renamed `InertialSensor::is_calibrating` to `InertialSensor::calibrating`. (**Breaking Change**) (#65) - Battery API functions now return `Result<_, BatteryError>`. (**Breaking Change**) (#62) - Renamed `battery::get_capacity` to `battery::capacity`, `battery::get_current` -> `battery::current`, `battery::get_temperature` -> `battery::temperature`, `battery::get_voltage` -> `battery::voltage`. (**Breaking Change**) (#62) @@ -57,6 +129,7 @@ Before releasing: - Removed several broken bindings in `pros_sys` relating to competition state. (#38) (**Breaking Change**) - `LocalKey` no longer implements `set` for non-`Cell`/`RefCell` stored values. (**Breaking change**) (#42) +- Removed the now-redundant `InertialStatus::error` function. (**Breaking Change**) (#65) ## [0.6.0] - 2024-01-14 @@ -117,7 +190,9 @@ Before releasing: ### Removed -[unreleased]: https://github.com/pros-rs/pros-rs/compare/v0.6.0...HEAD +[unreleased]: https://github.com/pros-rs/pros-rs/compare/v0.8.0...HEAD [0.4.0]: https://github.com/pros-rs/pros-rs/releases/tag/v0.4.0 [0.5.0]: https://github.com/pros-rs/pros-rs/compare/v0.4.0...v0.5.0 [0.6.0]: https://github.com/pros-rs/pros-rs/compare/v0.5.0...v0.6.0 +[0.7.0]: https://github.com/pros-rs/pros-rs/compare/v0.6.0...v0.7.0 +[0.8.0]: https://github.com/pros-rs/pros-rs/compare/v0.7.0...v0.8.0 diff --git a/Cargo.toml b/Cargo.toml index 225effa9..322f9564 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,11 @@ [workspace] members = ["packages/*"] resolver = "2" + +[workspace.lints.rust] +rust_2018_idioms = "warn" +missing_docs = "warn" +unsafe_op_in_unsafe_fn = "warn" + +[workspace.lints.clippy] +missing_const_for_fn = "warn" diff --git a/TODO.md b/TODO.md index f5a44a84..b7de5085 100644 --- a/TODO.md +++ b/TODO.md @@ -17,7 +17,7 @@ This is the todo list for the eventual 1.0.0 release of pros-rs * [ ] (Custom) Gear Ratios * [X] Make Robot Functions Take Self * [X] PID controllers -* [ ] Feedforward loops +* [X] Feedforward loops * [ ] ADI (3 wire ports) * [ ] Ext. ADI * [X] Sensors @@ -34,9 +34,9 @@ This is the todo list for the eventual 1.0.0 release of pros-rs * [X] Async runtime * [X] Returning top level futures * [X] Reactor -* [ ] More asynchronous APIs +* [ ] More asynchronous APIs * [ ] MPSC -* [X] Task Locals +* [X] Task Locals ## API diff --git a/armv7a-vexos-eabi.json b/armv7a-vexos-eabi.json index 52063217..6cbe8f55 100644 --- a/armv7a-vexos-eabi.json +++ b/armv7a-vexos-eabi.json @@ -1,11 +1,12 @@ { + "cpu": "cortex-a9", "arch": "arm", "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", "disable-redzone": true, "emit-debug-gdb-scripts": false, "env": "newlib", "executables": true, - "features": "+v7,+thumb2,+soft-float,-neon,+strict-align", + "features": "+thumb2,+neon,+vfp3", "linker": "arm-none-eabi-gcc", "linker-flavor": "gcc", "llvm-target": "armv7a-none-eabi", @@ -16,11 +17,12 @@ "-nostartfiles", "-nostdlib", "-Wl,-Tv5.ld,-Tv5-common.ld,--gc-sections", - "-Wl,--start-group,-lpros,-lc,-lm,-lgcc,-lstdc++,--end-group" + "-Wl,--start-group,-lgcc,-lpros,-lc,--end-group" ] }, "relocation-model": "static", "target-family": "unix", "target-pointer-width": "32", - "os": "vexos" + "os": "vexos", + "vendor": "vex" } diff --git a/packages/pros-async/Cargo.toml b/packages/pros-async/Cargo.toml new file mode 100644 index 00000000..cdff9b30 --- /dev/null +++ b/packages/pros-async/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "pros-async" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-task = { version = "4.5.0", default-features = false } +pros-core = { version = "0.1.0", path = "../pros-core" } +waker-fn = "1.1.1" +pros-sys = { version = "0.7.0", path = "../pros-sys" } + +[lints] +workspace = true diff --git a/packages/pros/src/async_runtime/executor.rs b/packages/pros-async/src/executor.rs similarity index 96% rename from packages/pros/src/async_runtime/executor.rs rename to packages/pros-async/src/executor.rs index 718c5572..5628263b 100644 --- a/packages/pros/src/async_runtime/executor.rs +++ b/packages/pros-async/src/executor.rs @@ -9,10 +9,10 @@ use core::{ }; use async_task::{Runnable, Task}; +use pros_core::{os_task_local, task::delay}; use waker_fn::waker_fn; use super::reactor::Reactor; -use crate::{os_task_local, task::delay}; os_task_local! { pub(crate) static EXECUTOR: Executor = Executor::new(); @@ -27,7 +27,7 @@ impl !Send for Executor {} impl !Sync for Executor {} impl Executor { - pub fn new() -> Self { + pub const fn new() -> Self { Self { queue: RefCell::new(VecDeque::new()), reactor: RefCell::new(Reactor::new()), diff --git a/packages/pros-async/src/lib.rs b/packages/pros-async/src/lib.rs new file mode 100644 index 00000000..6d1beb30 --- /dev/null +++ b/packages/pros-async/src/lib.rs @@ -0,0 +1,203 @@ +//! Tiny async runtime and robot traits for `pros-rs`. +//! The async executor supports spawning tasks and blocking on futures. +//! It has a reactor to improve the performance of some futures. +//! It is recommended to use the `AsyncRobot` trait to run robot code. +//! FreeRTOS tasks can still be used, but it is recommended to use only async tasks for performance. + +#![no_std] +#![feature(negative_impls)] + +extern crate alloc; + +use core::{future::Future, task::Poll}; + +use async_task::Task; +use executor::EXECUTOR; +use pros_core::error::Result; + +mod executor; +mod reactor; + +/// Runs a future in the background without having to await it +/// To get the the return value you can await a task. +pub fn spawn(future: impl Future + 'static) -> Task { + executor::EXECUTOR.with(|e| e.spawn(future)) +} + +/// Blocks the current task untill a return value can be extracted from the provided future. +/// Does not poll all futures to completion. +pub fn block_on(future: F) -> F::Output { + executor::EXECUTOR.with(|e| e.block_on(spawn(future))) +} + +/// A future that will complete after the given duration. +/// Sleep futures that are closer to completion are prioritized to improve accuracy. +#[derive(Debug)] +pub struct SleepFuture { + target_millis: u32, +} +impl Future for SleepFuture { + type Output = (); + + fn poll( + self: core::pin::Pin<&mut Self>, + cx: &mut core::task::Context<'_>, + ) -> core::task::Poll { + if self.target_millis < unsafe { pros_sys::millis() } { + Poll::Ready(()) + } else { + EXECUTOR.with(|e| { + e.reactor + .borrow_mut() + .sleepers + .push(cx.waker().clone(), self.target_millis) + }); + Poll::Pending + } + } +} + +/// Returns a future that will complete after the given duration. +pub fn sleep(duration: core::time::Duration) -> SleepFuture { + SleepFuture { + target_millis: unsafe { pros_sys::millis() + duration.as_millis() as u32 }, + } +} + +/// A trait for robot code that spins up the pros-rs async executor. +/// This is the preferred trait to run robot code. +pub trait AsyncRobot { + /// Runs during the operator control period. + /// This function may be called more than once. + /// For that reason, do not use `Peripherals::take`in this function. + fn opcontrol(&mut self) -> impl Future { + async { Ok(()) } + } + /// Runs during the autonomous period. + fn auto(&mut self) -> impl Future { + async { Ok(()) } + } + /// Runs continuously during the disabled period. + fn disabled(&mut self) -> impl Future { + async { Ok(()) } + } + /// Runs once when the competition system is initialized. + fn comp_init(&mut self) -> impl Future { + async { Ok(()) } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __gen_async_exports { + ($rbt:ty) => { + pub static mut ROBOT: Option<$rbt> = None; + + #[doc(hidden)] + #[no_mangle] + extern "C" fn opcontrol() { + $crate::block_on(<$rbt as $crate::AsyncRobot>::opcontrol(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before opcontrol") + })) + .unwrap(); + } + + #[doc(hidden)] + #[no_mangle] + extern "C" fn autonomous() { + $crate::block_on(<$rbt as $crate::AsyncRobot>::auto(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before auto") + })) + .unwrap(); + } + + #[doc(hidden)] + #[no_mangle] + extern "C" fn disabled() { + $crate::block_on(<$rbt as $crate::AsyncRobot>::disabled(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before disabled") + })) + .unwrap(); + } + + #[doc(hidden)] + #[no_mangle] + extern "C" fn competition_initialize() { + $crate::block_on(<$rbt as $crate::AsyncRobot>::comp_init(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before comp_init") + })) + .unwrap(); + } + }; +} + +/// Allows your async robot code to be executed by the pros kernel. +/// If your robot struct implements Default then you can just supply this macro with its type. +/// If not, you can supply an expression that returns your robot type to initialize your robot struct. +/// The code that runs to create your robot struct will run in the initialize function in PROS. +/// +/// Example of using the macro with a struct that implements Default: +/// ```rust +/// use pros::prelude::*; +/// #[derive(Default)] +/// struct ExampleRobot; +/// #[async_trait] +/// impl AsyncRobot for ExampleRobot { +/// asnyc fn opcontrol(&mut self) -> pros::Result { +/// println!("Hello, world!"); +/// Ok(()) +/// } +/// } +/// async_robot!(ExampleRobot); +/// ``` +/// +/// Example of using the macro with a struct that does not implement Default: +/// ```rust +/// use pros::prelude::*; +/// struct ExampleRobot { +/// x: i32, +/// } +/// #[async_trait] +/// impl AsyncRobot for ExampleRobot { +/// async fn opcontrol(&mut self) -> pros::Result { +/// println!("Hello, world! {}", self.x); +/// Ok(()) +/// } +/// } +/// impl ExampleRobot { +/// pub fn new() -> Self { +/// Self { x: 5 } +/// } +/// } +/// async_robot!(ExampleRobot, ExampleRobot::new()); +#[macro_export] +macro_rules! async_robot { + ($rbt:ty) => { + $crate::__gen_async_exports!($rbt); + + #[no_mangle] + extern "C" fn initialize() { + unsafe { + ROBOT = Some(Default::default()); + } + } + }; + ($rbt:ty, $init:expr) => { + $crate::__gen_async_exports!($rbt); + + #[no_mangle] + extern "C" fn initialize() { + unsafe { + ROBOT = Some($init); + } + } + }; +} diff --git a/packages/pros/src/async_runtime/reactor.rs b/packages/pros-async/src/reactor.rs similarity index 95% rename from packages/pros/src/async_runtime/reactor.rs rename to packages/pros-async/src/reactor.rs index b10f26e2..f47098b7 100644 --- a/packages/pros/src/async_runtime/reactor.rs +++ b/packages/pros-async/src/reactor.rs @@ -20,7 +20,7 @@ pub struct Reactor { } impl Reactor { - pub fn new() -> Self { + pub const fn new() -> Self { Self { sleepers: Sleepers { sleepers: BTreeMap::new(), diff --git a/packages/pros-core/Cargo.toml b/packages/pros-core/Cargo.toml new file mode 100644 index 00000000..60fc43d1 --- /dev/null +++ b/packages/pros-core/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pros-core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pros-sys = { version = "0.7.0", path = "../pros-sys" } +no_std_io = { version = "0.6.0", features = ["alloc"] } +snafu = { version = "0.8.0", default-features = false, features = [ + "rust_1_61", + "unstable-core-error", +] } +spin = "0.9.8" + +[target.'cfg(target_arch = "wasm32")'.dependencies] +dlmalloc = { version = "0.2.4", features = ["global"] } + +[lints] +workspace = true diff --git a/packages/pros-core/src/allocator/mod.rs b/packages/pros-core/src/allocator/mod.rs new file mode 100644 index 00000000..bd4c949c --- /dev/null +++ b/packages/pros-core/src/allocator/mod.rs @@ -0,0 +1,6 @@ +//! Simple allocator using the VEX libc allocation functions in vexos and jemalloc in the sim. + +#[cfg(target_os = "vexos")] +mod vexos; +#[cfg(target_arch = "wasm32")] +mod wasm; diff --git a/packages/pros-core/src/allocator/vexos.rs b/packages/pros-core/src/allocator/vexos.rs new file mode 100644 index 00000000..e6e17cbd --- /dev/null +++ b/packages/pros-core/src/allocator/vexos.rs @@ -0,0 +1,16 @@ +use core::alloc::{GlobalAlloc, Layout}; + +struct Allocator; +unsafe impl GlobalAlloc for Allocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // SAFETY: caller must ensure that the alignment and size are valid for the given layout + unsafe { pros_sys::memalign(layout.align() as _, layout.size() as _) as *mut u8 } + } + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + // SAFETY: caller must ensure that the given ptr can be deallocated + unsafe { pros_sys::free(ptr as *mut core::ffi::c_void) } + } +} + +#[global_allocator] +static ALLOCATOR: Allocator = Allocator; diff --git a/packages/pros/src/wasm_env.rs b/packages/pros-core/src/allocator/wasm.rs similarity index 91% rename from packages/pros/src/wasm_env.rs rename to packages/pros-core/src/allocator/wasm.rs index e9a48d59..5fabbe9f 100644 --- a/packages/pros/src/wasm_env.rs +++ b/packages/pros-core/src/allocator/wasm.rs @@ -12,11 +12,6 @@ use dlmalloc::GlobalDlmalloc; // no multithreading in wasm static mut LAYOUTS: BTreeMap<*mut u8, Layout> = BTreeMap::new(); -extern "C" { - /// Prints a backtrace to the debug console - pub fn sim_log_backtrace(); -} - #[no_mangle] extern "C" fn wasm_memalign(alignment: usize, size: usize) -> *mut u8 { if size == 0 { diff --git a/packages/pros/src/error.rs b/packages/pros-core/src/error.rs similarity index 81% rename from packages/pros/src/error.rs rename to packages/pros-core/src/error.rs index 2458b04c..f129cede 100644 --- a/packages/pros/src/error.rs +++ b/packages/pros-core/src/error.rs @@ -5,7 +5,11 @@ //! //! Most of the contents of this file are not public. -pub(crate) fn take_errno() -> i32 { +/// A result type that makes returning errors easier. +pub type Result = core::result::Result>; + +/// Gets the value of errno and sets errno to 0. +pub fn take_errno() -> i32 { let err = unsafe { *pros_sys::__errno() }; if err != 0 { unsafe { *pros_sys::__errno() = 0 }; @@ -24,6 +28,7 @@ pub(crate) fn take_errno() -> i32 { /// inherit PortError; /// } /// ``` +#[macro_export] macro_rules! map_errno { { $err_ty:ty { $($errno:pat => $err:expr),*$(,)? } @@ -49,9 +54,9 @@ macro_rules! map_errno { } } } -pub(crate) use map_errno; /// If errno has an error, return early. +#[macro_export] macro_rules! bail_errno { () => {{ let errno = $crate::error::take_errno(); @@ -62,10 +67,10 @@ macro_rules! bail_errno { } }}; } -pub(crate) use bail_errno; /// Checks if the value is equal to the error state, and if it is, /// uses the value of errno to create an error and return early. +#[macro_export] macro_rules! bail_on { ($err_state:expr, $val:expr) => {{ let val = $val; @@ -79,9 +84,9 @@ macro_rules! bail_on { val }}; } -pub(crate) use bail_on; use snafu::Snafu; +/// A trait for converting an errno value into an error type. pub trait FromErrno { /// Consume the current `errno` and, if it contains a known error, returns Self. fn from_errno(num: i32) -> Option @@ -90,17 +95,18 @@ pub trait FromErrno { } #[derive(Debug, Snafu)] +/// Generic erros that can take place when using ports on the V5 Brain. pub enum PortError { - #[snafu(display("The port you specified is outside of the allowed range!"))] + /// The specified port is outside of the allowed range! PortOutOfRange, - #[snafu(display( - // used to have "Is something else plugged in?" But the vex radio (link) uses the same errno, so that's not always applicable. - "The port you specified couldn't be configured as what you specified." - ))] + /// The specified port couldn't be configured as the specified type. PortCannotBeConfigured, + /// The specified port is already being used or is mismatched. + AlreadyInUse, } map_errno!(PortError { ENXIO => Self::PortOutOfRange, ENODEV => Self::PortCannotBeConfigured, + EADDRINUSE => Self::AlreadyInUse, }); diff --git a/packages/pros-core/src/io/mod.rs b/packages/pros-core/src/io/mod.rs new file mode 100644 index 00000000..12166332 --- /dev/null +++ b/packages/pros-core/src/io/mod.rs @@ -0,0 +1,239 @@ +//! Std-like I/O macros and types for use in pros. +//! +//! Implements `println!`, `eprintln!` and `dbg!` on top of the `pros_sys` crate without requiring +//! the use of an allocator. (Modified version of `libc_print` crate) +//! +//! Allows you to use these macros in a #!\[no_std\] context, or in a situation where the +//! traditional Rust streams might not be available (ie: at process shutdown time). +//! +//! ## Usage +//! +//! Exactly as you'd use `println!`, `eprintln!` and `dbg!`. +//! +//! ```rust +//! # use pros::io::*; +//! // Use the default ``-prefixed macros: +//! # fn test1() +//! # { +//! println!("Hello {}!", "stdout"); +//! eprintln!("Hello {}!", "stderr"); +//! let a = 2; +//! let b = dbg!(a * 2) + 1; +//! assert_eq!(b, 5); +//! # } +//! ``` +//! +//! Or you can import aliases to `std` names: +//! +//! ```rust +//! use pros::io::{println, eprintln, dbg}; +//! +//! # fn test2() +//! # { +//! println!("Hello {}!", "stdout"); +//! eprintln!("Hello {}!", "stderr"); +//! let a = 2; +//! let b = dbg!(a * 2) + 1; +//! assert_eq!(b, 5); +//! # } +//! ``` + +// libc_print is licensed under the MIT License: + +// Copyright (c) 2023 Matt Mastracci and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#[allow(unused_imports)] +use core::{convert::TryFrom, file, line, stringify}; + +pub use no_std_io::io::*; + +pub use crate::{dbg, eprint, eprintln, print, println}; + +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct __SerialWriter(i32); + +impl core::fmt::Write for __SerialWriter { + #[inline] + fn write_str(&mut self, s: &str) -> core::fmt::Result { + __println(self.0, s) + } +} + +impl __SerialWriter { + #[inline] + pub const fn new(err: bool) -> __SerialWriter { + __SerialWriter(if err { 2 } else { 1 }) + } + + #[inline] + pub fn write_fmt(&mut self, args: core::fmt::Arguments<'_>) -> core::fmt::Result { + core::fmt::Write::write_fmt(self, args) + } + + #[inline] + pub fn write_str(&mut self, s: &str) -> core::fmt::Result { + __println(self.0, s) + } + + #[inline] + pub fn write_nl(&mut self) -> core::fmt::Result { + __println(self.0, "\n") + } +} + +#[doc(hidden)] +#[inline] +pub fn __println(handle: i32, msg: &str) -> core::fmt::Result { + let msg = msg.as_bytes(); + + let mut written = 0; + while written < msg.len() { + match unsafe { write(handle, &msg[written..]) } { + // Ignore errors + None | Some(0) => break, + Some(res) => written += res, + } + } + + Ok(()) +} + +unsafe fn write(handle: i32, bytes: &[u8]) -> Option { + usize::try_from(unsafe { + pros_sys::write( + handle, + bytes.as_ptr().cast::(), + bytes.len(), + ) + }) + .ok() +} + +/// Macro for printing to the standard output, with a newline. +/// +/// Does not panic on failure to write - instead silently ignores errors. +/// +/// See [`println!`](https://doc.rust-lang.org/std/macro.println.html) for +/// full documentation. +#[macro_export] +macro_rules! println { + () => { $crate::println!("") }; + ($($arg:tt)*) => { + { + #[allow(unused_must_use)] + { + let mut stm = $crate::io::__SerialWriter::new(false); + stm.write_fmt(format_args!($($arg)*)); + stm.write_nl(); + } + } + }; +} + +/// Macro for printing to the standard output. +/// +/// Does not panic on failure to write - instead silently ignores errors. +/// +/// See [`print!`](https://doc.rust-lang.org/std/macro.print.html) for +/// full documentation. +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => { + { + #[allow(unused_must_use)] + { + let mut stm = $crate::io::__SerialWriter::new(false); + stm.write_fmt(format_args!($($arg)*)); + } + } + }; +} + +/// Macro for printing to the standard error, with a newline. +/// +/// Does not panic on failure to write - instead silently ignores errors. +/// +/// See [`eprintln!`](https://doc.rust-lang.org/std/macro.eprintln.html) for +/// full documentation. +#[macro_export] +macro_rules! eprintln { + () => { $crate::eprintln!("") }; + ($($arg:tt)*) => { + { + #[allow(unused_must_use)] + { + let mut stm = $crate::io::__SerialWriter::new(true); + stm.write_fmt(format_args!($($arg)*)); + stm.write_nl(); + } + } + }; +} + +/// Macro for printing to the standard error. +/// +/// Does not panic on failure to write - instead silently ignores errors. +/// +/// See [`eprint!`](https://doc.rust-lang.org/std/macro.eprint.html) for +/// full documentation. +#[macro_export] +macro_rules! eprint { + ($($arg:tt)*) => { + { + #[allow(unused_must_use)] + { + let mut stm = $crate::io::__SerialWriter::new(true); + stm.write_fmt(format_args!($($arg)*)); + } + } + }; +} + +/// Prints and returns the value of a given expression for quick and dirty +/// debugging. +/// +/// An example: +/// +/// ```rust +/// let a = 2; +/// let b = dbg!(a * 2) + 1; +/// // ^-- prints: [src/main.rs:2] a * 2 = 4 +/// assert_eq!(b, 5); +/// ``` +/// +/// See [dbg!](https://doc.rust-lang.org/std/macro.dbg.html) for full documentation. +#[macro_export] +macro_rules! dbg { + () => { + $crate::eprintln!("[{}:{}]", $file!(), $line!()) + }; + ($val:expr $(,)?) => { + match $val { + tmp => { + $crate::eprintln!("[{}:{}] {} = {:#?}", file!(), line!(), stringify!($val), &tmp); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($($crate::dbg!($val)),+,) + }; +} diff --git a/packages/pros-core/src/lib.rs b/packages/pros-core/src/lib.rs new file mode 100644 index 00000000..dc868f74 --- /dev/null +++ b/packages/pros-core/src/lib.rs @@ -0,0 +1,23 @@ +//! Low level core functionality for [`pros-rs`](https://crates.io/crates/pros). +//! The core crate is used in all other crates in the pros-rs ecosystem. +//! +//! Included in this crate: +//! - Global allocator: [`pros_alloc`] +//! - Competition state checking: [`competition`] +//! - Errno handling: [`error`] +//! - Serial terminal printing: [`io`] +//! - No-std [`Instant`](time::Instant)s: [`time`] +//! - Synchronization primitives: [`sync`] +//! - FreeRTOS task management: [`task`] + +#![no_std] +#![feature(error_in_core)] + +extern crate alloc; + +pub mod allocator; +pub mod error; +pub mod io; +pub mod sync; +pub mod task; +pub mod time; diff --git a/packages/pros/src/sync.rs b/packages/pros-core/src/sync.rs similarity index 91% rename from packages/pros/src/sync.rs rename to packages/pros-core/src/sync.rs index 8f03cca4..7233996e 100644 --- a/packages/pros/src/sync.rs +++ b/packages/pros-core/src/sync.rs @@ -28,7 +28,7 @@ impl Mutex { /// Locks the mutex so that it cannot be locked in another task at the same time. /// Blocks the current task until the lock is acquired. - pub fn lock(&self) -> MutexGuard { + pub fn lock(&self) -> MutexGuard<'_, T> { if !unsafe { pros_sys::mutex_take(self.pros_mutex, pros_sys::TIMEOUT_MAX) } { panic!("Mutex lock failed: {}", take_errno()); } @@ -37,16 +37,18 @@ impl Mutex { } /// Attempts to acquire this lock. This function does not block. - pub fn try_lock(&self) -> Option> { + pub fn try_lock(&self) -> Option> { let success = unsafe { pros_sys::mutex_take(self.pros_mutex, 0) }; success.then(|| MutexGuard::new(self)) } + /// Consumes the mutex and returns the inner data. pub fn into_inner(mut self) -> T { let data = mem::take(&mut self.data).unwrap(); data.into_inner() } + /// Gets a mutable reference to the inner data. pub fn get_mut(&mut self) -> &mut T { self.data.as_mut().unwrap().get_mut() } @@ -98,12 +100,13 @@ impl From for Mutex { /// Allows the user to access the data from a locked mutex. /// Dereference to get the inner data. +#[derive(Debug)] pub struct MutexGuard<'a, T> { mutex: &'a Mutex, } impl<'a, T> MutexGuard<'a, T> { - fn new(mutex: &'a Mutex) -> Self { + const fn new(mutex: &'a Mutex) -> Self { Self { mutex } } } diff --git a/packages/pros/src/task/local.rs b/packages/pros-core/src/task/local.rs similarity index 93% rename from packages/pros/src/task/local.rs rename to packages/pros-core/src/task/local.rs index 4f9b5913..f4c20bd6 100644 --- a/packages/pros/src/task/local.rs +++ b/packages/pros-core/src/task/local.rs @@ -33,15 +33,21 @@ static INDEX: AtomicU32 = AtomicU32::new(0); /// Unsafe because you can change the thread local storage while it is being read. unsafe fn thread_local_storage_set(task: pros_sys::task_t, val: &'static T, index: u32) { // Yes, we transmute val. This is the intended use of this function. - pros_sys::vTaskSetThreadLocalStoragePointer(task, index as _, (val as *const T).cast()); + // SAFETY: caller must ensure borrow rules are followed + unsafe { + pros_sys::vTaskSetThreadLocalStoragePointer(task, index as _, (val as *const T).cast()); + } } /// Get a value from OS TLS. /// # Safety /// Unsafe because we can't check if the type is the same as the one that was set. unsafe fn thread_local_storage_get(task: pros_sys::task_t, index: u32) -> Option<&'static T> { - let val = pros_sys::pvTaskGetThreadLocalStoragePointer(task, index as _); - val.cast::().as_ref() + // SAFETY: caller must ensure borrow rules are followed and the type is correct + unsafe { + let val = pros_sys::pvTaskGetThreadLocalStoragePointer(task, index as _); + val.cast::().as_ref() + } } /// Get or create the [`ThreadLocalStorage`] for the current task. @@ -50,7 +56,7 @@ fn fetch_storage() -> &'static RefCell { // Get the thread local storage for this task. // Creating it if it doesn't exist. - // This is safe as long as index 0 of the freeRTOS TLS is never set to any other type. + // SAFETY: This is safe as long as index 0 of the freeRTOS TLS is never set to any other type. unsafe { thread_local_storage_get(current.task, 0).unwrap_or_else(|| { let storage = Box::leak(Box::new(RefCell::new(ThreadLocalStorage { @@ -71,6 +77,7 @@ struct ThreadLocalStorage { /// A TLS key that owns its data. /// Can be created with the [`os_task_local`](crate::os_task_local!) macro. +#[derive(Debug)] pub struct LocalKey { index: Once, init: fn() -> T, diff --git a/packages/pros/src/task/mod.rs b/packages/pros-core/src/task/mod.rs similarity index 82% rename from packages/pros/src/task/mod.rs rename to packages/pros-core/src/task/mod.rs index 8f3ca4f9..7c40d103 100644 --- a/packages/pros/src/task/mod.rs +++ b/packages/pros-core/src/task/mod.rs @@ -23,18 +23,15 @@ use alloc::{ boxed::Box, string::{String, ToString}, }; -use core::{ffi::CStr, future::Future, hash::Hash, str::Utf8Error, task::Poll, time::Duration}; +use core::{ffi::CStr, hash::Hash, str::Utf8Error, time::Duration}; use snafu::Snafu; -use crate::{ - async_runtime::executor::EXECUTOR, - error::{bail_on, map_errno}, -}; +use crate::{bail_on, map_errno}; /// Creates a task to be run 'asynchronously' (More information at the [FreeRTOS docs](https://www.freertos.org/taskandcr.html)). /// Takes in a closure that can move variables if needed. -/// If your task has a loop it is advised to use [`sleep(duration)`](sleep) so that the task does not take up necessary system resources. +/// If your task has a loop it is advised to use [`delay`] so that the task does not take up necessary system resources. /// Tasks should be long-living; starting many tasks can be slow and is usually not necessary. pub fn spawn(f: F) -> TaskHandle where @@ -73,7 +70,7 @@ fn spawn_inner( } /// An owned permission to perform actions on a task. -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct TaskHandle { pub(crate) task: pros_sys::task_t, } @@ -141,6 +138,7 @@ impl TaskHandle { } } + /// Gets the name of the task if possible. pub fn name(&self) -> Result { unsafe { let name = pros_sys::task_get_name(self.task); @@ -151,7 +149,7 @@ impl TaskHandle { } /// An ergonomic builder for tasks. Alternatively you can use [`spawn`]. -#[derive(Default)] +#[derive(Debug, Default)] pub struct Builder<'a> { name: Option<&'a str>, priority: Option, @@ -165,20 +163,20 @@ impl<'a> Builder<'a> { } /// Sets the name of the task, this is useful for debugging. - pub fn name(mut self, name: &'a str) -> Self { + pub const fn name(mut self, name: &'a str) -> Self { self.name = Some(name); self } /// Sets the priority of the task (how much time the scheduler gives to it.). - pub fn priority(mut self, priority: TaskPriority) -> Self { + pub const fn priority(mut self, priority: TaskPriority) -> Self { self.priority = Some(priority); self } /// Sets how large the stack for the task is. /// This can usually be set to default - pub fn stack_depth(mut self, stack_depth: TaskStackDepth) -> Self { + pub const fn stack_depth(mut self, stack_depth: TaskStackDepth) -> Self { self.stack_depth = Some(stack_depth); self } @@ -198,12 +196,13 @@ impl<'a> Builder<'a> { } /// Represents the current state of a task. +#[derive(Debug)] pub enum TaskState { /// The task is currently utilizing the processor Running, /// The task is currently yielding but may run in the future Ready, - /// The task is blocked. For example, it may be [`sleep`]ing or waiting on a mutex. + /// The task is blocked. For example, it may be [`delay`]ing or waiting on a mutex. /// Tasks that are in this state will usually return to the task queue after a set timeout. Blocked, /// The task is suspended. For example, it may be waiting on a mutex or semaphore. @@ -228,21 +227,21 @@ impl From for TaskState { } } +#[repr(u32)] +#[derive(Debug, Default)] /// Represents how much time the cpu should spend on this task. /// (Otherwise known as the priority) -#[repr(u32)] pub enum TaskPriority { + /// The highest priority, should be used sparingly. + /// Loops **MUST** have delays or sleeps to prevent starving other tasks. High = 16, + /// The default priority. + #[default] Default = 8, + /// The lowest priority, tasks with this priority will barely ever get cpu time. Low = 1, } -impl Default for TaskPriority { - fn default() -> Self { - Self::Default - } -} - impl From for u32 { fn from(val: TaskPriority) -> Self { val as u32 @@ -252,17 +251,16 @@ impl From for u32 { /// Represents how large of a stack the task should get. /// Tasks that don't have any or many variables and/or don't need floats can use the low stack depth option. #[repr(u32)] +#[derive(Debug, Default)] pub enum TaskStackDepth { + #[default] + /// The default stack depth. Default = 8192, + /// Low task depth. Many tasks can get away with using this stack depth + /// however the brain has enough memory that this usually isn't necessary. Low = 512, } -impl Default for TaskStackDepth { - fn default() -> Self { - Self::Default - } -} - struct TaskEntrypoint { function: F, } @@ -272,15 +270,17 @@ where F: FnOnce(), { unsafe extern "C" fn cast_and_call_external(this: *mut core::ffi::c_void) { - let this = Box::from_raw(this.cast::()); + // SAFETY: caller must ensure `this` is an owned `TaskEntrypoint` on the heap + let this = unsafe { Box::from_raw(this.cast::()) }; (this.function)() } } #[derive(Debug, Snafu)] +/// Errors that can occur when spawning a task. pub enum SpawnError { - #[snafu(display("The stack cannot be used as the TCB was not created."))] + /// There is not enough memory to create the task. TCBNotCreated, } @@ -296,12 +296,13 @@ map_errno! { /// /// This function will block the entire task, preventing concurrent /// execution of async code. When in an async context, it is recommended -/// to use [`sleep`] instead. +/// to use the `sleep` function in [`pros_async`](https://crates.io/crates/pros-async) instead. pub fn delay(duration: Duration) { unsafe { pros_sys::delay(duration.as_millis() as u32) } } /// An interval that can be used to repeatedly run code at a given rate. +#[derive(Debug)] pub struct Interval { last_unblock_time: u32, } @@ -331,39 +332,6 @@ impl Interval { } } -/// A future that will complete after the given duration. -/// Sleep futures that are closer to completion are prioritized to improve accuracy. -pub struct SleepFuture { - target_millis: u32, -} -impl Future for SleepFuture { - type Output = (); - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - if self.target_millis < unsafe { pros_sys::millis() } { - Poll::Ready(()) - } else { - EXECUTOR.with(|e| { - e.reactor - .borrow_mut() - .sleepers - .push(cx.waker().clone(), self.target_millis) - }); - Poll::Pending - } - } -} - -/// Returns a future that will complete after the given duration. -pub fn sleep(duration: core::time::Duration) -> SleepFuture { - SleepFuture { - target_millis: unsafe { pros_sys::millis() + duration.as_millis() as u32 }, - } -} - /// Returns the task the function was called from. pub fn current() -> TaskHandle { unsafe { @@ -380,6 +348,9 @@ pub fn get_notification() -> u32 { unsafe { pros_sys::task_notify_take(false, pros_sys::TIMEOUT_MAX) } } +#[derive(Debug)] +/// A guard that can be used to suspend the FreeRTOS scheduler. +/// When dropped, the scheduler will be resumed. pub struct SchedulerSuspendGuard { _private: (), } @@ -401,13 +372,7 @@ impl Drop for SchedulerSuspendGuard { /// must not be called while the scheduler is suspended. #[must_use = "The scheduler will only remain suspended for the lifetime of the returned guard"] pub unsafe fn suspend_all() -> SchedulerSuspendGuard { - pros_sys::rtos_suspend_all(); + // SAFETY: Caller must ensure that other FreeRTOS API functions are not called while the scheduler is suspended. + unsafe { pros_sys::rtos_suspend_all() }; SchedulerSuspendGuard { _private: () } } - -#[doc(hidden)] -pub fn __init_entrypoint() { - unsafe { - pros_sys::lcd_initialize(); - } -} diff --git a/packages/pros/src/time.rs b/packages/pros-core/src/time.rs similarity index 98% rename from packages/pros/src/time.rs rename to packages/pros-core/src/time.rs index a4705ee8..84cae96b 100644 --- a/packages/pros/src/time.rs +++ b/packages/pros-core/src/time.rs @@ -62,7 +62,7 @@ impl Instant { /// println!("{:?}", new_now.checked_duration_since(now)); /// println!("{:?}", now.checked_duration_since(new_now)); // None /// ``` - pub fn checked_duration_since(&self, earlier: Instant) -> Option { + pub const fn checked_duration_since(&self, earlier: Instant) -> Option { if earlier.0 < self.0 { Some(Duration::from_micros(self.0 - earlier.0)) } else { diff --git a/packages/pros-devices/Cargo.toml b/packages/pros-devices/Cargo.toml new file mode 100644 index 00000000..17a62012 --- /dev/null +++ b/packages/pros-devices/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pros-devices" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pros-core = { version = "0.1.0", path = "../pros-core" } +pros-sys = { path = "../pros-sys", version = "0.7.0", features = ["xapi"] } +snafu = { version = "0.8.0", default-features = false, features = [ + "rust_1_61", + "unstable-core-error", +] } +no_std_io = { version = "0.6.0", features = ["alloc"] } + +[lints] +workspace = true diff --git a/packages/pros/src/devices/adi/analog.rs b/packages/pros-devices/src/adi/analog.rs similarity index 60% rename from packages/pros/src/devices/adi/analog.rs rename to packages/pros-devices/src/adi/analog.rs index 2023c4ea..1a4a83c9 100644 --- a/packages/pros/src/devices/adi/analog.rs +++ b/packages/pros-devices/src/adi/analog.rs @@ -1,8 +1,19 @@ +//! ADI Analog Interfaces +//! +//! # Overview +//! +//! Unlike digital ADI devices which can only report a "high" or "low" state, analog +//! ADI devices may report a wide range of values spanning 0-5 volts. These analog +//! voltages readings are then converted into a digital values using the internal +//! Analog-to-Digital Converter (ADC) in the V5 brain. The brain measures analog input +//! using 12-bit values ranging from 0 (0V) to 4095 (5V). + +use pros_core::bail_on; use pros_sys::PROS_ERR; use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; +/// Generic analog input ADI device. #[derive(Debug, Eq, PartialEq)] pub struct AdiAnalogIn { port: AdiPort, @@ -10,8 +21,16 @@ pub struct AdiAnalogIn { impl AdiAnalogIn { /// Create a analog input from an ADI port. - pub fn new(port: AdiPort) -> Self { - Self { port } + pub fn new(port: AdiPort) -> Result { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_config( + port.internal_expander_index(), + port.index(), + pros_sys::E_ADI_ANALOG_IN, + ) + }); + + Ok(Self { port }) } /// Calibrates the analog sensor on the specified channel. @@ -38,31 +57,48 @@ impl AdiAnalogIn { /// Reads an analog input channel and returns the 12-bit value. /// + /// # Sensor Compatibility + /// /// The value returned is undefined if the analog pin has been switched to a different mode. /// The meaning of the returned value varies depending on the sensor attached. - pub fn value(&self) -> Result { + pub fn value(&self) -> Result { Ok(bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_analog_read(self.port.internal_expander_index(), self.port.index()) - })) + }) as u16) + } + + /// Reads an analog input channel and returns the calculated voltage input (0-5V). + /// + /// # Precision + /// + /// This function has a precision of `5.0/4095.0` volts, as ADC reports 12-bit voltage data + /// on a scale of 0-4095. + /// + /// # Sensor Compatibility + /// + /// The value returned is undefined if the analog pin has been switched to a different mode. + /// The meaning of the returned value varies depending on the sensor attached. + pub fn voltage(&self) -> Result { + Ok(self.value()? as f64 / 4095.0 * 5.0) } /// Reads the calibrated value of an analog input channel. /// - /// The calibrate function must be run first on that channel. + /// The [`Self::calibrate`] function must be run first on that channel. /// /// This function is inappropriate for sensor values intended for integration, /// as round-off error can accumulate causing drift over time. - /// Use value_calbrated_hr instead. - pub fn value_calibrated(&self) -> Result { + /// Use [`Self::high_precision_calibrated_value`] instead. + pub fn calibrated_value(&self) -> Result { Ok(bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_analog_read_calibrated( self.port.internal_expander_index(), self.port.index(), ) - })) + }) as i16) } - /// Reads the calibrated value of an analog input channel 1-8 with enhanced precision. + /// Reads the calibrated value of an analog input channel with enhanced precision. /// /// The calibrate function must be run first. /// @@ -70,19 +106,19 @@ impl AdiAnalogIn { /// to reduce drift due to round-off, and should not be used on a sensor such as a /// line tracker or potentiometer. /// - /// The value returned actually has 16 bits of “precision”, + /// The value returned actually has 16 bits of "precision", /// even though the ADC only reads 12 bits, /// so that errors induced by the average value being /// between two values come out in the wash when integrated over time. /// /// Think of the value as the true value times 16. - pub fn value_calibrated_hr(&self) -> Result { + pub fn high_precision_calibrated_value(&self) -> Result { Ok(bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_analog_read_calibrated_HR( self.port.internal_expander_index(), self.port.index(), ) - })) + }) as i16) } } @@ -101,41 +137,3 @@ impl AdiDevice for AdiAnalogIn { AdiDeviceType::AnalogIn } } - -#[derive(Debug, Eq, PartialEq)] -pub struct AdiAnalogOut { - port: AdiPort, -} - -impl AdiAnalogOut { - /// Create a analog output from an [`AdiPort`]. - pub fn new(port: AdiPort) -> Self { - Self { port } - } - - /// Sets the output for the Analog Output from 0 (0V) to 4095 (5V). - pub fn set_value(&mut self, value: i32) -> Result { - Ok(unsafe { - bail_on! { - PROS_ERR, - pros_sys::ext_adi_port_set_value(self.port.internal_expander_index(), self.port.index(), value) - } - }) - } -} - -impl AdiDevice for AdiAnalogOut { - type PortIndexOutput = u8; - - fn port_index(&self) -> Self::PortIndexOutput { - self.port.index() - } - - fn expander_port_index(&self) -> Option { - self.port.expander_index() - } - - fn device_type(&self) -> AdiDeviceType { - AdiDeviceType::AnalogOut - } -} diff --git a/packages/pros-devices/src/adi/digital.rs b/packages/pros-devices/src/adi/digital.rs new file mode 100644 index 00000000..f1e7c9fa --- /dev/null +++ b/packages/pros-devices/src/adi/digital.rs @@ -0,0 +1,175 @@ +//! Digital input and output ADI devices + +use pros_core::bail_on; +use pros_sys::PROS_ERR; + +use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; + +/// Represents the logic level of a digital pin. +/// +/// On digital devices, logic levels represent the two possible voltage signals that define +/// the state of a pin. This value is either [`High`] or [`Low`], depending on the intended +/// state of the device. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LogicLevel { + /// A high digital signal. + /// + /// ADI ports operate on 3.3V logic, so this value indicates a voltage of 3.3V or above. + High, + + /// The low digital signal. + /// + /// ADI ports operate on 3.3V logic, so this value indicates a voltage below 3.3V. + Low, +} + +impl LogicLevel { + /// Returns `true` if the level is [`High`]. + pub const fn is_high(&self) -> bool { + match self { + Self::High => true, + Self::Low => false, + } + } + + /// Returns `true` if the level is [`Low`]. + pub const fn is_low(&self) -> bool { + match self { + Self::High => false, + Self::Low => true, + } + } +} + +impl core::ops::Not for LogicLevel { + type Output = Self; + + fn not(self) -> Self::Output { + match self { + Self::Low => Self::High, + Self::High => Self::Low, + } + } +} + +/// Generic digital input ADI device. +#[derive(Debug, Eq, PartialEq)] +/// Generic digital input ADI device. +pub struct AdiDigitalIn { + port: AdiPort, +} + +impl AdiDigitalIn { + /// Create a digital input from an ADI port. + pub fn new(port: AdiPort) -> Result { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_config( + port.internal_expander_index(), + port.index(), + pros_sys::E_ADI_DIGITAL_IN, + ) + }); + + Ok(Self { port }) + } + + /// Gets the current logic level of a digital input pin. + pub fn level(&self) -> Result { + let value = bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_digital_read(self.port.internal_expander_index(), self.port.index()) + }) != 0; + + Ok(match value { + true => LogicLevel::High, + false => LogicLevel::Low, + }) + } + + /// Returns `true` if the digital input's logic level level is [`LogicLevel::High`]. + pub fn is_high(&self) -> Result { + Ok(self.level()?.is_high()) + } + + /// Returns `true` if the digital input's logic level level is [`LogicLevel::Low`]. + pub fn is_low(&self) -> Result { + Ok(self.level()?.is_high()) + } +} + +impl AdiDevice for AdiDigitalIn { + type PortIndexOutput = u8; + + fn port_index(&self) -> Self::PortIndexOutput { + self.port.index() + } + + fn expander_port_index(&self) -> Option { + self.port.expander_index() + } + + fn device_type(&self) -> AdiDeviceType { + AdiDeviceType::DigitalIn + } +} + +/// Generic digital output ADI device. +#[derive(Debug, Eq, PartialEq)] +pub struct AdiDigitalOut { + port: AdiPort, +} + +impl AdiDigitalOut { + /// Create a digital output from an [`AdiPort`]. + pub fn new(port: AdiPort) -> Result { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_config( + port.internal_expander_index(), + port.index(), + pros_sys::E_ADI_DIGITAL_OUT, + ) + }); + + Ok(Self { port }) + } + + /// Sets the digital logic level (high or low) of a pin. + pub fn set_level(&mut self, level: LogicLevel) -> Result<(), AdiError> { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_digital_write( + self.port.internal_expander_index(), + self.port.index(), + level.is_high(), + ) + }); + + Ok(()) + } + + /// Set the digital logic level to [`LogicLevel::High`]. Analagous to + /// [`Self::set_level(LogicLevel::High)`]. + pub fn set_high(&mut self) -> Result<(), AdiError> { + self.set_level(LogicLevel::High) + } + + /// Set the digital logic level to [`LogicLevel::Low`]. Analagous to + /// [`Self::set_level(LogicLevel::Low)`]. + pub fn set_low(&mut self) -> Result<(), AdiError> { + self.set_level(LogicLevel::Low) + } +} + +impl AdiDevice for AdiDigitalOut { + type PortIndexOutput = u8; + + fn port_index(&self) -> Self::PortIndexOutput { + self.port.index() + } + + fn expander_port_index(&self) -> Option { + self.port.expander_index() + } + + fn device_type(&self) -> AdiDeviceType { + AdiDeviceType::DigitalOut + } +} diff --git a/packages/pros/src/devices/adi/encoder.rs b/packages/pros-devices/src/adi/encoder.rs similarity index 92% rename from packages/pros/src/devices/adi/encoder.rs rename to packages/pros-devices/src/adi/encoder.rs index 3539cd85..ee0ae8f9 100644 --- a/packages/pros/src/devices/adi/encoder.rs +++ b/packages/pros-devices/src/adi/encoder.rs @@ -1,8 +1,12 @@ +//! ADI encoder device. + +use pros_core::bail_on; use pros_sys::{ext_adi_encoder_t, PROS_ERR}; use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; +/// ADI encoder device. +/// Requires two adi ports. #[derive(Debug, Eq, PartialEq)] pub struct AdiEncoder { raw: ext_adi_encoder_t, @@ -45,7 +49,7 @@ impl AdiEncoder { } /// Gets the number of ticks recorded by the encoder. - pub fn value(&self) -> Result { + pub fn position(&self) -> Result { Ok(bail_on!(PROS_ERR, unsafe { pros_sys::adi_encoder_get(self.raw) })) diff --git a/packages/pros-devices/src/adi/gyro.rs b/packages/pros-devices/src/adi/gyro.rs new file mode 100644 index 00000000..617007e6 --- /dev/null +++ b/packages/pros-devices/src/adi/gyro.rs @@ -0,0 +1,65 @@ +//! ADI gyro device. + +use core::time::Duration; + +use pros_core::bail_on; +use pros_sys::{ext_adi_gyro_t, PROS_ERR, PROS_ERR_F}; + +use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; + +/// ADI gyro device. +#[derive(Debug, Eq, PartialEq)] +pub struct AdiGyro { + raw: ext_adi_gyro_t, + port: AdiPort, +} + +impl AdiGyro { + /// The time it takes to calibrate an [`AdiGyro`]. + /// + /// The theoretical calibration time is 1024ms, but in practice this seemed to be the + /// actual time that it takes. + pub const CALIBRATION_TIME: Duration = Duration::from_millis(1300); + + /// Create a new gyro from an [`AdiPort`]. + /// + /// If the given port has not previously been configured as a gyro, then this + /// function blocks for a 1300ms calibration period. + pub fn new(port: AdiPort, multiplier: f64) -> Result { + let raw = bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_gyro_init(port.internal_expander_index(), port.index(), multiplier) + }); + + Ok(Self { raw, port }) + } + + /// Gets the yaw angle of the gyroscope in degrees. + /// + /// Unless a multiplier is applied to the gyro, the return value will be a whole + /// number representing the number of degrees of rotation. + pub fn angle(&self) -> Result { + Ok(bail_on!(PROS_ERR_F, unsafe { pros_sys::ext_adi_gyro_get(self.raw) }) / 10.0) + } + + /// Reset the current gyro angle to zero degrees. + pub fn zero(&mut self) -> Result<(), AdiError> { + bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_gyro_reset(self.raw) }); + Ok(()) + } +} + +impl AdiDevice for AdiGyro { + type PortIndexOutput = u8; + + fn port_index(&self) -> Self::PortIndexOutput { + self.port.index() + } + + fn expander_port_index(&self) -> Option { + self.port.expander_index() + } + + fn device_type(&self) -> AdiDeviceType { + AdiDeviceType::LegacyGyro + } +} diff --git a/packages/pros-devices/src/adi/linetracker.rs b/packages/pros-devices/src/adi/linetracker.rs new file mode 100644 index 00000000..02068600 --- /dev/null +++ b/packages/pros-devices/src/adi/linetracker.rs @@ -0,0 +1,86 @@ +//! ADI Line Tracker +//! +//! Line trackers read the difference between a black line and a white surface. They can +//! be used to follow a marked path on the ground. +//! +//! # Overview +//! +//! A line tracker consists of an analog infrared light sensor and an infrared LED. +//! It works by illuminating a surface with infrared light; the sensor then picks up +//! the reflected infrared radiation and, based on its intensity, determines the +//! reflectivity of the surface in question. White surfaces will reflect more light +//! than dark surfaces, resulting in their appearing brighter to the sensor. This +//! allows the sensor to detect a dark line on a white background, or a white line on +//! a dark background. +//! +//! # Hardware +//! +//! The Line Tracking Sensor is an analog sensor, and it internally measures values in the +//! range of 0 to 4095 from 0-5V. Darker objects reflect less light, and are indicated by +//! higher numbers. Lighter objects reflect more light, and are indicated by lower numbers. +//! +//! For best results when using the Line Tracking Sensors, it is best to mount the sensors +//! between 1/8 and 1/4 of an inch away from the surface it is measuring. It is also important +//! to keep lighting in the room consistent, so sensors' readings remain accurate. + +use pros_core::bail_on; +use pros_sys::PROS_ERR; + +use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; + +/// Analog line tracker device. +#[derive(Debug, Eq, PartialEq)] +pub struct AdiLineTracker { + port: AdiPort, +} + +impl AdiLineTracker { + /// Create a line tracker on an ADI port. + pub fn new(port: AdiPort) -> Result { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_config( + port.internal_expander_index(), + port.index(), + pros_sys::E_ADI_ANALOG_IN, + ) + }); + + Ok(Self { port }) + } + + /// Get the reflectivity factor measured by the sensor. + /// + /// This is returned as a value ranging from [0.0, 1.0]. + pub fn reflectivity(&self) -> Result { + Ok(bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_analog_read(self.port.internal_expander_index(), self.port.index()) + }) as f64 + / 4095.0) + } + + /// Get the raw reflectivity factor of the sensor. + /// + /// This is a raw 12-bit value from [0, 4095] representing the voltage level from + /// 0-5V measured by the V5 brain's ADC. + pub fn raw_reflectivity(&self) -> Result { + Ok(bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_analog_read(self.port.internal_expander_index(), self.port.index()) + }) as u16) + } +} + +impl AdiDevice for AdiLineTracker { + type PortIndexOutput = u8; + + fn port_index(&self) -> Self::PortIndexOutput { + self.port.index() + } + + fn expander_port_index(&self) -> Option { + self.port.expander_index() + } + + fn device_type(&self) -> AdiDeviceType { + AdiDeviceType::AnalogIn + } +} diff --git a/packages/pros/src/devices/adi/mod.rs b/packages/pros-devices/src/adi/mod.rs similarity index 64% rename from packages/pros/src/devices/adi/mod.rs rename to packages/pros-devices/src/adi/mod.rs index 4cc6aa2a..9012d496 100644 --- a/packages/pros/src/devices/adi/mod.rs +++ b/packages/pros-devices/src/adi/mod.rs @@ -1,24 +1,31 @@ //! ADI (Triport) devices on the Vex V5. -use pros_sys::{adi_port_config_e_t, PROS_ERR}; +use pros_core::{bail_on, error::PortError, map_errno}; +use pros_sys::{adi_port_config_e_t, E_ADI_ERR, PROS_ERR}; use snafu::Snafu; -use crate::error::{bail_on, map_errno, PortError}; - +//TODO: much more in depth module documentation for device modules as well as this module. pub mod analog; pub mod digital; +pub mod pwm; + pub mod encoder; pub mod gyro; +pub mod linetracker; pub mod motor; pub mod potentiometer; +pub mod solenoid; +pub mod switch; pub mod ultrasonic; -pub use analog::{AdiAnalogIn, AdiAnalogOut}; +pub use analog::AdiAnalogIn; pub use digital::{AdiDigitalIn, AdiDigitalOut}; pub use encoder::AdiEncoder; pub use gyro::AdiGyro; +pub use linetracker::AdiLineTracker; pub use motor::AdiMotor; pub use potentiometer::AdiPotentiometer; +pub use solenoid::AdiSolenoid; pub use ultrasonic::AdiUltrasonic; /// Represents an ADI (three wire) port on a V5 Brain or V5 Three Wire Expander. @@ -42,7 +49,7 @@ impl AdiPort { /// /// Creating new `AdiPort`s is inherently unsafe due to the possibility of constructing /// more than one device on the same port index allowing multiple mutable references to - /// the same hardware device. Prefer using [`Peripherals`] to register devices if possible. + /// the same hardware device. Prefer using [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible. pub const unsafe fn new(index: u8, expander_index: Option) -> Self { Self { index, @@ -53,13 +60,13 @@ impl AdiPort { /// Get the index of the port (port number). /// /// Ports are indexed starting from 1. - pub fn index(&self) -> u8 { + pub const fn index(&self) -> u8 { self.index } /// Get the index of this port's associated [`AdiExpander`] smart port, or `None` if this port is not /// associated with an expander. - pub fn expander_index(&self) -> Option { + pub const fn expander_index(&self) -> Option { self.expander_index } @@ -72,15 +79,16 @@ impl AdiPort { /// Get the type of device this port is currently configured as. pub fn configured_type(&self) -> Result { - Ok(bail_on!(PROS_ERR, unsafe { + bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi::ext_adi_port_get_config(self.internal_expander_index(), self.index()) }) - .try_into()?) + .try_into() } } /// Common functionality for a ADI (three-wire) devices. pub trait AdiDevice { + /// The type that port_index should return. This is usually `u8`, but occasionally `(u8, u8)`. type PortIndexOutput; /// Get the index of the [`AdiPort`] this device is registered on. @@ -93,7 +101,7 @@ pub trait AdiDevice { /// Ports are indexed starting from 1. fn expander_port_index(&self) -> Option; - /// Get the variant of [`SmartDeviceType`] that this device is associated with. + /// Get the variant of [`AdiDeviceType`] that this device is associated with. fn device_type(&self) -> AdiDeviceType; } @@ -101,17 +109,38 @@ pub trait AdiDevice { #[repr(i32)] #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum AdiDeviceType { + /// Generic analog input. AnalogIn = pros_sys::adi::E_ADI_ANALOG_IN, - AnalogOut = pros_sys::adi::E_ADI_ANALOG_OUT, + + /// Generic PWM output. + /// + /// This is actually equivalent `pros_sys::adi::E_ADI_ANALOG_OUT`, which is a misnomer. + /// "Analog Out" in reality outputs an 8-bit PWM value. + PwmOut = pros_sys::adi::E_ADI_ANALOG_OUT, + + /// Generic digital input. DigitalIn = pros_sys::adi::E_ADI_DIGITAL_IN, + + /// Generic digital output. DigitalOut = pros_sys::adi::E_ADI_DIGITAL_OUT, + /// Cortex-era yaw-rate gyroscope. LegacyGyro = pros_sys::adi::E_ADI_LEGACY_GYRO, + /// Cortex-era servo motor. LegacyServo = pros_sys::adi::E_ADI_LEGACY_SERVO, + + /// MC29 Controller Output + /// + /// This differs from [`Self::PwmOut`] in that it is specifically designed for controlling + /// legacy ADI motors. Rather than taking a u8 for output, it takes a i8 allowing negative + /// values to be sent for controlling motors in reverse with a nicer API. LegacyPwm = pros_sys::adi::E_ADI_LEGACY_PWM, + /// Cortex-era encoder. LegacyEncoder = pros_sys::E_ADI_LEGACY_ENCODER, + + /// Cortex-era ultrasonic sensor. LegacyUltrasonic = pros_sys::E_ADI_LEGACY_ULTRASONIC, } @@ -119,9 +148,11 @@ impl TryFrom for AdiDeviceType { type Error = AdiError; fn try_from(value: adi_port_config_e_t) -> Result { + bail_on!(E_ADI_ERR, value); + match value { pros_sys::E_ADI_ANALOG_IN => Ok(AdiDeviceType::AnalogIn), - pros_sys::E_ADI_ANALOG_OUT => Ok(AdiDeviceType::AnalogOut), + pros_sys::E_ADI_ANALOG_OUT => Ok(AdiDeviceType::PwmOut), pros_sys::E_ADI_DIGITAL_IN => Ok(AdiDeviceType::DigitalIn), pros_sys::E_ADI_DIGITAL_OUT => Ok(AdiDeviceType::DigitalOut), @@ -133,7 +164,7 @@ impl TryFrom for AdiDeviceType { pros_sys::E_ADI_LEGACY_ENCODER => Ok(AdiDeviceType::LegacyEncoder), pros_sys::E_ADI_LEGACY_ULTRASONIC => Ok(AdiDeviceType::LegacyUltrasonic), - _ => Err(AdiError::InvalidConfigType), + _ => Err(AdiError::UnknownDeviceType), } } } @@ -145,37 +176,36 @@ impl From for adi_port_config_e_t { } #[derive(Debug, Snafu)] +/// Errors that can occur when working with ADI devices. pub enum AdiError { - #[snafu(display("Another resource is currently trying to access the ADI."))] + /// Another resource is currently trying to access the ADI. AlreadyInUse, - #[snafu(display( - "The port specified has been reconfigured or is not configured for digital input." - ))] - DigitalInputNotConfigured, + /// PROS returned an unrecognized device type. + UnknownDeviceType, - #[snafu(display( - "The port type specified is invalid, and cannot be used to configure a port." - ))] - InvalidConfigType, + /// The port specified has not been configured for the device type specified. + PortNotConfigured, - #[snafu(display("The port has already been configured."))] - AlreadyConfigured, - - #[snafu(display("The port specified is invalid."))] - InvalidPort, - - #[snafu(display("ADI devices may only be initialized from one expander port."))] + /// ADI devices may only be initialized from one expander port. ExpanderPortMismatch, + /// A given value is not correct, or the buffer is null. + InvalidValue, + #[snafu(display("{source}"), context(false))] - Port { source: PortError }, + /// An error occurred while interacting with a port. + Port { + /// The source of the error + source: PortError, + }, } map_errno! { AdiError { EACCES => Self::AlreadyInUse, - EADDRINUSE => Self::DigitalInputNotConfigured, + EADDRINUSE => Self::PortNotConfigured, + EINVAL => Self::InvalidValue, } inherit PortError; } diff --git a/packages/pros/src/devices/adi/motor.rs b/packages/pros-devices/src/adi/motor.rs similarity index 65% rename from packages/pros/src/devices/adi/motor.rs rename to packages/pros-devices/src/adi/motor.rs index b39be187..bda8d5df 100644 --- a/packages/pros/src/devices/adi/motor.rs +++ b/packages/pros-devices/src/adi/motor.rs @@ -1,21 +1,29 @@ +//! ADI motor device. + +use pros_core::bail_on; use pros_sys::PROS_ERR; use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; #[derive(Debug, Eq, PartialEq)] +/// Cortex era motor device. pub struct AdiMotor { port: AdiPort, } impl AdiMotor { /// Create a new motor from an [`AdiPort`]. - pub fn new(port: AdiPort) -> Self { + pub const fn new(port: AdiPort) -> Self { Self { port } } + /// Sets the PWM output of the given motor as an f32 from [-1.0, 1.0]. + pub fn set_output(&mut self, value: f32) -> Result<(), AdiError> { + self.set_raw_output((value * 127.0) as i8) + } + /// Sets the PWM output of the given motor as an i8 from [-127, 127]. - pub fn set_value(&mut self, value: i8) -> Result<(), AdiError> { + pub fn set_raw_output(&mut self, value: i8) -> Result<(), AdiError> { bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_motor_set( self.port.internal_expander_index(), @@ -23,14 +31,20 @@ impl AdiMotor { value, ) }); + Ok(()) } - /// Returns the last set PWM output of the motor on the given port. - pub fn value(&self) -> Result { + /// Returns the last set PWM output of the motor on the given port as an f32 from [-1.0, 1.0]. + pub fn output(&self) -> Result { + Ok(self.raw_output()? as f32 / 127.0) + } + + /// Returns the last set PWM output of the motor on the given port as an i8 from [-127, 127]. + pub fn raw_output(&self) -> Result { Ok(bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_motor_get(self.port.internal_expander_index(), self.port.index()) - })) + }) as i8) } /// Stops the given motor. diff --git a/packages/pros/src/devices/adi/potentiometer.rs b/packages/pros-devices/src/adi/potentiometer.rs similarity index 80% rename from packages/pros/src/devices/adi/potentiometer.rs rename to packages/pros-devices/src/adi/potentiometer.rs index 240122f5..80f6375a 100644 --- a/packages/pros/src/devices/adi/potentiometer.rs +++ b/packages/pros-devices/src/adi/potentiometer.rs @@ -1,9 +1,12 @@ -use pros_sys::{adi_potentiometer_type_e_t, ext_adi_potentiometer_t, PROS_ERR}; +//! ADI Potentiometer device. + +use pros_core::bail_on; +use pros_sys::{adi_potentiometer_type_e_t, ext_adi_potentiometer_t, PROS_ERR, PROS_ERR_F}; use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; #[derive(Debug, Eq, PartialEq)] +/// Analog potentiometer ADI device. pub struct AdiPotentiometer { potentiometer_type: AdiPotentiometerType, raw: ext_adi_potentiometer_t, @@ -28,27 +31,31 @@ impl AdiPotentiometer { }) } - pub fn potentiometer_type(&self) -> AdiPotentiometerType { + /// Get the type of ADI potentiometer device. + pub const fn potentiometer_type(&self) -> AdiPotentiometerType { self.potentiometer_type } - /// Gets the current potentiometer angle in tenths of a degree. + /// Gets the current potentiometer angle in degrees. /// /// The original potentiometer rotates 250 degrees /// thus returning an angle between 0-250 degrees. /// Potentiometer V2 rotates 330 degrees /// thus returning an angle between 0-330 degrees. pub fn angle(&self) -> Result { - Ok(bail_on!(PROS_ERR.into(), unsafe { + Ok(bail_on!(PROS_ERR_F, unsafe { pros_sys::ext_adi_potentiometer_get_angle(self.raw) - })) + }) / 10.0) } } #[derive(Debug, Clone, Copy, Eq, PartialEq)] #[repr(i32)] +/// The type of potentiometer device. pub enum AdiPotentiometerType { + /// EDR potentiometer. PotentiometerEdr = pros_sys::E_ADI_POT_EDR, + /// V2 potentiometer. PotentiometerV2 = pros_sys::E_ADI_POT_V2, } diff --git a/packages/pros-devices/src/adi/pwm.rs b/packages/pros-devices/src/adi/pwm.rs new file mode 100644 index 00000000..411869d1 --- /dev/null +++ b/packages/pros-devices/src/adi/pwm.rs @@ -0,0 +1,59 @@ +//! ADI Pulse-width modulation (PWM). + +use pros_core::bail_on; +use pros_sys::PROS_ERR; + +use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; + +/// Generic PWM output ADI device. +#[derive(Debug, Eq, PartialEq)] +pub struct AdiPwmOut { + port: AdiPort, +} + +impl AdiPwmOut { + /// Create a pwm output from an [`AdiPort`]. + pub fn new(port: AdiPort) -> Result { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_config( + port.internal_expander_index(), + port.index(), + pros_sys::E_ADI_ANALOG_OUT, + ) + }); + + Ok(Self { port }) + } + + /// Sets the PWM output width. + /// + /// This value is sent over 16ms periods with pulse widths ranging from roughly + /// 0.94mS to 2.03mS. + pub fn set_output(&mut self, value: u8) -> Result<(), AdiError> { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_value( + self.port.internal_expander_index(), + self.port.index(), + value as i32, + ) + }); + + Ok(()) + } +} + +impl AdiDevice for AdiPwmOut { + type PortIndexOutput = u8; + + fn port_index(&self) -> Self::PortIndexOutput { + self.port.index() + } + + fn expander_port_index(&self) -> Option { + self.port.expander_index() + } + + fn device_type(&self) -> AdiDeviceType { + AdiDeviceType::PwmOut + } +} diff --git a/packages/pros-devices/src/adi/solenoid.rs b/packages/pros-devices/src/adi/solenoid.rs new file mode 100644 index 00000000..e7fffea5 --- /dev/null +++ b/packages/pros-devices/src/adi/solenoid.rs @@ -0,0 +1,98 @@ +//! ADI Solenoid Pneumatic Control + +use pros_core::bail_on; +use pros_sys::PROS_ERR; + +use super::{digital::LogicLevel, AdiDevice, AdiDeviceType, AdiError, AdiPort}; + +/// Digital pneumatic solenoid valve. +#[derive(Debug, Eq, PartialEq)] +pub struct AdiSolenoid { + port: AdiPort, + level: LogicLevel, +} + +impl AdiSolenoid { + /// Create an AdiSolenoid. + pub fn new(port: AdiPort) -> Result { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_config( + port.internal_expander_index(), + port.index(), + pros_sys::E_ADI_DIGITAL_OUT, + ) + }); + + Ok(Self { + port, + level: LogicLevel::Low, + }) + } + + /// Sets the digital logic level of the solenoid. [`LogicLevel::Low`] will close the solenoid, + /// and [`LogicLevel::High`] will open it. + pub fn set_level(&mut self, level: LogicLevel) -> Result<(), AdiError> { + self.level = level; + + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_digital_write( + self.port.internal_expander_index(), + self.port.index(), + level.is_high(), + ) + }); + + Ok(()) + } + + /// Returns the current [`LogicLevel`] of the solenoid's digital output state. + pub const fn level(&self) -> LogicLevel { + self.level + } + + /// Returns `true` if the solenoid is open. + pub const fn is_open(&self) -> LogicLevel { + self.level + } + + /// Returns `true` if the solenoid is closed. + pub const fn is_closed(&self) -> LogicLevel { + self.level + } + + /// Open the solenoid, allowing air pressure through the "open" valve. + pub fn open(&mut self) -> Result<(), AdiError> { + self.set_level(LogicLevel::High) + } + + /// Close the solenoid. + /// + /// - On single-acting solenoids (e.g. SY113-SMO-PM3-F), this will simply block air pressure + /// through the "open" valve. + /// - On double-acting solenoids (e.g. SYJ3120-SMO-M3-F), this will block air pressure through + /// the "open" valve and allow air pressure into the "close" valve. + pub fn close(&mut self) -> Result<(), AdiError> { + self.set_level(LogicLevel::Low) + } + + /// Toggle the solenoid's state between open and closed. + pub fn toggle(&mut self) -> Result<(), AdiError> { + self.set_level(!self.level) + } +} + +impl AdiDevice for AdiSolenoid { + type PortIndexOutput = u8; + + fn port_index(&self) -> Self::PortIndexOutput { + self.port.index() + } + + fn expander_port_index(&self) -> Option { + self.port.expander_index() + } + + fn device_type(&self) -> AdiDeviceType { + AdiDeviceType::DigitalOut + } +} diff --git a/packages/pros-devices/src/adi/switch.rs b/packages/pros-devices/src/adi/switch.rs new file mode 100644 index 00000000..7cf972eb --- /dev/null +++ b/packages/pros-devices/src/adi/switch.rs @@ -0,0 +1,92 @@ +//! ADI Digital Switch + +use pros_core::bail_on; +use pros_sys::PROS_ERR; + +use super::{digital::LogicLevel, AdiDevice, AdiDeviceType, AdiDigitalIn, AdiError, AdiPort}; + +/// Generic digital input ADI device. +#[derive(Debug, Eq, PartialEq)] +pub struct AdiSwitch { + port: AdiPort, +} + +impl AdiSwitch { + /// Create a digital input from an ADI port. + pub fn new(port: AdiPort) -> Result { + bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_port_set_config( + port.internal_expander_index(), + port.index(), + pros_sys::E_ADI_DIGITAL_IN, + ) + }); + + Ok(Self { port }) + } + + /// Gets the current logic level of a digital switch. + pub fn level(&self) -> Result { + let value = bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_digital_read(self.port.internal_expander_index(), self.port.index()) + }) != 0; + + Ok(match value { + true => LogicLevel::High, + false => LogicLevel::Low, + }) + } + + /// Returrns `true` if the switch is currently being pressed. + /// + /// This is equivalent shorthand to calling `Self::level().is_high()`. + pub fn is_pressed(&self) -> Result { + Ok(self.level()?.is_high()) + } + + /// Returns `true` if the switch has been pressed again since the last time this + /// function was called. + /// + /// # Thread Safety + /// + /// This function is not thread-safe. + /// + /// Multiple tasks polling a single button may return different results under the + /// same circumstances, so only one task should call this function for any given + /// switch. E.g., Task A calls this function for buttons 1 and 2. Task B may call + /// this function for button 3, but should not for buttons 1 or 2. A typical + /// use-case for this function is to call inside opcontrol to detect new button + /// presses, and not in any other tasks. + pub fn was_pressed(&mut self) -> Result { + Ok(bail_on!(PROS_ERR, unsafe { + pros_sys::ext_adi_digital_get_new_press( + self.port.internal_expander_index(), + self.port.index(), + ) + }) != 0) + } +} + +impl From for AdiSwitch { + fn from(device: AdiDigitalIn) -> Self { + Self { + port: unsafe { AdiPort::new(device.port_index(), device.expander_port_index()) }, + } + } +} + +impl AdiDevice for AdiSwitch { + type PortIndexOutput = u8; + + fn port_index(&self) -> Self::PortIndexOutput { + self.port.index() + } + + fn expander_port_index(&self) -> Option { + self.port.expander_index() + } + + fn device_type(&self) -> AdiDeviceType { + AdiDeviceType::DigitalIn + } +} diff --git a/packages/pros/src/devices/adi/ultrasonic.rs b/packages/pros-devices/src/adi/ultrasonic.rs similarity index 78% rename from packages/pros/src/devices/adi/ultrasonic.rs rename to packages/pros-devices/src/adi/ultrasonic.rs index 333d51ef..098a867a 100644 --- a/packages/pros/src/devices/adi/ultrasonic.rs +++ b/packages/pros-devices/src/adi/ultrasonic.rs @@ -1,9 +1,13 @@ +//! ADI ultrasonic sensor. + +use pros_core::bail_on; use pros_sys::{ext_adi_ultrasonic_t, PROS_ERR}; use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; #[derive(Debug, Eq, PartialEq)] +/// Adi ultrasonic sensor. +/// Requires two ports one for pinging, and one for listening for the response. pub struct AdiUltrasonic { raw: ext_adi_ultrasonic_t, port_ping: AdiPort, @@ -35,11 +39,13 @@ impl AdiUltrasonic { }) } - /// Gets the current ultrasonic sensor value in centimeters. - pub fn value(&self) -> Result { + /// Get the distance reading of the ultrasonic sensor in centimeters. + /// + /// Round and/or fluffy objects can cause inaccurate values to be returned. + pub fn distance(&self) -> Result { Ok(bail_on!(PROS_ERR, unsafe { pros_sys::ext_adi_ultrasonic_get(self.raw) - })) + }) as u16) } } diff --git a/packages/pros/src/devices/battery.rs b/packages/pros-devices/src/battery.rs similarity index 86% rename from packages/pros/src/devices/battery.rs rename to packages/pros-devices/src/battery.rs index 3999ca11..ebb02ce4 100644 --- a/packages/pros/src/devices/battery.rs +++ b/packages/pros-devices/src/battery.rs @@ -1,10 +1,9 @@ //! Utilites for getting information about the robot's battery. +use pros_core::{bail_on, map_errno}; use pros_sys::{PROS_ERR, PROS_ERR_F}; use snafu::Snafu; -use crate::error::{bail_on, map_errno}; - /// Get the robot's battery capacity. pub fn capacity() -> Result { Ok(bail_on!(PROS_ERR_F, unsafe { @@ -34,8 +33,9 @@ pub fn voltage() -> Result { } #[derive(Debug, Snafu)] +/// Errors that can occur when interacting with the robot's battery. pub enum BatteryError { - #[snafu(display("Another resource is already using the battery"))] + /// Another resource is already using the battery. ConcurrentAccess, } diff --git a/packages/pros-devices/src/color.rs b/packages/pros-devices/src/color.rs new file mode 100644 index 00000000..94b3a276 --- /dev/null +++ b/packages/pros-devices/src/color.rs @@ -0,0 +1,384 @@ +//! Generic RGB8 color type and conversion trait. +//! The [`Rgb`] and [`IntoRgb`] types are used in multiple places in the library to represent colors. + +/// A trait for types that can be converted into an RGB8 color. +pub trait IntoRgb { + /// Consume the value and convert it into an RGB8 color. + fn into_rgb(self) -> Rgb; +} + +impl> IntoRgb for T { + fn into_rgb(self: T) -> Rgb { + Rgb::from_raw(self.into()) + } +} + +/// An RGB8 color. +/// The color space will almost always be assumed as sRGB in this library. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub struct Rgb { + /// Red value of the color. + pub r: u8, + /// Green value of the color. + pub g: u8, + /// Blue value of the color. + pub b: u8, +} + +impl Rgb { + /// #F0F8FF color constant. + pub const ALICE_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_ALICE_BLUE); + /// #FAEBD7 color constant. + pub const ANTIQUE_WHITE: Rgb = Rgb::from_raw(pros_sys::COLOR_ANTIQUE_WHITE); + /// #00FFFF color constant. + pub const AQUA: Rgb = Rgb::from_raw(pros_sys::COLOR_AQUA); + /// #7FFFD4 color constant. + pub const AQUAMARINE: Rgb = Rgb::from_raw(pros_sys::COLOR_AQUAMARINE); + /// #F0FFFF color constant. + pub const AZURE: Rgb = Rgb::from_raw(pros_sys::COLOR_AZURE); + /// #F5F5DC color constant. + pub const BEIGE: Rgb = Rgb::from_raw(pros_sys::COLOR_BEIGE); + /// #FFE4C4 color constant. + pub const BISQUE: Rgb = Rgb::from_raw(pros_sys::COLOR_BISQUE); + /// #000000 color constant. + pub const BLACK: Rgb = Rgb::from_raw(pros_sys::COLOR_BLACK); + /// #FFEBCD color constant. + pub const BLANCHED_ALMOND: Rgb = Rgb::from_raw(pros_sys::COLOR_BLANCHED_ALMOND); + /// #0000FF color constant. + pub const BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_BLUE); + /// #8A2BE2 color constant. + pub const BLUE_VIOLET: Rgb = Rgb::from_raw(pros_sys::COLOR_BLUE_VIOLET); + /// #A52A2A color constant. + pub const BROWN: Rgb = Rgb::from_raw(pros_sys::COLOR_BROWN); + /// #DEB887 color constant. + pub const BURLY_WOOD: Rgb = Rgb::from_raw(pros_sys::COLOR_BURLY_WOOD); + /// #5F9EA0 color constant. + pub const CADET_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_CADET_BLUE); + /// #7FFF00 color constant. + pub const CHARTREUSE: Rgb = Rgb::from_raw(pros_sys::COLOR_CHARTREUSE); + /// #D2691E color constant. + pub const CHOCOLATE: Rgb = Rgb::from_raw(pros_sys::COLOR_CHOCOLATE); + /// #FF7F50 color constant. + pub const CORAL: Rgb = Rgb::from_raw(pros_sys::COLOR_CORAL); + /// #6495ED color constant. + pub const CORNFLOWER_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_CORNFLOWER_BLUE); + /// #FFF8DC color constant. + pub const CORNSILK: Rgb = Rgb::from_raw(pros_sys::COLOR_CORNSILK); + /// #DC143C color constant. + pub const CRIMSON: Rgb = Rgb::from_raw(pros_sys::COLOR_CRIMSON); + /// #00FFFF color constant. + pub const CYAN: Rgb = Rgb::from_raw(pros_sys::COLOR_CYAN); + /// #00008B color constant. + pub const DARK_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_BLUE); + /// #008B8B color constant. + pub const DARK_CYAN: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_CYAN); + /// #B8860B color constant. + pub const DARK_GOLDENROD: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_GOLDENROD); + /// #A9A9A9 color constant. + pub const DARK_GRAY: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_GRAY); + /// #006400 color constant. + pub const DARK_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_GREEN); + /// #BDB76B color constant. + pub const DARK_KHAKI: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_KHAKI); + /// #8B008B color constant. + pub const DARK_MAGENTA: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_MAGENTA); + /// #556B2F color constant. + pub const DARK_OLIVE_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_OLIVE_GREEN); + /// #FF8C00 color constant. + pub const DARK_ORANGE: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_ORANGE); + /// #9932CC color constant. + pub const DARK_ORCHID: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_ORCHID); + /// #8B0000 color constant. + pub const DARK_RED: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_RED); + /// #E9967A color constant. + pub const DARK_SALMON: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_SALMON); + /// #8FBC8F color constant. + pub const DARK_SEA_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_SEA_GREEN); + /// #2F4F4F color constant. + pub const DARK_SLATE_GRAY: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_SLATE_GRAY); + /// #00CED1 color constant. + pub const DARK_TURQUOISE: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_TURQUOISE); + /// #9400D3 color constant. + pub const DARK_VIOLET: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_VIOLET); + /// #FF1493 color constant. + pub const DEEP_PINK: Rgb = Rgb::from_raw(pros_sys::COLOR_DEEP_PINK); + /// #00BFFF color constant. + pub const DEEP_SKY_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_DEEP_SKY_BLUE); + /// #696969 color constant. + pub const DIM_GRAY: Rgb = Rgb::from_raw(pros_sys::COLOR_DIM_GRAY); + /// #1E90FF color constant. + pub const DODGER_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_DODGER_BLUE); + /// #B22222 color constant. + pub const FIRE_BRICK: Rgb = Rgb::from_raw(pros_sys::COLOR_FIRE_BRICK); + /// #FFFAF0 color constant. + pub const FLORAL_WHITE: Rgb = Rgb::from_raw(pros_sys::COLOR_FLORAL_WHITE); + /// #228B22 color constant. + pub const FOREST_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_FOREST_GREEN); + /// #FF00FF color constant. + pub const FUCHSIA: Rgb = Rgb::from_raw(pros_sys::COLOR_FUCHSIA); + /// #DCDCDC color constant. + pub const GAINSBORO: Rgb = Rgb::from_raw(pros_sys::COLOR_GAINSBORO); + /// #F8F8FF color constant. + pub const GHOST_WHITE: Rgb = Rgb::from_raw(pros_sys::COLOR_GHOST_WHITE); + /// #FFD700 color constant. + pub const GOLD: Rgb = Rgb::from_raw(pros_sys::COLOR_GOLD); + /// #DAA520 color constant. + pub const GOLDENROD: Rgb = Rgb::from_raw(pros_sys::COLOR_GOLDENROD); + /// #808080 color constant. + pub const GRAY: Rgb = Rgb::from_raw(pros_sys::COLOR_GRAY); + /// #008000 color constant. + pub const GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_GREEN); + /// #ADFF2F color constant. + pub const GREEN_YELLOW: Rgb = Rgb::from_raw(pros_sys::COLOR_GREEN_YELLOW); + /// #F0FFF0 color constant. + pub const HONEYDEW: Rgb = Rgb::from_raw(pros_sys::COLOR_HONEYDEW); + /// #FF69B4 color constant. + pub const HOT_PINK: Rgb = Rgb::from_raw(pros_sys::COLOR_HOT_PINK); + /// #CD5C5C color constant. + pub const INDIAN_RED: Rgb = Rgb::from_raw(pros_sys::COLOR_INDIAN_RED); + /// #4B0082 color constant. + pub const INDIGO: Rgb = Rgb::from_raw(pros_sys::COLOR_INDIGO); + /// #FFFFF0 color constant. + pub const IVORY: Rgb = Rgb::from_raw(pros_sys::COLOR_IVORY); + /// #F0E68C color constant. + pub const KHAKI: Rgb = Rgb::from_raw(pros_sys::COLOR_KHAKI); + /// #E6E6FA color constant. + pub const LAVENDER: Rgb = Rgb::from_raw(pros_sys::COLOR_LAVENDER); + /// #FFF0F5 color constant. + pub const LAVENDER_BLUSH: Rgb = Rgb::from_raw(pros_sys::COLOR_LAVENDER_BLUSH); + /// #7CFC00 color constant. + pub const LAWN_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_LAWN_GREEN); + /// #FFFACD color constant. + pub const LEMON_CHIFFON: Rgb = Rgb::from_raw(pros_sys::COLOR_LEMON_CHIFFON); + /// #ADD8E6 color constant. + pub const LIGHT_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_BLUE); + /// #F08080 color constant. + pub const LIGHT_CORAL: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_CORAL); + /// #E0FFFF color constant. + pub const LIGHT_CYAN: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_CYAN); + /// #FAFAD2 color constant. + pub const LIGHT_GOLDENROD_YELLOW: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_GOLDENROD_YELLOW); + /// #90EE90 color constant. + pub const LIGHT_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_GREEN); + /// #D3D3D3 color constant. + pub const LIGHT_GRAY: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_GRAY); + /// #FFB6C1 color constant. + pub const LIGHT_PINK: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_PINK); + /// #FFA07A color constant. + pub const LIGHT_SALMON: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_SALMON); + /// #20B2AA color constant. + pub const LIGHT_SEA_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_SEA_GREEN); + /// #87CEFA color constant. + pub const LIGHT_SKY_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_SKY_BLUE); + /// #778899 color constant. + pub const LIGHT_SLATE_GRAY: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_SLATE_GRAY); + /// #B0C4DE color constant. + pub const LIGHT_STEEL_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_STEEL_BLUE); + /// #FFFFE0 color constant. + pub const LIGHT_YELLOW: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_YELLOW); + /// #00FF00 color constant. + pub const LIME: Rgb = Rgb::from_raw(pros_sys::COLOR_LIME); + /// #32CD32 color constant. + pub const LIME_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_LIME_GREEN); + /// #FAF0E6 color constant. + pub const LINEN: Rgb = Rgb::from_raw(pros_sys::COLOR_LINEN); + /// #FF00FF color constant. + pub const MAGENTA: Rgb = Rgb::from_raw(pros_sys::COLOR_MAGENTA); + /// #800000 color constant. + pub const MAROON: Rgb = Rgb::from_raw(pros_sys::COLOR_MAROON); + /// #66CDAA color constant. + pub const MEDIUM_AQUAMARINE: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_AQUAMARINE); + /// #0000CD color constant. + pub const MEDIUM_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_BLUE); + /// #BA55D3 color constant. + pub const MEDIUM_ORCHID: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_ORCHID); + /// #9370DB color constant. + pub const MEDIUM_PURPLE: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_PURPLE); + /// #3CB371 color constant. + pub const MEDIUM_SEA_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_SEA_GREEN); + /// #7B68EE color constant. + pub const MEDIUM_SLATE_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_SLATE_BLUE); + /// #00FA9A color constant. + pub const MEDIUM_SPRING_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_SPRING_GREEN); + /// #48D1CC color constant. + pub const MEDIUM_TURQUOISE: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_TURQUOISE); + /// #C71585 color constant. + pub const MEDIUM_VIOLET_RED: Rgb = Rgb::from_raw(pros_sys::COLOR_MEDIUM_VIOLET_RED); + /// #191970 color constant. + pub const MIDNIGHT_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_MIDNIGHT_BLUE); + /// #F5FFFA color constant. + pub const MINT_CREAM: Rgb = Rgb::from_raw(pros_sys::COLOR_MINT_CREAM); + /// #FFE4E1 color constant. + pub const MISTY_ROSE: Rgb = Rgb::from_raw(pros_sys::COLOR_MISTY_ROSE); + /// #FFE4B5 color constant. + pub const MOCCASIN: Rgb = Rgb::from_raw(pros_sys::COLOR_MOCCASIN); + /// #FFDEAD color constant. + pub const NAVAJO_WHITE: Rgb = Rgb::from_raw(pros_sys::COLOR_NAVAJO_WHITE); + /// #000080 color constant. + pub const NAVY: Rgb = Rgb::from_raw(pros_sys::COLOR_NAVY); + /// #FDF5E6 color constant. + pub const OLD_LACE: Rgb = Rgb::from_raw(pros_sys::COLOR_OLD_LACE); + /// #808000 color constant. + pub const OLIVE: Rgb = Rgb::from_raw(pros_sys::COLOR_OLIVE); + /// #6B8E23 color constant. + pub const OLIVE_DRAB: Rgb = Rgb::from_raw(pros_sys::COLOR_OLIVE_DRAB); + /// #FFA500 color constant. + pub const ORANGE: Rgb = Rgb::from_raw(pros_sys::COLOR_ORANGE); + /// #FF4500 color constant. + pub const ORANGE_RED: Rgb = Rgb::from_raw(pros_sys::COLOR_ORANGE_RED); + /// #DA70D6 color constant. + pub const ORCHID: Rgb = Rgb::from_raw(pros_sys::COLOR_ORCHID); + /// #EEE8AA color constant. + pub const PALE_GOLDENROD: Rgb = Rgb::from_raw(pros_sys::COLOR_PALE_GOLDENROD); + /// #98FB98 color constant. + pub const PALE_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_PALE_GREEN); + /// #AFEEEE color constant. + pub const PALE_TURQUOISE: Rgb = Rgb::from_raw(pros_sys::COLOR_PALE_TURQUOISE); + /// #DB7093 color constant. + pub const PALE_VIOLET_RED: Rgb = Rgb::from_raw(pros_sys::COLOR_PALE_VIOLET_RED); + /// #FFEFD5 color constant. + pub const PAPAY_WHIP: Rgb = Rgb::from_raw(pros_sys::COLOR_PAPAY_WHIP); + /// #FFDAB9 color constant. + pub const PEACH_PUFF: Rgb = Rgb::from_raw(pros_sys::COLOR_PEACH_PUFF); + /// #CD853F color constant. + pub const PERU: Rgb = Rgb::from_raw(pros_sys::COLOR_PERU); + /// #FFC0CB color constant. + pub const PINK: Rgb = Rgb::from_raw(pros_sys::COLOR_PINK); + /// #DDA0DD color constant. + pub const PLUM: Rgb = Rgb::from_raw(pros_sys::COLOR_PLUM); + /// #B0E0E6 color constant. + pub const POWDER_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_POWDER_BLUE); + /// #800080 color constant. + pub const PURPLE: Rgb = Rgb::from_raw(pros_sys::COLOR_PURPLE); + /// #FF0000 color constant. + pub const RED: Rgb = Rgb::from_raw(pros_sys::COLOR_RED); + /// #BC8F8F color constant. + pub const ROSY_BROWN: Rgb = Rgb::from_raw(pros_sys::COLOR_ROSY_BROWN); + /// #4169E1 color constant. + pub const ROYAL_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_ROYAL_BLUE); + /// #8B4513 color constant. + pub const SADDLE_BROWN: Rgb = Rgb::from_raw(pros_sys::COLOR_SADDLE_BROWN); + /// #FA8072 color constant. + pub const SALMON: Rgb = Rgb::from_raw(pros_sys::COLOR_SALMON); + /// #F4A460 color constant. + pub const SANDY_BROWN: Rgb = Rgb::from_raw(pros_sys::COLOR_SANDY_BROWN); + /// #2E8B57 color constant. + pub const SEA_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_SEA_GREEN); + /// #FFF5EE color constant. + pub const SEASHELL: Rgb = Rgb::from_raw(pros_sys::COLOR_SEASHELL); + /// #A0522D color constant. + pub const SIENNA: Rgb = Rgb::from_raw(pros_sys::COLOR_SIENNA); + /// #C0C0C0 color constant. + pub const SILVER: Rgb = Rgb::from_raw(pros_sys::COLOR_SILVER); + /// #87CEEB color constant. + pub const SKY_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_SKY_BLUE); + /// #6A5ACD color constant. + pub const SLATE_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_SLATE_BLUE); + /// #708090 color constant. + pub const SLATE_GRAY: Rgb = Rgb::from_raw(pros_sys::COLOR_SLATE_GRAY); + /// #FFFAFA color constant. + pub const SNOW: Rgb = Rgb::from_raw(pros_sys::COLOR_SNOW); + /// #00FF7F color constant. + pub const SPRING_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_SPRING_GREEN); + /// #4682B4 color constant. + pub const STEEL_BLUE: Rgb = Rgb::from_raw(pros_sys::COLOR_STEEL_BLUE); + /// #D2B48C color constant. + pub const TAN: Rgb = Rgb::from_raw(pros_sys::COLOR_TAN); + /// #008080 color constant. + pub const TEAL: Rgb = Rgb::from_raw(pros_sys::COLOR_TEAL); + /// #D8BFD8 color constant. + pub const THISTLE: Rgb = Rgb::from_raw(pros_sys::COLOR_THISTLE); + /// #FF6347 color constant. + pub const TOMATO: Rgb = Rgb::from_raw(pros_sys::COLOR_TOMATO); + /// #40E0D0 color constant. + pub const TURQUOISE: Rgb = Rgb::from_raw(pros_sys::COLOR_TURQUOISE); + /// #EE82EE color constant. + pub const VIOLET: Rgb = Rgb::from_raw(pros_sys::COLOR_VIOLET); + /// #F5DEB3 color constant. + pub const WHEAT: Rgb = Rgb::from_raw(pros_sys::COLOR_WHEAT); + /// #FFFFFF color constant. + pub const WHITE: Rgb = Rgb::from_raw(pros_sys::COLOR_WHITE); + /// #F5F5F5 color constant. + pub const WHITE_SMOKE: Rgb = Rgb::from_raw(pros_sys::COLOR_WHITE_SMOKE); + /// #FFFF00 color constant. + pub const YELLOW: Rgb = Rgb::from_raw(pros_sys::COLOR_YELLOW); + /// #9ACD32 color constant. + pub const YELLOW_GREEN: Rgb = Rgb::from_raw(pros_sys::COLOR_YELLOW_GREEN); + /// Alias to [`Self::SLATE_GRAY`]. + pub const DARK_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_GREY); + /// Alias to [`Self::DARK_SLATE_GRAY`]. + pub const DARK_SLATE_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_SLATE_GREY); + /// Alias to [`Self::DIM_GRAY`]. + pub const DIM_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_DIM_GREY); + /// Alias to [`Self::GRAY`]. + pub const GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_GREY); + /// Alias to [`Self::LIGHT_GRAY`]. + pub const LIGHT_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_GREY); + /// Alias to [`Self::LIGHT_SLATE_GRAY`]. + pub const LIGHT_SLATE_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_SLATE_GREY); + /// Alias to [`Self::SLATE_GREY`]. + pub const SLATE_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_SLATE_GREY); + + const BITMASK: u32 = 0b11111111; + + /// Create a new RGB8 color. + pub const fn new(red: u8, green: u8, blue: u8) -> Self { + Self { + r: red, + g: green, + b: blue, + } + } + + /// Create a new RGB8 color from a raw u32 value. + pub const fn from_raw(raw: u32) -> Self { + Self { + r: ((raw >> 16) & Self::BITMASK) as _, + g: ((raw >> 8) & Self::BITMASK) as _, + b: (raw & Self::BITMASK) as _, + } + } + + /// Get the red value of the color. + pub const fn red(&self) -> u8 { + self.r + } + + /// Get the green value of the color. + pub const fn green(&self) -> u8 { + self.g + } + + /// Get the blue value of the color. + pub const fn blue(&self) -> u8 { + self.b + } +} + +impl From<(u8, u8, u8)> for Rgb { + fn from(tuple: (u8, u8, u8)) -> Self { + Self { + r: tuple.0, + g: tuple.1, + b: tuple.2, + } + } +} + +impl From for (u8, u8, u8) { + fn from(value: Rgb) -> (u8, u8, u8) { + (value.r, value.g, value.b) + } +} + +impl From for u32 { + fn from(value: Rgb) -> u32 { + ((value.r as u32) << 16) + ((value.g as u32) << 8) + value.b as u32 + } +} + +impl From for Rgb { + fn from(value: u32) -> Self { + Self::from_raw(value) + } +} diff --git a/packages/pros/src/competition.rs b/packages/pros-devices/src/competition.rs similarity index 97% rename from packages/pros/src/competition.rs rename to packages/pros-devices/src/competition.rs index 92221f88..089b830b 100644 --- a/packages/pros/src/competition.rs +++ b/packages/pros-devices/src/competition.rs @@ -1,5 +1,5 @@ //! Utilities for getting what state of the competition the robot is in. -//! + use pros_sys::misc::{COMPETITION_AUTONOMOUS, COMPETITION_CONNECTED, COMPETITION_DISABLED}; // TODO: change this to use PROS' internal version once we switch to PROS 4. @@ -44,7 +44,7 @@ pub enum CompetitionSystem { /// Competition state is controlled by a VEX Field Controller. FieldControl, - // Competition state is controlled by a VEXnet competition switch. + /// Competition state is controlled by a VEXnet competition switch. CompetitionSwitch, } diff --git a/packages/pros-devices/src/controller.rs b/packages/pros-devices/src/controller.rs new file mode 100644 index 00000000..ba06b0b8 --- /dev/null +++ b/packages/pros-devices/src/controller.rs @@ -0,0 +1,350 @@ +//! Read from the buttons and joysticks on the controller and write to the controller's display. +//! +//! Controllers are identified by their id, which is either 0 (master) or 1 (partner). +//! State of a controller can be checked by calling [`Controller::state`] which will return a struct with all of the buttons' and joysticks' state. + +use alloc::{ffi::CString, vec::Vec}; + +use pros_core::{bail_on, map_errno}; +use pros_sys::{controller_id_e_t, PROS_ERR}; +use snafu::Snafu; + +/// Holds whether or not the buttons on the controller are pressed or not +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] +pub struct Buttons { + /// The 'A' button on the right button pad of the controller. + pub a: bool, + /// The 'B' button on the right button pad of the controller. + pub b: bool, + /// The 'X' button on the right button pad of the controller. + pub x: bool, + /// The 'Y' button on the right button pad of the controller. + pub y: bool, + + /// The up arrow on the left arrow pad of the controller. + pub up: bool, + /// The down arrow on the left arrow pad of the controller. + pub down: bool, + /// The left arrow on the left arrow pad of the controller. + pub left: bool, + /// The right arrow on the left arrow pad of the controller. + pub right: bool, + /// The first trigger on the left side of the controller. + pub left_trigger_1: bool, + /// The second trigger on the left side of the controller. + pub left_trigger_2: bool, + /// The first trigger on the right side of the controller. + pub right_trigger_1: bool, + /// The second trigger on the right side of the controller. + pub right_trigger_2: bool, +} + +/// Stores how far the joystick is away from the center (at *(0, 0)*) from -1 to 1. +/// On the x axis left is negative, and right is positive. +/// On the y axis down is negative, and up is positive. +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub struct Joystick { + /// Left and right x value of the joystick + pub x: f32, + /// Up and down y value of the joystick + pub y: f32, +} + +/// Stores both joysticks on the controller. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Joysticks { + /// Left joystick + pub left: Joystick, + /// Right joystick + pub right: Joystick, +} + +/// Stores the current state of the controller; the joysticks and buttons. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct ControllerState { + /// Analog joysticks state + pub joysticks: Joysticks, + /// Digital buttons state + pub buttons: Buttons, +} + +/// Represents one line on the controller console. +#[derive(Debug, Clone, Copy)] +pub struct ControllerLine { + controller: Controller, + line: u8, +} + +impl ControllerLine { + /// The maximum length that can fit in one line on the controllers display. + pub const MAX_TEXT_LEN: usize = 14; + /// The maximum line number that can be used on the controller display. + pub const MAX_LINE_NUM: u8 = 2; + + /// Attempts to print text to the controller display. + /// Returns an error if the text is too long to fit on the display or if an internal PROS error occured. + pub fn try_print(&self, text: impl Into>) -> Result<(), ControllerError> { + let text = text.into(); + let text_len = text.len(); + assert!( + text_len > ControllerLine::MAX_TEXT_LEN, + "Printed text is too long to fit on controller display ({text_len} > {})", + Self::MAX_TEXT_LEN + ); + let c_text = CString::new(text).expect("parameter `text` should not contain null bytes"); + bail_on!(PROS_ERR, unsafe { + pros_sys::controller_set_text(self.controller.id(), self.line, 0, c_text.as_ptr()) + }); + Ok(()) + } + /// Prints text to the controller display. + /// # Panics + /// Unlike [`ControllerLine::try_print`], + /// this function will panic if the text is too long to fit on the display + /// or if an internal PROS error occured. + pub fn print(&self, text: impl Into>) { + self.try_print(text).unwrap(); + } +} + +/// A digital channel (button) on the VEX controller. +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ControllerButton { + /// The 'A' button on the right button pad of the controller. + A = pros_sys::E_CONTROLLER_DIGITAL_A, + /// The 'B' button on the right button pad of the controller. + B = pros_sys::E_CONTROLLER_DIGITAL_B, + /// The 'X' button on the right button pad of the controller. + X = pros_sys::E_CONTROLLER_DIGITAL_X, + /// The 'Y' button on the right button pad of the controller. + Y = pros_sys::E_CONTROLLER_DIGITAL_Y, + /// The up arrow on the left arrow pad of the controller. + Up = pros_sys::E_CONTROLLER_DIGITAL_UP, + /// The down arrow on the left arrow pad of the controller. + Down = pros_sys::E_CONTROLLER_DIGITAL_DOWN, + /// The left arrow on the left arrow pad of the controller. + Left = pros_sys::E_CONTROLLER_DIGITAL_LEFT, + /// The right arrow on the left arrow pad of the controller. + Right = pros_sys::E_CONTROLLER_DIGITAL_RIGHT, + /// The first trigger on the left side of the controller. + LeftTrigger1 = pros_sys::E_CONTROLLER_DIGITAL_L1, + /// The second trigger on the left side of the controller. + LeftTrigger2 = pros_sys::E_CONTROLLER_DIGITAL_L2, + /// The first trigger on the right side of the controller. + RightTrigger1 = pros_sys::E_CONTROLLER_DIGITAL_R1, + /// The second trigger on the right side of the controller. + RightTrigger2 = pros_sys::E_CONTROLLER_DIGITAL_R2, +} + +/// An analog channel (joystick axis) on the VEX controller. +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum JoystickAxis { + /// Left (-1.0) and right (1.0) x axis of the left joystick + LeftX = pros_sys::E_CONTROLLER_ANALOG_LEFT_X, + /// Down (-1.0) and up (1.0) y axis of the left joystick + LeftY = pros_sys::E_CONTROLLER_ANALOG_LEFT_Y, + /// Left (-1.0) and right (1.0) x axis of the right joystick + RightX = pros_sys::E_CONTROLLER_ANALOG_RIGHT_X, + /// Down (-1.0) and up (1.0) y axis of the right joystick + RightY = pros_sys::E_CONTROLLER_ANALOG_RIGHT_Y, +} + +/// The basic type for a controller. +/// Used to get the state of its joysticks and controllers. +#[repr(u32)] +#[derive(Debug, Clone, Copy, Default)] +pub enum Controller { + /// The master controller. Controllers default to this value. + #[default] + Master = pros_sys::E_CONTROLLER_MASTER, + /// The partner controller. + Partner = pros_sys::E_CONTROLLER_PARTNER, +} + +impl Controller { + const fn id(&self) -> controller_id_e_t { + *self as controller_id_e_t + } + + /// Returns a line on the controller display that can be used to print to the controller. + pub fn line(&self, line_num: u8) -> ControllerLine { + assert!( + line_num > ControllerLine::MAX_LINE_NUM, + "Line number is too large for controller display ({line_num} > {})", + ControllerLine::MAX_LINE_NUM + ); + + ControllerLine { + controller: *self, + line: line_num, + } + } + + /// Gets the current state of the controller in its entirety. + pub fn state(&self) -> Result { + Ok(ControllerState { + joysticks: unsafe { + Joysticks { + left: Joystick { + x: bail_on!( + PROS_ERR, + pros_sys::controller_get_analog( + self.id(), + pros_sys::E_CONTROLLER_ANALOG_LEFT_X, + ) + ) as f32 + / 127.0, + y: bail_on!( + PROS_ERR, + pros_sys::controller_get_analog( + self.id(), + pros_sys::E_CONTROLLER_ANALOG_LEFT_Y, + ) + ) as f32 + / 127.0, + }, + right: Joystick { + x: bail_on!( + PROS_ERR, + pros_sys::controller_get_analog( + self.id(), + pros_sys::E_CONTROLLER_ANALOG_RIGHT_X, + ) + ) as f32 + / 127.0, + y: bail_on!( + PROS_ERR, + pros_sys::controller_get_analog( + self.id(), + pros_sys::E_CONTROLLER_ANALOG_RIGHT_Y, + ) + ) as f32 + / 127.0, + }, + } + }, + buttons: unsafe { + Buttons { + a: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_A, + ) + ) == 1, + b: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_B, + ) + ) == 1, + x: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_X, + ) + ) == 1, + y: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_Y, + ) + ) == 1, + up: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_UP, + ) + ) == 1, + down: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_DOWN, + ) + ) == 1, + left: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_LEFT, + ) + ) == 1, + right: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_RIGHT, + ) + ) == 1, + left_trigger_1: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_L1, + ) + ) == 1, + left_trigger_2: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_L2, + ) + ) == 1, + right_trigger_1: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_R1, + ) + ) == 1, + right_trigger_2: bail_on!( + PROS_ERR, + pros_sys::controller_get_digital( + self.id(), + pros_sys::E_CONTROLLER_DIGITAL_R2, + ) + ) == 1, + } + }, + }) + } + + /// Gets the state of a specific button on the controller. + pub fn button(&self, button: ControllerButton) -> Result { + Ok(bail_on!(PROS_ERR, unsafe { + pros_sys::controller_get_digital(self.id(), button as pros_sys::controller_digital_e_t) + }) == 1) + } + + /// Gets the state of a specific joystick axis on the controller. + pub fn joystick_axis(&self, axis: JoystickAxis) -> Result { + Ok(bail_on!(PROS_ERR, unsafe { + pros_sys::controller_get_analog(self.id(), axis as pros_sys::controller_analog_e_t) + }) as f32 + / 127.0) + } +} + +#[derive(Debug, Snafu)] +/// Errors that can occur when interacting with the controller. +pub enum ControllerError { + /// The controller ID given was invalid, expected E_CONTROLLER_MASTER or E_CONTROLLER_PARTNER. + InvalidControllerId, + + /// Another resource is already using the controller. + ConcurrentAccess, +} + +map_errno! { + ControllerError { + EACCES => Self::ConcurrentAccess, + EINVAL => Self::InvalidControllerId, + } +} diff --git a/packages/pros/src/devices/mod.rs b/packages/pros-devices/src/lib.rs similarity index 51% rename from packages/pros/src/devices/mod.rs rename to packages/pros-devices/src/lib.rs index 29fa348a..f4c7dfe0 100644 --- a/packages/pros/src/devices/mod.rs +++ b/packages/pros-devices/src/lib.rs @@ -4,26 +4,35 @@ //! //! # Overview //! -//! The V5 brain features 21 RJ11 4p4c connector ports (known as "Smart Ports") for communicating with +//! 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 analog-to-digital conversion capability for //! compatibility with legacy cortex devices. This module provides access for both smart devices and //! ADI devices. //! //! # Organization //! -//! - [`devices::smart`] contains abstractions and types for smart port connected devices. -//! - [`devices::adi`] contains abstractions for three wire ADI connected devices. -//! - [`devices::battery`] provides functions for getting information about the currently connected +//! - [`smart`] contains abstractions and types for smart port connected devices. +//! - [`adi`] contains abstractions for three wire ADI connected devices. +//! - [`battery`] provides functions for getting information about the currently connected //! battery. -//! - [`devices::controller`] provides types for interacting with the V5 controller. +//! - [`controller`] provides types for interacting with the V5 controller. + +#![no_std] + +extern crate alloc; pub mod adi; pub mod smart; pub mod battery; +pub mod color; +pub mod competition; pub mod controller; pub mod peripherals; pub mod position; +pub mod screen; +pub mod usd; pub use controller::Controller; pub use position::Position; +pub use screen::Screen; diff --git a/packages/pros-devices/src/peripherals.rs b/packages/pros-devices/src/peripherals.rs new file mode 100644 index 00000000..f99c769d --- /dev/null +++ b/packages/pros-devices/src/peripherals.rs @@ -0,0 +1,239 @@ +//! Peripherals implementations. +//! +//! Peripherals are the best way to create devices because they allow you to do it safely. +//! Both kinds of peripherals, [`Peripherals`] and [`DynamicPeripherals`], guarentee that a given port is only used to create one device. +//! This is important because creating multiple devices on the same port can cause bugs and unexpected behavior. +//! Devices can still be created unsafely without using peripherals, but it isn't recommended. +//! +//! ## Examples +//! +//! ### Using [`Peripherals`] +//! ```rust +//! # use pros::prelude::*; +//! let mut peripherals = Peripherals::take().unwrap(); +//! let motor = Motor::new(peripherals.port_1); +//! let adi_digital_in = AdiDigitalIn::new(peripherals.adi_d); +//! ``` +//! ### Using [`DynamicPeripherals`] +//! ```rust +//! # use pros::prelude::*; +//! let mut peripherals = DynamicPeripherals::new(Peripherals::take().unwrap()); +//! let motor = peripherals.take_smart_port(1).unwrap(); +//! let adi_digital_in = peripherals.take_adi_port(4).unwrap(); +//! ``` + +use core::sync::atomic::AtomicBool; + +use crate::{adi::AdiPort, screen::Screen, smart::SmartPort}; + +static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false); + +#[derive(Debug)] +/// A struct that contains all ports on the V5 Brain +/// and guarentees **at compile time** that each port is only used once. +/// Because of the fact that this checks at compile time, it cannot be moved once it has been used to create a device. +/// If you need to store a peripherals struct for use in multiple functions, use [`DynamicPeripherals`] instead. +/// This struct is always preferred over [`DynamicPeripherals`] when possible. +pub struct Peripherals { + /// Brain screen + pub screen: Screen, + + /// Smart port 1 on the brain + pub port_1: SmartPort, + /// Smart port 2 on the brain + pub port_2: SmartPort, + /// Smart port 3 on the brain + pub port_3: SmartPort, + /// Smart port 4 on the brain + pub port_4: SmartPort, + /// Smart port 5 on the brain + pub port_5: SmartPort, + /// Smart port 6 on the brain + pub port_6: SmartPort, + /// Smart port 7 on the brain + pub port_7: SmartPort, + /// Smart port 8 on the brain + pub port_8: SmartPort, + /// Smart port 9 on the brain + pub port_9: SmartPort, + /// Smart port 10 on the brain + pub port_10: SmartPort, + /// Smart port 11 on the brain + pub port_11: SmartPort, + /// Smart port 12 on the brain + pub port_12: SmartPort, + /// Smart port 13 on the brain + pub port_13: SmartPort, + /// Smart port 14 on the brain + pub port_14: SmartPort, + /// Smart port 15 on the brain + pub port_15: SmartPort, + /// Smart port 16 on the brain + pub port_16: SmartPort, + /// Smart port 17 on the brain + pub port_17: SmartPort, + /// Smart port 18 on the brain + pub port_18: SmartPort, + /// Smart port 19 on the brain + pub port_19: SmartPort, + /// Smart port 20 on the brain + pub port_20: SmartPort, + /// Smart port 21 on the brain + pub port_21: SmartPort, + + /// Adi port A on the brain. + pub adi_a: AdiPort, + /// Adi port B on the brain. + pub adi_b: AdiPort, + /// Adi port C on the brain. + pub adi_c: AdiPort, + /// Adi port D on the brain. + pub adi_d: AdiPort, + /// Adi port E on the brain. + pub adi_e: AdiPort, + /// Adi port F on the brain. + pub adi_f: AdiPort, + /// Adi port G on the brain. + pub adi_g: AdiPort, + /// Adi port H on the brain. + pub adi_h: AdiPort, +} + +impl Peripherals { + // SAFETY: caller must ensure that the SmartPorts and AdiPorts created are unique + unsafe fn new() -> Self { + // SAFETY: caller must ensure that this function is only called once + unsafe { + Self { + screen: Screen::new(), + + port_1: SmartPort::new(1), + port_2: SmartPort::new(2), + port_3: SmartPort::new(3), + port_4: SmartPort::new(4), + port_5: SmartPort::new(5), + port_6: SmartPort::new(6), + port_7: SmartPort::new(7), + port_8: SmartPort::new(8), + port_9: SmartPort::new(9), + port_10: SmartPort::new(10), + port_11: SmartPort::new(11), + port_12: SmartPort::new(12), + port_13: SmartPort::new(13), + port_14: SmartPort::new(14), + port_15: SmartPort::new(15), + port_16: SmartPort::new(16), + port_17: SmartPort::new(17), + port_18: SmartPort::new(18), + port_19: SmartPort::new(19), + port_20: SmartPort::new(20), + port_21: SmartPort::new(21), + + adi_a: AdiPort::new(1, None), + adi_b: AdiPort::new(2, None), + adi_c: AdiPort::new(3, None), + adi_d: AdiPort::new(4, None), + adi_e: AdiPort::new(5, None), + adi_f: AdiPort::new(6, None), + adi_g: AdiPort::new(7, None), + adi_h: AdiPort::new(8, None), + } + } + } + + /// Attempts to create a new [`Peripherals`] struct, returning `None` if one has already been created. + /// + /// After calling this function, future calls to [`Peripherals::take`] will return `None`. + pub fn take() -> Option { + if PERIPHERALS_TAKEN.swap(true, core::sync::atomic::Ordering::AcqRel) { + None + } else { + Some(unsafe { Self::new() }) + } + } + + /// Creates a new [`Peripherals`] struct without ensuring that is the only unique instance. + /// + /// After calling this function, future calls to [`Peripherals::take`] will return `None`. + /// + /// # Safety + /// + /// Creating new [`SmartPort`]s and [`Peripherals`] instances is inherently unsafe due to the possibility of constructing more than + /// one device on the same port index and allowing multiple mutable references to the same hardware device. + /// The caller must ensure that only one mutable reference to each port is used. + pub unsafe fn steal() -> Self { + PERIPHERALS_TAKEN.store(true, core::sync::atomic::Ordering::Release); + // SAFETY: caller must ensure that this call is safe + unsafe { Self::new() } + } +} + +/// Guarentees that ports are only used once **at runtime** +/// This is useful for when you want to store a peripherals struct for use in multiple functions. +/// When possible, use [`Peripherals`] instead. +#[derive(Debug)] +pub struct DynamicPeripherals { + screen: bool, + smart_ports: [bool; 21], + adi_slots: [bool; 8], +} +impl DynamicPeripherals { + /// Creates a new dynamic peripherals + /// In order to guarentee that no ports created by this struct, + /// this function takes a [`Peripherals`]. + /// This guarentees safety because [`Peripherals`] cannot be passed by value + /// after they have been used to create devices. + pub fn new(_peripherals: Peripherals) -> Self { + let smart_ports = [false; 21]; + let adi_slots = [false; 8]; + Self { + screen: false, + smart_ports, + adi_slots, + } + } + + /// Creates a [`SmartPort`] only if one has not been created on the given port before. + /// + /// # Panics + /// + /// This function panics if the provided port is outside the range 1-21. + /// Ports outside of this range are invalid and cannot be created. + pub fn take_smart_port(&mut self, port_index: u8) -> Option { + let port_index = port_index as usize - 1; + if self.smart_ports[port_index] { + return None; + }; + self.smart_ports[port_index] = true; + Some(unsafe { SmartPort::new(port_index as u8 + 1) }) + } + + /// Creates an [`AdiPort`] only if one has not been created on the given slot before. + /// + /// # Panics + /// + /// This function panics if the provided port is outside the range 1-8. + /// Slots outside of this range are invalid and cannot be created. + pub fn take_adi_port(&mut self, port_index: u8) -> Option { + let port_index = port_index as usize - 1; + if self.adi_slots[port_index] { + return None; + } + self.smart_ports[port_index] = true; + Some(unsafe { AdiPort::new(port_index as u8 + 1, None) }) + } + + /// Creates a [`Screen`] only if one has not been created before. + pub fn take_screen(&mut self) -> Option { + if self.screen { + return None; + } + self.screen = true; + Some(unsafe { Screen::new() }) + } +} +impl From for DynamicPeripherals { + fn from(peripherals: Peripherals) -> Self { + Self::new(peripherals) + } +} diff --git a/packages/pros/src/devices/position.rs b/packages/pros-devices/src/position.rs similarity index 93% rename from packages/pros/src/devices/position.rs rename to packages/pros-devices/src/position.rs index 09f0bd9d..72d89f2d 100644 --- a/packages/pros/src/devices/position.rs +++ b/packages/pros-devices/src/position.rs @@ -8,7 +8,9 @@ use core::{cmp::Ordering, ops::*}; /// Represents an angular position. #[derive(Clone, Copy, Debug)] pub enum Position { + /// Degrees of rotation. Degrees(f64), + /// Counts of full rotations, 360 degrees. Rotations(f64), /// Raw encoder ticks. Counts(i64), @@ -16,17 +18,17 @@ pub enum Position { impl Position { /// Creates a position from a specified number of degrees. - pub fn from_degrees(position: f64) -> Self { + pub const fn from_degrees(position: f64) -> Self { Self::Degrees(position) } /// Creates a position from a specified number of rotations. - pub fn from_rotations(position: f64) -> Self { + pub const fn from_rotations(position: f64) -> Self { Self::Rotations(position) } /// Creates a position from a specified number of counts (raw encoder tics). - pub fn from_counts(position: i64) -> Self { + pub const fn from_counts(position: i64) -> Self { Self::Counts(position) } diff --git a/packages/pros-devices/src/screen.rs b/packages/pros-devices/src/screen.rs new file mode 100644 index 00000000..f90a7548 --- /dev/null +++ b/packages/pros-devices/src/screen.rs @@ -0,0 +1,564 @@ +//! Brain screen display and touch functions. +//! +//! Contains user calls to the v5 screen for touching and displaying graphics. +//! The [`Fill`] trait can be used to draw shapes and text to the screen. + +use alloc::{ffi::CString, string::String, vec::Vec}; + +use pros_core::{bail_on, map_errno}; +use pros_sys::PROS_ERR; +use snafu::Snafu; + +use crate::color::{IntoRgb, Rgb}; + +#[derive(Debug, Eq, PartialEq)] +/// Represents the physical display on the V5 Brain. +pub struct Screen { + writer_buffer: String, + current_line: i16, +} + +impl core::fmt::Write for Screen { + fn write_str(&mut self, text: &str) -> core::fmt::Result { + for character in text.chars() { + if character == '\n' { + if self.current_line > (Self::MAX_VISIBLE_LINES as i16 - 2) { + self.scroll(0, Self::LINE_HEIGHT) + .map_err(|_| core::fmt::Error)?; + } else { + self.current_line += 1; + } + + self.flush_writer().map_err(|_| core::fmt::Error)?; + } else { + self.writer_buffer.push(character); + } + } + + self.fill( + &Text::new( + self.writer_buffer.as_str(), + TextPosition::Line(self.current_line), + TextFormat::Medium, + ), + Rgb::WHITE, + ) + .map_err(|_| core::fmt::Error)?; + + Ok(()) + } +} + +/// A type implementing this trait can draw a filled shape to the display. +pub trait Fill { + /// The type of error that can be generated when drawing to the screen. + type Error; + + /// Draw a filled shape to the display. + fn fill(&self, screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error>; +} + +/// A type implementing this trait can draw an outlined shape to the display. +pub trait Stroke { + /// The type of error that can be generated when drawing to the screen. + type Error; + + /// Draw an outlined shape to the display. + fn stroke(&self, screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error>; +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// A circle that can be drawn on the screen. +pub struct Circle { + x: i16, + y: i16, + radius: i16, +} + +impl Circle { + /// Create a circle with the given coordinates and radius. + /// The coordinates are the center of the circle. + pub const fn new(x: i16, y: i16, radius: i16) -> Self { + Self { x, y, radius } + } +} + +impl Fill for Circle { + type Error = ScreenError; + + fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_set_pen(color.into_rgb().into()) + }); + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_fill_circle(self.x, self.y, self.radius) + }); + + Ok(()) + } +} + +impl Stroke for Circle { + type Error = ScreenError; + + fn stroke(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_set_pen(color.into_rgb().into()) + }); + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_draw_circle(self.x, self.y, self.radius) + }); + + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// A line that can be drawn on the screen. +/// The width is the same as the pen width. +pub struct Line { + x0: i16, + y0: i16, + x1: i16, + y1: i16, +} + +impl Line { + /// Create a new line with the given coordinates. + pub const fn new(x0: i16, y0: i16, x1: i16, y1: i16) -> Self { + Self { x0, y0, x1, y1 } + } +} + +impl Fill for Line { + type Error = ScreenError; + + fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_set_pen(color.into_rgb().into()) + }); + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_draw_line(self.x0, self.y0, self.x1, self.y1) + }); + + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// A rectangle that can be drawn on the screen. +pub struct Rect { + x0: i16, + y0: i16, + x1: i16, + y1: i16, +} + +impl Rect { + /// Create a new rectangle with the given coordinates. + pub const fn new(start_x: i16, start_y: i16, end_x: i16, end_y: i16) -> Self { + Self { + x0: start_x, + y0: start_y, + x1: end_x, + y1: end_y, + } + } +} + +impl Stroke for Rect { + type Error = ScreenError; + + fn stroke(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_set_pen(color.into_rgb().into()) + }); + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_draw_rect(self.x0, self.y0, self.x1, self.y1) + }); + + Ok(()) + } +} + +impl Fill for Rect { + type Error = ScreenError; + + fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_set_pen(color.into_rgb().into()) + }); + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_fill_rect(self.x0, self.y0, self.x1, self.y1) + }); + + Ok(()) + } +} + +#[repr(i32)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// Options for how a text object should be formatted. +pub enum TextFormat { + /// Small text. + Small = pros_sys::E_TEXT_SMALL, + /// Medium text. + Medium = pros_sys::E_TEXT_MEDIUM, + /// Large text. + Large = pros_sys::E_TEXT_LARGE, + /// Medium horizontally centered text. + MediumCenter = pros_sys::E_TEXT_MEDIUM_CENTER, + /// Large horizontally centered text. + LargeCenter = pros_sys::E_TEXT_LARGE_CENTER, +} + +impl From for pros_sys::text_format_e_t { + fn from(value: TextFormat) -> pros_sys::text_format_e_t { + value as _ + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// The position of a text object on the screen. +pub enum TextPosition { + /// A point to draw the text at. + Point(i16, i16), + /// A line number to draw the text at. + Line(i16), +} + +#[derive(Debug, Clone, Eq, PartialEq)] +/// A peice of text that can be drawn on the display. +pub struct Text { + position: TextPosition, + text: CString, + format: TextFormat, +} + +impl Text { + /// Create a new text with a given position and format + pub fn new(text: &str, position: TextPosition, format: TextFormat) -> Self { + Self { + text: CString::new(text) + .expect("CString::new encountered NULL (U+0000) byte in non-terminating position."), + position, + format, + } + } +} + +impl Fill for Text { + type Error = ScreenError; + + fn fill(&self, _screen: &mut Screen, color: impl IntoRgb) -> Result<(), Self::Error> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_set_pen(color.into_rgb().into()) + }); + bail_on!(PROS_ERR as u32, unsafe { + match self.position { + TextPosition::Point(x, y) => { + pros_sys::screen_print_at(self.format.into(), x, y, self.text.as_ptr()) + } + TextPosition::Line(line) => { + pros_sys::screen_print(self.format.into(), line, self.text.as_ptr()) + } + } + }); + + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// A touch event on the screen. +pub struct TouchEvent { + /// Touch state. + pub state: TouchState, + /// X coordinate of the touch. + pub x: i16, + /// Y coordinate of the touch. + pub y: i16, + /// how many times the screen has been pressed. + pub press_count: i32, + /// how many times the screen has been released. + pub release_count: i32, +} + +impl TryFrom for TouchEvent { + type Error = ScreenError; + + fn try_from(value: pros_sys::screen_touch_status_s_t) -> Result { + Ok(Self { + state: value.touch_status.try_into()?, + x: value.x, + y: value.y, + press_count: value.press_count, + release_count: value.release_count, + }) + } +} + +#[repr(i32)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +/// The state of a given touch. +pub enum TouchState { + /// The touch has been released. + Released = pros_sys::E_TOUCH_RELEASED, + /// The screen has been touched. + Pressed = pros_sys::E_TOUCH_PRESSED, + /// The touch is still being held. + Held = pros_sys::E_TOUCH_HELD, +} + +impl TryFrom for TouchState { + type Error = ScreenError; + + fn try_from(value: pros_sys::last_touch_e_t) -> Result { + bail_on!(pros_sys::E_TOUCH_ERROR, value); + + Ok(match value { + pros_sys::E_TOUCH_RELEASED => Self::Released, + pros_sys::E_TOUCH_PRESSED => Self::Pressed, + pros_sys::E_TOUCH_HELD => Self::Held, + _ => unreachable!(), + }) + } +} + +impl From for pros_sys::last_touch_e_t { + fn from(value: TouchState) -> pros_sys::last_touch_e_t { + value as _ + } +} + +impl Screen { + /// The maximum number of lines that can be visible on the screen at once. + pub const MAX_VISIBLE_LINES: usize = 12; + + /// The height of a single line of text on the screen. + pub const LINE_HEIGHT: i16 = 20; + + /// The horizontal resolution of the display. + pub const HORIZONTAL_RESOLUTION: i16 = 480; + + /// The vertical resolution of the writable part of the display. + pub const VERTICAL_RESOLUTION: i16 = 240; + + /// Create a new screen. + /// + /// # Safety + /// + /// Creating new `Screen`s is inherently unsafe due to the possibility of constructing + /// more than one screen at once allowing multiple mutable references to the same + /// hardware device. Prefer using [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible. + pub unsafe fn new() -> Self { + Self { + current_line: 0, + writer_buffer: String::default(), + } + } + + fn flush_writer(&mut self) -> Result<(), ScreenError> { + self.fill( + &Text::new( + self.writer_buffer.as_str(), + TextPosition::Line(self.current_line), + TextFormat::Medium, + ), + Rgb::WHITE, + )?; + + self.writer_buffer.clear(); + + Ok(()) + } + + /// Scroll the entire display buffer. + /// + /// This function effectively y-offsets all pixels drawn to the display buffer by + /// a number (`offset`) of pixels. + pub fn scroll(&mut self, start: i16, offset: i16) -> Result<(), ScreenError> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_scroll(start, offset) + }); + + Ok(()) + } + + /// Scroll a region of the screen. + /// + /// This will effectively y-offset the display buffer in this area by + /// `offset` pixels. + pub fn scroll_area( + &mut self, + x0: i16, + y0: i16, + x1: i16, + y1: i16, + offset: i16, + ) -> Result<(), ScreenError> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_scroll_area(x0, y0, x1, y1, offset) + }); + + Ok(()) + } + + /// Draw a filled object to the screen. + pub fn fill( + &mut self, + shape: &impl Fill, + color: impl IntoRgb, + ) -> Result<(), ScreenError> { + shape.fill(self, color) + } + + /// Draw an outlined object to the screen. + pub fn stroke( + &mut self, + shape: &impl Stroke, + color: impl IntoRgb, + ) -> Result<(), ScreenError> { + shape.stroke(self, color) + } + + /// Wipe the entire display buffer, filling it with a specified color. + pub fn erase(color: impl IntoRgb) -> Result<(), ScreenError> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_set_eraser(color.into_rgb().into()) + }); + bail_on!(PROS_ERR as u32, unsafe { pros_sys::screen_erase() }); + + Ok(()) + } + + /// Draw a color to a specified pixel position on the screen. + pub fn draw_pixel(x: i16, y: i16) -> Result<(), ScreenError> { + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_draw_pixel(x, y) + }); + + Ok(()) + } + + /// Draw a buffer of pixel colors to a specified region of the screen. + pub fn draw_buffer( + &mut self, + x0: i16, + y0: i16, + x1: i16, + y1: i16, + buf: T, + src_stride: i32, + ) -> Result<(), ScreenError> + where + T: IntoIterator, + I: IntoRgb, + { + let raw_buf = buf + .into_iter() + .map(|i| i.into_rgb().into()) + .collect::>(); + // Convert the coordinates to u32 to avoid overflows when multiplying. + let expected_size = ((x1 - x0) as u32 * (y1 - y0) as u32) as usize; + if raw_buf.len() != expected_size { + return Err(ScreenError::CopyBufferWrongSize { + buffer_size: raw_buf.len(), + expected_size, + }); + } + + // SAFETY: The buffer is guaranteed to be the correct size. + bail_on!(PROS_ERR as u32, unsafe { + pros_sys::screen_copy_area(x0, y0, x1, y1, raw_buf.as_ptr(), src_stride) + }); + + Ok(()) + } + + /// Draw an error box to the screen. + /// + /// This function is internally used by the pros-rs panic handler for displaying + /// panic messages graphically before exiting. + pub fn draw_error(&mut self, msg: &str) -> Result<(), ScreenError> { + const ERROR_BOX_MARGIN: i16 = 16; + const ERROR_BOX_PADDING: i16 = 16; + const LINE_MAX_WIDTH: usize = 52; + + let error_box_rect = Rect::new( + ERROR_BOX_MARGIN, + ERROR_BOX_MARGIN, + Self::HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN, + Self::VERTICAL_RESOLUTION - ERROR_BOX_MARGIN, + ); + + self.fill(&error_box_rect, Rgb::RED)?; + self.stroke(&error_box_rect, Rgb::WHITE)?; + + let mut buffer = String::new(); + let mut line: i16 = 0; + + for (i, character) in msg.char_indices() { + if !character.is_ascii_control() { + buffer.push(character); + } + + if character == '\n' || ((buffer.len() % LINE_MAX_WIDTH == 0) && (i > 0)) { + self.fill( + &Text::new( + buffer.as_str(), + TextPosition::Point( + ERROR_BOX_MARGIN + ERROR_BOX_PADDING, + ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Self::LINE_HEIGHT), + ), + TextFormat::Small, + ), + Rgb::WHITE, + )?; + + line += 1; + buffer.clear(); + } + } + + self.fill( + &Text::new( + buffer.as_str(), + TextPosition::Point( + ERROR_BOX_MARGIN + ERROR_BOX_PADDING, + ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Self::LINE_HEIGHT), + ), + TextFormat::Small, + ), + Rgb::WHITE, + )?; + + Ok(()) + } + + /// Get the current touch status of the screen. + pub fn touch_status(&self) -> Result { + unsafe { pros_sys::screen_touch_status() }.try_into() + } +} + +#[derive(Debug, Snafu)] +/// Errors that can occur when interacting with the screen. +pub enum ScreenError { + /// Another resource is currently trying to access the screen mutex. + ConcurrentAccess, + + /// The given buffer of colors was wrong size to fill the specified area. + CopyBufferWrongSize { + /// The size of the buffer. + buffer_size: usize, + /// The expected size of the buffer. + expected_size: usize, + }, +} + +map_errno! { + ScreenError { + EACCES => Self::ConcurrentAccess, + } +} diff --git a/packages/pros/src/devices/smart/distance.rs b/packages/pros-devices/src/smart/distance.rs similarity index 95% rename from packages/pros/src/devices/smart/distance.rs rename to packages/pros-devices/src/smart/distance.rs index 55fec9a7..607991ca 100644 --- a/packages/pros/src/devices/smart/distance.rs +++ b/packages/pros-devices/src/smart/distance.rs @@ -4,10 +4,10 @@ use core::ffi::c_double; +use pros_core::{bail_on, error::PortError}; use pros_sys::PROS_ERR; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::error::{bail_on, PortError}; /// A physical distance sensor plugged into a port. /// Distance sensors can only keep track of one object at a time. @@ -18,7 +18,7 @@ pub struct DistanceSensor { impl DistanceSensor { /// Create a new distance sensor from a smart port index. - pub fn new(port: SmartPort) -> Self { + pub const fn new(port: SmartPort) -> Self { Self { port } } diff --git a/packages/pros-devices/src/smart/expander.rs b/packages/pros-devices/src/smart/expander.rs new file mode 100644 index 00000000..df4d26dc --- /dev/null +++ b/packages/pros-devices/src/smart/expander.rs @@ -0,0 +1,66 @@ +//! ADI expander module support. +//! +//! The ADI expander API is similar to that of [`Peripherals`]. +//! A main difference between the two is that ADI expanders can be created safely without returning an option. +//! This is because they require a [`SmartPort`] to be created which can only be created without either peripherals struct unsafely. + +use super::{SmartDevice, SmartDeviceType, SmartPort}; +use crate::adi::AdiPort; + +/// Represents an ADI expander module plugged into a smart port. +/// +/// ADI Expanders allow a smart port to be used as an "adapter" for eight additional ADI slots +/// if all onboard [`AdiPort`]s are used. +/// +/// This struct gives access to [`AdiPort`]s similarly to how [`Peripherals`] works. Ports may +/// be partially moved out of this struct to create devices. +#[derive(Debug, Eq, PartialEq)] +pub struct AdiExpander { + /// ADI port A on the expander. + pub adi_a: AdiPort, + /// ADI port B on the expander. + pub adi_b: AdiPort, + /// ADI Port C on the expander. + pub adi_c: AdiPort, + /// ADI Port D on the expander. + pub adi_d: AdiPort, + /// ADI Port E on the expander. + pub adi_e: AdiPort, + /// ADI Port F on the expander. + pub adi_f: AdiPort, + /// ADI Port G on the expander. + pub adi_g: AdiPort, + /// ADI Port H on the expander. + pub adi_h: AdiPort, + + port: SmartPort, +} + +impl AdiExpander { + /// Create a new expander from a smart port index. + pub fn new(port: SmartPort) -> Self { + unsafe { + Self { + adi_a: AdiPort::new(1, Some(port.index())), + adi_b: AdiPort::new(2, Some(port.index())), + adi_c: AdiPort::new(3, Some(port.index())), + adi_d: AdiPort::new(4, Some(port.index())), + adi_e: AdiPort::new(5, Some(port.index())), + adi_f: AdiPort::new(6, Some(port.index())), + adi_g: AdiPort::new(7, Some(port.index())), + adi_h: AdiPort::new(8, Some(port.index())), + port, + } + } + } +} + +impl SmartDevice for AdiExpander { + fn port_index(&self) -> u8 { + self.port.index() + } + + fn device_type(&self) -> SmartDeviceType { + SmartDeviceType::Adi + } +} diff --git a/packages/pros/src/devices/smart/gps.rs b/packages/pros-devices/src/smart/gps.rs similarity index 80% rename from packages/pros/src/devices/smart/gps.rs rename to packages/pros-devices/src/smart/gps.rs index 1a773832..16e561b0 100644 --- a/packages/pros/src/devices/smart/gps.rs +++ b/packages/pros-devices/src/smart/gps.rs @@ -3,24 +3,34 @@ //! A notable differenc between this API and that of PROS //! is that [`GpsSensor::status`] returns acceleration along with other status data. +use pros_core::{bail_on, error::PortError, map_errno}; use pros_sys::{PROS_ERR, PROS_ERR_F}; use snafu::Snafu; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::error::{bail_on, map_errno, PortError}; -/// Represents the data output from a GPS sensor. +//TODO: Figure out what all the units are #[derive(Default, Debug, Clone, Copy, PartialEq)] +/// Represents the data output from a GPS sensor. pub struct GpsStatus { + /// The x-coordinate of the GPS sensor in meters. pub x: f64, + /// The y-coordinate of the GPS sensor in meters. pub y: f64, + /// The pitch of the GPS sensor. pub pitch: f64, + /// The roll of the GPS sensor. pub roll: f64, + /// The yaw of the GPS sensor. pub yaw: f64, + /// The heading of the GPS sensor. pub heading: f64, + /// The x-acceleration of the GPS sensor. pub accel_x: f64, + /// The y-acceleration of the GPS sensor. pub accel_y: f64, + /// The z-acceleration of the GPS sensor. pub accel_z: f64, } @@ -100,11 +110,16 @@ impl SmartDevice for GpsSensor { } #[derive(Debug, Snafu)] +/// Errors that can occur when using a GPS sensor. pub enum GpsError { - #[snafu(display("GPS sensor is still calibrating."))] + /// The GPS sensor is still calibrating. StillCalibrating, #[snafu(display("{source}"), context(false))] - Port { source: PortError }, + /// Generic port related error. + Port { + /// The source of the error. + source: PortError, + }, } map_errno! { diff --git a/packages/pros/src/devices/smart/imu.rs b/packages/pros-devices/src/smart/imu.rs similarity index 74% rename from packages/pros/src/devices/smart/imu.rs rename to packages/pros-devices/src/smart/imu.rs index e2c6c66f..b60c5ba6 100644 --- a/packages/pros/src/devices/smart/imu.rs +++ b/packages/pros-devices/src/smart/imu.rs @@ -1,20 +1,21 @@ +//! Inertial sensor (IMU) device. + use core::{ pin::Pin, task::{Context, Poll}, time::Duration, }; +use pros_core::{ + bail_on, + error::{take_errno, FromErrno, PortError}, + map_errno, + time::Instant, +}; use pros_sys::{PROS_ERR, PROS_ERR_F}; use snafu::Snafu; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::{ - error::{bail_on, map_errno, take_errno, FromErrno, PortError}, - time::Instant, -}; - -pub const IMU_RESET_TIMEOUT: Duration = Duration::from_secs(3); -pub const IMU_MIN_DATA_RATE: Duration = Duration::from_millis(5); /// Represents a smart port configured as a V5 inertial sensor (IMU) #[derive(Debug, Eq, PartialEq)] @@ -23,8 +24,14 @@ pub struct InertialSensor { } impl InertialSensor { + /// The timeout for the IMU to calibrate. + pub const CALIBRATION_TIMEOUT: Duration = Duration::from_secs(3); + + /// The minimum data rate that you can set an IMU to. + pub const MIN_DATA_RATE: Duration = Duration::from_millis(5); + /// Create a new inertial sensor from a smart port index. - pub fn new(port: SmartPort) -> Self { + pub const fn new(port: SmartPort) -> Self { Self { port } } @@ -33,15 +40,15 @@ impl InertialSensor { /// This takes approximately 2 seconds, and is blocking until the IMU status flag is set properly. /// There is additionally a 3 second timeout that will return [`InertialError::CalibrationTimedOut`] if the timeout is exceeded. pub fn calibrate_blocking(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_reset_blocking(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_reset_blocking(self.port.index()) + }); Ok(()) } /// Calibrate IMU asynchronously. /// - /// Returns an [`InertialCalibrationFuture`] that is be polled until the IMU status flag reports the sensor as + /// Returns an [`InertialCalibrateFuture`] that is be polled until the IMU status flag reports the sensor as /// no longer calibrating. /// There a 3 second timeout that will return [`InertialError::CalibrationTimedOut`] if the timeout is exceeded. pub fn calibrate(&mut self) -> InertialCalibrateFuture { @@ -49,7 +56,7 @@ impl InertialSensor { } /// Check if the Intertial Sensor is currently calibrating. - pub fn is_calibrating(&mut self) -> Result { + pub fn calibrating(&mut self) -> Result { Ok(self.status()?.calibrating()) } @@ -58,12 +65,9 @@ impl InertialSensor { /// This value is theoretically unbounded. Clockwise rotations are represented with positive degree values, /// while counterclockwise rotations are represented with negative ones. pub fn rotation(&self) -> Result { - unsafe { - Ok(bail_on!( - PROS_ERR_F, - pros_sys::imu_get_rotation(self.port.index()) - )) - } + Ok(bail_on!(PROS_ERR_F, unsafe { + pros_sys::imu_get_rotation(self.port.index()) + })) } /// Get the Inertial Sensor’s heading relative to the initial direction of its x-axis. @@ -71,47 +75,35 @@ impl InertialSensor { /// This value is bounded by [0, 360) degrees. Clockwise rotations are represented with positive degree values, /// while counterclockwise rotations are represented with negative ones. pub fn heading(&self) -> Result { - unsafe { - Ok(bail_on!( - PROS_ERR_F, - pros_sys::imu_get_heading(self.port.index()) - )) - } + Ok(bail_on!(PROS_ERR_F, unsafe { + pros_sys::imu_get_heading(self.port.index()) + })) } /// Get the Inertial Sensor’s pitch angle bounded by (-180, 180) degrees. pub fn pitch(&self) -> Result { - unsafe { - Ok(bail_on!( - PROS_ERR_F, - pros_sys::imu_get_pitch(self.port.index()) - )) - } + Ok(bail_on!(PROS_ERR_F, unsafe { + pros_sys::imu_get_pitch(self.port.index()) + })) } /// Get the Inertial Sensor’s roll angle bounded by (-180, 180) degrees. pub fn roll(&self) -> Result { - unsafe { - Ok(bail_on!( - PROS_ERR_F, - pros_sys::imu_get_roll(self.port.index()) - )) - } + Ok(bail_on!(PROS_ERR_F, unsafe { + pros_sys::imu_get_roll(self.port.index()) + })) } /// Get the Inertial Sensor’s yaw angle bounded by (-180, 180) degrees. pub fn yaw(&self) -> Result { - unsafe { - Ok(bail_on!( - PROS_ERR_F, - pros_sys::imu_get_yaw(self.port.index()) - )) - } + Ok(bail_on!(PROS_ERR_F, unsafe { + pros_sys::imu_get_yaw(self.port.index()) + })) } /// Read the inertial sensor's status code. pub fn status(&self) -> Result { - unsafe { Ok(bail_on!(PROS_ERR as _, pros_sys::imu_get_status(self.port.index())).into()) } + unsafe { pros_sys::imu_get_status(self.port.index()).try_into() } } /// Get a quaternion representing the Inertial Sensor’s orientation. @@ -136,57 +128,55 @@ impl InertialSensor { /// Resets the current reading of the Inertial Sensor’s heading to zero. pub fn zero_heading(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_tare_heading(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_tare_heading(self.port.index()) + }); Ok(()) } /// Resets the current reading of the Inertial Sensor’s rotation to zero. pub fn zero_rotation(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_tare_rotation(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_tare_rotation(self.port.index()) + }); Ok(()) } /// Resets the current reading of the Inertial Sensor’s pitch to zero. pub fn zero_pitch(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_tare_pitch(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_tare_pitch(self.port.index()) + }); Ok(()) } /// Resets the current reading of the Inertial Sensor’s roll to zero. pub fn zero_roll(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_tare_roll(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_tare_roll(self.port.index()) + }); Ok(()) } /// Resets the current reading of the Inertial Sensor’s yaw to zero. pub fn zero_yaw(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_tare_yaw(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_tare_yaw(self.port.index()) + }); Ok(()) } /// Reset all 3 euler values of the Inertial Sensor to 0. pub fn zero_euler(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_tare_euler(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_tare_euler(self.port.index()) + }); Ok(()) } /// Resets all 5 values of the Inertial Sensor to 0. pub fn zero(&mut self) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_tare(self.port.index())); - } + bail_on!(PROS_ERR, unsafe { pros_sys::imu_tare(self.port.index()) }); Ok(()) } @@ -194,23 +184,17 @@ impl InertialSensor { /// /// Will default to +/- 180 if target exceeds +/- 180. pub fn set_euler(&mut self, euler: Euler) -> Result<(), InertialError> { - unsafe { - bail_on!( - PROS_ERR, - pros_sys::imu_set_euler(self.port.index(), euler.into()) - ); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_set_euler(self.port.index(), euler.into()) + }); Ok(()) } /// Sets the current reading of the Inertial Sensor’s rotation to target value. pub fn set_rotation(&mut self, rotation: f64) -> Result<(), InertialError> { - unsafe { - bail_on!( - PROS_ERR, - pros_sys::imu_set_rotation(self.port.index(), rotation) - ); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_set_rotation(self.port.index(), rotation) + }); Ok(()) } @@ -218,12 +202,9 @@ impl InertialSensor { /// /// Target will default to 360 if above 360 and default to 0 if below 0. pub fn set_heading(&mut self, heading: f64) -> Result<(), InertialError> { - unsafe { - bail_on!( - PROS_ERR, - pros_sys::imu_set_heading(self.port.index(), heading) - ); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_set_heading(self.port.index(), heading) + }); Ok(()) } @@ -231,9 +212,9 @@ impl InertialSensor { /// /// Will default to +/- 180 if target exceeds +/- 180. pub fn set_pitch(&mut self, pitch: f64) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_set_pitch(self.port.index(), pitch)); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_set_pitch(self.port.index(), pitch) + }); Ok(()) } @@ -241,9 +222,9 @@ impl InertialSensor { /// /// Will default to +/- 180 if target exceeds +/- 180. pub fn set_roll(&mut self, roll: f64) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_set_roll(self.port.index(), roll)); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_set_roll(self.port.index(), roll) + }); Ok(()) } @@ -251,18 +232,18 @@ impl InertialSensor { /// /// Will default to +/- 180 if target exceeds +/- 180. pub fn set_yaw(&mut self, yaw: f64) -> Result<(), InertialError> { - unsafe { - bail_on!(PROS_ERR, pros_sys::imu_set_yaw(self.port.index(), yaw)); - } + bail_on!(PROS_ERR, unsafe { + pros_sys::imu_set_yaw(self.port.index(), yaw) + }); Ok(()) } /// Sets the update rate of the IMU. /// - /// This duration must be above [`IMU_MIN_DATA_RATE`] (5 milliseconds). + /// This duration must be above [`Self::MIN_DATA_RATE`] (5 milliseconds). pub fn set_data_rate(&mut self, data_rate: Duration) -> Result<(), InertialError> { unsafe { - let rate_ms = if data_rate > IMU_MIN_DATA_RATE { + let rate_ms = if data_rate > Self::MIN_DATA_RATE { if let Ok(rate) = u32::try_from(data_rate.as_millis()) { rate } else { @@ -404,35 +385,36 @@ impl InertialStatus { pub const fn calibrating(&self) -> bool { self.0 & pros_sys::E_IMU_STATUS_CALIBRATING != 0 } - - /// Determine if an error state was reached when trying to get the IMU's status. - pub const fn error(&self) -> bool { - self.0 & pros_sys::E_IMU_STATUS_ERROR != 0 - } } -impl From for InertialStatus { - fn from(value: pros_sys::imu_status_e_t) -> Self { - Self(value) +impl TryFrom for InertialStatus { + type Error = InertialError; + + fn try_from(value: pros_sys::imu_status_e_t) -> Result { + Ok(Self(bail_on!(pros_sys::E_IMU_STATUS_ERROR, value))) } } #[derive(Debug, Clone, Copy)] +/// Future that calibrates an IMU +/// created with [`InertialSensor::calibrate`]. pub enum InertialCalibrateFuture { + /// Calibrate the IMU Calibrate(u8), + /// Wait for the IMU to finish calibrating Waiting(u8, Instant), } impl core::future::Future for InertialCalibrateFuture { type Output = Result<(), InertialError>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match *self { Self::Calibrate(port) => match unsafe { pros_sys::imu_reset(port) } { PROS_ERR => { let errno = take_errno(); - return Poll::Ready(Err(InertialError::from_errno(errno) - .unwrap_or_else(|| panic!("Unknown errno code {errno}")))); + Poll::Ready(Err(InertialError::from_errno(errno) + .unwrap_or_else(|| panic!("Unknown errno code {errno}")))) } _ => { *self = Self::Waiting(port, Instant::now()); @@ -441,9 +423,8 @@ impl core::future::Future for InertialCalibrateFuture { } }, Self::Waiting(port, timestamp) => { - const PROS_ERR_U32: u32 = PROS_ERR as _; let is_calibrating = match unsafe { pros_sys::imu_get_status(port) } { - PROS_ERR_U32 => { + pros_sys::E_IMU_STATUS_ERROR => { let errno = take_errno(); return Poll::Ready(Err(InertialError::from_errno(take_errno()) .unwrap_or_else(|| panic!("Unknown errno code {errno}")))); @@ -453,7 +434,7 @@ impl core::future::Future for InertialCalibrateFuture { if !is_calibrating { return Poll::Ready(Ok(())); - } else if timestamp.elapsed() > IMU_RESET_TIMEOUT { + } else if timestamp.elapsed() > InertialSensor::CALIBRATION_TIMEOUT { return Poll::Ready(Err(InertialError::CalibrationTimedOut)); } @@ -465,13 +446,18 @@ impl core::future::Future for InertialCalibrateFuture { } #[derive(Debug, Snafu)] +/// Errors that can occur when interacting with an Inertial Sensor. pub enum InertialError { - #[snafu(display("Inertial sensor is still calibrating, but exceeded calibration timeout."))] + /// The inertial sensor spent too long calibrating. CalibrationTimedOut, - #[snafu(display("Sensor data rate has a minimum duration of 5 milliseconds."))] + /// Invalid sensor data rate, expected >= 5 milliseconds. InvalidDataRate, #[snafu(display("{source}"), context(false))] - Port { source: PortError }, + /// Generic port related error. + Port { + /// The source of the error. + source: PortError, + }, } map_errno! { diff --git a/packages/pros/src/devices/smart/link.rs b/packages/pros-devices/src/smart/link.rs similarity index 81% rename from packages/pros/src/devices/smart/link.rs rename to packages/pros-devices/src/smart/link.rs index e66cedee..ddcebdf7 100644 --- a/packages/pros/src/devices/smart/link.rs +++ b/packages/pros-devices/src/smart/link.rs @@ -7,11 +7,15 @@ use alloc::{ffi::CString, string::String}; use core::ffi::CStr; use no_std_io::io; +use pros_core::{ + bail_errno, bail_on, + error::{FromErrno, PortError}, + map_errno, +}; use pros_sys::{link_receive, link_transmit, E_LINK_RECEIVER, E_LINK_TRANSMITTER}; use snafu::Snafu; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::error::{bail_errno, bail_on, map_errno, FromErrno, PortError}; /// Types that implement Link can be used to send data to another robot over VEXLink. pub trait Link: SmartDevice { @@ -30,12 +34,18 @@ pub trait Link: SmartDevice { } /// A recieving end of a VEXLink connection. +#[derive(Debug)] pub struct RxLink { port: SmartPort, id: CString, } impl RxLink { + /// Get the number of bytes in the incoming buffer. + /// Be aware that the number of incoming bytes can change between when this is called + /// and when you read from the link. + /// If you create a buffer of this size, and then attempt to read into it + /// you may encounter panics or data loss. pub fn num_incoming_bytes(&self) -> Result { let num = unsafe { bail_on!( @@ -47,6 +57,8 @@ impl RxLink { Ok(num) } + /// Clear all bytes in the incoming buffer. + /// All data in the incoming will be lost and completely unrecoverable. pub fn clear_incoming_buf(&self) -> Result<(), LinkError> { unsafe { bail_on!( @@ -58,6 +70,7 @@ impl RxLink { Ok(()) } + /// Receive data from the link incoming buffer into a buffer. pub fn receive(&self, buf: &mut [u8]) -> Result { const PROS_ERR_U32: u32 = pros_sys::PROS_ERR as _; @@ -112,6 +125,7 @@ impl io::Read for RxLink { } /// A transmitting end of a VEXLink connection. +#[derive(Debug)] pub struct TxLink { port: SmartPort, id: CString, @@ -120,6 +134,7 @@ pub struct TxLink { impl TxLink { // I have literally no idea what the purpose of this is, // there is no way to push to the transmission buffer without transmitting it. + /// Get the number of bytes to be sent over this link. pub fn num_outgoing_bytes(&self) -> Result { let num = unsafe { bail_on!( @@ -131,12 +146,13 @@ impl TxLink { Ok(num) } + /// Transmit a buffer of data over the link. pub fn transmit(&self, buf: &[u8]) -> Result { const PROS_ERR_U32: u32 = pros_sys::PROS_ERR as _; match unsafe { link_transmit(self.port.index(), buf.as_ptr().cast(), buf.len() as _) } { PROS_ERR_U32 => { - let errno = crate::error::take_errno(); + let errno = pros_core::error::take_errno(); Err(FromErrno::from_errno(errno) .unwrap_or_else(|| panic!("Unknown errno code {errno}"))) } @@ -193,19 +209,24 @@ impl SmartDevice for TxLink { } #[derive(Debug, Snafu)] +/// Errors that can occur when using VEXLink. pub enum LinkError { - #[snafu(display("No link is connected through the radio."))] + /// No link is connected through the radio. NoLink, - #[snafu(display("The transmitter buffer is still busy with a previous transmission, and there is no room in the FIFO buffer (queue) to transmit the data."))] + /// The transmitter buffer is still busy with a previous transmission, and there is no room in the FIFO buffer (queue) to transmit the data. BufferBusyFull, - #[snafu(display("The data given was a C NULL."))] + /// Invalid data: the data given was a C NULL. NullData, - #[snafu(display("Protocol error related to start byte, data size, or checksum during a transmission or reception."))] + /// Protocol error related to start byte, data size, or checksum during a transmission or reception. Protocol, - #[snafu(display("The link is busy."))] + /// The link is busy. Busy, #[snafu(display("{source}"), context(false))] - Port { source: PortError }, + /// Generic port related error + Port { + /// The source of the error + source: PortError, + }, } map_errno! { diff --git a/packages/pros/src/devices/smart/mod.rs b/packages/pros-devices/src/smart/mod.rs similarity index 62% rename from packages/pros/src/devices/smart/mod.rs rename to packages/pros-devices/src/smart/mod.rs index 9a7c081e..e45ae7cf 100644 --- a/packages/pros/src/devices/smart/mod.rs +++ b/packages/pros-devices/src/smart/mod.rs @@ -3,11 +3,17 @@ //! This module provides abstractions over device access connected through VEX V5 Smart Ports. This //! includes motors, many common sensors, vexlink, and raw serial access. //! -//! # Overview +//! # Hardware Overview +//! +//! The V5 brain features 21 RJ9 4p4c connector ports (known as "Smart Ports") for communicating with +//! newer V5 peripherals. Smart port devices have a variable sample rate (unlike ADI, which is limited +//! to 10ms), and can support basic data transfer over serial. +//! +//! # Smart Port Devices //! //! Most devices can be created with a `new` function that generally takes a port number along with other //! device-specific parameters. All sensors are thread safe, however sensors can only be safely constructed -//! using the [`Peripherals`] API. +//! using the [`peripherals`](crate::peripherals) API. //! //! In cases where PROS gives the option of a blocking or non-blocking API, //! the blocking API is used for a synchronous method and the non-blocking API is used to create a future. @@ -15,6 +21,7 @@ //! More specific info for each device is availible in their respective modules. pub mod distance; +pub mod expander; pub mod gps; pub mod imu; pub mod link; @@ -24,28 +31,54 @@ pub mod rotation; pub mod vision; pub use distance::DistanceSensor; +pub use expander::AdiExpander; pub use gps::GpsSensor; pub use imu::InertialSensor; pub use link::{Link, RxLink, TxLink}; pub use motor::Motor; pub use optical::OpticalSensor; +use pros_core::{bail_on, error::PortError}; pub use rotation::RotationSensor; pub use vision::VisionSensor; -use crate::{error::bail_on, prelude::PortError}; - -/// Common functionality for a smart port device. +/// Defines common functionality shared by all smart port devices. pub trait SmartDevice { /// Get the index of the [`SmartPort`] this device is registered on. /// /// Ports are indexed starting from 1. + /// + /// # Examples + /// + /// ``` + /// let sensor = InertialSensor::new(peripherals.port_1)?; + /// assert_eq!(sensor.port_index(), 1); + /// ``` fn port_index(&self) -> u8; /// Get the variant of [`SmartDeviceType`] that this device is associated with. + /// + /// # Examples + /// + /// ``` + /// let sensor = InertialSensor::new(peripherals.port_1)?; + /// assert_eq!(sensor.device_type(), SmartDeviceType::Imu); + /// ``` fn device_type(&self) -> SmartDeviceType; /// Determine if this device type is currently connected to the [`SmartPort`] /// that it's registered to. + /// + /// # Examples + /// + /// ``` + /// let sensor = InertialSensor::new(peripherals.port_1)?; + /// + /// if sensor.port_connected() { + /// println!("IMU is connected!"); + /// } else { + /// println!("No IMU connection found."); + /// } + /// ``` fn port_connected(&self) -> bool { let plugged_type_result: Result = unsafe { pros_sys::apix::registry_get_plugged_type(self.port_index() - 1).try_into() }; @@ -68,13 +101,23 @@ pub struct SmartPort { } impl SmartPort { - /// Create a new port. + /// Creates a new smart port on a specified index. /// /// # Safety /// /// Creating new `SmartPort`s is inherently unsafe due to the possibility of constructing /// more than one device on the same port index allowing multiple mutable references to - /// the same hardware device. Prefer using [`Peripherals`] to register devices if possible. + /// the same hardware device. This violates rust's borrow checked guarantees. Prefer using + /// [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible. + /// + /// # Examples + /// + /// ``` + /// // Create a new smart port at index 1. + /// // This is unsafe! You are responsible for ensuring that only one device registered on a + /// // single port index. + /// let my_port = unsafe { SmartPort::new(1) }; + /// ``` pub const unsafe fn new(index: u8) -> Self { Self { index } } @@ -82,16 +125,41 @@ impl SmartPort { /// Get the index of the port (port number). /// /// Ports are indexed starting from 1. - pub fn index(&self) -> u8 { + /// + /// # Examples + /// + /// ``` + /// let my_port = unsafe { SmartPort::new(1) }; + /// + /// assert_eq!(my_port.index(), 1); + /// ``` + pub const fn index(&self) -> u8 { self.index } /// Get the type of device currently connected to this port. + /// + /// # Examples + /// + /// ``` + /// let my_port = unsafe { SmartPort::new(1) }; + /// + /// println!("Type of device connected to port 1: {:?}", my_port.connected_type()?); + /// ``` pub fn connected_type(&self) -> Result { unsafe { pros_sys::apix::registry_get_plugged_type(self.index() - 1).try_into() } } /// Get the type of device this port is configured as. + /// + /// # Examples + /// + /// ``` + /// let my_port = unsafe { SmartPort::new(1) }; + /// let imu = InertialSensor::new(my_port)?; + /// + /// assert_eq!(my_port.configured_type()?, SmartDeviceType::Imu); + /// ``` pub fn configured_type(&self) -> Result { unsafe { pros_sys::apix::registry_get_bound_type(self.index() - 1).try_into() } } @@ -101,26 +169,51 @@ impl SmartPort { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum SmartDeviceType { + /// No device None = pros_sys::apix::E_DEVICE_NONE, + + /// Smart Motor Motor = pros_sys::apix::E_DEVICE_MOTOR, + + /// Rotation Sensor Rotation = pros_sys::apix::E_DEVICE_ROTATION, + + /// Inertial Sensor Imu = pros_sys::apix::E_DEVICE_IMU, + + /// Distance Sensor Distance = pros_sys::apix::E_DEVICE_DISTANCE, + + /// Vision Sensor Vision = pros_sys::apix::E_DEVICE_VISION, + + /// Optical Sensor Optical = pros_sys::apix::E_DEVICE_OPTICAL, + + /// GPS Sensor Gps = pros_sys::apix::E_DEVICE_GPS, + + /// Smart Radio Radio = pros_sys::apix::E_DEVICE_RADIO, + + /// ADI Expander + /// + /// This variant is also internally to represent the brain's onboard ADI slots. Adi = pros_sys::apix::E_DEVICE_ADI, + + /// Generic Serial Port Serial = pros_sys::apix::E_DEVICE_SERIAL, } impl TryFrom for SmartDeviceType { type Error = PortError; + /// Convert a raw `pros_sys::apix::v5_device_e_t` from `pros_sys` into a [`SmartDeviceType`]. fn try_from(value: pros_sys::apix::v5_device_e_t) -> Result { // PROS returns either -1 (WTF?!?!) or 255 which both cast to E_DEVICE_UNDEFINED // when setting ERRNO, which can only be ENXIO. - // https://github.com/purduesigbots/pros/issues/623 + // + // bail_on!(pros_sys::apix::E_DEVICE_UNDEFINED, value); Ok(match value { @@ -140,6 +233,7 @@ impl TryFrom for SmartDeviceType { } impl From for pros_sys::apix::v5_device_e_t { + /// Convert a [`SmartDeviceType`] into a raw `pros_sys::apix::v5_device_e_t`. fn from(value: SmartDeviceType) -> Self { value as _ } diff --git a/packages/pros/src/devices/smart/motor.rs b/packages/pros-devices/src/smart/motor.rs similarity index 92% rename from packages/pros/src/devices/smart/motor.rs rename to packages/pros-devices/src/smart/motor.rs index 2ff00579..b6310001 100644 --- a/packages/pros/src/devices/smart/motor.rs +++ b/packages/pros-devices/src/smart/motor.rs @@ -1,10 +1,6 @@ //! Motors and gearsets. //! -//! The motor API is similar to that of [`sensors`](crate::sensors). -//! Multiple motors can be created on the same port and they are thread safe. -//! -//! Motors can be created with the [`Motor::new`] function. -//! Once created they can be controlled with one three functions: +//! Once created motors can be controlled with one three functions: //! [`Motor::set_output`], [`Motor::set_raw_output`], and [`Motor::set_voltage`]. //! [`Motor::set_output`] takes in a f32 from -1 to 1 for ease of use with [`Controller`](crate::controller::Controller)s. //! [`Motor::set_raw_output`] takes in an i8 from -127 to 127. @@ -21,14 +17,12 @@ //! } //! ``` +use pros_core::{bail_on, error::PortError, map_errno}; use pros_sys::{PROS_ERR, PROS_ERR_F}; use snafu::Snafu; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::{ - devices::Position, - error::{bail_on, map_errno, PortError}, -}; +use crate::Position; /// The basic motor struct. #[derive(Debug, Eq, PartialEq)] @@ -39,6 +33,7 @@ pub struct Motor { //TODO: Implement good set_velocity and get_velocity functions. //TODO: Measure the number of counts per rotation. Fow now we assume it is 4096 impl Motor { + /// Create a new motor on the given port with the given brake mode. pub fn new(port: SmartPort, brake_mode: BrakeMode) -> Result { unsafe { bail_on!( @@ -54,6 +49,7 @@ impl Motor { Ok(Self { port }) } + /// Sets the gearset of the motor. pub fn set_gearset(&mut self, gearset: Gearset) -> Result<(), MotorError> { unsafe { bail_on!( @@ -64,6 +60,7 @@ impl Motor { Ok(()) } + /// Gets the gearset of the motor. pub fn gearset(&self) -> Result { Ok(unsafe { bail_on!(PROS_ERR, pros_sys::motor_get_gearing(self.port.index())) }.into()) } @@ -240,7 +237,7 @@ impl Motor { } /// Returns a future that completes when the motor reports that it has stopped. - pub fn wait_until_stopped(&self) -> MotorStoppedFuture { + pub const fn wait_until_stopped(&self) -> MotorStoppedFuture<'_> { MotorStoppedFuture { motor: self } } } @@ -279,7 +276,9 @@ impl From for pros_sys::motor_brake_mode_e_t { /// Represents what the physical motor is currently doing. #[derive(Debug, Clone, Default)] pub struct MotorState { + /// The motor is currently moving. pub busy: bool, + /// the motor is not moving. pub stopped: bool, /// the motor is at zero encoder units of rotation. pub zeroed: bool, @@ -300,8 +299,11 @@ impl From for MotorState { #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(i32)] pub enum Gearset { + /// 36:1 gear ratio Red = pros_sys::E_MOTOR_GEAR_RED, + /// 18:1 gear ratio Green = pros_sys::E_MOTOR_GEAR_GREEN, + /// 6:1 gear ratio Blue = pros_sys::E_MOTOR_GEAR_BLUE, } @@ -332,12 +334,15 @@ impl From for Gearset { } } +#[derive(Debug)] +/// A future that completes when the motor reports that it has stopped. +/// Created by [`Motor::wait_until_stopped`] pub struct MotorStoppedFuture<'a> { motor: &'a Motor, } impl<'a> core::future::Future for MotorStoppedFuture<'a> { - type Output = crate::Result; + type Output = pros_core::error::Result; fn poll( self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, @@ -353,11 +358,16 @@ impl<'a> core::future::Future for MotorStoppedFuture<'a> { } #[derive(Debug, Snafu)] +/// Errors that can occur when using a motor. pub enum MotorError { - #[snafu(display("The voltage supplied was outside of the allowed range (-12 to 12)."))] + /// The voltage supplied was outside of the allowed range of [-12, 12]. VoltageOutOfRange, #[snafu(display("{source}"), context(false))] - Port { source: PortError }, + /// Generic port related error. + Port { + /// The source of the error. + source: PortError, + }, } map_errno! { diff --git a/packages/pros/src/devices/smart/optical.rs b/packages/pros-devices/src/smart/optical.rs similarity index 81% rename from packages/pros/src/devices/smart/optical.rs rename to packages/pros-devices/src/smart/optical.rs index 93a2357c..d529887d 100644 --- a/packages/pros/src/devices/smart/optical.rs +++ b/packages/pros-devices/src/smart/optical.rs @@ -1,15 +1,12 @@ +//! Optical sensor device + use core::time::Duration; +use pros_core::{bail_on, error::PortError, map_errno}; use pros_sys::{OPT_GESTURE_ERR, PROS_ERR, PROS_ERR_F}; use snafu::Snafu; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::error::{bail_on, map_errno, PortError}; - -pub const MIN_INTEGRATION_TIME: Duration = Duration::from_millis(3); -pub const MAX_INTEGRATION_TIME: Duration = Duration::from_millis(712); - -pub const MAX_LED_PWM: u8 = 100; /// Represents a smart port configured as a V5 optical sensor #[derive(Debug, Eq, PartialEq)] @@ -19,6 +16,15 @@ pub struct OpticalSensor { } impl OpticalSensor { + /// The smallest integration time you can set on an optical sensor. + pub const MIN_INTEGRATION_TIME: Duration = Duration::from_millis(3); + + /// The largest integration time you can set on an optical sensor. + pub const MAX_INTEGRATION_TIME: Duration = Duration::from_millis(712); + + /// The maximum value for the LED PWM. + pub const MAX_LED_PWM: u8 = 100; + /// Creates a new inertial sensor from a smart port index. /// /// Gesture detection features can be optionally enabled, allowing the use of [`Self::last_gesture_direction()`] and [`Self::last_gesture_direction()`]. @@ -49,7 +55,7 @@ impl OpticalSensor { /// Sets the pwm value of the White LED. Valid values are in the range `0` `100`. pub fn set_led_pwm(&mut self, value: u8) -> Result<(), OpticalError> { - if value > MAX_LED_PWM { + if value > Self::MAX_LED_PWM { return Err(OpticalError::InvalidLedPwm); } unsafe { @@ -78,10 +84,10 @@ impl OpticalSensor { /// due to less available light being read by the sensor. /// /// Time value must be a [`Duration`] between 3 and 712 milliseconds. See - /// https://www.vexforum.com/t/v5-optical-sensor-refresh-rate/109632/9 for + /// for /// more information. pub fn set_integration_time(&mut self, time: Duration) -> Result<(), OpticalError> { - if time < MIN_INTEGRATION_TIME || time > MAX_INTEGRATION_TIME { + if time < Self::MIN_INTEGRATION_TIME || time > Self::MAX_INTEGRATION_TIME { return Err(OpticalError::InvalidIntegrationTime); } @@ -177,13 +183,13 @@ impl OpticalSensor { } /// Determine if gesture detection is enabled or not on the sensor. - pub fn gesture_detection_enabled(&self) -> bool { + pub const fn gesture_detection_enabled(&self) -> bool { self.gesture_detection_enabled } /// Get the most recent gesture data from the sensor. Gestures will be cleared after 500mS. /// - /// Will return [`OpticalError::GestureDetectionNotEnabled`] if the sensor is not + /// Will return [`OpticalError::GestureDetectionDisabled`] if the sensor is not /// confgured to detect gestures. pub fn last_gesture_direction(&self) -> Result { if !self.gesture_detection_enabled { @@ -195,7 +201,7 @@ impl OpticalSensor { /// Get the most recent raw gesture data from the sensor. /// - /// Will return [`OpticalError::GestureDetectionNotEnabled`] if the sensor is not + /// Will return [`OpticalError::GestureDetectionDisabled`] if the sensor is not /// confgured to detect gestures. pub fn last_gesture_raw(&self) -> Result { if !self.gesture_detection_enabled { @@ -217,13 +223,20 @@ impl SmartDevice for OpticalSensor { } #[derive(Default, Debug, Clone, Copy, PartialEq)] +/// Represents a gesture and its direction. pub enum GestureDirection { + /// Up gesture. Up, + /// Down gesture. Down, + /// Left gesture. Left, + /// Right gesture. Right, + /// Gesture error. Error, #[default] + /// No gesture detected. NoGesture, } @@ -245,13 +258,21 @@ impl TryFrom for GestureDirection { } #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] +/// Raw gesture data from an [`OpticalSensor`]. pub struct GestureRaw { + /// Up value. pub up: u8, + /// Down value. pub down: u8, + /// Left value. pub left: u8, + /// Right value. pub right: u8, + /// Gesture type. pub gesture_type: u8, + /// The count of the gesture. pub count: u16, + /// The time of the gesture. pub time: u32, } @@ -272,10 +293,15 @@ impl TryFrom for GestureRaw { } #[derive(Default, Debug, Clone, Copy, PartialEq)] +/// RGBC data from a [`OpticalSensor`]. pub struct Rgbc { + /// The red value from the sensor. pub red: f64, + /// The green value from the sensor. pub green: f64, + /// The blue value from the sensor. pub blue: f64, + /// The brightness value from the sensor. pub brightness: f64, } @@ -293,10 +319,15 @@ impl TryFrom for Rgbc { } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +/// Represents the raw RGBC data from the sensor. pub struct RgbcRaw { + /// The red value from the sensor. pub red: u32, + /// The green value from the sensor. pub green: u32, + /// The blue value from the sensor. pub blue: u32, + /// The clear value from the sensor. pub clear: u32, } @@ -314,18 +345,25 @@ impl TryFrom for RgbcRaw { } #[derive(Debug, Snafu)] +/// Errors that can occur when interacting with an optical sensor. pub enum OpticalError { - #[snafu(display("LED PWM value must be between 0 and 100."))] + /// Invalid LED PWM value, must be between 0 and 100. InvalidLedPwm, - #[snafu(display("Integration time must be between 3 and 712 milliseconds. See https://www.vexforum.com/t/v5-optical-sensor-refresh-rate/109632/9 for more information."))] + /// Integration time must be between 3 and 712 milliseconds. + /// + /// See for more information. InvalidIntegrationTime, - #[snafu(display("Gesture detection is not enabled for this sensor."))] + /// Gesture detection is not enabled for this sensor. GestureDetectionDisabled, #[snafu(display("{source}"), context(false))] - Port { source: PortError }, + /// Generic port related error. + Port { + /// The source of the error + source: PortError, + }, } map_errno! { diff --git a/packages/pros/src/devices/smart/rotation.rs b/packages/pros-devices/src/smart/rotation.rs similarity index 95% rename from packages/pros/src/devices/smart/rotation.rs rename to packages/pros-devices/src/smart/rotation.rs index 7a79f22b..2988af2a 100644 --- a/packages/pros/src/devices/smart/rotation.rs +++ b/packages/pros-devices/src/smart/rotation.rs @@ -2,18 +2,17 @@ //! //! Rotation sensors operate on the same [`Position`] type as motors to measure rotation. +use pros_core::{bail_on, error::PortError}; use pros_sys::PROS_ERR; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::{ - devices::position::Position, - error::{bail_on, PortError}, -}; +use crate::position::Position; /// A physical rotation sensor plugged into a port. #[derive(Debug, Eq, PartialEq)] pub struct RotationSensor { port: SmartPort, + /// Whether or not the sensor direction is reversed. pub reversed: bool, } diff --git a/packages/pros/src/devices/smart/vision.rs b/packages/pros-devices/src/smart/vision.rs similarity index 80% rename from packages/pros/src/devices/smart/vision.rs rename to packages/pros-devices/src/smart/vision.rs index 54957db0..ad27ce5e 100644 --- a/packages/pros/src/devices/smart/vision.rs +++ b/packages/pros-devices/src/smart/vision.rs @@ -5,14 +5,12 @@ extern crate alloc; use alloc::vec::Vec; +use pros_core::{bail_errno, bail_on, error::PortError, map_errno}; use pros_sys::{PROS_ERR, VISION_OBJECT_ERR_SIG}; use snafu::Snafu; use super::{SmartDevice, SmartDeviceType, SmartPort}; -use crate::{ - error::{bail_errno, bail_on, map_errno, PortError}, - lvgl::colors::LcdColor, -}; +use crate::color::Rgb; /// Represents a vision sensor plugged into the vex. #[derive(Debug, Eq, PartialEq)] @@ -139,13 +137,20 @@ impl SmartDevice for VisionSensor { //TODO: figure out how coordinates are done. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +/// An object detected by the vision sensor pub struct VisionObject { + /// The offset from the top of the object to the vision center. pub top: i16, + /// The offset from the left of the object to the vision center. pub left: i16, + /// The x-coordinate of the middle of the object relative to the vision center. pub middle_x: i16, + /// The y-coordinate of the middle of the object relative to the vision center. pub middle_y: i16, + /// The width of the object. pub width: i16, + /// The height of the object. pub height: i16, } @@ -168,89 +173,50 @@ impl TryFrom for VisionObject { } } -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] -pub struct Rgb { - r: u8, - g: u8, - b: u8, -} - -impl Rgb { - pub fn new(r: u8, g: u8, b: u8) -> Self { - Self { r, g, b } - } -} - -impl From for u32 { - fn from(other: Rgb) -> u32 { - ((other.r as u32) << 16) + ((other.g as u32) << 8) + other.b as u32 - } -} - -const BITMASK: u32 = 0b11111111; - -impl From for Rgb { - fn from(value: u32) -> Self { - Self { - r: ((value >> 16) & BITMASK) as _, - g: ((value >> 8) & BITMASK) as _, - b: (value & BITMASK) as _, - } - } -} - -impl From for LcdColor { - fn from(other: Rgb) -> Self { - Self(pros_sys::lv_color_t { - red: other.r, - green: other.g, - blue: other.b, - alpha: 0xFF, - }) - } -} - -impl From for Rgb { - fn from(other: LcdColor) -> Self { - Self { - r: other.red, - g: other.green, - b: other.blue, - } - } -} - #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The zero point of the vision sensor. +/// Vision object coordinates are relative to this point. pub enum VisionZeroPoint { + /// The zero point will be the top left corner of the vision sensor. TopLeft, + /// The zero point will be the top right corner of the vision sensor. Center, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The white balance of the vision sensor. pub enum WhiteBalance { + /// Provide a specific color to balance the white balance. Rgb(Rgb), + /// Automatically balance the white balance. Auto, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// The mode of the vision sensor led. pub enum LedMode { + /// Turn on the led with a certain color. On(Rgb), + /// Turn off the led. Off, } #[derive(Debug, Snafu)] +/// Errors that can occur when using a vision sensor. pub enum VisionError { - #[snafu(display( - "The index specified was higher than the total number of objects seen by the camera." - ))] + /// The camera could not be read. ReadingFailed, - #[snafu(display("The camera could not be read."))] + /// The index specified was higher than the total number of objects seen by the camera. IndexTooHigh, - #[snafu(display("Port already taken."))] + /// Port already taken. PortTaken, #[snafu(display("{source}"), context(false))] - Port { source: PortError }, + /// Generic port related error. + Port { + /// The source of the error. + source: PortError, + }, } map_errno! { diff --git a/packages/pros/src/usd.rs b/packages/pros-devices/src/usd.rs similarity index 54% rename from packages/pros/src/usd.rs rename to packages/pros-devices/src/usd.rs index 1167e45f..d8abd27f 100644 --- a/packages/pros/src/usd.rs +++ b/packages/pros-devices/src/usd.rs @@ -1,3 +1,7 @@ +//! USD api. +//! +//! The USD API provides functions for interacting with the SD card slot on the V5 Brain. + /// Checks if an SD card is installed. pub fn usd_installed() -> bool { unsafe { pros_sys::misc::usd_is_installed() == 1 } diff --git a/packages/pros-math/Cargo.toml b/packages/pros-math/Cargo.toml new file mode 100644 index 00000000..279a41d8 --- /dev/null +++ b/packages/pros-math/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pros-math" +version = "0.1.0" +edition = "2021" + +[dependencies] +num = { version = "0.4.1", default-features = false } +pros-core = { version = "0.1.0", path = "../pros-core" } + +[lints] +workspace = true diff --git a/packages/pros-math/src/feedforward.rs b/packages/pros-math/src/feedforward.rs new file mode 100644 index 00000000..c8e8b3c9 --- /dev/null +++ b/packages/pros-math/src/feedforward.rs @@ -0,0 +1,65 @@ +//! Simple feedforward controller for motors. +//! Computes the voltage to maintain an idealized DC motor in a certain state. +//! Uses this feedforward model: V = Kₛ sign(ω) + Kᵥ ω + Kₐ α + +/// Feedforward controller for motor control. +/// +/// This controller is used to apply feedforward control to achieve desired motor behavior +/// based on velocity and acceleration. +#[derive(Debug, Clone)] +pub struct MotorFeedforwardController { + /// Feedforward constant for static friction compensation. + pub ks: f32, + /// Feedforward constant for velocity compensation. + pub kv: f32, + /// Feedforward constant for acceleration compensation. + pub ka: f32, + /// Feedforward constant for the target acceleration. + pub target_acceleration: f32, + /// Target. + pub target: f32, +} + +impl MotorFeedforwardController { + /// Creates a new [`FeedforwardMotorController`] with the given constants and target. + /// + /// # Arguments + /// + /// * `ks` - Feedforward constant for static friction compensation. + /// * `kv` - Feedforward constant for velocity compensation. + /// * `ka` - Feedforward constant for acceleration compensation. + /// * `target_acceleration` - Feedforward constant for the target acceleration. + /// + /// # Returns + /// + /// A new [`FeedforwardMotorController`]. + pub fn new(ks: f32, kv: f32, ka: f32, target_acceleration: f32) -> Self { + Self { + ks, + kv, + ka, + target_acceleration, + target: 0.0, + } + } + + /// Calculates the control output. + /// + /// # Arguments + /// + /// * `target_acceleration` - The target_acceleration of the system. + /// * `target` - Target. + /// + /// # Returns + /// + /// The control output to apply to the motor. + pub fn calculate(&self, target: f32, target_acceleration: f32) -> f32 { + // Calculate the feedforward component based on velocity and acceleration + let v = self.ks * num::signum(target) + self.kv * target + self.ka * target_acceleration; + + // The output is the feedforward controller (V) + let output = v; + + output + } +} diff --git a/packages/pros-math/src/lib.rs b/packages/pros-math/src/lib.rs new file mode 100644 index 00000000..49c5474c --- /dev/null +++ b/packages/pros-math/src/lib.rs @@ -0,0 +1,6 @@ +//! Common mathematical formulas and models implemented for [`pros-rs`](https://crates.io/crates/pros). + +#![no_std] + +pub mod feedforward; +pub mod pid; diff --git a/packages/pros/src/pid.rs b/packages/pros-math/src/pid.rs similarity index 75% rename from packages/pros/src/pid.rs rename to packages/pros-math/src/pid.rs index 95f582af..74a8e40f 100644 --- a/packages/pros/src/pid.rs +++ b/packages/pros-math/src/pid.rs @@ -3,6 +3,8 @@ //! PID controllers are first created with [`PidController::new`] //! and then can be utilized by calling [`PidController::update`] repeatedly. +use core::time::Duration; + /// A proportional–integral–derivative controller. /// /// This controller is used to smoothly move motors to a certain point, @@ -20,37 +22,38 @@ pub struct PidController { /// based on the rate of change of the error (predicting future values). pub kd: f32, - last_time: i32, + last_time: pros_core::time::Instant, last_position: f32, i: f32, } impl PidController { + /// Create a new PID controller with the given constants. pub fn new(kp: f32, ki: f32, kd: f32) -> Self { Self { kp, ki, kd, - last_time: 0, + last_time: pros_core::time::Instant::now(), last_position: 0.0, i: 0.0, } } + /// Update the PID controller with the current setpoint and position. pub fn update(&mut self, setpoint: f32, position: f32) -> f32 { - let time = unsafe { pros_sys::clock() }; - let mut delta_time = (time - self.last_time) as f32 / pros_sys::CLOCKS_PER_SEC as f32; - if delta_time == 0.0 { - delta_time += 0.001; + let mut delta_time = self.last_time.elapsed(); + if delta_time.is_zero() { + delta_time += Duration::from_micros(1); } let error = setpoint - position; - self.i += error * delta_time; + self.i += error * delta_time.as_secs_f32(); let p = self.kp * error; let i = self.ki * self.i; - let mut d = (position - self.last_position) / delta_time; + let mut d = (position - self.last_position) / delta_time.as_secs_f32(); if d.is_nan() { d = 0.0 } @@ -58,7 +61,7 @@ impl PidController { let output = p + i + d; self.last_position = position; - self.last_time = time; + self.last_time = pros_core::time::Instant::now(); output } diff --git a/packages/pros-panic/Cargo.toml b/packages/pros-panic/Cargo.toml new file mode 100644 index 00000000..0314c46c --- /dev/null +++ b/packages/pros-panic/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pros-panic" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pros-core = { version = "0.1.0", path = "../pros-core" } +pros-devices = { version = "0.1.0", path = "../pros-devices", optional = true } +pros-sys = { version = "0.7.0", path = "../pros-sys" } + +[features] +default = ["display_panics"] + +display_panics = ["dep:pros-devices"] + +[lints] +workspace = true diff --git a/packages/pros-panic/src/lib.rs b/packages/pros-panic/src/lib.rs new file mode 100644 index 00000000..220725db --- /dev/null +++ b/packages/pros-panic/src/lib.rs @@ -0,0 +1,109 @@ +//! Panic handler implementation for [`pros-rs`](https://crates.io/crates/pros-rs). +//! Supports printing a backtrace when running in the simulator. +//! If the `display_panics` feature is enabled, it will also display the panic message on the V5 Brain display. + +#![no_std] + +extern crate alloc; + +use alloc::{format, string::String}; + +use pros_core::eprintln; +#[cfg(feature = "display_panics")] +use pros_devices::Screen; + +#[cfg(target_arch = "wasm32")] +extern "C" { + /// Prints a backtrace to the debug console + fn sim_log_backtrace(); +} + +/// Draw an error box to the screen. +/// +/// This function is internally used by the pros-rs panic handler for displaying +/// panic messages graphically before exiting. +#[cfg(feature = "display_panics")] +fn draw_error( + screen: &mut pros_devices::screen::Screen, + msg: &str, +) -> Result<(), pros_devices::screen::ScreenError> { + const ERROR_BOX_MARGIN: i16 = 16; + const ERROR_BOX_PADDING: i16 = 16; + const LINE_MAX_WIDTH: usize = 52; + + let error_box_rect = pros_devices::screen::Rect::new( + ERROR_BOX_MARGIN, + ERROR_BOX_MARGIN, + Screen::HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN, + Screen::VERTICAL_RESOLUTION - ERROR_BOX_MARGIN, + ); + + screen.fill(&error_box_rect, pros_devices::color::Rgb::RED)?; + screen.stroke(&error_box_rect, pros_devices::color::Rgb::WHITE)?; + + let mut buffer = String::new(); + let mut line: i16 = 0; + + for (i, character) in msg.char_indices() { + if !character.is_ascii_control() { + buffer.push(character); + } + + if character == '\n' || ((buffer.len() % LINE_MAX_WIDTH == 0) && (i > 0)) { + screen.fill( + &pros_devices::screen::Text::new( + buffer.as_str(), + pros_devices::screen::TextPosition::Point( + ERROR_BOX_MARGIN + ERROR_BOX_PADDING, + ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Screen::LINE_HEIGHT), + ), + pros_devices::screen::TextFormat::Small, + ), + pros_devices::color::Rgb::WHITE, + )?; + + line += 1; + buffer.clear(); + } + } + + screen.fill( + &pros_devices::screen::Text::new( + buffer.as_str(), + pros_devices::screen::TextPosition::Point( + ERROR_BOX_MARGIN + ERROR_BOX_PADDING, + ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Screen::LINE_HEIGHT), + ), + pros_devices::screen::TextFormat::Small, + ), + pros_devices::color::Rgb::WHITE, + )?; + + Ok(()) +} + +#[panic_handler] +/// The panic handler for pros-rs. +pub fn panic(info: &core::panic::PanicInfo<'_>) -> ! { + let current_task = pros_core::task::current(); + + let task_name = current_task.name().unwrap_or_else(|_| "".into()); + + // task 'User Initialization (PROS)' panicked at src/lib.rs:22:1: + // panic message here + let msg = format!("task '{task_name}' {info}"); + + eprintln!("{msg}"); + + unsafe { + #[cfg(feature = "display_panics")] + draw_error(&mut Screen::new(), &msg).unwrap_or_else(|err| { + eprintln!("Failed to draw error message to screen: {err}"); + }); + + #[cfg(target_arch = "wasm32")] + sim_log_backtrace(); + + pros_sys::exit(1); + } +} diff --git a/packages/pros-sync/Cargo.toml b/packages/pros-sync/Cargo.toml new file mode 100644 index 00000000..1df90f53 --- /dev/null +++ b/packages/pros-sync/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pros-sync" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pros-core = { version = "0.1.0", path = "../pros-core" } + +[lints] +workspace = true diff --git a/packages/pros-sync/src/lib.rs b/packages/pros-sync/src/lib.rs new file mode 100644 index 00000000..6f7df18a --- /dev/null +++ b/packages/pros-sync/src/lib.rs @@ -0,0 +1,141 @@ +//! Synchronous robot code trait for [pros-rs](https://crates.io/crates/pros). + +#![no_std] + +use pros_core::error::Result; + +/// A trait for robot code that runs without the async executor spun up. +/// This trait isn't recommended. See `AsyncRobot` in [pros-async](https://crates.io/crates/pros-async) for the preferred trait to run robot code. +pub trait SyncRobot { + /// Runs during the operator control period. + /// This function may be called more than once. + /// For that reason, do not use `Peripherals::take` in this function. + fn opcontrol(&mut self) -> Result { + Ok(()) + } + /// Runs during the autonomous period. + fn auto(&mut self) -> Result { + Ok(()) + } + /// Runs continuously during the disabled period. + fn disabled(&mut self) -> Result { + Ok(()) + } + /// Runs once when the competition system is initialized. + fn comp_init(&mut self) -> Result { + Ok(()) + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __gen_sync_exports { + ($rbt:ty) => { + pub static mut ROBOT: Option<$rbt> = None; + + #[doc(hidden)] + #[no_mangle] + extern "C" fn opcontrol() { + <$rbt as $crate::SyncRobot>::opcontrol(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before opcontrol") + }) + .unwrap(); + } + + #[doc(hidden)] + #[no_mangle] + extern "C" fn autonomous() { + <$rbt as $crate::SyncRobot>::auto(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before opcontrol") + }) + .unwrap(); + } + + #[doc(hidden)] + #[no_mangle] + extern "C" fn disabled() { + <$rbt as $crate::SyncRobot>::disabled(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before opcontrol") + }) + .unwrap(); + } + + #[doc(hidden)] + #[no_mangle] + extern "C" fn competition_initialize() { + <$rbt as $crate::SyncRobot>::comp_init(unsafe { + ROBOT + .as_mut() + .expect("Expected initialize to run before opcontrol") + }) + .unwrap(); + } + }; +} + +/// Allows your sync robot code to be executed by the pros kernel. +/// If your robot struct implements Default then you can just supply this macro with its type. +/// If not, you can supply an expression that returns your robot type to initialize your robot struct. +/// The code that runs to create your robot struct will run in the initialize function in PROS. +/// +/// Example of using the macro with a struct that implements Default: +/// ```rust +/// use pros::prelude::*; +/// #[derive(Default)] +/// struct ExampleRobot; +/// impl SyncRobot for ExampleRobot { +/// asnyc fn opcontrol(&mut self) -> pros::Result { +/// println!("Hello, world!"); +/// Ok(()) +/// } +/// } +/// sync_robot!(ExampleRobot); +/// ``` +/// +/// Example of using the macro with a struct that does not implement Default: +/// ```rust +/// use pros::prelude::*; +/// struct ExampleRobot { +/// x: i32, +/// } +/// impl SyncRobot for ExampleRobot { +/// async fn opcontrol(&mut self) -> pros::Result { +/// println!("Hello, world! {}", self.x); +/// Ok(()) +/// } +/// } +/// impl ExampleRobot { +/// pub fn new() -> Self { +/// Self { x: 5 } +/// } +/// } +/// sync_robot!(ExampleRobot, ExampleRobot::new()); +#[macro_export] +macro_rules! sync_robot { + ($rbt:ty) => { + $crate::__gen_sync_exports!($rbt); + + #[no_mangle] + extern "C" fn initialize() { + unsafe { + ROBOT = Some(Default::default()); + } + } + }; + ($rbt:ty, $init:expr) => { + $crate::__gen_sync_exports!($rbt); + + #[no_mangle] + extern "C" fn initialize() { + unsafe { + ROBOT = Some($init); + } + } + }; +} diff --git a/packages/pros-sys/Cargo.toml b/packages/pros-sys/Cargo.toml index f8266912..145824f7 100644 --- a/packages/pros-sys/Cargo.toml +++ b/packages/pros-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pros-sys" -version = "0.5.0" +version = "0.7.0" edition = "2021" description = "EFI for the PROS rust bindings" keywords = ["PROS", "Robotics", "bindings"] diff --git a/packages/pros-sys/link/libm.a b/packages/pros-sys/link/libm.a deleted file mode 100644 index 3d4066d5..00000000 Binary files a/packages/pros-sys/link/libm.a and /dev/null differ diff --git a/packages/pros-sys/link/libpros.a b/packages/pros-sys/link/libpros.a index 05399901..6e320035 100644 Binary files a/packages/pros-sys/link/libpros.a and b/packages/pros-sys/link/libpros.a differ diff --git a/packages/pros-sys/src/apix.rs b/packages/pros-sys/src/apix.rs index f155daf4..ff5daa95 100644 --- a/packages/pros-sys/src/apix.rs +++ b/packages/pros-sys/src/apix.rs @@ -32,7 +32,7 @@ identifier. When used with serctl, the extra argument must be the little endian representation of the stream identifier (e.g. "sout" -> 0x74756f73) -Visit https://pros.cs.purdue.edu/v5/tutorials/topical/filesystem.html#serial +Visit to learn more. */ pub const SERCTL_ACTIVATE: u32 = 10; @@ -43,7 +43,7 @@ identifier. When used with serctl, the extra argument must be the little endian representation of the stream identifier (e.g. "sout" -> 0x74756f73) -Visit https://pros.cs.purdue.edu/v5/tutorials/topical/filesystem.html#serial +Visit to learn more. */ pub const SERCTL_DEACTIVATE: u32 = 11; @@ -53,7 +53,7 @@ Action macro to pass into fdctl that enables blocking writes for the file The extra argument is not used with this action, provide any value (e.g. NULL) instead -Visit https://pros.cs.purdue.edu/v5/tutorials/topical/filesystem.html#serial +Visit to learn more. */ pub const SERCTL_BLKWRITE: u32 = 12; @@ -63,7 +63,7 @@ Action macro to pass into fdctl that makes writes non-blocking for the file The extra argument is not used with this action, provide any value (e.g. NULL) instead -Visit https://pros.cs.purdue.edu/v5/tutorials/topical/filesystem.html#serial +Visit to learn more. */ pub const SERCTL_NOBLKWRITE: u32 = 13; @@ -74,7 +74,7 @@ capabilities The extra argument is not used with this action, provide any value (e.g. NULL) instead -Visit https://pros.cs.purdue.edu/v5/tutorials/topical/filesystem.html#serial +Visit to learn more. */ pub const SERCTL_ENABLE_COBS: u32 = 14; @@ -85,7 +85,7 @@ capabilities The extra argument is not used with this action, provide any value (e.g. NULL) instead -Visit https://pros.cs.purdue.edu/v5/tutorials/topical/filesystem.html#serial +Visit to learn more. */ pub const SERCTL_DISABLE_COBS: u32 = 15; @@ -117,7 +117,7 @@ extern "C" { Unblocks a task in the Blocked state (e.g. waiting for a delay, on a semaphore, etc.). - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#abort_delay for + See for details. */ pub fn task_abort_delay(task: task_t) -> bool; @@ -148,7 +148,7 @@ extern "C" { Creates a recursive mutex which can be locked recursively by the owner. See - https://pros.cs.purdue.edu/v5/extended/multitasking.html#recursive_mutexes + for details. \return A newly created recursive mutex. @@ -158,7 +158,7 @@ extern "C" { Takes a recursive mutex. See - https://pros.cs.purdue.edu/v5/extended/multitasking.html#recursive_mutexes + for details. \param mutex @@ -173,7 +173,7 @@ extern "C" { Gives a recursive mutex. See - https://pros.cs.purdue.edu/v5/extended/multitasking.html#recursive_mutexes + for details. \param mutex @@ -185,7 +185,7 @@ extern "C" { /** Returns a handle to the current owner of a mutex. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#extra for + See for details. \param mutex @@ -198,7 +198,7 @@ extern "C" { /** Creates a counting sempahore. - See https://pros.cs.purdue.edu/v5/tutorials/multitasking.html#semaphores for + See for details. \param max_count @@ -213,7 +213,7 @@ extern "C" { /** Deletes a semaphore (or binary semaphore) - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#semaphores for + See for details. \param sem @@ -224,7 +224,7 @@ extern "C" { Creates a binary semaphore. See - https://pros.cs.purdue.edu/v5/extended/multitasking#.htmlbinary_semaphores + for details. \return A newly created semaphore. @@ -234,7 +234,7 @@ extern "C" { Waits for the semaphore's value to be greater than 0. If the value is already greater than 0, this function immediately returns. - See https://pros.cs.purdue.edu/v5/tutorials/multitasking.html#semaphores for + See for details. \param sem @@ -252,7 +252,7 @@ extern "C" { /** Increments a semaphore's value. - See https://pros.cs.purdue.edu/v5/tutorials/multitasking.html#semaphores for + See for details. \param sem @@ -266,7 +266,7 @@ extern "C" { /** Returns the current value of the semaphore. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#extra for + See for details. \param sem @@ -279,7 +279,7 @@ extern "C" { /** Creates a queue. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param length @@ -295,7 +295,7 @@ extern "C" { Posts an item to the front of a queue. The item is queued by copy, not by reference. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param queue @@ -314,7 +314,7 @@ extern "C" { Posts an item to the end of a queue. The item is queued by copy, not by reference. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param queue @@ -332,7 +332,7 @@ extern "C" { /** Receive an item from a queue without removing the item from the queue. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param queue @@ -349,7 +349,7 @@ extern "C" { /** Receive an item from the queue. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param queue @@ -368,7 +368,7 @@ extern "C" { /** Return the number of messages stored in a queue. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param queue @@ -380,7 +380,7 @@ extern "C" { /** Return the number of spaces left in a queue. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param queue @@ -392,7 +392,7 @@ extern "C" { /** Delete a queue. - See https://pros.cs.purdue.edu/v5/extended/multitasking.html#queues for + See for details. \param queue diff --git a/packages/pros-sys/src/lib.rs b/packages/pros-sys/src/lib.rs index d80658fe..5ed47133 100644 --- a/packages/pros-sys/src/lib.rs +++ b/packages/pros-sys/src/lib.rs @@ -14,15 +14,15 @@ pub mod ext_adi; pub mod gps; pub mod imu; pub mod link; -pub mod llemu; pub mod misc; pub mod motor; pub mod optical; pub mod rotation; pub mod rtos; +pub mod screen; pub mod vision; -use core::ffi::c_char; +use core::ffi::{c_char, c_int, c_void}; pub use adi::*; pub use colors::*; @@ -32,12 +32,12 @@ pub use ext_adi::*; pub use gps::*; pub use imu::*; pub use link::*; -pub use llemu::*; pub use misc::*; pub use motor::*; pub use optical::*; pub use rotation::*; pub use rtos::*; +pub use screen::*; #[cfg(feaute = "apix")] pub use serial::*; pub use vision::*; @@ -48,11 +48,43 @@ pub const CLOCKS_PER_SEC: u32 = 1000; extern "C" { #[cfg(not(target_arch = "wasm32"))] - pub fn memalign(alignment: usize, size: usize) -> *mut core::ffi::c_void; + pub fn memalign(alignment: usize, size: usize) -> *mut c_void; #[cfg(not(target_arch = "wasm32"))] - pub fn free(ptr: *mut core::ffi::c_void); + pub fn free(ptr: *mut c_void); pub fn __errno() -> *mut i32; pub fn clock() -> i32; pub fn puts(s: *const c_char) -> i32; pub fn exit(code: i32) -> !; + pub fn write(fd: c_int, buf: *const c_void, count: usize) -> isize; + + fn initialize(); + fn opcontrol(); + fn autonomous(); + fn disabled(); + fn competition_initialize(); +} + +#[no_mangle] +unsafe extern "C" fn cpp_opcontrol() { + opcontrol(); +} +#[no_mangle] +unsafe extern "C" fn cpp_autonomous() { + autonomous(); +} +#[no_mangle] +unsafe extern "C" fn cpp_disabled() { + disabled(); +} +#[no_mangle] +unsafe extern "C" fn cpp_competition_initialize() { + competition_initialize(); +} +#[no_mangle] +unsafe extern "C" fn cpp_initialize() { + initialize(); +} +#[no_mangle] +unsafe extern "C" fn task_fn_wrapper(function: task_fn_t, args: *mut c_void) { + function.unwrap()(args); } diff --git a/packages/pros-sys/src/llemu.rs b/packages/pros-sys/src/llemu.rs deleted file mode 100644 index c4a4c251..00000000 --- a/packages/pros-sys/src/llemu.rs +++ /dev/null @@ -1,208 +0,0 @@ -// #[cfg(feature = "xapi")] -// compile_error!("LVGL bindings (xapi) are a todo for now"); - -use cfg_if::cfg_if; - -pub const LCD_BTN_LEFT: core::ffi::c_int = 4; -pub const LCD_BTN_CENTER: core::ffi::c_int = 2; -pub const LCD_BTN_RIGHT: core::ffi::c_int = 1; - -pub type lcd_button_cb_fn_t = Option; - -cfg_if! { - if #[cfg(feature = "xapi")] { - // #[repr(C)] - // pub struct lcd_s_t { - // //TODO - // } - - #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub struct lv_color_t { - pub blue: u8, - pub green: u8, - pub red: u8, - pub alpha: u8, - } - - impl From for lv_color_t { - fn from(color: u32) -> Self { - Self { - blue: (color & 0xFF) as u8, - green: ((color >> 8) & 0xFF) as u8, - red: ((color >> 16) & 0xFF) as u8, - alpha: ((color >> 24) & 0xFF) as u8, - } - } - } - - impl From for u32 { - fn from(color: lv_color_t) -> Self { - (color.blue as u32) - | ((color.green as u32) << 8) - | ((color.red as u32) << 16) - | ((color.alpha as u32) << 24) - } - } - } -} - -extern "C" { - /** Checks whether the emulated three-button LCD has already been initialized. - - \return True if the LCD has been initialized or false if not.*/ - pub fn lcd_is_initialized() -> bool; - /** Creates an emulation of the three-button, UART-based VEX LCD on the display. - - \return True if the LCD was successfully initialized, or false if it has - already been initialized.*/ - pub fn lcd_initialize() -> bool; - /** Turns off the Legacy LCD Emulator. - - Calling this function will clear the entire display, and you will not be able - to call any further LLEMU functions until another call to lcd_initialize. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_shutdown() -> bool; - /** Displays a formatted string on the emulated three-button LCD screen. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - EINVAL - The line number specified is not in the range [0-7] - - \param line - The line on which to display the text [0-7] - \param fmt - Format string - \param ... - Optional list of arguments for the format string - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_print(line: i16, fmt: *const core::ffi::c_char, ...) -> bool; - /** Displays a string on the emulated three-button LCD screen. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - EINVAL - The line number specified is not in the range [0-7] - - \param line - The line on which to display the text [0-7] - \param text - The text to display - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_set_text(line: i16, text: *const core::ffi::c_char) -> bool; - /** Clears the contents of the emulated three-button LCD screen. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - EINVAL - The line number specified is not in the range [0-7] - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_clear() -> bool; - /** Clears the contents of a line of the emulated three-button LCD screen. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - EINVAL - The line number specified is not in the range [0-7] - - \param line - The line to clear - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_clear_line(line: i16) -> bool; - /** Registers a callback function for the leftmost button. - - When the leftmost button on the emulated three-button LCD is pressed, the - user-provided callback function will be invoked. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - - \param cb - A callback function of type lcd_btn_cb_fn_t (void (*cb)(void)) - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_register_btn0_cb(cb: lcd_button_cb_fn_t) -> bool; - /** Registers a callback function for the center button. - - When the center button on the emulated three-button LCD is pressed, the - user-provided callback function will be invoked. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - - \param cb - A callback function of type lcd_btn_cb_fn_t (void (*cb)(void)) - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_register_btn1_cb(cb: lcd_button_cb_fn_t) -> bool; - /** Registers a callback function for the rightmost button. - - When the rightmost button on the emulated three-button LCD is pressed, the - user-provided callback function will be invoked. - - This function uses the following values of errno when an error state is - reached: - ENXIO - The LCD has not been initialized. Call lcd_initialize() first. - - \param cb - A callback function of type lcd_btn_cb_fn_t (void (*cb)(void)) - - \return True if the operation was successful, or false otherwise, setting - errno values as specified above.*/ - pub fn lcd_register_btn2_cb(cb: lcd_button_cb_fn_t) -> bool; - /** Gets the button status from the emulated three-button LCD. - - The value returned is a 3-bit integer where 1 0 0 indicates the left button - is pressed, 0 1 0 indicates the center button is pressed, and 0 0 1 - indicates the right button is pressed. 0 is returned if no buttons are - currently being pressed. - - Note that this function is provided for legacy API compatibility purposes, - with the caveat that the V5 touch screen does not actually support pressing - multiple points on the screen at the same time. - - \return The buttons pressed as a bit mask*/ - pub fn lcd_read_buttons() -> u8; - - cfg_if! { - if #[cfg(feature = "xapi")] { - /** Changes the color of the LCD background to a provided color expressed in - type lv_color_t. - - \param color - A color of type lv_color_t - - \return void - */ - pub fn lcd_set_background_color(color: lv_color_t); - /** Changes the text color of the LCD to a provided color expressed in - type lv_color_t. - - \param color - A color of type lv_color_t - - \return void - */ - pub fn lcd_set_text_color(color: lv_color_t); - } - } -} diff --git a/packages/pros-sys/src/motor.rs b/packages/pros-sys/src/motor.rs index 0b2b0f0e..b893904b 100644 --- a/packages/pros-sys/src/motor.rs +++ b/packages/pros-sys/src/motor.rs @@ -449,7 +449,7 @@ extern "C" { \param port The V5 port number from 1-21 - \param[in] timestamp + \param\[in] timestamp A pointer to a time in milliseconds for which the encoder count will be returned. If NULL, the timestamp at which the encoder count was read will not be supplied diff --git a/packages/pros-sys/src/rtos.rs b/packages/pros-sys/src/rtos.rs index 402da0e5..480bd1c7 100644 --- a/packages/pros-sys/src/rtos.rs +++ b/packages/pros-sys/src/rtos.rs @@ -173,7 +173,7 @@ extern "C" { pub fn task_get_current() -> task_t; /** Sends a simple notification to task and increments the notification counter. - See https://pros.cs.purdue.edu/v5/tutorials/topical/notifications.html for + See for details. \param task @@ -184,7 +184,7 @@ extern "C" { /** Utilizes task notifications to wait until specified task is complete and deleted, then continues to execute the program. Analogous to std::thread::join in C++. - See https://pros.cs.purdue.edu/v5/tutorials/topical/notifications.html for + See for details. \param task @@ -196,7 +196,7 @@ extern "C" { retrieve the value of the notification in the target task before modifying the notification value. - See https://pros.cs.purdue.edu/v5/tutorials/topical/notifications.html for + See for details. \param task @@ -222,7 +222,7 @@ extern "C" { ) -> u32; /** Waits for a notification to be nonzero. - See https://pros.cs.purdue.edu/v5/tutorials/topical/notifications.html for + See for details. \param clear_on_exit @@ -237,7 +237,7 @@ extern "C" { pub fn task_notify_take(clear_on_exit: bool, timeout: u32) -> u32; /** Clears the notification for a task. - See https://pros.cs.purdue.edu/v5/tutorials/topical/notifications.html for + See for details. \param task @@ -247,7 +247,7 @@ extern "C" { pub fn task_notify_clear(task: task_t) -> bool; /** Creates a mutex. - See https://pros.cs.purdue.edu/v5/tutorials/topical/multitasking.html#mutexes + See for details. \return A handle to a newly created mutex. If an error occurred, NULL will be @@ -256,7 +256,7 @@ extern "C" { /** Takes and locks a mutex, waiting for up to a certain number of milliseconds before timing out. - See https://pros.cs.purdue.edu/v5/tutorials/topical/multitasking.html#mutexes + See for details. \param mutex diff --git a/packages/pros-sys/src/screen.rs b/packages/pros-sys/src/screen.rs new file mode 100644 index 00000000..2d74b9ae --- /dev/null +++ b/packages/pros-sys/src/screen.rs @@ -0,0 +1,410 @@ +//! Brain screen display and touch functions. +//! +//! Contains user calls to the v5 screen for touching and displaying graphics. + +use core::ffi::{c_char, c_int}; + +/// Struct representing screen touch status, screen last x, screen last y, press count, release count. +#[repr(C)] +pub struct screen_touch_status_s_t { + /// Represents if the screen is being held, released, or pressed. + pub touch_status: last_touch_e_t, + + /// Represents the x value of the location of the touch. + pub x: i16, + + /// Represents the y value of the location of the touch. + pub y: i16, + + /// Represents how many times the screen has be pressed. + pub press_count: i32, + + /// Represents how many times the user released after a touch on the screen. + pub release_count: i32, +} + +pub const E_TEXT_SMALL: c_int = 0; +pub const E_TEXT_MEDIUM: c_int = 1; +pub const E_TEXT_LARGE: c_int = 2; +pub const E_TEXT_MEDIUM_CENTER: c_int = 3; +pub const E_TEXT_LARGE_CENTER: c_int = 4; +pub type text_format_e_t = c_int; + +pub const E_TOUCH_RELEASED: c_int = 0; +pub const E_TOUCH_PRESSED: c_int = 1; +pub const E_TOUCH_HELD: c_int = 2; +pub const E_TOUCH_ERROR: c_int = 3; +pub type last_touch_e_t = c_int; + +pub type touch_event_cb_fn_t = unsafe extern "C" fn(); + +extern "C" { + /// Set the pen color for subsequent graphics operations + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param color The pen color to set (it is recommended to use values + /// from the enum defined in colors.h) + /// + /// \return Returns 1 if the mutex was successfully returned, or PROS_ERR if + /// there was an error either taking or returning the screen mutex. + /// + /// \b Example + /// \code + /// void initialize() { + /// screen_set_pen(COLOR_RED); + /// } + /// + /// void opcontrol() { + /// int iter = 0; + /// while(1){ + /// // This should print in red. + /// screen_print(TEXT_MEDIUM, 1, "%d", iter++); + /// } + /// } + /// \endcode + pub fn screen_set_pen(color: u32) -> u32; + + /// Set the eraser color for erasing and the current background. + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param color The background color to set (it is recommended to use values + /// from the enum defined in colors.h) + /// + /// \return Returns 1 if the mutex was successfully returned, or + /// PROS_ERR if there was an error either taking or returning the screen mutex. + /// + /// \b Example + /// \code + /// void initialize() { + /// screen_set_eraser(COLOR_RED); + /// } + /// + /// void opcontrol() { + /// while(1){ + /// // This should turn the screen red. + /// screen_erase(); + /// } + /// } + /// \endcode + pub fn screen_set_eraser(color: u32) -> u32; + + /// Get the current pen color. + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \return The current pen color in the form of a value from the enum defined + /// in colors.h, or PROS_ERR if there was an error taking or returning + /// the screen mutex. + pub fn screen_get_pen() -> u32; + + /// Get the current eraser color. + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \return The current eraser color in the form of a value from the enum + /// defined in colors.h, or PROS_ERR if there was an error taking or + /// returning the screen mutex. + pub fn screen_get_eraser() -> u32; + + /// Clear display with eraser color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_erase() -> u32; + + /// Scroll lines on the display upwards. + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// + /// \param start_line The line from which scrolling will start + /// \param lines The number of lines to scroll up + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_scroll(start_line: i16, lines: i16) -> u32; + + /// Scroll lines within a region on the display + /// + /// This function behaves in the same way as `screen_scroll`, except that you + /// specify a rectangular region within which to scroll lines instead of a start + /// line. + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x0, y0 The (x,y) coordinates of the first corner of the + /// rectangular region + /// \param x1, y1 The (x,y) coordinates of the second corner of the + /// rectangular region + /// \param lines The number of lines to scroll upwards + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_scroll_area(x0: i16, y0: i16, x1: i16, y1: i16, lines: i16) -> u32; + + /// Copy a screen region (designated by a rectangle) from an off-screen buffer + /// to the screen + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x0, y0 The (x,y) coordinates of the first corner of the + /// rectangular region of the screen + /// \param x1, y1 The (x,y) coordinates of the second corner of the + /// rectangular region of the screen + /// \param buf Off-screen buffer containing screen data + /// \param stride Off-screen buffer width in pixels, such that image size + /// is stride-padding + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_copy_area( + x0: i16, + y0: i16, + x1: i16, + y1: i16, + buf: *const u32, + stride: i32, + ) -> u32; + + /// Draw a single pixel on the screen using the current pen color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x, y The (x,y) coordinates of the pixel + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_draw_pixel(x: i16, y: i16) -> u32; + + /// Erase a pixel from the screen (Sets the location) + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x, y The (x,y) coordinates of the erased + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_erase_pixel(x: i16, y: i16) -> u32; + + /// Draw a line on the screen using the current pen color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x0, y0 The (x, y) coordinates of the first point of the line + /// \param x1, y1 The (x, y) coordinates of the second point of the line + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_draw_line(x0: i16, y0: i16, x1: i16, y1: i16) -> u32; + + /// Erase a line on the screen using the current eraser color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x0, y0 The (x, y) coordinates of the first point of the line + /// \param x1, y1 The (x, y) coordinates of the second point of the line + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_erase_line(x0: i16, y0: i16, x1: i16, y1: i16) -> u32; + + /// Draw a rectangle on the screen using the current pen color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x0, y0 The (x,y) coordinates of the first point of the rectangle + /// \param x1, y1 The (x,y) coordinates of the second point of the rectangle + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_draw_rect(x0: i16, y0: i16, x1: i16, y1: i16) -> u32; + + /// Erase a rectangle on the screen using the current eraser color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x0, y0 The (x,y) coordinates of the first point of the rectangle + /// \param x1, y1 The (x,y) coordinates of the second point of the rectangle + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_erase_rect(x0: i16, y0: i16, x1: i16, y1: i16) -> u32; + + /// Fill a rectangular region of the screen using the current pen + /// color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x0, y0 The (x,y) coordinates of the first point of the rectangle + /// \param x1, y1 The (x,y) coordinates of the second point of the rectangle + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_fill_rect(x0: i16, y0: i16, x1: i16, y1: i16) -> u32; + + /// Draw a circle on the screen using the current pen color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x, y The (x,y) coordinates of the center of the circle + /// \param r The radius of the circle + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_draw_circle(x: i16, y: i16, radius: i16) -> u32; + + /// Erase a circle on the screen using the current eraser color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x, y The (x,y) coordinates of the center of the circle + /// \param r The radius of the circle + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_erase_circle(x: i16, y: i16, radius: i16) -> u32; + + /// Fill a circular region of the screen using the current pen + /// color + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param x, y The (x,y) coordinates of the center of the circle + /// \param r The radius of the circle + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_fill_circle(x: i16, y: i16, radius: i16) -> u32; + + /// Print a formatted string to the screen on the specified line + /// + /// Will default to a medium sized font by default if invalid txt_fmt is given. + /// + /// \param txt_fmt Text format enum that determines if the text is medium, large, medium_center, or large_center. (DOES + /// NOT SUPPORT SMALL) \param line The line number on which to print \param text Format string \param ... Optional list + /// of arguments for the format string + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_print( + txt_fmt: text_format_e_t, + line: i16, + text: *const core::ffi::c_char, + ... + ) -> u32; + + /// Print a formatted string to the screen at the specified point + /// + /// Will default to a medium sized font by default if invalid txt_fmt is given. + /// + /// Text formats medium_center and large_center will default to medium and large respectively. + /// + /// \param txt_fmt Text format enum that determines if the text is small, medium, or large. + /// \param x The y coordinate of the top left corner of the string + /// \param y The x coordinate of the top left corner of the string + /// \param text Format string + /// \param ... Optional list of arguments for the format string + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// taking or returning the screen mutex. + pub fn screen_print_at( + txt_fmt: text_format_e_t, + x: i16, + y: i16, + text: *const core::ffi::c_char, + ... + ) -> u32; + + /// Print a formatted string to the screen on the specified line + /// + /// Same as `display_printf` except that this uses a `va_list` instead of the + /// ellipsis operator so this can be used by other functions. + /// + /// Will default to a medium sized font by default if invalid txt_fmt is given. + /// Exposed mostly for writing libraries and custom functions. + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param txt_fmt Text format enum that determines if the text is medium, large, medium_center, or large_center. (DOES + /// NOT SUPPORT SMALL) \param line The line number on which to print \param text Format string \param args List of + /// arguments for the format string + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// while taking or returning the screen mutex. + pub fn screen_vprintf( + txt_fmt: text_format_e_t, + line: i16, + text: *const core::ffi::c_char, + ... + ) -> u32; + + /// Gets the touch status of the last touch of the screen. + /// + /// \return The last_touch_e_t enum specifier that indicates the last touch status of the screen (E_TOUCH_EVENT_RELEASE, + /// E_TOUCH_EVENT_PRESS, or E_TOUCH_EVENT_PRESS_AND_HOLD). This will be released by default if no action was taken. If an + /// error occured, the screen_touch_status_s_t will have its last_touch_e_t enum specifier set to E_TOUCH_ERR, and other + /// values set to -1. + pub fn screen_touch_status() -> screen_touch_status_s_t; + + /// Assigns a callback function to be called when a certain touch event happens. + /// + /// This function uses the following values of errno when an error state is + /// reached: + /// EACCESS - Another resource is currently trying to access the screen mutex. + /// + /// \param cb Function pointer to callback when event type happens + /// \param event_type Touch event that will trigger the callback. + /// + /// \return 1 if there were no errors, or PROS_ERR if an error occured + /// while taking or returning the screen mutex. + pub fn screen_touch_callback(cb: touch_event_cb_fn_t, event_type: last_touch_e_t) -> u32; + + /// Display a fatal error to the built-in LCD/touch screen. + /// + /// This function is intended to be used when the integrity of the RTOS cannot be + /// trusted. No thread-safety mechanisms are used and this function only relies + /// on the use of the libv5rts. + pub fn display_fatal_error(text: *const c_char); +} diff --git a/packages/pros-sys/src/vision.rs b/packages/pros-sys/src/vision.rs index 0c29c265..a3272d5d 100644 --- a/packages/pros-sys/src/vision.rs +++ b/packages/pros-sys/src/vision.rs @@ -213,7 +213,7 @@ extern "C" { ) -> vision_object_s_t; /** Gets the exposure parameter of the Vision Sensor. See - https://pros.cs.purdue.edu/v5/tutorials/topical/vision.html#exposure-setting + for more details. This function uses the following values of errno when an error state is @@ -224,7 +224,7 @@ extern "C" { \param port The V5 port number from 1-21 - \return The current exposure setting from [0,150], PROS_ERR if an error + \return The current exposure setting from \[0,150], PROS_ERR if an error occurred */ pub fn vision_get_exposure(port: u8) -> i32; @@ -283,7 +283,7 @@ extern "C" { (0 is the largest item, 1 is the second largest, etc.) \param object_count The number of objects to read - \param[out] object_arr + \param\[out] object_arr A pointer to copy the objects into \return The number of object signatures copied. This number will be less than @@ -317,7 +317,7 @@ extern "C" { (0 is the largest item, 1 is the second largest, etc.) \param signature The signature ID [1-7] for which objects will be returned. - \param[out] object_arr + \param\[out] object_arr A pointer to copy the objects into \return The number of object signatures copied. This number will be less than @@ -351,7 +351,7 @@ extern "C" { (0 is the largest item, 1 is the second largest, etc.) \param color_code The vision_color_code_t for which objects will be returned - \param[out] object_arr + \param\[out] object_arr A pointer to copy the objects into \return The number of object signatures copied. This number will be less than @@ -388,7 +388,7 @@ extern "C" { The V5 port number from 1-21 \param signature_id The signature id to store into - \param[in] signature_ptr + \param\[in] signature_ptr A pointer to the signature to save \return 1 if no errors occurred, PROS_ERR otherwise @@ -418,7 +418,7 @@ extern "C" { pub fn vision_set_auto_white_balance(port: u8, enable: u8) -> i32; /** Sets the exposure parameter of the Vision Sensor. See - https://pros.cs.purdue.edu/v5/tutorials/topical/vision.html#exposure-setting + for more details. This function uses the following values of errno when an error state is @@ -429,7 +429,7 @@ extern "C" { \param port The V5 port number from 1-21 \param percent - The new exposure setting from [0,150] + The new exposure setting from \[0,150] \return 1 if the operation was successful or PROS_ERR if the operation failed, setting errno. diff --git a/packages/pros/Cargo.toml b/packages/pros/Cargo.toml index 6249bc21..2df5fa64 100644 --- a/packages/pros/Cargo.toml +++ b/packages/pros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pros" -version = "0.7.0-pre.1" +version = "0.8.0" edition = "2021" description = "Rust bindings for PROS" keywords = ["PROS", "Robotics", "bindings"] @@ -18,20 +18,25 @@ rust-version = "1.75.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lazy_static = { version = "1.4.0", features = ["spin_no_std"] } -spin = "0.9.8" -pros-sys = { version = "0.5", path = "../pros-sys", features = ["xapi"] } -snafu = { version = "0.8.0", default-features = false, features = [ - "rust_1_61", - "unstable-core-error", -] } -no_std_io = { version = "0.6.0", features = ["alloc"] } -futures = { version = "0.3.28", default-features = false, features = ["alloc"] } -slab = { version = "0.4.9", default-features = false } -hashbrown = { version = "0.14.1", default-features = true } -async-task = { version = "4.5.0", default-features = false } -waker-fn = "1.1.1" -libc-print = "0.1.22" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -dlmalloc = { version = "0.2.4", features = ["global"] } +pros-sync = { version = "0.1.0", path = "../pros-sync", optional = true } +pros-async = { version = "0.1.0", path = "../pros-async", optional = true } +pros-devices = { version = "0.1.0", path = "../pros-devices", optional = true } +pros-panic = { version = "0.1.0", path = "../pros-panic", optional = true } +pros-core = { version = "0.1.0", path = "../pros-core", optional = true } +pros-math = { version = "0.1.0", path = "../pros-math", optional = true } +pros-sys = { version = "0.7.0", path = "../pros-sys" } + +[features] +default = ["async", "devices", "panic", "display_panics", "core", "math"] + +core = ["dep:pros-core"] + +async = ["dep:pros-async"] +sync = ["dep:pros-sync"] + +devices = ["dep:pros-devices"] + +math = ["dep:pros-math"] + +panic = ["dep:pros-panic"] +display_panics = ["pros-panic/display_panics"] diff --git a/packages/pros/examples/accessories.rs b/packages/pros/examples/accessories.rs index 7056f4c0..fb19faba 100644 --- a/packages/pros/examples/accessories.rs +++ b/packages/pros/examples/accessories.rs @@ -7,13 +7,12 @@ use alloc::sync::Arc; use core::time::Duration; use pros::{ + core::sync::Mutex, devices::{ - smart::vision::{LedMode, Rgb, VisionZeroPoint}, + smart::vision::{LedMode, VisionZeroPoint}, Controller, }, prelude::*, - sync::Mutex, - task::delay, }; struct ExampleRobot { @@ -32,7 +31,7 @@ impl ExampleRobot { } impl AsyncRobot for ExampleRobot { - async fn opcontrol(&mut self) -> pros::Result { + async fn opcontrol(&mut self) -> Result { let handle = pros::async_runtime::spawn(async { for _ in 0..5 { println!("Hello from async!"); @@ -50,10 +49,8 @@ impl AsyncRobot for ExampleRobot { self.vision.set_led(LedMode::On(Rgb::new(0, 0, 255))); - pros::lcd::buttons::register(left_button_callback, Button::Left); - // Spawn a new task that will print whether or not the motor is stopped constantly. - spawn({ + pros_core::task::spawn({ let motor = Arc::clone(&self.motor); // Obtain a shared reference to our motor to safely share between tasks. move || loop { @@ -75,7 +72,7 @@ impl AsyncRobot for ExampleRobot { // Set output takes a float from -1 to 1 that is scaled to -12 to 12 volts. self.motor .lock() - .set_output(controller.state().joysticks.right.y)?; + .set_output(controller.state()?.joysticks.right.y)?; // println!("pid out {}", pid.update(10.0, motor.position().into_degrees() as f32)); println!( @@ -92,7 +89,3 @@ async_robot!( ExampleRobot, ExampleRobot::new(Peripherals::take().unwrap()) ); - -fn left_button_callback() { - println!("Left button pressed!"); -} diff --git a/packages/pros/examples/adi.rs b/packages/pros/examples/adi.rs index aa0d4fa4..10e83c16 100644 --- a/packages/pros/examples/adi.rs +++ b/packages/pros/examples/adi.rs @@ -21,15 +21,15 @@ impl ExampleRobot { } impl AsyncRobot for ExampleRobot { - async fn opcontrol(&mut self) -> pros::Result { + async fn opcontrol(&mut self) -> Result { self.gyro.zero()?; self.encoder.zero()?; loop { - println!("Encoder value: {:?}", self.encoder.value()); - println!("Ultrasonic value: {:?}", self.ultrasonic.value()); + println!("Encoder position: {:?}", self.encoder.position()); + println!("Ultrasonic distance: {:?}", self.ultrasonic.distance()); - pros::task::delay(Duration::from_millis(10)); + delay(Duration::from_millis(10)); } } } diff --git a/packages/pros/examples/adi_expander.rs b/packages/pros/examples/adi_expander.rs new file mode 100644 index 00000000..efd6eee1 --- /dev/null +++ b/packages/pros/examples/adi_expander.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use core::time::Duration; + +use pros::prelude::*; + +pub struct Robot { + encoder: AdiEncoder, +} +impl Robot { + fn new(peripherals: Peripherals) -> Self { + // Create an expander on smart port 1 + let expander = AdiExpander::new(peripherals.port_1); + + Self { + // Create an encoder on the expander's A and B ports. + encoder: AdiEncoder::new((expander.adi_a, expander.adi_b), false).unwrap(), + } + } +} + +impl AsyncRobot for Robot { + async fn opcontrol(&mut self) -> Result { + // Read from the encoder every second. + loop { + println!("Encoder position: {}", self.encoder.position()?); + + delay(Duration::from_secs(1)); + } + } +} +async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); diff --git a/packages/pros/examples/basic.rs b/packages/pros/examples/basic.rs index 78837a04..e1e120a3 100644 --- a/packages/pros/examples/basic.rs +++ b/packages/pros/examples/basic.rs @@ -7,7 +7,7 @@ use pros::prelude::*; pub struct Robot; impl AsyncRobot for Robot { - async fn opcontrol(&mut self) -> pros::Result { + async fn opcontrol(&mut self) -> Result { println!("basic example"); Ok(()) diff --git a/packages/pros/examples/battery.rs b/packages/pros/examples/battery.rs index 679e1079..e0384393 100644 --- a/packages/pros/examples/battery.rs +++ b/packages/pros/examples/battery.rs @@ -7,7 +7,7 @@ use pros::{devices::battery, prelude::*}; pub struct Robot; impl AsyncRobot for Robot { - async fn opcontrol(&mut self) -> pros::Result { + async fn opcontrol(&mut self) -> Result { if battery::capacity()? < 20.0 { println!("Battery is low!"); } else if battery::temperature()? > 999.0 { diff --git a/packages/pros/examples/dynamic_peripherals.rs b/packages/pros/examples/dynamic_peripherals.rs index 205ecea1..4b4c321a 100644 --- a/packages/pros/examples/dynamic_peripherals.rs +++ b/packages/pros/examples/dynamic_peripherals.rs @@ -14,7 +14,7 @@ impl Robot { } } impl AsyncRobot for Robot { - async fn opcontrol(&mut self) -> pros::Result { + async fn opcontrol(&mut self) -> Result { let motor = Motor::new( self.peripherals.take_smart_port(10).unwrap(), BrakeMode::Brake, diff --git a/packages/pros/examples/imu.rs b/packages/pros/examples/imu.rs index 214c2e84..20440fd9 100644 --- a/packages/pros/examples/imu.rs +++ b/packages/pros/examples/imu.rs @@ -17,7 +17,7 @@ impl Robot { } impl AsyncRobot for Robot { - async fn opcontrol(&mut self) -> pros::Result { + async fn opcontrol(&mut self) -> Result { self.imu.calibrate().await?; loop { @@ -28,7 +28,7 @@ impl AsyncRobot for Robot { euler.pitch, euler.roll, euler.yaw ); - pros::task::delay(Duration::from_secs(1)); + delay(Duration::from_secs(1)); } } } diff --git a/packages/pros/examples/llemu.rs b/packages/pros/examples/llemu.rs deleted file mode 100644 index d04295e7..00000000 --- a/packages/pros/examples/llemu.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_std] -#![no_main] - -use pros::prelude::*; - -#[derive(Default)] -pub struct Robot; -impl AsyncRobot for Robot { - async fn opcontrol(&mut self) -> pros::Result { - llemu_println!("basic example"); - - Ok(()) - } -} -async_robot!(Robot); diff --git a/packages/pros/examples/optical.rs b/packages/pros/examples/optical.rs index 5766fdc9..296abd6e 100644 --- a/packages/pros/examples/optical.rs +++ b/packages/pros/examples/optical.rs @@ -16,8 +16,8 @@ impl Robot { } } -impl SyncRobot for Robot { - fn opcontrol(&mut self) -> pros::Result { +impl AsyncRobot for Robot { + async fn opcontrol(&mut self) -> Result { loop { println!( "-----\nHue: {}\nSaturation: {}\nBrightess: {}\nLast Gesture Direction: {:?}\n-----\n", @@ -27,9 +27,9 @@ impl SyncRobot for Robot { self.optical.last_gesture_direction()? ); - pros::task::delay(Duration::from_millis(10)); + delay(Duration::from_millis(10)); } } } -sync_robot!(Robot, Robot::new(Peripherals::take().unwrap())); +async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); diff --git a/packages/pros/examples/screen.rs b/packages/pros/examples/screen.rs new file mode 100644 index 00000000..d6daa3e2 --- /dev/null +++ b/packages/pros/examples/screen.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +use core::fmt::Write; + +use pros::prelude::*; + +pub struct Robot { + screen: Screen, +} + +impl Robot { + fn new(peripherals: Peripherals) -> Self { + Self { + screen: peripherals.screen, + } + } +} + +impl AsyncRobot for Robot { + async fn opcontrol(&mut self) -> Result { + self.screen.fill(&Rect::new(0, 0, 20, 20), Rgb::RED)?; + self.screen.stroke(&Circle::new(25, 25, 20), Rgb::BLUE)?; + + writeln!(self.screen, "Hello, world.")?; + + Ok(()) + } +} +async_robot!(Robot, Robot::new(Peripherals::take().unwrap())); diff --git a/packages/pros/src/async_runtime/mod.rs b/packages/pros/src/async_runtime/mod.rs deleted file mode 100644 index 2fec27f4..00000000 --- a/packages/pros/src/async_runtime/mod.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! A tiny async runtime. -//! -//! This runtime can be used outside of the main task, but it is reccomended to only use either -//! real FreeRTOS tasks or this async runtime. - -use core::future::Future; - -use async_task::Task; - -pub(crate) mod executor; -pub(crate) mod reactor; - -/// Runs a future in the background without having to await it -/// To get the the return value you can await a task. -pub fn spawn(future: impl Future + 'static) -> Task { - executor::EXECUTOR.with(|e| e.spawn(future)) -} - -/// Blocks the current task untill a return value can be extracted from the provided future. -/// Does not poll all futures to completion. -/// If you want to complete all futures, use the [`complete_all`] function. -pub fn block_on(future: F) -> F::Output { - executor::EXECUTOR.with(|e| e.block_on(spawn(future))) -} diff --git a/packages/pros/src/devices/adi/digital.rs b/packages/pros/src/devices/adi/digital.rs deleted file mode 100644 index 00f2b84e..00000000 --- a/packages/pros/src/devices/adi/digital.rs +++ /dev/null @@ -1,97 +0,0 @@ -use pros_sys::PROS_ERR; - -use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; - -#[derive(Debug, Eq, PartialEq)] -pub struct AdiDigitalIn { - port: AdiPort, -} - -impl AdiDigitalIn { - /// Create a digital input from an ADI port. - pub fn new(port: AdiPort) -> Self { - Self { port } - } - - /// Gets a rising-edge case for a digital button press. - pub fn new_press(&mut self) -> Result { - Ok(unsafe { - bail_on!( - PROS_ERR, - pros_sys::ext_adi_digital_get_new_press( - self.port.internal_expander_index(), - self.port.index() - ) - ) != 0 - }) - } - - /// Gets the current value of a digital input pin. - pub fn value(&self) -> Result { - Ok(unsafe { - bail_on!( - PROS_ERR, - pros_sys::ext_adi_digital_read( - self.port.internal_expander_index(), - self.port.index() - ) - ) != 0 - }) - } -} - -impl AdiDevice for AdiDigitalIn { - type PortIndexOutput = u8; - - fn port_index(&self) -> Self::PortIndexOutput { - self.port.index() - } - - fn expander_port_index(&self) -> Option { - self.port.expander_index() - } - - fn device_type(&self) -> AdiDeviceType { - AdiDeviceType::DigitalIn - } -} - -#[derive(Debug, Eq, PartialEq)] -pub struct AdiDigitalOut { - port: AdiPort, -} - -impl AdiDigitalOut { - /// Create a digital output from an [`AdiPort`]. - pub fn new(port: AdiPort) -> Self { - Self { port } - } - - /// Sets the digital value (1 or 0) of a pin. - pub fn set_value(&mut self, value: bool) -> Result { - Ok(bail_on!(PROS_ERR, unsafe { - pros_sys::ext_adi_digital_write( - self.port.internal_expander_index(), - self.port.index(), - value, - ) - })) - } -} - -impl AdiDevice for AdiDigitalOut { - type PortIndexOutput = u8; - - fn port_index(&self) -> Self::PortIndexOutput { - self.port.index() - } - - fn expander_port_index(&self) -> Option { - self.port.expander_index() - } - - fn device_type(&self) -> AdiDeviceType { - AdiDeviceType::DigitalOut - } -} diff --git a/packages/pros/src/devices/adi/gyro.rs b/packages/pros/src/devices/adi/gyro.rs deleted file mode 100644 index 939e6e3c..00000000 --- a/packages/pros/src/devices/adi/gyro.rs +++ /dev/null @@ -1,54 +0,0 @@ -use pros_sys::{ext_adi_gyro_t, PROS_ERR}; - -use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; - -#[derive(Debug, Eq, PartialEq)] -pub struct AdiGyro { - raw: ext_adi_gyro_t, - port: AdiPort, -} - -impl AdiGyro { - /// Create a new gyro from an [`AdiPort`]. - pub fn new(port: AdiPort, multiplier: f64) -> Result { - let raw = bail_on!(PROS_ERR.into(), unsafe { - pros_sys::ext_adi_gyro_init(port.internal_expander_index(), port.index(), multiplier) - }); - - Ok(Self { raw, port }) - } - - /// Gets the current gyro angle in tenths of a degree. Unless a multiplier is applied to the gyro, the return value will be a whole number representing the number of degrees of rotation times 10. - /// - /// There are 360 degrees in a circle, thus the gyro will return 3600 for one whole rotation. - pub fn value(&self) -> Result { - Ok(bail_on!(PROS_ERR.into(), unsafe { - pros_sys::ext_adi_gyro_get(self.raw) - })) - } - - /// Reset the current gyro angle to zero degrees. - pub fn zero(&mut self) -> Result<(), AdiError> { - bail_on!(PROS_ERR.into(), unsafe { - pros_sys::ext_adi_gyro_reset(self.raw) - }); - Ok(()) - } -} - -impl AdiDevice for AdiGyro { - type PortIndexOutput = u8; - - fn port_index(&self) -> Self::PortIndexOutput { - self.port.index() - } - - fn expander_port_index(&self) -> Option { - self.port.expander_index() - } - - fn device_type(&self) -> AdiDeviceType { - AdiDeviceType::LegacyGyro - } -} diff --git a/packages/pros/src/devices/controller.rs b/packages/pros/src/devices/controller.rs deleted file mode 100644 index d5ad0857..00000000 --- a/packages/pros/src/devices/controller.rs +++ /dev/null @@ -1,244 +0,0 @@ -//! Read from the buttons and joysticks on the controller and write to the controller's display. -//! -//! Controllers are identified by their id, which is either 0 (master) or 1 (partner). -//! State of a controller can be checked by calling [`Controller::state`] which will return a struct with all of the buttons' and joysticks' state. - -use alloc::{ffi::CString, vec::Vec}; - -use pros_sys::{controller_id_e_t, PROS_ERR}; -use snafu::Snafu; - -use crate::error::{bail_on, map_errno}; - -/// Holds whether or not the buttons on the controller are pressed or not -#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] -pub struct Buttons { - pub a: bool, - pub b: bool, - pub x: bool, - pub y: bool, - pub up: bool, - pub down: bool, - pub left: bool, - pub right: bool, - pub left_trigger_1: bool, - pub left_trigger_2: bool, - pub right_trigger_1: bool, - pub right_trigger_2: bool, -} - -/// Stores how far the joystick is away from the center (at *(0, 0)*) from -1 to 1. -/// On the x axis left is negative, and right is positive. -/// On the y axis down is negative, and up is positive. -#[derive(Default, Debug, Clone, Copy, PartialEq)] -pub struct Joystick { - pub x: f32, - pub y: f32, -} - -/// Stores both joysticks on the controller. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct Joysticks { - pub left: Joystick, - pub right: Joystick, -} - -/// Stores the current state of the controller; the joysticks and buttons. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct ControllerState { - pub joysticks: Joysticks, - pub buttons: Buttons, -} - -/// Represents one line on the controller console. -#[derive(Debug, Clone, Copy)] -pub struct ControllerLine { - controller: Controller, - line: u8, -} - -impl ControllerLine { - pub const MAX_TEXT_LEN: usize = 14; - pub const MAX_LINE_NUM: u8 = 2; - pub fn try_print(&self, text: impl Into>) -> Result<(), ControllerError> { - let text = text.into(); - let text_len = text.len(); - assert!( - text_len > ControllerLine::MAX_TEXT_LEN, - "Printed text is too long to fit on controller display ({text_len} > {})", - Self::MAX_TEXT_LEN - ); - let c_text = CString::new(text).expect("parameter `text` should not contain null bytes"); - bail_on!(PROS_ERR, unsafe { - pros_sys::controller_set_text(self.controller.id(), self.line, 0, c_text.as_ptr()) - }); - Ok(()) - } - pub fn print(&self, text: impl Into>) { - self.try_print(text).unwrap(); - } -} - -/// A digital channel (button) on the VEX controller. -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ControllerButton { - A = pros_sys::E_CONTROLLER_DIGITAL_A, - B = pros_sys::E_CONTROLLER_DIGITAL_B, - X = pros_sys::E_CONTROLLER_DIGITAL_X, - Y = pros_sys::E_CONTROLLER_DIGITAL_Y, - Up = pros_sys::E_CONTROLLER_DIGITAL_UP, - Down = pros_sys::E_CONTROLLER_DIGITAL_DOWN, - Left = pros_sys::E_CONTROLLER_DIGITAL_LEFT, - Right = pros_sys::E_CONTROLLER_DIGITAL_RIGHT, - LeftTrigger1 = pros_sys::E_CONTROLLER_DIGITAL_L1, - LeftTrigger2 = pros_sys::E_CONTROLLER_DIGITAL_L2, - RightTrigger1 = pros_sys::E_CONTROLLER_DIGITAL_R1, - RightTrigger2 = pros_sys::E_CONTROLLER_DIGITAL_R2, -} - -/// An analog channel (joystick axis) on the VEX controller. -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum JoystickAxis { - LeftX = pros_sys::E_CONTROLLER_ANALOG_LEFT_X, - LeftY = pros_sys::E_CONTROLLER_ANALOG_LEFT_Y, - RightX = pros_sys::E_CONTROLLER_ANALOG_RIGHT_X, - RightY = pros_sys::E_CONTROLLER_ANALOG_RIGHT_Y, -} - -/// The basic type for a controller. -/// Used to get the state of its joysticks and controllers. -#[repr(u32)] -#[derive(Debug, Clone, Copy)] -pub enum Controller { - Master = pros_sys::E_CONTROLLER_MASTER, - Partner = pros_sys::E_CONTROLLER_PARTNER, -} - -impl Controller { - fn id(&self) -> controller_id_e_t { - *self as controller_id_e_t - } - - pub fn line(&self, line_num: u8) -> ControllerLine { - assert!( - line_num > ControllerLine::MAX_LINE_NUM, - "Line number is too large for controller display ({line_num} > {})", - ControllerLine::MAX_LINE_NUM - ); - - ControllerLine { - controller: *self, - line: line_num, - } - } - - /// Gets the current state of the controller in its entirety. - pub fn state(&self) -> ControllerState { - ControllerState { - joysticks: unsafe { - Joysticks { - left: Joystick { - x: pros_sys::controller_get_analog( - self.id(), - pros_sys::E_CONTROLLER_ANALOG_LEFT_X, - ) as f32 - / 127.0, - y: pros_sys::controller_get_analog( - self.id(), - pros_sys::E_CONTROLLER_ANALOG_LEFT_Y, - ) as f32 - / 127.0, - }, - right: Joystick { - x: pros_sys::controller_get_analog( - self.id(), - pros_sys::E_CONTROLLER_ANALOG_RIGHT_X, - ) as f32 - / 127.0, - y: pros_sys::controller_get_analog( - self.id(), - pros_sys::E_CONTROLLER_ANALOG_RIGHT_Y, - ) as f32 - / 127.0, - }, - } - }, - buttons: unsafe { - Buttons { - a: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_A, - ) == 1, - b: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_B, - ) == 1, - x: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_X, - ) == 1, - y: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_Y, - ) == 1, - up: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_UP, - ) == 1, - down: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_DOWN, - ) == 1, - left: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_LEFT, - ) == 1, - right: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_RIGHT, - ) == 1, - left_trigger_1: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_L1, - ) == 1, - left_trigger_2: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_L2, - ) == 1, - right_trigger_1: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_R1, - ) == 1, - right_trigger_2: pros_sys::controller_get_digital( - self.id(), - pros_sys::E_CONTROLLER_DIGITAL_R2, - ) == 1, - } - }, - } - } - - /// Gets the state of a specific button on the controller. - pub fn button(&self, button: ControllerButton) -> bool { - unsafe { pros_sys::controller_get_digital(self.id(), button as u32) == 1 } - } - - /// Gets the state of a specific joystick axis on the controller. - pub fn joystick_axis(&self, axis: JoystickAxis) -> f32 { - unsafe { pros_sys::controller_get_analog(self.id(), axis as u32) as f32 / 127.0 } - } -} - -#[derive(Debug, Snafu)] -pub enum ControllerError { - #[snafu(display("Another resource is already using the controller"))] - ConcurrentAccess, -} - -map_errno! { - ControllerError { - EACCES => Self::ConcurrentAccess, - } -} diff --git a/packages/pros/src/devices/peripherals.rs b/packages/pros/src/devices/peripherals.rs deleted file mode 100644 index 6a22ae26..00000000 --- a/packages/pros/src/devices/peripherals.rs +++ /dev/null @@ -1,146 +0,0 @@ -use core::sync::atomic::AtomicBool; - -use crate::devices::{adi::AdiPort, smart::SmartPort}; - -static PERIPHERALS_TAKEN: AtomicBool = AtomicBool::new(false); - -pub struct Peripherals { - pub port_1: SmartPort, - pub port_2: SmartPort, - pub port_3: SmartPort, - pub port_4: SmartPort, - pub port_5: SmartPort, - pub port_6: SmartPort, - pub port_7: SmartPort, - pub port_8: SmartPort, - pub port_9: SmartPort, - pub port_10: SmartPort, - pub port_11: SmartPort, - pub port_12: SmartPort, - pub port_13: SmartPort, - pub port_14: SmartPort, - pub port_15: SmartPort, - pub port_16: SmartPort, - pub port_17: SmartPort, - pub port_18: SmartPort, - pub port_19: SmartPort, - pub port_20: SmartPort, - pub port_21: SmartPort, - - pub adi_a: AdiPort, - pub adi_b: AdiPort, - pub adi_c: AdiPort, - pub adi_d: AdiPort, - pub adi_e: AdiPort, - pub adi_f: AdiPort, - pub adi_g: AdiPort, - pub adi_h: AdiPort, -} - -impl Peripherals { - const unsafe fn new() -> Self { - Self { - port_1: SmartPort::new(1), - port_2: SmartPort::new(2), - port_3: SmartPort::new(3), - port_4: SmartPort::new(4), - port_5: SmartPort::new(5), - port_6: SmartPort::new(6), - port_7: SmartPort::new(7), - port_8: SmartPort::new(8), - port_9: SmartPort::new(9), - port_10: SmartPort::new(10), - port_11: SmartPort::new(11), - port_12: SmartPort::new(12), - port_13: SmartPort::new(13), - port_14: SmartPort::new(14), - port_15: SmartPort::new(15), - port_16: SmartPort::new(16), - port_17: SmartPort::new(17), - port_18: SmartPort::new(18), - port_19: SmartPort::new(19), - port_20: SmartPort::new(20), - port_21: SmartPort::new(21), - - adi_a: AdiPort::new(1, None), - adi_b: AdiPort::new(2, None), - adi_c: AdiPort::new(3, None), - adi_d: AdiPort::new(4, None), - adi_e: AdiPort::new(5, None), - adi_f: AdiPort::new(6, None), - adi_g: AdiPort::new(7, None), - adi_h: AdiPort::new(8, None), - } - } - - pub fn take() -> Option { - if PERIPHERALS_TAKEN.swap(true, core::sync::atomic::Ordering::AcqRel) { - None - } else { - Some(unsafe { Self::new() }) - } - } - - pub unsafe fn steal() -> Self { - PERIPHERALS_TAKEN.store(true, core::sync::atomic::Ordering::Release); - Self::new() - } -} - -/// Guarentees that ports are only used once **at runtime** -/// This is useful for when you want to store a peripherals struct for use in multiple functions. -/// When possible, use [`Peripherals`] instead. -pub struct DynamicPeripherals { - smart_ports: [bool; 21], - adi_slots: [bool; 8], -} -impl DynamicPeripherals { - /// Creates a new dynamic peripherals - /// In order to guarentee that no ports created by this struct, - /// this function takes a [`Peripherals`]. - /// This guarentees safety because [`Peripherals`] cannot be passed by value - /// after they have been used to create devices. - pub const fn new(_peripherals: Peripherals) -> Self { - let smart_ports = [false; 21]; - let adi_slots = [false; 8]; - Self { - smart_ports, - adi_slots, - } - } - - /// Creates a [`SmartPort`] only if one has not been created on the given port before. - /// - /// # Panics - /// - /// This function panics if the provided port is outside the range 1-21. - /// Ports outside of this range are invalid and cannot be created. - pub fn take_smart_port(&mut self, port_index: u8) -> Option { - let port_index = port_index as usize - 1; - if self.smart_ports[port_index] { - return None; - }; - self.smart_ports[port_index] = true; - Some(unsafe { SmartPort::new(port_index as u8 + 1) }) - } - - /// Creates an [`AdiSlot`] only if one has not been created on the given slot before. - /// - /// # Panics - /// - /// This function panics if the provided port is outside the range 1-8. - /// Slots outside of this range are invalid and cannot be created. - pub fn take_adi_port(&mut self, port_index: u8) -> Option { - let port_index = port_index as usize - 1; - if self.adi_slots[port_index] { - return None; - } - self.smart_ports[port_index] = true; - Some(unsafe { AdiPort::new(port_index as u8 + 1, None) }) - } -} -impl From for DynamicPeripherals { - fn from(peripherals: Peripherals) -> Self { - Self::new(peripherals) - } -} diff --git a/packages/pros/src/io.rs b/packages/pros/src/io.rs deleted file mode 100644 index bfad2330..00000000 --- a/packages/pros/src/io.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Helpers for terminal I/O functionality. - -pub use libc_print::std_name::*; -pub use no_std_io::io::*; diff --git a/packages/pros/src/lcd/buttons.rs b/packages/pros/src/lcd/buttons.rs deleted file mode 100644 index 76112a47..00000000 --- a/packages/pros/src/lcd/buttons.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Register LCD button callbacks. -//! -//! Use of button callbacks isn't recommended, -//! instead run code when a button is pressed by checking the state of the buttons in a loop. -//! Buttons can be checked with [`read_buttons`]. - -extern crate alloc; - -use alloc::boxed::Box; - -use crate::sync::Mutex; - -pub struct ButtonsState { - pub left_pressed: bool, - pub middle_pressed: bool, - pub right_pressed: bool, -} - -pub fn read_buttons() -> ButtonsState { - let bit_mask = unsafe { pros_sys::lcd_read_buttons() }; - ButtonsState { - left_pressed: bit_mask & 0b001 == bit_mask, - middle_pressed: bit_mask & 0b010 == bit_mask, - right_pressed: bit_mask & 0b100 == bit_mask, - } -} - -pub enum Button { - Left, - Middle, - Right, -} - -pub struct ButtonCallbacks { - pub left_cb: Option>, - pub middle_cb: Option>, - pub right_cb: Option>, -} - -lazy_static::lazy_static! { - pub static ref BUTTON_CALLBACKS: Mutex = Mutex::new(ButtonCallbacks { - left_cb: None, - middle_cb: None, - right_cb: None, - }); -} - -// this needs to return errors -pub fn register(cb: impl Fn() + 'static + Send, button: Button) { - unsafe { - pros_sys::lcd_initialize(); - } - - extern "C" fn button_0_cb() { - if let Some(cb) = &BUTTON_CALLBACKS.lock().left_cb { - cb(); - } - } - - extern "C" fn button_1_cb() { - if let Some(cb) = &BUTTON_CALLBACKS.lock().middle_cb { - cb(); - } - } - - extern "C" fn button_2_cb() { - if let Some(cb) = &BUTTON_CALLBACKS.lock().right_cb { - cb(); - } - } - - if !match button { - Button::Left => { - BUTTON_CALLBACKS.lock().left_cb = Some(Box::new(cb)); - unsafe { pros_sys::lcd_register_btn0_cb(Some(button_0_cb)) } - } - Button::Middle => { - BUTTON_CALLBACKS.lock().middle_cb = Some(Box::new(cb)); - unsafe { pros_sys::lcd_register_btn1_cb(Some(button_1_cb)) } - } - Button::Right => { - BUTTON_CALLBACKS.lock().right_cb = Some(Box::new(cb)); - unsafe { pros_sys::lcd_register_btn2_cb(Some(button_2_cb)) } - } - } { - panic!("Setting button callback failed, even though lcd initialization was attempted."); - } -} diff --git a/packages/pros/src/lcd/macros.rs b/packages/pros/src/lcd/macros.rs deleted file mode 100644 index 24f9b615..00000000 --- a/packages/pros/src/lcd/macros.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Macros for printing to the LCD screen. -//! -//! These macros are designed to be exactly the same as the standard library's equivalents. - -use core::fmt::{self, Write}; - -use super::WRITER; - -#[doc(hidden)] -pub fn _llemu_print(args: fmt::Arguments) { - WRITER.lock().write_fmt(args).unwrap(); -} - -#[macro_export] -macro_rules! llemu_print { - ($($arg:tt)*) => { - $crate::lcd::macros::_llemu_print(core::format_args!($($arg)*)); - }; -} - -#[macro_export] -macro_rules! llemu_println { - () => { - $crate::llemu_print!("\n"); - }; - ($($arg:tt)*) => { - $crate::llemu_print!("{}\n", core::format_args!($($arg)*)); - }; -} - -pub use llemu_print; -pub use llemu_println; diff --git a/packages/pros/src/lcd/mod.rs b/packages/pros/src/lcd/mod.rs deleted file mode 100644 index dbf16c5a..00000000 --- a/packages/pros/src/lcd/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Print to and handle button presses on the LLEMU -//! -//! Anything this module provides is only availible when not using a custom ui made with LVGL. -//! This module is specific to the premade interface (LLEMU). - -use snafu::Snafu; - -use crate::{lvgl::colors::LcdColor, sync::Mutex}; - -pub mod buttons; -pub mod macros; - -pub use macros::*; - -pub(crate) mod writer; - -/// Sets the background color of the LCD. -pub fn set_background_color(color: LcdColor) { - unsafe { - pros_sys::lcd_initialize(); - pros_sys::lcd_set_background_color(*color); - } -} - -/// Sets the text color of the LCD. -pub fn set_text_color(color: LcdColor) { - unsafe { - pros_sys::lcd_initialize(); - pros_sys::lcd_set_background_color(*color); - } -} - -lazy_static::lazy_static! { - pub(crate) static ref WRITER: Mutex = { - Mutex::new(writer::ConsoleLcd::new()) - }; -} - -#[derive(Debug, Snafu)] -pub enum LcdError { - #[snafu(display("LCD not initialized"))] - NotInitialized, -} diff --git a/packages/pros/src/lcd/writer.rs b/packages/pros/src/lcd/writer.rs deleted file mode 100644 index c1280fb1..00000000 --- a/packages/pros/src/lcd/writer.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Scrolling text writer for the LLEMU. - -extern crate alloc; - -use alloc::{ffi::CString, string::String}; - -const V5_SCREEN_HEIGHT: usize = 8; - -pub(crate) struct ConsoleLcd { - lines: [CString; V5_SCREEN_HEIGHT], - bottom_line_index: usize, - current_line: String, -} - -impl ConsoleLcd { - pub fn new() -> Self { - unsafe { - pros_sys::lcd_initialize(); - } - - Self { - lines: Default::default(), - bottom_line_index: V5_SCREEN_HEIGHT - 1, - current_line: String::new(), - } - } -} - -impl core::fmt::Write for ConsoleLcd { - fn write_str(&mut self, text: &str) -> core::fmt::Result { - let mut should_render = false; - for c in text.chars() { - if c == '\n' { - should_render = true; - let line = CString::new(core::mem::take(&mut self.current_line)) - .expect("line should not contain null (U+0000) bytes"); - - self.shift_up_wrapping(); - self.lines[self.bottom_line_index] = line; - } else { - self.current_line.push(c); - } - } - - if should_render { - self.render()?; - } - - Ok(()) - } -} - -impl ConsoleLcd { - fn shift_up_wrapping(&mut self) { - self.bottom_line_index = (self.bottom_line_index + 1) % V5_SCREEN_HEIGHT; - } - fn render(&self) -> core::fmt::Result { - for (i, text) in self.lines.iter().enumerate() { - const MAX_INDEX: usize = V5_SCREEN_HEIGHT - 1; - let index_offset = MAX_INDEX - self.bottom_line_index; - let line_num = (i + index_offset) % V5_SCREEN_HEIGHT; - let success = - unsafe { pros_sys::lcd_set_text(line_num.try_into().unwrap(), text.as_ptr()) }; - if !success { - return Err(core::fmt::Error); - } - } - Ok(()) - } -} diff --git a/packages/pros/src/lib.rs b/packages/pros/src/lib.rs index d4f76885..7e24f127 100644 --- a/packages/pros/src/lib.rs +++ b/packages/pros/src/lib.rs @@ -28,6 +28,7 @@ //! loop { //! // Do something //! sleep(Duration::from_millis(20)).await; +//! } //! } //! } //! async_robot!(Robot); @@ -44,368 +45,76 @@ //! loop { //! // Do something //! delay(Duration::from_millis(20)); +//! } //! } //! } //! sync_robot!(Robot); //! ``` //! //! You may have noticed the `#[derive(Default)]` attribute on these Robot structs. -//! If you want to learn why, look at the docs for [`async_robot`] or [`sync_robot`]. - -#![feature(error_in_core, stdsimd, negative_impls)] +//! If you want to learn why, look at the docs for [`pros_async::async_robot`] or [`pros_sync::sync_robot`]. #![no_std] -extern crate alloc; - -use core::future::Future; - -pub mod async_runtime; -pub mod devices; -pub mod error; -pub mod pid; -pub mod sync; -#[macro_use] -pub mod task; - -#[doc(hidden)] -pub use pros_sys as __pros_sys; -#[cfg(target_os = "vexos")] -mod vexos_env; -#[cfg(target_arch = "wasm32")] -mod wasm_env; -#[macro_use] -pub mod lcd; -pub mod competition; -pub mod io; -pub mod lvgl; -pub mod time; -pub mod usd; - -pub type Result = core::result::Result>; - -use crate::io::println; - -pub trait AsyncRobot { - fn opcontrol(&mut self) -> impl Future { - async { Ok(()) } - } - fn auto(&mut self) -> impl Future { - async { Ok(()) } - } - fn disabled(&mut self) -> impl Future { - async { Ok(()) } - } - fn comp_init(&mut self) -> impl Future { - async { Ok(()) } - } -} - -pub trait SyncRobot { - fn opcontrol(&mut self) -> Result { - Ok(()) - } - fn auto(&mut self) -> Result { - Ok(()) - } - fn disabled(&mut self) -> Result { - Ok(()) - } - fn comp_init(&mut self) -> Result { - Ok(()) - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __gen_sync_exports { - ($rbt:ty) => { - pub static mut ROBOT: Option<$rbt> = None; - - #[doc(hidden)] - #[no_mangle] - extern "C" fn opcontrol() { - <$rbt as $crate::SyncRobot>::opcontrol(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before opcontrol") - }) - .unwrap(); - } - - #[doc(hidden)] - #[no_mangle] - extern "C" fn autonomous() { - <$rbt as $crate::SyncRobot>::auto(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before opcontrol") - }) - .unwrap(); - } - - #[doc(hidden)] - #[no_mangle] - extern "C" fn disabled() { - <$rbt as $crate::SyncRobot>::disabled(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before opcontrol") - }) - .unwrap(); - } - - #[doc(hidden)] - #[no_mangle] - extern "C" fn competition_initialize() { - <$rbt as $crate::SyncRobot>::comp_init(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before opcontrol") - }) - .unwrap(); - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __gen_async_exports { - ($rbt:ty) => { - pub static mut ROBOT: Option<$rbt> = None; - - #[doc(hidden)] - #[no_mangle] - extern "C" fn opcontrol() { - $crate::async_runtime::block_on(<$rbt as $crate::AsyncRobot>::opcontrol(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before opcontrol") - })) - .unwrap(); - } - - #[doc(hidden)] - #[no_mangle] - extern "C" fn autonomous() { - $crate::async_runtime::block_on(<$rbt as $crate::AsyncRobot>::opcontrol(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before auto") - })) - .unwrap(); - } - - #[doc(hidden)] - #[no_mangle] - extern "C" fn disabled() { - $crate::async_runtime::block_on(<$rbt as $crate::AsyncRobot>::opcontrol(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before disabled") - })) - .unwrap(); - } - - #[doc(hidden)] - #[no_mangle] - extern "C" fn competition_initialize() { - $crate::async_runtime::block_on(<$rbt as $crate::AsyncRobot>::opcontrol(unsafe { - ROBOT - .as_mut() - .expect("Expected initialize to run before comp_init") - })) - .unwrap(); - } - }; -} - -/// Allows your async robot code to be executed by the pros kernel. -/// If your robot struct implements Default then you can just supply this macro with its type. -/// If not, you can supply an expression that returns your robot type to initialize your robot struct. -/// The code that runs to create your robot struct will run in the initialize function in PROS. -/// -/// Example of using the macro with a struct that implements Default: -/// ```rust -/// use pros::prelude::*; -/// #[derive(Default)] -/// struct ExampleRobot; -/// #[async_trait] -/// impl AsyncRobot for ExampleRobot { -/// asnyc fn opcontrol(&mut self) -> pros::Result { -/// println!("Hello, world!"); -/// Ok(()) -/// } -/// } -/// async_robot!(ExampleRobot); -/// ``` -/// -/// Example of using the macro with a struct that does not implement Default: -/// ```rust -/// use pros::prelude::*; -/// struct ExampleRobot { -/// x: i32, -/// } -/// #[async_trait] -/// impl AsyncRobot for ExampleRobot { -/// async fn opcontrol(&mut self) -> pros::Result { -/// println!("Hello, world! {}", self.x); -/// Ok(()) -/// } -/// } -/// impl ExampleRobot { -/// pub fn new() -> Self { -/// Self { x: 5 } -/// } -/// } -/// async_robot!(ExampleRobot, ExampleRobot::new()); -#[macro_export] -macro_rules! async_robot { - ($rbt:ty) => { - $crate::__gen_async_exports!($rbt); - - #[no_mangle] - extern "C" fn initialize() { - ::pros::task::__init_entrypoint(); - unsafe { - ROBOT = Some(Default::default()); - } - } - }; - ($rbt:ty, $init:expr) => { - $crate::__gen_async_exports!($rbt); - - #[no_mangle] - extern "C" fn initialize() { - ::pros::task::__init_entrypoint(); - unsafe { - ROBOT = Some($init); - } - } - }; -} - -/// Allows your sync robot code to be executed by the pros kernel. -/// If your robot struct implements Default then you can just supply this macro with its type. -/// If not, you can supply an expression that returns your robot type to initialize your robot struct. -/// The code that runs to create your robot struct will run in the initialize function in PROS. -/// -/// Example of using the macro with a struct that implements Default: -/// ```rust -/// use pros::prelude::*; -/// #[derive(Default)] -/// struct ExampleRobot; -/// impl SyncRobot for ExampleRobot { -/// asnyc fn opcontrol(&mut self) -> pros::Result { -/// println!("Hello, world!"); -/// Ok(()) -/// } -/// } -/// sync_robot!(ExampleRobot); -/// ``` -/// -/// Example of using the macro with a struct that does not implement Default: -/// ```rust -/// use pros::prelude::*; -/// struct ExampleRobot { -/// x: i32, -/// } -/// impl SyncRobot for ExampleRobot { -/// async fn opcontrol(&mut self) -> pros::Result { -/// println!("Hello, world! {}", self.x); -/// Ok(()) -/// } -/// } -/// impl ExampleRobot { -/// pub fn new() -> Self { -/// Self { x: 5 } -/// } -/// } -/// sync_robot!(ExampleRobot, ExampleRobot::new()); -#[macro_export] -macro_rules! sync_robot { - ($rbt:ty) => { - $crate::__gen_sync_exports!($rbt); - - #[no_mangle] - extern "C" fn initialize() { - ::pros::task::__init_entrypoint(); - unsafe { - ROBOT = Some(Default::default()); - } - } - }; - ($rbt:ty, $init:expr) => { - $crate::__gen_sync_exports!($rbt); - - #[no_mangle] - extern "C" fn initialize() { - ::pros::task::__init_entrypoint(); - unsafe { - ROBOT = Some($init); - } - } - }; -} - -#[panic_handler] -pub fn panic(info: &core::panic::PanicInfo) -> ! { - let current_task = task::current(); - - let task_name = current_task.name().unwrap_or_else(|_| "".into()); - - // task 'User Initialization (PROS)' panicked at src/lib.rs:22:1: - // panic message here - println!("task '{task_name}' {info}"); - - #[cfg(target_arch = "wasm32")] - wasm_env::sim_log_backtrace(); - - unsafe { - pros_sys::exit(1); - } -} +#[cfg(feature = "async")] +pub use pros_async as async_runtime; +#[cfg(feature = "core")] +pub use pros_core as core; +#[cfg(feature = "devices")] +pub use pros_devices as devices; +#[cfg(feature = "math")] +pub use pros_math as math; +#[cfg(feature = "panic")] +pub use pros_panic as panic; +#[cfg(feature = "sync")] +pub use pros_sync as sync; +pub use pros_sys as sys; /// Commonly used features of pros-rs. /// This module is meant to be glob imported. pub mod prelude { - // Import Box from alloc so that it can be used in async_trait! - pub use alloc::boxed::Box; - - pub use crate::{ - async_robot, - async_runtime::*, - devices::{ - adi::{ - analog::{AdiAnalogIn, AdiAnalogOut}, - digital::{AdiDigitalIn, AdiDigitalOut}, - encoder::AdiEncoder, - gyro::AdiGyro, - motor::AdiMotor, - potentiometer::{AdiPotentiometer, AdiPotentiometerType}, - ultrasonic::AdiUltrasonic, - AdiDevice, AdiPort, - }, - peripherals::{DynamicPeripherals, Peripherals}, - position::Position, - smart::{ - distance::DistanceSensor, - gps::GpsSensor, - imu::InertialSensor, - link::{Link, RxLink, TxLink}, - motor::{BrakeMode, Gearset, Motor}, - optical::OpticalSensor, - rotation::RotationSensor, - vision::VisionSensor, - SmartDevice, SmartPort, - }, + #[cfg(feature = "async")] + pub use pros_async::{async_robot, block_on, sleep, spawn, AsyncRobot}; + #[cfg(feature = "core")] + pub use pros_core::{ + dbg, eprint, eprintln, + error::{PortError, Result}, + io::{BufRead, Read, Seek, Write}, + print, println, + task::delay, + }; + #[cfg(feature = "devices")] + pub use pros_devices::{ + adi::{ + analog::AdiAnalogIn, + digital::{AdiDigitalIn, AdiDigitalOut}, + encoder::AdiEncoder, + gyro::AdiGyro, + motor::AdiMotor, + potentiometer::{AdiPotentiometer, AdiPotentiometerType}, + pwm::AdiPwmOut, + solenoid::AdiSolenoid, + ultrasonic::AdiUltrasonic, + AdiDevice, AdiPort, + }, + color::Rgb, + peripherals::{DynamicPeripherals, Peripherals}, + position::Position, + screen::{Circle, Line, Rect, Screen, Text, TextFormat, TextPosition, TouchState}, + smart::{ + distance::DistanceSensor, + expander::AdiExpander, + gps::GpsSensor, + imu::InertialSensor, + link::{Link, RxLink, TxLink}, + motor::{BrakeMode, Gearset, Motor}, + optical::OpticalSensor, + rotation::RotationSensor, + vision::VisionSensor, + SmartDevice, SmartPort, }, - error::PortError, - io::{dbg, eprint, eprintln, print, println, BufRead, Read, Seek, Write}, - lcd::{buttons::Button, llemu_print, llemu_println, LcdError}, - os_task_local, - pid::*, - sync_robot, - task::{delay, sleep, spawn}, - AsyncRobot, SyncRobot, }; + #[cfg(feature = "math")] + pub use pros_math::{feedforward::MotorFeedforwardController, pid::PidController}; + #[cfg(feature = "sync")] + pub use pros_sync::{sync_robot, SyncRobot}; } diff --git a/packages/pros/src/lvgl/colors.rs b/packages/pros/src/lvgl/colors.rs deleted file mode 100644 index 2e2fe600..00000000 --- a/packages/pros/src/lvgl/colors.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! LVGL colors and presets. - -use core::ops::{Deref, DerefMut}; - -use pros_sys::lv_color_t; - -/// A color that can be used on the LCD. -/// The color space is dependent on the LCD driver. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct LcdColor(pub lv_color_t); - -impl LcdColor { - /// Create an RGB color without any transparency. - pub const fn new_rgb(red: u8, green: u8, blue: u8) -> Self { - Self(lv_color_t { - red, - green, - blue, - alpha: 0xFF, - }) - } - /// Create an RGBA color with a certain opacity. - pub const fn new_rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self { - Self(lv_color_t { - red, - green, - blue, - alpha, - }) - } -} - -impl From for LcdColor { - fn from(other: lv_color_t) -> Self { - Self(other) - } -} - -impl Deref for LcdColor { - type Target = lv_color_t; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for LcdColor { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl LcdColor { - pub const WHITE: Self = Self::new_rgb(0xFF, 0xFF, 0xFF); - pub const SILVER: Self = Self::new_rgb(0xC0, 0xC0, 0xC0); - pub const GRAY: Self = Self::new_rgb(0x80, 0x80, 0x80); - pub const BLACK: Self = Self::new_rgb(0x00, 0x00, 0x00); - pub const RED: Self = Self::new_rgb(0xFF, 0x00, 0x00); - pub const MAROON: Self = Self::new_rgb(0x80, 0x00, 0x00); - pub const YELLOW: Self = Self::new_rgb(0xFF, 0xFF, 0x00); - pub const OLIVE: Self = Self::new_rgb(0x80, 0x80, 0x00); - pub const LIME: Self = Self::new_rgb(0x00, 0xFF, 0x00); - pub const GREEN: Self = Self::new_rgb(0x00, 0x80, 0x00); - pub const CYAN: Self = Self::new_rgb(0x00, 0xFF, 0xFF); - pub const AQUA: Self = Self::CYAN; - pub const TEAL: Self = Self::new_rgb(0x00, 0x80, 0x80); - pub const BLUE: Self = Self::new_rgb(0x00, 0x00, 0xFF); - pub const NAVY: Self = Self::new_rgb(0x00, 0x00, 0x80); - pub const MAGENTA: Self = Self::new_rgb(0xFF, 0x00, 0xFF); - pub const PURPLE: Self = Self::new_rgb(0x80, 0x00, 0x80); - pub const ORANGE: Self = Self::new_rgb(0xFF, 0xA5, 0x00); -} diff --git a/packages/pros/src/lvgl/mod.rs b/packages/pros/src/lvgl/mod.rs deleted file mode 100644 index 40fd5c10..00000000 --- a/packages/pros/src/lvgl/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod colors; diff --git a/packages/pros/src/vexos_env.rs b/packages/pros/src/vexos_env.rs deleted file mode 100644 index cdb622d4..00000000 --- a/packages/pros/src/vexos_env.rs +++ /dev/null @@ -1,14 +0,0 @@ -use core::alloc::{GlobalAlloc, Layout}; - -struct Allocator; -unsafe impl GlobalAlloc for Allocator { - unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - pros_sys::memalign(layout.align() as _, layout.size() as _) as *mut u8 - } - unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { - pros_sys::free(ptr as *mut core::ffi::c_void) - } -} - -#[global_allocator] -static ALLOCATOR: Allocator = Allocator; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0a44ea93..a2fae3f5 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly" -components = [ "rust-src" ] -targets = [ "armv7a-none-eabi" ] \ No newline at end of file +channel = "nightly-2024-02-07" +components = ["rust-src"] +targets = ["armv7a-none-eabi"]