diff --git a/Cargo.lock b/Cargo.lock index ce4f182..3764ad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,7 +42,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcb2392079bf27198570d6af79ecbd9ec7d8f16d3ec6b60933922fdb66287127" dependencies = [ "heapless", - "nom 4.2.3", + "nom", ] [[package]] @@ -74,6 +74,7 @@ dependencies = [ "bitflags", "cortex-m", "embedded-hal", + "embedded-sdmmc", "modular-bitfield", "nb 1.1.0", "num-traits", @@ -341,12 +342,14 @@ dependencies = [ [[package]] name = "embedded-sdmmc" -version = "0.5.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4d14180a76a8af24a45a0e1a4f9c97491b05a3b962d59d5e4ce0e6ab103736" +checksum = "6d3bf0a2b5becb87e9a329d9290f131e4d10fec39b56d129926826a7cbea1e7a" dependencies = [ "byteorder", "embedded-hal", + "log", + "nb 0.1.3", ] [[package]] @@ -530,6 +533,7 @@ version = "0.10.0" dependencies = [ "atsamd-hal", "cortex-m-rt", + "embedded-sdmmc", "usb-device", ] @@ -619,16 +623,6 @@ dependencies = [ "version_check 0.1.5", ] -[[package]] -name = "nom" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a7a9657c84d5814c6196b68bb4429df09c18b1573806259fba397ea4ad0d44" -dependencies = [ - "memchr", - "version_check 0.9.4", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -832,8 +826,6 @@ dependencies = [ "epd-waveshare", "maduino_zero_4g", "panic-halt", - "tinybmp", - "tinytga", "tz-rs", "tzdb", ] @@ -939,25 +931,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "tinybmp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e959c507975d768a226a08227d56791f6e60bddcf714ad7ef67ae2d20bae743" -dependencies = [ - "embedded-graphics", -] - -[[package]] -name = "tinytga" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756dcc0078b35e5f8ab052f19a48225821fd046011342d31deef6b8d5c825d2b" -dependencies = [ - "embedded-graphics", - "nom 6.2.2", -] - [[package]] name = "typenum" version = "1.16.0" diff --git a/Cargo.toml b/Cargo.toml index 410c764..a067b98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,10 @@ atsamd-hal = { version = "0.15.1", default_features = false, features = ["samd21 cfg-if = "1.0.0" embedded-graphics = "0.7.1" embedded-text = "0.5.0" -embedded-sdmmc = { version = "0.5.0", default_features = false } +embedded-sdmmc = { version = "0.3.0", default_features = false } epd-waveshare = "0.5.0" maduino_zero_4g = { git = "https://github.com/ZettaScript/atsamd", branch = "maduino-zero-4g", features = ["usb"] } panic-halt = "0.2.0" -tinybmp = "0.4.0" -tinytga = "0.4.1" tz-rs = { version = "0.6.14", default_features = false, features = ["const"] } tzdb = { version = "0.5.7", optional = true } @@ -31,6 +29,7 @@ simulator = ["embedded-graphics-simulator", "tzdb"] [profile.release] lto = "fat" +opt-level = 3 [patch."https://github.com/ZettaScript/atsamd"] maduino_zero_4g = { path = "../atsamd/boards/maduino_zero_4g" } diff --git a/README.md b/README.md index 9452945..a437930 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ rustup target add thumbv6m-none-eabi ## Run simulator ```bash -cargo run +cargo run --features simulator ``` ## Build @@ -59,7 +59,7 @@ Note: at most 5 outputs of the 74HC565 may be used as GPO. https://www.makerfabs.com/maduino-zero-4g-lte-sim7600.html https://www.waveshare.com/1.54inch-e-Paper-Module.htm1 https://bulkmemorycards.com/shop/microsd-cards/microsd-32gb/sd-32gb-class-10/32gb-microsd-ultra-sandisk-memory-card-2/ -Maybe an ESP for WIFI and Bluetooth. +Maybe an ESP for WIFI and Bluetooth: https://www.sparkfun.com/products/18034 ## crates bitmap-font ? diff --git a/src/apps.rs b/src/apps.rs index 9946f8a..0cd1ff3 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -1,9 +1,11 @@ -use crate::display::Display; +use crate::{display::Display, keypad::KeyEvents, state::ModeState, Context}; +pub mod clock; pub mod dial; pub trait App { - type State; + type AppModeState; - fn update(display: &mut Display); + /// Return Some if the mode should be changed + fn update(context: &mut Context, mode_state: &mut Self::AppModeState) -> Option; } diff --git a/src/apps/clock.rs b/src/apps/clock.rs new file mode 100644 index 0000000..2bdcc4d --- /dev/null +++ b/src/apps/clock.rs @@ -0,0 +1,112 @@ +use crate::{ + apps, + apps::App, + display::Display, + keypad::*, + state::{self, ModeState}, + strf, Context, +}; + +use arrayvec::ArrayString; +use embedded_graphics::{ + mono_font::{ascii::FONT_10X20, MonoTextStyleBuilder}, + pixelcolor::BinaryColor, + prelude::*, + text::{Alignment, Text}, +}; + +pub struct Clock; + +pub struct ClockState { + year: i32, + month: u8, + day: u8, + week_day: u8, +} + +impl Default for ClockState { + fn default() -> Self { + Self { + year: 0, + month: 0, + day: 0, + week_day: 0, + } + } +} + +impl App for Clock { + type AppModeState = ClockState; + + fn update(ctx: &mut Context, mode_state: &mut ClockState) -> Option { + // TODO move at init + let clock_text_style = MonoTextStyleBuilder::new() + .font(&FONT_10X20) + .text_color(BinaryColor::On) + .background_color(BinaryColor::Off) + .build(); + + for key_event in &ctx.key_events { + if key_event.event_type != KeyEventType::Pressed { + continue; + } + match key_event.key { + _ => { + if let Some(ch) = key_event.get_char(KeyInputMode::Digit) { + let mut buf = [0; 4]; + return Some(ModeState::Dial(apps::dial::DialState { + line: ArrayString::from(&*ch.encode_utf8(&mut buf)).unwrap(), + })); + } + } + } + } + + if ctx.hm_change { + Text::with_alignment( + unsafe { + core::str::from_utf8_unchecked(&strf::fmt_time_hm( + ctx.state.hour, + ctx.state.minute, + )) + }, + ctx.display.inner().bounding_box().center() + Point::new(0, 10), + clock_text_style, + Alignment::Center, + ) + .draw(ctx.display.inner_mut()) + .unwrap(); + + let year_ = ctx.now.year(); + let month_ = ctx.now.month(); + let day_ = ctx.now.month_day(); + let week_day_ = ctx.now.week_day(); + if (year_, month_, day_, week_day_) + != ( + mode_state.year, + mode_state.month, + mode_state.day, + mode_state.week_day, + ) { + mode_state.year = year_; + mode_state.month = month_; + mode_state.day = day_; + mode_state.week_day = week_day_; + Text::with_alignment( + unsafe { + core::str::from_utf8_unchecked(&strf::fmt_time_ymdw( + year_, month_, day_, week_day_, + )) + }, + ctx.display.inner().bounding_box().center() + Point::new(0, -20), + clock_text_style, + Alignment::Center, + ) + .draw(ctx.display.inner_mut()) + .unwrap(); + } + } + + None + } +} diff --git a/src/apps/dial.rs b/src/apps/dial.rs index 8b13789..6ec8335 100644 --- a/src/apps/dial.rs +++ b/src/apps/dial.rs @@ -1 +1,45 @@ +use crate::{ + apps::App, + display::Display, + keypad::{Key, KeyEvent, KeyEventType, KeyEvents, KeyInputMode}, + state::{ModeState, State}, + Context, +}; +use arrayvec::ArrayString; + +pub struct Dial; + +pub struct DialState { + // TODO what should max length be? + pub line: ArrayString<255>, +} + +impl App for Dial { + type AppModeState = DialState; + + fn update(ctx: &mut Context, mode_state: &mut DialState) -> Option { + for key_event in &ctx.key_events { + if key_event.event_type != KeyEventType::Pressed { + continue; + } + match key_event.key { + Key::HangUp => { + return Some(ModeState::Clock(Default::default())); + } + Key::PickUp => { + // TODO + } + _ => { + if let Some(ch) = key_event.get_char(KeyInputMode::Digit) { + if mode_state.line.try_push(ch).is_err() { + // TODO + } + } + } + } + } + + None + } +} diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..7d3f6eb --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,29 @@ +use atsamd_hal::{delay::Delay, pac::Peripherals, prelude::*, sercom::spi::EightBit}; +use embedded_sdmmc::{Controller, SdMmcSpi, VolumeIdx}; + +pub struct Fs { + controller: Controller, ClockMock>, +} + +impl Fs { + pub fn new(spi: maduino_zero_4g::SdSpi, cs: maduino_zero_4g::SdCs) -> Self { + let controller = Controller::new(SdMmcSpi::new(spi, cs), ClockMock); + Self { controller } + } +} + +struct ClockMock; + +// TODO +impl embedded_sdmmc::TimeSource for ClockMock { + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + embedded_sdmmc::Timestamp { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } +} diff --git a/src/gui.rs b/src/gui.rs index 8b13789..51be9a0 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1 +1 @@ - +pub trait Widget {} diff --git a/src/keypad.rs b/src/keypad.rs index a43be6d..c2300c6 100644 --- a/src/keypad.rs +++ b/src/keypad.rs @@ -4,6 +4,8 @@ const NB_KEYS: usize = 21; /// Key repeat max delay (ms) const REPEAT_DELAY: u64 = 500; +pub type KeyEvents = ArrayVec; + pub struct Keypad { last_key: Option<(Key, u64, u8)>, pressed: [bool; NB_KEYS], @@ -19,7 +21,7 @@ impl Default for Keypad { } impl Keypad { - pub fn update(&mut self) -> ArrayVec { + pub fn update(&mut self) -> KeyEvents { self.get_keys() .into_iter() .zip(self.pressed.iter_mut()) @@ -97,7 +99,7 @@ impl Keypad { .for_each(|key| keys[key as usize] = true); keys } - + #[cfg(not(feature = "simulator"))] fn get_keys(&self) -> [bool; NB_KEYS] { [false; NB_KEYS] @@ -137,26 +139,41 @@ impl Key { } pub struct KeyEvent { - event_type: KeyEventType, - key: Key, - repeats: u8, + pub event_type: KeyEventType, + pub key: Key, + pub repeats: u8, } -/*impl KeyEvent { +impl KeyEvent { pub fn get_char(&self, key_input_mode: KeyInputMode) -> Option { use Key::*; match key_input_mode { - KeyInputMode::Digit => match self.key { - D0 => + D0 => Some('0'), + D1 => Some('1'), + D2 => Some('2'), + D3 => Some('3'), + D4 => Some('4'), + D5 => Some('5'), + D6 => Some('6'), + D7 => Some('7'), + D8 => Some('8'), + D9 => Some('9'), + Asterisk => Some('*'), + Hash => Some('#'), + _ => None, + }, + _ => { + None /* TODO */ } } } -}*/ +} //ncb1 upz2 tdk3 //eow4 lqh5 age6 //sfx7 rmj8 ivy9 +#[derive(Eq, PartialEq)] pub enum KeyEventType { Pressed, Down, diff --git a/src/main.rs b/src/main.rs index c574795..26ed54b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,25 +5,19 @@ mod apps; mod config; mod display; mod energy; +mod fs; mod gui; mod keypad; mod state; mod strf; mod time; +use apps::App; +use display::Display; use energy::EnergyStatus; +use keypad::KeyEvents; use state::*; -cfg_if::cfg_if! { - if #[cfg(not(feature = "simulator"))] { - use maduino_zero_4g as bsp; - use bsp::hal; - - use hal::pac::{CorePeripherals, Peripherals}; - use hal::prelude::*; - } -} - use arrayvec::ArrayString; use core::fmt::Write; use embedded_graphics::{ @@ -33,36 +27,55 @@ use embedded_graphics::{ primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, StrokeAlignment}, text::{Alignment, Text}, }; - #[cfg(not(feature = "simulator"))] -use panic_halt as _; - -static mut STATE: State = State { - energy: EnergyStatus { - battery: 255, - charging: false, - }, - hour: 0, - minute: 0, - mode: Mode::Clock { - year: 0, - month: 0, - day: 0, - week_day: 0, +use maduino_zero_4g::{ + self as bsp, + hal::{ + clock::GenericClockController, + delay::Delay, + pac::{CorePeripherals, Peripherals}, + prelude::*, }, }; - -fn state() -> &'static State { - unsafe { &STATE } -} - -fn state_mut() -> &'static mut State { - unsafe { &mut STATE } -} +#[cfg(not(feature = "simulator"))] +use panic_halt as _; +use tz::DateTime; #[cfg_attr(not(feature = "simulator"), bsp::entry)] fn main() -> ! { - let mut display = display::Display::new(); + cfg_if::cfg_if! { + if #[cfg(not(feature = "simulator"))] { + let mut peripherals = Peripherals::take().unwrap(); + let mut pins = maduino_zero_4g::Pins::new(peripherals.PORT); + let core = CorePeripherals::take().unwrap(); + let mut clocks = GenericClockController::with_external_32kosc( + peripherals.GCLK, + &mut peripherals.PM, + &mut peripherals.SYSCTRL, + &mut peripherals.NVMCTRL, + ); + let mut delay = Delay::new(core.SYST, &mut clocks); + + let mut fs = fs::Fs::new(atsamd_hal::sercom::spi::Config::new( + &peripherals.PM, + peripherals.SERCOM4, + atsamd_hal::sercom::spi::Pads::default().data_in(pins.sd_miso).data_out(pins.sd_mosi).sclk(pins.sd_sck), + 10_u32.mhz(), + ).enable(), pins.d4.into_push_pull_output()); + } + } + + let mut state = State { + energy: EnergyStatus { + battery: 255, + charging: false, + }, + hour: 0, + minute: 0, + }; + let mut mode_state = ModeState::Clock(Default::default()); + + let mut display = Display::new(); let mut keypad = keypad::Keypad::default(); let thin_stroke = PrimitiveStyle::with_stroke(BinaryColor::On, 1); @@ -73,11 +86,6 @@ fn main() -> ! { .text_color(BinaryColor::On) .background_color(BinaryColor::Off) .build(); - let clock_text_style = MonoTextStyleBuilder::new() - .font(&FONT_10X20) - .text_color(BinaryColor::On) - .background_color(BinaryColor::Off) - .build(); Line::new(Point::new(0, 13), Point::new(199, 13)) .into_styled(thin_stroke) @@ -87,13 +95,19 @@ fn main() -> ! { #[cfg(feature = "simulator")] display.update(); - let mut update = true; - let mut hm_change = true; + let mut ctx = Context { + display: &mut display, + hm_change: true, + key_events: Default::default(), + now: DateTime::from_timespec(0, 0, tz::TimeZoneRef::utc()).unwrap(), + update: true, + state: &mut state, + }; loop { let energy_status = energy::get_energy_status(); - if energy_status != state().energy { - update = true; + if energy_status != ctx.state.energy { + ctx.update = true; Text::with_alignment( unsafe { core::str::from_utf8_unchecked(&strf::fmt_energy(&energy_status)) }, @@ -101,21 +115,21 @@ fn main() -> ! { statusbar_text_style, Alignment::Left, ) - .draw(display.inner_mut()) + .draw(ctx.display.inner_mut()) .unwrap(); - state_mut().energy = energy_status; + ctx.state.energy = energy_status; } - let now = time::now(); + ctx.now = time::now(); - let hour = now.hour(); - let minute = now.minute(); - if (hour, minute) != (state().hour, state().minute) { - update = true; - hm_change = true; - state_mut().hour = hour; - state_mut().minute = minute; + let hour = ctx.now.hour(); + let minute = ctx.now.minute(); + if (hour, minute) != (ctx.state.hour, ctx.state.minute) { + ctx.update = true; + ctx.hm_change = true; + ctx.state.hour = hour; + ctx.state.minute = minute; Text::with_alignment( unsafe { core::str::from_utf8_unchecked(&strf::fmt_time_hm(hour, minute)) }, @@ -123,67 +137,41 @@ fn main() -> ! { statusbar_text_style, Alignment::Right, ) - .draw(display.inner_mut()) + .draw(ctx.display.inner_mut()) .unwrap(); } - let key_events = keypad.update(); + ctx.key_events = keypad.update(); - match &mut state_mut().mode { - Mode::Clock { - year, - month, - day, - week_day, - } => { - if hm_change { - Text::with_alignment( - unsafe { core::str::from_utf8_unchecked(&strf::fmt_time_hm(hour, minute)) }, - display.inner().bounding_box().center() + Point::new(0, 10), - clock_text_style, - Alignment::Center, - ) - .draw(display.inner_mut()) - .unwrap(); - - let year_ = now.year(); - let month_ = now.month(); - let day_ = now.month_day(); - let week_day_ = now.week_day(); - if (year_, month_, day_, week_day_) != (*year, *month, *day, *week_day) { - *year = year_; - *month = month_; - *day = day_; - *week_day = week_day_; - Text::with_alignment( - unsafe { - core::str::from_utf8_unchecked(&strf::fmt_time_ymdw( - year_, month_, day_, week_day_, - )) - }, - display.inner().bounding_box().center() + Point::new(0, -20), - clock_text_style, - Alignment::Center, - ) - .draw(display.inner_mut()) - .unwrap(); - } - } - } + if let Some(new_mode_state) = match &mut mode_state { + ModeState::Clock(clock_state) => apps::clock::Clock::update(&mut ctx, clock_state), + ModeState::Dial(dial_state) => apps::dial::Dial::update(&mut ctx, dial_state), + } { + mode_state = new_mode_state; } #[cfg(feature = "simulator")] { - display.update(); + ctx.display.update(); std::thread::sleep(core::time::Duration::from_millis(50)); } #[cfg(not(feature = "simulator"))] - if update { - display.update(); + if ctx.update { + ctx.display.update(); } - // TODO sleep on samd + #[cfg(not(feature = "simulator"))] + delay.delay_ms(50_u8); - update = false; - hm_change = false; + ctx.update = false; + ctx.hm_change = false; } } + +pub struct Context<'a> { + pub state: &'a mut State, + pub display: &'a mut Display, + pub hm_change: bool, + pub now: DateTime, + pub update: bool, + pub key_events: KeyEvents, +} diff --git a/src/state.rs b/src/state.rs index 0b32aee..76bc7c5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,18 +1,18 @@ -use crate::energy::EnergyStatus; +use crate::{apps, energy::EnergyStatus}; + +/*pub struct State { + pub global: GlobalState, + pub mode: ModeState, +}*/ -/// Global state pub struct State { pub energy: EnergyStatus, pub hour: u8, pub minute: u8, - pub mode: Mode, } -pub enum Mode { - Clock { - year: i32, - month: u8, - day: u8, - week_day: u8, - }, +#[warn(clippy::large_enum_variant)] +pub enum ModeState { + Clock(apps::clock::ClockState), + Dial(apps::dial::DialState), } diff --git a/src/strf.rs b/src/strf.rs index 0d3e874..7252f8c 100644 --- a/src/strf.rs +++ b/src/strf.rs @@ -1,5 +1,3 @@ -use tz::DateTime; - use crate::energy::EnergyStatus; static WEEK_DAYS: [[u8; 3]; 7] = [