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 e8f0d3e9..7e6b0afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,16 +21,32 @@ Before releasing: ## [Unreleased] +### Added + +### 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) @@ -40,6 +56,26 @@ Before releasing: - 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 @@ -48,6 +84,7 @@ Before releasing: - 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] @@ -66,6 +103,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) +- Support for ADI Expander modules with `AdiExpander`. (#63) ### Fixed @@ -150,8 +188,9 @@ Before releasing: ### Removed -[unreleased]: https://github.com/pros-rs/pros-rs/compare/v0.7.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/packages/pros-async/Cargo.toml b/packages/pros-async/Cargo.toml new file mode 100644 index 00000000..498b9af2 --- /dev/null +++ b/packages/pros-async/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "pros-async" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "A simple async executor for pros-rs" +keywords = ["PROS", "Robotics", "bindings", "async", "vex", "v5"] +categories = [ + "no-std", + "science::robotics", + "Asynchronous" +] +repository = "https://github.com/gavin-niederman/pros-rs" +authors = [ + "pros-rs", + "Gavin Niederman ", + "doinkythederp ", +] + +# 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-async/README.md b/packages/pros-async/README.md new file mode 100644 index 00000000..abc23d78 --- /dev/null +++ b/packages/pros-async/README.md @@ -0,0 +1,6 @@ +# pros-async + +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. +FreeRTOS tasks can still be used, but it is recommended to use only async tasks for performance. diff --git a/packages/pros/src/async_runtime/executor.rs b/packages/pros-async/src/executor.rs similarity index 98% rename from packages/pros/src/async_runtime/executor.rs rename to packages/pros-async/src/executor.rs index 24cfef37..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(); 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 100% rename from packages/pros/src/async_runtime/reactor.rs rename to packages/pros-async/src/reactor.rs diff --git a/packages/pros-core/Cargo.toml b/packages/pros-core/Cargo.toml new file mode 100644 index 00000000..ed6c827a --- /dev/null +++ b/packages/pros-core/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "pros-core" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Core functionality for pros-rs" +keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] +categories = [ + "api-bindings", + "no-std", + "science::robotics", +] +repository = "https://github.com/gavin-niederman/pros-rs" +authors = [ + "pros-rs", + "Gavin Niederman ", + "doinkythederp ", +] + +# 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/README.md b/packages/pros-core/README.md new file mode 100644 index 00000000..fd26132c --- /dev/null +++ b/packages/pros-core/README.md @@ -0,0 +1,10 @@ +# pros-core +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 +- Errno handling +- Serial terminal printing +- No-std `Instant`s +- Synchronization primitives +- FreeRTOS task management 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/src/vexos_env.rs b/packages/pros-core/src/allocator/vexos.rs similarity index 100% rename from packages/pros/src/vexos_env.rs rename to packages/pros-core/src/allocator/vexos.rs 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 88% rename from packages/pros/src/error.rs rename to packages/pros-core/src/error.rs index c619f01d..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,7 +84,6 @@ macro_rules! bail_on { val }}; } -pub(crate) use bail_on; use snafu::Snafu; /// A trait for converting an errno value into an error type. @@ -97,9 +101,12 @@ pub enum PortError { PortOutOfRange, /// 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/src/io/print_impl.rs b/packages/pros-core/src/io/mod.rs similarity index 92% rename from packages/pros/src/io/print_impl.rs rename to packages/pros-core/src/io/mod.rs index 6932b9f6..12166332 100644 --- a/packages/pros/src/io/print_impl.rs +++ b/packages/pros-core/src/io/mod.rs @@ -1,3 +1,5 @@ +//! 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) //! @@ -9,7 +11,7 @@ //! Exactly as you'd use `println!`, `eprintln!` and `dbg!`. //! //! ```rust -//! # use pros::io::print_impl::*; +//! # use pros::io::*; //! // Use the default ``-prefixed macros: //! # fn test1() //! # { @@ -24,7 +26,7 @@ //! Or you can import aliases to `std` names: //! //! ```rust -//! use pros::io::print_impl::{println, eprintln, dbg}; +//! use pros::io::{println, eprintln, dbg}; //! //! # fn test2() //! # { @@ -57,10 +59,13 @@ // 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); @@ -135,7 +140,7 @@ macro_rules! println { { #[allow(unused_must_use)] { - let mut stm = $crate::io::print_impl::__SerialWriter::new(false); + let mut stm = $crate::io::__SerialWriter::new(false); stm.write_fmt(format_args!($($arg)*)); stm.write_nl(); } @@ -155,7 +160,7 @@ macro_rules! print { { #[allow(unused_must_use)] { - let mut stm = $crate::io::print_impl::__SerialWriter::new(false); + let mut stm = $crate::io::__SerialWriter::new(false); stm.write_fmt(format_args!($($arg)*)); } } @@ -175,7 +180,7 @@ macro_rules! eprintln { { #[allow(unused_must_use)] { - let mut stm = $crate::io::print_impl::__SerialWriter::new(true); + let mut stm = $crate::io::__SerialWriter::new(true); stm.write_fmt(format_args!($($arg)*)); stm.write_nl(); } @@ -195,7 +200,7 @@ macro_rules! eprint { { #[allow(unused_must_use)] { - let mut stm = $crate::io::print_impl::__SerialWriter::new(true); + let mut stm = $crate::io::__SerialWriter::new(true); stm.write_fmt(format_args!($($arg)*)); } } diff --git a/packages/pros-core/src/lib.rs b/packages/pros-core/src/lib.rs new file mode 100644 index 00000000..67838b74 --- /dev/null +++ b/packages/pros-core/src/lib.rs @@ -0,0 +1,22 @@ +//! 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`] +//! - 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 100% rename from packages/pros/src/sync.rs rename to packages/pros-core/src/sync.rs diff --git a/packages/pros/src/task/local.rs b/packages/pros-core/src/task/local.rs similarity index 100% rename from packages/pros/src/task/local.rs rename to packages/pros-core/src/task/local.rs diff --git a/packages/pros/src/task/mod.rs b/packages/pros-core/src/task/mod.rs similarity index 88% rename from packages/pros/src/task/mod.rs rename to packages/pros-core/src/task/mod.rs index b815d868..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 @@ -205,7 +202,7 @@ pub enum TaskState { 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. @@ -299,7 +296,7 @@ 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) } } @@ -335,40 +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. -#[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 }, - } -} - /// Returns the task the function was called from. pub fn current() -> TaskHandle { unsafe { diff --git a/packages/pros/src/time.rs b/packages/pros-core/src/time.rs similarity index 100% rename from packages/pros/src/time.rs rename to packages/pros-core/src/time.rs diff --git a/packages/pros-devices/Cargo.toml b/packages/pros-devices/Cargo.toml new file mode 100644 index 00000000..6d49ac81 --- /dev/null +++ b/packages/pros-devices/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "pros-devices" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "High level device for pros-rs" +keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] +categories = [ + "api-bindings", + "no-std", + "science::robotics", +] +repository = "https://github.com/gavin-niederman/pros-rs" +authors = [ + "pros-rs", + "Gavin Niederman ", + "doinkythederp ", +] + +# 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-devices/README.md b/packages/pros-devices/README.md new file mode 100644 index 00000000..b5e924ee --- /dev/null +++ b/packages/pros-devices/README.md @@ -0,0 +1,14 @@ +# pros-devices + +Functionality for accessing hardware connected to the V5 brain. + +## Overview + +The V5 brain features 21 RJ9 4p4c connector ports (known as "Smart ports") for communicating with newer V5 peripherals, as well as six 3-wire ports with log-to-digital conversion capability for compatibility with legacy Cortex devices. This module provides access to both smart devices and ADI devices. + +## Organization + +- `smart` contains abstractions and types for smart port connected ices. +- `adi` contains abstractions for three wire ADI connected devices. +- `battery` provides functions for getting information about the battery. +- `controller` provides types for interacting with the V5 controller. 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 7ff05c41..1a4a83c9 100644 --- a/packages/pros/src/devices/adi/analog.rs +++ b/packages/pros-devices/src/adi/analog.rs @@ -1,20 +1,36 @@ -//! Analog input and output ADI devices. - +//! 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; -#[derive(Debug, Eq, PartialEq)] /// Generic analog input ADI device. +#[derive(Debug, Eq, PartialEq)] pub struct AdiAnalogIn { port: AdiPort, } impl AdiAnalogIn { /// Create a analog input from an ADI port. - pub const 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. @@ -41,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. /// @@ -73,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) } } @@ -104,42 +137,3 @@ impl AdiDevice for AdiAnalogIn { AdiDeviceType::AnalogIn } } - -#[derive(Debug, Eq, PartialEq)] -/// Generic analog output ADI device. -pub struct AdiAnalogOut { - port: AdiPort, -} - -impl AdiAnalogOut { - /// Create a analog output from an [`AdiPort`]. - pub const 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 96% rename from packages/pros/src/devices/adi/encoder.rs rename to packages/pros-devices/src/adi/encoder.rs index e4752532..ee0ae8f9 100644 --- a/packages/pros/src/devices/adi/encoder.rs +++ b/packages/pros-devices/src/adi/encoder.rs @@ -1,13 +1,13 @@ //! 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; -#[derive(Debug, Eq, PartialEq)] /// ADI encoder device. /// Requires two adi ports. +#[derive(Debug, Eq, PartialEq)] pub struct AdiEncoder { raw: ext_adi_encoder_t, port_top: AdiPort, @@ -49,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 74% rename from packages/pros/src/devices/adi/mod.rs rename to packages/pros-devices/src/adi/mod.rs index ec5531d7..9012d496 100644 --- a/packages/pros/src/devices/adi/mod.rs +++ b/packages/pros-devices/src/adi/mod.rs @@ -1,25 +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. @@ -43,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, @@ -73,10 +79,10 @@ 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() } } @@ -95,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; } @@ -105,24 +111,36 @@ pub trait AdiDevice { pub enum AdiDeviceType { /// Generic analog input. AnalogIn = pros_sys::adi::E_ADI_ANALOG_IN, - /// Generic analog output. - 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 gyro. + /// Cortex-era yaw-rate gyroscope. LegacyGyro = pros_sys::adi::E_ADI_LEGACY_GYRO, - /// Cortex era servo motor. + /// Cortex-era servo motor. LegacyServo = pros_sys::adi::E_ADI_LEGACY_SERVO, - /// PWM output. + + /// 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. + /// Cortex-era encoder. LegacyEncoder = pros_sys::E_ADI_LEGACY_ENCODER, - /// Cortex era ultrasonic sensor. + + /// Cortex-era ultrasonic sensor. LegacyUltrasonic = pros_sys::E_ADI_LEGACY_ULTRASONIC, } @@ -130,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), @@ -144,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), } } } @@ -161,21 +181,18 @@ pub enum AdiError { /// Another resource is currently trying to access the ADI. AlreadyInUse, - /// The port specified has been reconfigured or is not configured for digital input. - DigitalInputNotConfigured, + /// PROS returned an unrecognized device type. + UnknownDeviceType, - /// The port type specified is invalid, and cannot be used to configure a port. - InvalidConfigType, - - /// The port has already been configured. - AlreadyConfigured, - - /// The port specified is invalid. - InvalidPort, + /// The port specified has not been configured for the device type specified. + PortNotConfigured, /// 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))] /// An error occurred while interacting with a port. Port { @@ -187,7 +204,8 @@ pub enum AdiError { 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 70% rename from packages/pros/src/devices/adi/motor.rs rename to packages/pros-devices/src/adi/motor.rs index bdb52490..bda8d5df 100644 --- a/packages/pros/src/devices/adi/motor.rs +++ b/packages/pros-devices/src/adi/motor.rs @@ -1,9 +1,9 @@ //! 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. @@ -17,8 +17,13 @@ impl AdiMotor { 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(), @@ -26,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 92% rename from packages/pros/src/devices/adi/potentiometer.rs rename to packages/pros-devices/src/adi/potentiometer.rs index 8e33fdb0..80f6375a 100644 --- a/packages/pros/src/devices/adi/potentiometer.rs +++ b/packages/pros-devices/src/adi/potentiometer.rs @@ -1,9 +1,9 @@ //! ADI Potentiometer device. -use pros_sys::{adi_potentiometer_type_e_t, ext_adi_potentiometer_t, PROS_ERR}; +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. @@ -36,16 +36,16 @@ impl AdiPotentiometer { 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) } } 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 86% rename from packages/pros/src/devices/adi/ultrasonic.rs rename to packages/pros-devices/src/adi/ultrasonic.rs index 3a3a63ee..098a867a 100644 --- a/packages/pros/src/devices/adi/ultrasonic.rs +++ b/packages/pros-devices/src/adi/ultrasonic.rs @@ -1,9 +1,9 @@ //! 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. @@ -39,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 96% rename from packages/pros/src/devices/battery.rs rename to packages/pros-devices/src/battery.rs index f209bcef..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 { diff --git a/packages/pros/src/color.rs b/packages/pros-devices/src/color.rs similarity index 98% rename from packages/pros/src/color.rs rename to packages/pros-devices/src/color.rs index c4e1e140..94b3a276 100644 --- a/packages/pros/src/color.rs +++ b/packages/pros-devices/src/color.rs @@ -304,19 +304,19 @@ impl Rgb { 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 [`COLOR_DARK_GREY`]. + /// Alias to [`Self::SLATE_GRAY`]. pub const DARK_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_GREY); - /// Alias tp [`COLOR_DARK_SLATE_GREY`]. + /// Alias to [`Self::DARK_SLATE_GRAY`]. pub const DARK_SLATE_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_DARK_SLATE_GREY); - /// Alias to [`COLOR_DIM_GREY`]. + /// Alias to [`Self::DIM_GRAY`]. pub const DIM_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_DIM_GREY); - /// Alias to [`COLOR_GREY`]. + /// Alias to [`Self::GRAY`]. pub const GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_GREY); - /// Alias to [`COLOR_LIGHT_GREY`]. + /// Alias to [`Self::LIGHT_GRAY`]. pub const LIGHT_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_GREY); - /// Alias to [`COLOR_LIGHT_SLATE_GREY`]. + /// Alias to [`Self::LIGHT_SLATE_GRAY`]. pub const LIGHT_SLATE_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_LIGHT_SLATE_GREY); - /// Alias to [`COLOR_SLATE_GREY`]. + /// Alias to [`Self::SLATE_GREY`]. pub const SLATE_GREY: Rgb = Rgb::from_raw(pros_sys::COLOR_SLATE_GREY); const BITMASK: u32 = 0b11111111; diff --git a/packages/pros/src/competition.rs b/packages/pros-devices/src/competition.rs similarity index 99% rename from packages/pros/src/competition.rs rename to packages/pros-devices/src/competition.rs index 7c006d1e..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. diff --git a/packages/pros/src/devices/controller.rs b/packages/pros-devices/src/controller.rs similarity index 99% rename from packages/pros/src/devices/controller.rs rename to packages/pros-devices/src/controller.rs index 25cd7883..56b69521 100644 --- a/packages/pros/src/devices/controller.rs +++ b/packages/pros-devices/src/controller.rs @@ -5,11 +5,10 @@ use alloc::ffi::CString; +use pros_core::{bail_on, map_errno}; use pros_sys::{E_CONTROLLER_MASTER, E_CONTROLLER_PARTNER, PROS_ERR}; use snafu::Snafu; -use crate::error::{bail_on, map_errno}; - pub const CONTROLLER_MAX_LINE_LENGTH: usize = 14; pub const CONTROLLER_MAX_LINES: usize = 2; @@ -32,7 +31,6 @@ impl Button { }) == 1) } } - /// Holds whether or not the buttons on the controller are pressed or not #[derive(Debug, Eq, PartialEq)] pub struct Buttons { diff --git a/packages/pros-devices/src/lib.rs b/packages/pros-devices/src/lib.rs new file mode 100644 index 00000000..d3e19f4e --- /dev/null +++ b/packages/pros-devices/src/lib.rs @@ -0,0 +1,35 @@ +//! # pros-devices +//! +//! Functionality for accessing hardware connected to the V5 brain. +//! +//! ## Overview +//! +//! The V5 brain features 21 RJ9 4p4c connector ports (known as "Smart ports") for communicating with newer V5 peripherals, as well as six 3-wire ports with log-to-digital conversion capability for compatibility with legacy Cortex devices. This module provides access to both smart devices and ADI devices. +//! +//! ## Organization +//! +//! - [`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. +//! - [`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/src/devices/peripherals.rs b/packages/pros-devices/src/peripherals.rs similarity index 99% rename from packages/pros/src/devices/peripherals.rs rename to packages/pros-devices/src/peripherals.rs index fc3189ce..53177ed3 100644 --- a/packages/pros/src/devices/peripherals.rs +++ b/packages/pros-devices/src/peripherals.rs @@ -24,7 +24,7 @@ use core::sync::atomic::AtomicBool; -use crate::devices::{ +use crate::{ adi::AdiPort, controller::{Controller, ControllerId}, screen::Screen, @@ -33,12 +33,12 @@ use crate::devices::{ 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. +#[derive(Debug)] pub struct Peripherals { /// Brain screen pub screen: Screen, @@ -223,7 +223,7 @@ impl DynamicPeripherals { Some(unsafe { SmartPort::new(port_index as u8 + 1) }) } - /// Creates an [`AdiSlot`] only if one has not been created on the given slot before. + /// Creates an [`AdiPort`] only if one has not been created on the given slot before. /// /// # Panics /// diff --git a/packages/pros/src/devices/position.rs b/packages/pros-devices/src/position.rs similarity index 100% rename from packages/pros/src/devices/position.rs rename to packages/pros-devices/src/position.rs diff --git a/packages/pros/src/devices/screen.rs b/packages/pros-devices/src/screen.rs similarity index 92% rename from packages/pros/src/devices/screen.rs rename to packages/pros-devices/src/screen.rs index 34413e7e..f90a7548 100644 --- a/packages/pros/src/devices/screen.rs +++ b/packages/pros-devices/src/screen.rs @@ -5,13 +5,11 @@ 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}, - error::{bail_on, map_errno}, -}; +use crate::color::{IntoRgb, Rgb}; #[derive(Debug, Eq, PartialEq)] /// Represents the physical display on the V5 Brain. @@ -20,22 +18,12 @@ pub struct Screen { current_line: i16, } -/// The maximum number of lines that can be visible on the screen at once. -pub const SCREEN_MAX_VISIBLE_LINES: usize = 12; -/// The height of a single line of text on the screen. -pub const SCREEN_LINE_HEIGHT: i16 = 20; - -/// The horizontal resolution of the display. -pub const SCREEN_HORIZONTAL_RESOLUTION: i16 = 480; -/// The vertical resolution of the writable part of the display. -pub const SCREEN_VERTICAL_RESOLUTION: i16 = 240; - 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 > (SCREEN_MAX_VISIBLE_LINES as i16 - 2) { - self.scroll(0, SCREEN_LINE_HEIGHT) + 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; @@ -344,13 +332,25 @@ impl From for pros_sys::last_touch_e_t { } 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`] to register devices if possible. + /// hardware device. Prefer using [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible. pub unsafe fn new() -> Self { Self { current_line: 0, @@ -459,7 +459,8 @@ impl Screen { .into_iter() .map(|i| i.into_rgb().into()) .collect::>(); - let expected_size = ((x1 - x0) * (y1 - y0)) as usize; + // 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(), @@ -479,7 +480,7 @@ impl Screen { /// /// This function is internally used by the pros-rs panic handler for displaying /// panic messages graphically before exiting. - pub(crate) fn draw_error(&mut self, msg: &str) -> Result<(), ScreenError> { + 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; @@ -487,8 +488,8 @@ impl Screen { let error_box_rect = Rect::new( ERROR_BOX_MARGIN, ERROR_BOX_MARGIN, - SCREEN_HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN, - SCREEN_VERTICAL_RESOLUTION - ERROR_BOX_MARGIN, + Self::HORIZONTAL_RESOLUTION - ERROR_BOX_MARGIN, + Self::VERTICAL_RESOLUTION - ERROR_BOX_MARGIN, ); self.fill(&error_box_rect, Rgb::RED)?; @@ -508,7 +509,7 @@ impl Screen { buffer.as_str(), TextPosition::Point( ERROR_BOX_MARGIN + ERROR_BOX_PADDING, - ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * SCREEN_LINE_HEIGHT), + ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Self::LINE_HEIGHT), ), TextFormat::Small, ), @@ -525,7 +526,7 @@ impl Screen { buffer.as_str(), TextPosition::Point( ERROR_BOX_MARGIN + ERROR_BOX_PADDING, - ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * SCREEN_LINE_HEIGHT), + ERROR_BOX_MARGIN + ERROR_BOX_PADDING + (line * Self::LINE_HEIGHT), ), TextFormat::Small, ), diff --git a/packages/pros/src/devices/smart/distance.rs b/packages/pros-devices/src/smart/distance.rs similarity index 97% rename from packages/pros/src/devices/smart/distance.rs rename to packages/pros-devices/src/smart/distance.rs index c81f606c..37f55703 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. 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 98% rename from packages/pros/src/devices/smart/gps.rs rename to packages/pros-devices/src/smart/gps.rs index 3f3dd2fd..16e561b0 100644 --- a/packages/pros/src/devices/smart/gps.rs +++ b/packages/pros-devices/src/smart/gps.rs @@ -3,11 +3,11 @@ //! 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}; //TODO: Figure out what all the units are #[derive(Default, Debug, Clone, Copy, PartialEq)] diff --git a/packages/pros/src/devices/smart/imu.rs b/packages/pros-devices/src/smart/imu.rs similarity index 95% rename from packages/pros/src/devices/smart/imu.rs rename to packages/pros-devices/src/smart/imu.rs index 0b6b4f9c..b60c5ba6 100644 --- a/packages/pros/src/devices/smart/imu.rs +++ b/packages/pros-devices/src/smart/imu.rs @@ -6,19 +6,16 @@ use core::{ 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, -}; - -/// The timeout for the IMU to calibrate. -pub const IMU_RESET_TIMEOUT: Duration = Duration::from_secs(3); -/// The minimum data rate that you can set an IMU to. -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)] @@ -27,6 +24,12 @@ 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 const fn new(port: SmartPort) -> Self { Self { port } @@ -45,7 +48,7 @@ impl InertialSensor { /// 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 { @@ -237,10 +240,10 @@ impl InertialSensor { /// 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 { @@ -410,8 +413,8 @@ impl core::future::Future for InertialCalibrateFuture { 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()); @@ -431,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)); } diff --git a/packages/pros/src/devices/smart/link.rs b/packages/pros-devices/src/smart/link.rs similarity index 97% rename from packages/pros/src/devices/smart/link.rs rename to packages/pros-devices/src/smart/link.rs index 8ea262a9..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 { @@ -148,7 +152,7 @@ impl TxLink { 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}"))) } diff --git a/packages/pros/src/devices/smart/mod.rs b/packages/pros-devices/src/smart/mod.rs similarity index 96% rename from packages/pros/src/devices/smart/mod.rs rename to packages/pros-devices/src/smart/mod.rs index 5be21a60..e45ae7cf 100644 --- a/packages/pros/src/devices/smart/mod.rs +++ b/packages/pros-devices/src/smart/mod.rs @@ -13,7 +13,7 @@ //! //! 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. @@ -21,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; @@ -30,16 +31,16 @@ 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}; - /// Defines common functionality shared by all smart port devices. pub trait SmartDevice { /// Get the index of the [`SmartPort`] this device is registered on. @@ -107,7 +108,7 @@ impl SmartPort { /// 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. This violates rust's borrow checked guarantees. Prefer using - /// [`Peripherals`] to register devices if possible. + /// [`Peripherals`](crate::peripherals::Peripherals) to register devices if possible. /// /// # Examples /// diff --git a/packages/pros/src/devices/smart/motor.rs b/packages/pros-devices/src/smart/motor.rs similarity index 96% rename from packages/pros/src/devices/smart/motor.rs rename to packages/pros-devices/src/smart/motor.rs index f5bb9959..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)] @@ -348,7 +342,7 @@ pub struct MotorStoppedFuture<'a> { } 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<'_>, diff --git a/packages/pros/src/devices/smart/optical.rs b/packages/pros-devices/src/smart/optical.rs similarity index 92% rename from packages/pros/src/devices/smart/optical.rs rename to packages/pros-devices/src/smart/optical.rs index c80d9387..d529887d 100644 --- a/packages/pros/src/devices/smart/optical.rs +++ b/packages/pros-devices/src/smart/optical.rs @@ -2,19 +2,11 @@ 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}; - -/// 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; /// Represents a smart port configured as a V5 optical sensor #[derive(Debug, Eq, PartialEq)] @@ -24,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()`]. @@ -54,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 { @@ -83,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); } @@ -188,7 +189,7 @@ impl OpticalSensor { /// 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 { @@ -200,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 { @@ -351,7 +352,7 @@ pub enum OpticalError { /// 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. + /// See for more information. InvalidIntegrationTime, /// Gesture detection is not enabled for this sensor. diff --git a/packages/pros/src/devices/smart/rotation.rs b/packages/pros-devices/src/smart/rotation.rs similarity index 97% rename from packages/pros/src/devices/smart/rotation.rs rename to packages/pros-devices/src/smart/rotation.rs index 0be6e772..2988af2a 100644 --- a/packages/pros/src/devices/smart/rotation.rs +++ b/packages/pros-devices/src/smart/rotation.rs @@ -2,13 +2,11 @@ //! //! 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)] diff --git a/packages/pros/src/devices/smart/vision.rs b/packages/pros-devices/src/smart/vision.rs similarity index 98% rename from packages/pros/src/devices/smart/vision.rs rename to packages/pros-devices/src/smart/vision.rs index f37b8e82..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::{ - color::Rgb, - error::{bail_errno, bail_on, map_errno, PortError}, -}; +use crate::color::Rgb; /// Represents a vision sensor plugged into the vex. #[derive(Debug, Eq, PartialEq)] diff --git a/packages/pros/src/usd.rs b/packages/pros-devices/src/usd.rs similarity index 100% rename from packages/pros/src/usd.rs rename to packages/pros-devices/src/usd.rs diff --git a/packages/pros-math/Cargo.toml b/packages/pros-math/Cargo.toml new file mode 100644 index 00000000..e2a39aca --- /dev/null +++ b/packages/pros-math/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pros-math" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Commonly used mathematical formulas for pros-rs" +keywords = ["PROS", "Robotics", "vex", "v5"] +categories = [ + "no-std", + "science::robotics", + "Mathematics", +] +repository = "https://github.com/gavin-niederman/pros-rs" +authors = [ + "pros-rs", + "Gavin Niederman ", + "doinkythederp ", +] + +[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/README.md b/packages/pros-math/README.md new file mode 100644 index 00000000..ea2cda52 --- /dev/null +++ b/packages/pros-math/README.md @@ -0,0 +1,3 @@ +# pros-math + +Common mathematical formulas and models implemented for [`pros-rs`](https://crates.io/crates/pros). diff --git a/packages/pros/src/feedforward.rs b/packages/pros-math/src/feedforward.rs similarity index 74% rename from packages/pros/src/feedforward.rs rename to packages/pros-math/src/feedforward.rs index 8639b963..c8e8b3c9 100644 --- a/packages/pros/src/feedforward.rs +++ b/packages/pros-math/src/feedforward.rs @@ -1,9 +1,13 @@ +//! 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 FeedforwardMotorController { +pub struct MotorFeedforwardController { /// Feedforward constant for static friction compensation. pub ks: f32, /// Feedforward constant for velocity compensation. @@ -16,7 +20,7 @@ pub struct FeedforwardMotorController { pub target: f32, } -impl FeedforwardMotorController { +impl MotorFeedforwardController { /// Creates a new [`FeedforwardMotorController`] with the given constants and target. /// /// # Arguments @@ -34,26 +38,28 @@ impl FeedforwardMotorController { ks, kv, ka, + target_acceleration, + target: 0.0, } } - /// Updates the feedforward controller to calculate the control output. + /// 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 update(&self, target: f32, target_acceleration: f32) -> f32 { + pub fn calculate(&self, target: f32, target_acceleration: f32) -> f32 { // Calculate the feedforward component based on velocity and acceleration - let v = self.ks * target.signum() + self.kv * target + self.ka * target_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 79% rename from packages/pros/src/pid.rs rename to packages/pros-math/src/pid.rs index 4d8b37fe..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,19 +22,19 @@ 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 const fn new(kp: f32, ki: f32, kd: f32) -> Self { + 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, } @@ -40,19 +42,18 @@ impl PidController { /// 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 } @@ -60,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..39237284 --- /dev/null +++ b/packages/pros-panic/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "pros-panic" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Panic handler for pros-rs" +keywords = ["PROS", "Robotics", "vex", "v5"] +categories = [ + "no-std", + "science::robotics", +] +repository = "https://github.com/gavin-niederman/pros-rs" +authors = [ + "pros-rs", + "Gavin Niederman ", + "doinkythederp ", +] + +# 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/README.md b/packages/pros-panic/README.md new file mode 100644 index 00000000..9b485e8a --- /dev/null +++ b/packages/pros-panic/README.md @@ -0,0 +1,5 @@ +# pros-panic + +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. 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..e30b4f0b --- /dev/null +++ b/packages/pros-sync/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pros-sync" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "`SyncRobot` trait and macro for pros-rs" +keywords = ["PROS", "Robotics", "vex", "v5"] +categories = [ + "no-std", + "science::robotics", +] +repository = "https://github.com/gavin-niederman/pros-rs" +authors = [ + "pros-rs", + "Gavin Niederman ", + "doinkythederp ", +] + +# 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/README.md b/packages/pros-sync/README.md new file mode 100644 index 00000000..e65db59a --- /dev/null +++ b/packages/pros-sync/README.md @@ -0,0 +1,3 @@ +# pros-sync + +Synchronous robot code trait for [pros-rs](https://crates.io/crates/pros). 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 d9912b00..682b1784 100644 --- a/packages/pros-sys/Cargo.toml +++ b/packages/pros-sys/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "pros-sys" -version = "0.6.0" +version = "0.7.0" edition = "2021" description = "EFI for the PROS rust bindings" -keywords = ["PROS", "Robotics", "bindings"] +keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] categories = [ "api-bindings", "development-tools::ffi", diff --git a/packages/pros-sys/README.md b/packages/pros-sys/README.md index 2623d9de..9182522b 100644 --- a/packages/pros-sys/README.md +++ b/packages/pros-sys/README.md @@ -1,7 +1,7 @@ # Pros-sys - EFI for Rust PROS bindings, used in [pros-rs](https://crates.io/crates/pros) +EFI for Rust PROS bindings, used in [pros-rs](https://crates.io/crates/pros) ## This project is still very early in development - Make sure to check out the todo list [(TODO.md)](../TODO.md) +Make sure to check out the todo list [(TODO.md)](../TODO.md) 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/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/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 702142b2..4740916b 100644 --- a/packages/pros/Cargo.toml +++ b/packages/pros/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "pros" -version = "0.7.0" +version = "0.8.0" edition = "2021" description = "Rust bindings for PROS" -keywords = ["PROS", "Robotics", "bindings"] +keywords = ["PROS", "Robotics", "bindings", "vex", "v5"] categories = ["os", "api-bindings", "no-std", "science::robotics"] license = "MIT" repository = "https://github.com/pros-rs/pros-rs" @@ -18,17 +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.6", 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"] } -async-task = { version = "4.5.0", default-features = false } -waker-fn = "1.1.1" - -[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 b2e74b96..fb19faba 100644 --- a/packages/pros/examples/accessories.rs +++ b/packages/pros/examples/accessories.rs @@ -7,14 +7,12 @@ use alloc::sync::Arc; use core::time::Duration; use pros::{ - color::Rgb, + core::sync::Mutex, devices::{ smart::vision::{LedMode, VisionZeroPoint}, Controller, }, prelude::*, - sync::Mutex, - task::delay, }; struct ExampleRobot { @@ -33,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!"); @@ -52,7 +50,7 @@ impl AsyncRobot for ExampleRobot { self.vision.set_led(LedMode::On(Rgb::new(0, 0, 255))); // 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 { 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/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 index 04d692f4..d6daa3e2 100644 --- a/packages/pros/examples/screen.rs +++ b/packages/pros/examples/screen.rs @@ -18,7 +18,7 @@ impl Robot { } impl AsyncRobot for Robot { - async fn opcontrol(&mut self) -> pros::Result { + 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)?; 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 070f00d9..00000000 --- a/packages/pros/src/devices/adi/digital.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Digital input and output ADI devices - -use pros_sys::PROS_ERR; - -use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; - -#[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 const 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)] -/// Generic digital output ADI device. -pub struct AdiDigitalOut { - port: AdiPort, -} - -impl AdiDigitalOut { - /// Create a digital output from an [`AdiPort`]. - pub const 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 0cc392a7..00000000 --- a/packages/pros/src/devices/adi/gyro.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! ADI gyro device. - -use pros_sys::{ext_adi_gyro_t, PROS_ERR}; - -use super::{AdiDevice, AdiDeviceType, AdiError, AdiPort}; -use crate::error::bail_on; - -#[derive(Debug, Eq, PartialEq)] -/// ADI gyro device. -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/mod.rs b/packages/pros/src/devices/mod.rs deleted file mode 100644 index 700a576f..00000000 --- a/packages/pros/src/devices/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Devices -//! -//! This module provides functionality for accessing hardware and devices connected to the V5 brain. -//! -//! # Overview -//! -//! 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 -//! battery. -//! - [`devices::controller`] provides types for interacting with the V5 controller. - -pub mod adi; -pub mod smart; - -pub mod battery; -pub mod controller; -pub mod peripherals; -pub mod position; -pub mod screen; - -pub use controller::Controller; -pub use position::Position; -pub use screen::Screen; diff --git a/packages/pros/src/io/mod.rs b/packages/pros/src/io/mod.rs deleted file mode 100644 index 58db33a1..00000000 --- a/packages/pros/src/io/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Helpers for terminal I/O functionality. - -pub mod print_impl; - -pub use no_std_io::io::*; - -pub use crate::{dbg, eprint, eprintln, print, println}; diff --git a/packages/pros/src/lib.rs b/packages/pros/src/lib.rs index 51bdec2a..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,369 +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] -#![warn( - missing_docs, - rust_2018_idioms, - missing_debug_implementations, - unsafe_op_in_unsafe_fn, - clippy::missing_const_for_fn -)] - -extern crate alloc; - -pub mod async_runtime; -pub mod devices; -pub mod error; -pub mod pid; -pub mod sync; -#[macro_use] -pub mod task; -pub mod panic; - -#[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 competition; -pub mod color; -pub mod io; -pub mod time; -pub mod usd; - -use core::future::Future; - -/// A result type that makes returning errors easier. -pub type Result = core::result::Result>; - -/// 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`](prelude::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(()) } - } -} - -/// A trait for robot code that runs without the async executor spun up. -/// This trait isn't recommended. See [`AsyncRobot`] 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`](prelude::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(); - } - }; -} - -#[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() { - unsafe { - ROBOT = Some(Default::default()); - } - } - }; - ($rbt:ty, $init:expr) => { - $crate::__gen_async_exports!($rbt); - - #[no_mangle] - extern "C" fn initialize() { - 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() { - unsafe { - ROBOT = Some(Default::default()); - } - } - }; - ($rbt:ty, $init:expr) => { - $crate::__gen_sync_exports!($rbt); - - #[no_mangle] - extern "C" fn initialize() { - unsafe { - ROBOT = Some($init); - } - } - }; -} +#[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::*, + #[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, - 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, - screen::{Circle, Line, Rect, Screen, Text, TextFormat, TextPosition, TouchState}, - smart::{ - distance::DistanceSensor, - gps::GpsSensor, - imu::InertialSensor, - link::{Link, RxLink, TxLink}, - motor::{BrakeMode, Gearset, Motor}, - optical::OpticalSensor, - rotation::RotationSensor, - vision::VisionSensor, - SmartDevice, SmartPort, - }, + 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, eprintln, print, println, BufRead, Read, Seek, Write}, - 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/panic.rs b/packages/pros/src/panic.rs deleted file mode 100644 index ebfb9bd4..00000000 --- a/packages/pros/src/panic.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Panic Handler -//! -//! This module contains the internal `#[panic_handler]` used by pros-rs for -//! dealing with unrecoverable program errors. - -use alloc::format; - -use crate::{devices::screen::Screen, io::eprintln, task}; - -#[panic_handler] -/// The panic handler for pros-rs. -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 - let msg = format!("task '{task_name}' {info}"); - - eprintln!("{msg}"); - - unsafe { - Screen::new().draw_error(&msg).unwrap_or_else(|err| { - eprintln!("Failed to draw error message to screen: {err}"); - }); - - #[cfg(target_arch = "wasm32")] - crate::wasm_env::sim_log_backtrace(); - - pros_sys::exit(1); - } -} 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"]