diff --git a/CHANGELOG.md b/CHANGELOG.md index a8942371..af93c6e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ Before releasing: - Added support for getting motor fault flags (e.g. over-temperature, over-current, H-bridge faults). - Added support for internal motor PID tuning. Feature gated behind `dangerous_motor_tuning`, as this can cause hardware damage and is not recommended. - Added various constants for convenience around `Motor` and `Gearset`. +- An ``embedded_graphics`` driver in the new `pros_graphics` crate. +- New `graphics` and `embedded-graphics` features in the `pros` crate. ### Fixed diff --git a/flake.lock b/flake.lock index 6fded360..46ebd0b4 100644 --- a/flake.lock +++ b/flake.lock @@ -3,14 +3,15 @@ "cargo-pros": { "inputs": { "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "pros-cli-nix": "pros-cli-nix" }, "locked": { - "lastModified": 1703362525, - "narHash": "sha256-QPFRaWCDlHKyFu4P4HCsw8pKkJcznup0NIwOjGkkI7g=", + "lastModified": 1709775910, + "narHash": "sha256-PF60KNzSX+Z2zrk1KLAPtrzsnoW7A9k8r60zmZbsyQ4=", "owner": "pros-rs", "repo": "cargo-pros", - "rev": "479331022e4a74a62eaa1f776325c690e449f5dd", + "rev": "fc8d1f336cd09314b69a7810a59e0c0e4d65a252", "type": "github" }, "original": { @@ -36,16 +37,33 @@ "type": "indirect" } }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1688254665, + "narHash": "sha256-8FHEgBrr7gYNiS/NzCxIO3m4hvtLRW9YY1nYo1ivm3o=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "267149c58a14d15f7f81b4d737308421de9d7152", + "type": "github" + }, + "original": { + "id": "flake-parts", + "type": "indirect" + } + }, "flake-utils": { "inputs": { "systems": "systems" }, "locked": { - "lastModified": 1692799911, - "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", + "lastModified": 1709126324, + "narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=", "owner": "numtide", "repo": "flake-utils", - "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", + "rev": "d465f4819400de7c8d874d50b982301f28a84605", "type": "github" }, "original": { @@ -59,11 +77,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -74,11 +92,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1694062546, - "narHash": "sha256-PiGI4f2BGnZcedP6slLjCLGLRLXPa9+ogGGgVPfGxys=", + "lastModified": 1709675310, + "narHash": "sha256-w61tqFEmuJ+/1rAwU7nkYZ+dN6sLwyobfLwX2Yn42FE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b200e0df08f80c32974a6108ce431d8a8a5e6547", + "rev": "43d259f8d726113fac056e8bb17d5ac2dea3e0a8", "type": "github" }, "original": { @@ -104,21 +122,53 @@ "type": "github" } }, + "nixpkgs-lib_2": { + "locked": { + "dir": "lib", + "lastModified": 1688049487, + "narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs_2": { "locked": { - "lastModified": 1681358109, - "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", + "lastModified": 1688231357, + "narHash": "sha256-ZOn16X5jZ6X5ror58gOJAxPfFLAQhZJ6nOUeS4tfFwo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", + "rev": "645ff62e09d294a30de823cb568e9c6d68e92606", "type": "github" }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 0, + "narHash": "sha256-n6F0n8UV6lnTZbYPl1A9q1BS0p4hduAv1mGAP17CVd0=", + "path": "/nix/store/bjvqq8c79dbi59g7xzcc6lhl0f19m3d7-source", + "type": "path" + }, "original": { "id": "nixpkgs", "type": "indirect" } }, - "nixpkgs_3": { + "nixpkgs_4": { "locked": { "lastModified": 1688231357, "narHash": "sha256-ZOn16X5jZ6X5ror58gOJAxPfFLAQhZJ6nOUeS4tfFwo=", @@ -137,14 +187,33 @@ "pros-cli-nix": { "inputs": { "flake-parts": "flake-parts", - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1691848327, - "narHash": "sha256-bGijqyZb+orLxmY541hUdVJKa44VB6iV2Aviwn5Ji5k=", + "lastModified": 1706024690, + "narHash": "sha256-SVzHiRW6edEhN/etZvG6T6h54z+LySxNWOi179ly8/4=", "owner": "BattleCh1cken", "repo": "pros-cli-nix", - "rev": "4c00d8e3c6caa377e67ce0cf84923feba5533f23", + "rev": "3c2c47944735013c6252b16e086534b76d55bf36", + "type": "github" + }, + "original": { + "owner": "BattleCh1cken", + "repo": "pros-cli-nix", + "type": "github" + } + }, + "pros-cli-nix_2": { + "inputs": { + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_4" + }, + "locked": { + "lastModified": 1706024690, + "narHash": "sha256-SVzHiRW6edEhN/etZvG6T6h54z+LySxNWOi179ly8/4=", + "owner": "BattleCh1cken", + "repo": "pros-cli-nix", + "rev": "3c2c47944735013c6252b16e086534b76d55bf36", "type": "github" }, "original": { @@ -157,8 +226,8 @@ "inputs": { "cargo-pros": "cargo-pros", "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2", - "pros-cli-nix": "pros-cli-nix" + "nixpkgs": "nixpkgs_3", + "pros-cli-nix": "pros-cli-nix_2" } }, "systems": { diff --git a/packages/pros-graphics/Cargo.toml b/packages/pros-graphics/Cargo.toml new file mode 100644 index 00000000..9a8935fb --- /dev/null +++ b/packages/pros-graphics/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pros-graphics" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Core functionality for pros-rs" +keywords = ["PROS", "Robotics", "graphics", "vex", "v5"] +categories = [ + "api-bindings", + "no-std", + "science::robotics", +] + +[dependencies] +embedded-graphics-core = { version = "0.4.0", optional = true } +pros-devices = { version = "0.1.0", path = "../pros-devices" } +pros-sys = { version = "0.7.0", path = "../pros-sys" } + +[lints] +workspace = true + +[features] +default = [] +embedded-graphics = ["dep:embedded-graphics-core"] diff --git a/packages/pros-graphics/src/embedded_graphics.rs b/packages/pros-graphics/src/embedded_graphics.rs new file mode 100644 index 00000000..0e74a0b3 --- /dev/null +++ b/packages/pros-graphics/src/embedded_graphics.rs @@ -0,0 +1,94 @@ +//! An embedded_graphics driver for VEX V5 Brain displays. + +use alloc::boxed::Box; + +use embedded_graphics_core::{ + draw_target::DrawTarget, + geometry::{Dimensions, Point, Size}, + pixelcolor::{Rgb888, RgbColor}, + primitives::Rectangle, + Pixel, +}; +use pros_devices::{color::Rgb, Screen}; + +/// An embedded_graphics driver for the V5 Brain display +pub struct V5BrainDisplay { + pixel_buffer: + Box<[u32; Screen::HORIZONTAL_RESOLUTION as usize * Screen::VERTICAL_RESOLUTION as usize]>, +} + +impl V5BrainDisplay { + /// Creates a new VexDisplay from a Screen + pub fn new(_screen: Screen) -> Self { + let pixel_buffer = Box::new_zeroed(); + let pixel_buffer = unsafe { pixel_buffer.assume_init() }; + + Self { pixel_buffer } + } + + /// Draws the pixel buffer to the screen + /// + /// # Note + /// + /// I would use the [`Screen::draw_buffer`](pros_devices::screen::Screen::draw_buffer) API, + /// but unfortunately it stack overflows with a buffer this big and is more complicated. + fn draw_buffer(&self) { + // SAFETY: The pixel buffer is guarenteed to be large enough and live long enough and we take ownership of the screen when created. + unsafe { + pros_sys::screen_copy_area( + 0, + 0, + Screen::HORIZONTAL_RESOLUTION, + Screen::VERTICAL_RESOLUTION, + self.pixel_buffer.as_ptr(), + Screen::HORIZONTAL_RESOLUTION as _, + ); + } + } +} + +impl From for V5BrainDisplay { + fn from(value: Screen) -> Self { + Self::new(value) + } +} + +impl Dimensions for V5BrainDisplay { + fn bounding_box(&self) -> Rectangle { + Rectangle::new( + Point::new(0, 0), + Size::new( + Screen::HORIZONTAL_RESOLUTION as _, + Screen::VERTICAL_RESOLUTION as _, + ), + ) + } +} + +impl DrawTarget for V5BrainDisplay { + type Color = Rgb888; + type Error = pros_devices::screen::ScreenError; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + pixels + .into_iter() + .map(|pixel| (pixel.0, Rgb::new(pixel.1.r(), pixel.1.g(), pixel.1.b()))) + .for_each(|(pos, color)| { + // Make sure that the coordinate is valid to index with. + if !(pos.x > Screen::HORIZONTAL_RESOLUTION as _ || pos.x < 0) + && !(pos.y > Screen::VERTICAL_RESOLUTION as _ || pos.y < 0) + { + // SAFETY: We initialize the buffer with zeroes, so it's safe to assume it's initialized. + self.pixel_buffer[pos.y as usize * Screen::HORIZONTAL_RESOLUTION as usize + + pos.x as usize] = color.into(); + } + }); + + self.draw_buffer(); + + Ok(()) + } +} diff --git a/packages/pros-graphics/src/lib.rs b/packages/pros-graphics/src/lib.rs new file mode 100644 index 00000000..aaec18cf --- /dev/null +++ b/packages/pros-graphics/src/lib.rs @@ -0,0 +1,13 @@ +//! Graphics driver implementations for the V5 Brain display. +//! +//! Currently supports: +//! - [embedded-graphics](https://crates.io/crates/embedded-graphics) +//! Implemented for the [`pros-rs`](https://crates.io/crates/pros) ecosystem and implemented using [pros-devices](https://crates.io/crates/pros-devices). +#![no_std] +#![cfg_attr(feature = "embedded-graphics", feature(new_uninit))] + +#[cfg(feature = "embedded-graphics")] +extern crate alloc; + +#[cfg(feature = "embedded-graphics")] +pub mod embedded_graphics; diff --git a/packages/pros/Cargo.toml b/packages/pros/Cargo.toml index 97e368fc..306848c1 100644 --- a/packages/pros/Cargo.toml +++ b/packages/pros/Cargo.toml @@ -21,6 +21,7 @@ rust-version = "1.75.0" 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-graphics = { version = "0.1.0", path = "../pros-graphics", 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 } @@ -36,6 +37,9 @@ sync = ["dep:pros-sync"] devices = ["dep:pros-devices"] +graphics = ["dep:pros-graphics"] +embedded-graphics = ["pros-graphics/embedded-graphics"] + math = ["dep:pros-math"] panic = ["dep:pros-panic"] diff --git a/packages/pros/src/lib.rs b/packages/pros/src/lib.rs index e14accd7..014569b2 100644 --- a/packages/pros/src/lib.rs +++ b/packages/pros/src/lib.rs @@ -61,6 +61,8 @@ pub use pros_async as async_runtime; pub use pros_core as core; #[cfg(feature = "devices")] pub use pros_devices as devices; +#[cfg(feature = "graphics")] +pub use pros_graphics as graphics; #[cfg(feature = "math")] pub use pros_math as math; #[cfg(feature = "panic")] @@ -82,6 +84,10 @@ pub mod prelude { print, println, task::delay, }; + #[cfg(all(not(feature = "graphics"), feature = "devices"))] + pub use pros_devices::screen::{ + Circle, Line, Rect, Text, TextFormat, TextPosition, TouchState, + }; #[cfg(feature = "devices")] pub use pros_devices::{ adi::{ @@ -99,7 +105,7 @@ pub mod prelude { color::Rgb, peripherals::{DynamicPeripherals, Peripherals}, position::Position, - screen::{Circle, Line, Rect, Screen, Text, TextFormat, TextPosition, TouchState}, + screen::Screen, smart::{ distance::DistanceSensor, expander::AdiExpander, @@ -113,6 +119,8 @@ pub mod prelude { SmartDevice, SmartPort, }, }; + #[cfg(all(feature = "graphics", feature = "embedded-graphics"))] + pub use pros_graphics::embedded_graphics::V5BrainDisplay; #[cfg(feature = "math")] pub use pros_math::{feedforward::MotorFeedforwardController, pid::PidController}; #[cfg(feature = "sync")]