use std::fs::File;
use std::io::{Read, Write};
use std::mem::MaybeUninit;
use std::path::Path;
use std::sync::{Arc, Mutex};
use color_eyre::eyre::{Result, eyre};
use log::{debug, trace};
use crate::serial::remote::*;
pub struct BmdRspInterface
{
handle: File,
protocol_version: ProtocolVersion,
read_buffer: [u8; REMOTE_MAX_MSG_SIZE],
read_buffer_fullness: usize,
read_buffer_offset: usize,
}
const REMOTE_START: &str = "+#!GA#";
const REMOTE_HL_CHECK: &str = "!HC#";
impl BmdRspInterface
{
pub fn from_path(serial_port: &Path) -> Result<Self>
{
debug!("Opening probe interface at {:?}", serial_port);
let handle = File::options().read(true).write(true).open(serial_port)?;
let mut result = Self {
handle,
protocol_version: ProtocolVersion::Unknown,
read_buffer: [0; REMOTE_MAX_MSG_SIZE],
read_buffer_fullness: 0,
read_buffer_offset: 0,
};
result.init_handle()?;
result.buffer_write(REMOTE_START)?;
let buffer = result.buffer_read()?;
if buffer.is_empty() || buffer.as_bytes()[0] != REMOTE_RESP_OK {
let message = if buffer.len() > 1 {
&buffer[1..]
} else {
"unknown"
};
return Err(eyre!("Remote protocol startup failed, error {}", message));
}
debug!("Remote is {}", &buffer[1..]);
result.buffer_write(REMOTE_HL_CHECK)?;
let buffer = result.buffer_read()?;
if buffer.is_empty() {
return Err(eyre!("Probe failed to respond at all to protocol version request"));
} else if buffer.as_bytes()[0] != REMOTE_RESP_OK && buffer.as_bytes()[0] != REMOTE_RESP_NOTSUP {
return Err(eyre!("Probe responded improperly to protocol version request with {}", buffer));
}
if buffer.as_bytes()[0] == REMOTE_RESP_NOTSUP {
result.protocol_version = ProtocolVersion::V0;
} else {
let version = decode_response(&buffer[1..], 8);
result.protocol_version = match version {
0 => ProtocolVersion::V0Plus,
1 => ProtocolVersion::V1,
2 => ProtocolVersion::V2,
3 => ProtocolVersion::V3,
4 => ProtocolVersion::V4,
_ => return Err(eyre!("Unknown remote protocol version {}", version)),
};
}
trace!("Probe talks BMD RSP {}", result.protocol_version);
Ok(result)
}
pub fn remote(self) -> Result<Box<dyn BmdRemoteProtocol>>
{
let interface = Arc::new(Mutex::new(self));
let protocol = interface
.lock()
.map_err(|_| eyre!("Failed to aquire lock on interface to access remote protocol"))?
.protocol_version;
protocol.protocol_impl(interface.clone())
}
pub(crate) fn buffer_write(&mut self, message: &str) -> Result<()>
{
debug!("BMD RSP write: {}", message);
Ok(self.handle.write_all(message.as_bytes())?)
}
pub(crate) fn buffer_read(&mut self) -> Result<String>
{
let mut response = 0;
while response != REMOTE_RESP {
if self.read_buffer_offset == self.read_buffer_fullness {
self.read_more_data()?;
}
response = self.read_buffer[self.read_buffer_offset];
self.read_buffer_offset += 1;
}
let mut buffer = [0u8; REMOTE_MAX_MSG_SIZE];
let mut offset = 0;
while offset < buffer.len() {
if self.read_buffer_offset == self.read_buffer_fullness {
self.read_more_data()?;
}
let mut response_length = 0;
while self.read_buffer_offset + response_length < self.read_buffer_fullness &&
offset + response_length < buffer.len()
{
if self.read_buffer[self.read_buffer_offset + response_length] == REMOTE_EOM {
response_length += 1;
break;
}
response_length += 1;
}
let read_buffer_offset = self.read_buffer_offset;
buffer[offset..offset + response_length]
.copy_from_slice(&self.read_buffer[read_buffer_offset..read_buffer_offset + response_length]);
self.read_buffer_offset += response_length;
offset += response_length - 1;
if buffer[offset] == REMOTE_EOM {
buffer[offset] = 0;
let result = unsafe { String::from_utf8_unchecked(buffer[..offset].to_vec()) };
debug!("BMD RSP read: {}", result);
return Ok(result);
}
offset += 1;
}
let result = unsafe { String::from_utf8_unchecked(buffer.to_vec()) };
debug!("BMD RSP read: {}", result);
Ok(result)
}
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
impl BmdRspInterface
{
fn init_handle(&self) -> Result<()>
{
use std::os::fd::AsRawFd;
#[cfg(any(target_os = "linux", target_os = "android"))]
use termios::os::linux::CRTSCTS;
#[cfg(target_os = "macos")]
use termios::os::macos::CRTSCTS;
use termios::*;
let fd = self.handle.as_raw_fd();
let mut attrs = Termios::from_fd(fd)?;
attrs.c_cflag &= !(CSIZE | CSTOPB);
attrs.c_cflag |= CS8 | CLOCAL | CREAD | CRTSCTS;
attrs.c_iflag &= !(IGNBRK | IXON | IXOFF | IXANY);
attrs.c_lflag = 0;
attrs.c_oflag = 0;
attrs.c_cc[VMIN] = 0;
attrs.c_cc[VTIME] = 5;
tcsetattr(fd, TCSANOW, &attrs)?;
trace!("Configured comms handle to probe remote serial interface");
Ok(())
}
fn read_more_data(&mut self) -> Result<()>
{
use std::os::fd::AsRawFd;
use std::ptr::null_mut;
use color_eyre::eyre::eyre;
use libc::{FD_SET, FD_SETSIZE, FD_ZERO, c_int, fd_set, select, timeval};
let mut select_set = MaybeUninit::<fd_set>::uninit();
unsafe {
FD_ZERO(select_set.as_mut_ptr());
FD_SET(self.handle.as_raw_fd(), select_set.as_mut_ptr());
}
let mut select_set = unsafe { select_set.assume_init() };
let mut timeout = timeval {
tv_sec: 2,
tv_usec: 0,
};
let result = unsafe { select(FD_SETSIZE as c_int, &mut select_set, null_mut(), null_mut(), &mut timeout) };
if result < 0 {
Err(eyre!("Failed on select"))
} else if result == 0 {
Err(eyre!("Timeout while waiting for BMD remote protocol response"))
} else {
let bytes_received = self.handle.read(&mut self.read_buffer)?;
trace!("Read {} bytes from probe", bytes_received);
self.read_buffer_fullness = bytes_received;
self.read_buffer_offset = 0;
Ok(())
}
}
}
#[cfg(target_os = "windows")]
impl BmdRspInterface
{
const DCB_CHECK_PARITY: u32 = 1 << 1;
const DCB_DSR_SENSITIVE: u32 = 1 << 6;
const DCB_DTR_CONTROL_ENABLE: u32 = 1 << 4;
const DCB_DTR_CONTROL_MASK: u32 = 3 << 4;
const DCB_RTS_CONTROL_DISABLE: u32 = 0 << 12;
const DCB_RTS_CONTROL_MASK: u32 = 3 << 12;
const DCB_USE_CTS: u32 = 1 << 2;
const DCB_USE_DSR: u32 = 1 << 3;
const DCB_USE_XOFF: u32 = 1 << 9;
const DCB_USE_XON: u32 = 1 << 8;
fn init_handle(&self) -> Result<()>
{
use std::os::windows::io::AsRawHandle;
use windows::Win32::Devices::Communication::{
COMMTIMEOUTS, DCB, GetCommState, NOPARITY, PURGE_RXCLEAR, PurgeComm, SetCommState, SetCommTimeouts,
};
use windows::Win32::Foundation::HANDLE;
let handle = HANDLE(self.handle.as_raw_handle());
let mut serial_params = MaybeUninit::<DCB>::uninit();
let mut serial_params = unsafe {
GetCommState(handle, serial_params.as_mut_ptr())?;
serial_params.assume_init()
};
serial_params.ByteSize = 8;
serial_params.Parity = NOPARITY;
serial_params._bitfield &= !(Self::DCB_CHECK_PARITY |
Self::DCB_USE_CTS |
Self::DCB_USE_DSR |
Self::DCB_DTR_CONTROL_MASK |
Self::DCB_DSR_SENSITIVE |
Self::DCB_USE_XOFF |
Self::DCB_USE_XON |
Self::DCB_RTS_CONTROL_MASK);
serial_params._bitfield |= Self::DCB_DTR_CONTROL_ENABLE | Self::DCB_RTS_CONTROL_DISABLE;
unsafe { SetCommState(handle, &serial_params)? };
let timeouts = COMMTIMEOUTS {
ReadIntervalTimeout: u32::MAX,
ReadTotalTimeoutMultiplier: 0,
ReadTotalTimeoutConstant: 0,
WriteTotalTimeoutMultiplier: 0,
WriteTotalTimeoutConstant: 100,
};
unsafe {
SetCommTimeouts(handle, &timeouts)?;
PurgeComm(handle, PURGE_RXCLEAR)?;
}
trace!("Configured comms handle to probe remote serial interface");
Ok(())
}
fn read_more_data(&mut self) -> Result<()>
{
use std::os::windows::io::AsRawHandle;
use windows::Win32::Foundation::{HANDLE, WAIT_OBJECT_0};
use windows::Win32::System::Threading::WaitForSingleObject;
use windows_result::Error;
let handle = HANDLE(self.handle.as_raw_handle());
if unsafe { WaitForSingleObject(handle, 100) } != WAIT_OBJECT_0 {
return Err(eyre!("Timeout while waiting for BMD RSP response: {}", Error::from_win32()));
}
let bytes_received = self.handle.read(&mut self.read_buffer)?;
trace!("Read {} bytes from probe", bytes_received);
self.read_buffer_fullness = bytes_received;
self.read_buffer_offset = 0;
Ok(())
}
}