zwote_sonne/src/server.rs

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
),
}
});
}