Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial UDP client implementation #186

Merged
merged 1 commit into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions animator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,14 @@ impl ControllerBuilder {
Ok(self)
}

pub fn udp_lights(mut self, path: &str) -> Result<Self, Box<dyn Error>> {
info!("Using udp light client with endpoint {}", path);
self.client_builder = self
.client_builder
.with(Box::new(client::udp::UdpLightClient::new(path)));
Ok(self)
}

#[cfg(feature = "visualiser")]
pub fn visualiser_lights(mut self) -> Result<Self, Box<dyn Error>> {
info!("Using local visualiser");
Expand Down
7 changes: 7 additions & 0 deletions configurator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ fn capturer_from_options(
);
Box::new(light_client::tcp::TcpLightClient::new(&tcp_endpoint))
}
udp_endpoint if endpoint.starts_with("udp://") => {
info!(
"Using remote UDP light client at endpoint: {}",
udp_endpoint
);
Box::new(light_client::udp::UdpLightClient::new(&udp_endpoint))
}
_ => {
info!("Using local TTY light client");
Box::new(light_client::tty::TtyLightClient::new()?)
Expand Down
1 change: 1 addition & 0 deletions light-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod feedback;
pub mod http;
pub mod tcp;
pub mod tty;
pub mod udp;
#[cfg(feature = "websocket")]
pub mod websocket;

Expand Down
78 changes: 78 additions & 0 deletions light-client/src/udp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::{LightClient, LightClientError};
use async_trait::async_trait;
use lightfx::Frame;
use log::{debug, error, info};
use std::{error::Error, sync::Arc};
use tokio::{net::UdpSocket, sync::Mutex};

#[derive(Clone)]
pub struct UdpLightClient {
url: String,
socket: Arc<Mutex<Option<UdpSocket>>>,
}

impl UdpLightClient {
pub fn new(url: &str) -> Self {
let url = url.strip_prefix("udp://").unwrap_or(url);
Self {
url: url.to_owned(),
socket: Arc::new(Mutex::new(None)),
}
}

async fn connect(&self) -> Result<(), Box<dyn Error>> {
let mut socket = self.socket.lock().await;
if socket.is_none() {
info!("Connecting to remote lights via UDP");
let s = UdpSocket::bind("0.0.0.0:0").await?;
s.connect(&self.url).await?;
// hopefully this gets us some priority, see:
// https://linuxreviews.org/Type_of_Service_(ToS)_and_DSCP_Values
// this sets `high throughput` and `low delay` along with high precedence
s.set_tos(152)?;
*socket = Some(s);
}

Ok(())
}
}

#[async_trait]
impl LightClient for UdpLightClient {
async fn display_frame(&self, frame: &Frame) -> Result<(), LightClientError> {
let pixels: Vec<_> = frame
.pixels_iter()
.cloned()
.map(crate::gamma_correction)
.flat_map(|pixel| vec![pixel.g, pixel.r, pixel.b])
.collect();

if self.socket.lock().await.is_none() {
if let Err(e) = self.connect().await {
error!("Failed to connect to UDP endpoint: {}", e);
} else {
info!("Successfully connected to UDP endpoint");
}
}

let res = {
let mut stream = self.socket.lock().await;
let Some(stream) = stream.as_mut() else {
debug!("UDP endpoint not connected!");
return Err(LightClientError::ConnectionLost);
};
stream
.send(&[&(pixels.len() as u16).to_le_bytes(), pixels.as_slice()].concat())
.await
};

match res {
Ok(_) => Ok(()),
Err(e) => {
error!("Failed to send frame to light client: {}", e);
*self.socket.lock().await = None;
Err(LightClientError::ConnectionLost)
}
}
}
}
2 changes: 2 additions & 0 deletions webapi/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
error!("Web API built without websocket support, ignoring");
} else if url.starts_with("tcp://") {
builder = builder.tcp_lights(&url)?;
} else if url.starts_with("udp://") {
builder = builder.udp_lights(&url)?;
} else {
error!("Unknown remote client protocol, ignoring");
}
Expand Down
Loading