#[cfg(feature = "discovery")]
use crate::discovery::DiscoveredDevice;
#[cfg(feature = "discovery")]
use crate::discovery::DiscoveryMethod::USBRaw;
#[cfg(all(feature = "usb", feature = "discovery"))]
use crate::HardwareConnection;
use anyhow::{anyhow, Error};
use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient, RequestBuffer};
use nusb::Interface;
use pigdef::config::HardwareConfigMessage::Disconnect;
use pigdef::config::{HardwareConfig, HardwareConfigMessage};
#[cfg(feature = "discovery")]
use pigdef::description::HardwareDetails;
#[cfg(feature = "discovery")]
use pigdef::description::WiFiDetails;
use pigdef::description::{HardwareDescription, SerialNumber, SsidSpec};
#[cfg(feature = "discovery")]
use pigdef::usb_values::GET_HARDWARE_DETAILS_VALUE;
#[cfg(feature = "discovery")]
use pigdef::usb_values::GET_WIFI_VALUE;
use pigdef::usb_values::{
GET_HARDWARE_DESCRIPTION_VALUE, HW_CONFIG_MESSAGE, PIGGUI_REQUEST, RESET_SSID_VALUE,
SET_SSID_VALUE,
};
use serde::de::DeserializeOwned;
use serde::Deserialize;
#[cfg(all(feature = "usb", feature = "discovery"))]
use std::collections::HashMap;
#[cfg(all(feature = "usb", feature = "discovery", feature = "tcp"))]
use std::net::IpAddr;
use std::time::Duration;
const GET_HARDWARE_DESCRIPTION: ControlIn = ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request: PIGGUI_REQUEST,
value: GET_HARDWARE_DESCRIPTION_VALUE,
index: 0,
length: 1024,
};
#[cfg(feature = "discovery")]
const GET_HARDWARE_DETAILS: ControlIn = ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request: PIGGUI_REQUEST,
value: GET_HARDWARE_DETAILS_VALUE,
index: 0,
length: 1024,
};
#[cfg(feature = "discovery")]
const GET_WIFI_DETAILS: ControlIn = ControlIn {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request: PIGGUI_REQUEST,
value: GET_WIFI_VALUE,
index: 0,
length: 1024,
};
const RESET_SSID: ControlOut = ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request: PIGGUI_REQUEST,
value: RESET_SSID_VALUE,
index: 0,
data: &[],
};
async fn receive_control_in<T>(porky: &Interface, control_in: ControlIn) -> Result<T, Error>
where
T: for<'a> Deserialize<'a>,
{
let response = porky.control_in(control_in).await;
response.status?;
let data = response.data;
let length = data.len();
Ok(postcard::from_bytes(&data[0..length])?)
}
async fn get_hardware_description(porky: &Interface) -> Result<HardwareDescription, Error> {
receive_control_in(porky, GET_HARDWARE_DESCRIPTION).await
}
#[cfg(feature = "discovery")]
pub async fn get_hardware_details(porky: &Interface) -> Result<HardwareDetails, Error> {
match receive_control_in(porky, GET_HARDWARE_DETAILS).await {
Ok(hwd) => Ok(hwd),
Err(e) => {
eprintln!("Receive in error: '{e}'");
Err(e)
}
}
}
#[cfg(feature = "discovery")]
pub async fn get_wifi_details(porky: &Interface) -> Result<WiFiDetails, Error> {
match receive_control_in(porky, GET_WIFI_DETAILS).await {
Ok(wifi) => Ok(wifi),
Err(e) => {
eprintln!("Receive in error: '{e}'");
Err(e)
}
}
}
async fn send_control_out(porky: &Interface, control_out: ControlOut<'_>) -> Result<(), Error> {
Ok(porky.control_out(control_out).await.status?)
}
async fn interface_from_serial(serial: &SerialNumber) -> Result<Interface, Error> {
for device_info in
nusb::list_devices()?.filter(|d| d.vendor_id() == 0xbabe && d.product_id() == 0xface)
{
if let Some(serial_number) = device_info.serial_number() {
if serial_number == serial {
let device = device_info.open()?;
let interface = device.claim_interface(0)?;
interface.set_alt_setting(1)?;
return Ok(interface);
}
}
}
Err(anyhow!(
"Could not find USB device with Serial Number: {serial}"
))
}
pub async fn send_ssid_spec(serial_number: SerialNumber, ssid_spec: SsidSpec) -> Result<(), Error> {
let porky = interface_from_serial(&serial_number).await?;
let mut buf = [0; 1024];
let data = postcard::to_slice(&ssid_spec, &mut buf)?;
let set_wifi_details: ControlOut = ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request: PIGGUI_REQUEST,
value: SET_SSID_VALUE,
index: 0,
data,
};
send_control_out(&porky, set_wifi_details).await
}
pub async fn reset_ssid_spec(serial_number: SerialNumber) -> Result<(), Error> {
let porky = interface_from_serial(&serial_number).await?;
send_control_out(&porky, RESET_SSID).await
}
#[derive(Clone)]
pub struct UsbConnection {
interface: Interface,
}
pub async fn wait_for_remote_message<T>(porky: &UsbConnection) -> Result<T, Error>
where
T: DeserializeOwned,
{
loop {
let buf = RequestBuffer::new(1024);
let bytes = porky.interface.interrupt_in(0x81, buf).await;
if bytes.status.is_ok() {
let msg = postcard::from_bytes(&bytes.data)?;
return Ok(msg);
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
pub async fn send_config_message(
porky: &UsbConnection,
hardware_config_message: &HardwareConfigMessage,
) -> Result<(), Error> {
let mut buf = [0; 1024];
let data = postcard::to_slice(hardware_config_message, &mut buf)?;
let hw_message: ControlOut = ControlOut {
control_type: ControlType::Vendor,
recipient: Recipient::Interface,
request: PIGGUI_REQUEST,
value: HW_CONFIG_MESSAGE,
index: 0,
data,
};
send_control_out(&porky.interface, hw_message).await
}
pub async fn disconnect(porky: &UsbConnection) -> Result<(), Error> {
send_config_message(porky, &Disconnect).await
}
pub async fn connect(
serial_number: &SerialNumber,
) -> Result<(HardwareDescription, HardwareConfig, UsbConnection), Error> {
let interface = interface_from_serial(serial_number).await?;
let connection = UsbConnection { interface };
let hardware_description = get_hardware_description(&connection.interface).await?;
send_config_message(&connection, &HardwareConfigMessage::GetConfig).await?;
let hardware_config: HardwareConfig = wait_for_remote_message(&connection).await?;
Ok((hardware_description, hardware_config, connection))
}
#[cfg(all(feature = "usb", feature = "discovery"))]
pub async fn get_serials() -> Result<Vec<SerialNumber>, Error> {
Ok(nusb::list_devices()?
.filter(|d| d.vendor_id() == 0xbabe && d.product_id() == 0xface)
.filter_map(|device_info| {
device_info
.serial_number()
.and_then(|s| Option::from(s.to_string()))
})
.collect())
}
#[cfg(all(feature = "usb", feature = "discovery"))]
pub async fn get_details(
serial_numbers: &[SerialNumber],
) -> Result<HashMap<SerialNumber, DiscoveredDevice>, Error> {
let device_list = nusb::list_devices()?;
let mut devices = HashMap::<SerialNumber, DiscoveredDevice>::new();
for device_info in device_list.filter(|d| d.vendor_id() == 0xbabe && d.product_id() == 0xface) {
let serial_number = device_info
.serial_number()
.ok_or(anyhow!("Could not get device serial_number"))?;
if serial_numbers.contains(&serial_number.to_string()) {
let device = device_info.open()?;
let interface = device.claim_interface(0)?;
interface.set_alt_setting(1)?;
let hardware_details = get_hardware_details(&interface).await?;
let wifi_details = if hardware_details.wifi {
get_wifi_details(&interface).await.ok()
} else {
None
};
let ssid_spec = wifi_details.as_ref().and_then(|wf| wf.ssid_spec.clone());
let mut hardware_connections = HashMap::new();
#[cfg(feature = "tcp")]
if let Some((ip, port)) = wifi_details.and_then(|wf| wf.tcp) {
let connection = HardwareConnection::Tcp(IpAddr::from(ip), port);
hardware_connections.insert(connection.name().to_string(), connection);
}
let usb_connection = HardwareConnection::Usb(hardware_details.serial.clone());
hardware_connections.insert(usb_connection.name().to_string(), usb_connection);
devices.insert(
hardware_details.serial.clone(),
DiscoveredDevice {
discovery_method: USBRaw,
hardware_details,
ssid_spec,
hardware_connections,
},
);
}
}
Ok(devices)
}
#[cfg(feature = "usb")]
#[cfg(test)]
mod test {
use pigdef::config::HardwareConfigMessage;
use pigdef::usb_values::USB_PACKET_SIZE;
#[test]
fn check_buf_size() {
assert!(size_of::<HardwareConfigMessage>() < USB_PACKET_SIZE.into());
}
}