State transitions, key labels

This commit is contained in:
Pascal Engélibert 2023-09-17 00:15:48 +02:00
parent d8865488e3
commit a4884bd189
7 changed files with 187 additions and 13 deletions

View file

@ -4,6 +4,21 @@
OS for a dumbphone based on Maduino Zero 4G LTE board. 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 ## Install tools
```bash ```bash
@ -56,15 +71,15 @@ Used pins:
Note: at most 5 outputs of the 74HC565 may be used as GPO. Note: at most 5 outputs of the 74HC565 may be used as GPO.
## components ## components
https://www.makerfabs.com/maduino-zero-4g-lte-sim7600.html * https://www.makerfabs.com/maduino-zero-4g-lte-sim7600.html
https://www.waveshare.com/1.54inch-e-Paper-Module.htm1 * 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/ * 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 * Maybe an ESP for WIFI and Bluetooth: https://www.sparkfun.com/products/18034
## crates ## crates
bitmap-font ? * bitmap-font ?
https://lib.rs/crates/at-commands * https://lib.rs/crates/at-commands OR https://lib.rs/crates/atat
OR https://lib.rs/crates/atat * https://crates.io/crates/embedded-menu
## Apps ## Apps
* Calculator * 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. 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 ncb1 upz2 tdk3
eow4 lqh5 age6 eow4 lqh5 age6
sfx7 rmj8 ivy9 sfx7 rmj8 ivy9
```

View file

@ -1,5 +1,6 @@
hard_tabs = true hard_tabs = true
newline_style = "unix" newline_style = "unix"
imports_granularity = "Crate"
unstable_features = true unstable_features = true
format_code_in_doc_comments = true format_code_in_doc_comments = true

View file

@ -6,6 +6,12 @@ pub mod dial;
pub trait App { pub trait App {
type AppModeState; 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 /// Return Some if the mode should be changed
fn update(context: &mut Context, mode_state: &mut Self::AppModeState) -> Option<ModeState>; fn update(context: &mut Context, mode_state: &mut Self::AppModeState) -> Option<ModeState>;
/// Called when the state is left, after update
fn on_leave(context: &mut Context, mode_state: &mut Self::AppModeState);
} }

View file

@ -1,6 +1,5 @@
use crate::{ use crate::{
apps, apps::{self, App},
apps::App,
display::Display, display::Display,
keypad::*, keypad::*,
state::{self, ModeState}, state::{self, ModeState},
@ -24,6 +23,7 @@ pub struct ClockState {
week_day: u8, week_day: u8,
} }
#[allow(clippy::derivable_impls)]
impl Default for ClockState { impl Default for ClockState {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -38,6 +38,16 @@ impl Default for ClockState {
impl App for Clock { impl App for Clock {
type AppModeState = ClockState; 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<ModeState> { fn update(ctx: &mut Context, mode_state: &mut ClockState) -> Option<ModeState> {
// TODO move at init // TODO move at init
let clock_text_style = MonoTextStyleBuilder::new() let clock_text_style = MonoTextStyleBuilder::new()
@ -56,6 +66,7 @@ impl App for Clock {
let mut buf = [0; 4]; let mut buf = [0; 4];
return Some(ModeState::Dial(apps::dial::DialState { return Some(ModeState::Dial(apps::dial::DialState {
line: ArrayString::from(&*ch.encode_utf8(&mut buf)).unwrap(), line: ArrayString::from(&*ch.encode_utf8(&mut buf)).unwrap(),
line_changed: true,
})); }));
} }
} }
@ -109,4 +120,6 @@ impl App for Clock {
None None
} }
fn on_leave(ctx: &mut Context, mode_state: &mut ClockState) {}
} }

View file

@ -7,18 +7,55 @@ use crate::{
}; };
use arrayvec::ArrayString; 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 Dial;
pub struct DialState { pub struct DialState {
// TODO what should max length be? pub line: ArrayString<40>,
pub line: ArrayString<255>, pub line_changed: bool,
} }
impl App for Dial { impl App for Dial {
type AppModeState = DialState; 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<ModeState> { fn update(ctx: &mut Context, mode_state: &mut DialState) -> Option<ModeState> {
// 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 { for key_event in &ctx.key_events {
if key_event.event_type != KeyEventType::Pressed { if key_event.event_type != KeyEventType::Pressed {
continue; continue;
@ -30,16 +67,38 @@ impl App for Dial {
Key::PickUp => { Key::PickUp => {
// TODO // 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 let Some(ch) = key_event.get_char(KeyInputMode::Digit) {
if mode_state.line.try_push(ch).is_err() { if mode_state.line.try_push(ch).is_err() {
// TODO // 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 None
} }
fn on_leave(ctx: &mut Context, mode_state: &mut DialState) {}
} }

View file

@ -72,8 +72,11 @@ fn main() -> ! {
}, },
hour: 0, hour: 0,
minute: 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 display = Display::new();
let mut keypad = keypad::Keypad::default(); let mut keypad = keypad::Keypad::default();
@ -86,11 +89,19 @@ fn main() -> ! {
.text_color(BinaryColor::On) .text_color(BinaryColor::On)
.background_color(BinaryColor::Off) .background_color(BinaryColor::Off)
.build(); .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)) Line::new(Point::new(0, 13), Point::new(199, 13))
.into_styled(thin_stroke) .into_styled(thin_stroke)
.draw(display.inner_mut()) .draw(display.inner_mut())
.unwrap(); .unwrap();
Line::new(Point::new(0, 189), Point::new(199, 189))
.into_styled(thin_stroke)
.draw(display.inner_mut())
.unwrap();
#[cfg(feature = "simulator")] #[cfg(feature = "simulator")]
display.update(); display.update();
@ -98,6 +109,7 @@ fn main() -> ! {
let mut ctx = Context { let mut ctx = Context {
display: &mut display, display: &mut display,
hm_change: true, hm_change: true,
key_labels_change: false,
key_events: Default::default(), key_events: Default::default(),
now: DateTime::from_timespec(0, 0, tz::TimeZoneRef::utc()).unwrap(), now: DateTime::from_timespec(0, 0, tz::TimeZoneRef::utc()).unwrap(),
update: true, update: true,
@ -144,10 +156,61 @@ fn main() -> ! {
ctx.key_events = keypad.update(); ctx.key_events = keypad.update();
if let Some(new_mode_state) = match &mut mode_state { 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::Clock(clock_state) => apps::clock::Clock::update(&mut ctx, clock_state),
ModeState::Dial(dial_state) => apps::dial::Dial::update(&mut ctx, dial_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; 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")] #[cfg(feature = "simulator")]
@ -171,6 +234,7 @@ pub struct Context<'a> {
pub state: &'a mut State, pub state: &'a mut State,
pub display: &'a mut Display, pub display: &'a mut Display,
pub hm_change: bool, pub hm_change: bool,
pub key_labels_change: bool,
pub now: DateTime, pub now: DateTime,
pub update: bool, pub update: bool,
pub key_events: KeyEvents, pub key_events: KeyEvents,

View file

@ -1,3 +1,5 @@
use arrayvec::ArrayString;
use crate::{apps, energy::EnergyStatus}; use crate::{apps, energy::EnergyStatus};
/*pub struct State { /*pub struct State {
@ -9,10 +11,20 @@ pub struct State {
pub energy: EnergyStatus, pub energy: EnergyStatus,
pub hour: u8, pub hour: u8,
pub minute: 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 { pub enum ModeState {
Nothing,
Clock(apps::clock::ClockState), Clock(apps::clock::ClockState),
Dial(apps::dial::DialState), Dial(apps::dial::DialState),
} }
impl Default for ModeState {
fn default() -> Self {
Self::Nothing
}
}