use std::cmp::min; use std::io; use std::io::{ErrorKind, Read, Write}; use std::sync::mpsc; use bytes::{Buf, BufMut, Bytes, BytesMut}; use enum_ordinalize::Ordinalize; use serialport::SerialPort; use crate::characteristics::{LoupedeckButton, LoupedeckKnob}; use crate::commands::LoupedeckCommand; use crate::events::RotationDirection::{Clockwise, Counterclockwise}; use crate::events::{LoupedeckEvent, LoupedeckInternalEvent}; pub(crate) const WS_UPGRADE_REQUEST: &str = r#"GET /index.html HTTP/1.1 Connection: Upgrade Upgrade: websocket Sec-WebSocket-Key: 123abc "#; pub(crate) const WS_UPGRADE_RESPONSE_START: &str = "HTTP/1.1 101 Switching Protocols\r\n\ Upgrade: websocket\r\n\ Connection: Upgrade\r\n\ Sec-WebSocket-Accept: ALtlZo9FMEUEQleXJmq++ukUQ1s="; const MESSAGE_START_BYTE: u8 = 0x82; const MAX_MESSAGE_LENGTH: usize = u8::MAX as usize; enum ParseMessageResult { InternalEvent(LoupedeckInternalEvent), PublicEvent(LoupedeckEvent), Nothing, } impl From for ParseMessageResult { fn from(value: LoupedeckInternalEvent) -> Self { ParseMessageResult::InternalEvent(value) } } impl From for ParseMessageResult { fn from(value: LoupedeckEvent) -> Self { ParseMessageResult::PublicEvent(value) } } pub(crate) fn read_messages_worker( mut port: Box, public_sender: flume::Sender, internal_sender: mpsc::SyncSender, ) { let mut internal_sender = Some(internal_sender); let mut should_stop = false; let mut buffer = BytesMut::new(); while !should_stop { let mut chunk = BytesMut::zeroed(MAX_MESSAGE_LENGTH); let read_result = port.read(&mut chunk); let read_length = match read_result { Ok(length) => length, Err(err) => { match err.kind() { ErrorKind::BrokenPipe => { // This fails only if the other side is disconnected. // In that case, this thread should terminate anyway and we can ignore the error. public_sender.send(LoupedeckEvent::Disconnected).ok(); break; } ErrorKind::TimedOut => continue, _ => panic!("{}", err), } } }; chunk.truncate(read_length); buffer.put(chunk); while !should_stop { let start_index = buffer.iter().position(|b| *b == MESSAGE_START_BYTE); if let Some(start_index) = start_index { if start_index > 0 { buffer.advance(start_index); if buffer.remaining() == 0 { break; } } let length = buffer[1] as usize + 2; if length > buffer.remaining() { break; } else { let mut message = buffer.split_to(length); let command = message[3]; // let transaction_id = message[4]; message.advance(5); let result = parse_message(command, message.freeze()); match result { ParseMessageResult::InternalEvent(event) => { // Does nothing after the receiving side has been closed if let Some(sender) = internal_sender.take() { let is_open = sender.send(event).is_ok(); if is_open { internal_sender = Some(sender); } } } ParseMessageResult::PublicEvent(event) => { if public_sender.send(event).is_err() { should_stop = false } } ParseMessageResult::Nothing => {} } } } else { break; } } } } fn parse_message(command: u8, mut message: Bytes) -> ParseMessageResult { match command { 0x00 => match message[1] { 0x00 => match message[0] { 0x01 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::LeftTop }, 0x02 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::LeftMiddle, }, 0x03 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::LeftBottom, }, 0x04 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::RightTop }, 0x05 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::RightMiddle, }, 0x06 => LoupedeckEvent::KnobDown { knob: LoupedeckKnob::RightBottom, }, 0x07 => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N0 }, 0x08 => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N1 }, 0x09 => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N2 }, 0x0a => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N3 }, 0x0b => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N4 }, 0x0c => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N5 }, 0x0d => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N6 }, 0x0e => LoupedeckEvent::ButtonDown { button: LoupedeckButton::N7 }, _ => panic!("Illegal button id: {}", message[1]), }, _ => match message[0] { 0x01 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::LeftTop }, 0x02 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::LeftMiddle, }, 0x03 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::LeftBottom, }, 0x04 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::RightTop }, 0x05 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::RightMiddle, }, 0x06 => LoupedeckEvent::KnobUp { knob: LoupedeckKnob::RightBottom, }, 0x07 => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N0 }, 0x08 => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N1 }, 0x09 => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N2 }, 0x0a => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N3 }, 0x0b => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N4 }, 0x0c => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N5 }, 0x0d => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N6 }, 0x0e => LoupedeckEvent::ButtonUp { button: LoupedeckButton::N7 }, _ => panic!("Illegal button id: {}", message[1]), }, } .into(), 0x01 => { let knob = LoupedeckKnob::from_ordinal(message[0]).expect("Invalid button ID"); LoupedeckEvent::KnobRotate { knob, direction: if message[1] == 1 { Clockwise } else { Counterclockwise }, } .into() } 0x03 => LoupedeckInternalEvent::GetSerialNumberResponse { serial_number: String::from_utf8_lossy(&message).into_owned(), } .into(), 0x07 => LoupedeckInternalEvent::GetFirmwareVersionResponse { firmware_version: format!("{}.{}.{}", message[0], message[1], message[2]), } .into(), 0x4d | 0x6d => { message.advance(1); let x = message.get_u16(); let y = message.get_u16(); let touch_id = message.get_u8(); LoupedeckEvent::Touch { touch_id, x, y, is_end: command == 0x6d, } .into() } _ => ParseMessageResult::Nothing, } } pub(crate) fn write_messages_worker(mut port: Box, receiver: flume::Receiver) { let mut next_transaction_id = 0; let mut send = |command_id: u8, data: Bytes| -> Result<(), io::Error> { if next_transaction_id == 0 { next_transaction_id += 1; } let mut data_with_header = BytesMut::with_capacity(data.len() + 3); data_with_header.put_u8(min(u8::MAX as usize, data.len() + 3) as u8); data_with_header.put_u8(command_id); data_with_header.put_u8(next_transaction_id); data_with_header.put(data); let length = data_with_header.len(); if length > u8::MAX as usize { let mut prep = BytesMut::with_capacity(14); prep.put_u8(0x82); prep.put_u8(0xff); prep.put_bytes(0x00, 4); prep.put_u32(length as u32); prep.put_bytes(0x00, 4); port.write_all(&prep)?; port.flush()?; } else { let mut prep = BytesMut::zeroed(6); prep[0] = 0x82; prep[1] = (0x80 + length) as u8; port.write_all(&prep)?; port.flush()?; } port.write_all(&data_with_header)?; port.flush()?; next_transaction_id = next_transaction_id.wrapping_add(1); Ok(()) }; for command in receiver { let result = match command { LoupedeckCommand::RequestSerialNumber => send(0x03, Bytes::new()), LoupedeckCommand::RequestFirmwareVersion => send(0x07, Bytes::new()), LoupedeckCommand::SetBrightness(value) => { let raw_value = (value.clamp(0f32, 1f32) * 10.0) as u8; send(0x09, Bytes::copy_from_slice(&[raw_value])) } LoupedeckCommand::SetButtonColor { button, color } => send(0x02, Bytes::copy_from_slice(&[button.ordinal(), color.r, color.g, color.b])), LoupedeckCommand::ReplaceFramebufferArea { display_id, x, y, width, height, buffer, } => { let mut data = BytesMut::with_capacity(10 + buffer.len()); data.put_u8(0); data.put_u8(display_id); data.put_u16(x); data.put_u16(y); data.put_u16(width); data.put_u16(height); data.put(buffer); send(0x10, data.freeze()) } LoupedeckCommand::RefreshDisplay { display_id } => send(0x0f, Bytes::copy_from_slice(&[0, display_id])), LoupedeckCommand::Vibrate { pattern } => send(0x1b, Bytes::copy_from_slice(&[pattern.ordinal()])), }; if let Err(error) = result { match error.kind() { ErrorKind::TimedOut | ErrorKind::BrokenPipe => break, _ => { panic!("IO error during write: {}", error); } } } } }