add ability to play separate spectacles.

This commit is contained in:
Tobias Ollive 2022-03-09 15:53:42 +01:00
parent 3d8adc0d52
commit 4c788f7313
6 changed files with 237 additions and 102 deletions

View File

@ -1,40 +1,69 @@
- pattern: - name : toto
type: Fade mac_addresses :
current_iteration: 0 - C9:81:9C:BA:53:BC
nbr_iterations: 20 - D8:D3:AF:87:70:FA
begin_color: sequence:
red: 255 - pattern:
green: 0 type: Fade
blue: 0 current_iteration: 0
end_color: nbr_iterations: 20
red: 0 begin_color:
green: 0 red: 255
blue: 255 green: 0
period: blue: 0
secs: 0 end_color:
nanos: 200000000 red: 0
- pattern: green: 0
type: FillRandom blue: 255
color: period:
red: 0 secs: 0
green: 0 nanos: 200000000
blue: 255 - pattern:
stability: 40 type: ColorWipe
period: max_iteration: 25
secs: 0 current_iteration: 0
nanos: 100000000 color:
- pattern: red: 0
type: ColorWipe green: 255
current_iteration: 0 blue: 0
color: background_color:
red: 0 red: 255
green: 255 green: 0
blue: 0 blue: 255
background_color: period:
red: 255 secs: 0
green: 0 nanos: 100000000
blue: 255 - pattern:
infinite: false type: FillRandom
period: color:
secs: 0 red: 0
nanos: 100000000 green: 0
blue: 255
stability: 40
period:
secs: 0
nanos: 100000000
- name: titi
sequence:
- pattern:
type: Fade
current_iteration: 0
nbr_iterations: 20
begin_color:
red: 255
green: 0
blue: 0
end_color:
red: 0
green: 0
blue: 255
period:
secs: 0
nanos: 200000000
- pattern:
type: Rainbow
current_iteration: 0
period:
secs: 0
nanos: 100000000

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,

23
src/config.rs Normal file
View File

@ -0,0 +1,23 @@
use std::collections::HashSet;
use std::fs;
use crate::runner::DeviceSequence;
use serde_derive::{Deserialize, Serialize};
pub(crate) type Config<const N:usize> = Vec<Device<N>>;
#[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) -> Config<N> {
let file = fs::read_to_string(file).unwrap();
let config: Config<N> = serde_yaml::from_str(&*file).unwrap();
config
}

View File

