embassy_buddy 0.0.3

A board support crate for the Prusa Buddy Board.
Documentation
#![no_std]
#![no_main]

use core::fmt::Write;
use defmt::info;
use defmt_rtt as _;
use embassy_buddy::{
    BoardBuilder, BuddyDisplay, BuddyFilamentSensor, BuddyPinda, BuddyRotaryButton,
    BuddyRotaryEncoder, BuddyStepperExti, BuddyThermistor, Direction,
};
use embassy_executor::Spawner;
use embassy_futures::join::{join3, join5};
use embassy_time::Timer;
use embedded_graphics::{
    image::{Image, ImageRawLE},
    mono_font::{MonoTextStyle, iso_8859_16::FONT_10X20},
    pixelcolor::Rgb565,
    prelude::*,
    primitives::Rectangle,
    text::{Alignment, Text},
};
use heapless::String;
use panic_probe as _;

pub enum Temp {
    Bed,
    Board,
    Hotend,
}

impl core::fmt::Display for Temp {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let s = match self {
            Self::Bed => "[BED]",
            Self::Board => "[BOARD]",
            Self::Hotend => "[HOTEND]",
        };
        write!(f, "{s}")
    }
}

impl defmt::Format for Temp {
    fn format(&self, f: defmt::Formatter) {
        let s = match self {
            Self::Bed => "[BED]",
            Self::Board => "[BOARD]",
            Self::Hotend => "[HOTEND]",
        };
        defmt::write!(f, "{}", s)
    }
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
    info!("Booting...");
    let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
    let board = BoardBuilder::new()
        .ethernet(&spawner, mac_addr)
        .build()
        .await;

    let pinda = &board.pinda.unwrap();
    let filament_sensor = &board.filament_sensor.unwrap();
    let rotary_button = &board.rotary_button.unwrap();
    let rotaty_encoder = &board.rotary_encoder.unwrap();
    let bed_thermistor = &board.bed_thermistor.unwrap();
    let board_thermistor = &board.board_thermistor.unwrap();
    let hotend_thermistor = &board.hotend_thermistor.unwrap();
    let display = &board.display.unwrap();
    let x_stepper = &board.x_stepper.unwrap();
    let y_stepper = &board.y_stepper.unwrap();
    let z_stepper = &board.z_stepper.unwrap();

    let fut_01 = pinda_interrupt(pinda);
    let fut_02 = filament_interrupt(filament_sensor);
    let fut_03 = click(rotary_button);
    let fut_04 = spun(rotaty_encoder);
    let fut_05 = report_temp(bed_thermistor, Temp::Bed, display);
    let fut_06 = report_temp(board_thermistor, Temp::Board, display);
    let fut_07 = report_temp(hotend_thermistor, Temp::Hotend, display);
    let fut_08 = back_and_forth(x_stepper, "[STEPPER_X]");
    let fut_09 = back_and_forth(y_stepper, "[STEPPER_Y]");
    let fut_10 = back_and_forth(z_stepper, "[STEPPER_Z]");
    let fut_11 = display_ferris(display);

    let fut = join5(fut_01, fut_02, fut_03, fut_04, fut_05);
    let fut = join5(fut, fut_06, fut_07, fut_08, fut_09);
    let fut = join3(fut, fut_10, fut_11);
    fut.await;
}

async fn display_ferris(p: &BuddyDisplay<'_>) {
    let mut display = p.lock().await;
    display.clear(Rgb565::BLACK).unwrap();

    info!("[DISPLAY] FERRIS!");

    let raw_image_data = ImageRawLE::new(include_bytes!("../assets/ferris.raw"), 86);
    const X: i32 = (240 / 2) - (86 / 2);
    const Y: i32 = (320 / 2) - 50;
    info!("{} {}", X, Y);
    let ferris = Image::new(&raw_image_data, Point::new(X, Y));
    display.clear(Rgb565::BLACK).unwrap();
    ferris.draw(&mut *display).unwrap();

    let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
    let text = "Embassy Buddy! ^_^";
    Text::with_alignment(
        text,
        Point::new(240 / 2, 195),
        text_style,
        Alignment::Center,
    )
    .draw(&mut *display)
    .unwrap();

    info!("[DISPLAY] Rendering Complete");
}

async fn pinda_interrupt(p: &BuddyPinda<'_>) -> ! {
    loop {
        if let Ok(change) = p.try_on_change().await {
            info!("[PINDA]: {}", change);
        } else {
            info!("[PINDA]: Error");
        }
    }
}

async fn filament_interrupt(p: &BuddyFilamentSensor<'_>) -> ! {
    loop {
        if let Ok(change) = p.try_on_change().await {
            info!("[FILAMENT]: {}", change);
        } else {
            info!("[FILAMENT]: Error");
        }
    }
}

async fn click(btn: &BuddyRotaryButton<'_>) -> ! {
    loop {
        if btn.try_on_click().await.is_ok() {
            info!("[BTN] Click");
        } else {
            info!("[BTN] Error");
        }
    }
}

async fn spun(rot: &BuddyRotaryEncoder<'_>) -> ! {
    loop {
        if let Ok(dir) = rot.try_spun().await {
            info!("[ROTARY]: {}", dir);
        } else {
            info!("[ROTARY]: Error");
        }
    }
}

async fn report_temp(sensor: &BuddyThermistor<'_>, label: Temp, display: &BuddyDisplay<'_>) -> ! {
    let mut text: String<20> = String::new();
    let point = match label {
        Temp::Bed => Point::new(0, 0),
        Temp::Board => Point::new(0, 22),
        Temp::Hotend => Point::new(0, 44),
    };
    let size = Size::new(240, 22);
    let rect = Rectangle::new(point, size);

    loop {
        Timer::after_secs(2).await;
        let temp = sensor.read().await;
        info!("{} Temp: {}", label, temp);

        let mut display = display.lock().await;

        display.fill_solid(&rect, Rgb565::BLACK).unwrap();
        text.clear();
        write!(&mut text, "{label}: {temp:.2}").unwrap();
        let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
        let point = match label {
            Temp::Bed => Point::new(0, 20),
            Temp::Board => Point::new(0, 40),
            Temp::Hotend => Point::new(0, 60),
        };
        Text::with_alignment(text.as_str(), point, text_style, Alignment::Left)
            .draw(&mut *display)
            .unwrap();
    }
}

async fn back_and_forth(stepper: &BuddyStepperExti<'_>, label: &str) {
    stepper.enable().await;
    let mut n = 0;
    let microstep = 64;
    loop {
        n += 1;
        if n > 3 {
            break;
        }
        info!("{} Forward", label);
        stepper.set_direction(Direction::Clockwise).await;
        for _ in 0..100 * microstep {
            stepper.try_step().unwrap();
            Timer::after_micros(200).await
        }
        info!("{} Backward", label);
        stepper.set_direction(Direction::CounterClockwise).await;
        for _ in 0..100 * microstep {
            stepper.try_step().unwrap();
            Timer::after_micros(200).await
        }
    }
    info!("{} Disabled", label);
    stepper.disable().await;
}