Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ---> Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
55 changes: 54 additions & 1 deletion firmware/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use defmt::info;
use embassy_futures::select::{select, select3, Either, Either3};
use embassy_time::{Duration, Instant, Ticker, Timer};
use embedded_graphics::prelude::*;
use watchful_ui::{FirmwareDetails, MenuAction, MenuView, TimeView, WorkoutView};
use watchful_ui::{FirmwareDetails, MenuAction, MenuView, TimerView, TimeView, WorkoutView};

use crate::device::Device;

Expand Down Expand Up @@ -41,6 +41,7 @@ pub enum WatchState {
Time(TimeState),
Menu(MenuState),
// FindPhone,
Timer(TimerState),
Workout(WorkoutState),
}

Expand All @@ -56,6 +57,7 @@ impl defmt::Format for WatchState {
Self::Idle(_) => defmt::write!(fmt, "Idle"),
Self::Time(_) => defmt::write!(fmt, "Time"),
Self::Menu(_) => defmt::write!(fmt, "Menu"),
Self::Timer(_) => defmt::write!(fmt, "Timer"),
Self::Workout(_) => defmt::write!(fmt, "Workout"),
}
}
Expand All @@ -68,6 +70,7 @@ impl WatchState {
WatchState::Time(state) => state.draw(device).await,
WatchState::Menu(state) => state.draw(device).await,
WatchState::Workout(state) => state.draw(device).await,
WatchState::Timer(state) => state.draw(device).await,
}
}

Expand All @@ -77,6 +80,7 @@ impl WatchState {
WatchState::Time(state) => state.next(device).await,
WatchState::Menu(state) => state.next(device).await,
WatchState::Workout(state) => state.next(device).await,
WatchState::Timer(state) => state.next(device).await,
}
}
}
Expand Down Expand Up @@ -206,6 +210,10 @@ impl MenuState {
defmt::info!("Not implemented");
WatchState::Workout(WorkoutState {})
}
MenuAction::Timer => {
defmt::info!("Not implemented");
WatchState::Timer(TimerState {})
}
MenuAction::FindPhone => {
defmt::info!("Not implemented");
WatchState::Time(TimeState::new(device, Timeout::new(IDLE_TIMEOUT)).await)
Expand Down Expand Up @@ -272,6 +280,51 @@ impl WorkoutState {
}
}

#[derive(PartialEq)]
pub struct TimerState {}

