add ability to play separate spectacles.
This commit is contained in:
parent
3d8adc0d52
commit
4c788f7313
|
@ -1,4 +1,9 @@
|
||||||
- pattern:
|
- name : toto
|
||||||
|
mac_addresses :
|
||||||
|
- C9:81:9C:BA:53:BC
|
||||||
|
- D8:D3:AF:87:70:FA
|
||||||
|
sequence:
|
||||||
|
- pattern:
|
||||||
type: Fade
|
type: Fade
|
||||||
current_iteration: 0
|
current_iteration: 0
|
||||||
nbr_iterations: 20
|
nbr_iterations: 20
|
||||||
|
@ -13,18 +18,9 @@
|
||||||
period:
|
period:
|
||||||
secs: 0
|
secs: 0
|
||||||
nanos: 200000000
|
nanos: 200000000
|
||||||
- pattern:
|
- pattern:
|
||||||
type: FillRandom
|
|
||||||
color:
|
|
||||||
red: 0
|
|
||||||
green: 0
|
|
||||||
blue: 255
|
|
||||||
stability: 40
|
|
||||||
period:
|
|
||||||
secs: 0
|
|
||||||
nanos: 100000000
|
|
||||||
- pattern:
|
|
||||||
type: ColorWipe
|
type: ColorWipe
|
||||||
|
max_iteration: 25
|
||||||
current_iteration: 0
|
current_iteration: 0
|
||||||
color:
|
color:
|
||||||
red: 0
|
red: 0
|
||||||
|
@ -34,7 +30,40 @@
|
||||||
red: 255
|
red: 255
|
||||||
green: 0
|
green: 0
|
||||||
blue: 255
|
blue: 255
|
||||||
infinite: false
|
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:
|
period:
|
||||||
secs: 0
|
secs: 0
|
||||||
nanos: 100000000
|
nanos: 100000000
|
|
@ -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
23
src/config.rs
Normal 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
|
||||||
|
}
|
41
src/main.rs
41
src/main.rs
|
@ -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,18 +37,22 @@ 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>>>;
|
||||||
|
{
|
||||||
|
let spectacle_channels = spectacle_channels.lock().unwrap();
|
||||||
|
rx = spectacle_channels.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;
|
||||||
};
|
};
|
||||||
|
@ -52,11 +62,24 @@ async fn main() -> bluer::Result<()> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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(())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
118
src/runner.rs
118
src/runner.rs
|
@ -1,40 +1,26 @@
|
||||||
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
pub async fn runner_loop<const N: usize>(runner: DeviceSequence<N>, tx: Sender<Strip<N>>) {
|
||||||
pub(crate) struct Runner<const N: usize>(Vec<Context<N>>);
|
for mut context in runner {
|
||||||
|
|
||||||
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>) {
|
|
||||||
self.0.push(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn runner_loop(self, tx: Sender<Strip<N>>) {
|
|
||||||
for mut context in self {
|
|
||||||
let mut strip = context.pattern.next().unwrap();
|
let mut strip = context.pattern.next().unwrap();
|
||||||
let delay = context.period;
|
let delay = context.period;
|
||||||
println!("{:?}", delay);
|
println!("{:?}", delay);
|
||||||
|
@ -47,11 +33,87 @@ impl<const N: usize> Runner<N> {
|
||||||
tokio::time::sleep(delay).await;
|
tokio::time::sleep(delay).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>::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<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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user