153 lines
3.6 KiB
Rust
153 lines
3.6 KiB
Rust
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<Packet> for LightMode
|
|
{
|
|
type Error = ArgumentOutOfRange;
|
|
|
|
fn try_from(p: Packet) -> Result<Self, Self::Error>
|
|
{
|
|
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<Packet>)
|
|
{
|
|
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<Packet>)
|
|
{
|
|
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<Packet>)
|
|
{
|
|
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
|
|
),
|
|
}
|
|
});
|
|
}
|