@ -2,14 +2,18 @@ mod bluetooth;
mod patterns; mod patterns;
mod runner; mod runner;
mod spectacle; mod spectacle;
mod config;
use crate::runner::Runner; use std::borrow::BorrowMut;
use crate::runner::Spectacle;
use bluer::gatt::remote::Characteristic; use bluer::gatt::remote::Characteristic;
use patterns::Strip; use patterns::Strip;
use std::path::Path; use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio::sync::watch; use tokio::sync::watch::Receiver;
use crate::config::{Config, load_from_file};
const SERVICE_UUID: bluer::Uuid = bluer::Uuid::from_u128(0xadaf0900c33242a893bd25e905756cb8); const SERVICE_UUID: bluer::Uuid = bluer::Uuid::from_u128(0xadaf0900c33242a893bd25e905756cb8);
const PIXEL_DATA_UUID: bluer::Uuid = bluer::Uuid::from_u128(0xadaf0903c33242a893bd25e905756cb8); const PIXEL_DATA_UUID: bluer::Uuid = bluer::Uuid::from_u128(0xadaf0903c33242a893bd25e905756cb8);
@ -19,7 +23,9 @@ 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 file = Path::new("spectacle.yml"); let file = Path::new("spectacle.yml");
let config: Runner<25> = Runner::load_from_file(file); let config: Config<25> = load_from_file(file);
let mut spectacle = Spectacle::from(config);
let mut spectacle = Arc::new(Mutex::new(spectacle));
let adapter = bluetooth::create_session().await?; let adapter = bluetooth::create_session().await?;
@ -31,32 +37,49 @@ async fn main() -> bluer::Result<()> {
SERVICE_UUID, SERVICE_UUID,
)); ));
let (tx, rx) = watch::channel(Strip::<STRIP_SIZE>::default()); let spectacle_channels = spectacle.clone();
tokio::spawn(async move { tokio::spawn(async move {
while let Some(device) = rx_scan.recv().await { while let Some(device) = rx_scan.recv().await {
bluetooth::connect(&device, 3).await?; bluetooth::connect(&device, 3).await?;
let char = bluetooth::get_char(&device, SERVICE_UUID, PIXEL_DATA_UUID).await?; let char = bluetooth::get_char(&device, SERVICE_UUID, PIXEL_DATA_UUID).await?;
if let Some(char) = char { if let Some(char) = char {
let mut rx_device = rx.clone(); let rx : Option<Receiver<Strip<STRIP_SIZE>>>;
tokio::spawn(async move { {
println!("device connected : {}", &device.address()); let spectacle_channels = spectacle_channels.lock().unwrap();
while rx_device.changed().await.is_ok() { rx = spectacle_channels.get_channel(&device.address());
let strip = *rx_device.borrow(); }
if write_strip(&strip, &char).await.is_err() { if let Some(mut rx) = rx {
break; tokio::spawn(async move {
}; println!("device connected : {}", &device.address());
} while rx.changed().await.is_ok() {
println!("device {} disconnected", &device.address()); let strip = *rx.borrow();
// drop(rx_device); if write_strip(&strip, &char).await.is_err() {
}); break;
};
}
println!("device {} disconnected", &device.address());
// drop(rx_device);
});
}
} }
} }
bluer::Result::Ok(()) bluer::Result::Ok(())
}); });
println!("starting"); println!("starting");
config.runner_loop(tx).await; // runner_loop(config, &tx).await;
let futures;
{
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;
}
tokio::time::sleep(Duration::from_secs(5)).await; tokio::time::sleep(Duration::from_secs(5)).await;
println!("error sending value"); println!("error sending value");
Ok(()) Ok(())

View File

