Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
members = [
"twinleaf",
"twinleaf-tools",
"twinleaf-core",
]

resolver = "2"
20 changes: 20 additions & 0 deletions twinleaf-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "twinleaf-core"
version = "1.0.0"
edition = "2021"
license = "MIT"
description = "Core tasks utilizing the Twinleaf I/O protocol for reading data from Twinleaf quantum sensors."
homepage = "https://twinleaf.com"
repository = "https://github.com/twinleaf/twinleaf-rust"
readme = "README.md"

[dependencies]
chrono = "0.4.38"
crossbeam = "0.8.4"
crossterm = {version = "0.29.0"}
getopts = "0.2.21"
serialport = "4.5.1"
serde = {version = "1.0.219", features = ["derive"]}
thiserror = "2.0.12"
toml_edit = {version = "0.22.24", features = ["parse"]}
twinleaf = { version = "1.3.1", path = "../twinleaf" }
3 changes: 3 additions & 0 deletions twinleaf-core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Twinleaf I/O Workflows in Rust

This repository exposes an API for common workflows when using the low-level `twinleaf` library. It also serves as an example of how to interface with the devices in Rust.
Empty file added twinleaf-core/src/error.rs
Empty file.
1 change: 1 addition & 0 deletions twinleaf-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod workflows;
77 changes: 77 additions & 0 deletions twinleaf-core/src/workflows/connect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConnectError {
#[error("Failed to enumerate available serial ports")]
EnumerationFailed(#[from] serialport::Error),

#[error("Could not automatically find a sensor")]
AutoDetectionFailed,

#[error("Found {0} multiple possible sensors; please specify one explicitly")]
MultipleSensorsFound(usize),
}

#[derive(Debug)]
pub enum TwinleafPortInterface {
FTDI,
STM32,
Unknown(u16, u16),
}

#[derive(Debug)]
pub struct SerialDevice {
pub url: String,
pub ifc: TwinleafPortInterface,
}

pub fn enum_devices(all: bool) -> Result<Vec<SerialDevice>, ConnectError> {
let mut ports: Vec<SerialDevice> = Vec::new();

let avail_ports = serialport::available_ports()?;

for p in avail_ports.iter() {
if let serialport::SerialPortType::UsbPort(info) = &p.port_type {
let interface = match (info.vid, info.pid) {
(0x0403, 0x6015) => TwinleafPortInterface::FTDI,
(0x0483, 0x5740) => TwinleafPortInterface::STM32,
(vid, pid) => {
if !all {
continue;
};
TwinleafPortInterface::Unknown(vid, pid)
}
};
#[cfg(target_os = "macos")]
if p.port_name.starts_with("/dev/tty.") && !all {
continue;
}
ports.push(SerialDevice {
url: format!("serial://{}", p.port_name),
ifc: interface,
});
} // else ignore other types for now: bluetooth, pci, unknown
}

Ok(ports)
}

pub fn auto_detect_sensor() -> Result<String, ConnectError> {
let devices = enum_devices(false)?;

let valid_urls: Vec<String> = devices
.into_iter()
.filter_map(|dev| match dev.ifc {
TwinleafPortInterface::STM32 | TwinleafPortInterface::FTDI => Some(dev.url),
_ => None,
})
.collect();

if valid_urls.is_empty() {
Err(ConnectError::AutoDetectionFailed)
} else if valid_urls.len() > 1 {
Err(ConnectError::MultipleSensorsFound(valid_urls.len()))
} else {
Ok(valid_urls[0].clone())
}
}
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions twinleaf-core/src/workflows/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod connect;
pub mod firmware;
pub mod log;
pub mod proxy;
pub mod rpc;
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions twinleaf-tools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ crossbeam = "0.8.4"
crossterm = {version = "0.29.0"}
getopts = "0.2.21"
serialport = "4.5.1"
anyhow = "1.0.98"
toml_edit = {version = "0.22.24", features = ["parse"]}
twinleaf = { version = "1.3.1", path = "../twinleaf" }
twinleaf-core = { version = "1.0.0", path = "../twinleaf-core" }
87 changes: 17 additions & 70 deletions twinleaf-tools/src/bin/tio-proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,7 @@ use std::time::Duration;
use tio::{proto, proxy};
use twinleaf::tio;

// Unfortunately we cannot access USB details via the serialport module, so
// we are stuck guessing based on VID/PID. This returns a vector of possible
// serial ports.

#[derive(Debug)]
enum TwinleafPortInterface {
FTDI,
STM32,
Unknown(u16, u16),
}

struct SerialDevice {
url: String,
ifc: TwinleafPortInterface,
}

fn enum_devices(all: bool) -> Vec<SerialDevice> {
let mut ports: Vec<SerialDevice> = Vec::new();

if let Ok(avail_ports) = serialport::available_ports() {
for p in avail_ports.iter() {
if let serialport::SerialPortType::UsbPort(info) = &p.port_type {
let interface = match (info.vid, info.pid) {
(0x0403, 0x6015) => TwinleafPortInterface::FTDI,
(0x0483, 0x5740) => TwinleafPortInterface::STM32,
(vid, pid) => {
if !all {
continue;
};
TwinleafPortInterface::Unknown(vid, pid)
}
};
#[cfg(target_os = "macos")]
if p.port_name.starts_with("/dev/tty.") && !all {
continue;
}
ports.push(SerialDevice {
url: format!("serial://{}", p.port_name),
ifc: interface,
});
} // else ignore other types for now: bluetooth, pci, unknown
}
}

ports
}
use twinleaf_core::workflows::connect;

macro_rules! log{
($tf:expr, $msg:expr)=>{
Expand Down Expand Up @@ -161,17 +116,22 @@ fn main() -> ExitCode {
if matches.opt_present("enum") {
let mut unknown_devices = vec![];
let mut found_any = false;
for dev in enum_devices(true) {
if let TwinleafPortInterface::Unknown(vid, pid) = dev.ifc {
unknown_devices.push(format!("{} (vid: {} pid:{})", dev.url, vid, pid));
} else {
if !found_any {
println!("Possible tio ports:");
found_any = true;
match connect::enum_devices(true) {
Ok(devices) => {
for dev in devices {
if let connect::TwinleafPortInterface::Unknown(vid, pid) = dev.ifc {
unknown_devices.push(format!("{} (vid: {} pid:{})", dev.url, vid, pid));
} else {
if !found_any {
println!("Possible tio ports:");
found_any = true;
}
println!(" * {}", dev.url);
}
println!(" * {}", dev.url);
}
}
Err(e) => die!("Could not enumerate devices: {}", e),
};
if !found_any {
println!("No likely ports found")
}
Expand Down Expand Up @@ -226,23 +186,10 @@ fn main() -> ExitCode {
let sensor_url = if matches.free.len() == 1 {
matches.free[0].clone()
} else {
let devices = enum_devices(false);
let mut valid_urls = Vec::new();
for dev in devices {
match dev.ifc {
TwinleafPortInterface::STM32 | TwinleafPortInterface::FTDI => {
valid_urls.push(dev.url.clone());
}
_ => {}
}
}
if valid_urls.len() == 0 {
die!("Cannot find any sensor to connect to, specify URL manually")
}
if valid_urls.len() > 1 {
die!("Too many sensors detected, specify URL manually")
match connect::auto_detect_sensor() {
Ok(url) => url,
Err(e) => die!("{}", e),
}
valid_urls[0].clone()
};

if verbose || auto_sensor {
Expand Down