feat: usb mode, battery percentage
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
use crate::usb;
|
||||||
|
|
||||||
use super::common::file::{Input, Output};
|
use super::common::file::{Input, Output};
|
||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
|
|
||||||
@@ -17,3 +21,36 @@ pub enum BackupCmd {
|
|||||||
input_file: Input,
|
input_file: Input,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_export(
|
||||||
|
device_id: Option<&str>,
|
||||||
|
output: Output,
|
||||||
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
let camera = usb::get_camera(device_id)?;
|
||||||
|
|
||||||
|
let mut writer = output.get_writer()?;
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_import(
|
||||||
|
device_id: Option<&str>,
|
||||||
|
input: Input,
|
||||||
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
let camera = usb::get_camera(device_id)?;
|
||||||
|
|
||||||
|
let mut reader = input.get_reader()?;
|
||||||
|
|
||||||
|
todo!();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(cmd: BackupCmd, device_id: Option<&str>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
match cmd {
|
||||||
|
BackupCmd::Export { output_file } => handle_export(device_id, output_file),
|
||||||
|
BackupCmd::Import { input_file } => handle_import(device_id, input_file),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use std::{error::Error, path::PathBuf, str::FromStr};
|
use std::{error::Error, fs::File, io, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Input {
|
pub enum Input {
|
||||||
@@ -17,6 +17,15 @@ impl FromStr for Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Input {
|
||||||
|
pub fn get_reader(&self) -> Result<Box<dyn io::Read>, Box<dyn Error + Send + Sync>> {
|
||||||
|
match self {
|
||||||
|
Input::Stdin => Ok(Box::new(io::stdin())),
|
||||||
|
Input::Path(path) => Ok(Box::new(File::open(path)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Output {
|
pub enum Output {
|
||||||
Path(PathBuf),
|
Path(PathBuf),
|
||||||
@@ -33,3 +42,12 @@ impl FromStr for Output {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Output {
|
||||||
|
pub fn get_writer(&self) -> Result<Box<dyn io::Write>, Box<dyn Error + Send + Sync>> {
|
||||||
|
match self {
|
||||||
|
Output::Stdout => Ok(Box::new(io::stdout())),
|
||||||
|
Output::Path(path) => Ok(Box::new(File::create(path)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -3,30 +3,30 @@ use std::{error::Error, fmt};
|
|||||||
use clap::Subcommand;
|
use clap::Subcommand;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::usb;
|
use crate::{hardware::FujiUsbMode, usb};
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
pub enum DeviceCmd {
|
pub enum DeviceCmd {
|
||||||
/// List devices
|
/// List cameras
|
||||||
#[command(alias = "l")]
|
#[command(alias = "l")]
|
||||||
List,
|
List,
|
||||||
|
|
||||||
/// Dump device info
|
/// Get camera info
|
||||||
#[command(alias = "i")]
|
#[command(alias = "i")]
|
||||||
Info,
|
Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct DeviceItemRepr {
|
pub struct CameraItemRepr {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub vendor_id: String,
|
pub vendor_id: String,
|
||||||
pub product_id: String,
|
pub product_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&usb::Device> for DeviceItemRepr {
|
impl From<&usb::Camera> for CameraItemRepr {
|
||||||
fn from(device: &usb::Device) -> Self {
|
fn from(device: &usb::Camera) -> Self {
|
||||||
DeviceItemRepr {
|
CameraItemRepr {
|
||||||
id: device.id(),
|
id: device.id(),
|
||||||
name: device.name(),
|
name: device.name(),
|
||||||
vendor_id: format!("0x{:04x}", device.vendor_id()),
|
vendor_id: format!("0x{:04x}", device.vendor_id()),
|
||||||
@@ -35,7 +35,7 @@ impl From<&usb::Device> for DeviceItemRepr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DeviceItemRepr {
|
impl fmt::Display for CameraItemRepr {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@@ -45,24 +45,24 @@ impl fmt::Display for DeviceItemRepr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_list(json: bool) -> Result<(), Box<dyn Error + Send + Sync>> {
|
fn handle_list(json: bool) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
let devices: Vec<DeviceItemRepr> = usb::get_connected_devices()?
|
let cameras: Vec<CameraItemRepr> = usb::get_connected_camers()?
|
||||||
.iter()
|
.iter()
|
||||||
.map(|d| d.into())
|
.map(|d| d.into())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
println!("{}", serde_json::to_string_pretty(&devices)?);
|
println!("{}", serde_json::to_string_pretty(&cameras)?);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if devices.is_empty() {
|
if cameras.is_empty() {
|
||||||
println!("No supported devices connected.");
|
println!("No supported cameras connected.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Connected devices:");
|
println!("Connected cameras:");
|
||||||
for d in devices {
|
for d in cameras {
|
||||||
println!("- {}", d);
|
println!("- {}", d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,29 +70,19 @@ pub fn handle_list(json: bool) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct DeviceRepr {
|
pub struct CameraRepr {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub device: DeviceItemRepr,
|
pub device: CameraItemRepr,
|
||||||
|
|
||||||
pub manufacturer: String,
|
pub manufacturer: String,
|
||||||
pub model: String,
|
pub model: String,
|
||||||
pub device_version: String,
|
pub device_version: String,
|
||||||
pub serial_number: String,
|
pub serial_number: String,
|
||||||
|
pub mode: FujiUsbMode,
|
||||||
|
pub battery: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceRepr {
|
impl fmt::Display for CameraRepr {
|
||||||
pub fn from_info(device: &usb::Device, info: &libptp::DeviceInfo) -> Self {
|
|
||||||
DeviceRepr {
|
|
||||||
device: device.into(),
|
|
||||||
manufacturer: info.Manufacturer.clone(),
|
|
||||||
model: info.Model.clone(),
|
|
||||||
device_version: info.DeviceVersion.clone(),
|
|
||||||
serial_number: info.SerialNumber.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for DeviceRepr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
writeln!(f, "Name: {}", self.device.name)?;
|
writeln!(f, "Name: {}", self.device.name)?;
|
||||||
writeln!(f, "ID: {}", self.device.id)?;
|
writeln!(f, "ID: {}", self.device.id)?;
|
||||||
@@ -103,20 +93,29 @@ impl fmt::Display for DeviceRepr {
|
|||||||
)?;
|
)?;
|
||||||
writeln!(f, "Manufacturer: {}", self.manufacturer)?;
|
writeln!(f, "Manufacturer: {}", self.manufacturer)?;
|
||||||
writeln!(f, "Model: {}", self.model)?;
|
writeln!(f, "Model: {}", self.model)?;
|
||||||
writeln!(f, "Device Version: {}", self.device_version)?;
|
writeln!(f, "Version: {}", self.device_version)?;
|
||||||
write!(f, "Serial Number: {}", self.serial_number)
|
writeln!(f, "Serial Number: {}", self.serial_number)?;
|
||||||
|
writeln!(f, "Mode: {}", self.mode)?;
|
||||||
|
write!(f, "Battery: {}%", self.battery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_info(
|
fn handle_info(json: bool, device_id: Option<&str>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
json: bool,
|
let camera = usb::get_camera(device_id)?;
|
||||||
device_id: Option<&str>,
|
|
||||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
||||||
let device = usb::get_device(device_id)?;
|
|
||||||
|
|
||||||
let mut camera = device.camera()?;
|
let info = camera.get_info()?;
|
||||||
let info = device.model.get_device_info(&mut camera)?;
|
let mode = camera.get_fuji_usb_mode()?;
|
||||||
let repr = DeviceRepr::from_info(&device, &info);
|
let battery = camera.get_fuji_battery_info()?;
|
||||||
|
|
||||||
|
let repr = CameraRepr {
|
||||||
|
device: (&camera).into(),
|
||||||
|
manufacturer: info.Manufacturer.clone(),
|
||||||
|
model: info.Model.clone(),
|
||||||
|
device_version: info.DeviceVersion.clone(),
|
||||||
|
serial_number: info.SerialNumber.clone(),
|
||||||
|
mode,
|
||||||
|
battery,
|
||||||
|
};
|
||||||
|
|
||||||
if json {
|
if json {
|
||||||
println!("{}", serde_json::to_string_pretty(&repr)?);
|
println!("{}", serde_json::to_string_pretty(&repr)?);
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
pub const TIMEOUT: Duration = Duration::from_millis(500);
|
|
@@ -1,36 +1,194 @@
|
|||||||
use std::error::Error;
|
use std::{error::Error, fmt, time::Duration};
|
||||||
|
|
||||||
use libptp::{DeviceInfo, StandardCommandCode};
|
use libptp::{DeviceInfo, StandardCommandCode};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use rusb::GlobalContext;
|
use rusb::{DeviceDescriptor, GlobalContext};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
mod common;
|
|
||||||
mod xt5;
|
mod xt5;
|
||||||
|
|
||||||
pub trait Camera {
|
pub const TIMEOUT: Duration = Duration::from_millis(500);
|
||||||
fn vendor_id(&self) -> u16;
|
|
||||||
fn product_id(&self) -> u16;
|
|
||||||
fn name(&self) -> &'static str;
|
|
||||||
|
|
||||||
fn get_device_info(
|
#[repr(u32)]
|
||||||
&self,
|
#[derive(Debug, Clone, Copy)]
|
||||||
camera: &mut libptp::Camera<GlobalContext>,
|
pub enum DevicePropCode {
|
||||||
) -> Result<DeviceInfo, Box<dyn Error + Send + Sync>> {
|
FujiUsbMode = 0xd16e,
|
||||||
debug!("Using default GetDeviceInfo command for {}", self.name());
|
FujiBatteryInfo1 = 0xD36A,
|
||||||
|
FujiBatteryInfo2 = 0xD36B,
|
||||||
|
}
|
||||||
|
|
||||||
let response = camera.command(
|
#[derive(Debug, Clone, Copy, Serialize)]
|
||||||
StandardCommandCode::GetDeviceInfo,
|
pub enum FujiUsbMode {
|
||||||
&[],
|
RawConversion, // mode == 6
|
||||||
None,
|
Unsupported,
|
||||||
Some(common::TIMEOUT),
|
}
|
||||||
)?;
|
|
||||||
|
|
||||||
debug!("Received response with {} bytes", response.len());
|
impl From<u32> for FujiUsbMode {
|
||||||
|
fn from(val: u32) -> Self {
|
||||||
let device_info = DeviceInfo::decode(&response)?;
|
match val {
|
||||||
|
6 => FujiUsbMode::RawConversion,
|
||||||
Ok(device_info)
|
_ => FujiUsbMode::Unsupported,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const SUPPORTED_MODELS: &[&dyn Camera] = &[&xt5::FujifilmXT5];
|
impl fmt::Display for FujiUsbMode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match self {
|
||||||
|
FujiUsbMode::RawConversion => "USB RAW CONV./BACKUP RESTORE",
|
||||||
|
FujiUsbMode::Unsupported => "Unsupported USB Mode",
|
||||||
|
};
|
||||||
|
write!(f, "{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CameraImpl {
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
|
fn next_session_id(&self) -> u32;
|
||||||
|
|
||||||
|
fn open_session(
|
||||||
|
&self,
|
||||||
|
ptp: &mut libptp::Camera<GlobalContext>,
|
||||||
|
session_id: u32,
|
||||||
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
debug!("Opening new session with id {}", session_id);
|
||||||
|
ptp.command(
|
||||||
|
StandardCommandCode::OpenSession,
|
||||||
|
&[session_id],
|
||||||
|
None,
|
||||||
|
Some(TIMEOUT),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close_session(
|
||||||
|
&self,
|
||||||
|
ptp: &mut libptp::Camera<GlobalContext>,
|
||||||
|
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||||
|
debug!("Closing session");
|
||||||
|
ptp.command(StandardCommandCode::CloseSession, &[], None, Some(TIMEOUT))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_prop_value_raw(
|
||||||
|
&self,
|
||||||
|
ptp: &mut libptp::Camera<GlobalContext>,
|
||||||
|
prop: DevicePropCode,
|
||||||
|
) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
|
||||||
|
let session_id = self.next_session_id();
|
||||||
|
self.open_session(ptp, session_id)?;
|
||||||
|
|
||||||
|
debug!("Getting property {:?}", prop);
|
||||||
|
|
||||||
|
let response = ptp.command(
|
||||||
|
StandardCommandCode::GetDevicePropValue,
|
||||||
|
&[prop as u32],
|
||||||
|
None,
|
||||||
|
Some(TIMEOUT),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.close_session(ptp)?;
|
||||||
|
|
||||||
|
let response = response?;
|
||||||
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_prop_value_scalar(
|
||||||
|
&self,
|
||||||
|
ptp: &mut libptp::Camera<GlobalContext>,
|
||||||
|
prop: DevicePropCode,
|
||||||
|
) -> Result<u32, Box<dyn Error + Send + Sync>> {
|
||||||
|
let data = self.get_prop_value_raw(ptp, prop)?;
|
||||||
|
|
||||||
|
match data.len() {
|
||||||
|
1 => Ok(data[0] as u32),
|
||||||
|
2 => Ok(u16::from_le_bytes([data[0], data[1]]) as u32),
|
||||||
|
4 => Ok(u32::from_le_bytes([data[0], data[1], data[2], data[3]])),
|
||||||
|
n => Err(format!("Cannot parse property {:?} as scalar: {} bytes", prop, n).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fuji_usb_mode(
|
||||||
|
&self,
|
||||||
|
ptp: &mut libptp::Camera<GlobalContext>,
|
||||||
|
) -> Result<FujiUsbMode, Box<dyn Error + Send + Sync>> {
|
||||||
|
let result = self.get_prop_value_scalar(ptp, DevicePropCode::FujiUsbMode)?;
|
||||||
|
Ok(result.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_fuji_battery_info(
|
||||||
|
&self,
|
||||||
|
ptp: &mut libptp::Camera<GlobalContext>,
|
||||||
|
) -> Result<u32, Box<dyn Error + Send + Sync>> {
|
||||||
|
let data = self.get_prop_value_raw(ptp, DevicePropCode::FujiBatteryInfo2)?;
|
||||||
|
debug!("Raw battery data: {:?}", data);
|
||||||
|
|
||||||
|
if data.len() < 3 {
|
||||||
|
return Err("Battery info payload too short".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let utf16: Vec<u16> = data[1..]
|
||||||
|
.chunks(2)
|
||||||
|
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
|
||||||
|
.take_while(|&c| c != 0)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
debug!("Decoded UTF-16 units: {:?}", utf16);
|
||||||
|
|
||||||
|
let utf8_string = String::from_utf16(&utf16)?;
|
||||||
|
debug!("Decoded UTF-16 string: {}", utf8_string);
|
||||||
|
|
||||||
|
let percentage: u32 = utf8_string
|
||||||
|
.split(',')
|
||||||
|
.next()
|
||||||
|
.ok_or("Failed to parse battery percentage")?
|
||||||
|
.parse()?;
|
||||||
|
|
||||||
|
Ok(percentage)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_info(
|
||||||
|
&self,
|
||||||
|
ptp: &mut libptp::Camera<GlobalContext>,
|
||||||
|
) -> Result<DeviceInfo, Box<dyn Error + Send + Sync>> {
|
||||||
|
debug!("Sending GetDeviceInfo command");
|
||||||
|
let response = ptp.command(StandardCommandCode::GetDeviceInfo, &[], None, Some(TIMEOUT))?;
|
||||||
|
debug!("Received response with {} bytes", response.len());
|
||||||
|
|
||||||
|
let info = DeviceInfo::decode(&response)?;
|
||||||
|
Ok(info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct CameraId {
|
||||||
|
pub vendor: u16,
|
||||||
|
pub product: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SupportedCamera {
|
||||||
|
pub id: CameraId,
|
||||||
|
pub factory: fn() -> Box<dyn CameraImpl>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SUPPORTED_CAMERAS: &[SupportedCamera] = &[SupportedCamera {
|
||||||
|
id: xt5::FUJIFILM_XT5,
|
||||||
|
factory: || Box::new(xt5::FujifilmXT5::new()),
|
||||||
|
}];
|
||||||
|
|
||||||
|
impl From<&SupportedCamera> for Box<dyn CameraImpl> {
|
||||||
|
fn from(camera: &SupportedCamera) -> Self {
|
||||||
|
(camera.factory)()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SupportedCamera {
|
||||||
|
pub fn matches_descriptor(&self, descriptor: &DeviceDescriptor) -> bool {
|
||||||
|
descriptor.vendor_id() == self.id.vendor && descriptor.product_id() == self.id.product
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,18 +1,31 @@
|
|||||||
use super::Camera;
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
use super::{CameraId, CameraImpl};
|
||||||
|
|
||||||
|
pub const FUJIFILM_XT5: CameraId = CameraId {
|
||||||
|
vendor: 0x04cb,
|
||||||
|
product: 0x02fc,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FujifilmXT5;
|
pub struct FujifilmXT5 {
|
||||||
|
session_counter: AtomicU32,
|
||||||
|
}
|
||||||
|
|
||||||
impl Camera for FujifilmXT5 {
|
impl FujifilmXT5 {
|
||||||
fn vendor_id(&self) -> u16 {
|
pub fn new() -> Self {
|
||||||
0x04cb
|
Self {
|
||||||
|
session_counter: AtomicU32::new(1),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn product_id(&self) -> u16 {
|
|
||||||
0x02fc
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CameraImpl for FujifilmXT5 {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"FUJIFILM X-T5"
|
"FUJIFILM X-T5"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn next_session_id(&self) -> u32 {
|
||||||
|
self.session_counter.fetch_add(1, Ordering::SeqCst)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +1,16 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
use libptp::DeviceInfo;
|
||||||
use rusb::GlobalContext;
|
use rusb::GlobalContext;
|
||||||
|
|
||||||
use crate::hardware::SUPPORTED_MODELS;
|
use crate::hardware::{FujiUsbMode, SUPPORTED_CAMERAS};
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub struct Camera {
|
||||||
pub struct Device {
|
camera_impl: Box<dyn crate::hardware::CameraImpl>,
|
||||||
pub model: &'static dyn crate::hardware::Camera,
|
rusb_device: rusb::Device<GlobalContext>,
|
||||||
pub rusb_device: rusb::Device<GlobalContext>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Camera {
|
||||||
pub fn camera(&self) -> Result<libptp::Camera<GlobalContext>, Box<dyn Error + Send + Sync>> {
|
|
||||||
let handle = self.rusb_device.open()?;
|
|
||||||
let device = handle.device();
|
|
||||||
let camera = libptp::Camera::new(&device)?;
|
|
||||||
Ok(camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> String {
|
pub fn id(&self) -> String {
|
||||||
let bus = self.rusb_device.bus_number();
|
let bus = self.rusb_device.bus_number();
|
||||||
let address = self.rusb_device.address();
|
let address = self.rusb_device.address();
|
||||||
@@ -25,7 +18,7 @@ impl Device {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> String {
|
||||||
self.model.name().to_string()
|
self.camera_impl.name().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vendor_id(&self) -> u16 {
|
pub fn vendor_id(&self) -> u16 {
|
||||||
@@ -37,10 +30,32 @@ impl Device {
|
|||||||
let descriptor = self.rusb_device.device_descriptor().unwrap();
|
let descriptor = self.rusb_device.device_descriptor().unwrap();
|
||||||
descriptor.product_id()
|
descriptor.product_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ptp(&self) -> Result<libptp::Camera<GlobalContext>, Box<dyn Error + Send + Sync>> {
|
||||||
|
let handle = self.rusb_device.open()?;
|
||||||
|
let device = handle.device();
|
||||||
|
let ptp = libptp::Camera::new(&device)?;
|
||||||
|
Ok(ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_info(&self) -> Result<DeviceInfo, Box<dyn Error + Send + Sync>> {
|
||||||
|
let mut ptp = self.ptp()?;
|
||||||
|
self.camera_impl.get_info(&mut ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fuji_usb_mode(&self) -> Result<FujiUsbMode, Box<dyn Error + Send + Sync>> {
|
||||||
|
let mut ptp = self.ptp()?;
|
||||||
|
self.camera_impl.get_fuji_usb_mode(&mut ptp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_fuji_battery_info(&self) -> Result<u32, Box<dyn Error + Send + Sync>> {
|
||||||
|
let mut ptp = self.ptp()?;
|
||||||
|
self.camera_impl.get_fuji_battery_info(&mut ptp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_connected_devices() -> Result<Vec<Device>, Box<dyn Error + Send + Sync>> {
|
pub fn get_connected_camers() -> Result<Vec<Camera>, Box<dyn Error + Send + Sync>> {
|
||||||
let mut connected_devices = Vec::new();
|
let mut connected_cameras = Vec::new();
|
||||||
|
|
||||||
for device in rusb::devices()?.iter() {
|
for device in rusb::devices()?.iter() {
|
||||||
let descriptor = match device.device_descriptor() {
|
let descriptor = match device.device_descriptor() {
|
||||||
@@ -48,25 +63,23 @@ pub fn get_connected_devices() -> Result<Vec<Device>, Box<dyn Error + Send + Syn
|
|||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
for model in SUPPORTED_MODELS.iter() {
|
for camera in SUPPORTED_CAMERAS.iter() {
|
||||||
if descriptor.vendor_id() == model.vendor_id()
|
if camera.matches_descriptor(&descriptor) {
|
||||||
&& descriptor.product_id() == model.product_id()
|
let camera = Camera {
|
||||||
{
|
camera_impl: camera.into(),
|
||||||
let connected_device = Device {
|
|
||||||
model: *model,
|
|
||||||
rusb_device: device,
|
rusb_device: device,
|
||||||
};
|
};
|
||||||
|
|
||||||
connected_devices.push(connected_device);
|
connected_cameras.push(camera);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(connected_devices)
|
Ok(connected_cameras)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_connected_device_by_id(id: &str) -> Result<Device, Box<dyn Error + Send + Sync>> {
|
pub fn get_connected_camera_by_id(id: &str) -> Result<Camera, Box<dyn Error + Send + Sync>> {
|
||||||
let parts: Vec<&str> = id.split('.').collect();
|
let parts: Vec<&str> = id.split('.').collect();
|
||||||
if parts.len() != 2 {
|
if parts.len() != 2 {
|
||||||
return Err(format!("Invalid device id format: {}", id).into());
|
return Err(format!("Invalid device id format: {}", id).into());
|
||||||
@@ -79,12 +92,10 @@ pub fn get_connected_device_by_id(id: &str) -> Result<Device, Box<dyn Error + Se
|
|||||||
if device.bus_number() == bus && device.address() == address {
|
if device.bus_number() == bus && device.address() == address {
|
||||||
let descriptor = device.device_descriptor()?;
|
let descriptor = device.device_descriptor()?;
|
||||||
|
|
||||||
for model in SUPPORTED_MODELS.iter() {
|
for camera in SUPPORTED_CAMERAS.iter() {
|
||||||
if descriptor.vendor_id() == model.vendor_id()
|
if camera.matches_descriptor(&descriptor) {
|
||||||
&& descriptor.product_id() == model.product_id()
|
return Ok(Camera {
|
||||||
{
|
camera_impl: camera.into(),
|
||||||
return Ok(Device {
|
|
||||||
model: *model,
|
|
||||||
rusb_device: device,
|
rusb_device: device,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -97,10 +108,10 @@ pub fn get_connected_device_by_id(id: &str) -> Result<Device, Box<dyn Error + Se
|
|||||||
Err(format!("No device found with id: {}", id).into())
|
Err(format!("No device found with id: {}", id).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_device(device_id: Option<&str>) -> Result<Device, Box<dyn Error + Send + Sync>> {
|
pub fn get_camera(device_id: Option<&str>) -> Result<Camera, Box<dyn Error + Send + Sync>> {
|
||||||
match device_id {
|
match device_id {
|
||||||
Some(id) => get_connected_device_by_id(id),
|
Some(id) => get_connected_camera_by_id(id),
|
||||||
None => get_connected_devices()?
|
None => get_connected_camers()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| "No supported devices connected.".into()),
|
.ok_or_else(|| "No supported devices connected.".into()),
|
||||||
|
Reference in New Issue
Block a user