Skip to content

Commit

Permalink
Add initial UDP client implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
krzmaz committed Nov 5, 2023
1 parent ba9b2cd commit f2749d0
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 20 deletions.
2 changes: 1 addition & 1 deletion animator/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
rustmas_animator::Controller::builder().points_from_file(&cli.positions_file)?;

builder = match cli.lights_endpoint {
Some(path) => builder.remote_lights(&path)?,
Some(path) => builder.http_lights(&path)?,
None => {
#[cfg(not(feature = "visualiser"))]
panic!("Visualiser feature is disabled, please provide a light client endpoint");
Expand Down
14 changes: 11 additions & 3 deletions animator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,19 @@ impl ControllerBuilder {
Ok(self)
}

pub fn remote_lights(mut self, path: &str) -> Result<Self, Box<dyn Error>> {
info!("Using remote light client with endpoint {}", path);
pub fn http_lights(mut self, path: &str) -> Result<Self, Box<dyn Error>> {
info!("Using http light client with endpoint {}", path);
self.client_builder = self
.client_builder
.with(Box::new(client::RemoteLightClient::new(path)));
.with(Box::new(client::HttpLightClient::new(path)));
Ok(self)
}

pub async 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::UdpLightClient::new(path).await));
Ok(self)
}

Expand Down
33 changes: 24 additions & 9 deletions configurator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ enum Commands {
},
}

fn capturer_from_options(
async fn capturer_from_options(
lights_endpoint: Option<String>,
ip_camera: Option<String>,
number_of_lights: usize,
Expand All @@ -80,12 +80,25 @@ fn capturer_from_options(
};

let light_client: Box<dyn light_client::LightClient> = if let Some(endpoint) = lights_endpoint {
if endpoint.starts_with("http://") {
info!("Using remote HTTP light client at endpoint: {}", endpoint);
Box::new(light_client::RemoteLightClient::new(&endpoint))
} else {
info!("Using local TTY light client");
Box::new(light_client::tty::TtyLightClient::new()?)
match endpoint {
http_endpoint if endpoint.starts_with("http://") => {
info!(
"Using remote HTTP light client at endpoint: {}",
http_endpoint
);
Box::new(light_client::HttpLightClient::new(&http_endpoint))
}
udp_endpoint if endpoint.starts_with("udp://") => {
info!(
"Using remote HTTP light client at endpoint: {}",
udp_endpoint
);
Box::new(light_client::UdpLightClient::new(&udp_endpoint).await)
}
_ => {
info!("Using local TTY light client");
Box::new(light_client::tty::TtyLightClient::new()?)
}
}
} else {
info!("Using mock light client");
Expand Down Expand Up @@ -134,7 +147,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
front_coordinates,
back_coordinates,
} => {
let mut capturer = capturer_from_options(lights_endpoint, ip_camera, number_of_lights)?;
let mut capturer =
capturer_from_options(lights_endpoint, ip_camera, number_of_lights).await?;
let front = capture_or_read_coordinates(
&mut capturer,
"front",
Expand Down Expand Up @@ -175,7 +189,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
number_of_lights,
save_pictures,
} => {
let mut capturer = capturer_from_options(lights_endpoint, ip_camera, number_of_lights)?;
let mut capturer =
capturer_from_options(lights_endpoint, ip_camera, number_of_lights).await?;
let coords = capturer.capture_perspective("2d", save_pictures).await?;
let light_positions = coords
.iter()
Expand Down
54 changes: 48 additions & 6 deletions light-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ pub mod websocket;
use std::{error::Error, sync::mpsc};
use std::{
fmt,
sync::{Mutex, MutexGuard},
sync::{Arc, Mutex, MutexGuard},
time::Duration,
};

use async_trait::async_trait;
use lightfx::{Color, Frame};
use log::debug;
#[cfg(feature = "visualiser")]
use log::{error, info};
use log::error;
use log::{debug, info};
use reqwest::header::CONNECTION;
use tokio::net::{self, UdpSocket};

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum LightClientError {
Expand Down Expand Up @@ -63,12 +64,12 @@ impl LightClient for MockLightClient {
}

#[derive(Clone)]
pub struct RemoteLightClient {
pub struct HttpLightClient {
url: String,
http_client: reqwest::Client,
}

impl RemoteLightClient {
impl HttpLightClient {
pub fn new(url: &str) -> Self {
Self {
url: url.to_owned(),
Expand All @@ -95,7 +96,7 @@ fn gamma_correction(color: Color) -> Color {
}

#[async_trait]
impl LightClient for RemoteLightClient {
impl LightClient for HttpLightClient {
async fn display_frame(&self, frame: &Frame) -> Result<(), LightClientError> {
let pixels: Vec<_> = frame
.pixels_iter()
Expand All @@ -121,6 +122,47 @@ impl LightClient for RemoteLightClient {
}
}

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

impl UdpLightClient {
pub async fn new(url: &str) -> Self {
let url = url.replace("udp://", "");
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap());
info!("Bound socket: {}", socket.local_addr().unwrap());
info!("Trying to connect to: {}", url);
let _ = socket.connect(url).await;
// we don't want old news
socket.set_ttl(1).unwrap();
// hopefully this gets us some priority
socket.set_tos(152).unwrap();
Self { socket }
}
}

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

match self.socket.send(&pixels).await {
Ok(_) => Ok(()),
Err(err) => {
debug!("Failed to send frame to light client: {}", err);
Err(LightClientError::ConnectionLost)
}
}
}
}

#[cfg(feature = "visualiser")]
pub struct VisualiserLightClient {
_join_handle: std::thread::JoinHandle<()>,
Expand Down
4 changes: 3 additions & 1 deletion webapi/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

if let Ok(url) = env::var("RUSTMAS_LIGHTS_URL") {
if url.starts_with("http://") {
builder = builder.remote_lights(&url)?;
builder = builder.http_lights(&url)?;
} else if url.starts_with("ws://") {
#[cfg(feature = "websocket")]
{
Expand All @@ -214,6 +214,8 @@ async fn main() -> Result<(), Box<dyn Error>> {

#[cfg(not(feature = "websocket"))]
error!("Web API built without websocket support, ignoring");
} else if url.starts_with("udp://") {
builder = builder.udp_lights(&url).await?;
} else {
error!("Unknown remote client protocol, ignoring");
}
Expand Down

0 comments on commit f2749d0

Please sign in to comment.