diff --git a/CHANGELOG.md b/CHANGELOG.md index d16d784c..40bb58f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Unreleased + +## Features + +- Change the LED patterns so that the LED is off by default, blinks white during a user confirmation request and blinks blue when winking([#34][]) + +[#34]: https://github.com/Nitrokey/nitrokey-3-firmware/issues/34 + # v1.0.3 (2022-04-11) This release fixes a FIDO authentication issue with Google. diff --git a/runners/lpc55/board/src/lib.rs b/runners/lpc55/board/src/lib.rs index f137da94..60b7c3e2 100644 --- a/runners/lpc55/board/src/lib.rs +++ b/runners/lpc55/board/src/lib.rs @@ -36,5 +36,6 @@ pub use specifics::{ pub mod clock_controller; pub mod nfc; pub mod trussed; +pub mod ui; // pub use rgb_led::RgbLed; diff --git a/runners/lpc55/board/src/trussed.rs b/runners/lpc55/board/src/trussed.rs index 946ce81d..29d7b117 100644 --- a/runners/lpc55/board/src/trussed.rs +++ b/runners/lpc55/board/src/trussed.rs @@ -7,9 +7,10 @@ use crate::hal::{ peripherals::rtc::Rtc, typestates::init_state, }; +use crate::ui::Status; use crate::traits::buttons::{Press, Edge}; -use crate::traits::rgb_led::{Intensities, RgbLed}; -use trussed::platform::{consent, ui}; +use crate::traits::rgb_led::RgbLed; +use trussed::platform::{self, consent}; // Assuming there will only be one way to // get user presence, this should be fine. @@ -32,8 +33,8 @@ RGB: RgbLed, { rtc: Rtc, buttons: Option, + status: Status, rgb: Option, - wink: Option>, provisioner: bool, } @@ -48,25 +49,23 @@ RGB: RgbLed, rgb: Option, provisioner: bool, ) -> Self { - let wink = None; + let status = Default::default(); #[cfg(not(feature = "no-buttons"))] - let ui = Self { rtc, buttons: _buttons, rgb, wink, provisioner }; + let ui = Self { rtc, buttons: _buttons, status, rgb, provisioner }; #[cfg(feature = "no-buttons")] - let ui = Self { rtc, buttons: None, rgb, wink, provisioner }; + let ui = Self { rtc, buttons: None, status, rgb, provisioner }; ui } -} -// color codes Conor picked -const BLACK: Intensities = Intensities { red: 0, green: 0, blue: 0 }; -const RED: Intensities = Intensities { red: u8::MAX, green: 0, blue: 0 }; -const GREEN: Intensities = Intensities { red: 0, green: u8::MAX, blue: 0x02 }; -#[allow(dead_code)] -const BLUE: Intensities = Intensities { red: 0, green: 0, blue: u8::MAX }; -const TEAL: Intensities = Intensities { red: 0, green: u8::MAX, blue: 0x5a }; -const ORANGE: Intensities = Intensities { red: u8::MAX, green: 0x7e, blue: 0 }; -const WHITE: Intensities = Intensities { red: u8::MAX, green: u8::MAX, blue: u8::MAX }; + fn refresh_ui(&mut self, uptime: Duration) { + if let Some(rgb) = &mut self.rgb { + self.status.refresh(uptime); + let mode = self.status.led_mode(self.provisioner); + rgb.set(mode.color(uptime)); + } + } +} impl trussed::platform::UserInterface for UserInterface where @@ -101,62 +100,15 @@ RGB: RgbLed, } } - fn set_status(&mut self, status: ui::Status) { - if let Some(rgb) = &mut self.rgb { - - match status { - ui::Status::Idle => { - if self.provisioner { - // white - rgb.set(WHITE.into()); - } else { - // green - rgb.set(GREEN.into()); - } - }, - ui::Status::Processing => { - // teal - rgb.set(TEAL.into()); - } - ui::Status::WaitingForUserPresence => { - // orange - rgb.set(ORANGE.into()); - }, - ui::Status::Error => { - // Red - rgb.set(RED.into()); - }, - } - - } - - // Abort winking if the device is no longer idle - if status != ui::Status::Idle { - self.wink = None; - } + fn set_status(&mut self, status: platform::ui::Status) { + let uptime = self.uptime(); + self.status.update(status, uptime); + self.refresh_ui(uptime); } fn refresh(&mut self) { - if self.rgb.is_none() { - return; - } - - if let Some(wink) = self.wink.clone() { - let time = self.uptime(); - if wink.contains(&time) { - // 250 ms white, 250 ms off - let color = if (time - wink.start).as_millis() % 500 < 250 { - WHITE - } else { - BLACK - }; - self.rgb.as_mut().unwrap().set(color.into()); - return; - } else { - self.set_status(ui::Status::Idle); - self.wink = None; - } - } + let uptime = self.uptime(); + self.refresh_ui(uptime); } fn uptime(&mut self) -> Duration { @@ -164,8 +116,8 @@ RGB: RgbLed, } fn wink(&mut self, duration: Duration) { - let time = self.uptime(); - self.wink = Some(time..time + duration); - self.rgb.as_mut().unwrap().set(WHITE.into()); + let uptime = self.uptime(); + self.status = Status::Winking(uptime..uptime + duration); + self.refresh_ui(uptime); } } diff --git a/runners/lpc55/board/src/ui.rs b/runners/lpc55/board/src/ui.rs new file mode 100644 index 00000000..1b87fd5d --- /dev/null +++ b/runners/lpc55/board/src/ui.rs @@ -0,0 +1,113 @@ +use core::{ops::Range, time::Duration}; + +use trussed::platform::ui; + +use crate::traits::rgb_led::Intensities; + +const BLACK: Intensities = Intensities { red: 0, green: 0, blue: 0 }; +const RED: Intensities = Intensities { red: u8::MAX, green: 0, blue: 0 }; +const BLUE: Intensities = Intensities { red: 0, green: 0, blue: u8::MAX }; +const TEAL: Intensities = Intensities { red: 0, green: u8::MAX, blue: 0x5a }; +const WHITE: Intensities = Intensities { red: u8::MAX, green: u8::MAX, blue: u8::MAX }; + +pub enum Status { + Idle, + Processing, + WaitingForUserPresence(Duration), + Winking(Range), + Error, +} + +impl Status { + pub fn update(&mut self, status: ui::Status, uptime: Duration) { + if matches!(self, Self::Winking(_)) && status == ui::Status::Idle { + return; + } + *self = (status, uptime).into(); + } + + pub fn refresh(&mut self, uptime: Duration) { + if let Self::Winking(ref range) = self { + if !range.contains(&uptime) { + *self = Self::Idle; + } + } + } + + pub fn led_mode(&self, is_provisioner: bool) -> LedMode { + match self { + Self::Idle => if is_provisioner { + LedMode::constant(WHITE) + } else { + LedMode::constant(BLACK) + }, + Self::Processing => LedMode::constant(TEAL), + Self::WaitingForUserPresence(start) => LedMode::simple_blinking(WHITE, *start), + Self::Error => LedMode::constant(RED), + Self::Winking(range) => LedMode::simple_blinking(BLUE, range.start), + } + } +} + +impl Default for Status { + fn default() -> Self { + Self::Idle + } +} + +impl From<(ui::Status, Duration)> for Status { + fn from((status, uptime): (ui::Status, Duration)) -> Self { + match status { + ui::Status::Idle => Self::Idle, + ui::Status::Processing => Self::Processing, + ui::Status::WaitingForUserPresence => Self::WaitingForUserPresence(uptime), + ui::Status::Error => Self::Error, + } + } +} + +pub enum LedMode { + Constant { + color: Intensities, + }, + Blinking { + on_color: Intensities, + off_color: Intensities, + period: Duration, + start: Duration, + }, +} + +impl LedMode { + pub fn constant(color: Intensities) -> Self { + Self::Constant { color } + } + + pub fn blinking( + on_color: Intensities, + off_color: Intensities, + period: Duration, + start: Duration, + ) -> Self { + Self::Blinking { on_color, off_color, period, start } + } + + pub fn simple_blinking(color: Intensities, start: Duration) -> Self { + Self::blinking(color, BLACK, Duration::from_millis(500), start) + } + + pub fn color(&self, uptime: Duration) -> Intensities { + match self { + Self::Constant { color } => *color, + Self::Blinking { on_color, off_color, period, start } => { + let delta = (uptime - *start).as_millis() % period.as_millis(); + let is_on = delta < period.as_millis() / 2; + if is_on { + *on_color + } else { + *off_color + } + }, + } + } +}