This commit is contained in:
Moritz Ruth 2023-12-29 18:43:32 +01:00
parent 482123638f
commit 8058858bf5
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
3 changed files with 71 additions and 41 deletions

View file

@ -8,7 +8,7 @@ use loupedeck_serial::device::LoupedeckDevice;
use loupedeck_serial::events::LoupedeckEvent;
fn main() -> Result<()> {
let available_devices = loupedeck_serial::device::LoupedeckDevice::discover()?;
let available_devices = LoupedeckDevice::discover()?;
let device = available_devices.first()
.wrap_err("at least one device should be connected")?
@ -30,9 +30,21 @@ fn run_rainbow(device: &LoupedeckDevice) -> Result<()> {
let buttons = device.characteristics().available_buttons.iter().filter(|b| b.supports_color()).collect::<Vec<_>>();
loop {
'outer: loop {
let ms = start.elapsed().as_millis() as u64;
if !device.events_channel().is_empty() {
for event in device.events_channel() {
if let LoupedeckEvent::Disconnected = event {
break 'outer;
}
if device.events_channel().is_empty() {
break;
}
}
}
for (index, button) in buttons.iter().enumerate() {
let t = (ms + (index * 100) as u64) as f32;
@ -45,7 +57,9 @@ fn run_rainbow(device: &LoupedeckDevice) -> Result<()> {
sleep((iteration + 1) * interval - start.elapsed());
iteration += 1;
}
};
Ok(())
}
fn run_vibrations(device: &LoupedeckDevice) -> Result<()> {

View file

@ -1,7 +1,7 @@
use std::io;
use std::{io, thread};
use std::io::{Read, Write};
use std::sync::mpsc;
use std::thread::{sleep, spawn};
use std::thread::{sleep, spawn, Thread};
use std::time::Duration;
use bytes::Bytes;
use rgb::{ComponentSlice, RGB8};
@ -120,7 +120,9 @@ impl LoupedeckDevice {
return Err(SetButtonColorError::ColorNotSupported);
}
self.commands_sender.send(LoupedeckCommand::SetButtonColor { button, color }).unwrap();
// The write worker thread not running means the device was disconnected.
// In that case, the read worker thread sends a LoupedeckEvent::Disconnected.
self.commands_sender.send(LoupedeckCommand::SetButtonColor { button, color }).ok();
Ok(())
}
@ -244,16 +246,18 @@ impl LoupedeckDevice {
port.clear(ClearBuffer::Input)?;
let cloned_port = port.try_clone().expect("port must be cloneable");
let thread_name_base = format!("loupedeck_serial ({})", port.name().unwrap_or("<unnamed>".to_owned()));
let (public_events_sender, public_events_receiver) = crossbeam_channel::unbounded::<LoupedeckEvent>();
let (internal_events_sender, internal_events_receiver) = mpsc::sync_channel(2);
spawn(move || {
thread::Builder::new().name(thread_name_base.to_owned() + " read worker").spawn(move || {
read_messages_worker(port, public_events_sender, internal_events_sender);
});
})?;
let (commands_sender, commands_receiver) = crossbeam_channel::unbounded::<LoupedeckCommand>();
spawn(move || {
thread::Builder::new().name(thread_name_base.to_owned() + " write worker").spawn(move || {
write_messages_worker(cloned_port, commands_receiver);
});
})?;
commands_sender.send(LoupedeckCommand::RequestSerialNumber).unwrap();
let serial_number = match internal_events_receiver.recv_timeout(Duration::from_secs(1)) {

View file

@ -1,6 +1,6 @@
use std::cmp::min;
use std::io::ErrorKind::TimedOut;
use std::io::{Read, Write};
use std::io;
use std::io::{ErrorKind, Read, Write};
use std::sync::mpsc;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use enum_ordinalize::Ordinalize;
@ -51,27 +51,24 @@ pub(crate) fn read_messages_worker(
) {
let mut internal_sender = Some(internal_sender);
let mut should_stop = false;
let mut buffer = BytesMut::new();
loop {
while !should_stop {
let mut chunk = BytesMut::zeroed(MAX_MESSAGE_LENGTH);
let read_result = port.read(&mut chunk);
let read_length = port.read(&mut chunk).unwrap_or(0);
if let Err(err) = &read_result {
if err.kind() == TimedOut {
continue;
}
if read_length == 0 {
// 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;
}
let len = read_result.unwrap();
if len == 0 {
panic!("read 0 bytes");
}
chunk.truncate(len);
chunk.truncate(read_length);
buffer.put(chunk);
loop {
while !should_stop {
let start_index = buffer.iter().position(|b| *b == MESSAGE_START_BYTE);
if let Some(start_index) = start_index {
@ -105,7 +102,9 @@ pub(crate) fn read_messages_worker(
}
}
ParseMessageResult::PublicEvent(event) => {
public_sender.send(event).unwrap()
if public_sender.send(event).is_err() {
should_stop = false
}
}
ParseMessageResult::Nothing => {}
}
@ -169,7 +168,7 @@ fn parse_message(command: u8, mut message: Bytes) -> ParseMessageResult {
pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: crossbeam_channel::Receiver<LoupedeckCommand>) {
let mut next_transaction_id = 0;
let mut send = |command_id: u8, data: Bytes| {
let mut send = |command_id: u8, data: Bytes| -> Result<(), io::Error> {
if next_transaction_id == 0 {
next_transaction_id += 1;
}
@ -189,37 +188,39 @@ pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: cro
prep.put_u32(length as u32);
prep.put_bytes(0x00, 4);
port.write_all(&prep).unwrap();
port.flush().unwrap();
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).unwrap();
port.flush().unwrap();
port.write_all(&prep)?;
port.flush()?;
}
port.write_all(&data_with_header).unwrap();
port.flush().unwrap();
port.write_all(&data_with_header)?;
port.flush()?;
next_transaction_id = next_transaction_id.wrapping_add(1);
Ok(())
};
for command in receiver {
match command {
let result = match command {
LoupedeckCommand::RequestSerialNumber => {
send(0x03, Bytes::new());
send(0x03, Bytes::new())
}
LoupedeckCommand::RequestFirmwareVersion => {
send(0x07, Bytes::new());
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]));
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]));
send(0x02, Bytes::copy_from_slice(&[button.ordinal(), color.r, color.g, color.b]))
}
LoupedeckCommand::ReplaceFramebufferArea {
display_id,
@ -238,13 +239,24 @@ pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: cro
data.put_u16(height);
data.put(buffer);
send(0x10, data.freeze());
send(0x10, data.freeze())
}
LoupedeckCommand::RefreshDisplay { display_id } => {
send(0x0f, Bytes::copy_from_slice(&[0, display_id]));
send(0x0f, Bytes::copy_from_slice(&[0, display_id]))
}
LoupedeckCommand::Vibrate { pattern } => {
send(0x1b, Bytes::copy_from_slice(&[pattern.ordinal()]));
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);
}
}
}
}