Compare commits

..

18 Commits

Author SHA1 Message Date
Tobias Ollive
66c431f20c improve time management 2022-03-18 14:41:16 +01:00
Tobias Ollive
b332d75c7f refactor and fix starrandom error 2022-03-16 18:44:14 +01:00
Tobias Ollive
6deb31836f add random stars pattern
fix wrong multi devices selection
add color section to simplify devices section
2022-03-15 16:18:29 +01:00
Tobias Ollive
13326a49d9 use macro rule for default implementation of pattern 2022-03-13 10:16:40 +01:00
Tobias Ollive
20b8f6d4db add ring and ringscanner pattern 2022-03-12 21:20:55 +01:00
Tobias Ollive
4dd5587b49 add fill_unstable and use rust fmt 2022-03-12 20:52:37 +01:00
Tobias Ollive
ae215ce252 add fade_random pattern 2022-03-12 20:45:27 +01:00
Tobias Ollive
a9abc2e05b add rain pattern 2022-03-11 18:43:48 +01:00
Tobias Ollive
ee133379f2 add background color for color wipe and my first unit test ! 2022-03-11 17:10:33 +01:00
Tobias Ollive
b718334392 refactor patterns 2022-03-11 12:40:12 +01:00
Tobias Ollive
c2b2517661 add blink pattern 2022-03-10 09:54:02 +01:00
Tobias Ollive
4451917154 add fillUnstable pattern 2022-03-09 18:09:26 +01:00
Tobias Ollive
086e194a1d add first audio support 2022-03-09 17:58:31 +01:00
Tobias Ollive
4c788f7313 add ability to play separate spectacles. 2022-03-09 15:53:42 +01:00
Tobias Ollive
3d8adc0d52 add fillRandom pattern 2022-03-02 18:47:03 +01:00
Tobias Ollive
20983a8c08 add show example and scanner pattern 2022-03-02 13:45:55 +01:00
Tobias Ollive
7521c87460 read show from file instead of writing it in the code 2022-03-02 13:44:28 +01:00
Tobias Ollive
b43490e876 WIP working on main patterns loop 2022-02-23 19:48:38 +01:00
25 changed files with 2287 additions and 298 deletions

841
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,4 +8,12 @@ edition = "2018"
[dependencies] [dependencies]
bluer = "0.13.1" bluer = "0.13.1"
tokio = {version = "1.15.0", features = ["full"]} tokio = {version = "1.15.0", features = ["full"]}
futures = "0.3.19" futures = "0.3.19"
rand = "0.8.5"
rodio = "0.15.0"
clap = { version = "3.1.6", features = ["derive"] }
pattern_derive = { path = "pattern_derive" }
serde_derive = "1.0.136"
serde = "1.0.136"
serde_yaml = "0.8"

81
spectacle.yml Normal file
View File

@@ -0,0 +1,81 @@
colors:
- &red
red: 255
green: 0
blue: 0
devices:
- name : toto
mac_addresses :
- C9:81:9C:BA:53:BC
sequence:
- pattern:
type: Rain
color: &blue
red: 0
green: 0
blue: 255
background_color: &yellow
red: 0
green: 255
blue: 255
stability: 10
lines: 5
period: 200
- pattern:
type: Blink
fade:
current_iteration: 10
steps: 20
begin_color: &black
red: 0
green: 0
blue: 0
end_color: &purple
red: 255
green: 0
blue: 255
max_iteration: 10
period: 50
- pattern:
type: Fade
steps: 20
begin_color:
red: 255
green: 0
blue: 0
end_color: *blue
period: 200
- pattern:
type: ColorWipe
max_iteration: 25
color: &green
red: 0
green: 255
blue: 0
period: 100
- pattern:
type: FillRandom
color: *blue
stability: 40
period: 80
- name: titi
sequence:
- pattern:
type: StarsRandom
color: *green
ratio: 8
steps: 20
background_color:
red: 0
green: 255
blue: 100
period: 50
- pattern:
type: Rainbow
max_iteration: 200
period: 10
- pattern:
type: Scanner
color: *blue
period: 100

13
src/args.rs Normal file
View File

@@ -0,0 +1,13 @@
use clap::Parser;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct Cli {
/// file for light sequences
#[clap(short, long)]
pub spectacle_file: String,
/// musique file that will be played
#[clap(short, long)]
pub musique_file: String,
}

18
src/audio.rs Normal file
View File

@@ -0,0 +1,18 @@
use rodio::{Decoder, OutputStream, Sink};
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
pub fn play_sound(path: &Path) {
println!("starting musique");
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
let sink = Sink::try_new(&stream_handle).unwrap();
let file = BufReader::new(File::open(path).unwrap());
let source = Some(Decoder::new(file).unwrap()).unwrap();
sink.append(source);
// The sound plays in a separate thread. This call will block the current thread until the sink
// has finished playing all its queued sounds.
sink.sleep_until_end();
}

View File

