Initial commit, copied examples from postcard-rpc repo.

This commit is contained in:
2025-08-04 16:59:17 +02:00
commit d494cd75aa
7 changed files with 1810 additions and 0 deletions

130
src/client.rs Normal file
View File

@@ -0,0 +1,130 @@
use postcard_rpc::{
header::VarSeqKind,
host_client::{HostClient, HostErr},
standard_icd::{PingEndpoint, WireError, ERROR_PATH},
};
use std::convert::Infallible;
use bioz_icd::{
AccelRange, BadPositionError, GetUniqueIdEndpoint, Rgb8, SetAllLedEndpoint,
SetSingleLedEndpoint, SingleLed, StartAccel, StartAccelerationEndpoint,
StopAccelerationEndpoint,
};
#[derive(Debug)]
pub struct WorkbookClient {
pub client: HostClient<WireError>,
}
#[derive(Debug)]
pub enum WorkbookError<E> {
Comms(HostErr<WireError>),
Endpoint(E),
}
impl<E> From<HostErr<WireError>> for WorkbookError<E> {
fn from(value: HostErr<WireError>) -> Self {
Self::Comms(value)
}
}
trait FlattenErr {
type Good;
type Bad;
fn flatten(self) -> Result<Self::Good, WorkbookError<Self::Bad>>;
}
impl<T, E> FlattenErr for Result<T, E> {
type Good = T;
type Bad = E;
fn flatten(self) -> Result<Self::Good, WorkbookError<Self::Bad>> {
self.map_err(WorkbookError::Endpoint)
}
}
// ---
impl WorkbookClient {
pub fn new() -> Self {
let client = HostClient::new_raw_nusb(
|d| d.product_string() == Some("bioz-amplifier"),
ERROR_PATH,
8,
VarSeqKind::Seq2,
);
Self { client }
}
pub fn new_serial(port: &str) -> Self {
let client = HostClient::new_serial_cobs(port, ERROR_PATH, 8, 9600, VarSeqKind::Seq2);
Self { client }
}
pub async fn wait_closed(&self) {
self.client.wait_closed().await;
}
pub async fn ping(&self, id: u32) -> Result<u32, WorkbookError<Infallible>> {
let val = self.client.send_resp::<PingEndpoint>(&id).await?;
Ok(val)
}
pub async fn get_id(&self) -> Result<u64, WorkbookError<Infallible>> {
let id = self.client.send_resp::<GetUniqueIdEndpoint>(&()).await?;
Ok(id)
}
pub async fn set_rgb_single(
&self,
position: u32,
r: u8,
g: u8,
b: u8,
) -> Result<(), WorkbookError<BadPositionError>> {
self.client
.send_resp::<SetSingleLedEndpoint>(&SingleLed {
position,
rgb: Rgb8 { r, g, b },
})
.await?
.flatten()
}
pub async fn set_all_rgb_single(
&self,
r: u8,
g: u8,
b: u8,
) -> Result<(), WorkbookError<Infallible>> {
self.client
.send_resp::<SetAllLedEndpoint>(&[Rgb8 { r, g, b }; 24])
.await?;
Ok(())
}
pub async fn start_accelerometer(
&self,
interval_ms: u32,
range: AccelRange,
) -> Result<(), WorkbookError<Infallible>> {
self.client
.send_resp::<StartAccelerationEndpoint>(&StartAccel { interval_ms, range })
.await?;
Ok(())
}
pub async fn stop_accelerometer(&self) -> Result<bool, WorkbookError<Infallible>> {
let res = self
.client
.send_resp::<StopAccelerationEndpoint>(&())
.await?;
Ok(res)
}
}
impl Default for WorkbookClient {
fn default() -> Self {
Self::new()
}
}

14
src/lib.rs Normal file
View File

@@ -0,0 +1,14 @@
#![deny(missing_debug_implementations)]
pub mod client;
pub use bioz_icd as icd;
pub async fn read_line() -> String {
tokio::task::spawn_blocking(|| {
let mut line = String::new();
std::io::stdin().read_line(&mut line).unwrap();
line
})
.await
.unwrap()
}

157
src/main.rs Normal file
View File

