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}