@@ -39,7 +39,6 @@ pub(crate) async fn bluetooth_scan(
while let Some(evt) = discover.next().await { while let Some(evt) = discover.next().await {
match evt { match evt {
AdapterEvent::DeviceAdded(addr) => { AdapterEvent::DeviceAdded(addr) => {
println!("new device {}", addr);
if already_scanned.contains(&addr) { if already_scanned.contains(&addr) {
continue; continue;
} }
@@ -49,7 +48,6 @@ pub(crate) async fn bluetooth_scan(
Ok(service_found) => { Ok(service_found) => {
if service_found { if service_found {
tx.send(device).await.unwrap(); tx.send(device).await.unwrap();
println!("found service in device {}", addr);
} }
} }
Err(_) => continue, Err(_) => continue,

24
src/config.rs Normal file
View File

@@ -0,0 +1,24 @@
use crate::pixel_color::PixelColor;
use crate::runner::DeviceSequence;
use serde_derive::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fs;
#[derive(Serialize, Deserialize)]
pub struct Config<const N: usize> {
devices: Vec<Device<N>>,
colors: Vec<PixelColor>,
}
#[derive(Deserialize, Serialize)]
pub struct Device<const N: usize> {
pub mac_addresses: Option<HashSet<String>>,
name: Option<String>,
pub sequence: DeviceSequence<N>,
}
pub fn load_from_file<const N: usize>(file: &std::path::Path) -> Vec<Device<N>> {
let file = fs::read_to_string(file).unwrap();
let config: Config<N> = serde_yaml::from_str(&*file).unwrap();
config.devices
}

View File

@@ -1,10 +1,22 @@
mod args;
mod audio;
mod bluetooth; mod bluetooth;
mod config;
mod patterns; mod patterns;
mod pixel_color;
mod runner;
mod spectacle;
use crate::patterns::{ColorWipe, Fade, PixelColor, Rainbow}; use crate::args::Cli;
use crate::audio::play_sound;
use crate::config::load_from_file;
use crate::runner::Spectacle;
use bluer::gatt::remote::Characteristic; use bluer::gatt::remote::Characteristic;
use bluer::{Device, Error};
use clap::Parser;
use patterns::Strip; use patterns::Strip;
use std::time::Duration; use std::path::Path;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::watch; use tokio::sync::watch;
@@ -15,9 +27,15 @@ const BASE_STRIP_DATA: [u8; 3] = [0x00, 0x00, 0x01];
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> bluer::Result<()> { async fn main() -> bluer::Result<()> {
let adapter = bluetooth::create_session().await?; let command_line = Cli::parse();
let (tx_scan, mut rx_scan) = mpsc::channel(3); let file = Path::new(&command_line.spectacle_file);
let config = load_from_file(file);
let spectacle = Spectacle::from(config);
let spectacle = Arc::new(Mutex::new(spectacle));
let adapter = bluetooth::create_session().await?;
let (tx_scan, rx_scan) = mpsc::channel(3);
tokio::spawn(bluetooth::bluetooth_scan( tokio::spawn(bluetooth::bluetooth_scan(
tx_scan, tx_scan,
@@ -25,110 +43,57 @@ async fn main() -> bluer::Result<()> {
SERVICE_UUID, SERVICE_UUID,
)); ));
let (tx, rx) = watch::channel(Strip::<STRIP_SIZE>::default()); tokio::spawn(connect_device(spectacle.clone(), rx_scan));
tokio::spawn(async move { println!("starting");
while let Some(device) = rx_scan.recv().await { let musique_file = command_line.musique_file;
bluetooth::connect(&device, 3).await?; let musique = tokio::task::spawn_blocking(move || play_sound(Path::new(&musique_file)));
let char = bluetooth::get_char(&device, SERVICE_UUID, PIXEL_DATA_UUID).await?; let futures;
if let Some(char) = char { {
let mut rx_device = rx.clone(); let mut spectacle = spectacle.lock().unwrap();
futures = spectacle.run();
}
let mut joinhandles = vec![];
for future in futures {
joinhandles.push(tokio::spawn(future));
}
for joinhandle in joinhandles {
joinhandle.await.unwrap();
}
musique.await.unwrap();
Ok(())
}
async fn connect_device(
spectacle: Arc<Mutex<Spectacle<STRIP_SIZE>>>,
mut rx_scan: mpsc::Receiver<Device>,
) -> Result<(), Error> {
while let Some(device) = rx_scan.recv().await {
bluetooth::connect(&device, 3).await?;
let char = bluetooth::get_char(&device, SERVICE_UUID, PIXEL_DATA_UUID).await?;
if let Some(char) = char {
let rx: Option<watch::Receiver<Strip<STRIP_SIZE>>>;
{
let spectacle = spectacle.lock().unwrap();
rx = spectacle.get_channel(&device.address());
}
if let Some(mut rx) = rx {
tokio::spawn(async move { tokio::spawn(async move {
println!("device connected : {}", &device.address()); println!("device connected : {}", &device.address());
while rx_device.changed().await.is_ok() { while rx.changed().await.is_ok() {
let strip = *rx_device.borrow(); let strip = *rx.borrow();
if write_strip(&strip, &char).await.is_err() { if write_strip(&strip, &char).await.is_err() {
break; break;
}; };
} }
println!("device {} disconnected", &device.address()); println!("device {} disconnected", &device.address());
// drop(rx_device);
}); });
} }
} }
bluer::Result::Ok(())
});
let mut pixels = Vec::<PixelColor>::new();
for _ in 0..STRIP_SIZE {
pixels.push(PixelColor {
red: 255,
green: 0,
blue: 0,
})
} }
bluer::Result::Ok(())
let mut _colorwipe = ColorWipe::<25> {
current_iteration: 0,
color: PixelColor {
red: 0,
green: 255,
blue: 0,
},
infinite: true,
};
let mut fade = Fade::<25> {
current_iteration: 0,
nbr_iterations: 25,
begin_color: PixelColor {
red: 255,
green: 0,
blue: 0,
},
end_color: PixelColor {
red: 0,
green: 0,
blue: 255,
},
};
let mut pattern = Rainbow::<25> {
current_iteration: 0,
max_iteration: None,
step: Some(10),
};
let mut strip = fade.next().unwrap();
tokio::time::sleep(Duration::from_secs(5)).await;
println!("starting");
while tx.send(strip).is_ok() {
if let Some(value) = fade.next() {
strip = value;
} else {
break;
}
tokio::time::sleep(Duration::from_millis(1000)).await;
}
println!("starting rainbow");
while tx.send(pattern.next().unwrap()).is_ok() {
tokio::time::sleep(Duration::from_millis(100)).await;
}
println!("error sending value");
Ok(())
} }
// fn create_pattern_list() -> Vec<patterns>
// async fn send_seq(char: &Characteristic) -> bluer::Result<()> {
// println!(" Characteristic flags : {:?}, ", char.flags().await?);
//
// let mut strip_red : Strip<10> = Strip::default();
// strip_red.strip.fill(PixelColor { red: 255, green: 0, blue: 0});
//
// let mut strip_green: Strip<10> = Strip::default();
// strip_green.strip.fill(PixelColor { red: 0, green: 255, blue: 0});
//
// let pattern = RainbowPattern::<STRIP_SIZE> { current_iteration: 0,};
//
//
// for strip in pattern {
// write_strip(strip, char).await?;
// sleep(Duration::from_millis(50)).await;
// }
//
// Ok(())
// }
pub async fn write_strip<const N: usize>( pub async fn write_strip<const N: usize>(
data: &Strip<N>, data: &Strip<N>,
char: &Characteristic, char: &Characteristic,
@@ -138,36 +103,3 @@ pub async fn write_strip<const N: usize>(
char.write(&*frame).await?; char.write(&*frame).await?;
Ok(()) Ok(())
} }
// async fn find_neopixel_service(device: &Device) -> bluer::Result<Option<Characteristic>> {
// let addr = device.address();
// let uuids = device.uuids().await?.unwrap_or_default();
//
// if uuids.contains(&SERVICE_UUID) {
// println!("service neopixel found for device {}", &addr);
// match connect(device, 3).await {
// Ok(()) => {
// println!("successefully connected");
// }
// Err(err) => {
// println!("unable to connect, trying new device, {}", err);
// return Err(err)
// }
// }
// for service in device.services().await? {
// if SERVICE_UUID == service.uuid().await? {
// for char in service.characteristics().await? {
// println!("uuid : {}", &char.uuid().await?);
// if PIXEL_DATA_UUID == char.uuid().await? {
//
// return Ok(Some(char));
// }
//
// }
// }
// }
//
// }
//
// Ok(None)
// }

View File

@@ -1,27 +1,183 @@
use std::fmt; mod blink;
mod color_wipe;
mod fade;
mod fade_random;
mod fade_unstable;
mod fill_random;
mod fill_unstable;
mod rain;
mod rainbow;
mod ring;
mod ring_scanner;
mod scanner;
mod stars_random;
#[derive(Clone, Copy, Debug, Default)] use crate::patterns::blink::Blink;
pub struct PixelColor { use crate::patterns::color_wipe::ColorWipe;
pub(crate) red: u8, use crate::patterns::fade::Fade;
pub(crate) green: u8, use crate::patterns::fade_random::FadeRandom;
pub(crate) blue: u8, use crate::patterns::fade_unstable::FadeUnstable;
use crate::patterns::fill_random::FillRandom;
use crate::patterns::fill_unstable::FillUnstable;
use crate::patterns::rain::Rain;
use crate::patterns::rainbow::Rainbow;
use crate::patterns::ring::Ring;
use crate::patterns::ring_scanner::RingScanner;
use crate::patterns::scanner::Scanner;
use crate::patterns::stars_random::StarsRandom;
use crate::pixel_color::PixelColor;
use serde_derive::{Deserialize, Serialize};
use std::ops::{Index, IndexMut};
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
#[serde(tag = "type")]
pub(crate) enum Patterns<const N: usize> {
Rainbow(Rainbow<N>),
Fade(Fade<N>),
ColorWipe(ColorWipe<N>),
Scanner(Scanner<N>),
FillRandom(FillRandom<N>),
FillUnstable(FillUnstable<N>),
Blink(Blink<N>),
Rain(Rain<N>),
FadeRandom(FadeRandom<N>),
FadeUnstable(FadeUnstable<N>),
Ring(Ring<N>),
RingScanner(RingScanner<N>),
StarsRandom(StarsRandom<N>),
} }
impl PixelColor { /// Pattern that have to be implemented
fn new() -> Self { ///
Default::default() ///
///
///
pub trait Pattern: Iterator {
type Strip;
fn init(&mut self) -> Option<Self::Strip> {
None
}
fn is_last_iteration(&self) -> bool;
}
#[macro_export]
macro_rules! impl_pattern_init_background {
($generic: tt) => {
fn init(&mut self) -> Option<Self::Strip> {
if let Some(color) = self.background_color {
Some(Strip::<N>::new(color))
} else {
None
}
}
};
}
#[macro_export]
macro_rules! impl_pattern_last_iteration {
($name:ty) => {
fn is_last_iteration(&self) -> bool {
match self.max_iteration {
None => false,
Some(max_iter) => {
if self.current_iteration >= max_iter {
true
} else {
false
}
}
}
}
};
}
impl<const N: usize> Iterator for Patterns<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
match self {
Patterns::Rainbow(p) => p.next(),
Patterns::Fade(p) => p.next(),
Patterns::ColorWipe(p) => p.next(),
Patterns::Scanner(p) => p.next(),
Patterns::FillRandom(p) => p.next(),
Patterns::FillUnstable(p) => p.next(),
Patterns::Blink(p) => p.next(),
Patterns::Rain(p) => p.next(),
Patterns::FadeRandom(p) => p.next(),
Patterns::FadeUnstable(p) => p.next(),
Patterns::Ring(p) => p.next(),
Patterns::RingScanner(p) => p.next(),
Patterns::StarsRandom(p) => p.next(),
}
} }
} }
impl fmt::Display for PixelColor { impl<const N: usize> Pattern for Patterns<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { type Strip = Strip<N>;
write!(f, "[{},{},{}]", self.red, self.green, self.blue)
fn init(&mut self) -> Option<Self::Strip> {
match self {
Patterns::Rainbow(p) => p.init(),
Patterns::Fade(p) => p.init(),
Patterns::ColorWipe(p) => p.init(),
Patterns::Scanner(p) => p.init(),
Patterns::FillRandom(p) => p.init(),
Patterns::FillUnstable(p) => p.init(),
Patterns::Blink(p) => p.init(),
Patterns::Rain(p) => p.init(),
Patterns::FadeRandom(p) => p.init(),
Patterns::FadeUnstable(p) => p.init(),
Patterns::Ring(p) => p.init(),
Patterns::RingScanner(p) => p.init(),
Patterns::StarsRandom(p) => p.init(),
}
}
fn is_last_iteration(&self) -> bool {
match self {
Patterns::Rainbow(p) => p.is_last_iteration(),
Patterns::Fade(p) => p.is_last_iteration(),
Patterns::ColorWipe(p) => p.is_last_iteration(),
Patterns::Scanner(p) => p.is_last_iteration(),
Patterns::FillRandom(p) => p.is_last_iteration(),
Patterns::FillUnstable(p) => p.is_last_iteration(),
Patterns::Blink(p) => p.is_last_iteration(),
Patterns::Rain(p) => p.is_last_iteration(),
Patterns::FadeRandom(p) => p.is_last_iteration(),
Patterns::FadeUnstable(p) => p.is_last_iteration(),
Patterns::Ring(p) => p.is_last_iteration(),
Patterns::RingScanner(p) => p.is_last_iteration(),
Patterns::StarsRandom(p) => p.is_last_iteration(),
}
} }
} }
#[derive(Copy, Clone)] ///
pub struct Strip<const N: usize> { /// a basic newtype based on a fix array size.
pub strip: [PixelColor; N], ///
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct Strip<const N: usize>([Option<PixelColor>; N]);
impl<const N: usize> Default for Strip<N> {
fn default() -> Self {
Strip([None; N])
}
}
impl<const N: usize> Index<usize> for Strip<N> {
type Output = Option<PixelColor>;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl<const N: usize> IndexMut<usize> for Strip<N> {
fn index_mut(&mut self, index: usize) -> &mut Option<PixelColor> {
&mut self.0[index]
}
} }
impl<const N: usize> Strip<N> { impl<const N: usize> Strip<N> {
@@ -29,161 +185,19 @@ impl<const N: usize> Strip<N> {
let mut data: Vec<u8> = vec![]; let mut data: Vec<u8> = vec![];
for i in 0..N { for i in 0..N {
data.append(&mut vec![ let color = self[i].unwrap_or(PixelColor::default());
self.strip[i].green, data.append(&mut vec![color.green, color.red, color.blue]);
self.strip[i].red,
self.strip[i].blue,
]);
} }
data data
} }
pub fn fill(&mut self, color: PixelColor) { pub fn new(color: PixelColor) -> Strip<N> {
self.strip = [color; N];
}
}
impl<const N: usize> Default for Strip<N> {
fn default() -> Self {
Strip { Strip {
strip: [PixelColor::new(); N], 0: [Some(color); N],
} }
} }
}
/// Rainbow pattern ///////////////////////////////////////////////// pub fn fill(&mut self, color: PixelColor) {
self.0.fill(Some(color));
pub struct Rainbow<const N: usize> {
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) step: Option<usize>,
}
impl<const N: usize> Iterator for Rainbow<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(nbr_iteration) = self.max_iteration {
if nbr_iteration == self.current_iteration {
return None;
}
}
let mut strip = Strip::default();
let step = self.step.unwrap_or(255 / N);
for i in 0..N {
let pos = (i * step + self.current_iteration) as u8;
strip.strip[i] = wheel(pos)
}
self.current_iteration += 1;
Some(strip)
}
}
fn wheel(index: u8) -> PixelColor {
let pos = 255 - index;
match pos {
0..=85 => PixelColor {
red: 255 - (pos * 3),
green: 0,
blue: pos * 3,
},
86..=170 => {
let pos = pos - 85;
PixelColor {
red: 0,
green: pos * 3,
blue: 255 - (pos * 3),
}
}
_ => {
let pos = pos - 170;
PixelColor {
red: pos * 3,
green: 255 - (pos * 3),
blue: 0,
}
}
}
}
//////////////////////////////////////////////////////////////////////////
/// Colorwipe pattern ////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
pub struct ColorWipe<const N: usize> {
pub(crate) current_iteration: usize,
pub(crate) color: PixelColor,
pub(crate) infinite: bool,
}
impl<const N: usize> Iterator for ColorWipe<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
let mut strip = Strip::default();
if self.infinite {
self.current_iteration %= N;
} else if self.current_iteration >= N {
return None;
}
for i in 0..self.current_iteration {
strip.strip[i] = self.color;
}
self.current_iteration += 1;
Some(strip)
}
}
//////////////////////////////////////////////////////////////////////////
/// fade pattern ////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
pub struct Fade<const N: usize> {
pub(crate) current_iteration: usize,
pub(crate) nbr_iterations: usize,
pub(crate) begin_color: PixelColor,
pub(crate) end_color: PixelColor,
}
fn fade_value(value_start: u8, value_end: u8, index: usize, nbr_iter: usize) -> u8 {
((value_start as usize * (nbr_iter - index) + value_end as usize * index) / nbr_iter) as u8
}
impl<const N: usize> Iterator for Fade<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
let mut strip = Strip::default();
if self.current_iteration >= self.nbr_iterations {
return None;
}
let red = fade_value(
self.begin_color.red,
self.end_color.red,
self.current_iteration,
self.nbr_iterations,
);
let green = fade_value(
self.begin_color.green,
self.end_color.green,
self.current_iteration,
self.nbr_iterations,
);
let blue = fade_value(
self.begin_color.blue,
self.end_color.blue,
self.current_iteration,
self.nbr_iterations,
);
let current_color = PixelColor { red, green, blue };
strip.fill(current_color);
self.current_iteration += 1;
Some(strip)
} }
} }

