Skip to main content

ppa6/
lib.rs

1use std::{
2    fmt::{self, Debug, Display, Formatter},
3    time::Duration,
4};
5
6use anyhow::{bail, Context, Result};
7
8macro_rules! backends {
9	[$($(# [$($m:tt)*])? $mod:ident :: $name:ident),* $(,)?] => {
10		$(
11			$(# [$($m)*])*
12			mod $mod;
13			$(# [$($m)*])*
14			pub use crate::$mod::$name;
15		)*
16	};
17}
18
19backends![
20    #[cfg(feature = "usb")]
21    usb::UsbBackend,
22    #[cfg(feature = "file")]
23    file::FileBackend,
24];
25
26/// Printing backend.
27pub trait Backend {
28    /// Send data to the printer.
29    /// TODO: return number of bytes sent
30    fn send(&mut self, buf: &[u8], timeout: Duration) -> Result<()>;
31
32    /// Receive at most `buf.len()` bytes of data from the printer.
33    ///
34    /// # Return value
35    /// This functions the number of bytes received from the printer.
36    fn recv(&mut self, buf: &mut [u8], timeout: Duration) -> Result<usize>;
37}
38
39/// MAC Address, see [`Printer::get_mac()`].
40#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41pub struct MacAddr(pub [u8; 6]);
42
43/// PeriPage A6 printer.
44pub struct Printer {
45    backend: Box<dyn Backend>,
46}
47
48impl Printer {
49    /// Construct a new printer using `backend` as it's printing [`Backend`].
50    pub fn new(backend: impl Backend + 'static) -> Self {
51        Self {
52            backend: Box::new(backend),
53        }
54    }
55
56    /// Find any printer, connected using any backend.
57    pub fn find() -> Result<Self> {
58        #[cfg(feature = "usb")]
59        {
60            match crate::usb::UsbBackend::list() {
61                Ok(devs) => {
62                    if let Some(dev) = devs.first() {
63                        let backend = UsbBackend::open(dev)?;
64                        return Ok(Self::new(backend));
65                    }
66                }
67                Err(e) => log::error!("cannot get list of usb devices: {e}"),
68            }
69        }
70
71        bail!("no printer found");
72    }
73
74    fn send(&mut self, buf: &[u8], timeout: u64) -> Result<()> {
75        log::trace!("send({}{buf:x?}, {timeout}s);", buf.len());
76        self.backend.send(buf, Duration::from_secs(timeout))
77    }
78    fn recv(&mut self, buf: &mut [u8], timeout: u64) -> Result<usize> {
79        let n = self.backend.recv(buf, Duration::from_secs(timeout))?;
80        log::trace!("recv({}, {timeout}s): {n}{:x?}", buf.len(), &buf[0..n]);
81        Ok(n)
82    }
83    fn query(&mut self, cmd: &[u8]) -> Result<Vec<u8>> {
84        self.send(cmd, 3).context("failed to send request")?;
85        let mut buf = vec![0u8; 1024];
86        let n = self.recv(&mut buf, 3).context("failed receive response")?;
87        buf.truncate(n);
88        Ok(buf)
89    }
90    fn query_string(&mut self, cmd: &[u8]) -> Result<String> {
91        let buf = self.query(cmd)?;
92        let s = String::from_utf8_lossy(&buf);
93        Ok(s.into_owned())
94    }
95
96    /// Get printer's "IP" string.
97    pub fn get_ip(&mut self) -> Result<String> {
98        self.query_string(&[0x10, 0xff, 0x20, 0xf0])
99    }
100
101    /// Get printer's firmware version.
102    pub fn get_firmware_ver(&mut self) -> Result<String> {
103        self.query_string(&[0x10, 0xff, 0x20, 0xf1])
104    }
105
106    /// Get printer's serial number.
107    pub fn get_serial(&mut self) -> Result<String> {
108        self.query_string(&[0x10, 0xff, 0x20, 0xf2])
109    }
110
111    /// Get printer's hardware version.
112    pub fn get_hardware_ver(&mut self) -> Result<String> {
113        self.query_string(&[0x10, 0xff, 0x30, 0x10])
114    }
115
116    /// Get printer's name.
117    pub fn get_name(&mut self) -> Result<String> {
118        self.query_string(&[0x10, 0xff, 0x30, 0x11])
119    }
120
121    /// Get printer's MAC address.
122    /// TODO: Return a MacAddr struct i
123    pub fn get_mac(&mut self) -> Result<MacAddr> {
124        let buf = self.query(&[0x10, 0xff, 0x30, 0x12])?;
125        // for some reason the printer sends the MAC address twice
126        if buf.len() < 6 {
127            bail!(
128                "invalid MAC address response, got {} bytes: {:x?}",
129                buf.len(),
130                &buf
131            );
132        }
133        let mut mac = [0u8; 6];
134        mac.copy_from_slice(&buf[0..6]);
135        Ok(MacAddr(mac))
136    }
137
138    /// Get printer's battery state.
139    pub fn get_battery(&mut self) -> Result<u8> {
140        let buf = self.query(&[0x10, 0xff, 0x50, 0xf1])?;
141        if buf.len() != 2 {
142            bail!("invalid battery response");
143        }
144        Ok(buf[1])
145    }
146
147    /// Set printing concentration, valid values are between `0..=2`.
148    pub fn set_concentration(&mut self, c: u8) -> Result<()> {
149        if c > 2 {
150            bail!("invalid concentration: {c}");
151        }
152
153        self.send(&[0x10, 0xff, 0x10, 0x00, c], 1)
154    }
155
156    /// Reset the printer.
157    /// This command has to be sent, before printing can be done.
158    pub fn reset(&mut self) -> Result<()> {
159        let buf = [
160            0x10, 0xff, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
161            0x00, 0x00,
162        ];
163        self.send(&buf, 3)?;
164        let mut buf = [0u8; 128];
165        let _ = self.backend.recv(&mut buf, Duration::from_secs(1));
166        Ok(())
167    }
168
169    /// Print ASCII text.
170    /// Please don't use this, better use a font rasterizer, like [cosmic-text](https://docs.rs/cosmic-text).
171    ///
172    /// # Printer Bugs (PeriPage A6)
173    /// - Only ASCII, no Unicode
174    /// - No ASCII escape sequences, except '\n' (line feed)
175    /// - Line wrapping is very buggy, sometimes it works, sometimes it discards the rest of the line.
176    /// - No font size/weight settings
177    pub fn print_text(&mut self, text: &str) -> Result<()> {
178        let text: Vec<u8> = text
179            .chars()
180            .filter(|ch| matches!(ch, '\n' | '\x20'..='\x7f'))
181            .map(|ch| ch as u8)
182            .collect();
183
184        self.send(&text, 30)?;
185        Ok(())
186    }
187
188    /// Print raw pixels.
189    ///
190    /// # Overheating
191    /// The printer can overheat, if too much black is being printed at once,
192    /// therefore it's better to use the [`Printer::print_image_chunked()`] function instead.
193    ///
194    /// # Printing Limitations
195    /// While the printer has a density of 203dpi,
196    /// printing very small things and thin lines should be avoided,
197    /// as the printer is simply not precise enough.
198    ///
199    /// # "Concentration"
200    /// The printing concentration can be adjusted with [`Printer::set_concentration()`],
201    /// to make the output brighter or darker.
202    ///
203    /// # Format
204    /// TODO: describe pixel format:
205    /// - monochrome
206    /// - 0=white, 1=black
207    /// - MSB: left, LSB: right
208    /// - must be multiples of `width/8` bytes
209    /// - must not be longer than `65535` rows
210    /// - due to accuracy constraints, printing single pixels should be avoided
211    ///
212    /// # Notes
213    /// Printing gray scale pictures is possible,
214    /// by using [dithering](https://en.wikipedia.org/wiki/Dithering) to convert them to monochrome first.
215    /// Similarly, color images must be first converted to gray scale.
216    /// The [image](https://docs.rs/image/latest/image/) crate can be used, to do the conversions.
217    pub fn print_image(&mut self, pixels: &[u8], width: u16) -> Result<()> {
218        if width == 0 || width % 8 != 0 {
219            bail!("width must be non-zero and divisible by 8");
220        }
221
222        let n = pixels.len() * 8;
223        let w = width as usize;
224        let h = n / w;
225
226        if h > 0xff {
227            bail!("document too long");
228        }
229
230        if pixels.len() != (w * h / 8) {
231            bail!("invalid length of pixels: {}", pixels.len());
232        }
233
234        let rs = w / 8;
235
236        let mut packet = vec![
237            0x1d,
238            0x76,
239            0x30,
240            (rs >> 8) as u8,
241            (rs & 0xff) as u8,
242            0x00,
243            h as u8,
244            0x00,
245        ];
246        packet.extend_from_slice(pixels);
247        self.send(&packet, 60)?;
248
249        // no idea what this does, but the Windows driver sends this after every print.
250        self.send(&[0x10, 0xff, 0xfe, 0x45], 1)?;
251        Ok(())
252    }
253
254    /// Just like [`Printer::print_image()`], but breaks the pixels into rows of `chunk_height`.
255    /// This may be needed, to prevent the printer from overheating, while printing a long document.
256    pub fn print_image_chunked_ext(
257        &mut self,
258        pixels: &[u8],
259        width: u16,
260        chunk_height: u16,
261        delay: Duration,
262    ) -> Result<()> {
263        pixels
264            .chunks(width as usize * chunk_height as usize / 8)
265            .try_for_each(|chunk| {
266                self.print_image(chunk, width)?;
267                std::thread::sleep(delay);
268                Ok(())
269            })
270    }
271
272    pub fn print_image_chunked(&mut self, pixels: &[u8], width: u16) -> Result<()> {
273        self.print_image_chunked_ext(pixels, width, 24, Duration::from_millis(50))
274    }
275
276    /// Push out `num` rows of paper.
277    pub fn push(&mut self, num: u8) -> Result<()> {
278        self.send(&[0x1b, 0x4a, num], 5)?;
279        Ok(())
280    }
281}
282
283impl Display for MacAddr {
284    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
285        let [x0, x1, x2, x3, x4, x5] = self.0;
286        write!(f, "{x0:02x}:{x1:02x}:{x2:02x}:{x3:02x}:{x4:02x}:{x5:02x}")
287    }
288}
289
290impl Debug for MacAddr {
291    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
292        <Self as Display>::fmt(self, f)
293    }
294}