chore: refactor ptp handling
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::{error::Error, fmt};
|
||||
use std::error::Error;
|
||||
|
||||
use crate::usb;
|
||||
|
||||
|
@@ -3,7 +3,10 @@ use std::{error::Error, fmt};
|
||||
use clap::Subcommand;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{hardware::FujiUsbMode, usb};
|
||||
use crate::{
|
||||
hardware::{CameraImpl, FujiUsbMode},
|
||||
usb,
|
||||
};
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub enum DeviceCmd {
|
||||
@@ -24,13 +27,13 @@ pub struct CameraItemRepr {
|
||||
pub product_id: String,
|
||||
}
|
||||
|
||||
impl From<&usb::Camera> for CameraItemRepr {
|
||||
fn from(device: &usb::Camera) -> Self {
|
||||
impl From<&Box<dyn CameraImpl>> for CameraItemRepr {
|
||||
fn from(camera: &Box<dyn CameraImpl>) -> Self {
|
||||
CameraItemRepr {
|
||||
id: device.id(),
|
||||
name: device.name(),
|
||||
vendor_id: format!("0x{:04x}", device.vendor_id()),
|
||||
product_id: format!("0x{:04x}", device.product_id()),
|
||||
id: camera.usb_id(),
|
||||
name: camera.id().name.to_string(),
|
||||
vendor_id: format!("0x{:04x}", camera.id().vendor),
|
||||
product_id: format!("0x{:04x}", camera.id().product),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,7 +104,7 @@ impl fmt::Display for CameraRepr {
|
||||
}
|
||||
|
||||
fn handle_info(json: bool, device_id: Option<&str>) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let camera = usb::get_camera(device_id)?;
|
||||
let mut camera = usb::get_camera(device_id)?;
|
||||
|
||||
let info = camera.get_info()?;
|
||||
let mode = camera.get_fuji_usb_mode()?;
|
||||
|
@@ -7,13 +7,37 @@ use serde::Serialize;
|
||||
|
||||
mod xt5;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct CameraId {
|
||||
pub name: &'static str,
|
||||
pub vendor: u16,
|
||||
pub product: u16,
|
||||
}
|
||||
|
||||
pub struct SupportedCamera {
|
||||
pub id: CameraId,
|
||||
pub factory: fn(
|
||||
rusb::Device<GlobalContext>,
|
||||
) -> Result<Box<dyn CameraImpl>, Box<dyn Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
pub const SUPPORTED_CAMERAS: &[SupportedCamera] = &[SupportedCamera {
|
||||
id: xt5::FUJIFILM_XT5,
|
||||
factory: |d| xt5::FujifilmXT5::new_boxed(d),
|
||||
}];
|
||||
|
||||
impl SupportedCamera {
|
||||
pub fn matches_descriptor(&self, descriptor: &DeviceDescriptor) -> bool {
|
||||
descriptor.vendor_id() == self.id.vendor && descriptor.product_id() == self.id.product
|
||||
}
|
||||
}
|
||||
|
||||
pub const TIMEOUT: Duration = Duration::from_millis(500);
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DevicePropCode {
|
||||
FujiUsbMode = 0xd16e,
|
||||
FujiBatteryInfo1 = 0xD36A,
|
||||
FujiBatteryInfo2 = 0xD36B,
|
||||
}
|
||||
|
||||
@@ -43,17 +67,30 @@ impl fmt::Display for FujiUsbMode {
|
||||
}
|
||||
|
||||
pub trait CameraImpl {
|
||||
fn name(&self) -> &'static str;
|
||||
fn id(&self) -> &'static CameraId;
|
||||
|
||||
fn usb_id(&self) -> String;
|
||||
|
||||
fn ptp(&self) -> libptp::Camera<GlobalContext>;
|
||||
|
||||
fn get_info(&self) -> Result<DeviceInfo, Box<dyn Error + Send + Sync>> {
|
||||
debug!("Sending GetDeviceInfo command");
|
||||
let response =
|
||||
self.ptp()
|
||||
.command(StandardCommandCode::GetDeviceInfo, &[], None, Some(TIMEOUT))?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
|
||||
let info = DeviceInfo::decode(&response)?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn next_session_id(&self) -> u32;
|
||||
|
||||
fn open_session(
|
||||
&self,
|
||||
ptp: &mut libptp::Camera<GlobalContext>,
|
||||
session_id: u32,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
fn open_session(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let session_id = self.next_session_id();
|
||||
|
||||
debug!("Opening new session with id {}", session_id);
|
||||
ptp.command(
|
||||
self.ptp().command(
|
||||
StandardCommandCode::OpenSession,
|
||||
&[session_id],
|
||||
None,
|
||||
@@ -63,34 +100,30 @@ pub trait CameraImpl {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close_session(
|
||||
&self,
|
||||
ptp: &mut libptp::Camera<GlobalContext>,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
fn close_session(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
debug!("Closing session");
|
||||
ptp.command(StandardCommandCode::CloseSession, &[], None, Some(TIMEOUT))?;
|
||||
self.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)?;
|
||||
self.open_session()?;
|
||||
|
||||
debug!("Getting property {:?}", prop);
|
||||
|
||||
let response = ptp.command(
|
||||
let response = self.ptp().command(
|
||||
StandardCommandCode::GetDevicePropValue,
|
||||
&[prop as u32],
|
||||
None,
|
||||
Some(TIMEOUT),
|
||||
);
|
||||
|
||||
self.close_session(ptp)?;
|
||||
self.close_session()?;
|
||||
|
||||
let response = response?;
|
||||
debug!("Received response with {} bytes", response.len());
|
||||
@@ -100,10 +133,9 @@ pub trait CameraImpl {
|
||||
|
||||
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)?;
|
||||
let data = self.get_prop_value_raw(prop)?;
|
||||
|
||||
match data.len() {
|
||||
1 => Ok(data[0] as u32),
|
||||
@@ -113,19 +145,13 @@ pub trait CameraImpl {
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
fn get_fuji_usb_mode(&self) -> Result<FujiUsbMode, Box<dyn Error + Send + Sync>> {
|
||||
let result = self.get_prop_value_scalar(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)?;
|
||||
fn get_fuji_battery_info(&self) -> Result<u32, Box<dyn Error + Send + Sync>> {
|
||||
let data = self.get_prop_value_raw(DevicePropCode::FujiBatteryInfo2)?;
|
||||
debug!("Raw battery data: {:?}", data);
|
||||
|
||||
if data.len() < 3 {
|
||||
@@ -151,44 +177,4 @@ pub trait CameraImpl {
|
||||
|
||||
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,28 +1,52 @@
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::{
|
||||
error::Error,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use rusb::GlobalContext;
|
||||
|
||||
use super::{CameraId, CameraImpl};
|
||||
|
||||
pub const FUJIFILM_XT5: CameraId = CameraId {
|
||||
name: "FUJIFILM XT-5",
|
||||
vendor: 0x04cb,
|
||||
product: 0x02fc,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FujifilmXT5 {
|
||||
device: rusb::Device<GlobalContext>,
|
||||
session_counter: AtomicU32,
|
||||
}
|
||||
|
||||
impl FujifilmXT5 {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
session_counter: AtomicU32::new(1),
|
||||
}
|
||||
pub fn new_boxed(
|
||||
rusb_device: rusb::Device<GlobalContext>,
|
||||
) -> Result<Box<dyn CameraImpl>, Box<dyn Error + Send + Sync>> {
|
||||
let session_counter = AtomicU32::new(1);
|
||||
|
||||
let handle = rusb_device.open()?;
|
||||
let device = handle.device();
|
||||
|
||||
Ok(Box::new(Self {
|
||||
session_counter,
|
||||
device,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl CameraImpl for FujifilmXT5 {
|
||||
fn name(&self) -> &'static str {
|
||||
"FUJIFILM X-T5"
|
||||
fn id(&self) -> &'static CameraId {
|
||||
&FUJIFILM_XT5
|
||||
}
|
||||
|
||||
fn usb_id(&self) -> String {
|
||||
let bus = self.device.bus_number();
|
||||
let address = self.device.address();
|
||||
format!("{}.{}", bus, address)
|
||||
}
|
||||
|
||||
fn ptp(&self) -> libptp::Camera<rusb::GlobalContext> {
|
||||
libptp::Camera::new(&self.device).unwrap()
|
||||
}
|
||||
|
||||
fn next_session_id(&self) -> u32 {
|
||||
|
@@ -1,60 +1,9 @@
|
||||
use std::error::Error;
|
||||
|
||||
use libptp::DeviceInfo;
|
||||
use rusb::GlobalContext;
|
||||
use crate::hardware::{CameraImpl, SUPPORTED_CAMERAS};
|
||||
|
||||
use crate::hardware::{FujiUsbMode, SUPPORTED_CAMERAS};
|
||||
|
||||
pub struct Camera {
|
||||
camera_impl: Box<dyn crate::hardware::CameraImpl>,
|
||||
rusb_device: rusb::Device<GlobalContext>,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn id(&self) -> String {
|
||||
let bus = self.rusb_device.bus_number();
|
||||
let address = self.rusb_device.address();
|
||||
format!("{}.{}", bus, address)
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.camera_impl.name().to_string()
|
||||
}
|
||||
|
||||
pub fn vendor_id(&self) -> u16 {
|
||||
let descriptor = self.rusb_device.device_descriptor().unwrap();
|
||||
descriptor.vendor_id()
|
||||
}
|
||||
|
||||
pub fn product_id(&self) -> u16 {
|
||||
let descriptor = self.rusb_device.device_descriptor().unwrap();
|
||||
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_camers() -> Result<Vec<Camera>, Box<dyn Error + Send + Sync>> {
|
||||
pub fn get_connected_camers()
|
||||
-> Result<Vec<Box<dyn crate::hardware::CameraImpl>>, Box<dyn Error + Send + Sync>> {
|
||||
let mut connected_cameras = Vec::new();
|
||||
|
||||
for device in rusb::devices()?.iter() {
|
||||
@@ -65,11 +14,7 @@ pub fn get_connected_camers() -> Result<Vec<Camera>, Box<dyn Error + Send + Sync
|
||||
|
||||
for camera in SUPPORTED_CAMERAS.iter() {
|
||||
if camera.matches_descriptor(&descriptor) {
|
||||
let camera = Camera {
|
||||
camera_impl: camera.into(),
|
||||
rusb_device: device,
|
||||
};
|
||||
|
||||
let camera = (camera.factory)(device)?;
|
||||
connected_cameras.push(camera);
|
||||
break;
|
||||
}
|
||||
@@ -79,7 +24,9 @@ pub fn get_connected_camers() -> Result<Vec<Camera>, Box<dyn Error + Send + Sync
|
||||
Ok(connected_cameras)
|
||||
}
|
||||
|
||||
pub fn get_connected_camera_by_id(id: &str) -> Result<Camera, Box<dyn Error + Send + Sync>> {
|
||||
pub fn get_connected_camera_by_id(
|
||||
id: &str,
|
||||
) -> Result<Box<dyn CameraImpl>, Box<dyn Error + Send + Sync>> {
|
||||
let parts: Vec<&str> = id.split('.').collect();
|
||||
if parts.len() != 2 {
|
||||
return Err(format!("Invalid device id format: {}", id).into());
|
||||
@@ -94,10 +41,8 @@ pub fn get_connected_camera_by_id(id: &str) -> Result<Camera, Box<dyn Error + Se
|
||||
|
||||
for camera in SUPPORTED_CAMERAS.iter() {
|
||||
if camera.matches_descriptor(&descriptor) {
|
||||
return Ok(Camera {
|
||||
camera_impl: camera.into(),
|
||||
rusb_device: device,
|
||||
});
|
||||
let camera = (camera.factory)(device)?;
|
||||
return Ok(camera);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +53,9 @@ pub fn get_connected_camera_by_id(id: &str) -> Result<Camera, Box<dyn Error + Se
|
||||
Err(format!("No device found with id: {}", id).into())
|
||||
}
|
||||
|
||||
pub fn get_camera(device_id: Option<&str>) -> Result<Camera, Box<dyn Error + Send + Sync>> {
|
||||
pub fn get_camera(
|
||||
device_id: Option<&str>,
|
||||
) -> Result<Box<dyn CameraImpl>, Box<dyn Error + Send + Sync>> {
|
||||
match device_id {
|
||||
Some(id) => get_connected_camera_by_id(id),
|
||||
None => get_connected_camers()?
|
||||
|
Reference in New Issue
Block a user