~ruther/map-led-strip

b0fe6c33a16fd7911c28720d24fc214eb26a0d7d — František Boháček 1 year, 9 months ago d9a635d
feat: implement animations
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> {

Do not follow this link