mod buttons;
mod catch;
mod info;
mod locks;
mod movement;
mod stream;
use std::cell::Cell;
use std::ops::Deref;
use std::time::Duration;
use crossbeam_channel as channel;
use crate::error::{MakcuError, Result};
use crate::protocol::parser::{self, ResponseKind};
use crate::transport::TransportHandle;
use crate::transport::serial;
use crate::types::ConnectionState;
thread_local! {
static FF_OVERRIDE: Cell<bool> = const { Cell::new(false) };
}
fn is_ff_override() -> bool {
FF_OVERRIDE.with(|c| c.get())
}
struct FfGuard {
prev: bool,
}
impl FfGuard {
fn new() -> Self {
let prev = FF_OVERRIDE.with(|c| c.replace(true));
Self { prev }
}
}
impl Drop for FfGuard {
fn drop(&mut self) {
FF_OVERRIDE.with(|c| c.set(self.prev));
}
}
const DEFAULT_TIMEOUT: Duration = Duration::from_millis(500);
#[derive(Debug, Clone)]
pub struct DeviceConfig {
pub port: Option<String>,
pub try_4m_first: bool,
pub command_timeout: Duration,
pub reconnect: bool,
pub reconnect_backoff: Duration,
pub fire_and_forget: bool,
}
impl Default for DeviceConfig {
fn default() -> Self {
Self {
port: None,
try_4m_first: true,
command_timeout: DEFAULT_TIMEOUT,
reconnect: true,
reconnect_backoff: Duration::from_millis(100),
fire_and_forget: false,
}
}
}
pub struct Device {
transport: TransportHandle,
config: DeviceConfig,
}
impl std::fmt::Debug for Device {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Device")
.field("port", &self.transport.port_name())
.field("connected", &self.transport.is_connected())
.finish()
}
}
#[allow(dead_code)]
const _: () = {
fn assert_send_sync<T: Send + Sync>() {}
fn _assertions() {
assert_send_sync::<Device>();
}
};
impl Device {
pub fn connect() -> Result<Self> {
Self::with_config(DeviceConfig::default())
}
pub fn connect_port(port: &str) -> Result<Self> {
Self::with_config(DeviceConfig {
port: Some(port.to_string()),
..Default::default()
})
}
pub fn with_config(config: DeviceConfig) -> Result<Self> {
let port_name = match &config.port {
Some(p) => p.clone(),
None => serial::find_port()?,
};
let transport = TransportHandle::connect(
port_name,
config.try_4m_first,
config.reconnect,
config.reconnect_backoff,
)?;
Ok(Self { transport, config })
}
pub fn disconnect(&self) {
self.transport.shutdown();
}
pub fn is_connected(&self) -> bool {
self.transport.is_connected()
}
pub fn port_name(&self) -> String {
self.transport.port_name()
}
pub fn connection_events(&self) -> channel::Receiver<ConnectionState> {
self.transport.subscribe_state()
}
pub fn ff(&self) -> FireAndForget<'_> {
FireAndForget {
device: self,
_guard: FfGuard::new(),
}
}
pub fn send_raw(&self, cmd: &[u8]) -> Result<Vec<u8>> {
let ff = self.is_ff();
let resp = self
.transport
.send_static(cmd, ff, self.config.command_timeout)?;
match resp {
Some(data) => Ok(data),
None if ff => Ok(Vec::new()),
None => Err(MakcuError::Timeout),
}
}
#[cfg(feature = "batch")]
pub fn batch(&self) -> crate::batch::BatchBuilder<'_> {
crate::batch::BatchBuilder::new(self)
}
pub(crate) fn is_ff(&self) -> bool {
self.config.fire_and_forget || is_ff_override()
}
pub(crate) fn exec(&self, cmd: &[u8]) -> Result<()> {
if self.is_ff() {
self.transport
.send_static(cmd, true, self.config.command_timeout)?;
return Ok(());
}
let raw = self.send_raw(cmd)?;
match parser::classify_response(&raw) {
ResponseKind::Executed | ResponseKind::ValueOrEcho(_) | ResponseKind::Value(_) => {
Ok(())
}
}
}
pub(crate) fn query(&self, cmd: &[u8]) -> Result<String> {
let raw = self
.transport
.send_static(cmd, false, self.config.command_timeout)?
.ok_or(MakcuError::Timeout)?;
classify_as_value(&raw)
}
pub(crate) fn exec_dynamic(&self, cmd: &[u8]) -> Result<()> {
self.transport
.send_command(cmd.to_vec(), self.is_ff(), self.config.command_timeout)?;
Ok(())
}
pub(crate) fn query_dynamic(&self, cmd: &[u8]) -> Result<String> {
let raw = self
.transport
.send_command(cmd.to_vec(), false, self.config.command_timeout)?
.ok_or(MakcuError::Timeout)?;
classify_as_value(&raw)
}
#[cfg(feature = "batch")]
pub(crate) fn timeout(&self) -> Duration {
self.config.command_timeout
}
pub(crate) fn transport(&self) -> &TransportHandle {
&self.transport
}
}
#[cfg(feature = "mock")]
impl Device {
pub fn mock() -> (Self, std::sync::Arc<crate::transport::mock::MockTransport>) {
let (transport, mock) = TransportHandle::from_mock();
let device = Self {
transport,
config: DeviceConfig::default(),
};
(device, mock)
}
}
pub struct FireAndForget<'d> {
device: &'d Device,
_guard: FfGuard,
}
impl Deref for FireAndForget<'_> {
type Target = Device;
fn deref(&self) -> &Device {
self.device
}
}
#[cfg(feature = "async")]
pub struct AsyncDevice {
transport: TransportHandle,
config: DeviceConfig,
}
#[cfg(feature = "async")]
#[allow(dead_code)]
const _: () = {
fn assert_send_sync<T: Send + Sync>() {}
fn _assertions() {
assert_send_sync::<AsyncDevice>();
}
};
#[cfg(feature = "async")]
impl std::fmt::Debug for AsyncDevice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AsyncDevice")
.field("port", &self.transport.port_name())
.field("connected", &self.transport.is_connected())
.finish()
}
}
#[cfg(feature = "async")]
impl AsyncDevice {
pub async fn connect() -> Result<Self> {
Self::with_config(DeviceConfig::default()).await
}
pub async fn connect_port(port: &str) -> Result<Self> {
Self::with_config(DeviceConfig {
port: Some(port.to_string()),
..Default::default()
})
.await
}
pub async fn with_config(config: DeviceConfig) -> Result<Self> {
let cfg = config.clone();
let (transport, config) = tokio::task::spawn_blocking(move || -> Result<_> {
let port_name = match &cfg.port {
Some(p) => p.clone(),
None => serial::find_port()?,
};
let transport = TransportHandle::connect(
port_name,
cfg.try_4m_first,
cfg.reconnect,
cfg.reconnect_backoff,
)?;
Ok((transport, cfg))
})
.await
.map_err(|e| MakcuError::Protocol(format!("join error: {}", e)))??;
Ok(Self { transport, config })
}
pub fn disconnect(&self) {
self.transport.shutdown();
}
pub fn is_connected(&self) -> bool {
self.transport.is_connected()
}
pub fn port_name(&self) -> String {
self.transport.port_name()
}
pub fn connection_events(&self) -> channel::Receiver<ConnectionState> {
self.transport.subscribe_state()
}
pub fn ff(&self) -> AsyncFireAndForget<'_> {
AsyncFireAndForget {
device: self,
_guard: FfGuard::new(),
}
}
pub async fn send_raw(&self, cmd: &[u8]) -> Result<Vec<u8>> {
let ff = self.is_ff();
let resp = self
.transport
.send_static_async(cmd, ff, self.config.command_timeout)
.await?;
match resp {
Some(data) => Ok(data),
None if ff => Ok(Vec::new()),
None => Err(MakcuError::Timeout),
}
}
#[cfg(feature = "batch")]
pub fn batch(&self) -> crate::batch::AsyncBatchBuilder<'_> {
crate::batch::AsyncBatchBuilder::new(self)
}
pub(crate) fn is_ff(&self) -> bool {
self.config.fire_and_forget || is_ff_override()
}
pub(crate) async fn exec(&self, cmd: &[u8]) -> Result<()> {
if self.is_ff() {
self.transport
.send_static(cmd, true, self.config.command_timeout)?;
return Ok(());
}
let raw = self.send_raw(cmd).await?;
match parser::classify_response(&raw) {
ResponseKind::Executed | ResponseKind::ValueOrEcho(_) | ResponseKind::Value(_) => {
Ok(())
}
}
}
pub(crate) async fn query(&self, cmd: &[u8]) -> Result<String> {
let raw = self
.transport
.send_static_async(cmd, false, self.config.command_timeout)
.await?
.ok_or(MakcuError::Timeout)?;
classify_as_value(&raw)
}
pub(crate) async fn exec_dynamic(&self, cmd: &[u8]) -> Result<()> {
self.transport
.send_command_async(cmd.to_vec(), self.is_ff(), self.config.command_timeout)
.await?;
Ok(())
}
pub(crate) async fn query_dynamic(&self, cmd: &[u8]) -> Result<String> {
let raw = self
.transport
.send_command_async(cmd.to_vec(), false, self.config.command_timeout)
.await?
.ok_or(MakcuError::Timeout)?;
classify_as_value(&raw)
}
pub(crate) fn transport(&self) -> &TransportHandle {
&self.transport
}
#[cfg(feature = "batch")]
pub(crate) fn timeout(&self) -> std::time::Duration {
self.config.command_timeout
}
}
#[cfg(all(feature = "async", feature = "mock"))]
impl AsyncDevice {
pub fn mock() -> (Self, std::sync::Arc<crate::transport::mock::MockTransport>) {
let (transport, mock) = TransportHandle::from_mock();
let device = Self {
transport,
config: DeviceConfig::default(),
};
(device, mock)
}
}
#[cfg(feature = "async")]
pub struct AsyncFireAndForget<'d> {
device: &'d AsyncDevice,
_guard: FfGuard,
}
#[cfg(feature = "async")]
impl Deref for AsyncFireAndForget<'_> {
type Target = AsyncDevice;
fn deref(&self) -> &AsyncDevice {
self.device
}
}
fn classify_as_value(raw: &[u8]) -> Result<String> {
match parser::classify_response(raw) {
ResponseKind::Value(v) => Ok(v),
ResponseKind::ValueOrEcho(v) => Ok(v),
ResponseKind::Executed => Err(MakcuError::Protocol(
"expected a value but got EXECUTED".into(),
)),
}
}