An applicaiton and simulator for a bike lighting system #245
|
@ -11,13 +11,13 @@ use embedded_alloc::Heap;
|
|||
use embedded_hal::{blocking::spi::Write, digital::v2::InputPin};
|
||||
use fixed::types::I16F16;
|
||||
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 rp_pico::{
|
||||
entry,
|
||||
hal::{
|
||||
clocks::init_clocks_and_plls,
|
||||
gpio::{FunctionSio, Pin, PinId, PullDown, SioInput},
|
||||
gpio::{FunctionSio, Pin, PinId, PullUp, SioInput},
|
||||
pac::{CorePeripherals, Peripherals},
|
||||
spi::{Enabled, Spi, SpiDevice, ValidSpiPinout},
|
||||
watchdog::Watchdog,
|
||||
|
@ -30,7 +30,8 @@ use rp_pico::{
|
|||
static HEAP: Heap = Heap::empty();
|
||||
|
||||
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<
|
||||
D: SpiDevice,
|
||||
|
@ -41,10 +42,10 @@ struct BikeUI<
|
|||
NextId: PinId,
|
||||
> {
|
||||
spi: RefCell<Spi<Enabled, D, P, 8>>,
|
||||
left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullDown>,
|
||||
right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullDown>,
|
||||
previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullDown>,
|
||||
next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullDown>,
|
||||
left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullUp>,
|
||||
right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullUp>,
|
||||
previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullUp>,
|
||||
next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullUp>,
|
||||
}
|
||||
|
||||
impl<
|
||||
|
@ -58,10 +59,10 @@ impl<
|
|||
{
|
||||
fn new(
|
||||
spi: Spi<Enabled, D, P, 8>,
|
||||
left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullDown>,
|
||||
right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullDown>,
|
||||
previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullDown>,
|
||||
next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullDown>,
|
||||
left_blinker_button: Pin<LeftId, FunctionSio<SioInput>, PullUp>,
|
||||
right_blinker_button: Pin<RightId, FunctionSio<SioInput>, PullUp>,
|
||||
previous_animation_button: Pin<PreviousId, FunctionSio<SioInput>, PullUp>,
|
||||
next_animation_button: Pin<NextId, FunctionSio<SioInput>, PullUp>,
|
||||
) -> Self {
|
||||
Self {
|
||||
spi: RefCell::new(spi),
|
||||
|
@ -83,13 +84,13 @@ impl<
|
|||
> UI for BikeUI<D, P, LeftId, RightId, PreviousId, NextId>
|
||||
{
|
||||
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)
|
||||
} 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)
|
||||
} 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)
|
||||
} 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)
|
||||
} else {
|
||||
None
|
||||
|
@ -98,14 +99,12 @@ impl<
|
|||
|
||||
fn update_lights(&self, dashboard_lights: DashboardPattern, body_lights: BodyPattern) {
|
||||
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[17] = 0xff;
|
||||
lights[18] = 0xff;
|
||||
lights[19] = 0xff;
|
||||
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 + 2] = (I16F16::from(rgb.b) * 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,
|
||||
);
|
||||
|
||||
let left_blinker_button = pins.gpio18.into_pull_down_input();
|
||||
let right_blinker_button = pins.gpio19.into_function();
|
||||
let previous_animation_button = pins.gpio20.into_function();
|
||||
let next_animation_button = pins.gpio21.into_pull_down_input();
|
||||
let left_blinker_button = pins.gpio18.into_pull_up_input();
|
||||
let right_blinker_button = pins.gpio19.into_pull_up_input();
|
||||
let previous_animation_button = pins.gpio20.into_pull_up_input();
|
||||
let next_animation_button = pins.gpio21.into_pull_up_input();
|
||||
|
||||
let ui = BikeUI::new(
|
||||
spi,
|
||||
|
|
|
@ -169,6 +169,7 @@ impl Animation for Fade {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum FadeDirection {
|
||||
Transition,
|
||||
FadeIn,
|
||||
FadeOut,
|
||||
}
|
||||
|
@ -179,6 +180,7 @@ pub enum BlinkerDirection {
|
|||
}
|
||||
|
||||
pub struct Blinker {
|
||||
transition: Fade,
|
||||
fade_in: Fade,
|
||||
fade_out: Fade,
|
||||
direction: FadeDirection,
|
||||
|
@ -191,10 +193,12 @@ impl Blinker {
|
|||
fn new(
|
||||
starting_dashboard: DashboardPattern,
|
||||
starting_body: BodyPattern,
|
||||
home_dashboard: DashboardPattern,
|
||||
home_body: BodyPattern,
|
||||
direction: BlinkerDirection,
|
||||
time: Instant,
|
||||
) -> Self {
|
||||
let mut ending_dashboard = starting_dashboard.clone();
|
||||
let mut ending_dashboard = home_dashboard.clone();
|
||||
|
||||
match direction {
|
||||
BlinkerDirection::Left => {
|
||||
|
@ -209,7 +213,7 @@ impl Blinker {
|
|||
}
|
||||
}
|
||||
|
||||
let mut ending_body = starting_body.clone();
|
||||
let mut ending_body = home_body.clone();
|
||||
match direction {
|
||||
BlinkerDirection::Left => {
|
||||
for i in 0..30 {
|
||||
|
@ -228,7 +232,7 @@ impl Blinker {
|
|||
}
|
||||
|
||||
Blinker {
|
||||
fade_in: Fade::new(
|
||||
transition: Fade::new(
|
||||
starting_dashboard.clone(),
|
||||
starting_body.clone(),
|
||||
ending_dashboard.clone(),
|
||||
|
@ -236,15 +240,23 @@ impl Blinker {
|
|||
BLINKER_FRAMES,
|
||||
time,
|
||||
),
|
||||
fade_in: Fade::new(
|
||||
home_dashboard.clone(),
|
||||
home_body.clone(),
|
||||
ending_dashboard.clone(),
|
||||
ending_body.clone(),
|
||||
BLINKER_FRAMES,
|
||||
time,
|
||||
),
|
||||
fade_out: Fade::new(
|
||||
ending_dashboard.clone(),
|
||||
ending_body.clone(),
|
||||
starting_dashboard.clone(),
|
||||
starting_body.clone(),
|
||||
home_dashboard.clone(),
|
||||
home_body.clone(),
|
||||
BLINKER_FRAMES,
|
||||
time,
|
||||
),
|
||||
direction: FadeDirection::FadeIn,
|
||||
direction: FadeDirection::Transition,
|
||||
start_time: time,
|
||||
frames: BLINKER_FRAMES,
|
||||
}
|
||||
|
@ -256,6 +268,10 @@ impl Animation for Blinker {
|
|||
let frames = calculate_frames(self.start_time.0, time.0);
|
||||
if frames > self.frames {
|
||||
match self.direction {
|
||||
FadeDirection::Transition => {
|
||||
self.direction = FadeDirection::FadeOut;
|
||||
self.fade_out.start_time = time;
|
||||
}
|
||||
FadeDirection::FadeIn => {
|
||||
self.direction = FadeDirection::FadeOut;
|
||||
self.fade_out.start_time = time;
|
||||
|
@ -269,6 +285,7 @@ impl Animation for Blinker {
|
|||
}
|
||||
|
||||
match self.direction {
|
||||
FadeDirection::Transition => self.transition.tick(time),
|
||||
FadeDirection::FadeIn => self.fade_in.tick(time),
|
||||
FadeDirection::FadeOut => self.fade_out.tick(time),
|
||||
}
|
||||
|
@ -285,9 +302,45 @@ pub enum Event {
|
|||
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)]
|
||||
pub enum State {
|
||||
Pattern(u8),
|
||||
Pattern(Pattern),
|
||||
Brake,
|
||||
LeftBlinker,
|
||||
RightBlinker,
|
||||
|
@ -298,7 +351,7 @@ pub enum State {
|
|||
pub struct App {
|
||||
ui: Box<dyn UI>,
|
||||
state: State,
|
||||
home_state: State,
|
||||
home_pattern: Pattern,
|
||||
current_animation: Box<dyn Animation>,
|
||||
dashboard_lights: DashboardPattern,
|
||||
lights: BodyPattern,
|
||||
|
@ -308,8 +361,8 @@ impl App {
|
|||
pub fn new(ui: Box<dyn UI>) -> Self {
|
||||
Self {
|
||||
ui,
|
||||
state: State::Pattern(0),
|
||||
home_state: State::Pattern(0),
|
||||
state: State::Pattern(Pattern::GayPride),
|
||||
home_pattern: Pattern::GayPride,
|
||||
current_animation: Box::new(DefaultAnimation {}),
|
||||
dashboard_lights: OFF_DASHBOARD,
|
||||
lights: OFF_BODY,
|
||||
|
@ -318,27 +371,16 @@ impl App {
|
|||
|
||||
fn update_animation(&mut self, time: Instant) {
|
||||
match self.state {
|
||||
State::Pattern(0) => {
|
||||
State::Pattern(ref pattern) => {
|
||||
self.current_animation = Box::new(Fade::new(
|
||||
self.dashboard_lights.clone(),
|
||||
self.lights.clone(),
|
||||
PRIDE_DASHBOARD,
|
||||
PRIDE_BODY,
|
||||
pattern.dashboard(),
|
||||
pattern.body(),
|
||||
DEFAULT_FRAMES,
|
||||
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 => {
|
||||
self.current_animation = Box::new(Fade::new(
|
||||
self.dashboard_lights.clone(),
|
||||
|
@ -353,6 +395,8 @@ impl App {
|
|||
self.current_animation = Box::new(Blinker::new(
|
||||
self.dashboard_lights.clone(),
|
||||
self.lights.clone(),
|
||||
self.home_pattern.dashboard(),
|
||||
self.home_pattern.body(),
|
||||
BlinkerDirection::Left,
|
||||
time,
|
||||
));
|
||||
|
@ -361,6 +405,8 @@ impl App {
|
|||
self.current_animation = Box::new(Blinker::new(
|
||||
self.dashboard_lights.clone(),
|
||||
self.lights.clone(),
|
||||
self.home_pattern.dashboard(),
|
||||
self.home_pattern.body(),
|
||||
BlinkerDirection::Right,
|
||||
time,
|
||||
));
|
||||
|
@ -374,41 +420,36 @@ impl App {
|
|||
match event {
|
||||
Event::Brake => {
|
||||
if self.state == State::Brake {
|
||||
self.state = self.home_state.clone();
|
||||
self.state = State::Pattern(self.home_pattern);
|
||||
} else {
|
||||
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 {
|
||||
State::Brake => self.state = State::BrakeLeftBlinker,
|
||||
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,
|
||||
},
|
||||
Event::NextPattern => match self.state {
|
||||
State::Pattern(i) => {
|
||||
let next = i + 1;
|
||||
self.state = State::Pattern(if next > 1 { 0 } else { next });
|
||||
self.home_state = self.state.clone();
|
||||
State::Pattern(ref pattern) => {
|
||||
self.home_pattern = pattern.next();
|
||||
self.state = State::Pattern(self.home_pattern);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::PreviousPattern => match self.state {
|
||||
State::Pattern(i) => {
|
||||
if i == 0 {
|
||||
self.state = State::Pattern(1);
|
||||
} else {
|
||||
self.state = State::Pattern(i - 1);
|
||||
}
|
||||
self.home_state = self.state.clone();
|
||||
State::Pattern(ref pattern) => {
|
||||
self.home_pattern = pattern.previous();
|
||||
self.state = State::Pattern(self.home_pattern);
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Event::RightBlinker => match self.state {
|
||||
State::Brake => self.state = State::BrakeRightBlinker,
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ pub const BRAKES_RED: RGB<I8F8> = RGB {
|
|||
|
||||
pub const BLINKER_AMBER: RGB<I8F8> = RGB {
|
||||
r: I8F8::lit("1"),
|
||||
g: I8F8::lit("0.74"),
|
||||
g: I8F8::lit("0.15"),
|
||||
b: I8F8::lit("0"),
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue