Skip to main content

hardless_http_dialects/
lib.rs

1#![doc = include_str!("../README.md")]
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4
5/// This crate's README, which holds the request and response reference for both dialects.
6/// The MCP server's `hardless://docs/http-dialects` resource and the docs site read it, so
7/// the reference has one source.
8pub const REFERENCE: &str = include_str!("../README.md");
9
10pub mod epos;
11mod escpos;
12pub mod webprnt;
13mod xml;
14
15use std::fmt;
16
17/// Largest request document accepted, in bytes. Real receipts with images stay far below it.
18pub const MAX_DOCUMENT_BYTES: usize = 8 * 1024 * 1024;
19
20/// A print request translated to ESC/POS (FR-31). Feeding [`PrintRequest::escpos`] to an
21/// ESC/POS printer gives the same command AST as printing over Web Serial.
22#[derive(Clone, Debug, Default, PartialEq, Eq)]
23pub struct PrintRequest {
24    /// The equivalent ESC/POS bytes. They start with `ESC @`: each request starts from the
25    /// printer's power-on settings.
26    pub escpos: Vec<u8>,
27    /// Parts of the request the emulator does not reproduce (a buzzer, page mode, ...), for
28    /// the event log.
29    pub warnings: Vec<String>,
30}
31
32/// What a request document could not be understood for. Both dialects answer it as a
33/// schema error.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct DialectError(String);
36
37impl DialectError {
38    pub(crate) fn new(message: impl Into<String>) -> Self {
39        Self(message.into())
40    }
41}
42
43impl fmt::Display for DialectError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str(&self.0)
46    }
47}
48
49impl std::error::Error for DialectError {}
50
51/// The printer's condition after a print, as both dialects report it.
52///
53/// Read it back from the printer's own `DLE EOT` replies with
54/// [`PrinterStatus::from_dle_eot`], so faults and the drawer setting count exactly as they do
55/// for Web Serial.
56///
57/// ```
58/// use hardless_http_dialects::PrinterStatus;
59/// // DLE EOT 1, 2 and 4 replies of a printer that is out of paper.
60/// let status = PrinterStatus::from_dle_eot(0x1A, 0x32, 0x7E);
61/// assert!(status.paper_end && status.paper_near_end && !status.online);
62/// ```
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
64pub struct PrinterStatus {
65    /// The printer is online (not stopped by paper end or an open cover).
66    pub online: bool,
67    /// The cover is open.
68    pub cover_open: bool,
69    /// The roll is empty.
70    pub paper_end: bool,
71    /// The roll is nearly empty.
72    pub paper_near_end: bool,
73    /// The drawer-kick connector's pin 3 (the drawer sensor) reads high.
74    pub drawer_pin3_high: bool,
75}
76
77impl PrinterStatus {
78    /// From the replies to `DLE EOT 1` (printer), `DLE EOT 2` (offline cause) and `DLE EOT 4`
79    /// (paper sensor), as defined in the ESC/POS command reference.
80    pub fn from_dle_eot(printer: u8, offline_cause: u8, paper: u8) -> Self {
81        Self {
82            online: printer & 0x08 == 0,
83            drawer_pin3_high: printer & 0x04 != 0,
84            cover_open: offline_cause & 0x04 != 0,
85            paper_end: paper & 0x60 != 0,
86            paper_near_end: paper & 0x0C != 0,
87        }
88    }
89}
90
91/// How a print request turned out.
92#[derive(Clone, Debug, PartialEq, Eq)]
93pub enum Outcome {
94    /// Printed.
95    Printed,
96    /// Not printed: the roll is empty.
97    PaperEnd,
98    /// Not printed: the cover is open.
99    CoverOpen,
100    /// Not printed in time: the request's timeout passed.
101    Timeout,
102    /// The request document could not be understood.
103    BadDocument(String),
104    /// No printer with the requested device ID.
105    DeviceNotFound,
106}
107
108impl Outcome {
109    /// The outcome of a print from the printer's status afterwards: paper end and an open
110    /// cover stop a printer, so the print did not happen.
111    pub fn after_print(status: &PrinterStatus) -> Self {
112        if status.paper_end {
113            Outcome::PaperEnd
114        } else if status.cover_open {
115            Outcome::CoverOpen
116        } else {
117            Outcome::Printed
118        }
119    }
120
121    /// Whether the document printed.
122    pub fn printed(&self) -> bool {
123        matches!(self, Outcome::Printed)
124    }
125}