use std::time::Duration;
#[cfg(feature = "log")]
use log::debug;
use rand::random;
use serialport::{SerialPortType, TTYPort};
use thiserror::Error;
const BAUD: u32 = 57600;
const MSG_OK: u8 = 0xFF;
const RQ_TEST: u8 = 1;
const RQ_DIGITAL_WRITE_0: u8 = 5;
const RQ_DIGITAL_WRITE_1: u8 = 6;
const RQ_DIGITAL_READ_0: u8 = 7;
const RQ_DIGITAL_READ_1: u8 = 8;
const RQ_ANALOG_WRITE_0: u8 = 10;
const RQ_ANALOG_WRITE_1: u8 = 11;
const RQ_ANALOG_READ: u8 = 12;
const RQ_PWM_SET_FREQ: u8 = 14;
const RQ_PWM_SET_VALUE: u8 = 15;
#[derive(Debug, Copy, Clone)]
pub enum AnalogWritePort {
Port0,
Port1,
}
#[derive(Debug, Copy, Clone)]
pub enum DigitalWritePort {
Port0,
Port1,
}
#[derive(Debug, Copy, Clone)]
pub enum DigitalReadPort {
Port0,
Port1,
}
#[derive(Debug, Error)]
pub enum B15FCommandError {
#[error("board error responded with error")]
B15FError,
#[error("Serial port error: {0}")]
SerialPortError(#[from] serialport::Error),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
#[derive(Debug, Error)]
pub enum B15FInitError {
#[error("command error: {0}")]
CommandError(#[from] B15FCommandError),
#[error("device not found")]
DeviceNotFound,
#[error("device not supported")]
DeviceNotSupported,
#[error("Serial port error: {0}")]
SerialPortError(#[from] serialport::Error),
}
pub struct B15F<P>
where
P: serialport::SerialPort,
{
port: P,
}
impl B15F<TTYPort> {
pub fn open_port(port_name: &str) -> Result<B15F<TTYPort>, B15FInitError> {
let port = serialport::new(port_name, BAUD)
.timeout(Duration::from_millis(2000))
.open_native()
.map_err(B15FInitError::SerialPortError)?;
B15F::from(port)
}
pub fn instance() -> Option<B15F<TTYPort>> {
let mut ports = serialport::available_ports().ok()?;
ports.sort_unstable_by_key(port_priority);
for port in ports {
#[cfg(feature = "log")]
debug!("[Discover] Check for B15 board on {}", port.port_name);
let board = B15F::open_port(&port.port_name)
.inspect_err(|err| {
#[cfg(feature = "log")]
debug!("[Discover] Failed to open {}: {}", port.port_name, err);
})
.ok()
.and_then(|mut board| {
board.test().inspect_err(|err| {
#[cfg(feature = "log")]
debug!("[Discover] Test failed for {}: {}", port.port_name, err);
}).ok()?;
Some(board)
});
if let Some(board) = board {
#[cfg(feature = "log")]
debug!("[Discover] Choose B15 board on {}", port.port_name);
return Some(board);
}
}
None
}
}
impl<P> B15F<P>
where
P: serialport::SerialPort,
{
pub fn from(port: P) -> Result<B15F<P>, B15FInitError> {
let mut board = B15F {
port
};
let pass = board.test()?;
if !pass {
return Err(B15FInitError::DeviceNotSupported);
}
Ok(board)
}
pub fn test(&mut self) -> Result<bool, B15FCommandError> {
let rand = random::<u8>();
let data = [RQ_TEST, rand];
self.port.write_all(&data)
.map_err(B15FCommandError::IoError)?;
let mut response = [0u8; 2];
self.port.read_exact(&mut response)
.map_err(B15FCommandError::IoError)?;
if response[0] != MSG_OK {
return Err(B15FCommandError::B15FError);
}
let response = response[1];
let pass = response == rand;
Ok(pass)
}
pub fn digital_write(&mut self, port: DigitalWritePort, value: u8) -> Result<(), B15FCommandError> {
let request = match port {
DigitalWritePort::Port0 => RQ_DIGITAL_WRITE_0,
DigitalWritePort::Port1 => RQ_DIGITAL_WRITE_1,
};
let data = [request, value];
self.port.write_all(&data)
.map_err(B15FCommandError::IoError)?;
self.port.flush()
.map_err(B15FCommandError::IoError)?;
let mut response = [0u8];
self.port.read_exact(&mut response)
.map_err(B15FCommandError::IoError)?;
let response = response[0];
if response == MSG_OK {
Ok(())
} else {
Err(B15FCommandError::B15FError)
}
}
pub fn digital_read(&mut self, port: DigitalReadPort) -> Result<u8, B15FCommandError> {
let request = match port {
DigitalReadPort::Port0 => RQ_DIGITAL_READ_0,
DigitalReadPort::Port1 => RQ_DIGITAL_READ_1,
};
let data = [request];
self.port.write_all(&data)
.map_err(B15FCommandError::IoError)?;
self.port.flush()
.map_err(B15FCommandError::IoError)?;
let mut response = [0u8];
self.port.read_exact(&mut response)
.map_err(B15FCommandError::IoError)?;
let response = response[0].reverse_bits();
Ok(response)
}
pub fn analog_write(&mut self, port: AnalogWritePort, value: u16) -> Result<(), B15FCommandError> {
let request = match port {
AnalogWritePort::Port0 => RQ_ANALOG_WRITE_0,
AnalogWritePort::Port1 => RQ_ANALOG_WRITE_1,
};
if value > 1023 {
panic!("analog write value must be between 0 and 1023")
}
let data = [request, (value & 0xFF) as u8, (value >> 8) as u8];
self.port.write_all(&data)
.map_err(B15FCommandError::IoError)?;
self.port.flush()
.map_err(B15FCommandError::IoError)?;
let mut response = [0u8];
self.port.read_exact(&mut response)
.map_err(B15FCommandError::IoError)?;
let response = response[0];
if response == MSG_OK {
Ok(())
} else {
Err(B15FCommandError::B15FError)
}
}
pub fn analog_read(&mut self, port: u8) -> Result<u16, B15FCommandError> {
let request = RQ_ANALOG_READ;
if port > 7 {
panic!("analog read port must be between 0 and 7")
}
let data = [request, port];
self.port.write_all(&data)
.map_err(B15FCommandError::IoError)?;
self.port.flush()
.map_err(B15FCommandError::IoError)?;
let mut response = [0u8; 2];
self.port.read_exact(&mut response)
.map_err(B15FCommandError::IoError)?;
let response = u16::from_le_bytes(response);
Ok(response)
}
pub fn set_pwm_frequency(&mut self, frequency: f32) -> Result<u8, B15FCommandError> {
let data = frequency.to_le_bytes();
let data = [RQ_PWM_SET_FREQ, data[0], data[1], data[2], data[3]];
self.port.write_all(&data)
.map_err(B15FCommandError::IoError)?;
self.port.flush()
.map_err(B15FCommandError::IoError)?;
let mut response = [0u8];
self.port.read_exact(&mut response)
.map_err(B15FCommandError::IoError)?;
let response = response[0];
Ok(response)
}
pub fn set_pwm_vale(&mut self, value: u8) -> Result<(), B15FCommandError> {
let data = [RQ_PWM_SET_VALUE, value];
self.port.write_all(&data)
.map_err(B15FCommandError::IoError)?;
self.port.flush()
.map_err(B15FCommandError::IoError)?;
let mut response = [0u8];
self.port.read_exact(&mut response)
.map_err(B15FCommandError::IoError)?;
let response = response[0];
if response == MSG_OK {
Ok(())
} else {
Err(B15FCommandError::B15FError)
}
}
}
fn port_priority(port: &serialport::SerialPortInfo) -> u8 {
let priority = match port.port_type {
SerialPortType::UsbPort(_) => 0,
SerialPortType::PciPort => 1,
SerialPortType::BluetoothPort => 2,
SerialPortType::Unknown => 3,
};
#[cfg(feature = "log")]
debug!("[Discover] Port priority: {} -> {}", port.port_name, priority);
priority
}