40
src/patterns/blink.rs Normal file
View File

@@ -0,0 +1,40 @@
use crate::patterns::fade::Fade;
use crate::patterns::Pattern;
use crate::{impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # Blink
///
/// fade from one color to an other then go back several times
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Blink<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
fade: Fade<N>,
}
impl<const N: usize> Pattern for Blink<N> {
type Strip = Strip<N>;
impl_pattern_last_iteration!(ColorWipe);
}
impl<const N: usize> Iterator for Blink<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
let value = self.fade.next();
match value {
None => {
if self.is_last_iteration() {
return None;
}
self.current_iteration += 1;
self.fade.current_iteration = 0;
std::mem::swap(&mut self.fade.begin_color, &mut self.fade.end_color);
self.fade.next()
}
Some(value) => Some(value),
}
}
}

View File

@@ -0,0 +1,50 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_init_background, impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # Colorwipe pattern
/// every pattern implement the iterator trait
///
/// This pattern fill the strip with a specific color, one by one.
///
/// ### Note
///
/// setting max_iteration to None lead the pattern to loop infinitely
///
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct ColorWipe<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) color: PixelColor,
pub(crate) background_color: Option<PixelColor>,
}
impl<const N: usize> Pattern for ColorWipe<N> {
type Strip = Strip<N>;
impl_pattern_init_background!(N);
impl_pattern_last_iteration!(ColorWipe);
}
impl<const N: usize> Iterator for ColorWipe<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut strip = Strip::default();
if let Some(c) = self.background_color {
strip.fill(c);
}
let iteration = self.current_iteration % N;
for i in 0..iteration {
strip[i] = Some(self.color);
}
self.current_iteration += 1;
Some(strip)
}
}

