Improve the blinker animations and state transitions when switching blinkers

This commit is contained in:
Savanni D'Gerinel 2023-12-14 22:03:51 -05:00
parent 72b62ecb5d
commit d40e9bb3d0
3 changed files with 102 additions and 62 deletions

View File

@ -11,13 +11,13 @@ use embedded_alloc::Heap;
use embedded_hal::{blocking::spi::Write, digital::v2::InputPin}; use embedded_hal::{blocking::spi::Write, digital::v2::InputPin};
use fixed::types::I16F16; use fixed::types::I16F16;
use fugit::RateExtU32; use fugit::RateExtU32;
use lights_core::{App, BodyPattern, DashboardPattern, Event, Instant, State, FPS, UI}; use lights_core::{App, BodyPattern, DashboardPattern, Event, Instant, FPS, UI};
use panic_halt as _; use panic_halt as _;
use rp_pico::{ use rp_pico::{
entry, entry,
hal::{ hal::{
clocks::init_clocks_and_plls, clocks::init_clocks_and_plls,
gpio::{FunctionSio, Pin, PinId, PullDown, SioInput}, gpio::{FunctionSio, Pin, PinId, PullUp, SioInput},
pac::{CorePeripherals, Peripherals}, pac::{CorePeripherals, Peripherals},
spi::{Enabled, Spi, SpiDevice, ValidSpiPinout}, spi::{Enabled, Spi, SpiDevice, ValidSpiPinout},
watchdog::Watchdog, watchdog::Watchdog,
@ -30,7 +30,8 @@ use rp_pico::{
static HEAP: Heap = Heap::empty(); static HEAP: Heap = Heap::empty();
const LIGHT_SCALE: I16F16 = I16F16::lit("256.0"); const LIGHT_SCALE: I16F16 = I16F16::lit("256.0");
const GLOBAL_BRIGHTESS: u8 = 1; const DASHBOARD_BRIGHTESS: u8 = 1;
const BODY_BRIGHTNESS: u8 = 8;
struct BikeUI< struct BikeUI<
D: SpiDevice, D: SpiDevice,
@ -41,10 +42,10 @@ struct BikeUI<
NextId: PinId, NextId: PinId,
> { > {
spi: RefCell<Spi<Enabled, D, P, 8>>, spi: RefCell<Spi<Enabled, D, P, 8>>,
left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullDown>, left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullUp>,
right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullDown>, right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullUp>,
previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullDown>, previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullUp>,
next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullDown>, next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullUp>,
} }
impl< impl<
@ -58,10 +59,10 @@ impl<
{ {
fn new( fn new(
spi: Spi<Enabled, D, P, 8>, spi: Spi<Enabled, D, P, 8>,
left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullDown>, left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullUp>,
right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullDown>, right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullUp>,
previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullDown>, previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullUp>,
next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullDown>, next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullUp>,
) -> Self { ) -> Self {
Self { Self {
spi: RefCell::new(spi), spi: RefCell::new(spi),
@ -83,13 +84,13 @@ impl<
> UI for BikeUI<D, P, LeftId, RightId, PreviousId, NextId> > UI for BikeUI<D, P, LeftId, RightId, PreviousId, NextId>
{ {
fn check_event(&self) -> Option<Event> { fn check_event(&self) -> Option<Event> {
if self.left_blinker_button.is_high().unwrap_or(false) { if self.left_blinker_button.is_low().unwrap_or(false) {
Some(Event::LeftBlinker) Some(Event::LeftBlinker)
} else if self.right_blinker_button.is_high().unwrap_or(false) { } else if self.right_blinker_button.is_low().unwrap_or(false) {
Some(Event::RightBlinker) Some(Event::RightBlinker)
} else if self.previous_animation_button.is_high().unwrap_or(false) { } else if self.previous_animation_button.is_low().unwrap_or(false) {
Some(Event::PreviousPattern) Some(Event::PreviousPattern)
} else if self.next_animation_button.is_high().unwrap_or(false) { } else if self.next_animation_button.is_low().unwrap_or(false) {
Some(Event::NextPattern) Some(Event::NextPattern)
} else { } else {
None None
@ -98,14 +99,12 @@ impl<
fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern) { fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern) {
let mut lights: [u8; 20] = [0; 20]; let mut lights: [u8; 20] = [0; 20];
// Check https://www.pololu.com/product/3089 for the end frame calculations. It is not what
// I thought.
lights[16] = 0xff; lights[16] = 0xff;
lights[17] = 0xff; lights[17] = 0xff;
lights[18] = 0xff; lights[18] = 0xff;
lights[19] = 0xff; lights[19] = 0xff;
for (idx, rgb) in dashboard_lights.iter().enumerate() { for (idx, rgb) in dashboard_lights.iter().enumerate() {
lights[(idx + 1) * 4 + 0] = 0xe0 + GLOBAL_BRIGHTESS; lights[(idx + 1) * 4 + 0] = 0xe0 + DASHBOARD_BRIGHTESS;
lights[(idx + 1) * 4 + 1] = (I16F16::from(rgb.r) * LIGHT_SCALE).saturating_as(); lights[(idx + 1) * 4 + 1] = (I16F16::from(rgb.r) * LIGHT_SCALE).saturating_as();
lights[(idx + 1) * 4 + 2] = (I16F16::from(rgb.b) * LIGHT_SCALE).saturating_as(); lights[(idx + 1) * 4 + 2] = (I16F16::from(rgb.b) * LIGHT_SCALE).saturating_as();
lights[(idx + 1) * 4 + 3] = (I16F16::from(rgb.g) * LIGHT_SCALE).saturating_as(); lights[(idx + 1) * 4 + 3] = (I16F16::from(rgb.g) * LIGHT_SCALE).saturating_as();
@ -159,10 +158,10 @@ fn main() -> ! {
embedded_hal::spi::MODE_1, embedded_hal::spi::MODE_1,
); );
let left_blinker_button = pins.gpio18.into_pull_down_input(); let left_blinker_button = pins.gpio18.into_pull_up_input();
let right_blinker_button = pins.gpio19.into_function(); let right_blinker_button = pins.gpio19.into_pull_up_input();
let previous_animation_button = pins.gpio20.into_function(); let previous_animation_button = pins.gpio20.into_pull_up_input();
let next_animation_button = pins.gpio21.into_pull_down_input(); let next_animation_button = pins.gpio21.into_pull_up_input();
let ui = BikeUI::new( let ui = BikeUI::new(
spi, spi,

View File

@ -169,6 +169,7 @@ impl Animation for Fade {
#[derive(Debug)] #[derive(Debug)]
pub enum FadeDirection { pub enum FadeDirection {
Transition,
FadeIn, FadeIn,
FadeOut, FadeOut,
} }
@ -179,6 +180,7 @@ pub enum BlinkerDirection {
} }
pub struct Blinker { pub struct Blinker {
transition: Fade,
fade_in: Fade, fade_in: Fade,
fade_out: Fade, fade_out: Fade,
direction: FadeDirection, direction: FadeDirection,
@ -191,10 +193,12 @@ impl Blinker {
fn new( fn new(
starting_dashboard: DashboardPattern, starting_dashboard: DashboardPattern,
starting_body: BodyPattern, starting_body: BodyPattern,
home_dashboard: DashboardPattern,
home_body: BodyPattern,
direction: BlinkerDirection, direction: BlinkerDirection,
time: Instant, time: Instant,
) -> Self { ) -> Self {
let mut ending_dashboard = starting_dashboard.clone(); let mut ending_dashboard = home_dashboard.clone();
match direction { match direction {
BlinkerDirection::Left => { BlinkerDirection::Left => {
@ -209,7 +213,7 @@ impl Blinker {
} }
} }
let mut ending_body = starting_body.clone(); let mut ending_body = home_body.clone();
match direction { match direction {
BlinkerDirection::Left => { BlinkerDirection::Left => {
for i in 0..30 { for i in 0..30 {
@ -228,7 +232,7 @@ impl Blinker {
} }
Blinker { Blinker {
fade_in: Fade::new( transition: Fade::new(
starting_dashboard.clone(), starting_dashboard.clone(),
starting_body.clone(), starting_body.clone(),
ending_dashboard.clone(), ending_dashboard.clone(),
@ -236,15 +240,23 @@ impl Blinker {
BLINKER_FRAMES, BLINKER_FRAMES,
time, time,
), ),
fade_in: Fade::new(
home_dashboard.clone(),
home_body.clone(),
ending_dashboard.clone(),
ending_body.clone(),
BLINKER_FRAMES,
time,
),
fade_out: Fade::new( fade_out: Fade::new(
ending_dashboard.clone(), ending_dashboard.clone(),
ending_body.clone(), ending_body.clone(),
starting_dashboard.clone(), home_dashboard.clone(),
starting_body.clone(), home_body.clone(),
BLINKER_FRAMES, BLINKER_FRAMES,
time, time,
), ),
direction: FadeDirection::FadeIn, direction: FadeDirection::Transition,
start_time: time, start_time: time,
frames: BLINKER_FRAMES, frames: BLINKER_FRAMES,
} }
@ -256,6 +268,10 @@ impl Animation for Blinker {
let frames = calculate_frames(self.start_time.0, time.0); let frames = calculate_frames(self.start_time.0, time.0);
if frames > self.frames { if frames > self.frames {
match self.direction { match self.direction {
FadeDirection::Transition => {
self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time;
}
FadeDirection::FadeIn => { FadeDirection::FadeIn => {
self.direction = FadeDirection::FadeOut; self.direction = FadeDirection::FadeOut;
self.fade_out.start_time = time; self.fade_out.start_time = time;
@ -269,6 +285,7 @@ impl Animation for Blinker {
} }
match self.direction { match self.direction {
FadeDirection::Transition => self.transition.tick(time),
FadeDirection::FadeIn => self.fade_in.tick(time), FadeDirection::FadeIn => self.fade_in.tick(time),
FadeDirection::FadeOut => self.fade_out.tick(time), FadeDirection::FadeOut => self.fade_out.tick(time),
} }
@ -285,9 +302,45 @@ pub enum Event {
RightBlinker, RightBlinker,
} }
#[derive(Clone, Copy, PartialEq)]
pub enum Pattern {
GayPride,
TransPride,
}
impl Pattern {
fn previous(&self) -> Pattern {
match self {
Pattern::GayPride => Pattern::TransPride,
Pattern::TransPride => Pattern::GayPride,
}
}
fn next(&self) -> Pattern {
match self {
Pattern::GayPride => Pattern::TransPride,
Pattern::TransPride => Pattern::GayPride,
}
}
fn dashboard(&self) -> DashboardPattern {
match self {
Pattern::GayPride => PRIDE_DASHBOARD,
Pattern::TransPride => TRANS_PRIDE_DASHBOARD,
}
}
fn body(&self) -> BodyPattern {
match self {
Pattern::GayPride => PRIDE_BODY,
Pattern::TransPride => TRANS_PRIDE_BODY,
}
}
}
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum State { pub enum State {
Pattern(u8), Pattern(Pattern),
Brake, Brake,
LeftBlinker, LeftBlinker,
RightBlinker, RightBlinker,
@ -298,7 +351,7 @@ pub enum State {
pub struct App { pub struct App {
ui: Box<dyn UI>, ui: Box<dyn UI>,
state: State, state: State,
home_state: State, home_pattern: Pattern,
current_animation: Box<dyn Animation>, current_animation: Box<dyn Animation>,
dashboard_lights: DashboardPattern, dashboard_lights: DashboardPattern,
lights: BodyPattern, lights: BodyPattern,
@ -308,8 +361,8 @@ impl App {
pub fn new(ui: Box<dyn UI>) -> Self { pub fn new(ui: Box<dyn UI>) -> Self {
Self { Self {
ui, ui,
state: State::Pattern(0), state: State::Pattern(Pattern::GayPride),
home_state: State::Pattern(0), home_pattern: Pattern::GayPride,
current_animation: Box::new(DefaultAnimation {}), current_animation: Box::new(DefaultAnimation {}),
dashboard_lights: OFF_DASHBOARD, dashboard_lights: OFF_DASHBOARD,
lights: OFF_BODY, lights: OFF_BODY,
@ -318,27 +371,16 @@ impl App {
fn update_animation(&mut self, time: Instant) { fn update_animation(&mut self, time: Instant) {
match self.state { match self.state {
State::Pattern(0) => { State::Pattern(ref pattern) => {
self.current_animation = Box::new(Fade::new( self.current_animation = Box::new(Fade::new(
self.dashboard_lights.clone(), self.dashboard_lights.clone(),
self.lights.clone(), self.lights.clone(),
PRIDE_DASHBOARD, pattern.dashboard(),
PRIDE_BODY, pattern.body(),
DEFAULT_FRAMES, DEFAULT_FRAMES,
time, time,
)) ))
} }
State::Pattern(1) => {
self.current_animation = Box::new(Fade::new(
self.dashboard_lights.clone(),
self.lights.clone(),
TRANS_PRIDE_DASHBOARD,
TRANS_PRIDE_BODY,
DEFAULT_FRAMES,
time,
))
}
State::Pattern(_) => {}
State::Brake => { State::Brake => {
self.current_animation = Box::new(Fade::new( self.current_animation = Box::new(Fade::new(
self.dashboard_lights.clone(), self.dashboard_lights.clone(),
@ -353,6 +395,8 @@ impl App {
self.current_animation = Box::new(Blinker::new( self.current_animation = Box::new(Blinker::new(
self.dashboard_lights.clone(), self.dashboard_lights.clone(),
self.lights.clone(), self.lights.clone(),
self.home_pattern.dashboard(),
self.home_pattern.body(),
BlinkerDirection::Left, BlinkerDirection::Left,
time, time,
)); ));
@ -361,6 +405,8 @@ impl App {
self.current_animation = Box::new(Blinker::new( self.current_animation = Box::new(Blinker::new(
self.dashboard_lights.clone(), self.dashboard_lights.clone(),
self.lights.clone(), self.lights.clone(),
self.home_pattern.dashboard(),
self.home_pattern.body(),
BlinkerDirection::Right, BlinkerDirection::Right,
time, time,
)); ));
@ -374,41 +420,36 @@ impl App {
match event { match event {
Event::Brake => { Event::Brake => {
if self.state == State::Brake { if self.state == State::Brake {
self.state = self.home_state.clone(); self.state = State::Pattern(self.home_pattern);
} else { } else {
self.state = State::Brake; self.state = State::Brake;
} }
} }
Event::BrakeRelease => self.state = self.home_state.clone(), Event::BrakeRelease => self.state = State::Pattern(self.home_pattern),
Event::LeftBlinker => match self.state { Event::LeftBlinker => match self.state {
State::Brake => self.state = State::BrakeLeftBlinker, State::Brake => self.state = State::BrakeLeftBlinker,
State::BrakeLeftBlinker => self.state = State::Brake, State::BrakeLeftBlinker => self.state = State::Brake,
State::LeftBlinker => self.state = self.home_state.clone(), State::LeftBlinker => self.state = State::Pattern(self.home_pattern),
_ => self.state = State::LeftBlinker, _ => self.state = State::LeftBlinker,
}, },
Event::NextPattern => match self.state { Event::NextPattern => match self.state {
State::Pattern(i) => { State::Pattern(ref pattern) => {
let next = i + 1; self.home_pattern = pattern.next();
self.state = State::Pattern(if next > 1 { 0 } else { next }); self.state = State::Pattern(self.home_pattern);
self.home_state = self.state.clone();
} }
_ => (), _ => (),
}, },
Event::PreviousPattern => match self.state { Event::PreviousPattern => match self.state {
State::Pattern(i) => { State::Pattern(ref pattern) => {
if i == 0 { self.home_pattern = pattern.previous();
self.state = State::Pattern(1); self.state = State::Pattern(self.home_pattern);
} else {
self.state = State::Pattern(i - 1);
}
self.home_state = self.state.clone();
} }
_ => (), _ => (),
}, },
Event::RightBlinker => match self.state { Event::RightBlinker => match self.state {
State::Brake => self.state = State::BrakeRightBlinker, State::Brake => self.state = State::BrakeRightBlinker,
State::BrakeRightBlinker => self.state = State::Brake, State::BrakeRightBlinker => self.state = State::Brake,
State::RightBlinker => self.state = self.home_state.clone(), State::RightBlinker => self.state = State::Pattern(self.home_pattern),
_ => self.state = State::RightBlinker, _ => self.state = State::RightBlinker,
}, },
} }

View File

@ -21,7 +21,7 @@ pub const BRAKES_RED: RGB<I8F8> = RGB {
pub const BLINKER_AMBER: RGB<I8F8> = RGB { pub const BLINKER_AMBER: RGB<I8F8> = RGB {
r: I8F8::lit("1"), r: I8F8::lit("1"),
g: I8F8::lit("0.74"), g: I8F8::lit("0.15"),
b: I8F8::lit("0"), b: I8F8::lit("0"),
}; };