impl TimerState {
pub async fn draw(&mut self, _device: &mut Device<'_>) {}
pub async fn next(&mut self, device: &mut Device<'_>) -> WatchState {
let screen = &mut device.screen;
let button = &mut device.button;
let mut ticker = Ticker::every(Duration::from_secs(1));
let vibrator = &mut device.vibrator;

// Hardcoded for now
let mut seconds = 5;
let timer = async {
loop {
TimerView::new(time::Duration::new(seconds, 0), true)
.draw(screen.display())
.unwrap();
screen.on();
ticker.next().await;
seconds -= 1;

if seconds <= 0 {
break;
}
}

TimerView::new(time::Duration::ZERO, false)
.draw(screen.display())
.unwrap();
screen.on();
vibrator.on_for(1500).await;
};

let next = match select(button.wait(), timer).await {
Either::First(_) => {
vibrator.off();
WatchState::Menu(MenuState::new(MenuView::main()))
},
Either::Second(_) => WatchState::Timer(TimerState {}),
};
next
}
}

async fn firmware_details(battery: &crate::device::Battery<'_>, validated: bool) -> FirmwareDetails {
const CARGO_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_VERSION: &str = env!("CARGO_PKG_VERSION");
Expand Down
65 changes: 60 additions & 5 deletions watchful-ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use u8g2_fonts::{fonts, U8g2TextStyle};

const WIDTH: u32 = 240;
const HEIGHT: u32 = 240;
const GRID_ITEMS: u32 = 3;
const GRID_ITEMS: u32 = 4;

fn watch_text_style(color: Rgb) -> U8g2TextStyle<Rgb> {
//U8g2TextStyle::new(fonts::u8g2_font_unifont_t_symbols, Rgb::YELLOW)
Expand Down Expand Up @@ -187,10 +187,58 @@ impl WorkoutView {
}
}

#[derive(PartialEq, Copy, Clone)]
pub struct TimerView {
pub remaining: time::Duration,
pub running: bool,
}

impl TimerView {
pub fn new(remaining: time::Duration, running: bool) -> Self {
Self { remaining, running }
}
pub fn draw<D: DrawTarget<Color = Rgb>>(&self, display: &mut D) -> Result<(), D::Error> {
display.clear(Rgb::BLACK)?;

let mut buf: heapless::String<16> = heapless::String::new();
write!(
buf,
//"{:02}:{:02}:{:02}",
"{:02}:{:02}",
//self.remaining.whole_hours(),
self.remaining.whole_minutes(),
self.remaining.whole_seconds() % 60,
//self.remaining.subsec_milliseconds() / 100
)
.unwrap();

let cd = Text::with_text_style(
&buf,
display.bounding_box().center(),
watch_text_style(Rgb::CSS_DARK_CYAN),
TextStyleBuilder::new()
.alignment(embedded_graphics::text::Alignment::Center)
.baseline(embedded_graphics::text::Baseline::Alphabetic)
.build(),
);

let display_area = display.bounding_box();
LinearLayout::vertical(Chain::new(cd))
.with_spacing(spacing::FixedMargin(10))
.with_alignment(horizontal::Center)
.arrange()
.align_to(&display_area, horizontal::Center, vertical::Center)
.draw(display)?;

Ok(())
}
}

#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum MenuAction {
Workout,
Timer,
FindPhone,
Settings,
FirmwareSettings,
Expand All @@ -203,6 +251,7 @@ pub enum MenuAction {
pub enum MenuView {
Main {
workout: MenuItem,
timer: MenuItem,
find_phone: MenuItem,
settings: MenuItem,
},
Expand All @@ -221,8 +270,9 @@ impl MenuView {
pub fn main() -> Self {
Self::Main {
workout: MenuItem::new("Workout", 0),
find_phone: MenuItem::new("Find Phone", 1),
settings: MenuItem::new("Settings", 2),
timer: MenuItem::new("Timer", 1),
find_phone: MenuItem::new("Find Phone", 2),
settings: MenuItem::new("Settings", 3),
}
}

Expand All @@ -238,7 +288,7 @@ impl MenuView {
let valid = details.validated;
Self::Firmware {
details,
item: MenuItem::new(if valid { "Validated" } else { "Validate" }, 2),
item: MenuItem::new(if valid { "Validated" } else { "Validate" }, 3),
}
}

Expand All @@ -248,10 +298,12 @@ impl MenuView {
match self {
Self::Main {
workout,
timer,
find_phone,
settings,
} => {
workout.draw(display)?;
timer.draw(display)?;
find_phone.draw(display)?;
settings.draw(display)?;
}
Expand All @@ -275,11 +327,14 @@ impl MenuView {
match self {
Self::Main {
workout,
timer,
find_phone,
settings,
} => {
if workout.is_clicked(input) {
Some(MenuAction::Workout)
} else if timer.is_clicked(input) {
Some(MenuAction::Timer)
} else if find_phone.is_clicked(input) {
Some(MenuAction::FindPhone)
} else if settings.is_clicked(input) {
Expand Down Expand Up @@ -336,7 +391,7 @@ impl MenuItem {
self.text,
Point::new(
(WIDTH as i32) / 2,
self.idx as i32 * (HEIGHT as i32 / GRID_ITEMS as i32) + 47,
self.idx as i32 * (HEIGHT as i32 / GRID_ITEMS as i32) + 40,
),
menu_text_style(Rgb::CSS_CORNSILK),
TextStyleBuilder::new()
Expand Down