mirror of
https://github.com/4yn/slidershim.git
synced 2025-02-08 07:08:15 +01:00
UMIGURI LED is here!
This commit is contained in:
parent
46c9f1c722
commit
7e387ea541
@ -5,7 +5,7 @@ use std::sync::{atomic::Ordering, Arc};
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
device::{brokenithm::BrokenithmJob, config::DeviceMode, diva::DivaSliderJob, hid::HidJob},
|
device::{brokenithm::BrokenithmJob, config::DeviceMode, diva::DivaSliderJob, hid::HidJob},
|
||||||
lighting::{config::LightsMode, lighting::LightsJob},
|
lighting::{config::LightsMode, lighting::LightsJob, umgr_websocket::UmgrWebsocketJob},
|
||||||
output::{config::OutputMode, output::OutputJob},
|
output::{config::OutputMode, output::OutputJob},
|
||||||
shared::{
|
shared::{
|
||||||
utils::LoopTimer,
|
utils::LoopTimer,
|
||||||
@ -23,6 +23,7 @@ pub struct Context {
|
|||||||
device_async_haltable_worker: Option<AsyncHaltableWorker>,
|
device_async_haltable_worker: Option<AsyncHaltableWorker>,
|
||||||
output_worker: Option<AsyncWorker>,
|
output_worker: Option<AsyncWorker>,
|
||||||
lights_worker: Option<AsyncWorker>,
|
lights_worker: Option<AsyncWorker>,
|
||||||
|
lights_haltable_worker: Option<AsyncHaltableWorker>,
|
||||||
timers: Vec<(&'static str, Arc<AtomicF64>)>,
|
timers: Vec<(&'static str, Arc<AtomicF64>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,16 +91,26 @@ impl Context {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let lights_worker = match &config.lights_mode {
|
let (lights_worker, lights_haltable_worker) = match &config.lights_mode {
|
||||||
LightsMode::None => None,
|
LightsMode::None => (None, None),
|
||||||
|
LightsMode::UmgrWebsocket { faster, port } => (
|
||||||
|
None,
|
||||||
|
Some(AsyncHaltableWorker::new(
|
||||||
|
"lights",
|
||||||
|
UmgrWebsocketJob::new(&state, faster, port),
|
||||||
|
)),
|
||||||
|
),
|
||||||
_ => {
|
_ => {
|
||||||
let timer = LoopTimer::new();
|
let timer = LoopTimer::new();
|
||||||
timers.push(("l", timer.fork()));
|
timers.push(("l", timer.fork()));
|
||||||
Some(AsyncWorker::new(
|
(
|
||||||
"lights",
|
Some(AsyncWorker::new(
|
||||||
LightsJob::new(&state, &config.lights_mode),
|
"lights",
|
||||||
timer,
|
LightsJob::new(&state, &config.lights_mode),
|
||||||
))
|
timer,
|
||||||
|
)),
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,6 +122,7 @@ impl Context {
|
|||||||
device_async_haltable_worker,
|
device_async_haltable_worker,
|
||||||
output_worker,
|
output_worker,
|
||||||
lights_worker,
|
lights_worker,
|
||||||
|
lights_haltable_worker,
|
||||||
timers,
|
timers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,10 @@ pub enum LightsMode {
|
|||||||
faster: bool,
|
faster: bool,
|
||||||
url: String,
|
url: String,
|
||||||
},
|
},
|
||||||
|
UmgrWebsocket {
|
||||||
|
faster: bool,
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
Serial {
|
Serial {
|
||||||
faster: bool,
|
faster: bool,
|
||||||
port: String,
|
port: String,
|
||||||
@ -123,6 +127,10 @@ impl LightsMode {
|
|||||||
faster: v["ledFaster"].as_bool()?,
|
faster: v["ledFaster"].as_bool()?,
|
||||||
url: v["ledWebsocketUrl"].as_str()?.to_string(),
|
url: v["ledWebsocketUrl"].as_str()?.to_string(),
|
||||||
},
|
},
|
||||||
|
"umgr-websocket" => LightsMode::UmgrWebsocket {
|
||||||
|
faster: v["ledFaster"].as_bool()?,
|
||||||
|
port: u16::try_from(v["ledUmgrWebsocketPort"].as_i64()?).ok()?,
|
||||||
|
},
|
||||||
"serial" => LightsMode::Serial {
|
"serial" => LightsMode::Serial {
|
||||||
faster: v["ledFaster"].as_bool()?,
|
faster: v["ledFaster"].as_bool()?,
|
||||||
port: v["ledSerialPort"].as_str()?.to_string(),
|
port: v["ledSerialPort"].as_str()?.to_string(),
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
pub mod lighting;
|
pub mod lighting;
|
||||||
|
pub mod umgr_websocket;
|
||||||
|
307
src-slider_io/src/lighting/umgr_websocket.rs
Normal file
307
src-slider_io/src/lighting/umgr_websocket.rs
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use hyper::{
|
||||||
|
header,
|
||||||
|
server::conn::AddrStream,
|
||||||
|
service::{make_service_fn, service_fn},
|
||||||
|
upgrade::{self, Upgraded},
|
||||||
|
Body, Method, Request, Response, Server, StatusCode,
|
||||||
|
};
|
||||||
|
use log::{error, info};
|
||||||
|
use std::{convert::Infallible, future::Future, net::SocketAddr};
|
||||||
|
use tokio::{
|
||||||
|
select,
|
||||||
|
sync::mpsc,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
use tokio_tungstenite::WebSocketStream;
|
||||||
|
use tungstenite::{handshake, Message};
|
||||||
|
|
||||||
|
use crate::{shared::worker::AsyncHaltableJob, state::SliderState};
|
||||||
|
|
||||||
|
async fn error_response() -> Result<Response<Body>, Infallible> {
|
||||||
|
Ok(
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::NOT_FOUND)
|
||||||
|
.body(Body::from(format!("Not found")))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_umgr_leds(ws_stream: WebSocketStream<Upgraded>, state: SliderState, faster: bool) {
|
||||||
|
let (mut ws_write, mut ws_read) = ws_stream.split();
|
||||||
|
|
||||||
|
let (msg_write, mut msg_read) = mpsc::unbounded_channel::<Message>();
|
||||||
|
|
||||||
|
let write_task = async move {
|
||||||
|
// info!("UMGR LED write task started");
|
||||||
|
loop {
|
||||||
|
let message = msg_read.recv().await;
|
||||||
|
// info!("UMGR LED Sending {:?}", message);
|
||||||
|
match message {
|
||||||
|
Some(msg) => match ws_write.send(msg).await.ok() {
|
||||||
|
Some(_) => {}
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// info!("Websocket write task done");
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg_write_handle = msg_write.clone();
|
||||||
|
let state_handle = state.clone();
|
||||||
|
let read_task = async move {
|
||||||
|
// info!("UMGR LED read task started");
|
||||||
|
let mut latest_lights = Instant::now();
|
||||||
|
let delay = match faster {
|
||||||
|
true => Duration::from_micros(33333),
|
||||||
|
false => Duration::from_micros(66666),
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match ws_read.next().await {
|
||||||
|
Some(msg) => match msg {
|
||||||
|
Ok(msg) => match msg {
|
||||||
|
Message::Binary(msg) => {
|
||||||
|
// Full protocol at
|
||||||
|
// https://gist.github.com/inonote/00251fed881a82c9df1e505eef1722bc
|
||||||
|
// https://gist.github.com/4yn/a6737a33fbce51a8c7d64a8045cb2bee
|
||||||
|
|
||||||
|
// info!("UMGR Packet {:?}", msg);
|
||||||
|
|
||||||
|
if msg.len() < 3 {
|
||||||
|
error!("Unexpected length of UMGR led packet");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let version = msg[0];
|
||||||
|
let opcode = msg[1];
|
||||||
|
let payload_len = msg[2];
|
||||||
|
let payload = &msg[3..];
|
||||||
|
|
||||||
|
if payload_len as usize != payload.len() {
|
||||||
|
error!("Unexpected length of UMGR led packet");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (version, opcode, payload_len) {
|
||||||
|
(0x01, 0x10, 103) => {
|
||||||
|
// SetLED
|
||||||
|
let mut lights_handle = state_handle.lights.lock();
|
||||||
|
|
||||||
|
for i in 0..16 {
|
||||||
|
// Copy lights
|
||||||
|
let pos = 1 + i * 3;
|
||||||
|
lights_handle.paint(i * 2, &[payload[pos], payload[pos + 1], payload[pos + 2]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..15 {
|
||||||
|
let pos = 49 + i * 3;
|
||||||
|
lights_handle.paint(
|
||||||
|
1 + i * 2,
|
||||||
|
&[payload[pos], payload[pos + 1], payload[pos + 2]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if latest_lights.elapsed() > delay {
|
||||||
|
lights_handle.dirty = true;
|
||||||
|
latest_lights = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(0x01, 0x11, 0) => {
|
||||||
|
// Initialize
|
||||||
|
info!("UMGR LED Initialize");
|
||||||
|
// Reply with Ready
|
||||||
|
msg_write_handle
|
||||||
|
.send(Message::Binary(vec![0x01, 0x19, 0x00]))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
(0x01, 0x12, 4) => {
|
||||||
|
// Client Ping
|
||||||
|
info!("UMGR LED Ping");
|
||||||
|
// Reply with Pong and extra server magic
|
||||||
|
msg_write_handle
|
||||||
|
.send(Message::Binary(vec![
|
||||||
|
0x01, 0x1a, 6, payload[0], payload[1], payload[2], payload[3], 0x51, 0xed,
|
||||||
|
]))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
(0x01, 0xD0, 0) => {
|
||||||
|
// Client RequestServerInfo
|
||||||
|
info!("UMGR LED Info");
|
||||||
|
// Reply with ReportServerInfo
|
||||||
|
let server_major: u16 = env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap();
|
||||||
|
let server_minor: u16 = env!("CARGO_PKG_VERSION_MINOR").parse().unwrap();
|
||||||
|
let response = [
|
||||||
|
// Command
|
||||||
|
vec![0x01, 0xd8, 44],
|
||||||
|
// Server name
|
||||||
|
b"slider-io\x00\x00\x00\x00\x00\x00\x00".to_vec(),
|
||||||
|
// Server version
|
||||||
|
vec![
|
||||||
|
(server_major >> 8) as u8,
|
||||||
|
(server_major & 0xff) as u8,
|
||||||
|
(server_minor >> 8) as u8,
|
||||||
|
(server_minor & 0xff) as u8,
|
||||||
|
],
|
||||||
|
// Reserved
|
||||||
|
vec![0x00, 0x00],
|
||||||
|
// Hardware name
|
||||||
|
b"generic-slider\x00\x00".to_vec(),
|
||||||
|
// Hardware version
|
||||||
|
vec![0x00, 0x01, 0x00, 0x01],
|
||||||
|
// Reserved
|
||||||
|
vec![0x00, 0x00],
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
|
||||||
|
msg_write_handle.send(Message::Binary(response)).ok();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::Close(_) => {
|
||||||
|
info!("Websocket connection closed");
|
||||||
|
let mut lights_handle = state_handle.lights.lock();
|
||||||
|
lights_handle.reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("Websocket connection error: {}", e);
|
||||||
|
let mut lights_handle = state_handle.lights.lock();
|
||||||
|
lights_handle.reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// info!("Websocket read task done");
|
||||||
|
};
|
||||||
|
|
||||||
|
select! {
|
||||||
|
_ = read_task => {}
|
||||||
|
_ = write_task => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_websocket(
|
||||||
|
mut request: Request<Body>,
|
||||||
|
state: SliderState,
|
||||||
|
faster: bool,
|
||||||
|
) -> Result<Response<Body>, Infallible> {
|
||||||
|
let res = match handshake::server::create_response_with_body(&request, || Body::empty()) {
|
||||||
|
Ok(res) => {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
match upgrade::on(&mut request).await {
|
||||||
|
Ok(upgraded) => {
|
||||||
|
let ws_stream = WebSocketStream::from_raw_socket(
|
||||||
|
upgraded,
|
||||||
|
tokio_tungstenite::tungstenite::protocol::Role::Server,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
handle_umgr_leds(ws_stream, state, faster).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
error!("Websocket upgrade error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Websocket creation error: {}", e);
|
||||||
|
Response::builder()
|
||||||
|
.status(StatusCode::BAD_REQUEST)
|
||||||
|
.body(Body::from(format!("Failed to create websocket: {}", e)))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_request(
|
||||||
|
request: Request<Body>,
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
state: SliderState,
|
||||||
|
faster: bool,
|
||||||
|
) -> Result<Response<Body>, Infallible> {
|
||||||
|
let method = request.method();
|
||||||
|
let path = request.uri().path();
|
||||||
|
if method != Method::GET {
|
||||||
|
error!(
|
||||||
|
"Server unknown method {} -> {} {}",
|
||||||
|
remote_addr, method, path
|
||||||
|
);
|
||||||
|
return error_response().await;
|
||||||
|
}
|
||||||
|
info!("Server {} -> {} {}", remote_addr, method, path);
|
||||||
|
|
||||||
|
match (
|
||||||
|
request.uri().path(),
|
||||||
|
request.headers().contains_key(header::UPGRADE),
|
||||||
|
) {
|
||||||
|
("/", true) => handle_websocket(request, state, faster).await,
|
||||||
|
_ => error_response().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UmgrWebsocketJob {
|
||||||
|
state: SliderState,
|
||||||
|
faster: bool,
|
||||||
|
port: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UmgrWebsocketJob {
|
||||||
|
pub fn new(state: &SliderState, faster: &bool, port: &u16) -> Self {
|
||||||
|
Self {
|
||||||
|
state: state.clone(),
|
||||||
|
faster: *faster,
|
||||||
|
port: *port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AsyncHaltableJob for UmgrWebsocketJob {
|
||||||
|
async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F) {
|
||||||
|
let state = self.state.clone();
|
||||||
|
let faster = self.faster;
|
||||||
|
let make_svc = make_service_fn(|conn: &AddrStream| {
|
||||||
|
let remote_addr = conn.remote_addr();
|
||||||
|
let make_svc_state = state.clone();
|
||||||
|
async move {
|
||||||
|
Ok::<_, Infallible>(service_fn(move |request: Request<Body>| {
|
||||||
|
let svc_state = make_svc_state.clone();
|
||||||
|
handle_request(request, remote_addr, svc_state, faster)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([0, 0, 0, 0], self.port));
|
||||||
|
info!("UMGR LED websocket server listening on {}", addr);
|
||||||
|
|
||||||
|
let server = Server::bind(&addr)
|
||||||
|
.serve(make_svc)
|
||||||
|
.with_graceful_shutdown(stop_signal);
|
||||||
|
|
||||||
|
if let Err(e) = server.await {
|
||||||
|
info!("UMGR LED websocket server stopped: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,11 @@ impl SliderLights {
|
|||||||
pub fn paint(&mut self, idx: usize, color: &[u8; 3]) {
|
pub fn paint(&mut self, idx: usize, color: &[u8; 3]) {
|
||||||
self.ground[3 * idx..3 * (idx + 1)].copy_from_slice(color);
|
self.ground[3 * idx..3 * (idx + 1)].copy_from_slice(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.ground.fill(0);
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores data required for a single slider controller. Data and lighting
|
/// Stores data required for a single slider controller. Data and lighting
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
let ledColorInactive = "#ffff00";
|
let ledColorInactive = "#ffff00";
|
||||||
let ledSensitivity = 20;
|
let ledSensitivity = 20;
|
||||||
let ledWebsocketUrl = "http://localhost:3001";
|
let ledWebsocketUrl = "http://localhost:3001";
|
||||||
|
let ledUmgrWebsocketPort = 7124;
|
||||||
let ledSerialPort = "COM5";
|
let ledSerialPort = "COM5";
|
||||||
|
|
||||||
let dirty = false;
|
let dirty = false;
|
||||||
@ -76,6 +77,7 @@
|
|||||||
ledColorInactive = payload.ledColorInactive || "#ffff00";
|
ledColorInactive = payload.ledColorInactive || "#ffff00";
|
||||||
ledSensitivity = payload.ledSensitivity || 20;
|
ledSensitivity = payload.ledSensitivity || 20;
|
||||||
ledWebsocketUrl = payload.ledWebsocketUrl || "http://localhost:3001";
|
ledWebsocketUrl = payload.ledWebsocketUrl || "http://localhost:3001";
|
||||||
|
ledUmgrWebsocketPort = payload.ledUmgrWebsocketPort || 7124;
|
||||||
ledSerialPort = payload.ledSerialPort || "COM5";
|
ledSerialPort = payload.ledSerialPort || "COM5";
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,6 +132,7 @@
|
|||||||
ledColorInactive,
|
ledColorInactive,
|
||||||
ledSensitivity,
|
ledSensitivity,
|
||||||
ledWebsocketUrl,
|
ledWebsocketUrl,
|
||||||
|
ledUmgrWebsocketPort,
|
||||||
ledSerialPort,
|
ledSerialPort,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -410,6 +413,7 @@
|
|||||||
>
|
>
|
||||||
<option value="attract">Rainbow Attract Mode</option>
|
<option value="attract">Rainbow Attract Mode</option>
|
||||||
<!-- <option value="websocket">Websocket</option> -->
|
<!-- <option value="websocket">Websocket</option> -->
|
||||||
|
<option value="umgr-websocket">UMIGURI Websocket</option>
|
||||||
<option value="serial">Serial</option>
|
<option value="serial">Serial</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -495,6 +499,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if ledMode === "umgr-websocket"}
|
||||||
|
<div class="row">
|
||||||
|
<div class="label">UMIGURI Port</div>
|
||||||
|
<div class="input">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="1024"
|
||||||
|
max="65535"
|
||||||
|
step="1"
|
||||||
|
bind:value={ledUmgrWebsocketPort}
|
||||||
|
on:change={markDirty}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if ledMode === "serial"}
|
{#if ledMode === "serial"}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="label" />
|
<div class="label" />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user