@@ -0,0 +1,157 @@
use std::{
io::{stdout, Write},
time::{Duration, Instant},
};
use bioz_host_rs::{client::WorkbookClient, icd, read_line};
#[tokio::main]
async fn main() {
println!("Connecting to USB device...");
let client = WorkbookClient::new();
for i in 0..10 {
print!("Pinging with {i}... ");
let res = client.ping(i).await.unwrap();
println!("got {res}!");
assert_eq!(res, i);
}
println!("Connected! Pinging 42");
let ping = client.ping(42).await.unwrap();
println!("Got: {ping}.");
let uid = client.get_id().await.unwrap();
println!("ID: {uid:016X}");
println!("Connected! Pinging 42");
let ping = client.ping(42).await.unwrap();
println!("Got: {ping}.");
println!();
// Begin repl...
loop {
print!("> ");
stdout().flush().unwrap();
let line = read_line().await;
let parts: Vec<&str> = line.split_whitespace().collect();
match parts.as_slice() {
["ping"] => {
let ping = client.ping(42).await.unwrap();
println!("Got: {ping}.");
}
["ping", n] => {
let Ok(idx) = n.parse::<u32>() else {
println!("Bad u32: '{n}'");
continue;
};
let ping = client.ping(idx).await.unwrap();
println!("Got: {ping}.");
}
["rgb", pos, r, g, b] => {
let (Ok(pos), Ok(r), Ok(g), Ok(b)) = (pos.parse(), r.parse(), g.parse(), b.parse())
else {
panic!();
};
client.set_rgb_single(pos, r, g, b).await.unwrap();
}
["rgball", r, g, b] => {
let (Ok(r), Ok(g), Ok(b)) = (r.parse(), g.parse(), b.parse()) else {
panic!();
};
client.set_all_rgb_single(r, g, b).await.unwrap();
}
["accel", "listen", ms, range, dur] => {
let Ok(ms) = ms.parse::<u32>() else {
println!("Bad ms: {ms}");
continue;
};
let Ok(dur) = dur.parse::<u32>() else {
println!("Bad dur: {dur}");
continue;
};
let range = match *range {
"2" => icd::AccelRange::G2,
"4" => icd::AccelRange::G4,
"8" => icd::AccelRange::G8,
"16" => icd::AccelRange::G16,
_ => {
println!("Bad range: {range}");
continue;
}
};
let mut sub = client
.client
.subscribe_multi::<icd::AccelTopic>(8)
.await
.unwrap();
client.start_accelerometer(ms, range).await.unwrap();
println!("Started!");
let dur = Duration::from_millis(dur.into());
let start = Instant::now();
while start.elapsed() < dur {
let val = sub.recv().await.unwrap();
println!("acc: {val:?}");
}
client.stop_accelerometer().await.unwrap();
println!("Stopped!");
}
["accel", "start", ms, range] => {
let Ok(ms) = ms.parse::<u32>() else {
println!("Bad ms: {ms}");
continue;
};
let range = match *range {
"2" => icd::AccelRange::G2,
"4" => icd::AccelRange::G4,
"8" => icd::AccelRange::G8,
"16" => icd::AccelRange::G16,
_ => {
println!("Bad range: {range}");
continue;
}
};
client.start_accelerometer(ms, range).await.unwrap();
println!("Started!");
}
["accel", "stop"] => {
let res = client.stop_accelerometer().await.unwrap();
println!("Stopped: {res}");
}
["schema"] => {
let schema = client.client.get_schema_report().await.unwrap();
println!();
println!("# Endpoints");
println!();
for ep in &schema.endpoints {
println!("* '{}'", ep.path);
println!(" * Request: {}", ep.req_ty);
println!(" * Response: {}", ep.resp_ty);
}
println!();
println!("# Topics Client -> Server");
println!();
for tp in &schema.topics_in {
println!("* '{}'", tp.path);
println!(" * Message: {}", tp.ty);
}
println!();
println!("# Topics Client <- Server");
println!();
for tp in &schema.topics_out {
println!("* '{}'", tp.path);
println!(" * Message: {}", tp.ty);
}
println!();
}
other => {
println!("Error, didn't understand '{other:?};");
}
}
}
}