M Cargo.toml => Cargo.toml +1 -0
@@ 14,4 14,5 @@ nb = "1.1.0"
smart-leds = "0.3.0"
smart-leds-trait = "0.2.1"
fugit = "0.3.7"
+libm = "0.2.7"
esp-alloc = "0.3.0"
A src/animations.rs => src/animations.rs +5 -0
@@ 0,0 1,5 @@
+pub mod animation;
+pub mod animation_manager;
+pub mod animation_step;
+pub mod animation_storage;
+pub mod snake_animation;<
\ No newline at end of file
A src/animations/animation.rs => src/animations/animation.rs +21 -0
@@ 0,0 1,21 @@
+// different kinds of animation
+// steps, each step has given length
+// each step has LED states
+
+// each step will show current led states on the board
+
+use libm::{ceilf, powf};
+use crate::animations::animation_step::AnimationStep;
+use crate::map::Map;
+
+pub trait Animation {
+ fn is_started(&self) -> bool;
+ fn init(&mut self) -> Result<(), AnimationError>;
+ fn next(&mut self) -> Result<AnimationStep, AnimationError>;
+ fn apply(&mut self, map: &mut Map) -> Result<(), AnimationError>;
+}
+
+#[derive(Eq, PartialEq)]
+pub enum AnimationError {
+ LastStep
+}<
\ No newline at end of file
A src/animations/animation_manager.rs => src/animations/animation_manager.rs +64 -0
@@ 0,0 1,64 @@
+use embedded_hal::timer::CountDown;
+use fugit::MicrosDurationU64;
+use nb::Error::{Other, WouldBlock};
+use crate::animations::animation::{Animation, AnimationError};
+use crate::animations::animation_storage::AnimationStorage;
+use crate::map::Map;
+
+pub struct AnimationManager<Timer> {
+ timer: Timer,
+ storage: AnimationStorage,
+}
+
+impl<Timer: CountDown<Time=MicrosDurationU64>> AnimationManager<Timer> {
+ pub fn new(timer: Timer) -> Self {
+ Self {
+ timer,
+ storage: AnimationStorage::new(),
+ }
+ }
+
+ pub fn storage(&mut self) -> &mut AnimationStorage {
+ &mut self.storage
+ }
+
+ pub fn update(&mut self, map: &mut Map) -> nb::Result<(), AnimationError> {
+ let step_result;
+ {
+ let mut animation = if let Some(animation) = self.storage.animation() {
+ animation
+ } else { // animation is not set, no job to do
+ return Ok(());
+ };
+
+ let mut timer_ran_off = false;
+ if animation.is_started() {
+ timer_ran_off = match self.timer.wait() {
+ Err(_) => return Err(WouldBlock),
+ _ => true // timer has reached next step
+ };
+ }
+
+ step_result = if !animation.is_started() || timer_ran_off {
+ animation.next()
+ } else {
+ return Err(WouldBlock);
+ };
+ animation.apply(map)?;
+ }
+
+ let step = match step_result {
+ Ok(step) => step,
+ Err(err) if err == AnimationError::LastStep => {
+ self.storage.remove_animation();
+ return Ok(());
+ }
+ Err(err) => {
+ return Err(Other(err));
+ }
+ };
+
+ self.timer.start(step.duration());
+ Ok(())
+ }
+}<
\ No newline at end of file
A src/animations/animation_step.rs => src/animations/animation_step.rs +17 -0
@@ 0,0 1,17 @@
+use fugit::MicrosDurationU64;
+
+pub struct AnimationStep {
+ duration: MicrosDurationU64,
+}
+
+impl AnimationStep {
+ pub fn new(duration: MicrosDurationU64) -> Self {
+ Self {
+ duration
+ }
+ }
+
+ pub fn duration(&self) -> MicrosDurationU64 {
+ self.duration
+ }
+}<
\ No newline at end of file
A src/animations/animation_storage.rs => src/animations/animation_storage.rs +54 -0
@@ 0,0 1,54 @@
+use alloc::boxed::Box;
+use crate::animations::animation::{Animation, AnimationError};
+use crate::animations::animation_step::AnimationStep;
+use crate::map::Map;
+
+pub struct AnimationStorage {
+ animation: Option<Box<dyn Animation>>,
+}
+
+impl AnimationStorage {
+ pub fn new() -> Self {
+ Self {
+ animation: None
+ }
+ }
+
+ pub fn animation<'a>(&'a mut self) -> Option<impl Animation + 'a> {
+ if self.animation.is_none() {
+ None
+ } else {
+ Some(StorageAnimation { storage: self })
+ }
+ }
+
+ pub fn set_animation<T: Animation + 'static>(&mut self, animation: T) -> () {
+ self.animation = Some(Box::new(animation));
+ }
+
+ pub fn remove_animation(&mut self) -> () {
+ self.animation = None;
+ }
+}
+
+struct StorageAnimation<'a> {
+ storage: &'a mut AnimationStorage
+}
+
+impl<'a> Animation for StorageAnimation<'a> {
+ fn is_started(&self) -> bool {
+ self.storage.animation.as_ref().unwrap().is_started()
+ }
+
+ fn init(&mut self) -> Result<(), AnimationError> {
+ self.storage.animation.as_mut().unwrap().init()
+ }
+
+ fn next(&mut self) -> Result<AnimationStep, AnimationError> {
+ self.storage.animation.as_mut().unwrap().next()
+ }
+
+ fn apply(&mut self, map: &mut Map) -> Result<(), AnimationError> {
+ self.storage.animation.as_mut().unwrap().apply(map)
+ }
+}<
\ No newline at end of file
A src/animations/snake_animation.rs => src/animations/snake_animation.rs +62 -0
@@ 0,0 1,62 @@
+use fugit::MicrosDurationU64;
+use libm::{ceilf, powf};
+use smart_leds::RGB8;
+use crate::animations::animation::{Animation, AnimationError};
+use crate::animations::animation_step::AnimationStep;
+use crate::map::Map;
+
+pub struct SnakeAnimation<const LEDS_COUNT: usize> {
+ step: usize,
+ order: [usize; LEDS_COUNT],
+ previous_factor: f32,
+ color: RGB8,
+ step_duration: MicrosDurationU64,
+}
+
+impl<const LEDS_COUNT: usize> SnakeAnimation<LEDS_COUNT> {
+ pub fn new(order: [usize; LEDS_COUNT], previous_factor: f32, color: RGB8, step_duration: MicrosDurationU64) -> Self {
+ Self {
+ step: 0,
+ order,
+ previous_factor,
+ color,
+ step_duration
+ }
+ }
+}
+
+impl<const LEDS_COUNT: usize> Animation for SnakeAnimation<LEDS_COUNT> {
+ fn is_started(&self) -> bool {
+ self.step > 0
+ }
+
+ fn init(&mut self) -> Result<(), AnimationError> {
+ Ok(())
+ }
+
+ fn next(&mut self) -> Result<AnimationStep, AnimationError> {
+ if self.step == LEDS_COUNT - 1 {
+ return Err(AnimationError::LastStep);
+ }
+
+ self.step += 1;
+ Ok(AnimationStep::new(self.step_duration))
+ }
+
+ fn apply(&mut self, map: &mut Map) -> Result<(), AnimationError> {
+ for (i, led_index) in self.order.iter().take(self.step).enumerate() {
+ let mult_factor = self.step - i - 1;
+ let coeff = powf(self.previous_factor, mult_factor as f32);
+ let rgb = self.color;
+ let color = RGB8 {
+ r: ceilf(rgb.r as f32 * coeff) as u8,
+ g: ceilf(rgb.g as f32 * coeff) as u8,
+ b: ceilf(rgb.r as f32 * coeff) as u8,
+ };
+
+ map.set(*led_index, color).ok().unwrap();
+ }
+
+ Ok(())
+ }
+}<
\ No newline at end of file
M src/commands/command_data.rs => src/commands/command_data.rs +13 -2
@@ 1,19 1,26 @@
+use crate::animations::animation_storage::AnimationStorage;
use crate::commands::command::Command;
use crate::map::Map;
pub struct CommandData<'d, 'a> {
command: &'d Command<'d>,
map: &'d mut Map<'a>,
+ animation_storage: &'d mut AnimationStorage
}
impl<'d, 'a> CommandData<'d, 'a> {
- pub fn new(command: &'d Command<'d>, map: &'d mut Map<'a>) -> Self {
+ pub fn new(command: &'d Command<'d>, map: &'d mut Map<'a>, animation_manager: &'d mut AnimationStorage) -> Self {
CommandData {
command,
- map
+ map,
+ animation_storage: animation_manager
}
}
+ pub fn animation_storage(self) -> &'d mut AnimationStorage {
+ self.animation_storage
+ }
+
pub fn command(self) -> &'d Command<'d> {
self.command
}
@@ 25,4 32,8 @@ impl<'d, 'a> CommandData<'d, 'a> {
pub fn deconstruct(self) -> (&'d Command<'d>, &'d mut Map<'a>) {
(self.command, self.map)
}
+
+ pub fn deconstruct_animation(self) -> (&'d Command<'d>, &'d mut AnimationStorage) {
+ (self.command, self.animation_storage)
+ }
}=
\ No newline at end of file
M src/commands/command_handler.rs => src/commands/command_handler.rs +3 -2
@@ 3,6 3,7 @@ use embedded_hal::serial::{Read, Write};
use esp_println::println;
use nb::block;
use nb::Error::{Other, WouldBlock};
+use crate::animations::animation_storage::AnimationStorage;
use crate::command_handler::{CommandHandleError::{CommandNotRead, NotFound}, CommandReadError::{BufferOverflowed, CommandLoadedAlready, UnexpectedEndOfLine}};
use crate::commands::{command::Command, command_argument::CommandArgument, command_data::CommandData};
use crate::map::Map;
@@ 164,7 165,7 @@ impl<'d, const BUFFER_SIZE: usize, const HANDLERS_COUNT: usize> CommandHandler<'
Ok(())
}
- pub fn handle_command(&mut self, map: &mut Map) -> Result<(), CommandHandleError>
+ pub fn handle_command(&mut self, map: &mut Map, animation_storage: &mut AnimationStorage) -> Result<(), CommandHandleError>
{
if !self.command_loaded {
return Err(CommandNotRead);
@@ 193,7 194,7 @@ impl<'d, const BUFFER_SIZE: usize, const HANDLERS_COUNT: usize> CommandHandler<'
continue;
}
- let command_data = CommandData::new(&command, map);
+ let command_data = CommandData::new(&command, map, animation_storage);
let handled = handler.handle(command_data);
self.reset();
return handled;
A src/constants.rs => src/constants.rs +2 -0
@@ 0,0 1,2 @@
+pub const LEDS_COUNT: usize = 72;
+pub const COMMAND_BUFFER: usize = 200;<
\ No newline at end of file
M src/main.rs => src/main.rs +43 -33
@@ 6,9 6,12 @@ extern crate alloc;
mod strip;
mod map;
mod commands;
+mod animations;
+mod constants;
-use embedded_hal::serial::{Write};
use alloc::boxed::Box;
+use embedded_hal::serial::{Read, Write};
+use embedded_hal::timer::CountDown;
use esp_backtrace as _;
use esp_println::println;
use hal::{clock::ClockControl, peripherals::Peripherals, prelude::*, timer::{TimerGroup}, Rtc, IO, Delay, PulseControl, Uart};
@@ 17,16 20,18 @@ use hal::uart::TxRxPins;
use nb::block;
use nb::Error::{Other};
use smart_leds::{RGB8, SmartLedsWrite};
+use esp_alloc::EspHeap;
+use crate::animations::animation_manager::AnimationManager;
use crate::commands::all_command::AllCommand;
use crate::commands::command_handler::{CommandHandler};
use crate::commands::command_handler;
use crate::commands::hello_world_command::HelloWorldCommand;
use crate::commands::reset_command::ResetCommand;
use crate::commands::set_command::SetCommand;
+use crate::commands::snake_command::SnakeCommand;
+use crate::map::Map;
use crate::strip::StripTiming;
-const LEDS_COUNT: usize = 72;
-const COMMAND_BUFFER: usize = 200;
#[global_allocator]
static ALLOCATOR: EspHeap = EspHeap::empty();
@@ 45,7 50,6 @@ fn init_heap() {
#[entry]
fn main() -> ! {
- // setup
let peripherals = Peripherals::take();
let mut system = peripherals.DPORT.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
@@ 71,6 75,8 @@ fn main() -> ! {
wdt1.disable();
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
+
+ // Init UART
let pins = TxRxPins::new_tx_rx(
io.pins.gpio1.into_push_pull_output(),
io.pins.gpio3.into_floating_input(),
@@ 91,14 97,13 @@ fn main() -> ! {
&mut system.peripheral_clock_control,
);
- let mut delay = Delay::new(&clocks);
-
+ // Init strip
let pulse = PulseControl::new(
peripherals.RMT,
&mut system.peripheral_clock_control,
).unwrap();
- let mut strip = strip::Strip::<_, { LEDS_COUNT * 24 + 1 }>::new(
+ let mut strip = strip::Strip::<_, { constants::LEDS_COUNT * 24 + 1 }>::new(
pulse.channel0,
io.pins.gpio25,
StripTiming::new(
@@ 109,8 114,11 @@ fn main() -> ! {
),
);
+ // Init map
let mut rgb_data: [RGB8; 72] = [RGB8 { r: 0, g: 0, b: 0 }; 72];
let mut map = map::Map::new(&map::INDEX_MAP, &mut rgb_data);
+ let mut animations = AnimationManager::new(timer_group0.timer0);
+ let mut delay = Delay::new(&clocks);
// Init commands
let mut handler = CommandHandler::new(
@@ 124,19 132,31 @@ fn main() -> ! {
['\0'; constants::COMMAND_BUFFER],
);
- block!(serial.write(b'>')).ok().unwrap();
- block!(serial.write(b' ')).ok().unwrap();
+ print_new_command(&mut serial);
+
loop {
- let handled = match handler.read_command(&mut serial) {
+ // result is either ok, then do nothing,
+ // would block, then do nothing
+ // or last step, then do nothing as well...
+ let _ = animations.update(&mut map);
+
+ let new_command = match handler.read_command(&mut serial) {
Ok(()) => {
println!("\r");
- let result = handler.handle_command(&mut map);
+ let result = handler.handle_command(&mut map, animations.storage());
match result {
- Ok(()) => Ok(true),
- Err(err) => Err(err)
+ Ok(()) => true,
+ Err(err) => {
+ match err {
+ command_handler::CommandHandleError::NotFound => println!("Command not found.\r"),
+ command_handler::CommandHandleError::WrongArguments => println!("Wrong arguments.\r"),
+ command_handler::CommandHandleError::CommandNotRead => println!("FATAL: Command is not prepared.\r")
+ }
+ true
+ }
}
- }
+ },
Err(err) => {
match err {
Other(error) => {
@@ 145,34 165,24 @@ fn main() -> ! {
command_handler::CommandReadError::UnexpectedEndOfLine => (),
command_handler::CommandReadError::CommandLoadedAlready => println!("FATAL: Previous command not processed correctly.\r")
};
- Ok(true)
+ true
}
- _ => Ok(false)
+ _ => false
}
}
};
- let new_command = match handled {
- Ok(handled) => {
- handled
- }
- Err(err) => {
- match err {
- command_handler::CommandHandleError::NotFound => println!("Command not found.\r"),
- command_handler::CommandHandleError::WrongArguments => println!("Wrong arguments.\r"),
- command_handler::CommandHandleError::CommandNotRead => println!("FATAL: Command is not prepared.\r")
- }
- true
- }
- };
-
if new_command {
- println!("\r");
- block!(serial.write(b'>')).ok().unwrap();
- block!(serial.write(b' ')).ok().unwrap();
+ print_new_command(&mut serial);
}
strip.write(map.get_map().cloned()).unwrap();
delay.delay_us(500u32);
}
+
+ fn print_new_command<T: Write<u8>>(serial: &mut T) {
+ println!("\r");
+ block!(serial.write(b'>')).ok().unwrap();
+ block!(serial.write(b' ')).ok().unwrap();
+ }
}=
\ No newline at end of file
M src/map.rs => src/map.rs +7 -4
@@ 115,19 115,22 @@ impl<'d> Map<'d> {
return Err(Error::NotFound);
}
+ pub fn set(&mut self, index: usize, rgb: RGB8) -> Result<(), Error> {
+ self.data[index] = rgb;
+ Ok(())
+ }
+
pub fn set_rgb(&mut self, index: usize, r: Option<u8>, g: Option<u8>, b: Option<u8>) -> Result<(), Error> {
let original = self.data[index];
if self.data.len() <= index {
return Err(Error::NotFound)
}
- self.data[index] = RGB8 {
+ self.set(index, RGB8 {
r: r.unwrap_or(original.r),
g: g.unwrap_or(original.g),
b: b.unwrap_or(original.b),
- };
-
- Ok(())
+ })
}
pub fn set_rgb_by_name(&mut self, name: &[char], r: Option<u8>, g: Option<u8>, b: Option<u8>) -> Result<(), Error> {