Skip to main content

psdk_fruit_emapi/
printer.rs

1use std::time::Duration;
2
3use psdk_father::device::ConnectedDevice;
4
5use crate::{
6  chunker::{chunk_print_data, file_transfer_payload_capacity, print_transfer_payload_capacity},
7  command::EmapiCommand,
8  connection::EmapiConnection,
9  constants::{
10    CHILD_DEVICE_INFO, CHILD_FILE_END, CHILD_FILE_START, CHILD_FILE_TRANSFER, CHILD_FILE_UPGRADE,
11    CHILD_PRINTER_PARAMS, CHILD_PRINT_SELF_TEST_PAGE, CHILD_PRINT_STATUS,
12    CHILD_RFID_AUTH_FAILURE_HANDLING, CHILD_RFID_CARD_INFO, CHILD_RFID_PAPER_LENGTH,
13    CHILD_RFID_UID, CHILD_SET_SHUTDOWN_TIME, CHILD_SLEEP_SHUTDOWN, CHILD_TRANSFER_DATA,
14    CHILD_WIFI_CONFIG, CHILD_WIFI_CONNECTION_STATE, CHILD_WIFI_FILE_END, CHILD_WIFI_FILE_START,
15    CHILD_WIFI_FILE_TRANSFER, CHILD_WIFI_HOTSPOT_INFO, PARENT_FILE, PARENT_PRINTER, PARENT_RFID,
16    PARENT_SYSTEM, PARENT_WIFI, TAG_MTU, TYPE_PASSTHROUGH_REQUEST, TYPE_PASSTHROUGH_RESPONSE,
17    TYPE_REQUEST, TYPE_RESPONSE,
18  },
19  device_connection::ConnectedDeviceEmapiConnection,
20  endian::read_int16,
21  error::{EmapiError, Result},
22  models::{
23    EmapiPrintStatus, EmapiPrinterParams, EmapiReport, EmapiRfidAuthFailurePolicy,
24    EmapiRfidCardInfo, EmapiWifiConnectionState, EmapiWifiHotspotInfo,
25  },
26  payload::EmapiPayload,
27  printer_info::EmapiPrinterInfo,
28  session::EmapiSession,
29  tlv::{Tlv, TlvData, TlvEntry},
30};
31
32pub struct EmapiPrinter<C> {
33  session: EmapiSession<C>,
34  fallback_mtu: usize,
35  mtu: Option<usize>,
36}
37
38impl<C> EmapiPrinter<C>
39where
40  C: EmapiConnection,
41{
42  pub fn new(connection: C) -> Self {
43    Self {
44      session: EmapiSession::new(connection),
45      fallback_mtu: 512,
46      mtu: None,
47    }
48  }
49
50  pub fn with_timeout(mut self, timeout: Duration) -> Self {
51    self.session.set_timeout(timeout);
52    self
53  }
54
55  pub fn with_max_retries(mut self, max_retries: usize) -> Result<Self> {
56    self.session.set_max_retries(max_retries);
57    Ok(self)
58  }
59
60  pub fn with_mtu(mut self, mtu: usize) -> Result<Self> {
61    if mtu == 0 {
62      return Err(EmapiError::Protocol {
63        message: "mtu must be greater than zero".to_string(),
64      });
65    }
66    self.mtu = Some(mtu);
67    Ok(self)
68  }
69
70  pub fn with_fallback_mtu(mut self, fallback_mtu: usize) -> Result<Self> {
71    if fallback_mtu == 0 {
72      return Err(EmapiError::Protocol {
73        message: "fallbackMtu must be greater than zero".to_string(),
74      });
75    }
76    self.fallback_mtu = fallback_mtu;
77    Ok(self)
78  }
79
80  pub fn reports(&self) -> &[EmapiReport] {
81    self.session.reports()
82  }
83
84  pub fn read_report(&mut self) -> Result<EmapiReport> {
85    self.session.read_report()
86  }
87
88  pub fn into_inner(self) -> C {
89    self.session.into_inner()
90  }
91
92  pub fn query_device_info(&mut self) -> Result<EmapiPrinterInfo> {
93    let response = self.send_command(
94      TYPE_REQUEST,
95      TYPE_RESPONSE,
96      PARENT_SYSTEM,
97      CHILD_DEVICE_INFO,
98      Vec::new(),
99      false,
100    )?;
101    let tlv = Tlv::decode(&response.payload)?;
102    let info = EmapiPrinterInfo {
103      device_type: tlv.string(0x01)?,
104      device_model: tlv.string(0x02)?,
105      brand: tlv.string(0x03)?,
106      serial_number: tlv.string(0x04)?,
107      hardware_version: tlv.string(0x05)?,
108      software_version: tlv.string(0x06)?,
109      boot_version: tlv.string(0x07)?,
110      mtu: tlv.uint16(TAG_MTU)?,
111    };
112    if let Some(mtu) = info.mtu {
113      self.mtu = Some(usize::from(mtu));
114    }
115    Ok(info)
116  }
117
118  pub fn sleep_shutdown(&mut self) -> Result<()> {
119    self.send_acked(
120      TYPE_REQUEST,
121      TYPE_RESPONSE,
122      PARENT_SYSTEM,
123      CHILD_SLEEP_SHUTDOWN,
124      [],
125    )
126  }
127
128  pub fn set_shutdown_time(&mut self, minutes: u16) -> Result<()> {
129    self.send_acked(
130      TYPE_REQUEST,
131      TYPE_RESPONSE,
132      PARENT_SYSTEM,
133      CHILD_SET_SHUTDOWN_TIME,
134      EmapiPayload::uint16(minutes),
135    )
136  }
137
138  pub fn query_rfid_uid(&mut self) -> Result<String> {
139    let response = self.send_command(
140      TYPE_REQUEST,
141      TYPE_RESPONSE,
142      PARENT_RFID,
143      CHILD_RFID_UID,
144      Vec::new(),
145      false,
146    )?;
147    String::from_utf8(response.payload).map_err(|_| EmapiError::InvalidUtf8)
148  }
149
150  pub fn query_rfid_card_info(&mut self) -> Result<EmapiRfidCardInfo> {
151    let response = self.send_command(
152      TYPE_REQUEST,
153      TYPE_RESPONSE,
154      PARENT_RFID,
155      CHILD_RFID_CARD_INFO,
156      Vec::new(),
157      false,
158    )?;
159    let tlv = Tlv::decode(&response.payload)?;
160    Ok(EmapiRfidCardInfo {
161      paper_model: tlv.string(0x01)?,
162      paper_length: tlv.string(0x02)?,
163      paper_width: tlv.string(0x03)?,
164      paper_color: tlv.string(0x04)?,
165      paper_material_number: tlv.string(0x05)?,
166    })
167  }
168
169  pub fn query_rfid_paper_length(&mut self) -> Result<u32> {
170    let response = self.send_command(
171      TYPE_REQUEST,
172      TYPE_RESPONSE,
173      PARENT_RFID,
174      CHILD_RFID_PAPER_LENGTH,
175      Vec::new(),
176      false,
177    )?;
178    EmapiPayload::read_uint32(&response.payload)
179  }
180
181  pub fn set_rfid_auth_failure_handling(
182    &mut self,
183    policy: EmapiRfidAuthFailurePolicy,
184  ) -> Result<()> {
185    self.send_acked(
186      TYPE_REQUEST,
187      TYPE_RESPONSE,
188      PARENT_RFID,
189      CHILD_RFID_AUTH_FAILURE_HANDLING,
190      EmapiPayload::uint8(if policy == EmapiRfidAuthFailurePolicy::ForbidPrint {
191        0x01
192      } else {
193        0x00
194      }),
195    )
196  }
197
198  pub fn set_wifi_config(
199    &mut self,
200    ssid: &str,
201    password: &str,
202    encryption_method: Option<u8>,
203  ) -> Result<()> {
204    let mut entries = vec![
205      TlvEntry::string(0x01, ssid)?,
206      TlvEntry::string(0x02, password)?,
207    ];
208    if let Some(encryption_method) = encryption_method {
209      entries.push(TlvEntry::uint8(0x03, encryption_method)?);
210    }
211    self.send_acked(
212      TYPE_PASSTHROUGH_REQUEST,
213      TYPE_PASSTHROUGH_RESPONSE,
214      PARENT_WIFI,
215      CHILD_WIFI_CONFIG,
216      Tlv::encode(&entries)?,
217    )
218  }
219
220  pub fn query_wifi_connection_state(&mut self) -> Result<EmapiWifiConnectionState> {
221    let response = self.send_command(
222      TYPE_PASSTHROUGH_REQUEST,
223      TYPE_PASSTHROUGH_RESPONSE,
224      PARENT_WIFI,
225      CHILD_WIFI_CONNECTION_STATE,
226      Vec::new(),
227      false,
228    )?;
229    match response.payload.first().copied() {
230      Some(0x00) => Ok(EmapiWifiConnectionState::NotConnected),
231      Some(0x01) => Ok(EmapiWifiConnectionState::HotspotConnected),
232      Some(0x02) => Ok(EmapiWifiConnectionState::IotConnected),
233      Some(_) => Ok(EmapiWifiConnectionState::Unknown),
234      None => Err(EmapiError::Protocol {
235        message: "missing WIFI connection state payload".to_string(),
236      }),
237    }
238  }
239
240  pub fn query_wifi_hotspot_info(&mut self) -> Result<EmapiWifiHotspotInfo> {
241    let response = self.send_command(
242      TYPE_PASSTHROUGH_REQUEST,
243      TYPE_PASSTHROUGH_RESPONSE,
244      PARENT_WIFI,
245      CHILD_WIFI_HOTSPOT_INFO,
246      Vec::new(),
247      false,
248    )?;
249    let tlv = Tlv::decode(&response.payload)?;
250    Ok(EmapiWifiHotspotInfo {
251      ssid: tlv.string(0x01)?,
252      rssi: tlv_int16(&tlv, 0x02)?,
253      ip: tlv.string(0x03)?,
254      port: tlv.string(0x04)?,
255    })
256  }
257
258  pub fn start_wifi_file_download(&mut self, file_type: u16, total_size: u32) -> Result<()> {
259    self.send_acked(
260      TYPE_PASSTHROUGH_REQUEST,
261      TYPE_PASSTHROUGH_RESPONSE,
262      PARENT_WIFI,
263      CHILD_WIFI_FILE_START,
264      file_start_payload(file_type, total_size)?,
265    )
266  }
267
268  pub fn transfer_wifi_file_download_chunk(&mut self, index: u32, data: &[u8]) -> Result<()> {
269    self.validate_file_transfer_chunk(data)?;
270    self.send_acked(
271      TYPE_PASSTHROUGH_REQUEST,
272      TYPE_PASSTHROUGH_RESPONSE,
273      PARENT_WIFI,
274      CHILD_WIFI_FILE_TRANSFER,
275      EmapiPayload::file_chunk(index, data)?,
276    )
277  }
278
279  pub fn finish_wifi_file_download(&mut self) -> Result<()> {
280    self.send_acked(
281      TYPE_PASSTHROUGH_REQUEST,
282      TYPE_PASSTHROUGH_RESPONSE,
283      PARENT_WIFI,
284      CHILD_WIFI_FILE_END,
285      [],
286    )
287  }
288
289  pub fn query_printer_params(&mut self) -> Result<EmapiPrinterParams> {
290    let response = self.send_command(
291      TYPE_REQUEST,
292      TYPE_RESPONSE,
293      PARENT_PRINTER,
294      CHILD_PRINTER_PARAMS,
295      Vec::new(),
296      false,
297    )?;
298    let tlv = Tlv::decode(&response.payload)?;
299    Ok(EmapiPrinterParams {
300      print_size: tlv.uint32(0x01)?,
301      resolution: tlv.uint32(0x02)?,
302      dots_per_byte: tlv.uint32(0x03)?,
303    })
304  }
305
306  pub fn query_print_status(&mut self) -> Result<EmapiPrintStatus> {
307    let response = self.send_command(
308      TYPE_REQUEST,
309      TYPE_RESPONSE,
310      PARENT_PRINTER,
311      CHILD_PRINT_STATUS,
312      Vec::new(),
313      false,
314    )?;
315    let tlv = Tlv::decode(&response.payload)?;
316    Ok(EmapiPrintStatus {
317      paper_status: tlv.uint8(0x01)?,
318      cover_status: tlv.uint8(0x02)?,
319      low_battery: tlv.uint8(0x03)?,
320      overheat: tlv.uint8(0x04)?,
321      battery_percent: tlv.uint8(0x05)?,
322      battery_voltage: tlv.uint16(0x06)?,
323      tph_temperature: tlv.uint8(0x07)?.map(|value| value as i8),
324    })
325  }
326
327  pub fn print_self_test_page(&mut self) -> Result<()> {
328    self.send_acked(
329      TYPE_REQUEST,
330      TYPE_RESPONSE,
331      PARENT_PRINTER,
332      CHILD_PRINT_SELF_TEST_PAGE,
333      [],
334    )
335  }
336
337  pub fn start_main_controller_ota(&mut self, file_type: u16, total_size: u32) -> Result<()> {
338    self.send_acked(
339      TYPE_REQUEST,
340      TYPE_RESPONSE,
341      PARENT_FILE,
342      CHILD_FILE_START,
343      file_start_payload(file_type, total_size)?,
344    )
345  }
346
347  pub fn start_main_controller_ota_default(&mut self, total_size: u32) -> Result<()> {
348    self.start_main_controller_ota(0x0001, total_size)
349  }
350
351  pub fn transfer_main_controller_ota_chunk(&mut self, index: u32, data: &[u8]) -> Result<()> {
352    self.validate_file_transfer_chunk(data)?;
353    self.send_acked(
354      TYPE_REQUEST,
355      TYPE_RESPONSE,
356      PARENT_FILE,
357      CHILD_FILE_TRANSFER,
358      EmapiPayload::file_chunk(index, data)?,
359    )
360  }
361
362  pub fn finish_main_controller_ota(&mut self) -> Result<()> {
363    self.send_acked(TYPE_REQUEST, TYPE_RESPONSE, PARENT_FILE, CHILD_FILE_END, [])
364  }
365
366  pub fn upgrade_main_controller(&mut self) -> Result<()> {
367    self.send_acked(
368      TYPE_REQUEST,
369      TYPE_RESPONSE,
370      PARENT_FILE,
371      CHILD_FILE_UPGRADE,
372      [],
373    )
374  }
375
376  pub fn print_esc(&mut self, data: &[u8]) -> Result<()> {
377    let mtu = self.ensure_mtu()?;
378    print_transfer_payload_capacity(mtu).map_err(|_| EmapiError::Protocol {
379      message: format!("invalid max packet length for print transfer: {mtu}"),
380    })?;
381    for chunk in chunk_print_data(data, mtu)? {
382      self.session.send_and_wait(
383        EmapiCommand::new(TYPE_REQUEST, PARENT_PRINTER, CHILD_TRANSFER_DATA, chunk),
384        |command| command.is_ack_for(PARENT_PRINTER, CHILD_TRANSFER_DATA),
385      )?;
386    }
387    Ok(())
388  }
389
390  fn ensure_mtu(&mut self) -> Result<usize> {
391    if let Some(mtu) = self.mtu {
392      return Ok(mtu);
393    }
394    let info = self.query_device_info()?;
395    let mtu = info.mtu.map(usize::from).unwrap_or(self.fallback_mtu);
396    if mtu == 0 {
397      return Err(EmapiError::Protocol {
398        message: "invalid max packet length: 0".to_string(),
399      });
400    }
401    self.mtu = Some(mtu);
402    Ok(mtu)
403  }
404
405  fn validate_file_transfer_chunk(&mut self, data: &[u8]) -> Result<()> {
406    let mtu = self.ensure_mtu()?;
407    let capacity = file_transfer_payload_capacity(mtu).map_err(|_| EmapiError::Protocol {
408      message: format!("invalid max packet length for file transfer: {mtu}"),
409    })?;
410    if data.len() > capacity {
411      return Err(EmapiError::Protocol {
412        message: format!(
413          "file transfer chunk is too large: {} bytes (capacity: {capacity}, mtu: {mtu})",
414          data.len()
415        ),
416      });
417    }
418    Ok(())
419  }
420
421  fn send_acked(
422    &mut self,
423    command_type: u8,
424    response_type: u8,
425    parent: u8,
426    child: u8,
427    payload: impl Into<Vec<u8>>,
428  ) -> Result<()> {
429    self.send_command(command_type, response_type, parent, child, payload, true)?;
430    Ok(())
431  }
432
433  fn send_command(
434    &mut self,
435    command_type: u8,
436    response_type: u8,
437    parent: u8,
438    child: u8,
439    payload: impl Into<Vec<u8>>,
440    require_empty_payload: bool,
441  ) -> Result<EmapiCommand> {
442    self.session.send_and_wait(
443      EmapiCommand::new(command_type, parent, child, payload),
444      |command| {
445        command.is_response_for(response_type, parent, child)
446          && (!require_empty_payload || command.payload.is_empty())
447      },
448    )
449  }
450}
451
452impl<D> EmapiPrinter<ConnectedDeviceEmapiConnection<D>>
453where
454  D: ConnectedDevice,
455{
456  pub fn connected_device(device: D) -> Self {
457    EmapiPrinter::new(ConnectedDeviceEmapiConnection::new(device))
458  }
459}
460
461fn file_start_payload(file_type: u16, total_size: u32) -> Result<Vec<u8>> {
462  Tlv::encode(&[
463    TlvEntry::uint16(0x01, file_type)?,
464    TlvEntry::uint32(0x02, total_size)?,
465  ])
466}
467
468fn tlv_int16(tlv: &TlvData, tag: u8) -> Result<Option<i16>> {
469  let Some(entry) = tlv.get(tag) else {
470    return Ok(None);
471  };
472  if entry.value.len() != 2 {
473    return Err(EmapiError::InvalidTlvType {
474      tag,
475      expected: "int16",
476      actual_len: entry.value.len(),
477    });
478  }
479  read_int16(&entry.value, 0).map(Some)
480}