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::Result;
|
||||
use rgb::RGB8;
|
||||
use loupedeck_serial::characteristics::LoupedeckButton;
|
||||
use loupedeck_serial::events::{LoupedeckEvent, RotationDirection};
|
||||
use loupedeck_serial::commands::VibrationPattern;
|
||||
use loupedeck_serial::device::LoupedeckDevice;
|
||||
use loupedeck_serial::events::LoupedeckEvent;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let available_devices = loupedeck_serial::device::LoupedeckDevice::discover()?;
|
||||
|
@ -13,39 +14,23 @@ fn main() -> Result<()> {
|
|||
.wrap_err("at least one device should be connected")?
|
||||
.connect()?;
|
||||
|
||||
println!("Version: {}\nSerial number: {}", device.firmware_version(), device.serial_number());
|
||||
device.set_brightness(1.0);
|
||||
|
||||
let buttons = [
|
||||
LoupedeckButton::N0,
|
||||
LoupedeckButton::N1,
|
||||
LoupedeckButton::N2,
|
||||
LoupedeckButton::N3,
|
||||
LoupedeckButton::N4,
|
||||
LoupedeckButton::N5,
|
||||
LoupedeckButton::N6,
|
||||
LoupedeckButton::N7,
|
||||
];
|
||||
// run_vibrations(&device)?;
|
||||
run_rainbow(&device)?;
|
||||
|
||||
let mut value = 0u8;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_rainbow(device: &LoupedeckDevice) -> Result<()> {
|
||||
let interval = Duration::from_millis(50);
|
||||
let start = Instant::now();
|
||||
let mut iteration = 0;
|
||||
|
||||
let buttons = device.characteristics().available_buttons.iter().filter(|b| b.supports_color()).collect::<Vec<_>>();
|
||||
|
||||
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;
|
||||
|
||||
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 / 500.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(
|
||||
&device.characteristics().key_grid_display,
|
||||
0,
|
||||
0,
|
||||
90,
|
||||
90,
|
||||
&[RGB8::new(value, 0, 0); 90 * 90]
|
||||
)?;
|
||||
|
||||
device.refresh_display(&device.characteristics().key_grid_display)?;
|
||||
|
||||
sleep(Duration::from_millis(50));
|
||||
sleep((iteration + 1) * interval - start.elapsed());
|
||||
iteration += 1;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
#[derive(Debug, Ordinalize, EnumSetType)]
|
||||
#[repr(u16)]
|
||||
#[repr(u8)]
|
||||
pub enum LoupedeckKnob {
|
||||
KnobTopLeft = 0x01,
|
||||
KnobCenterLeft = 0x02,
|
||||
|
@ -14,7 +14,7 @@ pub enum LoupedeckKnob {
|
|||
}
|
||||
|
||||
#[derive(Debug, Ordinalize, EnumSetType)]
|
||||
#[repr(u16)]
|
||||
#[repr(u8)]
|
||||
pub enum LoupedeckButton {
|
||||
KnobTopLeft = 0x01,
|
||||
KnobCenterLeft = 0x02,
|
||||
|
@ -32,6 +32,12 @@ pub enum LoupedeckButton {
|
|||
N7 = 0x0e,
|
||||
}
|
||||
|
||||
impl LoupedeckButton {
|
||||
pub fn supports_color(&self) -> bool {
|
||||
self.ordinal() >= LoupedeckButton::N0.ordinal() && self.ordinal() <= LoupedeckButton::N7.ordinal()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct LoupedeckDeviceDisplayConfiguration {
|
||||
|
|
|
@ -1,9 +1,45 @@
|
|||
use bytes::Bytes;
|
||||
use enum_ordinalize::Ordinalize;
|
||||
use rgb::RGB8;
|
||||
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)]
|
||||
pub(crate) enum LoupedeckCommand {
|
||||
RequestSerialNumber,
|
||||
RequestFirmwareVersion,
|
||||
SetBrightness(f32),
|
||||
SetButtonColor {
|
||||
button: LoupedeckButton,
|
||||
|
@ -19,5 +55,8 @@ pub(crate) enum LoupedeckCommand {
|
|||
},
|
||||
RefreshDisplay {
|
||||
display_id: u8,
|
||||
}
|
||||
},
|
||||
Vibrate {
|
||||
pattern: VibrationPattern
|
||||
},
|
||||
}
|
|
@ -1,16 +1,15 @@
|
|||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::sync::mpsc;
|
||||
use std::thread::{sleep, spawn};
|
||||
use std::time::Duration;
|
||||
use bytes::Bytes;
|
||||
use crossbeam_channel::{Receiver};
|
||||
use rgb::{ComponentSlice, RGB8};
|
||||
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, SerialPortType, StopBits};
|
||||
use thiserror::Error;
|
||||
use crate::characteristics::{CHARACTERISTICS, LoupedeckButton, LoupedeckDeviceCharacteristics, LoupedeckDeviceDisplayConfiguration};
|
||||
use crate::commands::LoupedeckCommand;
|
||||
use crate::events::LoupedeckEvent;
|
||||
use crate::commands::{LoupedeckCommand, VibrationPattern};
|
||||
use crate::events::{LoupedeckEvent, LoupedeckInternalEvent};
|
||||
use crate::messages::{read_messages_worker, write_messages_worker, WS_UPGRADE_REQUEST, WS_UPGRADE_RESPONSE_START};
|
||||
use crate::util::convert_rgb888_to_rgb565;
|
||||
|
||||
|
@ -37,8 +36,10 @@ impl AvailableLoupedeckDevice {
|
|||
#[derive(Debug)]
|
||||
pub struct LoupedeckDevice {
|
||||
pub(crate) characteristics: &'static LoupedeckDeviceCharacteristics,
|
||||
pub(crate) events_channel: Receiver<LoupedeckEvent>,
|
||||
commands_sender: Sender<LoupedeckCommand>,
|
||||
pub(crate) serial_number: String,
|
||||
pub(crate) firmware_version: String,
|
||||
events_receiver: crossbeam_channel::Receiver<LoupedeckEvent>,
|
||||
commands_sender: crossbeam_channel::Sender<LoupedeckCommand>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -49,8 +50,11 @@ pub enum ConnectError {
|
|||
#[error("IO error: {0}")]
|
||||
IO(#[from] io::Error),
|
||||
|
||||
#[error("The device did not respond with the expected handshake response.")]
|
||||
WrongHandshakeResponse,
|
||||
#[error("The device did not respond with the expected handshake response (early-stage).")]
|
||||
WrongEarlyHandshakeResponse,
|
||||
|
||||
#[error("The device did not respond with the expected handshake response (late-stage).")]
|
||||
WrongLateHandshakeResponse,
|
||||
|
||||
#[error("The device was already connected.")]
|
||||
AlreadyConnected,
|
||||
|
@ -62,6 +66,15 @@ pub enum RefreshDisplayError {
|
|||
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)]
|
||||
pub enum ReplaceFramebufferAreaError {
|
||||
#[error("The specified display is not available for this device.")]
|
||||
|
@ -82,16 +95,34 @@ impl LoupedeckDevice {
|
|||
self.characteristics
|
||||
}
|
||||
|
||||
pub fn events_channel(&self) -> Receiver<LoupedeckEvent> {
|
||||
self.events_channel.clone()
|
||||
pub fn serial_number(&self) -> &String {
|
||||
&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) {
|
||||
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();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Replaces the specified framebuffer area of the display with `buffer`.
|
||||
|
@ -163,6 +194,12 @@ impl LoupedeckDevice {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn vibrate(&self, pattern: VibrationPattern) {
|
||||
self.commands_sender.send(LoupedeckCommand::Vibrate {
|
||||
pattern
|
||||
}).unwrap();
|
||||
}
|
||||
|
||||
pub fn discover() -> Result<Vec<AvailableLoupedeckDevice>, serialport::Error> {
|
||||
let ports = serialport::available_ports()?;
|
||||
|
||||
|
@ -199,7 +236,7 @@ impl LoupedeckDevice {
|
|||
port.read_exact(&mut buf)?;
|
||||
|
||||
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.
|
||||
|
@ -207,19 +244,36 @@ impl LoupedeckDevice {
|
|||
port.clear(ClearBuffer::Input)?;
|
||||
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 || {
|
||||
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 || {
|
||||
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 {
|
||||
characteristics,
|
||||
events_channel: events_receiver,
|
||||
serial_number,
|
||||
firmware_version,
|
||||
events_receiver: public_events_receiver,
|
||||
commands_sender,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,8 +6,19 @@ pub enum RotationDirection {
|
|||
Counterclockwise
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum LoupedeckInternalEvent {
|
||||
GetSerialNumberResponse {
|
||||
serial_number: String
|
||||
},
|
||||
GetFirmwareVersionResponse {
|
||||
firmware_version: String
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LoupedeckEvent {
|
||||
Disconnected,
|
||||
ButtonDown {
|
||||
button: LoupedeckButton
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pub mod characteristics;
|
||||
pub mod device;
|
||||
pub mod events;
|
||||
pub mod commands;
|
||||
mod messages;
|
||||
mod commands;
|
||||
mod util;
|
|
@ -1,14 +1,13 @@
|
|||
use std::cmp::min;
|
||||
use std::io::ErrorKind::TimedOut;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::mpsc;
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use crossbeam_channel::Sender;
|
||||
use enum_ordinalize::Ordinalize;
|
||||
use serialport::SerialPort;
|
||||
use crate::characteristics::{LoupedeckButton, LoupedeckKnob};
|
||||
use crate::commands::LoupedeckCommand;
|
||||
use crate::events::LoupedeckEvent;
|
||||
use crate::events::{LoupedeckEvent, LoupedeckInternalEvent};
|
||||
use crate::events::RotationDirection::{Clockwise, Counterclockwise};
|
||||
|
||||
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 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();
|
||||
|
||||
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 command = message[3];
|
||||
let transaction_id = message[4];
|
||||
// let transaction_id = message[4];
|
||||
message.advance(5);
|
||||
|
||||
let event = parse_message(command, message.freeze());
|
||||
if let Some(event) = event {
|
||||
sender.send(event).unwrap();
|
||||
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) => {
|
||||
public_sender.send(event).unwrap()
|
||||
}
|
||||
ParseMessageResult::Nothing => {}
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
0x00 => { // Button
|
||||
let button = LoupedeckButton::from_ordinal(message[0] as u16)
|
||||
let button = LoupedeckButton::from_ordinal(message[0])
|
||||
.expect("Invalid button ID");
|
||||
|
||||
Some(match message[1] {
|
||||
match message[1] {
|
||||
0x00 => LoupedeckEvent::ButtonDown { button },
|
||||
_ => LoupedeckEvent::ButtonUp { button },
|
||||
})
|
||||
}.into()
|
||||
}
|
||||
0x01 => { // Knob
|
||||
let knob = LoupedeckKnob::from_ordinal(message[0] as u16)
|
||||
let knob = LoupedeckKnob::from_ordinal(message[0])
|
||||
.expect("Invalid button ID");
|
||||
|
||||
Some(LoupedeckEvent::KnobRotate {
|
||||
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 => { // Touch
|
||||
message.advance(1);
|
||||
|
@ -108,21 +153,20 @@ fn parse_message(command: u8, mut message: Bytes) -> Option<LoupedeckEvent> {
|
|||
let y = message.get_u16();
|
||||
let touch_id = message.get_u8();
|
||||
|
||||
Some(LoupedeckEvent::Touch {
|
||||
LoupedeckEvent::Touch {
|
||||
touch_id,
|
||||
x,
|
||||
y,
|
||||
is_end: command == 0x6d,
|
||||
})
|
||||
}.into()
|
||||
}
|
||||
_ => {
|
||||
// println!("Unknown command: {}", command);
|
||||
None
|
||||
ParseMessageResult::Nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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 {
|
||||
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() as u8, color.r, color.g, color.b]));
|
||||
send(0x02, Bytes::copy_from_slice(&[button.ordinal(), color.r, color.g, color.b]));
|
||||
}
|
||||
LoupedeckCommand::ReplaceFramebufferArea {
|
||||
display_id,
|
||||
|
@ -193,6 +243,9 @@ pub(crate) fn write_messages_worker(mut port: Box<dyn SerialPort>, receiver: Rec
|
|||
LoupedeckCommand::RefreshDisplay { 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