65
src/patterns/fade.rs Normal file
View File

@@ -0,0 +1,65 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::Strip;
use serde::{Deserialize, Serialize};
/// # fade pattern
/// fade from one color to an other
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Fade<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) steps: usize,
pub(crate) begin_color: PixelColor,
pub(crate) end_color: PixelColor,
}
fn fade_value(value_start: u8, value_end: u8, index: usize, nbr_iter: usize) -> u8 {
((value_start as usize * (nbr_iter - index) + value_end as usize * index) / nbr_iter) as u8
}
impl<const N: usize> Pattern for Fade<N> {
type Strip = Strip<N>;
fn is_last_iteration(&self) -> bool {
self.current_iteration >= self.steps
}
}
impl<const N: usize> Iterator for Fade<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
let mut strip = Strip::default();
if self.is_last_iteration() {
return None;
}
let red = fade_value(
self.begin_color.red,
self.end_color.red,
self.current_iteration,
self.steps,
);
let green = fade_value(
self.begin_color.green,
self.end_color.green,
self.current_iteration,
self.steps,
);
let blue = fade_value(
self.begin_color.blue,
self.end_color.blue,
self.current_iteration,
self.steps,
);
let current_color = PixelColor { red, green, blue };
strip.fill(current_color);
self.current_iteration += 1;
Some(strip)
}
}

