From 66fdd992d5f18b53b4d06b07cfecb77a0f4b62b6 Mon Sep 17 00:00:00 2001 From: CanadianBaconBoi Date: Fri, 9 May 2025 16:58:53 +0200 Subject: [PATCH] Initial Commit --- .cargo/config.toml | 16 +++++ .gitignore | 4 ++ .idea/.gitignore | 8 +++ .idea/docker-watcher-esp.iml | 11 ++++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 ++ Cargo.toml | 35 +++++++++++ build.rs | 3 + diagram.json | 34 ++++++++++ rust-toolchain.toml | 2 + sdkconfig.defaults | 10 +++ src/display.rs | 3 + src/display/screens.rs | 6 ++ src/display/screens/base.rs | 77 +++++++++++++++++++++++ src/display/screens/error.rs | 27 ++++++++ src/display/screens/home.rs | 26 ++++++++ src/display/screens/macros.rs | 71 +++++++++++++++++++++ src/display/screens/prelude.rs | 9 +++ src/display/screens/success.rs | 18 ++++++ src/display/theme.rs | 63 +++++++++++++++++++ src/display/tools.rs | 72 +++++++++++++++++++++ src/ferris.raw | Bin 0 -> 11008 bytes src/main.rs | 111 +++++++++++++++++++++++++++++++++ wokwi.toml | 5 ++ 24 files changed, 625 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/docker-watcher-esp.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 diagram.json create mode 100644 rust-toolchain.toml create mode 100644 sdkconfig.defaults create mode 100644 src/display.rs create mode 100644 src/display/screens.rs create mode 100644 src/display/screens/base.rs create mode 100644 src/display/screens/error.rs create mode 100644 src/display/screens/home.rs create mode 100644 src/display/screens/macros.rs create mode 100644 src/display/screens/prelude.rs create mode 100644 src/display/screens/success.rs create mode 100644 src/display/theme.rs create mode 100644 src/display/tools.rs create mode 100644 src/ferris.raw create mode 100644 src/main.rs create mode 100644 wokwi.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a798062 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,16 @@ +[build] +target = "xtensa-esp32-espidf" + +[target.xtensa-esp32-espidf] +linker = "ldproxy" +runner = "espflash flash --monitor --baud=921600" +rustflags = [ "--cfg", "espidf_time64"] + +[unstable] +build-std = ["std", "panic_abort"] + +[env] +MCU="esp32" +# Note: this variable is not used by the pio builder (`cargo build --features pio`) +ESP_IDF_VERSION = "v5.3.2" + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73a638b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.vscode +/.embuild +/target +/Cargo.lock diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/docker-watcher-esp.iml b/.idea/docker-watcher-esp.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/docker-watcher-esp.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5aba204 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..63a6d22 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "docker-watcher-esp" +version = "0.1.0" +authors = ["CanadianBaconBoi "] +edition = "2021" +resolver = "2" +rust-version = "1.77" + +[[bin]] +name = "docker-watcher-esp" +harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors + +[profile.release] +opt-level = "s" + +[profile.dev] +debug = true # Symbols are nice and they don't increase the size on Flash +opt-level = "z" + +[features] +default = [] + +experimental = ["esp-idf-svc/experimental"] + +[dependencies] +log = "0.4" +esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync"] } +mipidsi = "0.9.0" +anyhow = "1.0.98" +embedded-graphics = "0.8.1" +embedded-hal = "1.0.0" + +[build-dependencies] +embuild = "0.33" + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..112ec3f --- /dev/null +++ b/build.rs @@ -0,0 +1,3 @@ +fn main() { + embuild::espidf::sysenv::output(); +} diff --git a/diagram.json b/diagram.json new file mode 100644 index 0000000..b9035cf --- /dev/null +++ b/diagram.json @@ -0,0 +1,34 @@ +{ + "version": 1, + "editor": "wokwi", + "author": "CanadianBaconBoi ", + "parts": [ + { + "type": "board-esp32-devkit-c-v4", + "id": "esp", + "top": 0.59, + "left": 0.67, + "attrs": { + "flashSize": "16" + } + } + ], + "connections": [ + [ + "esp:TX", + "$serialMonitor:RX", + "", + [] + ], + [ + "esp:RX", + "$serialMonitor:TX", + "", + [] + ] + ], + "serialMonitor": { + "display": "terminal", + "convertEol": true + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a2f5ab5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "esp" diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..c25b89d --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 + +# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). +# This allows to use 1 ms granularity for thread sleeps (10 ms by default). +#CONFIG_FREERTOS_HZ=1000 + +# Workaround for https://github.com/espressif/esp-idf/issues/7631 +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..b7ae37a --- /dev/null +++ b/src/display.rs @@ -0,0 +1,3 @@ +pub mod theme; +pub mod screens; +pub mod tools; \ No newline at end of file diff --git a/src/display/screens.rs b/src/display/screens.rs new file mode 100644 index 0000000..44a06b2 --- /dev/null +++ b/src/display/screens.rs @@ -0,0 +1,6 @@ +pub mod base; +pub mod home; +pub mod success; +pub mod error; +pub mod macros; +mod prelude; \ No newline at end of file diff --git a/src/display/screens/base.rs b/src/display/screens/base.rs new file mode 100644 index 0000000..cdab2da --- /dev/null +++ b/src/display/screens/base.rs @@ -0,0 +1,77 @@ +use embedded_graphics::geometry::{Point, Size}; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::primitives::CornerRadii; +use embedded_hal::digital::OutputPin; +use mipidsi::interface::InterfacePixelFormat; +use crate::display::tools::DrawTools; +use crate::{W, H}; +use crate::display::theme::MACCHIATO; + +pub trait DisplayScreen +where + DI: mipidsi::interface::Interface, + M: mipidsi::models::Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin, +{ + fn show(index: &mut usize, frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display) -> anyhow::Result<(), DI::Error>; + fn input(button: ButtonPosition) -> anyhow::Result<(), DI::Error> { + Ok(()) + } +} + +pub struct StandardBase; +impl DisplayScreen for StandardBase +where + DI: mipidsi::interface::Interface, + M: mipidsi::models::Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin, +{ + fn show(_index: &mut usize, _frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display) -> anyhow::Result<(), DI::Error> { + if *dirty { + display.draw_rectangle_bounds(Point::new(0, 0), Point::new(W, H), MACCHIATO.crust)?; + display.draw_rounded_rectangle_bounds(Point::new(10, 10), Point::new(W -10, H -10), MACCHIATO.mantle, CornerRadii::new(Size::new(10, 10)))?; + } + Ok(()) + } +} + +pub struct ErrorBase; +impl DisplayScreen for ErrorBase +where + DI: mipidsi::interface::Interface, + M: mipidsi::models::Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin, +{ + fn show(_index: &mut usize, _frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display) -> anyhow::Result<(), DI::Error> { + if *dirty { + display.draw_rectangle_bounds(Point::new(0, 0), Point::new(W, H), MACCHIATO.maroon)?; + display.draw_rounded_rectangle_bounds(Point::new(10, 10), Point::new(W -10, H -10), MACCHIATO.mantle, CornerRadii::new(Size::new(10, 10)))?; + } + Ok(()) + } +} + +pub struct SuccessBase; +impl DisplayScreen for SuccessBase +where + DI: mipidsi::interface::Interface, + M: mipidsi::models::Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin, +{ + fn show(_index: &mut usize, _frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display) -> anyhow::Result<(), DI::Error> { + if *dirty { + display.draw_rectangle_bounds(Point::new(0, 0), Point::new(W, H), MACCHIATO.green)?; + display.draw_rounded_rectangle_bounds(Point::new(10, 10), Point::new(W -10, H -10), MACCHIATO.mantle, CornerRadii::new(Size::new(10, 10)))?; + } + Ok(()) + } +} + +pub enum ButtonPosition { + Up, + Down +} \ No newline at end of file diff --git a/src/display/screens/error.rs b/src/display/screens/error.rs new file mode 100644 index 0000000..495a783 --- /dev/null +++ b/src/display/screens/error.rs @@ -0,0 +1,27 @@ +use crate::{error_screen, FRAMERATE, H, W}; +use crate::display::screens::base::{ButtonPosition, ErrorBase}; +use crate::DisplayScreen; +use mipidsi::models::Model; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::Point; +use mipidsi::interface::InterfacePixelFormat; +use embedded_hal::digital::OutputPin; +use crate::display::tools::DrawTools; + +error_screen!(ErrorScreen, |index: &mut usize, frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display<_, _, _>|->anyhow::Result<(), _> { + if *dirty { + display.draw_text_centered(Point::new(W/2, H/2), "Error!")?; + *dirty = false; + } else if *frame_counter % FRAMERATE*4 == 0 { + #[cfg(debug_assertions)] + println!("Switching to screen {}", *index + 1); + + *index += 1; + *frame_counter = 0; + *dirty = true; + } + return Ok(()) +}, |button: ButtonPosition|->anyhow::Result<(), _> { + Ok(()) +} +); \ No newline at end of file diff --git a/src/display/screens/home.rs b/src/display/screens/home.rs new file mode 100644 index 0000000..2bb58eb --- /dev/null +++ b/src/display/screens/home.rs @@ -0,0 +1,26 @@ +use embedded_graphics::geometry::Point; +use crate::{standard_screen, FRAMERATE, H, W}; +use crate::display::screens::base::{ButtonPosition, StandardBase}; +use crate::DisplayScreen; +use mipidsi::models::Model; +use embedded_graphics::pixelcolor::Rgb565; +use mipidsi::interface::InterfacePixelFormat; +use embedded_hal::digital::OutputPin; +use crate::display::tools::DrawTools; + +standard_screen!(StandardScreen, |index: &mut usize, frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display<_, _, _>|->anyhow::Result<(), _> { + if *dirty { + display.draw_text_centered(Point::new(W/2, H/2), "Standard!")?; + *dirty = false; + } else if *frame_counter % FRAMERATE*4 == 0 { + #[cfg(debug_assertions)] + println!("Switching to screen {}", *index + 1); + + *index += 1; + *frame_counter = 0; + *dirty = true; + } + return Ok(()) +}, |button: ButtonPosition|->anyhow::Result<(), _> { + Ok(()) +}); \ No newline at end of file diff --git a/src/display/screens/macros.rs b/src/display/screens/macros.rs new file mode 100644 index 0000000..c97682f --- /dev/null +++ b/src/display/screens/macros.rs @@ -0,0 +1,71 @@ +#[macro_export] +macro_rules! standard_screen { + ( $name: ident, $show: expr, $input: expr ) => { + pub struct $name; + impl DisplayScreen for $name + where + DI: mipidsi::interface::Interface, + M: Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin { + #[inline] + fn show(idex: &mut usize, frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display) -> anyhow::Result<(), DI::Error> { + StandardBase::show(idex, frame_counter, dirty, display)?; + $show(idex, frame_counter, dirty, display) + } + + #[inline] + fn input(button: ButtonPosition) { + $input(button); + } + } + }; +} + +#[macro_export] +macro_rules! error_screen { + ( $name: ident, $show: expr, $input: expr ) => { + pub struct $name; + impl DisplayScreen for $name + where + DI: mipidsi::interface::Interface, + M: Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin { + #[inline] + fn show(idex: &mut usize, frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display) -> anyhow::Result<(), DI::Error> { + ErrorBase::show(idex, frame_counter, dirty, display)?; + $show(idex, frame_counter, dirty, display) + } + + #[inline] + fn input(button: ButtonPosition) { + $input(button); + } + } + }; +} + +#[macro_export] +macro_rules! success_screen { + ( $name: ident, $show: expr, $input: expr ) => { + pub struct $name; + impl DisplayScreen for $name + where + DI: mipidsi::interface::Interface, + M: Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin { + #[inline] + fn show(idex: &mut usize, frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display) -> anyhow::Result<(), DI::Error> { + SuccessBase::show(idex, frame_counter, dirty, display)?; + $show(idex, frame_counter, dirty, display) + } + + #[inline] + fn input(button: ButtonPosition) { + $input(button); + } + } + }; +} \ No newline at end of file diff --git a/src/display/screens/prelude.rs b/src/display/screens/prelude.rs new file mode 100644 index 0000000..62db7e0 --- /dev/null +++ b/src/display/screens/prelude.rs @@ -0,0 +1,9 @@ +use embedded_graphics::geometry::Point; +use crate::{success_screen, FRAMERATE, H, W}; +use crate::display::screens::base::{ButtonPosition, SuccessBase}; +use crate::DisplayScreen; +use mipidsi::models::Model; +use embedded_graphics::pixelcolor::Rgb565; +use mipidsi::interface::InterfacePixelFormat; +use embedded_hal::digital::OutputPin; +use crate::display::tools::DrawTools; \ No newline at end of file diff --git a/src/display/screens/success.rs b/src/display/screens/success.rs new file mode 100644 index 0000000..ef4b4ec --- /dev/null +++ b/src/display/screens/success.rs @@ -0,0 +1,18 @@ +use crate::display::screens::prelude; + +success_screen!(SuccessScreen, |index: &mut usize, frame_counter: &mut usize, dirty: &mut bool, display: &mut mipidsi::Display<_, _, _>|->anyhow::Result<(), _> { + if *dirty { + display.draw_text_centered(Point::new(W/2, H/2), "Success!")?; + *dirty = false; + } else if *frame_counter % FRAMERATE*4 == 0 { + #[cfg(debug_assertions)] + println!("Switching to screen {}", *index + 1); + + *index += 1; + *frame_counter = 0; + *dirty = true; + } + return Ok(()) +}, |button: ButtonPosition|->anyhow::Result<(), _> { + Ok(()) +}); \ No newline at end of file diff --git a/src/display/theme.rs b/src/display/theme.rs new file mode 100644 index 0000000..0959ec2 --- /dev/null +++ b/src/display/theme.rs @@ -0,0 +1,63 @@ +use embedded_graphics::pixelcolor::{Rgb565}; + +pub struct Theme { + pub rosewater: Rgb565, + pub flamingo: Rgb565, + pub pink: Rgb565, + pub mauve: Rgb565, + pub red: Rgb565, + pub maroon: Rgb565, + pub peach: Rgb565, + pub yellow: Rgb565, + pub green: Rgb565, + pub teal: Rgb565, + pub sky: Rgb565, + pub sapphire: Rgb565, + pub blue: Rgb565, + pub lavender: Rgb565, + pub text: Rgb565, + pub subtext_1: Rgb565, + pub subtext_0: Rgb565, + pub overlay_2: Rgb565, + pub overlay_1: Rgb565, + pub overlay_0: Rgb565, + pub surface_2: Rgb565, + pub surface_1: Rgb565, + pub surface_0: Rgb565, + pub base: Rgb565, + pub mantle: Rgb565, + pub crust: Rgb565, +} + +pub const MACCHIATO: Theme = Theme { + rosewater: to_rgb565([244, 219, 214]), + flamingo: to_rgb565([240, 198, 198]), + pink: to_rgb565([245, 189, 230]), + mauve: to_rgb565([198, 160, 246]), + red: to_rgb565([237, 135, 150]), + maroon: to_rgb565([238, 153, 160]), + peach: to_rgb565([245, 169, 127]), + yellow: to_rgb565([238, 212, 159]), + green: to_rgb565([166, 218, 149]), + teal: to_rgb565([139, 213, 202]), + sky: to_rgb565([145, 215, 227]), + sapphire: to_rgb565([125, 196, 228]), + blue: to_rgb565([138, 173, 244]), + lavender: to_rgb565([183, 189, 248]), + text: to_rgb565([202, 211, 245]), + subtext_1: to_rgb565([184, 192, 224]), + subtext_0: to_rgb565([165, 173, 203]), + overlay_2: to_rgb565([147, 154, 183]), + overlay_1: to_rgb565([128, 135, 162]), + overlay_0: to_rgb565([110, 115, 141]), + surface_2: to_rgb565([91, 96, 120]), + surface_1: to_rgb565([73, 77, 100]), + surface_0: to_rgb565([54, 58, 79]), + base: to_rgb565([36, 39, 58]), + mantle: to_rgb565([30, 32, 48]), + crust: to_rgb565([24, 25, 38]), +}; + +const fn to_rgb565(rgb: [u8; 3]) -> Rgb565 { + Rgb565::new(rgb[0] >> 3, rgb[1] >> 2, rgb[2] >> 3) +} diff --git a/src/display/tools.rs b/src/display/tools.rs new file mode 100644 index 0000000..2c7c14c --- /dev/null +++ b/src/display/tools.rs @@ -0,0 +1,72 @@ +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::Drawable; +use embedded_graphics::geometry::{Point, Size}; +use embedded_graphics::mono_font::MonoTextStyle; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::primitives::{CornerRadii, PrimitiveStyle, Rectangle, RoundedRectangle, StyledDrawable}; +use embedded_graphics::text::Text; +use embedded_hal::digital::OutputPin; +use mipidsi::Display; +use mipidsi::interface::InterfacePixelFormat; +use crate::display::theme::MACCHIATO; + +pub trait DrawTools +where + DI: mipidsi::interface::Interface, + M: mipidsi::models::Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin, +{ + fn draw_rectangle_bounds(&mut self, top_left: Point, bottom_right: Point, color: M::ColorFormat) -> anyhow::Result<(), DI::Error>; + fn draw_rectangle_top_left(&mut self, top_left: Point, size: Size, color: M::ColorFormat) -> anyhow::Result<(), DI::Error>; + fn draw_rectangle_center(&mut self, center: Point, size: Size, color: M::ColorFormat) -> anyhow::Result<(), DI::Error>; + + fn draw_rounded_rectangle_bounds(&mut self, top_left: Point, bottom_right: Point, color: M::ColorFormat, corner_radii: CornerRadii) -> anyhow::Result<(), DI::Error>; + fn draw_rounded_rectangle_top_left(&mut self, top_left: Point, size: Size, color: M::ColorFormat, corner_radii: CornerRadii) -> anyhow::Result<(), DI::Error>; + fn draw_rounded_rectangle_center(&mut self, center: Point, size: Size, color: M::ColorFormat, corner_radii: CornerRadii) -> anyhow::Result<(), DI::Error>; + + fn draw_text_centered(&mut self, center: Point, text: &str) -> anyhow::Result<(), DI::Error>; +} + + +impl DrawTools for Display +where + DI: mipidsi::interface::Interface, + M: mipidsi::models::Model, + M::ColorFormat: InterfacePixelFormat, + RST: OutputPin, +{ + fn draw_rectangle_bounds(&mut self, top_left: Point, bottom_right: Point, color: M::ColorFormat) -> anyhow::Result<(), DI::Error> { + self.draw_rectangle_top_left(top_left, Size::new((bottom_right.x - top_left.x) as u32, (bottom_right.y - top_left.y) as u32), color) + } + + fn draw_rectangle_top_left(&mut self, top_left: Point, size: Size, color: M::ColorFormat) -> anyhow::Result<(), DI::Error> { + self.fill_solid(&Rectangle::new(top_left, size), color) + } + + fn draw_rectangle_center(&mut self, center: Point, size: Size, color: M::ColorFormat) -> anyhow::Result<(), DI::Error> { + self.draw_rectangle_top_left(center - Point::new((size.width / 2) as i32, (size.height / 2) as i32), size, color) + } + + fn draw_rounded_rectangle_bounds(&mut self, top_left: Point, bottom_right: Point, color: M::ColorFormat, corner_radii: CornerRadii) -> anyhow::Result<(), DI::Error> { + self.draw_rounded_rectangle_top_left(top_left, Size::new((bottom_right.x - top_left.x) as u32, (bottom_right.y - top_left.y) as u32), color, corner_radii) + } + + fn draw_rounded_rectangle_top_left(&mut self, top_left: Point, size: Size, color: M::ColorFormat, corner_radii: CornerRadii) -> anyhow::Result<(), DI::Error> { + let rectangle = Rectangle::new(top_left, size); + let rounded_rectangle = RoundedRectangle::new(rectangle, corner_radii); + + rounded_rectangle.draw_styled(&PrimitiveStyle::with_fill(color), self) + } + + fn draw_rounded_rectangle_center(&mut self, center: Point, size: Size, color: M::ColorFormat, corner_radii: CornerRadii) -> anyhow::Result<(), DI::Error> { + self.draw_rounded_rectangle_top_left(center - Point::new((size.width / 2) as i32, (size.height / 2) as i32), size, color, corner_radii) + } + + fn draw_text_centered(&mut self, center: Point, text: &str) -> anyhow::Result<(), DI::Error> { + Text::new(text, center - Point::new((text.len()*5) as i32, -10), TEXT_STYLE).draw(self)?; + Ok(()) + } +} + +static TEXT_STYLE: MonoTextStyle = MonoTextStyle::new(&embedded_graphics::mono_font::ascii::FONT_10X20, MACCHIATO.text); \ No newline at end of file diff --git a/src/ferris.raw b/src/ferris.raw new file mode 100644 index 0000000000000000000000000000000000000000..9733889c577bdcc9277858ce019abff5b7dd23d0 GIT binary patch literal 11008 zcmd5?F^ua(5VexV#V@WS1%*kP(1k9)^CeA06$+&At2oh@0@XE0K{Vfvgzh8?f}lvb zaTTFoMCc>A0LerGojwRfh{6R({wd%DqNSjK9Zxp1YdbzW$#K|Hylc$27uoSiXeOk>`2)ETl94!X5 z7Q>oZcLS)K_4`3-o24s~T)IN69FL)C0^2HN9bPjKDaXE$Ic$?^cUdyd0GqoOdUSk& z)m^jRz!NpcXI*DkTWzhvYD}(y;w}+V+oZYh0`w@b-S)J(p2N{9vWWl6CTvs4TZIEp zjPY$qVZOB%L?z8dQo~6~tV8R|HSw}(AjjO>R-?O3dK=P)$+xlX57w1NDRi&LI%rLY>sr5EE+juPZOcMX}yJB|mkDykmG+wG!3h1HTYe>d!M?e7lGC6 z*CPx~h==!G##uBQ12?+&w8cHc-QHX25Ibgg1deqZUEVF4{H^cPD!jixPnCv0%w84v z9`yfmyFr${f@Z6`o!)(89(aH!>$KBeG2&lFFpG5`z_}1?1`ZJ zzdYT*@mtKNoQ}5jtBFy_hZy}K?`K9&cQgmekZnQU4d+9MR8IK)kI#O-dPmdEiq%ZF zpLzJvAD{m8_@Sm-jp}rDQ>1;Q75OHC_^|Q+?)QJ9>x0*@9m*FGqCY=JnpFj8{1JW0 zS8~^s$3rQH&HtDFL>zB?h^=VaV5uOu3CESMkmkV36t9Zas@`UY{l z`&3|6_ht~||NRTn{O~@~!0ssjE$8fnjPL?;V)H*Oy51 z{1MWal_ECl1_MuCzYM*&PI;ehH2uL4aeVtwVAXWTr~bl<{`?Wpy!@-P%N`e>8<4ld ztEdGb7Tm+V{ra0ve>)c~@uz~-AHkQ$uYUH3kaC;$s5f~_nC(dSG`F{rzTY$ytm*sb zL$LB#R;SGK?dW0@H&$KJ=eB56X!lH+c$Kb*y^_pu9FD10w3K#Y!D6{Dd0rsmLo*|- z#X;k-^Er^&mMDtjgp%`EIYqZ>m$$oNcE0y}`1NwxaV@ zIM*6GS+|$vS-el~+KEmYWGwCF=CU@MB>EuJsO7=ZAC;~$ddT;hTmu=g4YlX4YqQNI z-1H7h125?X+yH6I)AnK_`N}x2Qa>OfQTQXPBAi@XEdIU4(sDsHhG;jJ_EfOaR>A|sbvyIQkX zDuS;z6DZt(=b6lTe>L7b**qyv%QKF2Ib60ET>3{q5ASMGAD|yHuIS4m!pf1(M^5LQ zdopRZ@HNzG2$@`lG8=?e!BfXiTKHP8pVyIdHFkW#>8>#v(e!F;)`3?l&*uHFNBq8k z-}z|XpZA=u)2T64?~V^=&&HYTgCKI4_fVqKQSrnx1@Q06y8nrpOg>(GZ03D6SCy83QFk90Az3DH8)x}RC72juAcQ zf!J1$kVgA!XE&1ja`G~Tv(3;r-Oc5>0Xoj>ulQbW8v*3&AY!`+ON%XiwnDdZz8Cc@AOcvBcOCKD8vW4f211 zh$8MU(vc*+U^-p8UQ6#Gb$U+-3zQHocz*Rg)KveKjsX76lBZ8x8JU zd?+d1uH*ioWE&N$VRu;1ABE$RUAltRlJy*a;0>0oMT=FZrK|9Q_3V!7m${Emo#U`Z zq+1{%luNMsrH?`KKO}ii)KOnF7U{a+dgXfp+HJ_F_LWT|eS|b!T{Ylz`L;O#GU9!t zf4syI*qyw+k8`Hg9cf(wJtf!HI2VtT1JbDa-?BLEY3pj-0T|*tT2)>=qR*yQc~4P` wR$bCnkn#T-omP-|F6|UZ^0i?#!;9#oVeR<*+&^_zgQd0@_=A$oddFDezjq(d^8f$< literal 0 HcmV?d00001 diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..69e75c4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,111 @@ +mod display; + +use std::{ + thread, + time::Duration +}; +use esp_idf_svc::hal::{ + delay::Ets, + gpio::{Gpio16, Gpio18, Gpio19, Gpio20, Gpio23, Gpio4, Gpio5, PinDriver}, + peripherals::Peripherals, + spi::{config, SpiDeviceDriver, SpiDriverConfig}, + spi::config::MODE_3, + units::* +}; +use mipidsi::{Builder, interface::SpiInterface, models::{ST7789}, options::{ColorInversion, Orientation}}; +use mipidsi::options::{ColorOrder, Rotation}; +use crate::display::screens::base::DisplayScreen; +use crate::display::screens::error::ErrorScreen; +use crate::display::screens::home::StandardScreen; +use crate::display::screens::success::SuccessScreen; + +// Display +const H: i32 = 135; +const X_OFF: i32 = 52; +const W: i32 = 240; +const Y_OFF: i32 = 40; +const FRAMERATE: usize = 30; + +static mut BUFFER: [u8; (H * W) as usize] = [0u8; (H * W) as usize]; + +fn main() -> anyhow::Result<()> { + let peripherals: Peripherals = Peripherals::take()?; + let pin_rst: Gpio23 = peripherals.pins.gpio23; + let pin_dc: Gpio16 = peripherals.pins.gpio16; + let pin_bl: Gpio4 = peripherals.pins.gpio4; + let pin_sclk: Gpio18 = peripherals.pins.gpio18; + let pin_mosi: Gpio19 = peripherals.pins.gpio19; + let pin_miso: Gpio20 = peripherals.pins.gpio20; + let pin_cs: Gpio5 = peripherals.pins.gpio5; + + let screens: Vec) -> anyhow::Result<(), _>> = vec!( + StandardScreen::show, + ErrorScreen::show, + SuccessScreen::show, + ); + + + unsafe { + esp_idf_svc::sys::link_patches(); + esp_idf_svc::log::EspLogger::initialize_default(); + log::info!("Hello, world!"); + + let spi = peripherals.spi2; + + let mut backlight = PinDriver::output(pin_bl)?; + + let mut delay = Ets; + + // configuring the spi interface, note that in order for the ST7789 to work, the data_mode needs to be set to MODE_3 + let config = config::Config::new() + .baudrate(MegaHertz(26).into()) + .data_mode(MODE_3); + + let device = SpiDeviceDriver::new_single( + spi, + pin_sclk, + pin_mosi, + Some(pin_miso), + Some(pin_cs), + &SpiDriverConfig::new(), + &config, + )?; + + // display interface abstraction from SPI and DC + let di = SpiInterface::new(device, PinDriver::output(pin_dc)?, &mut *&raw mut BUFFER); + + // create driver + let mut display = Builder::new(ST7789, di) + .display_size(H as u16, W as u16) + .orientation(Orientation::new().rotate(Rotation::Deg270)) + .reset_pin(PinDriver::output(pin_rst)?) + .invert_colors(ColorInversion::Inverted) + .color_order(ColorOrder::Rgb) + .display_offset(X_OFF as u16, Y_OFF as u16) + .init(&mut delay) + .unwrap(); + + // turn on the backlight + backlight.set_high()?; + + let mut screen_index = 0; + let mut frame_counter = 0; + let mut dirty = true; + + loop { + screens[screen_index](&mut screen_index, &mut frame_counter, &mut dirty, &mut display).unwrap(); + + if screen_index == screens.len() { + screen_index = 0; + } else if screen_index >= screens.len() + 1 { + screen_index = screens.len() - 1; + } + + #[cfg(debug_assertions)] + println!("Sleeping for {}", 1000/FRAMERATE); + + thread::sleep(Duration::from_millis((1000 / FRAMERATE) as u64)); + frame_counter += 1; + } + } +} \ No newline at end of file diff --git a/wokwi.toml b/wokwi.toml new file mode 100644 index 0000000..be497a7 --- /dev/null +++ b/wokwi.toml @@ -0,0 +1,5 @@ +[wokwi] +version = 1 +gdbServerPort = 3333 +elf = "target/xtensa-esp32-espidf/debug/docker-watcher-esp" +firmware = "target/xtensa-esp32-espidf/debug/docker-watcher-esp"