diff --git a/README.md b/README.md index a437930..be8b40b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,21 @@ OS for a dumbphone based on Maduino Zero 4G LTE board. +## Goals + +GSM will be shutting down in France in a few years so if you have an old cellphone and don't want to buy a fancy, spying, expensive smartphone, making your own 4G cellphone is an interesting option. + +* Low cost +* Privacy +* Long battery life (this one will be complicated) + * Low-consumption components + * Use less CPU cycles + * Update display less often + * Use less wireless RX/TX +* Usable for basic things (call, messages, agenda, contacts, notepad) +* Customizable (smartphones don't do exactly what I want) +* (long term) Mesh networking and other cool stuff + ## Install tools ```bash @@ -56,15 +71,15 @@ Used pins: Note: at most 5 outputs of the 74HC565 may be used as GPO. ## components -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: https://www.sparkfun.com/products/18034 +* 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: https://www.sparkfun.com/products/18034 ## crates -bitmap-font ? -https://lib.rs/crates/at-commands -OR https://lib.rs/crates/atat +* bitmap-font ? +* https://lib.rs/crates/at-commands OR https://lib.rs/crates/atat +* https://crates.io/crates/embedded-menu ## Apps * Calculator @@ -80,6 +95,10 @@ OR https://lib.rs/crates/atat Optimized to reduce the number of consecutive presses on the same key in French. +For example, the letters _m_, _o_, _n_ are on the same key `MNO6` but they frequently appear consecutively in French, so you have to wait between each letter. Having them on three different keys solves this problem. + +``` ncb1 upz2 tdk3 eow4 lqh5 age6 sfx7 rmj8 ivy9 +``` diff --git a/rustfmt.toml b/rustfmt.toml index ade1f81..843c6fe 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,6 @@ hard_tabs = true newline_style = "unix" +imports_granularity = "Crate" unstable_features = true format_code_in_doc_comments = true diff --git a/src/apps.rs b/src/apps.rs index 0cd1ff3..7318e7d 100644 --- a/src/apps.rs +++ b/src/apps.rs @@ -6,6 +6,12 @@ pub mod dial; pub trait App { type AppModeState; + /// Called when the state is entered, before update + fn on_enter(context: &mut Context, mode_state: &mut Self::AppModeState); + /// Return Some if the mode should be changed fn update(context: &mut Context, mode_state: &mut Self::AppModeState) -> Option; + + /// Called when the state is left, after update + fn on_leave(context: &mut Context, mode_state: &mut Self::AppModeState); } diff --git a/src/apps/clock.rs b/src/apps/clock.rs index 2bdcc4d..2667291 100644 --- a/src/apps/clock.rs +++ b/src/apps/clock.rs @@ -1,6 +1,5 @@ use crate::{ - apps, - apps::App, + apps::{self, App}, display::Display, keypad::*, state::{self, ModeState}, @@ -24,6 +23,7 @@ pub struct ClockState { week_day: u8, } +#[allow(clippy::derivable_impls)] impl Default for ClockState { fn default() -> Self { Self { @@ -38,6 +38,16 @@ impl Default for ClockState { impl App for Clock { type AppModeState = ClockState; + fn on_enter(ctx: &mut Context, mode_state: &mut ClockState) { + ctx.state.key_label_left.clear(); + ctx.state.key_label_left.push_str("Journal"); + ctx.state.key_label_enter.clear(); + ctx.state.key_label_enter.push_str("Menu"); + ctx.state.key_label_right.clear(); + ctx.state.key_label_right.push_str("Contacts"); + ctx.key_labels_change = true; + } + fn update(ctx: &mut Context, mode_state: &mut ClockState) -> Option { // TODO move at init let clock_text_style = MonoTextStyleBuilder::new() @@ -56,6 +66,7 @@ impl App for Clock { let mut buf = [0; 4]; return Some(ModeState::Dial(apps::dial::DialState { line: ArrayString::from(&*ch.encode_utf8(&mut buf)).unwrap(), + line_changed: true, })); } } @@ -109,4 +120,6 @@ impl App for Clock { None } + + fn on_leave(ctx: &mut Context, mode_state: &mut ClockState) {} } diff --git a/src/apps/dial.rs b/src/apps/dial.rs index 6ec8335..234c037 100644 --- a/src/apps/dial.rs +++ b/src/apps/dial.rs @@ -7,18 +7,55 @@ use crate::{ }; use arrayvec::ArrayString; +use embedded_graphics::{ + mono_font::{ + ascii::{FONT_10X20, FONT_6X10, FONT_9X15}, + MonoTextStyleBuilder, + }, + pixelcolor::BinaryColor, + prelude::*, + primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle, StrokeAlignment}, + text::{Alignment, Text}, +}; +use embedded_text::{ + alignment::*, + style::{HeightMode, TextBoxStyleBuilder}, + TextBox, +}; pub struct Dial; pub struct DialState { - // TODO what should max length be? - pub line: ArrayString<255>, + pub line: ArrayString<40>, + pub line_changed: bool, } impl App for Dial { type AppModeState = DialState; + fn on_enter(ctx: &mut Context, mode_state: &mut DialState) { + ctx.state.key_label_left.clear(); + ctx.state.key_label_left.push_str("Message"); + ctx.state.key_label_enter.clear(); + ctx.state.key_label_enter.push_str("Save"); + ctx.state.key_label_right.clear(); + ctx.state.key_label_right.push_str("Delete"); + ctx.key_labels_change = true; + } + fn update(ctx: &mut Context, mode_state: &mut DialState) -> Option { + // TODO move to init + let dial_text_style = MonoTextStyleBuilder::new() + .font(&FONT_10X20) + .text_color(BinaryColor::On) + .background_color(BinaryColor::Off) + .build(); + let textbox_style = TextBoxStyleBuilder::new() + .height_mode(HeightMode::FitToText) + .alignment(HorizontalAlignment::Left) + .paragraph_spacing(6) + .build(); + for key_event in &ctx.key_events { if key_event.event_type != KeyEventType::Pressed { continue; @@ -30,16 +67,38 @@ impl App for Dial { Key::PickUp => { // TODO } + Key::OptionRight => { + if mode_state.line.pop().is_some() { + mode_state.line_changed = true; + } else { + return Some(ModeState::Clock(Default::default())); + } + } _ => { if let Some(ch) = key_event.get_char(KeyInputMode::Digit) { if mode_state.line.try_push(ch).is_err() { // TODO + } else { + mode_state.line_changed = true; } } } } } + if mode_state.line_changed { + let bounds = Rectangle::new(Point::new(0, 16), Size::new(200, 0)); + let text_box = TextBox::with_textbox_style( + &mode_state.line, + bounds, + dial_text_style, + textbox_style, + ); + text_box.draw(ctx.display.inner_mut()).unwrap(); + } + None } + + fn on_leave(ctx: &mut Context, mode_state: &mut DialState) {} } diff --git a/src/main.rs b/src/main.rs index 26ed54b..859301a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,8 +72,11 @@ fn main() -> ! { }, hour: 0, minute: 0, + key_label_left: ArrayString::new_const(), + key_label_enter: ArrayString::new_const(), + key_label_right: ArrayString::new_const(), }; - let mut mode_state = ModeState::Clock(Default::default()); + let mut mode_state = ModeState::default(); let mut display = Display::new(); let mut keypad = keypad::Keypad::default(); @@ -86,11 +89,19 @@ fn main() -> ! { .text_color(BinaryColor::On) .background_color(BinaryColor::Off) .build(); + let key_label_text_style = MonoTextStyleBuilder::new() + .font(&FONT_6X10) + .text_color(BinaryColor::On) + .build(); Line::new(Point::new(0, 13), Point::new(199, 13)) .into_styled(thin_stroke) .draw(display.inner_mut()) .unwrap(); + Line::new(Point::new(0, 189), Point::new(199, 189)) + .into_styled(thin_stroke) + .draw(display.inner_mut()) + .unwrap(); #[cfg(feature = "simulator")] display.update(); @@ -98,6 +109,7 @@ fn main() -> ! { let mut ctx = Context { display: &mut display, hm_change: true, + key_labels_change: false, key_events: Default::default(), now: DateTime::from_timespec(0, 0, tz::TimeZoneRef::utc()).unwrap(), update: true, @@ -144,10 +156,61 @@ fn main() -> ! { ctx.key_events = keypad.update(); if let Some(new_mode_state) = match &mut mode_state { + ModeState::Nothing => Some(ModeState::Clock(Default::default())), ModeState::Clock(clock_state) => apps::clock::Clock::update(&mut ctx, clock_state), ModeState::Dial(dial_state) => apps::dial::Dial::update(&mut ctx, dial_state), } { + match &mut mode_state { + ModeState::Nothing => {} + ModeState::Clock(clock_state) => { + apps::clock::Clock::on_leave(&mut ctx, clock_state) + } + ModeState::Dial(dial_state) => apps::dial::Dial::on_leave(&mut ctx, dial_state), + } mode_state = new_mode_state; + match &mut mode_state { + ModeState::Nothing => {} + ModeState::Clock(clock_state) => { + apps::clock::Clock::on_enter(&mut ctx, clock_state) + } + ModeState::Dial(dial_state) => apps::dial::Dial::on_enter(&mut ctx, dial_state), + } + } + + if ctx.key_labels_change { + let bg_style = PrimitiveStyleBuilder::new() + .fill_color(BinaryColor::Off) + .build(); + Rectangle::new(Point::new(0, 190), Size::new(200, 12)) + .into_styled(bg_style) + .draw(ctx.display.inner_mut()) + .unwrap(); + + Text::with_alignment( + &ctx.state.key_label_left, + Point::new(0, 197), + key_label_text_style, + Alignment::Left, + ) + .draw(ctx.display.inner_mut()) + .unwrap(); + Text::with_alignment( + &ctx.state.key_label_right, + Point::new(200, 197), + key_label_text_style, + Alignment::Right, + ) + .draw(ctx.display.inner_mut()) + .unwrap(); + Text::with_alignment( + &ctx.state.key_label_enter, + Point::new(100, 197), + key_label_text_style, + Alignment::Center, + ) + .draw(ctx.display.inner_mut()) + .unwrap(); + ctx.key_labels_change = false; } #[cfg(feature = "simulator")] @@ -171,6 +234,7 @@ pub struct Context<'a> { pub state: &'a mut State, pub display: &'a mut Display, pub hm_change: bool, + pub key_labels_change: bool, pub now: DateTime, pub update: bool, pub key_events: KeyEvents, diff --git a/src/state.rs b/src/state.rs index 76bc7c5..a5b4cc2 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,3 +1,5 @@ +use arrayvec::ArrayString; + use crate::{apps, energy::EnergyStatus}; /*pub struct State { @@ -9,10 +11,20 @@ pub struct State { pub energy: EnergyStatus, pub hour: u8, pub minute: u8, + pub key_label_left: ArrayString<8>, + pub key_label_enter: ArrayString<8>, + pub key_label_right: ArrayString<8>, } -#[warn(clippy::large_enum_variant)] +#[allow(clippy::large_enum_variant)] pub enum ModeState { + Nothing, Clock(apps::clock::ClockState), Dial(apps::dial::DialState), } + +impl Default for ModeState { + fn default() -> Self { + Self::Nothing + } +}