commit
This commit is contained in:
parent
dbb492a72d
commit
482123638f
7 changed files with 229 additions and 83 deletions
|
@ -3,8 +3,9 @@ use std::time::{Duration, Instant};
|
||||||
use color_eyre::eyre::ContextCompat;
|
use color_eyre::eyre::ContextCompat;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use loupedeck_serial::characteristics::LoupedeckButton;
|
use loupedeck_serial::commands::VibrationPattern;
|
||||||
use loupedeck_serial::events::{LoupedeckEvent, RotationDirection};
|
use loupedeck_serial::device::LoupedeckDevice;
|
||||||
|
use loupedeck_serial::events::LoupedeckEvent;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let available_devices = loupedeck_serial::device::LoupedeckDevice::discover()?;
|
let available_devices = loupedeck_serial::device::LoupedeckDevice::discover()?;
|
||||||
|
@ -13,39 +14,23 @@ fn main() -> Result<()> {
|
||||||
.wrap_err("at least one device should be connected")?
|
.wrap_err("at least one device should be connected")?
|
||||||
.connect()?;
|
.connect()?;
|
||||||
|
|
||||||
|
println!("Version: {}\nSerial number: {}", device.firmware_version(), device.serial_number());
|
||||||
device.set_brightness(1.0);
|
device.set_brightness(1.0);
|
||||||
|
|
||||||
let buttons = [
|
// run_vibrations(&device)?;
|
||||||
LoupedeckButton::N0,
|
run_rainbow(&device)?;
|
||||||
LoupedeckButton::N1,
|
|
||||||
LoupedeckButton::N2,
|
|
||||||
LoupedeckButton::N3,
|
|
||||||
LoupedeckButton::N4,
|
|
||||||
LoupedeckButton::N5,
|
|
||||||
LoupedeckButton::N6,
|
|
||||||
LoupedeckButton::N7,
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut value = 0u8;
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_rainbow(device: &LoupedeckDevice) -> Result<()> {
|
||||||
|
let interval = Duration::from_millis(50);
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
let mut iteration = 0;
|
||||||
|
|
||||||
|
let buttons = device.characteristics().available_buttons.iter().filter(|b| b.supports_color()).collect::<Vec<_>>();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
while !device.events_channel().is_empty() {
|
|
||||||
let event = device.events_channel().recv().unwrap();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
LoupedeckEvent::KnobRotate { direction: RotationDirection::Clockwise, .. } => {
|
|
||||||
value = value.wrapping_add(1);
|
|
||||||
println!("{}, {:#010b}, {:#018b}", value, value, ((value as u16) & 0b11111000) << 8);
|
|
||||||
}
|
|
||||||
LoupedeckEvent::KnobRotate { direction: RotationDirection::Counterclockwise, .. } => {
|
|
||||||
value = value.wrapping_sub(1);
|
|
||||||
println!("{}, {:#010b}, {:#018b}", value, value, ((value as u16) & 0b11111000) << 8);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let ms = start.elapsed().as_millis() as u64;
|
let ms = start.elapsed().as_millis() as u64;
|
||||||
|
|
||||||
for (index, button) in buttons.iter().enumerate() {
|
for (index, button) in buttons.iter().enumerate() {
|
||||||
|
@ -55,20 +40,18 @@ fn main() -> Result<()> {
|
||||||
(((t / 1000.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
(((t / 1000.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||||
(((t / 500.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
(((t / 500.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||||
(((t / 250.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
(((t / 250.0).sin() / 2.0 + 0.5) * 255.0) as u8,
|
||||||
));
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
device.replace_framebuffer_area(
|
sleep((iteration + 1) * interval - start.elapsed());
|
||||||
&device.characteristics().key_grid_display,
|
iteration += 1;
|
||||||
0,
|
|
||||||
0,
|
|
||||||
90,
|
|
||||||
90,
|
|
||||||
&[RGB8::new(value, 0, 0); 90 * 90]
|
|
||||||
)?;
|
|
||||||
|
|
||||||
device.refresh_display(&device.characteristics().key_grid_display)?;
|
|
||||||
|
|
||||||
sleep(Duration::from_millis(50));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_vibrations(device: &LoupedeckDevice) -> Result<()> {
|
||||||
|
for event in device.events_channel() {
|
||||||
|
if let LoupedeckEvent::Touch { is_end: false, .. } = event { device.vibrate(VibrationPattern::Low) }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use enumset::{enum_set, EnumSet, EnumSetType};
|
||||||
use crate::util::Endianness;
|
use crate::util::Endianness;
|
||||||
|
|
||||||
#[derive(Debug, Ordinalize, EnumSetType)]
|
#[derive(Debug, Ordinalize, EnumSetType)]
|
||||||
#[repr(u16)]
|
#[repr(u8)]
|
||||||
pub enum LoupedeckKnob {
|
pub enum LoupedeckKnob {
|
||||||
KnobTopLeft = 0x01,
|
KnobTopLeft = 0x01,
|
||||||
KnobCenterLeft = 0x02,
|
KnobCenterLeft = 0x02,
|
||||||
|
@ -14,7 +14,7 @@ pub enum LoupedeckKnob {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Ordinalize, EnumSetType)]
|
#[derive(Debug, Ordinalize, EnumSetType)]
|
||||||
#[repr(u16)]
|
#[repr(u8)]
|
||||||
pub enum LoupedeckButton {
|
pub enum LoupedeckButton {
|
||||||
KnobTopLeft = 0x01,
|
KnobTopLeft = 0x01,
|
||||||
KnobCenterLeft = 0x02,
|
KnobCenterLeft = 0x02,
|
||||||
|
@ -32,6 +32,12 @@ pub enum LoupedeckButton {
|
||||||
N7 = 0x0e,
|
N7 = 0x0e,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LoupedeckButton {
|
||||||
|
pub fn supports_color(&self) -> bool {
|
||||||
|
self.ordinal() >= LoupedeckButton::N0.ordinal() && self.ordinal() <= LoupedeckButton::N7.ordinal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct LoupedeckDeviceDisplayConfiguration {
|
pub struct LoupedeckDeviceDisplayConfiguration {
|
||||||
|
|
|
@ -1,9 +1,45 @@
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use enum_ordinalize::Ordinalize;
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
use crate::characteristics::LoupedeckButton;
|
use crate::characteristics::LoupedeckButton;
|
||||||
|
|
||||||
|
#[derive(Debug, Ordinalize)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum VibrationPattern {
|
||||||
|
Short = 0x01,
|
||||||
|
Medium = 0x0a,
|
||||||
|
Long = 0x0f,
|
||||||
|
Low = 0x31,
|
||||||
|
ShortLow = 0x32,
|
||||||
|
ShortLower = 0x33,
|
||||||
|
Lower = 0x40,
|
||||||
|
Lowest = 0x41,
|
||||||
|
DescendSlow = 0x46,
|
||||||
|
DescendMed = 0x47,
|
||||||
|
DescendFast = 0x48,
|
||||||
|
AscendSlow = 0x52,
|
||||||
|
AscendMed = 0x53,
|
||||||
|
AscendFast = 0x58,
|
||||||
|
RevSlowest = 0x5e,
|
||||||
|
RevSlow = 0x5f,
|
||||||
|
RevMed = 0x60,
|
||||||
|
RevFast = 0x61,
|
||||||
|
RevFaster = 0x62,
|
||||||
|
RevFastest = 0x63,
|
||||||
|
RiseFall = 0x6a,
|
||||||
|
Buzz = 0x70,
|
||||||
|
Rumble5 = 0x77,
|
||||||
|
Rumble4 = 0x78,
|
||||||
|
Rumble3 = 0x79,
|
||||||
|
Rumble2 = 0x7a,
|
||||||
|
Rumble1 = 0x7b,
|
||||||
|
VeryLong = 0x76,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum LoupedeckCommand {
|
pub(crate) enum LoupedeckCommand {
|
||||||
|
RequestSerialNumber,
|
||||||
|
RequestFirmwareVersion,
|
||||||
SetBrightness(f32),
|
SetBrightness(f32),
|
||||||
SetButtonColor {
|
SetButtonColor {
|
||||||
button: LoupedeckButton,
|
button: LoupedeckButton,
|
||||||
|
@ -19,5 +55,8 @@ pub(crate) enum LoupedeckCommand {
|
||||||
},
|
},
|
||||||
RefreshDisplay {
|
RefreshDisplay {
|
||||||
display_id: u8,
|
display_id: u8,
|
||||||
}
|
},
|
||||||
|
Vibrate {
|
||||||
|
pattern: VibrationPattern
|
||||||
|
},
|
||||||
}
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::sync::mpsc::{channel, Sender};
|
use std::sync::mpsc;
|
||||||
use std::thread::{sleep, spawn};
|
use std::thread::{sleep, spawn};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use crossbeam_channel::{Receiver};
|
|
||||||
use rgb::{ComponentSlice, RGB8};
|
use rgb::{ComponentSlice, RGB8};
|
||||||
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPortType, StopBits};
|
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPortType, StopBits};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use crate::characteristics::{CHARACTERISTICS, LoupedeckButton, LoupedeckDeviceCharacteristics, LoupedeckDeviceDisplayConfiguration};
|
use crate::characteristics::{CHARACTERISTICS, LoupedeckButton, LoupedeckDeviceCharacteristics, LoupedeckDeviceDisplayConfiguration};
|
||||||
use crate::commands::LoupedeckCommand;
|
use crate::commands::{LoupedeckCommand, VibrationPattern};
|
||||||
use crate::events::LoupedeckEvent;
|
use crate::events::{LoupedeckEvent, LoupedeckInternalEvent};
|
||||||
use crate::messages::{read_messages_worker, write_messages_worker, WS_UPGRADE_REQUEST, WS_UPGRADE_RESPONSE_START};
|
use crate::messages::{read_messages_worker, write_messages_worker, WS_UPGRADE_REQUEST, WS_UPGRADE_RESPONSE_START};
|
||||||
use crate::util::convert_rgb888_to_rgb565;
|
use crate::util::convert_rgb888_to_rgb565;
|
||||||
|
|
||||||
|
@ -37,8 +36,10 @@ impl AvailableLoupedeckDevice {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LoupedeckDevice {
|
pub struct LoupedeckDevice {
|
||||||
pub(crate) characteristics: &'static LoupedeckDeviceCharacteristics,
|
pub(crate) characteristics: &'static LoupedeckDeviceCharacteristics,
|
||||||
pub(crate) events_channel: Receiver<LoupedeckEvent>,
|
pub(crate) serial_number: String,
|
||||||
commands_sender: Sender<LoupedeckCommand>,
|
pub(crate) firmware_version: String,
|
||||||
|
events_receiver: crossbeam_channel::Receiver<LoupedeckEvent>,
|
||||||
|
commands_sender: crossbeam_channel::Sender<LoupedeckCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -49,8 +50,11 @@ pub enum ConnectError {
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
IO(#[from] io::Error),
|
IO(#[from] io::Error),
|
||||||
|
|
||||||
#[error("The device did not respond with the expected handshake response.")]
|
#[error("The device did not respond with the expected handshake response (early-stage).")]
|
||||||
WrongHandshakeResponse,
|
WrongEarlyHandshakeResponse,
|
||||||
|
|
||||||
|
#[error("The device did not respond with the expected handshake response (late-stage).")]
|
||||||
|
WrongLateHandshakeResponse,
|
||||||
|
|
||||||
#[error("The device was already connected.")]
|
#[error("The device was already connected.")]
|
||||||
AlreadyConnected,
|
AlreadyConnected,
|
||||||
|
@ -62,6 +66,15 @@ pub enum RefreshDisplayError {
|
||||||
UnknownDisplay,
|
UnknownDisplay,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum SetButtonColorError {
|
||||||
|
#[error("The specified button is not available for this device.")]
|
||||||
|
UnknownButton,
|
||||||
|
|
||||||
|
#[error("The button does not allow setting a color.")]
|
||||||
|
ColorNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ReplaceFramebufferAreaError {
|
pub enum ReplaceFramebufferAreaError {
|
||||||
#[error("The specified display is not available for this device.")]
|
#[error("The specified display is not available for this device.")]
|
||||||
|
@ -82,16 +95,34 @@ impl LoupedeckDevice {
|
||||||
self.characteristics
|
self.characteristics
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn events_channel(&self) -> Receiver<LoupedeckEvent> {
|
pub fn serial_number(&self) -> &String {
|
||||||
self.events_channel.clone()
|
&self.serial_number
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn firmware_version(&self) -> &String {
|
||||||
|
&self.firmware_version
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn events_channel(&self) -> crossbeam_channel::Receiver<LoupedeckEvent> {
|
||||||
|
self.events_receiver.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_brightness(&self, value: f32) {
|
pub fn set_brightness(&self, value: f32) {
|
||||||
self.commands_sender.send(LoupedeckCommand::SetBrightness(value)).unwrap();
|
self.commands_sender.send(LoupedeckCommand::SetBrightness(value)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_button_color(&self, button: LoupedeckButton, color: RGB8) {
|
pub fn set_button_color(&self, button: LoupedeckButton, color: RGB8) -> Result<(), SetButtonColorError> {
|
||||||
|
if !self.characteristics.available_buttons.contains(button) {
|
||||||
|
return Err(SetButtonColorError::UnknownButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !button.supports_color() {
|
||||||
|
return Err(SetButtonColorError::ColorNotSupported);
|
||||||
|
}
|
||||||
|
|
||||||
self.commands_sender.send(LoupedeckCommand::SetButtonColor { button, color }).unwrap();
|
self.commands_sender.send(LoupedeckCommand::SetButtonColor { button, color }).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces the specified framebuffer area of the display with `buffer`.
|
/// Replaces the specified framebuffer area of the display with `buffer`.
|
||||||
|
@ -163,6 +194,12 @@ impl LoupedeckDevice {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn vibrate(&self, pattern: VibrationPattern) {
|
||||||
|
self.commands_sender.send(LoupedeckCommand::Vibrate {
|
||||||
|
pattern
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn discover() -> Result<Vec<AvailableLoupedeckDevice>, serialport::Error> {
|
pub fn discover() -> Result<Vec<AvailableLoupedeckDevice>, serialport::Error> {
|
||||||
let ports = serialport::available_ports()?;
|
let ports = serialport::available_ports()?;
|
||||||
|
|
||||||
|
@ -199,7 +236,7 @@ impl LoupedeckDevice {
|
||||||
port.read_exact(&mut buf)?;
|
port.read_exact(&mut buf)?;
|
||||||
|
|
||||||
if buf != WS_UPGRADE_RESPONSE_START.as_bytes() {
|
if buf != WS_UPGRADE_RESPONSE_START.as_bytes() {
|
||||||
return Err(ConnectError::WrongHandshakeResponse);
|
return Err(ConnectError::WrongEarlyHandshakeResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// I don’t know why. There is garbage in the buffer without this.
|
// I don’t know why. There is garbage in the buffer without this.
|
||||||
|
@ -207,19 +244,36 @@ impl LoupedeckDevice {
|
||||||
port.clear(ClearBuffer::Input)?;
|
port.clear(ClearBuffer::Input)?;
|
||||||
let cloned_port = port.try_clone().expect("port must be cloneable");
|
let cloned_port = port.try_clone().expect("port must be cloneable");
|
||||||
|
|
||||||
let (events_sender, events_receiver) = crossbeam_channel::unbounded::<LoupedeckEvent>();
|
let (public_events_sender, public_events_receiver) = crossbeam_channel::unbounded::<LoupedeckEvent>();
|
||||||
|
let (internal_events_sender, internal_events_receiver) = mpsc::sync_channel(2);
|
||||||
spawn(move || {
|
spawn(move || {
|
||||||
read_messages_worker(port, events_sender);
|
read_messages_worker(port, public_events_sender, internal_events_sender);
|
||||||
});
|
});
|
||||||
|
|
||||||
let (commands_sender, commands_receiver) = channel();
|
let (commands_sender, commands_receiver) = crossbeam_channel::unbounded::<LoupedeckCommand>();
|
||||||
spawn(move || {
|
spawn(move || {
|
||||||
write_messages_worker(cloned_port, commands_receiver);
|
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)) {
|
||||||
|
Ok(LoupedeckInternalEvent::GetSerialNumberResponse { serial_number }) => Ok(serial_number),
|
||||||
|
_ => Err(ConnectError::WrongLateHandshakeResponse)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
commands_sender.send(LoupedeckCommand::RequestFirmwareVersion).unwrap();
|
||||||
|
let firmware_version = match internal_events_receiver.recv_timeout(Duration::from_secs(1)) {
|
||||||
|
Ok(LoupedeckInternalEvent::GetFirmwareVersionResponse { firmware_version }) => Ok(firmware_version),
|
||||||
|
_ => Err(ConnectError::WrongLateHandshakeResponse)
|
||||||
|
}?;
|
||||||
|
|
||||||
|
drop(internal_events_receiver);
|
||||||
|
|
||||||
Ok(LoupedeckDevice {
|
Ok(LoupedeckDevice {
|
||||||
characteristics,
|
characteristics,
|
||||||
events_channel: events_receiver,
|
serial_number,
|
||||||
|
firmware_version,
|
||||||
|
events_receiver: public_events_receiver,
|
||||||
commands_sender,
|
commands_sender,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,19 @@ pub enum RotationDirection {
|
||||||
Counterclockwise
|
Counterclockwise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum LoupedeckInternalEvent {
|
||||||
|
GetSerialNumberResponse {
|
||||||
|
serial_number: String
|
||||||
|
},
|
||||||
|
GetFirmwareVersionResponse {
|
||||||
|
firmware_version: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoupedeckEvent {
|
pub enum LoupedeckEvent {
|
||||||
|
Disconnected,
|
||||||
ButtonDown {
|
ButtonDown {
|
||||||
button: LoupedeckButton
|
button: LoupedeckButton
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub mod characteristics;
|
pub mod characteristics;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
pub mod commands;
|
||||||
mod messages;
|
mod messages;
|
||||||
mod commands;
|
|
||||||
mod util;
|
mod util;
|
|
@ -1,14 +1,13 @@
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::io::ErrorKind::TimedOut;
|
use std::io::ErrorKind::TimedOut;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc;
|
||||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
use crossbeam_channel::Sender;
|
|
||||||
use enum_ordinalize::Ordinalize;
|
use enum_ordinalize::Ordinalize;
|
||||||
use serialport::SerialPort;
|
use serialport::SerialPort;
|
||||||
use crate::characteristics::{LoupedeckButton, LoupedeckKnob};
|
use crate::characteristics::{LoupedeckButton, LoupedeckKnob};
|
||||||
use crate::commands::LoupedeckCommand;
|
use crate::commands::LoupedeckCommand;
|
||||||
use crate::events::LoupedeckEvent;
|
use crate::events::{LoupedeckEvent, LoupedeckInternalEvent};
|
||||||
use crate::events::RotationDirection::{Clockwise, Counterclockwise};
|
use crate::events::RotationDirection::{Clockwise, Counterclockwise};
|
||||||
|
|
||||||
pub(crate) const WS_UPGRADE_REQUEST: &str = r#"GET /index.html
|
pub(crate) const WS_UPGRADE_REQUEST: &str = r#"GET /index.html
|
||||||
|
@ -27,7 +26,31 @@ Sec-WebSocket-Accept: ALtlZo9FMEUEQleXJmq++ukUQ1s=";
|
||||||
const MESSAGE_START_BYTE: u8 = 0x82;
|
const MESSAGE_START_BYTE: u8 = 0x82;
|
||||||
const MAX_MESSAGE_LENGTH: usize = u8::MAX as usize;
|
const MAX_MESSAGE_LENGTH: usize = u8::MAX as usize;
|
||||||
|
|
||||||
pub(crate) fn read_messages_worker(mut port: Box<dyn SerialPort>, sender: Sender<LoupedeckEvent>) {
|
enum ParseMessageResult {
|
||||||
|
InternalEvent(LoupedeckInternalEvent),
|
||||||
|
PublicEvent(LoupedeckEvent),
|
||||||
|
Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LoupedeckInternalEvent> for ParseMessageResult {
|
||||||
|
fn from(value: LoupedeckInternalEvent) -> Self {
|
||||||
|
ParseMessageResult::InternalEvent(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LoupedeckEvent> for ParseMessageResult {
|
||||||
|
fn from(value: LoupedeckEvent) -> Self {
|
||||||
|
ParseMessageResult::PublicEvent(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn read_messages_worker(
|
||||||
|
mut port: Box<dyn SerialPort>,
|
||||||
|
public_sender: crossbeam_channel::Sender<LoupedeckEvent>,
|
||||||
|
internal_sender: mpsc::SyncSender<LoupedeckInternalEvent>,
|
||||||
|
) {
|
||||||
|
let mut internal_sender = Some(internal_sender);
|
||||||
|
|
||||||
let mut buffer = BytesMut::new();
|
let mut buffer = BytesMut::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -67,12 +90,24 @@ pub(crate) fn read_messages_worker(mut port: Box<dyn SerialPort>, sender: Sender
|
||||||
let mut message = buffer.split_to(length);
|
let mut message = buffer.split_to(length);
|
||||||
|
|
||||||
let command = message[3];
|
let command = message[3];
|
||||||
let transaction_id = message[4];
|
// let transaction_id = message[4];
|
||||||
message.advance(5);
|
message.advance(5);
|
||||||
|
|
||||||
let event = parse_message(command, message.freeze());
|
let result = parse_message(command, message.freeze());
|
||||||
if let Some(event) = event {
|
match result {
|
||||||
sender.send(event).unwrap();
|
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) => {
|
||||||
|
public_sender.send(event).unwrap()
|
||||||
|
}
|
||||||
|
ParseMessageResult::Nothing => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,25 +117,35 @@ pub(crate) fn read_messages_worker(mut port: Box<dyn SerialPort>, sender: Sender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_message(command: u8, mut message: Bytes) -> Option<LoupedeckEvent> {
|
fn parse_message(command: u8, mut message: Bytes) -> ParseMessageResult {
|
||||||
match command {
|
match command {
|
||||||
0x00 => { // Button
|
0x00 => { // Button
|
||||||
let button = LoupedeckButton::from_ordinal(message[0] as u16)
|
let button = LoupedeckButton::from_ordinal(message[0])
|
||||||
.expect("Invalid button ID");
|
.expect("Invalid button ID");
|
||||||
|
|
||||||
Some(match message[1] {
|
match message[1] {
|
||||||
0x00 => LoupedeckEvent::ButtonDown { button },
|
0x00 => LoupedeckEvent::ButtonDown { button },
|
||||||
_ => LoupedeckEvent::ButtonUp { button },
|
_ => LoupedeckEvent::ButtonUp { button },
|
||||||
})
|
}.into()
|
||||||
}
|
}
|
||||||
0x01 => { // Knob
|
0x01 => { // Knob
|
||||||
let knob = LoupedeckKnob::from_ordinal(message[0] as u16)
|
let knob = LoupedeckKnob::from_ordinal(message[0])
|
||||||
.expect("Invalid button ID");
|
.expect("Invalid button ID");
|
||||||
|
|
||||||
Some(LoupedeckEvent::KnobRotate {
|
LoupedeckEvent::KnobRotate {
|
||||||
knob,
|
knob,
|
||||||
direction: if message[1] == 1 { Clockwise } else { Counterclockwise },
|
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 => { // Touch
|
0x4d | 0x6d => { // Touch
|
||||||
message.advance(1);
|
message.advance(1);
|
||||||
|
@ -108,21 +153,20 @@ fn parse_message(command: u8, mut message: Bytes) -> Option<LoupedeckEvent> {
|
||||||
let y = message.get_u16();
|
let y = message.get_u16();
|
||||||
let touch_id = message.get_u8();
|
let touch_id = message.get_u8();
|
||||||
|
|
||||||
Some(LoupedeckEvent::Touch {
|
LoupedeckEvent::Touch {
|
||||||
touch_id,
|
touch_id,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
is_end: command == 0x6d,
|
is_end: command == 0x6d,
|
||||||
})
|
}.into()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// println!("Unknown command: {}", command);
|
ParseMessageResult::Nothing
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: Receiver<LoupedeckCommand>) {
|
pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: crossbeam_channel::Receiver<LoupedeckCommand>) {
|
||||||
let mut next_transaction_id = 0;
|
let mut next_transaction_id = 0;
|
||||||
|
|
||||||
let mut send = |command_id: u8, data: Bytes| {
|
let mut send = |command_id: u8, data: Bytes| {
|
||||||
|
@ -164,12 +208,18 @@ pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: Rec
|
||||||
|
|
||||||
for command in receiver {
|
for command in receiver {
|
||||||
match command {
|
match command {
|
||||||
|
LoupedeckCommand::RequestSerialNumber => {
|
||||||
|
send(0x03, Bytes::new());
|
||||||
|
}
|
||||||
|
LoupedeckCommand::RequestFirmwareVersion => {
|
||||||
|
send(0x07, Bytes::new());
|
||||||
|
}
|
||||||
LoupedeckCommand::SetBrightness(value) => {
|
LoupedeckCommand::SetBrightness(value) => {
|
||||||
let raw_value = (value.clamp(0f32, 1f32) * 10.0) as u8;
|
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 } => {
|
LoupedeckCommand::SetButtonColor { button, color } => {
|
||||||
send(0x02, Bytes::copy_from_slice(&[button.ordinal() as u8, color.r, color.g, color.b]));
|
send(0x02, Bytes::copy_from_slice(&[button.ordinal(), color.r, color.g, color.b]));
|
||||||
}
|
}
|
||||||
LoupedeckCommand::ReplaceFramebufferArea {
|
LoupedeckCommand::ReplaceFramebufferArea {
|
||||||
display_id,
|
display_id,
|
||||||
|
@ -193,6 +243,9 @@ pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: Rec
|
||||||
LoupedeckCommand::RefreshDisplay { display_id } => {
|
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()]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue