ptouch/
lib.rs

1//! Rust PTouch Driver / Utility
2//
3// https://github.com/ryankurte/rust-ptouch
4// Copyright 2021 Ryan Kurte
5
6use std::time::Duration;
7
8use commands::Commands;
9use device::Status;
10use image::ImageError;
11use log::{trace, debug, error};
12
13#[cfg(feature = "structopt")]
14use structopt::StructOpt;
15
16#[cfg(feature = "strum")]
17use strum::VariantNames;
18
19use rusb::{Context, Device, DeviceDescriptor, DeviceHandle, Direction, TransferType, UsbContext};
20
21pub mod device;
22use device::*;
23
24pub mod commands;
25
26pub mod bitmap;
27
28pub mod tiff;
29
30pub mod render;
31
32/// PTouch device instance
33pub struct PTouch {
34    _device: Device<Context>,
35    handle: DeviceHandle<Context>,
36    descriptor: DeviceDescriptor,
37    //endpoints: Endpoints,
38    timeout: Duration,
39
40    cmd_ep: u8,
41    stat_ep: u8,
42}
43
44/// Brother USB Vendor ID
45pub const BROTHER_VID: u16 = 0x04F9;
46
47/// Options for connecting to a PTouch device
48#[derive(Clone, PartialEq, Debug)]
49#[cfg_attr(feature = "structopt", derive(StructOpt))]
50pub struct Options {
51    #[cfg_attr(feature = "structopt", structopt(long, possible_values = &device::PTouchDevice::VARIANTS, default_value = "pt-p710bt"))]
52    /// Label maker device kind
53    pub device: device::PTouchDevice,
54
55    #[cfg_attr(feature = "structopt", structopt(long, default_value = "0"))]
56    /// Index (if multiple devices are connected)
57    pub index: usize,
58
59    #[cfg_attr(feature = "structopt", structopt(long, default_value = "500"))]
60    /// Timeout to pass to the read_bulk and write_bulk methods
61    pub timeout_milliseconds: u64,
62
63    #[cfg_attr(feature = "structopt", structopt(long, hidden = true))]
64    /// Do not reset the device on connect
65    pub no_reset: bool,
66
67    #[cfg_attr(feature = "structopt", structopt(long, hidden = true))]
68    /// (DEBUG) Do not claim USB interface on connect
69    pub usb_no_claim: bool,
70
71    #[cfg_attr(feature = "structopt", structopt(long, hidden = true))]
72    /// (DEBUG) Do not detach from kernel drivers on connect
73    pub usb_no_detach: bool,
74
75    #[cfg_attr(feature = "structopt", structopt(long))]
76    /// If true, the program will not perform a status request
77    pub no_status_fetch: bool,
78}
79
80// Lazy initialised libusb context
81lazy_static::lazy_static! {
82    static ref CONTEXT: Context = {
83        Context::new().unwrap()
84    };
85}
86
87/// PTouch API errors
88#[derive(thiserror::Error, Debug)]
89pub enum Error {
90    #[error("USB error: {:?}", 0)]
91    Usb(rusb::Error),
92
93    #[error("IO error: {:?}", 0)]
94    Io(std::io::Error),
95
96    #[error("Image error: {:?}", 0)]
97    Image(ImageError),
98
99    #[error("Invalid device index")]
100    InvalidIndex,
101
102    #[error("No supported languages")]
103    NoLanguages,
104
105    #[error("Unable to locate expected endpoints")]
106    InvalidEndpoints,
107
108    #[error("Renderer error")]
109    Render,
110
111    #[error("Operation timeout")]
112    Timeout,
113
114    #[error("PTouch Error ({:?} {:?})", 0, 1)]
115    PTouch(Error1, Error2),
116}
117
118impl From<std::io::Error> for Error {
119    fn from(e: std::io::Error) -> Self {
120        Error::Io(e)
121    }
122}
123
124impl From<rusb::Error> for Error {
125    fn from(e: rusb::Error) -> Self {
126        Error::Usb(e)
127    }
128}
129
130impl From<ImageError> for Error {
131    fn from(e: ImageError) -> Self {
132        Error::Image(e)
133    }
134}
135
136/// PTouch device information
137#[derive(Clone, Debug, PartialEq)]
138pub struct Info {
139    pub manufacturer: String,
140    pub product: String,
141    pub serial: String,
142}
143
144impl PTouch {
145    /// Create a new PTouch driver with the provided USB options
146    pub fn new(o: &Options) -> Result<Self, Error> {
147        Self::new_with_context(o, &CONTEXT)
148    }
149
150    /// Create a new PTouch driver with the provided USB options and an existing rusb::Context
151    pub fn new_with_context(o: &Options, context: &Context) -> Result<Self, Error> {
152        // List available devices
153        let devices = context.devices()?;
154
155        // Find matching VID/PIDs
156        let mut matches: Vec<_> = devices
157            .iter()
158            .filter_map(|d| {
159                // Fetch device descriptor
160                let desc = match d.device_descriptor() {
161                    Ok(d) => d,
162                    Err(e) => {
163                        debug!("Could not fetch descriptor for device {:?}: {:?}", d, e);
164                        return None;
165                    }
166                };
167
168                // Return devices matching vid/pid filters
169                if desc.vendor_id() == BROTHER_VID && desc.product_id() == o.device as u16 {
170                    Some((d, desc))
171                } else {
172                    None
173                }
174            })
175            .collect();
176
177        // Check index is valid
178        if matches.len() < o.index || matches.len() == 0 {
179            debug!(
180                "Device index ({}) exceeds number of discovered devices ({})",
181                o.index,
182                matches.len()
183            );
184            return Err(Error::InvalidIndex);
185        }
186
187        debug!("Found matching devices: {:?}", matches);
188
189        // Fetch matching device
190        let (device, descriptor) = matches.remove(o.index);
191
192        // Open device handle
193        let mut handle = match device.open() {
194            Ok(v) => v,
195            Err(e) => {
196                debug!("Error opening device");
197                return Err(e.into());
198            }
199        };
200
201        // Reset device
202        if let Err(e) = handle.reset() {
203            debug!("Error resetting device handle");
204            return Err(e.into())
205        }
206
207        // Locate endpoints
208        let config_desc = match device.config_descriptor(0) {
209            Ok(v) => v,
210            Err(e) => {
211                debug!("Failed to fetch config descriptor");
212                return Err(e.into());
213            }
214        };
215
216        let interface = match config_desc.interfaces().next() {
217            Some(i) => i,
218            None => {
219                debug!("No interfaces found");
220                return Err(Error::InvalidEndpoints);
221            }
222        };
223
224        // EP1 is a bulk IN (printer -> PC) endpoint for status messages
225        // EP2 is a bulk OUT (PC -> printer) endpoint for print commands
226        // TODO: is this worth it, could we just, hard-code the endpoints?
227        let (mut cmd_ep, mut stat_ep) = (None, None);
228
229        for interface_desc in interface.descriptors() {
230            for endpoint_desc in interface_desc.endpoint_descriptors() {
231                // Find the relevant endpoints
232                match (endpoint_desc.transfer_type(), endpoint_desc.direction()) {
233                    (TransferType::Bulk, Direction::In) => stat_ep = Some(endpoint_desc.address()),
234                    (TransferType::Bulk, Direction::Out) => cmd_ep = Some(endpoint_desc.address()),
235                    (_, _) => continue,
236                }
237            }
238        }
239
240        let (cmd_ep, stat_ep) = match (cmd_ep, stat_ep) {
241            (Some(cmd), Some(stat)) => (cmd, stat),
242            _ => {
243                debug!("Failed to locate command and status endpoints");
244                return Err(Error::InvalidEndpoints);
245            }
246        };
247
248        // Detach kernel driver
249        // TODO: this is usually not supported on all libusb platforms
250        // for now this is enabled through hidden config options...
251        // needs testing and a cfg guard as appropriate
252        debug!("Checking for active kernel driver");
253        match handle.kernel_driver_active(interface.number())? {
254            true => {
255                if !o.usb_no_detach {
256                    debug!("Detaching kernel driver");
257                    handle.detach_kernel_driver(interface.number())?;
258                } else {
259                    debug!("Kernel driver detach disabled");
260                }
261            },
262            false => {
263                debug!("Kernel driver inactive");
264            },
265        }
266
267        // Claim interface for driver
268        // TODO: this is usually not supported on all libusb platforms
269        // for now this is enabled through hidden config options...
270        // needs testing and a cfg guard as appropriate
271        if !o.usb_no_claim {
272            debug!("Claiming interface");
273            handle.claim_interface(interface.number())?;
274        } else {
275            debug!("Claim interface disabled");
276        }
277
278        // Create device object
279        let mut s = Self {
280            _device: device,
281            handle,
282            descriptor,
283            cmd_ep,
284            stat_ep,
285            timeout: Duration::from_millis(o.timeout_milliseconds),
286        };
287
288        // Unless we're skipping reset
289        if !o.no_reset {
290            // Send invalidate to reset device
291            s.invalidate()?;
292            // Initialise device
293            s.init()?;
294        } else {
295            debug!("Skipping device reset");
296        }
297
298        Ok(s)
299    }
300
301    /// Fetch device information
302    pub fn info(&mut self) -> Result<Info, Error> {
303        let timeout = Duration::from_millis(200);
304
305        // Fetch base configuration
306        let languages = self.handle.read_languages(timeout)?;
307        let active_config = self.handle.active_configuration()?;
308
309        trace!("Active configuration: {}", active_config);
310        trace!("Languages: {:?}", languages);
311
312        // Check a language is available
313        if languages.len() == 0 {
314            return Err(Error::NoLanguages);
315        }
316
317        // Fetch information
318        let language = languages[0];
319        let manufacturer =
320            self.handle
321                .read_manufacturer_string(language, &self.descriptor, timeout)?;
322        let product = self
323            .handle
324            .read_product_string(language, &self.descriptor, timeout)?;
325        let serial = self
326            .handle
327            .read_serial_number_string(language, &self.descriptor, timeout)?;
328
329        Ok(Info {
330            manufacturer,
331            product,
332            serial,
333        })
334    }
335
336    /// Fetch the device status
337    pub fn status(&mut self) -> Result<Status, Error> {
338        // Issue status request
339        self.status_req()?;
340
341        // Read status response
342        let d = self.read(self.timeout)?;
343
344        // Convert to status object
345        let s = Status::from(d);
346
347        debug!("Status: {:02x?}", s);
348
349        Ok(s)
350    }
351
352    /// Setup the printer and print using raw raster data.
353    /// Print output must be shifted and in the correct bit-order for this function.
354    /// 
355    /// TODO: this is too low level of an interface, should be replaced with higher-level apis
356    pub fn print_raw(&mut self, data: Vec<[u8; 16]>, info: &PrintInfo) -> Result<(), Error> {
357        // TODO: should we check info (and size) match status here?
358
359
360        // Print sequence from raster guide Section 2.1
361        // 1. Set to raster mode
362        self.switch_mode(Mode::Raster)?;
363
364        // 2. Enable status notification
365        self.set_status_notify(true)?;
366
367        // 3. Set print information (media type etc.)
368        self.set_print_info(info)?;
369
370        // 4. Set various mode settings
371        self.set_various_mode(VariousMode::AUTO_CUT)?;
372
373        // 5. Specify page number in "cut each * labels"
374        // Note this is not supported on the PT-P710BT
375        // TODO: add this for other printers
376
377        // 6. Set advanced mode settings
378        self.set_advanced_mode(AdvancedMode::NO_CHAIN)?;
379
380        // 7. Specify margin amount
381        // TODO: based on what?
382        self.set_margin(0)?;
383
384        // 8. Set compression mode
385        // TODO: fix broken TIFF mode and add compression flag
386        self.set_compression_mode(CompressionMode::None)?;
387
388        // Send raster data
389        for line in data {
390            // TODO: re-add when TIFF mode issues resolved
391            //let l = tiff::compress(&line);
392
393            self.raster_transfer(&line)?;
394        }
395
396        // Execute print operation
397        self.print_and_feed()?;
398
399
400        // Poll on print completion
401        let mut i = 0;
402        loop {
403            if let Ok(s) = self.read_status(self.timeout) {
404                if !s.error1.is_empty() || !s.error2.is_empty() {
405                    debug!("Print error: {:?} {:?}", s.error1, s.error2);
406                    return Err(Error::PTouch(s.error1, s.error2));
407                }
408    
409                if s.status_type == DeviceStatus::PhaseChange {
410                    debug!("Started printing");
411                }
412
413                if s.status_type == DeviceStatus::Completed {
414                    debug!("Print completed");
415                    break;
416                }
417            }
418
419            if i > 10 {
420                debug!("Print timeout");
421                return Err(Error::Timeout);
422            }
423
424            i += 1;
425
426            std::thread::sleep(Duration::from_secs(1));
427        }
428
429
430        Ok(())
431    }
432
433    /// Read from status EP (with specified timeout)
434    fn read(&mut self, timeout: Duration) -> Result<[u8; 32], Error> {
435        let mut buff = [0u8; 32];
436
437        // Execute read
438        let n = self.handle.read_bulk(self.stat_ep, &mut buff, timeout)?;
439
440        if n != 32 {
441            return Err(Error::Timeout)
442        }
443
444        // TODO: parse out status?
445
446        Ok(buff)
447    }
448
449    /// Write to command EP (with specified timeout)
450    fn write(&mut self, data: &[u8], timeout: Duration) -> Result<(), Error> {
451        debug!("WRITE: {:02x?}", data);
452
453        // Execute write
454        let n = self.handle.write_bulk(self.cmd_ep, &data, timeout)?;
455
456        // Check write length for timeouts
457        if n != data.len() {
458            return Err(Error::Timeout)
459        }
460
461        Ok(())
462    }
463}