#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
use measurement::{MeasurementAccumulator, MeasurementIterExt, MeasurementMatch};
use serialport::{ClearBuffer::Input, FlowControl, SerialPort};
use std::str::Utf8Error;
use std::sync::mpsc::{self, Receiver, SendError, TryRecvError};
use std::{
borrow::Cow,
collections::VecDeque,
io,
sync::{Arc, Condvar, Mutex},
thread,
time::Duration,
};
use thiserror::Error;
use types::{DevicePower, LogicPortPins, MeasurementMode, Metadata, SourceVoltage};
use crate::cmd::Command;
pub mod cmd;
pub mod measurement;
pub mod types;
const SPS_MAX: usize = 100_000;
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum Error {
#[error("Serial port error: {0}")]
SerialPort(#[from] serialport::Error),
#[error("PPK2 not found. Is the device connected and are permissions set correctly?")]
Ppk2NotFound,
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Utf8 error {0}")]
Utf8(#[from] Utf8Error),
#[error("Parse error in \"{0}\"")]
Parse(String),
#[error("Error sending measurement: {0}")]
SendMeasurement(#[from] SendError<MeasurementMatch>),
#[error("Error sending stop signal: {0}")]
SendStopSignal(#[from] SendError<()>),
#[error("Worker thread signal error: {0}")]
WorkerSignalError(#[from] TryRecvError),
#[error("Error deserializeing a measurement: {0:?}")]
DeserializeMeasurement(Vec<u8>),
}
#[allow(missing_docs)]
pub type Result<T> = std::result::Result<T, Error>;
pub struct Ppk2 {
port: Box<dyn SerialPort>,
metadata: Metadata,
}
impl Ppk2 {
pub fn new<'a>(path: impl Into<Cow<'a, str>>, mode: MeasurementMode) -> Result<Self> {
let mut port = serialport::new(path, 9600)
.timeout(Duration::from_millis(500))
.flow_control(FlowControl::Hardware)
.open()?;
if let Err(e) = port.clear(serialport::ClearBuffer::All) {
tracing::warn!("failed to clear buffers: {:?}", e);
}
if let Err(e) = port.write_data_terminal_ready(true) {
tracing::warn!("failed to set DTR: {:?}", e);
}
let mut ppk2 = Self {
port,
metadata: Metadata::default(),
};
ppk2.metadata = ppk2.get_metadata()?;
ppk2.set_power_mode(mode)?;
Ok(ppk2)
}
pub fn send_command(&mut self, command: Command) -> Result<Vec<u8>> {
self.port.write_all(&Vec::from_iter(command.bytes()))?;
let mut response = Vec::with_capacity(command.expected_response_len());
let mut buf = [0u8; 128];
while !command.response_complete(&response) {
let n = self.port.read(&mut buf)?;
response.extend_from_slice(&buf[..n]);
}
Ok(response)
}
fn try_get_metadata(&mut self) -> Result<Metadata> {
let response = self.send_command(Command::GetMetaData)?;
Metadata::from_bytes(&response)
}
pub fn get_metadata(&mut self) -> Result<Metadata> {
let mut result: Result<Metadata> = Err(Error::Parse("Metadata".to_string()));
for _ in 0..3 {
match self.try_get_metadata() {
Ok(metadata) => {
result = Ok(metadata);
break;
}
Err(e) => {
tracing::warn!("Error fetching metadata: {:?}. Retrying..", e);
}
}
}
result
}
pub fn set_device_power(&mut self, power: DevicePower) -> Result<()> {
self.send_command(Command::DeviceRunningSet(power))?;
Ok(())
}
pub fn set_source_voltage(&mut self, vdd: SourceVoltage) -> Result<()> {
self.send_command(Command::RegulatorSet(vdd))?;
Ok(())
}
pub fn start_measurement(
self,
sps: usize,
) -> Result<(Receiver<MeasurementMatch>, impl FnOnce() -> Result<Self>)> {
self.start_measurement_matching(LogicPortPins::default(), sps)
}
pub fn start_measurement_matching(
mut self,
pins: LogicPortPins,
sps: usize,
) -> Result<(Receiver<MeasurementMatch>, impl FnOnce() -> Result<Self>)> {
let ready = Arc::new((Mutex::new(false), Condvar::new()));
let (meas_tx, meas_rx) = mpsc::channel::<MeasurementMatch>();
let (sig_tx, sig_rx) = mpsc::channel::<()>();
let task_ready = ready.clone();
let mut port = self.port.try_clone()?;
let metadata = self.metadata.clone();
let t = thread::spawn(move || {
let r = || -> Result<()> {
let mut accumulator = MeasurementAccumulator::new(metadata);
let (lock, cvar) = &*task_ready;
let _l = cvar
.wait_while(lock.lock().unwrap(), |ready| !*ready)
.unwrap();
let mut buf = [0u8; 4];
let mut measurement_buf = VecDeque::with_capacity(SPS_MAX);
let mut missed = 0;
loop {
match sig_rx.try_recv() {
Ok(_) => return Ok(()),
Err(TryRecvError::Empty) => {}
Err(e) => return Err(e.into()),
}
let n = port.read(&mut buf)?;
missed += accumulator.feed_into(&buf[..n], &mut measurement_buf);
let len = measurement_buf.len();
if len >= SPS_MAX / sps {
let measurement = measurement_buf.drain(..).combine_matching(missed, pins);
meas_tx.send(measurement)?;
missed = 0;
}
}
};
let res = r();
if let Err(e) = &res {
tracing::error!("Error fetching measurements: {:?}", e);
};
res
});
self.port.clear(Input)?;
let (lock, cvar) = &*ready;
let mut ready = lock.lock().unwrap();
*ready = true;
cvar.notify_all();
self.send_command(Command::AverageStart)?;
let stop = move || {
sig_tx.send(())?;
t.join().expect("Data receive thread panicked")?;
self.send_command(Command::AverageStop)?;
Ok(self)
};
Ok((meas_rx, stop))
}
pub fn reset(mut self) -> Result<()> {
self.send_command(Command::Reset)?;
Ok(())
}
fn set_power_mode(&mut self, mode: MeasurementMode) -> Result<()> {
self.send_command(Command::SetPowerMode(mode))?;
Ok(())
}
}
pub fn try_find_ppk2_port() -> Result<String> {
use serialport::SerialPortType::UsbPort;
Ok(serialport::available_ports()?
.into_iter()
.find(|p| match &p.port_type {
UsbPort(usb) => usb.vid == 0x1915 && usb.pid == 0xc00a,
_ => false,
})
.ok_or(Error::Ppk2NotFound)?
.port_name)
}