|
//! My custom panel |
|
|
|
#![no_main] |
|
#![no_std] |
|
|
|
// Required for panic handler |
|
extern crate flipperzero_rt; |
|
|
|
// Required for panic handler |
|
extern crate flipperzero_alloc; |
|
|
|
extern crate alloc; |
|
|
|
use alloc::vec; |
|
use alloc::vec::Vec; |
|
use core::ffi::CStr; |
|
|
|
use core::ffi::c_void; |
|
|
|
// GUI record |
|
const RECORD_GUI: &CStr = c"gui"; |
|
|
|
const FULLSCREEN: sys::GuiLayer = sys::GuiLayerFullscreen; |
|
|
|
use flipperzero; |
|
use flipperzero::furi::message_queue::MessageQueue; |
|
use flipperzero_rt::{entry, manifest}; |
|
use flipperzero_sys as sys; |
|
|
|
enum SnakeEvent { |
|
EventKey(sys::InputEvent), |
|
} |
|
type EventQueue = MessageQueue<SnakeEvent>; |
|
|
|
// Define the FAP Manifest for this application |
|
manifest!( |
|
name = "Charlie the Snake", |
|
app_version = 1, |
|
has_icon = true, |
|
// See https://github.com/flipperzero-rs/flipperzero/blob/v0.11.0/docs/icons.md for icon format |
|
icon = "../snake-10x10.icon", |
|
); |
|
|
|
// Define the entry function |
|
entry!(main); |
|
|
|
/// Input event handler. |
|
unsafe extern "C" fn input_callback(event: *mut sys::InputEvent, context: *mut c_void) { |
|
assert!(!context.is_null()); |
|
|
|
let q: &mut EventQueue = unsafe { &mut *(context as *mut EventQueue) }; |
|
|
|
let snake_event = SnakeEvent::EventKey(*event); |
|
|
|
// will block (this thread) until queue frees up |
|
let _ = q.put( |
|
snake_event, |
|
flipperzero::furi::time::FuriDuration::WAIT_FOREVER, |
|
); |
|
} |
|
|
|
#[derive(Clone, Copy)] |
|
struct State { |
|
high: sys::GpioPin, |
|
low: sys::GpioPin, |
|
} |
|
|
|
unsafe fn led_pins() -> [sys::GpioPin; 6] { |
|
[ |
|
sys::gpio_ext_pa7, |
|
sys::gpio_ext_pa6, |
|
sys::gpio_ext_pa4, |
|
sys::gpio_ext_pb3, |
|
sys::gpio_ext_pb2, |
|
sys::gpio_ext_pc3, |
|
] |
|
} |
|
|
|
unsafe fn all_off() { |
|
for pin in led_pins() { |
|
sys::furi_hal_gpio_init( |
|
&pin, |
|
sys::GpioModeInput, |
|
sys::GpioPullNo, |
|
sys::GpioSpeedVeryHigh, |
|
); |
|
} |
|
} |
|
|
|
fn lookup_pins(x: u8, y: u8) -> (u8, u8) { |
|
let a7 = 0; |
|
let a6 = 1; |
|
let a4 = 2; |
|
let b3 = 3; |
|
let b2 = 4; |
|
let c3 = 5; |
|
match (x, y) { |
|
// First double column |
|
(0, 0) => (a7, a6), |
|
(1, 0) => (a6, a7), |
|
|
|
(0, 1) => (a6, a4), |
|
(1, 1) => (a4, a6), |
|
|
|
(0, 2) => (a4, b3), |
|
(1, 2) => (b3, a4), |
|
|
|
(0, 3) => (b3, b2), |
|
(1, 3) => (b2, b3), |
|
|
|
(0, 4) => (b2, c3), |
|
(1, 4) => (c3, b2), |
|
|
|
// Second double column |
|
(2, 0) => (a7, a4), |
|
(3, 0) => (a4, a7), |
|
|
|
(2, 1) => (a4, b2), |
|
(3, 1) => (b2, a4), |
|
|
|
(2, 2) => (b2, a7), |
|
(3, 2) => (a7, b2), |
|
|
|
(2, 3) => (a7, b3), |
|
(3, 3) => (b3, a7), |
|
|
|
(2, 4) => (b3, c3), |
|
(3, 4) => (c3, b3), |
|
|
|
// Third double column |
|
(4, 0) => (b3, a6), |
|
(5, 0) => (a6, b3), |
|
|
|
(4, 1) => (a6, b2), |
|
(5, 1) => (b2, a6), |
|
|
|
(4, 2) => (b2, a7), |
|
(5, 2) => (a7, b2), |
|
|
|
(4, 3) => (a7, c3), |
|
(5, 3) => (c3, a7), |
|
|
|
(4, 4) => (c3, a4), |
|
(5, 4) => (a4, c3), |
|
|
|
_ => (0, 1), |
|
} |
|
} |
|
|
|
#[derive(Copy, Clone)] |
|
struct Panel { |
|
last: Option<State>, |
|
} |
|
|
|
impl Panel { |
|
unsafe fn set(self: &mut Self, p: Option<State>) { |
|
if let Some(last) = self.last { |
|
sys::furi_hal_gpio_init( |
|
&last.high, |
|
sys::GpioModeInput, |
|
sys::GpioPullNo, |
|
sys::GpioSpeedVeryHigh, |
|
); |
|
|
|
sys::furi_hal_gpio_init( |
|
&last.low, |
|
sys::GpioModeInput, |
|
sys::GpioPullNo, |
|
sys::GpioSpeedVeryHigh, |
|
); |
|
} |
|
|
|
if let Some(p) = p { |
|
sys::furi_hal_gpio_init( |
|
&p.high, |
|
sys::GpioModeOutputPushPull, |
|
sys::GpioPullNo, |
|
sys::GpioSpeedVeryHigh, |
|
); |
|
sys::furi_hal_gpio_write(&p.high, true); |
|
|
|
sys::furi_hal_gpio_init( |
|
&p.low, |
|
sys::GpioModeOutputPushPull, |
|
sys::GpioPullNo, |
|
sys::GpioSpeedVeryHigh, |
|
); |
|
sys::furi_hal_gpio_write(&p.low, false); |
|
} |
|
|
|
self.last = p; |
|
} |
|
} |
|
|
|
enum Direction { |
|
DOWN, |
|
UP, |
|
LEFT, |
|
RIGHT, |
|
} |
|
|
|
fn step(snake: &mut Vec<(u8, u8)>, direction: &Direction) { |
|
let mut iter = snake.iter_mut().peekable(); |
|
|
|
while let Some(part) = iter.next() { |
|
if let Some(nxt) = iter.peek() { |
|
part.0 = nxt.0; |
|
part.1 = nxt.1; |
|
} else { |
|
match direction { |
|
Direction::UP => { |
|
part.1 = part.1 - 1; |
|
} |
|
Direction::DOWN => { |
|
part.1 = part.1 + 1; |
|
} |
|
Direction::LEFT => { |
|
part.0 = part.0 - 1; |
|
} |
|
Direction::RIGHT => { |
|
part.0 = part.0 + 1; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
struct DisplayState { |
|
what: DisplayWhat, |
|
n_ticks: usize, |
|
} |
|
|
|
enum DisplayWhat { |
|
Snake { part_n: usize }, |
|
} |
|
|
|
impl DisplayState { |
|
fn tick(self: &mut Self, snake: &Vec<(u8, u8)>) { |
|
if self.n_ticks > 0 { |
|
self.n_ticks -= 1; |
|
return; |
|
} |
|
|
|
let (what, n_ticks) = match self.what { |
|
DisplayWhat::Snake { part_n } => { |
|
let part_n = (part_n + 1) % snake.len(); |
|
let what = DisplayWhat::Snake { part_n }; |
|
let n_ticks = part_n * part_n; |
|
(what, n_ticks) |
|
} |
|
}; |
|
|
|
self.what = what; |
|
self.n_ticks = n_ticks; |
|
} |
|
} |
|
|
|
fn main(_args: Option<&CStr>) -> i32 { |
|
unsafe { |
|
// Alloc some datastructures |
|
let view_port = sys::view_port_alloc(); |
|
let gui = sys::furi_record_open(RECORD_GUI.as_ptr()) as *mut sys::Gui; |
|
|
|
sys::gui_add_view_port(gui, view_port, FULLSCREEN); |
|
|
|
let mut event_queue: EventQueue = MessageQueue::new(100); |
|
let event_queue_p: *mut EventQueue = &mut event_queue; |
|
let event_queue_ptr: *mut c_void = event_queue_p.cast(); |
|
sys::view_port_input_callback_set(view_port, Some(input_callback), event_queue_ptr); |
|
|
|
let mut panel = Panel { last: None }; |
|
|
|
// Turn everything off |
|
all_off(); |
|
|
|
let mut i = 0; |
|
let pins = led_pins(); |
|
let mut snake: Vec<(u8, u8)> = vec![(1, 0), (1, 1), (1, 2), (1, 3)]; |
|
let mut display_state = DisplayState { |
|
what: DisplayWhat::Snake { part_n: 0 }, |
|
n_ticks: 0, |
|
}; |
|
let mut direction = Direction::DOWN; |
|
|
|
'processing: loop { |
|
i += 1; |
|
|
|
if i % 500 == 0 { |
|
step(&mut snake, &direction); |
|
} |
|
|
|
display_state.tick(&snake); |
|
|
|
let (c, r) = match display_state.what { |
|
DisplayWhat::Snake { part_n } => snake[part_n], |
|
}; |
|
|
|
let (h, l) = lookup_pins(c, r); |
|
let pin_h = pins[h as usize]; |
|
let pin_l = pins[l as usize]; |
|
let state = State { |
|
high: pin_h, |
|
low: pin_l, |
|
}; |
|
panel.set(Some(state)); |
|
|
|
if let Ok(snake_event) = |
|
event_queue.get(flipperzero::furi::time::FuriDuration::from_millis(1)) |
|
{ |
|
match snake_event { |
|
SnakeEvent::EventKey(key) => { |
|
if key.type_ != sys::InputTypePress { |
|
continue; |
|
} |
|
if key.key == sys::InputKeyBack { |
|
break 'processing; |
|
} else if key.key == sys::InputKeyDown { |
|
direction = Direction::DOWN; |
|
} else if key.key == sys::InputKeyUp { |
|
direction = Direction::UP; |
|
} else if key.key == sys::InputKeyLeft { |
|
direction = Direction::LEFT; |
|
} else if key.key == sys::InputKeyRight { |
|
direction = Direction::RIGHT; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
panel.set(None); // shut down any potential remaining pins |
|
|
|
sys::view_port_enabled_set(view_port, false); |
|
sys::gui_remove_view_port(gui, view_port); |
|
sys::furi_record_close(RECORD_GUI.as_ptr()); |
|
sys::view_port_free(view_port); |
|
} |
|
|
|
0 |
|
} |