Connection is now a trait
This commit is contained in:
parent
111f35b242
commit
b691ef33f8
20 changed files with 296 additions and 231 deletions
|
|
@ -24,7 +24,7 @@ use servicepoint::*;
|
|||
|
||||
fn main() {
|
||||
// establish connection
|
||||
let connection = Connection::open("172.23.42.29:2342")
|
||||
let connection = connection::Udp::open("172.23.42.29:2342")
|
||||
.expect("connection failed");
|
||||
|
||||
// clear screen content
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ fn main() {
|
|||
cli.text.push("Hello, CCCB!".to_string());
|
||||
}
|
||||
|
||||
let connection = Connection::open(&cli.destination)
|
||||
let connection = connection::Udp::open(&cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
if cli.clear {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ struct Cli {
|
|||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let connection = Connection::open(cli.destination)
|
||||
let connection = connection::Udp::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
use clap::Parser;
|
||||
use rand::{distributions, Rng};
|
||||
use servicepoint::*;
|
||||
use std::net::UdpSocket;
|
||||
use std::thread;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
|
|
@ -16,7 +17,7 @@ struct Cli {
|
|||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let connection = Connection::open(&cli.destination)
|
||||
let connection = connection::Udp::open(&cli.destination)
|
||||
.expect("could not connect to display");
|
||||
let mut field = make_random_field(cli.probability);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ struct Cli {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
let connection = Connection::open(Cli::parse().destination)
|
||||
let connection = connection::Udp::open(Cli::parse().destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut pixels = Bitmap::max_sized();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
use clap::Parser;
|
||||
use rand::Rng;
|
||||
use servicepoint::*;
|
||||
use std::net::UdpSocket;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
|
|
@ -19,7 +20,7 @@ struct Cli {
|
|||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
let connection = Connection::open(cli.destination)
|
||||
let connection = connection::Udp::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
let wait_duration = Duration::from_millis(cli.wait_ms);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
//! Example for how to use the WebSocket connection
|
||||
|
||||
use servicepoint::connection::Websocket;
|
||||
use servicepoint::{
|
||||
Bitmap, Command, CompressionCode, Connection, Grid, Origin,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let connection =
|
||||
Connection::open_websocket("ws://localhost:8080".parse().unwrap())
|
||||
.unwrap();
|
||||
Websocket::open("ws://localhost:8080".parse().unwrap()).unwrap();
|
||||
|
||||
connection.send(Command::Clear).unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! An example on how to modify the image on screen without knowing the current content.
|
||||
|
||||
use clap::Parser;
|
||||
use servicepoint::*;
|
||||
use std::thread;
|
||||
|
|
@ -20,7 +21,7 @@ fn main() {
|
|||
Duration::from_millis(cli.time / PIXEL_WIDTH as u64),
|
||||
);
|
||||
|
||||
let connection = Connection::open(cli.destination)
|
||||
let connection = connection::Udp::open(cli.destination)
|
||||
.expect("could not connect to display");
|
||||
|
||||
let mut enabled_pixels = Bitmap::max_sized();
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ use rand::{
|
|||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use servicepoint::{Brightness, Command, Connection};
|
||||
/// # use servicepoint::{Brightness, Command, Connection, connection};
|
||||
/// let b = Brightness::MAX;
|
||||
/// let val: u8 = b.into();
|
||||
///
|
||||
/// let b = Brightness::try_from(7).unwrap();
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # let connection = connection::Fake;
|
||||
/// let result = connection.send(Command::Brightness(b));
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ use crate::ByteGrid;
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin};
|
||||
/// # use servicepoint::{Brightness, BrightnessGrid, Command, Connection, Grid, Origin, connection};
|
||||
/// let mut grid = BrightnessGrid::new(2,2);
|
||||
/// grid.set(0, 0, Brightness::MIN);
|
||||
/// grid.set(1, 1, Brightness::MIN);
|
||||
///
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # let connection = connection::Fake;
|
||||
/// connection.send(Command::CharBrightness(Origin::new(3, 7), grid)).unwrap()
|
||||
/// ```
|
||||
pub type BrightnessGrid = ValueGrid<Brightness>;
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ use std::string::FromUtf8Error;
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{CharGrid, Command, Connection, Origin};
|
||||
/// # use servicepoint::{connection, CharGrid, Command, Connection, Origin};
|
||||
/// let grid = CharGrid::from("You can\nload multiline\nstrings directly");
|
||||
/// assert_eq!(grid.get_row_str(1), Some("load multiline\0\0".to_string()));
|
||||
///
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # let connection = connection::Fake;
|
||||
/// let command = Command::Utf8Data(Origin::ZERO, grid);
|
||||
/// ```
|
||||
pub type CharGrid = ValueGrid<char>;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ pub type Offset = usize;
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use servicepoint::{Brightness, Command, Connection, Packet};
|
||||
/// use servicepoint::{connection, Brightness, Command, Connection, Packet};
|
||||
/// #
|
||||
/// // create command
|
||||
/// let command = Command::Brightness(Brightness::MAX);
|
||||
|
|
@ -52,7 +52,7 @@ pub type Offset = usize;
|
|||
/// assert_eq!(command, round_tripped);
|
||||
///
|
||||
/// // send command
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # let connection = connection::Fake;
|
||||
/// connection.send(command).unwrap();
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -62,8 +62,8 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::{connection, Command, Connection};
|
||||
/// # let connection = connection::Fake;
|
||||
/// connection.send(Command::Clear).unwrap();
|
||||
/// ```
|
||||
Clear,
|
||||
|
|
@ -75,8 +75,8 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection, Origin, CharGrid};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::*;
|
||||
/// # let connection = connection::Fake;
|
||||
/// let grid = CharGrid::from("Hello,\nWorld!");
|
||||
/// connection.send(Command::Utf8Data(Origin::ZERO, grid)).expect("send failed");
|
||||
/// ```
|
||||
|
|
@ -91,16 +91,16 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::{Command, Connection, Origin, CharGrid, Cp437Grid, connection};
|
||||
/// # let connection = connection::Fake;
|
||||
/// let grid = CharGrid::from("Hello,\nWorld!");
|
||||
/// let grid = Cp437Grid::from(&grid);
|
||||
/// connection.send(Command::Cp437Data(Origin::ZERO, grid)).expect("send failed");
|
||||
/// ```
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection, Cp437Grid, Origin};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::{connection, Command, Connection, Cp437Grid, Origin};
|
||||
/// # let connection = connection::Fake;
|
||||
/// let grid = Cp437Grid::load_ascii("Hello\nWorld", 5, false).unwrap();
|
||||
/// connection.send(Command::Cp437Data(Origin::new(2, 2), grid)).unwrap();
|
||||
/// ```
|
||||
|
|
@ -114,8 +114,8 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
|
||||
/// # let connection = servicepoint::Connection::Fake;
|
||||
/// # use servicepoint::{Command, CompressionCode, Grid, Bitmap, Connection};
|
||||
/// # let connection = servicepoint::connection::Fake;
|
||||
/// #
|
||||
/// let mut pixels = Bitmap::max_sized();
|
||||
/// // draw something to the pixels here
|
||||
|
|
@ -137,8 +137,8 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Brightness, Command, Connection};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::{connection, Brightness, Command, Connection};
|
||||
/// # let connection = connection::Fake;
|
||||
/// let command = Command::Brightness(Brightness::MAX);
|
||||
/// connection.send(command).unwrap();
|
||||
/// ```
|
||||
|
|
@ -186,8 +186,8 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::{connection, Command, Connection};
|
||||
/// # let connection = connection::Fake;
|
||||
/// connection.send(Command::HardReset).unwrap();
|
||||
/// ```
|
||||
HardReset,
|
||||
|
|
@ -199,8 +199,8 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::{connection, Command, Connection};
|
||||
/// # let connection = connection::Fake;
|
||||
/// connection.send(Command::FadeOut).unwrap();
|
||||
/// ```
|
||||
FadeOut,
|
||||
|
|
@ -212,8 +212,8 @@ pub enum Command {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::{Command, Connection};
|
||||
/// # let connection = Connection::Fake;
|
||||
/// # use servicepoint::{connection, Command, Connection};
|
||||
/// # let connection = connection::Fake;
|
||||
/// // this sends a packet that does nothing
|
||||
/// # #[allow(deprecated)]
|
||||
/// connection.send(Command::BitmapLegacy).unwrap();
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
use crate::packet::Packet;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A connection to the display.
|
||||
///
|
||||
/// Used to send [Packets][Packet] or [Commands][crate::Command].
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// let connection = servicepoint::Connection::open("127.0.0.1:2342")
|
||||
/// .expect("connection failed");
|
||||
/// connection.send(servicepoint::Command::Clear)
|
||||
/// .expect("send failed");
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub enum Connection {
|
||||
/// A connection using the UDP protocol.
|
||||
///
|
||||
/// Use this when sending commands directly to the display.
|
||||
///
|
||||
/// Requires the feature "protocol_udp" which is enabled by default.
|
||||
#[cfg(feature = "protocol_udp")]
|
||||
Udp(std::net::UdpSocket),
|
||||
|
||||
/// A connection using the WebSocket protocol.
|
||||
///
|
||||
/// Note that you will need to forward the WebSocket messages via UDP to the display.
|
||||
/// You can use [servicepoint-websocket-relay] for this.
|
||||
///
|
||||
/// To create a new WebSocket automatically, use [Connection::open_websocket].
|
||||
///
|
||||
/// Requires the feature "protocol_websocket" which is disabled by default.
|
||||
///
|
||||
/// [servicepoint-websocket-relay]: https://github.com/kaesaecracker/servicepoint-websocket-relay
|
||||
#[cfg(feature = "protocol_websocket")]
|
||||
WebSocket(
|
||||
std::sync::Mutex<
|
||||
tungstenite::WebSocket<
|
||||
tungstenite::stream::MaybeTlsStream<std::net::TcpStream>,
|
||||
>,
|
||||
>,
|
||||
),
|
||||
|
||||
/// A fake connection for testing that does not actually send anything.
|
||||
Fake,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SendError {
|
||||
#[error("IO error occurred while sending")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[cfg(feature = "protocol_websocket")]
|
||||
#[error("WebSocket error occurred while sending")]
|
||||
WebsocketError(#[from] tungstenite::Error),
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Open a new UDP socket and connect to the provided host.
|
||||
///
|
||||
/// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
|
||||
///
|
||||
/// The address of the display in CCCB is `172.23.42.29:2342`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any errors resulting from binding the udp socket.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// let connection = servicepoint::Connection::open("127.0.0.1:2342")
|
||||
/// .expect("connection failed");
|
||||
/// ```
|
||||
#[cfg(feature = "protocol_udp")]
|
||||
pub fn open(
|
||||
addr: impl std::net::ToSocketAddrs + Debug,
|
||||
) -> std::io::Result<Self> {
|
||||
log::info!("connecting to {addr:?}");
|
||||
let socket = std::net::UdpSocket::bind("0.0.0.0:0")?;
|
||||
socket.connect(addr)?;
|
||||
Ok(Self::Udp(socket))
|
||||
}
|
||||
|
||||
/// Open a new WebSocket and connect to the provided host.
|
||||
///
|
||||
/// Requires the feature "protocol_websocket" which is disabled by default.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tungstenite::http::Uri;
|
||||
/// use servicepoint::{Command, Connection};
|
||||
/// let uri = "ws://localhost:8080".parse().unwrap();
|
||||
/// let mut connection = Connection::open_websocket(uri)
|
||||
/// .expect("could not connect");
|
||||
/// connection.send(Command::Clear)
|
||||
/// .expect("send failed");
|
||||
/// ```
|
||||
#[cfg(feature = "protocol_websocket")]
|
||||
pub fn open_websocket(
|
||||
uri: tungstenite::http::Uri,
|
||||
) -> tungstenite::Result<Self> {
|
||||
use tungstenite::{
|
||||
client::IntoClientRequest, connect, ClientRequestBuilder,
|
||||
};
|
||||
|
||||
log::info!("connecting to {uri:?}");
|
||||
|
||||
let request = ClientRequestBuilder::new(uri).into_client_request()?;
|
||||
let (sock, _) = connect(request)?;
|
||||
Ok(Self::WebSocket(std::sync::Mutex::new(sock)))
|
||||
}
|
||||
|
||||
/// Send something packet-like to the display. Usually this is in the form of a Command.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `packet`: the packet-like to send
|
||||
///
|
||||
/// returns: true if packet was sent, otherwise false
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// let connection = servicepoint::Connection::Fake;
|
||||
/// // turn off all pixels on display
|
||||
/// connection.send(servicepoint::Command::Clear)
|
||||
/// .expect("send failed");
|
||||
/// ```
|
||||
pub fn send(&self, packet: impl Into<Packet>) -> Result<(), SendError> {
|
||||
let packet = packet.into();
|
||||
log::debug!("sending {packet:?}");
|
||||
let data: Vec<u8> = packet.into();
|
||||
match self {
|
||||
#[cfg(feature = "protocol_udp")]
|
||||
Connection::Udp(socket) => {
|
||||
socket
|
||||
.send(&data)
|
||||
.map_err(SendError::IoError)
|
||||
.map(move |_| ()) // ignore Ok value
|
||||
}
|
||||
#[cfg(feature = "protocol_websocket")]
|
||||
Connection::WebSocket(socket) => {
|
||||
let mut socket = socket.lock().unwrap();
|
||||
socket
|
||||
.send(tungstenite::Message::Binary(data.into()))
|
||||
.map_err(SendError::WebsocketError)
|
||||
}
|
||||
Connection::Fake => {
|
||||
let _ = data;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Connection {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "protocol_websocket")]
|
||||
if let Connection::WebSocket(sock) = self {
|
||||
_ = sock.try_lock().map(move |mut sock| sock.close(None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn send_fake() {
|
||||
let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
let packet = Packet::try_from(data).unwrap();
|
||||
Connection::Fake.send(packet).unwrap()
|
||||
}
|
||||
}
|
||||
30
src/connection/fake.rs
Normal file
30
src/connection/fake.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use crate::{Connection, Packet};
|
||||
use log::debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A fake connection for testing that does not actually send anything.
|
||||
pub struct Fake;
|
||||
|
||||
impl Connection for Fake {
|
||||
// TODO: () does not implement Error+Debug, some placeholder is needed
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error> {
|
||||
let data: Vec<u8> = packet.into().into();
|
||||
debug!("Sending fake packet: {data:?}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Packet;
|
||||
|
||||
#[test]
|
||||
fn send_fake() {
|
||||
let data: &[u8] = &[0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
let packet = Packet::try_from(data).unwrap();
|
||||
Fake.send(packet).unwrap()
|
||||
}
|
||||
}
|
||||
53
src/connection/mod.rs
Normal file
53
src/connection/mod.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//! This module contains the [Connection] trait and all implementations provided in this library.
|
||||
|
||||
use crate::Packet;
|
||||
use std::error::Error;
|
||||
use std::fmt::Debug;
|
||||
|
||||
mod fake;
|
||||
#[cfg(feature = "protocol_udp")]
|
||||
mod udp;
|
||||
#[cfg(feature = "protocol_websocket")]
|
||||
mod websocket;
|
||||
|
||||
pub use fake::*;
|
||||
#[cfg(feature = "protocol_udp")]
|
||||
pub use udp::*;
|
||||
#[cfg(feature = "protocol_websocket")]
|
||||
pub use websocket::*;
|
||||
|
||||
/// A connection to the display.
|
||||
///
|
||||
/// Used to send [Packets][Packet] or [Commands][crate::Command].
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// # use servicepoint::Connection;
|
||||
/// let connection = servicepoint::connection::Udp::open("127.0.0.1:2342")
|
||||
/// .expect("connection failed");
|
||||
/// connection.send(servicepoint::Command::Clear)
|
||||
/// .expect("send failed");
|
||||
/// ```
|
||||
pub trait Connection: Debug {
|
||||
/// The error that can occur when sending a packet
|
||||
type Error: Error + Debug;
|
||||
|
||||
/// Send something packet-like to the display. Usually this is in the form of a Command.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `packet`: the packet-like to send
|
||||
///
|
||||
/// returns: true if packet was sent, otherwise false
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use servicepoint::connection::Connection;
|
||||
/// let connection = servicepoint::connection::Fake;
|
||||
/// // turn off all pixels on display
|
||||
/// connection.send(servicepoint::Command::Clear)
|
||||
/// .expect("send failed");
|
||||
/// ```
|
||||
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error>;
|
||||
}
|
||||
54
src/connection/udp.rs
Normal file
54
src/connection/udp.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use crate::{Connection, Packet};
|
||||
use std::fmt::Debug;
|
||||
use std::net::UdpSocket;
|
||||
|
||||
/// A connection using the UDP protocol.
|
||||
///
|
||||
/// Use this when sending commands directly to the display.
|
||||
///
|
||||
/// Requires the feature "protocol_udp" which is enabled by default.
|
||||
#[derive(Debug)]
|
||||
pub struct Udp {
|
||||
socket: UdpSocket,
|
||||
}
|
||||
|
||||
impl Udp {
|
||||
/// Open a new UDP socket and connect to the provided host.
|
||||
///
|
||||
/// Note that this is UDP, which means that the open call can succeed even if the display is unreachable.
|
||||
///
|
||||
/// The address of the display in CCCB is `172.23.42.29:2342`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Any errors resulting from binding the udp socket.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// let connection = servicepoint::connection::Udp::open("127.0.0.1:2342")
|
||||
/// .expect("connection failed");
|
||||
/// ```
|
||||
pub fn open(
|
||||
addr: impl std::net::ToSocketAddrs + Debug,
|
||||
) -> std::io::Result<Self> {
|
||||
log::info!("connecting to {addr:?}");
|
||||
let socket = UdpSocket::bind("0.0.0.0:0")?;
|
||||
socket.connect(addr)?;
|
||||
Ok(Self { socket })
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection for Udp {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error> {
|
||||
let data: Vec<u8> = packet.into().into();
|
||||
self.socket.send(&data).map(move |_| ()) // ignore Ok value
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UdpSocket> for Udp {
|
||||
fn from(socket: UdpSocket) -> Self {
|
||||
Self { socket }
|
||||
}
|
||||
}
|
||||
68
src/connection/websocket.rs
Normal file
68
src/connection/websocket.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use crate::{Connection, Packet};
|
||||
|
||||
/// A connection using the WebSocket protocol.
|
||||
///
|
||||
/// Note that you will need to forward the WebSocket messages via UDP to the display.
|
||||
/// You can use [servicepoint-websocket-relay] for this.
|
||||
///
|
||||
/// To create a new WebSocket automatically, use [Connection::open_websocket].
|
||||
///
|
||||
/// Requires the feature "protocol_websocket" which is disabled by default.
|
||||
///
|
||||
/// [servicepoint-websocket-relay]: https://github.com/kaesaecracker/servicepoint-websocket-relay
|
||||
#[derive(Debug)]
|
||||
pub struct Websocket(
|
||||
std::sync::Mutex<
|
||||
tungstenite::WebSocket<
|
||||
tungstenite::stream::MaybeTlsStream<std::net::TcpStream>,
|
||||
>,
|
||||
>,
|
||||
);
|
||||
|
||||
impl Connection for Websocket {
|
||||
type Error = tungstenite::Error;
|
||||
|
||||
fn send(&self, packet: impl Into<Packet>) -> Result<(), Self::Error> {
|
||||
let data: Vec<u8> = packet.into().into();
|
||||
let mut socket = self.0.lock().unwrap();
|
||||
socket.send(tungstenite::Message::Binary(data.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Websocket {
|
||||
/// Open a new WebSocket and connect to the provided host.
|
||||
///
|
||||
/// Requires the feature "protocol_websocket" which is disabled by default.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use tungstenite::http::Uri;
|
||||
/// use servicepoint::{
|
||||
/// Command,
|
||||
/// connection::{Websocket as WebsocketConnection, Connection}
|
||||
/// };
|
||||
/// let uri = "ws://localhost:8080".parse().unwrap();
|
||||
/// let mut connection = WebsocketConnection::open(uri)
|
||||
/// .expect("could not connect");
|
||||
/// connection.send(Command::Clear)
|
||||
/// .expect("send failed");
|
||||
/// ```
|
||||
pub fn open(uri: tungstenite::http::Uri) -> tungstenite::Result<Self> {
|
||||
use tungstenite::{
|
||||
client::IntoClientRequest, connect, ClientRequestBuilder,
|
||||
};
|
||||
|
||||
log::info!("connecting to {uri:?}");
|
||||
|
||||
let request = ClientRequestBuilder::new(uri).into_client_request()?;
|
||||
let (sock, _) = connect(request)?;
|
||||
Ok(Self(std::sync::Mutex::new(sock)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Websocket {
|
||||
fn drop(&mut self) {
|
||||
_ = self.0.try_lock().map(move |mut sock| sock.close(None));
|
||||
}
|
||||
}
|
||||
|
|
@ -52,8 +52,8 @@ pub const PIXEL_COUNT: usize = PIXEL_WIDTH * PIXEL_HEIGHT;
|
|||
///
|
||||
/// ```rust
|
||||
/// # use std::time::Instant;
|
||||
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap};
|
||||
/// # let connection = servicepoint::Connection::Fake;
|
||||
/// # use servicepoint::{Command, CompressionCode, FRAME_PACING, Origin, Bitmap, Connection};
|
||||
/// # let connection = servicepoint::connection::Fake;
|
||||
/// # let pixels = Bitmap::max_sized();
|
||||
/// loop {
|
||||
/// let start = Instant::now();
|
||||
|
|
|
|||
17
src/lib.rs
17
src/lib.rs
|
|
@ -9,10 +9,10 @@
|
|||
//! ### Clear display
|
||||
//!
|
||||
//! ```rust
|
||||
//! use servicepoint::{Connection, Command};
|
||||
//! use servicepoint::{Connection, Command, connection};
|
||||
//!
|
||||
//! // establish a connection
|
||||
//! let connection = Connection::open("127.0.0.1:2342")
|
||||
//! let connection = connection::Udp::open("127.0.0.1:2342")
|
||||
//! .expect("connection failed");
|
||||
//!
|
||||
//! // turn off all pixels on display
|
||||
|
|
@ -23,8 +23,8 @@
|
|||
//! ### Set all pixels to on
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap};
|
||||
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
|
||||
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, Connection};
|
||||
//! # let connection = servicepoint::connection::Udp::open("127.0.0.1:2342").expect("connection failed");
|
||||
//! // turn on all pixels in a grid
|
||||
//! let mut pixels = Bitmap::max_sized();
|
||||
//! pixels.fill(true);
|
||||
|
|
@ -43,14 +43,14 @@
|
|||
//! ### Send text
|
||||
//!
|
||||
//! ```rust
|
||||
//! # use servicepoint::{Command, CompressionCode, Grid, Bitmap, CharGrid};
|
||||
//! # let connection = servicepoint::Connection::open("127.0.0.1:2342").expect("connection failed");
|
||||
//! # use servicepoint::*;
|
||||
//! # let connection = connection::Udp::open("127.0.0.1:2342").expect("connection failed");
|
||||
//! // create a text grid
|
||||
//! let mut grid = CharGrid::from("Hello\nCCCB?");
|
||||
//! // modify the grid
|
||||
//! grid.set(grid.width() - 1, 1, '!');
|
||||
//! // create the command to send the data
|
||||
//! let command = Command::Utf8Data(servicepoint::Origin::ZERO, grid);
|
||||
//! let command = Command::Utf8Data(Origin::ZERO, grid);
|
||||
//! // send command to display
|
||||
//! connection.send(command).expect("send failed");
|
||||
//! ```
|
||||
|
|
@ -84,7 +84,7 @@ mod command;
|
|||
mod command_code;
|
||||
mod compression;
|
||||
mod compression_code;
|
||||
mod connection;
|
||||
pub mod connection;
|
||||
mod constants;
|
||||
mod cp437_grid;
|
||||
mod data_ref;
|
||||
|
|
@ -95,7 +95,6 @@ mod value_grid;
|
|||
|
||||
#[cfg(feature = "cp437")]
|
||||
mod cp437;
|
||||
|
||||
#[cfg(feature = "cp437")]
|
||||
pub use crate::cp437::Cp437Converter;
|
||||
|
||||
|
|
|
|||
|
|
@ -154,16 +154,13 @@ impl<T: Value> ValueGrid<T> {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn iter(&self) -> Iter<T> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
self.data.iter()
|
||||
}
|
||||
|
||||
/// Iterate over all rows in [ValueGrid] top to bottom.
|
||||
pub fn iter_rows(&self) -> IterGridRows<T> {
|
||||
IterGridRows {
|
||||
byte_grid: self,
|
||||
row: 0,
|
||||
}
|
||||
IterGridRows { grid: self, row: 0 }
|
||||
}
|
||||
|
||||
/// Returns an iterator that allows modifying each value.
|
||||
|
|
@ -318,6 +315,17 @@ impl<T: Value> ValueGrid<T> {
|
|||
chunk.copy_from_slice(row);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enumerates all values in the grid.
|
||||
pub fn enumerate(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (usize, usize, T)> + use<'_, T> {
|
||||
EnumerateGrid {
|
||||
grid: self,
|
||||
column: 0,
|
||||
row: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur when loading a grid
|
||||
|
|
@ -392,7 +400,7 @@ impl<T: Value> From<ValueGrid<T>> for Vec<T> {
|
|||
|
||||
/// An iterator iver the rows in a [ValueGrid]
|
||||
pub struct IterGridRows<'t, T: Value> {
|
||||
byte_grid: &'t ValueGrid<T>,
|
||||
grid: &'t ValueGrid<T>,
|
||||
row: usize,
|
||||
}
|
||||
|
||||
|
|
@ -400,18 +408,43 @@ impl<'t, T: Value> Iterator for IterGridRows<'t, T> {
|
|||
type Item = Iter<'t, T>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.row >= self.byte_grid.height {
|
||||
if self.row >= self.grid.height {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start = self.row * self.byte_grid.width;
|
||||
let end = start + self.byte_grid.width;
|
||||
let result = self.byte_grid.data[start..end].iter();
|
||||
let start = self.row * self.grid.width;
|
||||
let end = start + self.grid.width;
|
||||
let result = self.grid.data[start..end].iter();
|
||||
self.row += 1;
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnumerateGrid<'t, T: Value> {
|
||||
grid: &'t ValueGrid<T>,
|
||||
row: usize,
|
||||
column: usize,
|
||||
}
|
||||
|
||||
impl<'t, T: Value> Iterator for EnumerateGrid<'t, T> {
|
||||
type Item = (usize, usize, T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.row >= self.grid.height {
|
||||
return None;
|
||||
}
|
||||
|
||||
let result =
|
||||
Some((self.row, self.column, self.grid.get(self.row, self.column)));
|
||||
self.column += 1;
|
||||
if self.column == self.grid.width {
|
||||
self.column = 0;
|
||||
self.row += 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue