Initial commit

This commit is contained in:
Moritz Ruth 2023-12-28 18:39:46 +01:00
commit 64574bd2fa
Signed by: moritzruth
GPG key ID: C9BBAB79405EE56D
12 changed files with 954 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/.idea

558
Cargo.lock generated Normal file
View file

@ -0,0 +1,558 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color-eyre"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
dependencies = [
"backtrace",
"color-spantrace",
"eyre",
"indenter",
"once_cell",
"owo-colors",
"tracing-error",
]
[[package]]
name = "color-spantrace"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2"
dependencies = [
"once_cell",
"owo-colors",
"tracing-core",
"tracing-error",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "darling"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "deckster"
version = "0.1.0"
dependencies = [
"color-eyre",
"loupedeck_serial",
]
[[package]]
name = "enum-ordinalize"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5"
dependencies = [
"enum-ordinalize-derive",
]
[[package]]
name = "enum-ordinalize-derive"
version = "4.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "enumset"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d"
dependencies = [
"enumset_derive",
]
[[package]]
name = "enumset_derive"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "eyre"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "io-kit-sys"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4769cb30e5dcf1710fc6730d3e94f78c47723a014a567de385e113c737394640"
dependencies = [
"core-foundation-sys",
"mach2",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
name = "libudev"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b324152da65df7bb95acfcaab55e3097ceaab02fb19b228a9eb74d55f135e0"
dependencies = [
"libc",
"libudev-sys",
]
[[package]]
name = "libudev-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "loupedeck_serial"
version = "0.1.0"
dependencies = [
"bytes",
"enum-ordinalize",
"enumset",
"serialport",
"thiserror",
]
[[package]]
name = "mach2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
dependencies = [
"libc",
]
[[package]]
name = "memchr"
version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
]
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pkg-config"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
[[package]]
name = "proc-macro2"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serialport"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5a15d0be940df84846264b09b51b10b931fb2f275becb80934e3568a016828"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
"core-foundation-sys",
"io-kit-sys",
"libudev",
"mach2",
"nix",
"regex",
"scopeguard",
"unescaper",
"winapi",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "syn"
version = "2.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-error"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"sharded-slab",
"thread_local",
"tracing-core",
]
[[package]]
name = "unescaper"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8f0f68e58d297ba8b22b8b5a96a87b863ba6bb46aaf51e19a4b02c5a6dd5b7f"
dependencies = [
"thiserror",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[workspace]
members = [
"deckster",
"loupedeck_serial"
]
resolver = "2"

55
LICENSE.md Normal file
View file

@ -0,0 +1,55 @@
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***

6
README.md Normal file
View file

@ -0,0 +1,6 @@
# Deckster
## Attribution
[foxxyzs `loupedeck` library for JavaScript](https://github.com/foxxyz/loupedeck)
(licensed under the [MIT license](https://github.com/foxxyz/loupedeck/blob/e41e5d920130d9ef651e47173c68450b9c832b96/LICENSE))
was used as a reference for and inspired the design of `loupedeck_serial`.

8
deckster/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "deckster"
version = "0.1.0"
edition = "2021"
[dependencies]
loupedeck_serial = { path = "../loupedeck_serial" }
color-eyre = "0.6.2"

14
deckster/src/main.rs Normal file
View file

@ -0,0 +1,14 @@
use color_eyre::eyre::ContextCompat;
use color_eyre::Result;
fn main() -> Result<()> {
let available_devices = loupedeck_serial::device::LoupedeckDevice::discover()?;
let mut device = available_devices.first()
.wrap_err("at least one device should be connected")?
.open()?;
device.connect()?;
Ok(())
}

View file

@ -0,0 +1,11 @@
[package]
name = "loupedeck_serial"
version = "0.1.0"
edition = "2021"
[dependencies]
serialport = "4.3.0"
enum-ordinalize = "4.3.0"
enumset = "1.1.3"
bytes = "1.5.0"
thiserror = "1.0.52"

View file

@ -0,0 +1,112 @@
use enum_ordinalize::Ordinalize;
use enumset::{enum_set, EnumSet, EnumSetType};
#[derive(Debug, Ordinalize, EnumSetType)]
#[repr(u16)]
pub enum LoupedeckDeviceKnob {
KnobTopLeft = 0x01,
KnobCenterLeft = 0x02,
KnobBottomLeft = 0x03,
KnobTopRight = 0x04,
KnobCenterRight = 0x05,
KnobBottomRight = 0x06,
}
#[derive(Debug, Ordinalize, EnumSetType)]
#[repr(u16)]
pub enum LoupedeckDeviceButton {
KnobTopLeft = 0x01,
KnobCenterLeft = 0x02,
KnobBottomLeft = 0x03,
KnobTopRight = 0x04,
KnobCenterRight = 0x05,
KnobBottomRight = 0x06,
N0 = 0x07,
N1 = 0x08,
N2 = 0x09,
N3 = 0x0a,
N4 = 0x0b,
N5 = 0x0c,
N6 = 0x0d,
N7 = 0x0e,
}
#[derive(Debug)]
pub enum LoupedeckDeviceDisplayEndianness {
BigEndian,
LittleEndian
}
#[derive(Debug)]
pub struct LoupedeckDeviceDisplayConfiguration {
pub id: u8,
pub width: usize,
pub height: usize,
pub offset: (usize, usize),
pub endianness: LoupedeckDeviceDisplayEndianness
}
#[derive(Debug)]
pub struct LoupedeckDeviceCharacteristics {
pub vendor_id: u16,
pub product_id: u16,
pub name: &'static str,
pub available_knobs: EnumSet<LoupedeckDeviceKnob>,
pub available_buttons: EnumSet<LoupedeckDeviceButton>,
pub key_grid_dimensions: (usize, usize),
pub displays: &'static [LoupedeckDeviceDisplayConfiguration]
}
static LOUPEDECK_LIVE_CHARACTERISTIC: LoupedeckDeviceCharacteristics = LoupedeckDeviceCharacteristics {
vendor_id: 0x2ec2,
product_id: 0x0004,
name: "Loupedeck Live",
available_knobs: enum_set!(LoupedeckDeviceKnob::KnobTopLeft
| LoupedeckDeviceKnob::KnobCenterLeft
| LoupedeckDeviceKnob::KnobBottomLeft
| LoupedeckDeviceKnob::KnobTopRight
| LoupedeckDeviceKnob::KnobCenterRight
| LoupedeckDeviceKnob::KnobBottomRight),
available_buttons: enum_set!(LoupedeckDeviceButton::KnobTopLeft
| LoupedeckDeviceButton::KnobCenterLeft
| LoupedeckDeviceButton::KnobBottomLeft
| LoupedeckDeviceButton::KnobTopRight
| LoupedeckDeviceButton::KnobCenterRight
| LoupedeckDeviceButton::KnobBottomRight
| LoupedeckDeviceButton::N0
| LoupedeckDeviceButton::N1
| LoupedeckDeviceButton::N2
| LoupedeckDeviceButton::N3
| LoupedeckDeviceButton::N4
| LoupedeckDeviceButton::N5
| LoupedeckDeviceButton::N6
| LoupedeckDeviceButton::N7),
key_grid_dimensions: (4, 3),
displays: &[
LoupedeckDeviceDisplayConfiguration { // Left
id: 0x4d,
width: 60,
height: 270,
offset: (0, 0),
endianness: LoupedeckDeviceDisplayEndianness::LittleEndian,
},
LoupedeckDeviceDisplayConfiguration { // Center
id: 0x4d,
width: 360,
height: 270,
offset: (60, 0),
endianness: LoupedeckDeviceDisplayEndianness::LittleEndian,
},
LoupedeckDeviceDisplayConfiguration { // Right
id: 0x4d,
width: 60,
height: 270,
offset: (420, 0),
endianness: LoupedeckDeviceDisplayEndianness::LittleEndian,
},
]
};
pub static CHARACTERISTICS: [&LoupedeckDeviceCharacteristics; 1] = [
&LOUPEDECK_LIVE_CHARACTERISTIC
];

View file

@ -0,0 +1,145 @@
use std::io;
use std::io::{BufReader, Read, Write};
use std::io::ErrorKind::TimedOut;
use std::sync::Arc;
use std::sync::mpsc::{Sender, sync_channel, SyncSender};
use std::thread::spawn;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use serialport::{SerialPort, SerialPortType};
use thiserror::Error;
use crate::characteristics::{CHARACTERISTICS, LoupedeckDeviceCharacteristics};
use crate::discovery::AvailableLoupedeckDevice;
const WS_UPGRADE_REQUEST: &str = r#"GET /index.html
HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: 123abc
"#;
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;
#[derive(Debug)]
pub struct LoupedeckDevice {
pub(crate) port: Option<Box<dyn SerialPort>>,
pub characteristics: &'static LoupedeckDeviceCharacteristics,
}
#[derive(Debug, Error)]
pub enum ConnectError {
#[error("IO Error: {0}")]
IO(#[from] io::Error),
#[error("The device did not respond with the expected handshake response.")]
WrongHandshakeResponse,
}
impl LoupedeckDevice {
pub fn new_unchecked(port: Box<dyn SerialPort>, characteristics: &'static LoupedeckDeviceCharacteristics) -> LoupedeckDevice {
LoupedeckDevice {
port: Some(port),
characteristics,
}
}
pub fn discover() -> Result<Vec<AvailableLoupedeckDevice>, serialport::Error> {
let ports = serialport::available_ports()?;
Ok(ports.iter().filter_map(|port| {
if let SerialPortType::UsbPort(info) = &port.port_type {
let characteristics = CHARACTERISTICS.iter()
.find(|c| c.vendor_id == info.vid && c.product_id == info.pid);
if let Some(characteristics) = characteristics {
return Some(AvailableLoupedeckDevice {
port_name: port.port_name.clone(),
characteristics,
});
}
}
None
}).collect::<Vec<_>>())
}
pub fn connect(&mut self) -> Result<(), ConnectError> {
let mut port = self.port.take().expect("already connected");
port.write_all(WS_UPGRADE_REQUEST.as_bytes())?;
port.flush()?;
let mut buf = [0; WS_UPGRADE_RESPONSE_START.len()];
port.read_exact(&mut buf)?;
if buf != WS_UPGRADE_RESPONSE_START.as_bytes() {
return Err(ConnectError::WrongHandshakeResponse);
}
let (sender, receiver) = sync_channel::<Bytes>(8);
let handle = spawn(move || {
LoupedeckDevice::handle_messages(port, sender);
});
for x in receiver {
dbg!(&x[1]);
}
handle.join().unwrap();
Ok(())
}
fn handle_messages(mut port: Box<dyn SerialPort>, sender: SyncSender<Bytes>) {
let mut buffer = BytesMut::new();
loop {
let mut chunk = BytesMut::zeroed(MAX_MESSAGE_LENGTH);
let read_result = port.read(&mut chunk);
if let Err(err) = &read_result {
if err.kind() == TimedOut {
continue
}
}
let len = read_result.unwrap();
if len == 0 {
panic!("read 0 bytes");
}
chunk.truncate(len);
buffer.put(chunk);
loop {
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);
message.advance(2);
sender.send(message.freeze()).unwrap();
}
} else {
break;
}
}
}
}
}

View file

@ -0,0 +1,33 @@
use std::time::Duration;
use serialport::{DataBits, FlowControl, Parity, StopBits};
use crate::characteristics::LoupedeckDeviceCharacteristics;
use crate::device::LoupedeckDevice;
#[derive(Debug)]
pub struct AvailableLoupedeckDevice {
pub(crate) port_name: String,
pub(crate) characteristics: &'static LoupedeckDeviceCharacteristics,
}
impl AvailableLoupedeckDevice {
pub fn port_name(&self) -> &str {
&self.port_name
}
pub fn characteristics(&self) -> &'static LoupedeckDeviceCharacteristics {
self.characteristics
}
pub fn open(&self) -> Result<LoupedeckDevice, serialport::Error> {
Ok(LoupedeckDevice {
port: Some(serialport::new(&self.port_name, 256000)
.data_bits(DataBits::Eight)
.stop_bits(StopBits::One)
.parity(Parity::None)
.flow_control(FlowControl::None)
.timeout(Duration::from_secs(10))
.open()?),
characteristics: self.characteristics,
})
}
}

View file

@ -0,0 +1,3 @@
pub mod characteristics;
pub mod device;
pub mod discovery;