add ability to play separate spectacles.
This commit is contained in:
parent
3d8adc0d52
commit
4c788f7313
|
@ -1,3 +1,8 @@
|
|||
- name : toto
|
||||
mac_addresses :
|
||||
- C9:81:9C:BA:53:BC
|
||||
- D8:D3:AF:87:70:FA
|
||||
sequence:
|
||||
- pattern:
|
||||
type: Fade
|
||||
current_iteration: 0
|
||||
|
@ -13,18 +18,9 @@
|
|||
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
|
||||
max_iteration: 25
|
||||
current_iteration: 0
|
||||
color:
|
||||
red: 0
|
||||
|
@ -34,7 +30,40 @@
|
|||
red: 255
|
||||
green: 0
|
||||
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:
|
||||
secs: 0
|
||||
nanos: 100000000
|
|
@ -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,
|
||||
|
|
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 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,18 +37,22 @@ async fn main() -> bluer::Result<()> {
|
|||
SERVICE_UUID,
|
||||
));
|
||||
|
||||
let (tx, rx) = watch::channel(Strip::<STRIP_SIZE>::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();
|
||||
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 {
|
||||
println!("device connected : {}", &device.address());
|
||||
while rx_device.changed().await.is_ok() {
|
||||
let strip = *rx_device.borrow();
|
||||
while rx.changed().await.is_ok() {
|
||||
let strip = *rx.borrow();
|
||||
if write_strip(&strip, &char).await.is_err() {
|
||||
break;
|
||||
};
|
||||
|
@ -52,11 +62,24 @@ async fn main() -> bluer::Result<()> {
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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(())
|
||||
|
|
|
@ -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<const N: usize> {
|
||||
Rainbow(Rainbow<N>),
|
||||
|
@ -29,7 +29,7 @@ impl<const N: usize> Iterator for Patterns<N> {
|
|||
|
||||
///
|
||||
/// 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<const N: usize> Strip<N> {
|
|||
///
|
||||
/// 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(crate) current_iteration: 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
|
||||
///
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Eq, Hash, PartialEq, Copy, Clone)]
|
||||
pub struct ColorWipe<const N: usize> {
|
||||
pub(crate) current_iteration: usize,
|
||||
pub(crate) max_iteration: Option<usize>,
|
||||
|
@ -218,7 +218,7 @@ impl<const N: usize> Iterator for ColorWipe<N> {
|
|||
|
||||
/// # 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<const N: usize> {
|
||||
pub(crate) current_iteration: usize,
|
||||
pub(crate) nbr_iterations: usize,
|
||||
|
@ -274,7 +274,7 @@ impl<const N: usize> Iterator for Fade<N> {
|
|||
/// # 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<const N: usize> {
|
||||
pub(crate) current_iteration: usize,
|
||||
pub(crate) color: PixelColor,
|
||||
|
@ -312,7 +312,7 @@ impl<const N: usize> Iterator for Scanner<N> {
|
|||
/// # 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<const N: usize> {
|
||||
pub(crate) color: PixelColor,
|
||||
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::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<const N: usize> {
|
||||
pub(crate) pattern: Patterns<N>,
|
||||
pub period: Duration,
|
||||
}
|
||||
|
||||
impl<const N: usize> IntoIterator for Runner<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
|
||||
pub(crate) type DeviceSequence<const N: usize> = Vec<Context<N>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(crate) struct Runner<const N: usize>(Vec<Context<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>) {
|
||||
self.0.push(value)
|
||||
}
|
||||
|
||||
pub async fn runner_loop(self, tx: Sender<Strip<N>>) {
|
||||
for mut context in self {
|
||||
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);
|
||||
|
@ -49,9 +35,85 @@ impl<const N: usize> Runner<N> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn load_from_file(file: &std::path::Path) -> Runner<N> {
|
||||
let file = fs::read_to_string(file).unwrap();
|
||||
let spectacle: Runner<N> = serde_yaml::from_str(&*file).unwrap();
|
||||
#[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 add_default(&mut self, runner: DeviceSequence<N>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user