An applicaiton and simulator for a bike lighting system #245

Merged
savanni merged 16 commits from bike-lights into main 2024-08-01 01:32:29 +00:00
3 changed files with 102 additions and 62 deletions
Showing only changes of commit 54c4b99ab6 - Show all commits

View File

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

View File

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

View File

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