use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use crate::error::{Error, Result};
use crate::link::reconnect::BoxIdentity;
use crate::protocol::opcode::{Q_CAPS, Q_STATS, Q_VERSION};
use crate::protocol::{PROTO_VER, Resp, parse_resp};
use crate::transport::Transport;
use crate::types::Version;
use super::Device;
const HANDSHAKE_ATTEMPTS: usize = 5;
const HANDSHAKE_ATTEMPT_TIMEOUT: Duration = Duration::from_millis(250);
impl Device {
pub fn open(path: impl AsRef<Path>) -> Result<Device> {
let path = path.as_ref();
let serial = crate::transport::serial::SerialTransport::open(path)?;
let device = Device::from_transport(Arc::new(serial));
let version = device.handshake()?;
let port_serial = crate::transport::scan::find_medius()
.into_iter()
.find(|p| Path::new(&p.path) == path)
.and_then(|p| p.serial);
device.link.set_identity(BoxIdentity {
serial: port_serial,
mac: version.mac,
});
Ok(device)
}
#[cfg_attr(not(feature = "mock"), allow(dead_code))]
pub(crate) fn open_transport(transport: Arc<dyn Transport>) -> Result<Device> {
let device = Device::from_transport(transport);
device.handshake()?;
Ok(device)
}
fn handshake(&self) -> Result<Version> {
let _span =
trace_span!(target: "medius::device", tracing::Level::INFO, "connect").entered();
let version = self.read_version()?;
if version.proto_ver != PROTO_VER {
trace_event!(
target: "medius::device",
tracing::Level::WARN,
got = version.proto_ver,
expected = PROTO_VER,
"handshake: unsupported protocol version",
);
return Err(Error::BadProtoVer {
got: version.proto_ver,
});
}
trace_event!(
target: "medius::device",
tracing::Level::INFO,
proto_ver = version.proto_ver,
fw_major = version.fw_major,
fw_minor = version.fw_minor,
fw_patch = version.fw_patch,
"connected",
);
if let Ok(payload) = self.link.query_timeout(Q_CAPS, HANDSHAKE_ATTEMPT_TIMEOUT)
&& let Some(Resp::Caps(caps)) = parse_resp(&payload)
&& caps.mouse.n_buttons > 0
{
self.link
.desired()
.lock()
.note_declared_buttons(caps.mouse.n_buttons);
}
if let Ok(payload) = self.link.query_timeout(Q_STATS, HANDSHAKE_ATTEMPT_TIMEOUT)
&& let Some(Resp::Stats(stats)) = parse_resp(&payload)
{
self.link.restart_watch().note_session(stats.session);
}
Ok(version)
}
pub(crate) fn read_version(&self) -> Result<Version> {
for _ in 0..HANDSHAKE_ATTEMPTS {
match self
.link
.query_timeout(Q_VERSION, HANDSHAKE_ATTEMPT_TIMEOUT)
{
Ok(payload) => match parse_resp(&payload) {
Some(Resp::Version(v)) => return Ok(v),
_ => {
trace_event!(target: "medius::device", tracing::Level::DEBUG, "handshake: unparseable version reply, retrying");
}
},
Err(Error::QueryTimeout) => {
trace_event!(target: "medius::device", tracing::Level::DEBUG, "handshake: version probe timed out, retrying");
}
Err(e) => return Err(e),
}
}
trace_event!(target: "medius::device", tracing::Level::WARN, attempts = HANDSHAKE_ATTEMPTS, "handshake: no reply to version query");
Err(Error::NoReply)
}
pub fn find() -> Result<Device> {
let port = crate::transport::scan::find_medius()
.into_iter()
.next()
.ok_or(Error::NotFound)?;
Device::open(port.path)
}
}