@ -3,7 +3,7 @@ use serde_derive::{Deserialize, Serialize};
use std::fmt; use std::fmt;
use std::ops::{Div, Index, IndexMut, SubAssign}; use std::ops::{Div, Index, IndexMut, SubAssign};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub(crate) enum Patterns<const N: usize> { pub(crate) enum Patterns<const N: usize> {
Rainbow(Rainbow<N>), Rainbow(Rainbow<N>),
@ -29,7 +29,7 @@ impl<const N: usize> Iterator for Patterns<N> {
/// ///
/// a struct for an RGB color /// a struct for an RGB color
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Copy, Debug, Serialize, Deserialize, Default, Eq, Hash, PartialEq)]
pub struct PixelColor { pub struct PixelColor {
pub(crate) red: u8, pub(crate) red: u8,
pub(crate) green: u8, pub(crate) green: u8,
@ -114,7 +114,7 @@ impl<const N: usize> Strip<N> {
/// ///
/// If max iteration is let to None, it's an infinite pattern. /// If max iteration is let to None, it's an infinite pattern.
/// ///
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Rainbow<const N: usize> { pub struct Rainbow<const N: usize> {
pub(crate) current_iteration: usize, pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>, pub(crate) max_iteration: Option<usize>,
@ -185,7 +185,7 @@ fn wheel(index: u8) -> PixelColor {
/// ///
/// setting max_iteration to None lead the pattern to loop infinitely /// setting max_iteration to None lead the pattern to loop infinitely
/// ///
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct ColorWipe<const N: usize> { pub struct ColorWipe<const N: usize> {
pub(crate) current_iteration: usize, pub(crate) current_iteration: usize,
pub(crate) max_iteration: Option<usize>, pub(crate) max_iteration: Option<usize>,
@ -218,7 +218,7 @@ impl<const N: usize> Iterator for ColorWipe<N> {
/// # fade pattern /// # fade pattern
/// fade from one color to an other /// fade from one color to an other
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Fade<const N: usize> { pub struct Fade<const N: usize> {
pub(crate) current_iteration: usize, pub(crate) current_iteration: usize,
pub(crate) nbr_iterations: usize, pub(crate) nbr_iterations: usize,
@ -274,7 +274,7 @@ impl<const N: usize> Iterator for Fade<N> {
/// # note /// # note
/// ///
/// background_color work only for color not set by scanner pattern /// background_color work only for color not set by scanner pattern
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Scanner<const N: usize> { pub struct Scanner<const N: usize> {
pub(crate) current_iteration: usize, pub(crate) current_iteration: usize,
pub(crate) color: PixelColor, pub(crate) color: PixelColor,
@ -312,7 +312,7 @@ impl<const N: usize> Iterator for Scanner<N> {
/// # FillRandom /// # FillRandom
/// ///
/// fill strip with color then apply random variation /// fill strip with color then apply random variation
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct FillRandom<const N: usize> { pub struct FillRandom<const N: usize> {
pub(crate) color: PixelColor, pub(crate) color: PixelColor,
pub(crate) stability: usize, pub(crate) stability: usize,

View File

@ -1,57 +1,119 @@
use std::collections::HashMap;
use std::future::Future;
use std::str::FromStr;
use crate::patterns::Patterns; use crate::patterns::Patterns;
use crate::Strip; use crate::Strip;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::fs;
use std::time::Duration; use std::time::Duration;
use tokio::sync::watch::Sender; use bluer::Address;
use tokio::sync::watch::{Receiver, Sender};
use tokio::task::JoinHandle;
use crate::config::Config;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
pub struct Context<const N: usize> { pub struct Context<const N: usize> {
pub(crate) pattern: Patterns<N>, pub(crate) pattern: Patterns<N>,
pub period: Duration, pub period: Duration,
} }
impl<const N: usize> IntoIterator for Runner<N> { pub(crate) type DeviceSequence<const N: usize> = Vec<Context<N>>;
type Item = Context<N>;
type IntoIter = <Vec<Context<N>> as IntoIterator>::IntoIter; // so that you don't have to write std::vec::IntoIter, which nobody remembers anyway
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter() pub async fn runner_loop<const N: usize>(runner: DeviceSequence<N>, tx: Sender<Strip<N>>) {
for mut context in runner {
let mut strip = context.pattern.next().unwrap();
let delay = context.period;
println!("{:?}", delay);
while tx.send(strip).is_ok() {
if let Some(value) = context.pattern.next() {
strip = value;
} else {
break;
}
tokio::time::sleep(delay).await;
}
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Default)]
pub(crate) struct Runner<const N: usize>(Vec<Context<N>>); 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> Runner<N> {
pub fn new() -> Runner<N> {
let runner = Vec::<Context<N>>::new();
Runner(runner)
}
pub fn push(&mut self, value: Context<N>) { impl<const N: usize> Spectacle<N> {
self.0.push(value) 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());
pub async fn runner_loop(self, tx: Sender<Strip<N>>) { } else {
for mut context in self { let (tx, rx) = tokio::sync::watch::channel(Strip::<N>::default());
let mut strip = context.pattern.next().unwrap(); self.runners.insert(runner.clone(), (tx, rx.clone()));
let delay = context.period; self.channels.insert(mac_address, rx);
println!("{:?}", delay);
while tx.send(strip).is_ok() {
if let Some(value) = context.pattern.next() {
strip = value;
} else {
break;
}
tokio::time::sleep(delay).await;
}
} }
} }
pub fn load_from_file(file: &std::path::Path) -> Runner<N> {
let file = fs::read_to_string(file).unwrap(); pub fn add_default(&mut self, runner: DeviceSequence<N>) {
let spectacle: Runner<N> = serde_yaml::from_str(&*file).unwrap(); let (tx, rx) = tokio::sync::watch::channel(Strip::<N>::default());
// self.tasks.push(runner.runner_loop(tx));
self.default_runner = Some((runner, tx));
self.default_receiver = Some(rx);
}
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()) }
}
}
pub fn run(&mut self) -> Vec<impl Future<Output=()>> {
let mut joinhandles = vec![];
if let Some(_) = &self.default_runner {
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().take(1) {
joinhandles.push(runner_loop(runner.clone(), tx));
println!("add custom runner loop");
}
joinhandles
}
}
impl<const N: usize> From<Config<N>> for Spectacle<N> {
fn from(config: Config<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 spectacle
} }
} }