~ruther/simple-clock

2960f7a05ffe247364fe4605d630298fa57117be — František Boháček 1 year, 8 months ago a3e908e
feat(src): add clock edit mode

Adds modes for the clock app, that may be
changed. The new mode added is clock edit mode,
that adds support for editing the current clock.
M source/src/calendar.rs => source/src/calendar.rs +3 -3
@@ 1,4 1,4 @@
use core::cmp::{max, min};
use core::cmp::max;

#[derive(Clone, PartialEq, Eq)]
pub struct Calendar {


@@ 46,7 46,7 @@ impl Calendar {

    /// Calculate current date based off of the seconds elapsed since
    /// a base date
    pub fn from_ticks(base_year: u16, mut seconds: u32) -> Self {
    pub fn from_ticks(base_year: u16, seconds: u32) -> Self {
        let total_seconds = seconds;
        let seconds = total_seconds % 60;



@@ 255,7 255,7 @@ impl Calendar {
    }

    pub fn unfreeze(&mut self) {
        self.frozen = true;
        self.frozen = false;
    }

    fn days_in_month(month: u8, leap_year: bool) -> u8 {

M source/src/clock_app.rs => source/src/clock_app.rs +42 -107
@@ 1,43 1,28 @@
use core::marker::PhantomData;

use alloc::boxed::Box;
use stm32f1xx_hal::rtc::Rtc;

use crate::{
    brightness_manager::BrightnessManager,
    button::ButtonState,
    clock_display_viewer::{ClockDisplayViewer, DisplayView},
    clock_state::ClockState,
    clock_display_viewer::ClockDisplayViewer,
    clock_state::ClockState, app_mode::{ClockAppMode, ClockAppModes, default_app_mode::DefaultAppMode, edit_app_mode::EditAppMode},
};

pub struct ClockApp {
    rtc: Rtc,
    display: ClockDisplayViewer,
    state: ClockState,
    buttons: [Box<dyn ClockButton + Send>; 4],
    modes: [Box<dyn ClockAppMode + Send>; core::mem::variant_count::<ClockAppModes>()],
    brightness: BrightnessManager,
    current_view: DisplayView,
}

struct AppState<'a> {
    rtc: &'a mut Rtc,
    display: &'a mut ClockDisplayViewer,
    state: &'a mut ClockState,
    brightness: &'a mut BrightnessManager,
    current_view: &'a mut DisplayView,
}

trait ClockButton {
    fn handle(&self, state: ButtonState, state: AppState);
    current_mode: ClockAppModes,
}

struct ButtonSwitchView;
struct ButtonChangeTime;

struct Up;
struct Down;
struct ButtonBrightness<Direction> {
    direction: PhantomData<Direction>,
pub struct AppState<'a> {
    pub rtc: &'a mut Rtc,
    pub display: &'a mut ClockDisplayViewer,
    pub state: &'a mut ClockState,
    pub brightness: &'a mut BrightnessManager,
    pub current_mode: &'a mut ClockAppModes,
}

pub enum ClockInterrupt {


@@ 51,16 36,10 @@ impl ClockApp {
            rtc,
            display,
            state,
            current_view: DisplayView::ClockView,
            buttons: [
                Box::new(ButtonSwitchView),
                Box::new(ButtonChangeTime),
                Box::new(ButtonBrightness::<Down> {
                    direction: PhantomData::<Down>,
                }),
                Box::new(ButtonBrightness::<Up> {
                    direction: PhantomData::<Up>,
                }),
            current_mode: ClockAppModes::NormalMode,
            modes: [
                Box::new(DefaultAppMode::new()),
                Box::new(EditAppMode::new())
            ],
            brightness: BrightnessManager::new(),
        }


@@ 74,96 53,52 @@ impl ClockApp {
            }
            ClockInterrupt::DisplayTimer => {
                let _ = self.display.update(&self.state);
                self.brightness.update(&self.state);
                self.brightness.apply_brightness(&mut self.display);

                let mut mode = self.current_mode;
                let app_state = AppState {
                    rtc: &mut self.rtc,
                    display: &mut self.display,
                    state: &mut self.state,
                    brightness: &mut self.brightness,
                    current_mode: &mut mode,
                };
                self.modes[self.current_mode as usize].update(app_state);
            }
        }
    }

    pub fn handle_button(&mut self, index: usize, state: ButtonState) {
        self.buttons[index].handle(
            state,
            AppState {
        let mut mode = self.current_mode;
        let current_mode = self.modes[self.current_mode as usize].as_mut();

        {
            let app_state = AppState {
                rtc: &mut self.rtc,
                display: &mut self.display,
                state: &mut self.state,
                brightness: &mut self.brightness,
                current_view: &mut self.current_view,
            },
        );
    }

    pub fn display(&mut self) -> &mut ClockDisplayViewer {
        &mut self.display
    }
}
                current_mode: &mut mode,
            };

impl ClockButton for ButtonSwitchView {
    fn handle(&self, state: ButtonState, app: AppState) {
        match state {
            ButtonState::JustPressed => {
                let display = app.display;
                let current_view = *app.current_view as usize;
                let new_view = ((current_view + 1) % core::mem::variant_count::<DisplayView>())
                    .try_into()
                    .unwrap();
                display.set_current_view(new_view);
                *app.current_view = new_view;
            }
            _ => (),
            current_mode.handle_button(app_state, index, state);
        }
    }
}

// How long to turn off the brightness adjustment on user brightness change, in seconds
const BRIGHT_OFF_FOR: u32 = 30 * 60;
const BRIGHT_CHANGE: i8 = 10; // How much to change the brightness on press

impl ClockButton for ButtonBrightness<Down> {
    fn handle(&self, state: ButtonState, app: AppState) {
        match state {
            ButtonState::JustPressed | ButtonState::LongPress => {
                app.brightness.turn_off_for(&app.state, BRIGHT_OFF_FOR);
                app.brightness
                    .set_brightness(app.brightness.brightness() as i8 - BRIGHT_CHANGE);
        if self.current_mode != mode {
            let mut temp_mode = mode;
            {
                current_mode.stop(AppState { rtc: &mut self.rtc, display: &mut self.display, state: &mut self.state, brightness: &mut self.brightness, current_mode: &mut temp_mode });
            }
            _ => (),
        }
    }
}

impl ClockButton for ButtonBrightness<Up> {
    fn handle(&self, state: ButtonState, app: AppState) {
        match state {
            ButtonState::JustPressed | ButtonState::LongPress => {
                app.brightness.turn_off_for(&app.state, BRIGHT_OFF_FOR);
                app.brightness
                    .set_brightness(app.brightness.brightness() as i8 + BRIGHT_CHANGE);
            }
            _ => (),
            self.current_mode = temp_mode;

            let current_mode = self.modes[self.current_mode as usize].as_mut();
            current_mode.run(AppState { rtc: &mut self.rtc, display: &mut self.display, state: &mut self.state, brightness: &mut self.brightness, current_mode: &mut temp_mode });
        }
    }
}

impl ClockButton for ButtonChangeTime {
    fn handle(&self, state: ButtonState, app: AppState) {
        match state {
            ButtonState::JustPressed => {
                app.rtc.set_time(app.rtc.current_time() + 2);
            }
            _ => (),
        }
    pub fn display(&mut self) -> &mut ClockDisplayViewer {
        &mut self.display
    }
}

// to edit date, second button can be used
// to enter edit mode
// DateEditMode
//   editing_field
//     1. Hours, 2. Minutes, 3. Seconds,
//     4. Year,  5. Month,   6. Day
//   button functions:
//     1. next field
//     2. current_field++
//     3. current_field--
//     4. set

M source/src/clock_display.rs => source/src/clock_display.rs +5 -0
@@ 124,6 124,11 @@ impl ClockDisplay {
        let offset = Self::get_part_offset(part);
        let size = Self::get_part_size(part);

        self.hide_at(offset, size);
    }

    pub fn hide_at(&mut self, offset: usize, size: usize) {

        let mut data = self.display.data();
        for current_data in data.iter_mut().skip(offset).take(size) {
            *current_data = 0;

M source/src/clock_display_viewer.rs => source/src/clock_display_viewer.rs +17 -6
@@ 1,5 1,3 @@
use core::mem;

use crate::{
    clock_display::{ClockDisplay, DisplayPart},
    clock_state::ClockState,


@@ 29,6 27,7 @@ impl TryFrom<usize> for DisplayView {

#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(usize)]
#[derive(core::marker::ConstParamTy)]
pub enum ClockPart {
    Hours = 0,
    Minutes = 1,


@@ 75,10 74,6 @@ impl ClockDisplayViewer {
        for part in self.parts.iter_mut() {
            *part = false;
        }

        self.clock_display.hide(DisplayPart::MainDisplay);
        self.clock_display.hide(DisplayPart::SideDisplay1);
        self.clock_display.hide(DisplayPart::SideDisplay2);
    }

    pub fn clock_display(&mut self) -> &mut ClockDisplay {


@@ 180,6 175,22 @@ impl ClockDisplayViewer {
            }
        }

        if !self.parts[ClockPart::Day as usize] {
            self.clock_display.hide(DisplayPart::SideDisplay1);
        }

        if !self.parts[ClockPart::Month as usize] && !self.parts[ClockPart::Seconds as usize] {
            self.clock_display.hide(DisplayPart::SideDisplay2);
        }

        if !self.parts[ClockPart::Hours as usize] && !self.parts[ClockPart::Year as usize] {
            self.clock_display.hide_at(ClockDisplay::get_part_offset(DisplayPart::MainDisplay), 2);
        }

        if !self.parts[ClockPart::Minutes as usize] && !self.parts[ClockPart::Year as usize] {
            self.clock_display.hide_at(ClockDisplay::get_part_offset(DisplayPart::MainDisplay) + 2, 2);
        }

        if self.parts[ClockPart::Hours as usize] && self.parts[ClockPart::Minutes as usize] {
            self.clock_display
                .set_colon(state.calendar().seconds() % 2 == 0);

M source/src/clock_state.rs => source/src/clock_state.rs +4 -0
@@ 20,6 20,10 @@ impl ClockState {
        &self.calendar
    }

    pub fn mut_calendar(&mut self) -> &mut Calendar {
        &mut self.calendar
    }

    pub fn second_elapsed(&mut self) {
        self.calendar.second_elapsed()
    }

M source/src/lib.rs => source/src/lib.rs +2 -0
@@ 1,6 1,7 @@
#![cfg_attr(test, no_main)]
#![no_std]
#![feature(variant_count)]
#![feature(adt_const_params)]

use defmt_rtt as _; // global logger



@@ 21,6 22,7 @@ pub mod linear_interpolation;
pub mod mono_timer;
pub mod number_digits;
pub mod seven_segments;
pub mod app_mode;

extern crate alloc;


M source/src/main.rs => source/src/main.rs +2 -0
@@ 2,6 2,7 @@
#![no_main]
#![feature(alloc_error_handler)]
#![feature(variant_count)]
#![feature(adt_const_params)]

extern crate alloc;



@@ 18,6 19,7 @@ pub mod linear_interpolation;
pub mod mono_timer;
pub mod number_digits;
pub mod seven_segments;
pub mod app_mode;

use alloc::boxed::Box;
use button::{ActiveHigh, Button, ButtonState};

Do not follow this link