From 4c788f73130a9f4ad4af6dbd032b095287fd8a11 Mon Sep 17 00:00:00 2001 From: Tobias Ollive Date: Wed, 9 Mar 2022 15:53:42 +0100 Subject: [PATCH] add ability to play separate spectacles. --- spectacle.yml | 109 ++++++++++++++++++++++++-------------- src/bluetooth.rs | 2 - src/config.rs | 23 +++++++++ src/main.rs | 59 ++++++++++++++------- src/patterns.rs | 14 ++--- src/runner.rs | 132 ++++++++++++++++++++++++++++++++++------------- 6 files changed, 237 insertions(+), 102 deletions(-) create mode 100644 src/config.rs diff --git a/spectacle.yml b/spectacle.yml index 8d14055..8e955c9 100644 --- a/spectacle.yml +++ b/spectacle.yml @@ -1,40 +1,69 @@ -- 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: FillRandom - color: - red: 0 - green: 0 - blue: 255 - stability: 40 - period: - secs: 0 - nanos: 100000000 -- pattern: - type: ColorWipe - current_iteration: 0 - color: - red: 0 - green: 255 - blue: 0 - background_color: - red: 255 - green: 0 - blue: 255 - infinite: false - period: - secs: 0 - nanos: 100000000 + - name : toto + mac_addresses : + - C9:81:9C:BA:53:BC + - D8:D3:AF:87:70:FA + 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: ColorWipe + max_iteration: 25 + current_iteration: 0 + color: + red: 0 + green: 255 + blue: 0 + background_color: + red: 255 + green: 0 + blue: 255 + period: + secs: 0 + nanos: 100000000 + - pattern: + type: FillRandom + color: + red: 0 + 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 \ No newline at end of file diff --git a/src/bluetooth.rs b/src/bluetooth.rs index d6fb4b2..4805f8c 100644 --- a/src/bluetooth.rs +++ b/src/bluetooth.rs @@ -39,7 +39,6 @@ pub(crate) async fn bluetooth_scan( while let Some(evt) = discover.next().await { match evt { AdapterEvent::DeviceAdded(addr) => { - println!("new device {}", addr); if already_scanned.contains(&addr) { continue; } @@ -49,7 +48,6 @@ pub(crate) async fn bluetooth_scan( Ok(service_found) => { if service_found { tx.send(device).await.unwrap(); - println!("found service in device {}", addr); } } Err(_) => continue, diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..4badadb --- /dev/null +++ b/src/config.rs @@ -0,0 +1,23 @@ +use std::collections::HashSet; +use std::fs; +use crate::runner::DeviceSequence; +use serde_derive::{Deserialize, Serialize}; + + +pub(crate) type Config = Vec>; + +#[derive(Deserialize, Serialize)] +pub struct Device { + pub mac_addresses : Option>, + name : Option, + pub sequence : DeviceSequence, +} + + + pub fn load_from_file(file: &std::path::Path) -> Config { + + + let file = fs::read_to_string(file).unwrap(); + let config: Config = serde_yaml::from_str(&*file).unwrap(); + config + } diff --git a/src/main.rs b/src/main.rs index 194381d..65fde6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,18 @@ mod bluetooth; mod patterns; mod runner; mod spectacle; +mod config; -use crate::runner::Runner; +use std::borrow::BorrowMut; +use crate::runner::Spectacle; use bluer::gatt::remote::Characteristic; use patterns::Strip; use std::path::Path; +use std::sync::{Arc, Mutex}; use std::time::Duration; 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 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")] async fn main() -> bluer::Result<()> { 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?; @@ -31,32 +37,49 @@ async fn main() -> bluer::Result<()> { SERVICE_UUID, )); - let (tx, rx) = watch::channel(Strip::::default()); - + let spectacle_channels = spectacle.clone(); tokio::spawn(async move { 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 mut rx_device = rx.clone(); - tokio::spawn(async move { - println!("device connected : {}", &device.address()); - while rx_device.changed().await.is_ok() { - let strip = *rx_device.borrow(); - if write_strip(&strip, &char).await.is_err() { - break; - }; - } - println!("device {} disconnected", &device.address()); - // drop(rx_device); - }); + let rx : Option>>; + { + let spectacle_channels = spectacle_channels.lock().unwrap(); + rx = spectacle_channels.get_channel(&device.address()); + } + if let Some(mut rx) = rx { + tokio::spawn(async move { + println!("device connected : {}", &device.address()); + while rx.changed().await.is_ok() { + let strip = *rx.borrow(); + if write_strip(&strip, &char).await.is_err() { + break; + }; + } + println!("device {} disconnected", &device.address()); + // drop(rx_device); + }); + } } } bluer::Result::Ok(()) }); 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; println!("error sending value"); Ok(()) diff --git a/src/patterns.rs b/src/patterns.rs index 3afd81e..cf858dd 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -3,7 +3,7 @@ use serde_derive::{Deserialize, Serialize}; use std::fmt; use std::ops::{Div, Index, IndexMut, SubAssign}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)] #[serde(tag = "type")] pub(crate) enum Patterns { Rainbow(Rainbow), @@ -29,7 +29,7 @@ impl Iterator for Patterns { /// /// 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(crate) red: u8, pub(crate) green: u8, @@ -114,7 +114,7 @@ impl Strip { /// /// 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 { pub(crate) current_iteration: usize, pub(crate) max_iteration: Option, @@ -185,7 +185,7 @@ fn wheel(index: u8) -> PixelColor { /// /// 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 { pub(crate) current_iteration: usize, pub(crate) max_iteration: Option, @@ -218,7 +218,7 @@ impl Iterator for ColorWipe { /// # fade pattern /// fade from one color to an other -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)] pub struct Fade { pub(crate) current_iteration: usize, pub(crate) nbr_iterations: usize, @@ -274,7 +274,7 @@ impl Iterator for Fade { /// # note /// /// 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 { pub(crate) current_iteration: usize, pub(crate) color: PixelColor, @@ -312,7 +312,7 @@ impl Iterator for Scanner { /// # FillRandom /// /// fill strip with color then apply random variation -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)] pub struct FillRandom { pub(crate) color: PixelColor, pub(crate) stability: usize, diff --git a/src/runner.rs b/src/runner.rs index cea0adb..190a2b6 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,57 +1,119 @@ +use std::collections::HashMap; +use std::future::Future; +use std::str::FromStr; use crate::patterns::Patterns; use crate::Strip; use serde_derive::{Deserialize, Serialize}; -use std::fs; 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 { pub(crate) pattern: Patterns, pub period: Duration, } -impl IntoIterator for Runner { - type Item = Context; - type IntoIter = > as IntoIterator>::IntoIter; // so that you don't have to write std::vec::IntoIter, which nobody remembers anyway +pub(crate) type DeviceSequence = Vec>; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + +pub async fn runner_loop(runner: DeviceSequence, tx: Sender>) { + 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)] -pub(crate) struct Runner(Vec>); +#[derive(Default)] +pub struct Spectacle { + runners: HashMap, (Sender>, Receiver>)>, + channels: HashMap>>, + default_runner: Option<(DeviceSequence, Sender>)>, + default_receiver: Option>>, +} -impl Runner { - pub fn new() -> Runner { - let runner = Vec::>::new(); - Runner(runner) - } - pub fn push(&mut self, value: Context) { - self.0.push(value) - } - - pub async fn runner_loop(self, tx: Sender>) { - for mut context in self { - 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; - } +impl Spectacle { + pub fn add(&mut self, mac_address: Address, runner: &DeviceSequence) { + if let Some((_, rx)) = self.runners.get(&runner as &DeviceSequence) { + self.channels.insert(mac_address, rx.clone()); + } else { + let (tx, rx) = tokio::sync::watch::channel(Strip::::default()); + self.runners.insert(runner.clone(), (tx, rx.clone())); + self.channels.insert(mac_address, rx); } } - pub fn load_from_file(file: &std::path::Path) -> Runner { - let file = fs::read_to_string(file).unwrap(); - let spectacle: Runner = serde_yaml::from_str(&*file).unwrap(); + + pub fn add_default(&mut self, runner: DeviceSequence) { + let (tx, rx) = tokio::sync::watch::channel(Strip::::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>> { + 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> { + 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 From> for Spectacle { + fn from(config: Config) -> Self { + let mut spectacle: Spectacle = 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 } }