View File

@@ -0,0 +1,44 @@
use crate::patterns::fade::Fade;
use crate::patterns::Pattern;
use crate::{impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # FadeRandom
///
/// fade from one color to an other with random variations applied to each pixel
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct FadeRandom<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) stability: usize,
fade: Fade<N>,
}
impl<const N: usize> Pattern for FadeRandom<N> {
type Strip = Strip<N>;
impl_pattern_last_iteration!(ColorWipe);
}
impl<const N: usize> Iterator for FadeRandom<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
let strip = self.fade.next();
match strip {
None => None,
Some(mut fade) => {
let rng = rand::thread_rng();
for i in 0..N {
match fade[i] {
None => {}
Some(pixel) => {
fade[i] = Some(pixel.random_delta(self.stability, &mut rng.clone()))
}
}
}
Some(fade)
}
}
}
}

View File

@@ -0,0 +1,38 @@
use crate::patterns::fade::Fade;
use crate::patterns::Pattern;
use crate::{impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # FadeUnstable
///
/// fade from one color to an other with random variations applied to the entire strip
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct FadeUnstable<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) stability: usize,
fade: Fade<N>,
}
impl<const N: usize> Pattern for FadeUnstable<N> {
type Strip = Strip<N>;
impl_pattern_last_iteration!(ColorWipe);
}
impl<const N: usize> Iterator for FadeUnstable<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
let strip = self.fade.next();
match strip {
None => None,
Some(fade) => {
let mut rng = rand::thread_rng();
let pixel = fade[0].unwrap().random_delta(self.stability, &mut rng);
Some(Strip::new(pixel))
}
}
}
}

View File

@@ -0,0 +1,40 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # FillRandom
///
/// fill strip with color then apply random variation to each pixel
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct FillRandom<const N: usize> {
pub(crate) color: PixelColor,
pub(crate) stability: usize,
pub(crate) max_iteration: Option<usize>,
#[serde(default)]
pub(crate) current_iteration: usize,
}
impl<const N: usize> Pattern for FillRandom<N> {
type Strip = Strip<N>;
impl_pattern_last_iteration!(ColorWipe);
}
impl<const N: usize> Iterator for FillRandom<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut strip = Strip::<N>::default();
let rng = rand::thread_rng();
for i in 0..N {
let c = self.color.random_delta(self.stability, &mut rng.clone());
strip[i] = Some(c);
}
self.current_iteration += 1;
Some(strip)
}
}

View File

@@ -0,0 +1,36 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # FillUnstable
///
/// fill strip with color then apply same random variation to all pixel
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct FillUnstable<const N: usize> {
pub(crate) color: PixelColor,
pub(crate) stability: usize,
pub(crate) max_iteration: Option<usize>,
#[serde(default)]
pub(crate) current_iteration: usize,
}
impl<const N: usize> Pattern for FillUnstable<N> {
type Strip = Strip<N>;
impl_pattern_last_iteration!(FillUnstable);
}
impl<const N: usize> Iterator for FillUnstable<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut rng = rand::thread_rng();
let unstable_color = self.color.random_delta(self.stability, &mut rng);
self.current_iteration += 1;
Some(Strip::<N>::new(unstable_color))
}
}

70
src/patterns/rain.rs Normal file
View File

