~jojo/jojos-hue

cf432078869f3cb7d90f4abda253613b227780ad — JoJo 12 days ago 6a5bfbb
Base procedure color / brightness / etc off of last color
1 files changed, 46 insertions(+), 28 deletions(-)

M server/src/main.rs
M server/src/main.rs => server/src/main.rs +46 -28
@@ 1,5 1,5 @@
use byteorder::{ByteOrder, LittleEndian};
use palette::{FromColor, Hsv, RgbHue, Srgb};
use palette::{FromColor, Hsv, Hue, RgbHue, Shade, Srgb};
use std::{
    collections::HashMap,
    f32::consts as f32,


@@ 27,13 27,21 @@ struct QueryRgb {
}

#[derive(Clone, Copy, Debug)]
enum Cmd {
enum QueryCmd {
    All(Srgb),
    Off,
    Cycle,
    Breathing,
}

#[derive(Clone, Copy)]
enum Cmd {
    All,
    Off,
    Cycle,
    Breathing,
}

#[async_std::main]
async fn main() -> tide::Result<()> {
    let (heartbeat_tx, heartbeat_rx) = channel();


@@ 72,29 80,30 @@ async fn main() -> tide::Result<()> {
    Ok(())
}

async fn all_handler(req: Request<()>, tx: SyncSender<Cmd>) -> tide::Result {
async fn all_handler(req: Request<()>, tx: SyncSender<QueryCmd>) -> tide::Result {
    let c: QueryRgb = req.query()?;
    tx.send(Cmd::All(Srgb::new(c.r, c.g, c.b))).unwrap();
    tx.send(QueryCmd::All(Srgb::new(c.r, c.g, c.b))).unwrap();
    Ok("cool, ty".into())
}

async fn proc_handler(req: Request<()>, tx: SyncSender<Cmd>) -> tide::Result {
async fn proc_handler(req: Request<()>, tx: SyncSender<QueryCmd>) -> tide::Result {
    let s = req.param("name")?;
    match s {
        "off" => tx.send(Cmd::Off).unwrap(),
        "cycle" => tx.send(Cmd::Cycle).unwrap(),
        "breathing" => tx.send(Cmd::Breathing).unwrap(),
        "off" => tx.send(QueryCmd::Off).unwrap(),
        "cycle" => tx.send(QueryCmd::Cycle).unwrap(),
        "breathing" => tx.send(QueryCmd::Breathing).unwrap(),
        _ => {}
    }
    Ok("cool, ty".into())
}

fn controller(heartbeat_rx: Receiver<Heartbeat>, cmd_rx: Receiver<Cmd>) {
    let t0 = Instant::now();
fn controller(heartbeat_rx: Receiver<Heartbeat>, cmd_rx: Receiver<QueryCmd>) {
    let sender = Arc::new(UdpSocket::bind("192.168.0.41:7018").unwrap());
    let mut clients = HashMap::<SocketAddr, (Instant, Sender<_>)>::new();
    let mut tprev = Instant::now();
    let mut cmd = Cmd::All(Srgb::new(0.0, 0.0, 0.0));
    let mut tcmd = Instant::now();
    let mut base = Hsv::new(0.0, 0.0, 0.0);
    let mut cmd = Cmd::Off;
    loop {
        while let Ok((addr, nleds)) = heartbeat_rx.try_recv() {
            match clients.get_mut(&addr) {


@@ 116,11 125,20 @@ fn controller(heartbeat_rx: Receiver<Heartbeat>, cmd_rx: Receiver<Cmd>) {
        });
        while let Some(new_cmd) = cmd_rx.try_iter().last() {
            println!("New command: {:?}", new_cmd);
            cmd = new_cmd;
            tcmd = Instant::now();
            match new_cmd {
                QueryCmd::All(c) => {
                    base = Hsv::from_color(c);
                    cmd = Cmd::All;
                }
                QueryCmd::Off => cmd = Cmd::Off,
                QueryCmd::Cycle => cmd = Cmd::Cycle,
                QueryCmd::Breathing => cmd = Cmd::Breathing,
            }
        }
        for (_, tx) in clients.values() {
            let t = t0.elapsed().as_secs_f64();
            tx.send((cmd, t)).ok();
            let t = tcmd.elapsed().as_secs_f64();
            tx.send((cmd, base, t)).ok();
        }
        let dt = tprev.elapsed();
        tprev = Instant::now();


@@ 131,14 149,14 @@ fn controller(heartbeat_rx: Receiver<Heartbeat>, cmd_rx: Receiver<Cmd>) {
    }
}

fn setup_client(addr: SocketAddr, nleds: u16, sender: Arc<UdpSocket>) -> Sender<(Cmd, f64)> {
    let (tx, rx) = channel::<(Cmd, f64)>();
fn setup_client(addr: SocketAddr, nleds: u16, sender: Arc<UdpSocket>) -> Sender<(Cmd, Hsv, f64)> {
    let (tx, rx) = channel();
    thread::spawn(move || loop {
        let magic_cookie = b"satan";
        let (cmd, t) = rx
        let (cmd, base, t) = rx
            .recv()
            .expect("Client channel closed. Time for this thread to die!");
        let color_data = lightf(cmd, t, nleds)
        let color_data = lightf(cmd, base, t, nleds)
            .into_iter()
            .flat_map(|c| once(c.red).chain(once(c.green)).chain(once(c.blue)))
            .map(|x| (x * 256.0).min(256.0).max(0.0) as u8);


@@ 162,25 180,25 @@ fn parse_heartbeat(msg: &[u8; 13], addr: SocketAddr) -> Option<Heartbeat> {
    }
}

fn lightf(cmd: Cmd, t: f64, nleds: u16) -> Vec<Srgb> {
fn lightf(cmd: Cmd, base: Hsv, t: f64, nleds: u16) -> Vec<Srgb> {
    match cmd {
        Cmd::All(c) => vec![c; nleds as usize],
        Cmd::Off => vec![Srgb::new(0.0, 0.0, 0.0); nleds as usize],
        Cmd::All => vec![Srgb::from_color(base); nleds as usize],
        Cmd::Off => vec![Srgb::from_color(base.darken(t.min(1.0) as f32)); nleds as usize],
        Cmd::Cycle => {
            vec![
                Srgb::from_color(Hsv::new(RgbHue::from_radians(t / 2.0), 1.0, 1.0)).into_format();
                Srgb::from_color(base.shift_hue(hue_f64_to_f32(RgbHue::from_radians(t / 6.0))));
                nleds as usize
            ]
        }
        Cmd::Breathing => {
            vec![
                Srgb::from_color(Hsv::new(
                    RgbHue::from_radians(1.9 * f32::PI),
                    1.0,
                    0.5 + 0.4 * (t * 0.8).sin() as f32
                ));
                Srgb::from_color(base.darken(0.9 * (t / 2.0).sin().powi(2) as f32));
                nleds as usize
            ]
        }
        } // Cmd::AudioVis => {}
    }
}

fn hue_f64_to_f32(h: RgbHue<f64>) -> RgbHue<f32> {
    RgbHue::from_radians(h.to_radians() as f32)
}