This commit is contained in:
Pascal Engélibert 2023-09-11 22:22:20 +02:00
parent 8dda09689d
commit d8865488e3
12 changed files with 328 additions and 166 deletions

41
Cargo.lock generated
View file

@ -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"

View file

@ -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" }

View file

@ -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 ?

View file

@ -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<ModeState>;
}

112
src/apps/clock.rs Normal file
View file

@ -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<ModeState> {
// 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
}
}

View file

@ -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<ModeState> {
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
}
}

29
src/fs.rs Normal file
View file

@ -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<SdMmcSpi<maduino_zero_4g::SdSpi, maduino_zero_4g::SdCs>, 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,
}
}
}

View file

@ -1 +1 @@
pub trait Widget {}

View file

@ -4,6 +4,8 @@ const NB_KEYS: usize = 21;
/// Key repeat max delay (ms)
const REPEAT_DELAY: u64 = 500;
pub type KeyEvents = ArrayVec<KeyEvent, 4>;
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<KeyEvent, 4> {
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<char> {
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,

View file

@ -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,
}

View file

@ -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),
}

View file

@ -1,5 +1,3 @@
use tz::DateTime;
use crate::energy::EnergyStatus;
static WEEK_DAYS: [[u8; 3]; 7] = [