@@ -0,0 +1,70 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_last_iteration, Strip};
use rand::Rng;
use serde::{Deserialize, Serialize};
/// # rain pattern
/// randomly fall colors from top or bottom.
///
///
/// # note
///
/// `stability` is the probability (between 0 an 1) that any led will light on.
/// The probability of at least one led light on is `stability*number of lines`
///
/// this pattern works only for 5x5 square
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Rain<const N: usize> {
pub(crate) color: PixelColor,
pub(crate) background_color: Option<PixelColor>,
pub(crate) max_iteration: Option<usize>,
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) stability: usize,
pub(crate) lines: usize,
#[serde(skip)]
drops: Strip<N>,
}
impl<const N: usize> Pattern for Rain<N> {
type Strip = Strip<N>;
fn init(&mut self) -> Option<Self::Strip> {
if let Some(color) = self.background_color {
self.drops = Strip::<N>::new(color);
Some(self.drops)
} else {
None
}
}
impl_pattern_last_iteration!(ColorRain);
}
impl<const N: usize> Iterator for Rain<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
let mut strip = Strip::<N>::default();
for i in 0..(N - self.lines) {
strip[i + self.lines] = self.drops[i];
}
let mut rng = rand::thread_rng();
for i in 0..self.lines {
if rng.gen_bool(self.stability as f64 / 100 as f64) {
strip[i] = Some(self.color);
} else {
let c = strip[i + self.lines];
if self.background_color != c && c != Some(PixelColor::default()) {
strip[i] = Some(c.unwrap() / 2);
} else {
strip[i] = self.background_color;
}
}
}
self.drops = strip.clone();
self.current_iteration += 1;
Some(strip)
}
}

80
src/patterns/rainbow.rs Normal file
View File

@@ -0,0 +1,80 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # Rainbow pattern
///
/// every pattern implement the iterator trait
///
/// This pattern display a moving rainbow over the strip
///
/// ### Note
///
/// If max iteration is let to None, it's an infinite pattern.
///
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Rainbow<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) step: Option<usize>,
}
impl<const N: usize> Pattern for Rainbow<N> {
type Strip = Strip<N>;
impl_pattern_last_iteration!(Rainbow);
}
impl<const N: usize> Iterator for Rainbow<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut strip = Strip::default();
let step = self.step.unwrap_or(255 / N);
for i in 0..N {
let pos = (i * step + self.current_iteration) as u8;
strip[i] = Some(wheel(pos))
}
self.current_iteration += 1;
Some(strip)
}
}
/// compute **rgb** pixel color according to the **hsv** wheel
///
/// # Arguments
///
/// * `index`: position in the hsv wheel
///
/// returns: PixelColor
///
fn wheel(index: u8) -> PixelColor {
let pos = 255 - index;
match pos {
0..=85 => PixelColor {
red: 255 - (pos * 3),
green: 0,
blue: pos * 3,
},
86..=170 => {
let pos = pos - 85;
PixelColor {
red: 0,
green: pos * 3,
blue: 255 - (pos * 3),
}
}
_ => {
let pos = pos - 170;
PixelColor {
red: pos * 3,
green: 255 - (pos * 3),
blue: 0,
}
}
}
}

52
src/patterns/ring.rs Normal file
View File

@@ -0,0 +1,52 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_init_background, impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # Ring
///
/// display a ring on the strip
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Ring<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) color: PixelColor,
pub(crate) background_color: Option<PixelColor>,
pub(crate) ring_size: usize,
}
impl<const N: usize> Pattern for Ring<N> {
type Strip = Strip<N>;
impl_pattern_init_background!(N);
impl_pattern_last_iteration!(Ring);
}
impl<const N: usize> Iterator for Ring<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut strip: Strip<N>;
if let Some(color) = self.background_color {
strip = Strip::new(color);
} else {
strip = Strip::default();
}
let nbr_iter = N / self.ring_size;
let iter = self.current_iteration % nbr_iter;
let first_index = iter * self.ring_size;
let last_index = first_index + self.ring_size;
for i in first_index..last_index {
strip[i] = Some(self.color);
}
self.current_iteration += 1;
Some(strip)
}
}

View File

@@ -0,0 +1,56 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_init_background, impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # RingScanner
///
/// display a ring on the strip fading for the previous ones
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct RingScanner<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) color: PixelColor,
pub(crate) background_color: Option<PixelColor>,
pub(crate) ring_size: usize,
}
impl<const N: usize> Pattern for RingScanner<N> {
type Strip = Strip<N>;
impl_pattern_init_background!(N);
impl_pattern_last_iteration!(ColorWipe);
}
impl<const N: usize> Iterator for RingScanner<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut strip: Strip<N>;
if let Some(color) = self.background_color {
strip = Strip::new(color);
} else {
strip = Strip::default();
}
let nbr_iter = N / self.ring_size;
let iter = self.current_iteration % nbr_iter;
for j in 0..iter {
let first_index = j * self.ring_size;
let last_index = first_index + self.ring_size;
let fade_gain = 2 * (iter - j);
for i in first_index..last_index {
strip[i] = Some(self.color / fade_gain as u8);
}
}
self.current_iteration += 1;
Some(strip)
}
}

59
src/patterns/scanner.rs Normal file
View File

@@ -0,0 +1,59 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_init_background, impl_pattern_last_iteration, Strip};
use serde::{Deserialize, Serialize};
/// # scanner pattern
/// color one pixel with a color and leave a train of this color fading to black
///
/// # note
///
/// background_color work only for color not set by scanner pattern
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Scanner<const N: usize> {
#[serde(default)]
pub(crate) current_iteration: usize,
pub(crate) color: PixelColor,
pub(crate) background_color: Option<PixelColor>,
#[serde(default)]
pub(crate) max_iteration: Option<usize>,
}
impl<const N: usize> Pattern for Scanner<N> {
type Strip = Strip<N>;
impl_pattern_init_background!(N);
impl_pattern_last_iteration!(ColorWipe);
}
impl<const N: usize> Iterator for Scanner<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut strip: Strip<N>;
if let Some(color) = self.background_color {
strip = Strip::new(color);
} else {
strip = Strip::default();
}
let current_led = self.current_iteration % N;
let min_led;
if current_led < 8 {
min_led = 0;
} else {
min_led = current_led - 8;
}
let mut c = self.color;
for i in min_led..current_led {
strip[i] = Some(c);
c = c / 2;
}
self.current_iteration += 1;
Some(strip)
}
}

