use core::f64; use std::f64::consts::TAU; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, TcpListener, TcpStream}; use std::sync::mpsc::{self, Receiver, Sender}; use std::thread; use std::time::{Duration, Instant}; use rppal::pwm::{Channel, Polarity, Pwm}; mod packet; use packet::Packet; const ADDRESS: Ipv6Addr = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); const PORT: u16 = 6969; const CLIENT_TIMEOUT: Duration = Duration::from_secs(5); /// 1 kHz PWM frequency const PWM_FREQUENCY: f64 = 1000.; fn triangle_wave(t: f64) -> f64 { (t - (t + 0.5).floor()).abs() * 2. } fn sine_wave(t: f64) -> f64 { (f64::sin(TAU * t) + 1.) * 0.5 } fn square_wave(t: f64, high_time: f64) -> f64 { if t - t.floor() < high_time { 1. } else { 0. } } fn sawtooth_wave(t: f64) -> f64 { t - t.floor() } enum LightMode { Constant(f64), Sine(f64), Triangle(f64), Sawtooth(f64), Square(f64, f64), } impl LightMode { /// The brightness that the LEDs should have at the given instant pub fn brightness(&self, time: Duration) -> f64 { let t = time.as_secs_f64(); match self { Self::Constant(b) => *b, Self::Sine(p) => sine_wave(t / p), Self::Triangle(p) => triangle_wave(t / p), Self::Sawtooth(p) => sawtooth_wave(t / p), Self::Square(period, duty) => square_wave(t / period, *duty), } } } struct ArgumentOutOfRange; impl TryFrom for LightMode { type Error = ArgumentOutOfRange; fn try_from(p: Packet) -> Result { match p { Packet::Constant(b @ 0.0..=1.0) => Ok(Self::Constant(b)), Packet::Sine(0.0) => Err(ArgumentOutOfRange), Packet::Sine(p @ 0.0..) => Ok(Self::Sine(p)), Packet::Triangle(0.0) => Err(ArgumentOutOfRange), Packet::Triangle(p @ 0.0..) => Ok(Self::Triangle(p)), Packet::Sawtooth(0.0) => Err(ArgumentOutOfRange), Packet::Sawtooth(p @ 0.0..) => Ok(Self::Sawtooth(p)), Packet::Square { period: 0.0, .. } => Err(ArgumentOutOfRange), Packet::Square { period: p @ 0.0.., duty: d @ 0.0..=1.0, } => Ok(Self::Square(p, d)), _ => Err(ArgumentOutOfRange), } } } /// Thread to run the light control, meaning the actual PWM signal. Through the /// packet receiver it can be influenced to change the light mode. fn light_control(rx: Receiver) { thread::spawn(move || { let pwm = Pwm::with_frequency(Channel::Pwm0, PWM_FREQUENCY, 0.5, Polarity::Normal, true) .expect("Unable to initialise PWM"); let mut mode = LightMode::Constant(0.0); let mut out_of_sync = true; let start = Instant::now(); loop { if let Ok(p) = rx.try_recv() { match LightMode::try_from(p) { Ok(lm) => { mode = lm; out_of_sync = true; }, Err(_) => eprintln!("Rejecting invalid light mode"), } } if out_of_sync { pwm.set_duty_cycle(mode.brightness(start.elapsed())) .unwrap(); } thread::sleep(Duration::from_millis(10)); } }); } fn main() { println!("Starting light control"); let (tx, rx) = mpsc::channel(); light_control(rx); listen(tx); } fn listen(tx: Sender) { let listener = TcpListener::bind(SocketAddrV6::new(ADDRESS, PORT, 0, 0)).expect("Unable to open server"); for stream in listener.incoming() { if let Ok(stream) = stream { handle_client(stream, tx.clone()); } } } fn handle_client(mut stream: TcpStream, tx: Sender) { thread::spawn(move || { if let Err(e) = stream.set_read_timeout(Some(CLIENT_TIMEOUT)) { eprintln!( "Unable to set client stream timeout. Dropping client. {}", e ); } match Packet::read_from_stream(&mut stream) { Ok(p) => tx.send(p).unwrap(), Err(e) => eprintln!( "Unable to read command packet from stream. Dropping client. {}", e ), } }); }