View File

@@ -0,0 +1,83 @@
use crate::patterns::Pattern;
use crate::pixel_color::PixelColor;
use crate::{impl_pattern_last_iteration, Strip};
use rand::Rng;
use serde::{Deserialize, Serialize};
/// # StarsRandom
///
/// fill strip with background color then color random pixel with other color fading to background color
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct StarsRandom<const N: usize> {
pub(crate) color: PixelColor,
pub(crate) ratio: usize,
pub(crate) max_iteration: Option<usize>,
pub(crate) background_color: Option<PixelColor>,
pub(crate) steps: usize,
#[serde(default)]
pub(crate) current_iteration: usize,
#[serde(skip)]
strip: Strip<N>,
#[serde(skip)]
step: (i8, i8, i8),
}
impl<const N: usize> Pattern for StarsRandom<N> {
type Strip = Strip<N>;
fn init(&mut self) -> Option<Self::Strip> {
if let Some(color) = self.background_color {
self.strip = Strip::<N>::new(color);
let red = self.color.red as i16 - color.red as i16;
let green = color.green as i16 - self.color.green as i16;
let blue = self.color.blue as i16 - color.blue as i16;
self.step = (
-(red / self.steps as i16) as i8,
-(green / self.steps as i16) as i8,
-(blue / self.steps as i16) as i8,
);
Some(self.strip)
} else {
self.step = (
-((self.color.red / self.steps as u8) as i8),
-((self.color.green / self.steps as u8) as i8),
-((self.color.blue / self.steps as u8) as i8),
);
None
}
}
impl_pattern_last_iteration!(StarsRandom);
}
impl<const N: usize> Iterator for StarsRandom<N> {
type Item = Strip<N>;
fn next(&mut self) -> Option<Self::Item> {
if self.is_last_iteration() {
return None;
}
let mut rng = rand::thread_rng();
for i in 0..N {
let current_color = self.strip[i];
if rng.gen_bool(1.0 / self.ratio as f64) {
self.strip[i] = Some(self.color);
} else {
let color = current_color.unwrap();
let background = self.background_color.unwrap_or(PixelColor::default());
let distance = PixelColor {
red: self.step.0.abs() as u8,
green: self.step.1.abs() as u8,
blue: self.step.2.abs() as u8,
};
if color.is_closer(background, distance) {
self.strip[i] = self.background_color;
} else {
self.strip[i] = Some(color.delta(self.step));
}
}
}
self.current_iteration += 1;
Some(self.strip)
}
}

122
src/pixel_color.rs Normal file
View File

@@ -0,0 +1,122 @@
use rand::prelude::ThreadRng;
use rand::Rng;
use serde_derive::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Add, AddAssign, Div, Sub, SubAssign};
///
/// a struct for an RGB color
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Default, Eq, Hash, PartialEq)]
pub struct PixelColor {
pub(crate) red: u8,
pub(crate) green: u8,
pub(crate) blue: u8,
}
impl fmt::Display for PixelColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{},{},{}]", self.red, self.green, self.blue)
}
}
impl Div<u8> for PixelColor {
type Output = PixelColor;
fn div(self, rhs: u8) -> Self::Output {
PixelColor {
red: self.red / rhs,
green: self.green / rhs,
blue: self.blue / rhs,
}
}
}
impl Add for PixelColor {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
PixelColor {
red: self.red.saturating_add(rhs.red),
green: self.green.saturating_add(rhs.green),
blue: self.blue.saturating_add(rhs.blue),
}
}
}
impl Sub<u8> for PixelColor {
type Output = Self;
fn sub(self, rhs: u8) -> Self::Output {
PixelColor {
red: self.red.saturating_sub(rhs),
green: self.green.saturating_sub(rhs),
blue: self.blue.saturating_sub(rhs),
}
}
}
impl SubAssign for PixelColor {
fn sub_assign(&mut self, rhs: Self) {
self.red = self.red.saturating_sub(rhs.red);
self.green = self.red.saturating_sub(rhs.green);
self.blue = self.red.saturating_sub(rhs.blue);
}
}
impl AddAssign for PixelColor {
fn add_assign(&mut self, rhs: Self) {
self.red = self.red.saturating_add(rhs.red);
self.green = self.red.saturating_add(rhs.green);
self.blue = self.red.saturating_add(rhs.blue);
}
}
impl PixelColor {
/// apply random delta to pixel and return computed value
///
/// # Arguments
///
/// * `pixel`: pixel to apply delta
/// * `stability`: amplitude of delta between 0 and `stability`
///
/// returns: PixelColor
///
pub fn random_delta(&self, stability: usize, rng: &mut ThreadRng) -> PixelColor {
let minus_interval = -(stability as i8);
let max_interval = stability as i8;
let red_delta = rng.gen_range(minus_interval..max_interval) as i8;
let green_delta = rng.gen_range(minus_interval..max_interval) as i8;
let blue_delta = rng.gen_range(minus_interval..max_interval) as i8;
self.delta((red_delta, green_delta, blue_delta))
}
pub fn delta(&self, delta: (i8, i8, i8)) -> PixelColor {
let (red, green, blue) = delta;
let red = self.red as i16 + red as i16;
let green = self.green as i16 + green as i16;
let blue = self.blue as i16 + blue as i16;
let bound = |color: i16| {
if color < 0 {
0
} else if color > 255 {
255
} else {
color as u8
}
};
PixelColor {
red: bound(red),
green: bound(green),
blue: bound(blue),
}
}
pub fn is_closer(&self, pixel: PixelColor, distance: PixelColor) -> bool {
(self.red as i16 - pixel.red as i16).abs() <= distance.red as i16
&& (self.green as i16 - pixel.green as i16).abs() <= distance.green as i16
&& (self.blue as i16 - pixel.blue as i16).abs() <= distance.blue as i16
}
}

234
src/runner.rs Normal file
View File

@@ -0,0 +1,234 @@
use crate::config::Device;
use crate::patterns::{Pattern, Patterns};
use crate::pixel_color::PixelColor;
use crate::Strip;
use bluer::Address;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::future::Future;
use std::str::FromStr;
use std::time::Duration;
use tokio::sync::watch::{Receiver, Sender};
use tokio::time::Instant;
pub(crate) mod period {
use serde::{de::Error as _, Deserialize, Deserializer};
use std::time::Duration;
/// Custom deserializer for period from millisecond value
///
/// # Arguments
///
/// * `deserializer`:
///
/// returns: Result<Duration, <D as Deserializer>::Error>
///
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse::<u64>()
.and_then(|d| Ok(Duration::from_millis(d)))
.map_err(|e| D::Error::custom(format!("{}", e)))
}
}
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Context<const N: usize> {
pub(crate) pattern: Patterns<N>,
#[serde(deserialize_with = "period::deserialize")]
pub period: Duration,
}
pub(crate) type DeviceSequence<const N: usize> = Vec<Context<N>>;
/// iterate over `runner` and stream strip data to `tx` channel
///
/// # Arguments
///
/// * `runner`: Sequence to stream
/// * `tx`: channel to stream data to
///
/// returns: ()
///
async fn runner_loop<const N: usize>(runner: DeviceSequence<N>, tx: Sender<Strip<N>>) {
let mut background = Strip::<N>::default();
for mut context in runner {
let mut strip = context.pattern.init().unwrap_or(background);
let delay = context.period;
let mut next_instant = Instant::now() + delay;
while tx.send(strip).is_ok() {
if let Some(value) = context.pattern.next() {
strip = apply_mask(background, value);
} else {
break;
}
tokio::time::sleep_until(next_instant).await;
next_instant += delay;
}
background = strip;
}
}
fn apply_mask<const N: usize>(previous_strip: Strip<N>, strip: Strip<{ N }>) -> Strip<N> {
let mut new_strip = Strip::default();
for i in 0..N {
if let Some(color) = strip[i] {
new_strip[i] = Some(color);
} else {
new_strip[i] = previous_strip[i];
}
}
new_strip
}
#[derive(Default)]
pub struct Spectacle<const N: usize> {
runners: HashMap<DeviceSequence<N>, (Sender<Strip<N>>, Receiver<Strip<N>>)>,
channels: HashMap<Address, Receiver<Strip<N>>>,
default_runner: Option<(DeviceSequence<N>, Sender<Strip<N>>)>,
default_receiver: Option<Receiver<Strip<N>>>,
}
impl<const N: usize> Spectacle<N> {
pub fn add(&mut self, mac_address: Address, runner: &DeviceSequence<N>) {
if let Some((_, rx)) = self.runners.get(runner as &DeviceSequence<N>) {
self.channels.insert(mac_address, rx.clone());
} else {
let (tx, rx) = tokio::sync::watch::channel(Strip::<N>::new(PixelColor {
red: 0,
green: 255,
blue: 0,
}));
self.runners.insert(runner.clone(), (tx, rx.clone()));
self.channels.insert(mac_address, rx.clone());
}
}
/// add default sequence for any device implementing **adafruit neopixel BLE**
/// not specified in an other sequence.
///
/// # Arguments
///
/// * `runner`: sequence to stream
///
/// returns: ()
///
pub fn add_default(&mut self, runner: DeviceSequence<N>) {
let (tx, rx) = tokio::sync::watch::channel(Strip::<N>::new(PixelColor {
red: 0,
green: 0,
blue: 255,
}));
self.default_runner = Some((runner, tx));
self.default_receiver = Some(rx);
}
/// Get channel associated with the mac address passed in parameters. If no channel found,
/// give back the default channel if existing.
///
/// # Arguments
///
/// * `mac_address`: mac address to look for
///
/// returns: Option<Receiver<Strip<{ N }>>>
///
pub fn get_channel(&self, mac_address: &Address) -> Option<Receiver<Strip<N>>> {
let channel = self.channels.get(mac_address);
match channel {
None => {
let d = &self.default_receiver;
match d {
None => None,
Some(runner) => {
println!("default rx");
Some(runner.clone())
}
}
}
Some(channel) => {
println!("rx found ");
Some(channel.clone())
}
}
}
/// start to stream configured sequences. Returns the joinhandle of task to wait for
///
///
///
/// returns: Vec<impl Future<Output = ()>>
///
pub fn run(&mut self) -> Vec<impl Future<Output = ()>> {
let mut joinhandles = vec![];
if self.default_runner.is_some() {
let d = std::mem::take(&mut self.default_runner).unwrap();
joinhandles.push(runner_loop(d.0.clone(), d.1));
println!("add default runner loop");
}
for (runner, (tx, _)) in self.runners.drain().into_iter() {
joinhandles.push(runner_loop(runner.clone(), tx));
println!("add custom runner loop");
}
joinhandles
}
}
impl<const N: usize> From<Vec<Device<N>>> for Spectacle<N> {
fn from(config: Vec<Device<N>>) -> Self {
let mut spectacle: Spectacle<N> = Spectacle::default();
for device in config {
if let Some(mac_addresses) = device.mac_addresses {
for mac_address in mac_addresses {
let mac_address = Address::from_str(&*mac_address);
match mac_address {
Ok(address) => {
spectacle.add(address, &device.sequence);
}
Err(_) => {
println!("error reading mac address");
}
}
}
} else {
spectacle.add_default(device.sequence);
}
}
spectacle
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn apply_mask() {
let pixel_entry = PixelColor {
red: 10,
green: 10,
blue: 10,
};
let pixel_mask = PixelColor {
red: 20,
green: 20,
blue: 20,
};
let mut entry = Strip::<5>::new(pixel_entry);
entry[0] = None;
entry[1] = None;
let mut mask = Strip::<5>::new(pixel_mask);
mask[0] = None;
let mut expected = entry.clone();
expected[1] = Some(pixel_mask);
assert_eq!(expected, super::apply_mask(mask, entry));
}
}

1
src/spectacle.rs Normal file
View File

@@ -0,0 +1 @@