use std::time::Duration;
use psdk_father::device::FakeConnectedDevice;
use psdk_fruit_emapi::{
constants::*,
device_connection::ConnectedDeviceEmapiConnection,
models::{EmapiReport, EmapiRfidAuthFailurePolicy, EmapiWifiConnectionState},
tlv::{Tlv, TlvEntry},
EmapiCommand, EmapiPrinter, EmapiSession, FrameCodec,
};
fn ack(parent: u8, child: u8) -> Vec<u8> {
response(parent, child, [])
}
fn response(parent: u8, child: u8, payload: impl Into<Vec<u8>>) -> Vec<u8> {
FrameCodec::encode(&EmapiCommand::new(TYPE_RESPONSE, parent, child, payload)).unwrap()
}
fn error(parent: u8, child: u8) -> Vec<u8> {
FrameCodec::encode(&EmapiCommand::new(TYPE_ERROR, parent, child, [])).unwrap()
}
fn passthrough_ack(child: u8) -> Vec<u8> {
passthrough_response(child, [])
}
fn passthrough_response(child: u8, payload: impl Into<Vec<u8>>) -> Vec<u8> {
FrameCodec::encode(&EmapiCommand::new(
TYPE_PASSTHROUGH_RESPONSE,
PARENT_WIFI,
child,
payload,
))
.unwrap()
}
fn report(child: u8, payload: impl Into<Vec<u8>>) -> Vec<u8> {
FrameCodec::encode(&EmapiCommand::new(
TYPE_REQUEST,
PARENT_REPORT,
child,
payload,
))
.unwrap()
}
fn passthrough_report(child: u8, payload: impl Into<Vec<u8>>) -> Vec<u8> {
FrameCodec::encode(&EmapiCommand::new(
TYPE_PASSTHROUGH_REQUEST,
PARENT_WIFI,
child,
payload,
))
.unwrap()
}
#[test]
fn test_session_retries_timeout_and_protocol_error_before_ack() {
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
None,
Some(ack(PARENT_PRINTER, CHILD_TRANSFER_DATA)),
]);
let recorder = connection.clone();
let mut session = EmapiSession::new(connection)
.with_timeout(Duration::from_millis(1))
.with_max_retries(1)
.unwrap();
session
.send_and_wait(
EmapiCommand::new(TYPE_REQUEST, PARENT_PRINTER, CHILD_TRANSFER_DATA, [0x10]),
|command| command.is_ack_for(PARENT_PRINTER, CHILD_TRANSFER_DATA),
)
.unwrap();
assert_eq!(recorder.writes().len(), 2);
assert_eq!(recorder.reads(), 2);
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
Some(error(PARENT_PRINTER, CHILD_TRANSFER_DATA)),
Some(ack(PARENT_PRINTER, CHILD_TRANSFER_DATA)),
]);
let recorder = connection.clone();
let mut session = EmapiSession::new(connection)
.with_timeout(Duration::from_millis(1))
.with_max_retries(1)
.unwrap();
session
.send_and_wait(
EmapiCommand::new(TYPE_REQUEST, PARENT_PRINTER, CHILD_TRANSFER_DATA, []),
|command| command.is_ack_for(PARENT_PRINTER, CHILD_TRANSFER_DATA),
)
.unwrap();
assert_eq!(recorder.writes().len(), 2);
}
#[test]
fn test_session_continues_after_unexpected_and_buffers_following_frames() {
let first_ack = ack(PARENT_PRINTER, CHILD_TRANSFER_DATA);
let second = response(PARENT_SYSTEM, CHILD_DEVICE_INFO, []);
let mut combined = first_ack.clone();
combined.extend_from_slice(&second);
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
Some(response(PARENT_RFID, CHILD_RFID_UID, b"ignored")),
Some(combined),
]);
let recorder = connection.clone();
let mut session = EmapiSession::new(connection).with_timeout(Duration::from_millis(1));
session
.send_and_wait(
EmapiCommand::new(TYPE_REQUEST, PARENT_PRINTER, CHILD_TRANSFER_DATA, []),
|command| command.is_ack_for(PARENT_PRINTER, CHILD_TRANSFER_DATA),
)
.unwrap();
session
.send_and_wait(
EmapiCommand::new(TYPE_REQUEST, PARENT_SYSTEM, CHILD_DEVICE_INFO, []),
|command| command.is_response_for(TYPE_RESPONSE, PARENT_SYSTEM, CHILD_DEVICE_INFO),
)
.unwrap();
assert_eq!(recorder.writes().len(), 2);
assert_eq!(recorder.reads(), 2);
}
#[test]
fn test_connected_device_adapter_reads_and_writes_fake_device() {
let ack = ack(PARENT_PRINTER, CHILD_TRANSFER_DATA);
let device = FakeConnectedDevice::new().with_read_data(vec![ack.clone()]);
let mut connection = ConnectedDeviceEmapiConnection::new(device.clone());
connection.write(&[0x01, 0x02]).unwrap();
let read = connection.read(Duration::from_secs(1)).unwrap();
assert_eq!(device.written_chunks()[0].binary, vec![0x01, 0x02]);
assert_eq!(read, ack);
}
#[test]
fn test_print_esc_uses_device_mtu_and_ack_gated_chunks() {
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
Some(response(
PARENT_SYSTEM,
CHILD_DEVICE_INFO,
Tlv::encode(&[TlvEntry::uint16(TAG_MTU, 17).unwrap()]).unwrap(),
)),
Some(ack(PARENT_PRINTER, CHILD_TRANSFER_DATA)),
Some(ack(PARENT_PRINTER, CHILD_TRANSFER_DATA)),
]);
let recorder = connection.clone();
let mut printer = EmapiPrinter::new(connection).with_timeout(Duration::from_millis(1));
printer.print_esc(&[0x1B, 0x4A, 0x05, 0x1D]).unwrap();
let writes = recorder.writes();
assert_eq!(writes.len(), 3);
assert_eq!(
FrameCodec::decode(&writes[1]).unwrap().payload,
vec![0x1B, 0x4A, 0x05]
);
assert_eq!(FrameCodec::decode(&writes[2]).unwrap().payload, vec![0x1D]);
}
#[test]
fn test_facade_system_commands_match_dart_payloads() {
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
Some(response(
PARENT_SYSTEM,
CHILD_DEVICE_INFO,
Tlv::encode(&[
TlvEntry::string(0x01, "label").unwrap(),
TlvEntry::string(0x02, "Q3").unwrap(),
TlvEntry::string(0x03, "printer").unwrap(),
TlvEntry::string(0x04, "SN001").unwrap(),
TlvEntry::string(0x05, "HW1").unwrap(),
TlvEntry::string(0x06, "SW1").unwrap(),
TlvEntry::string(0x07, "BOOT1").unwrap(),
TlvEntry::uint16(TAG_MTU, 256).unwrap(),
])
.unwrap(),
)),
Some(ack(PARENT_SYSTEM, CHILD_SLEEP_SHUTDOWN)),
Some(ack(PARENT_SYSTEM, CHILD_SET_SHUTDOWN_TIME)),
Some(ack(PARENT_FILE, CHILD_FILE_START)),
]);
let recorder = connection.clone();
let mut printer = EmapiPrinter::new(connection).with_timeout(Duration::from_millis(20));
let info = printer.query_device_info().unwrap();
assert_eq!(info.device_type.as_deref(), Some("label"));
assert_eq!(info.device_model.as_deref(), Some("Q3"));
assert_eq!(info.brand.as_deref(), Some("printer"));
assert_eq!(info.serial_number.as_deref(), Some("SN001"));
assert_eq!(info.hardware_version.as_deref(), Some("HW1"));
assert_eq!(info.software_version.as_deref(), Some("SW1"));
assert_eq!(info.boot_version.as_deref(), Some("BOOT1"));
assert_eq!(info.mtu, Some(256));
printer.sleep_shutdown().unwrap();
printer.set_shutdown_time(30).unwrap();
printer.start_main_controller_ota(0x0002, 9).unwrap();
let writes = recorder.writes();
assert_eq!(
FrameCodec::decode(&writes[0]).unwrap().parent,
PARENT_SYSTEM
);
assert_eq!(
FrameCodec::decode(&writes[0]).unwrap().child,
CHILD_DEVICE_INFO
);
assert_eq!(
FrameCodec::decode(&writes[1]).unwrap().parent,
PARENT_SYSTEM
);
assert_eq!(
FrameCodec::decode(&writes[1]).unwrap().child,
CHILD_SLEEP_SHUTDOWN
);
assert_eq!(
FrameCodec::decode(&writes[2]).unwrap().child,
CHILD_SET_SHUTDOWN_TIME
);
assert_eq!(FrameCodec::decode(&writes[2]).unwrap().payload, vec![30, 0]);
assert_eq!(FrameCodec::decode(&writes[3]).unwrap().parent, PARENT_FILE);
assert_eq!(
FrameCodec::decode(&writes[3]).unwrap().child,
CHILD_FILE_START
);
assert_eq!(
FrameCodec::decode(&writes[3]).unwrap().payload,
vec![0x01, 0x02, 0x00, 0x02, 0x00, 0x02, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00]
);
}
#[test]
fn test_facade_wifi_rfid_printer_and_file_payloads_match_dart() {
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
Some(passthrough_ack(CHILD_WIFI_CONFIG)),
Some(passthrough_response(CHILD_WIFI_CONNECTION_STATE, [0x02])),
Some(passthrough_response(
CHILD_WIFI_HOTSPOT_INFO,
Tlv::encode(&[
TlvEntry::string(0x01, "demo-ap").unwrap(),
TlvEntry::uint16(0x02, 0xFFD8).unwrap(),
TlvEntry::string(0x03, "192.168.4.1").unwrap(),
TlvEntry::string(0x04, "9100").unwrap(),
])
.unwrap(),
)),
Some(response(PARENT_RFID, CHILD_RFID_UID, b"04AABBCC")),
Some(response(
PARENT_RFID,
CHILD_RFID_CARD_INFO,
Tlv::encode(&[
TlvEntry::string(0x01, "L801").unwrap(),
TlvEntry::string(0x02, "40m").unwrap(),
TlvEntry::string(0x03, "80mm").unwrap(),
TlvEntry::string(0x04, "white").unwrap(),
TlvEntry::string(0x05, "PAPER-01").unwrap(),
])
.unwrap(),
)),
Some(response(
PARENT_RFID,
CHILD_RFID_PAPER_LENGTH,
[0x78, 0x56, 0x34, 0x12],
)),
Some(ack(PARENT_RFID, CHILD_RFID_AUTH_FAILURE_HANDLING)),
Some(response(
PARENT_PRINTER,
CHILD_PRINTER_PARAMS,
Tlv::encode(&[
TlvEntry::uint32(0x01, 384).unwrap(),
TlvEntry::uint32(0x02, 203).unwrap(),
TlvEntry::uint32(0x03, 8).unwrap(),
])
.unwrap(),
)),
Some(response(
PARENT_PRINTER,
CHILD_PRINT_STATUS,
Tlv::encode(&[
TlvEntry::uint8(0x01, 1).unwrap(),
TlvEntry::uint8(0x05, 88).unwrap(),
TlvEntry::uint16(0x06, 7420).unwrap(),
TlvEntry::uint8(0x07, 246).unwrap(),
])
.unwrap(),
)),
Some(ack(PARENT_PRINTER, CHILD_PRINT_SELF_TEST_PAGE)),
Some(ack(PARENT_FILE, CHILD_FILE_START)),
Some(error(PARENT_FILE, CHILD_FILE_TRANSFER)),
Some(ack(PARENT_FILE, CHILD_FILE_TRANSFER)),
Some(ack(PARENT_FILE, CHILD_FILE_END)),
Some(ack(PARENT_FILE, CHILD_FILE_UPGRADE)),
Some(passthrough_ack(CHILD_WIFI_FILE_START)),
Some(passthrough_ack(CHILD_WIFI_FILE_TRANSFER)),
Some(passthrough_ack(CHILD_WIFI_FILE_END)),
]);
let recorder = connection.clone();
let mut printer = EmapiPrinter::new(connection)
.with_timeout(Duration::from_millis(20))
.with_max_retries(1)
.unwrap()
.with_mtu(512)
.unwrap();
printer.set_wifi_config("demo", "secret", None).unwrap();
assert_eq!(
printer.query_wifi_connection_state().unwrap(),
EmapiWifiConnectionState::IotConnected
);
let hotspot = printer.query_wifi_hotspot_info().unwrap();
assert_eq!(hotspot.ssid.as_deref(), Some("demo-ap"));
assert_eq!(hotspot.rssi, Some(-40));
assert_eq!(hotspot.ip.as_deref(), Some("192.168.4.1"));
assert_eq!(hotspot.port.as_deref(), Some("9100"));
assert_eq!(printer.query_rfid_uid().unwrap(), "04AABBCC");
let card = printer.query_rfid_card_info().unwrap();
assert_eq!(card.paper_model.as_deref(), Some("L801"));
assert_eq!(card.paper_length.as_deref(), Some("40m"));
assert_eq!(card.paper_width.as_deref(), Some("80mm"));
assert_eq!(card.paper_color.as_deref(), Some("white"));
assert_eq!(card.paper_material_number.as_deref(), Some("PAPER-01"));
assert_eq!(printer.query_rfid_paper_length().unwrap(), 0x12345678);
printer
.set_rfid_auth_failure_handling(EmapiRfidAuthFailurePolicy::ForbidPrint)
.unwrap();
let params = printer.query_printer_params().unwrap();
assert_eq!(params.print_size, Some(384));
assert_eq!(params.resolution, Some(203));
assert_eq!(params.dots_per_byte, Some(8));
let status = printer.query_print_status().unwrap();
assert_eq!(status.paper_status, Some(1));
assert_eq!(status.battery_percent, Some(88));
assert_eq!(status.battery_voltage, Some(7420));
assert_eq!(status.tph_temperature, Some(-10));
printer.print_self_test_page().unwrap();
printer.start_main_controller_ota_default(9).unwrap();
printer
.transfer_main_controller_ota_chunk(2, &[1, 2, 3])
.unwrap();
printer.finish_main_controller_ota().unwrap();
printer.upgrade_main_controller().unwrap();
printer.start_wifi_file_download(2, 3).unwrap();
printer
.transfer_wifi_file_download_chunk(1, &[9, 8, 7])
.unwrap();
printer.finish_wifi_file_download().unwrap();
let writes = recorder.writes();
assert_eq!(
FrameCodec::decode(&writes[0]).unwrap().command_type,
TYPE_PASSTHROUGH_REQUEST
);
assert_eq!(FrameCodec::decode(&writes[6]).unwrap().payload, vec![0x01]);
assert_eq!(
FrameCodec::decode(&writes[11]).unwrap().child,
CHILD_FILE_TRANSFER
);
assert_eq!(
FrameCodec::decode(&writes[12]).unwrap().payload,
vec![0x02, 0x00, 0x03, 0x00, 1, 2, 3]
);
assert_eq!(
FrameCodec::decode(&writes[15]).unwrap().command_type,
TYPE_PASSTHROUGH_REQUEST
);
assert_eq!(
FrameCodec::decode(&writes[16]).unwrap().payload,
vec![0x01, 0x00, 0x03, 0x00, 9, 8, 7]
);
}
#[test]
fn test_reports_are_acked_and_flow_control_blocks_next_send_until_idle() {
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
Some(report(CHILD_REPORT_FLOW_CONTROL, [0x01])),
Some(report(CHILD_REPORT_FLOW_CONTROL, [0x00])),
Some(passthrough_report(
CHILD_WIFI_CONFIG_STATUS,
Tlv::encode(&[
TlvEntry::string(0x01, "office").unwrap(),
TlvEntry::uint8(0x02, 1).unwrap(),
])
.unwrap(),
)),
Some(ack(PARENT_PRINTER, CHILD_PRINT_SELF_TEST_PAGE)),
]);
let recorder = connection.clone();
let mut printer = EmapiPrinter::new(connection).with_timeout(Duration::from_millis(20));
printer.print_self_test_page().unwrap();
let writes = recorder.writes();
assert_eq!(
FrameCodec::decode(&writes[0]).unwrap().child,
CHILD_PRINT_SELF_TEST_PAGE
);
assert_eq!(
FrameCodec::decode(&writes[1]).unwrap().command_type,
TYPE_RESPONSE
);
assert_eq!(
FrameCodec::decode(&writes[1]).unwrap().child,
CHILD_REPORT_FLOW_CONTROL
);
assert_eq!(
FrameCodec::decode(&writes[2]).unwrap().command_type,
TYPE_RESPONSE
);
assert_eq!(
FrameCodec::decode(&writes[3]).unwrap().command_type,
TYPE_PASSTHROUGH_RESPONSE
);
assert_eq!(printer.reports().len(), 3);
}
#[test]
fn test_read_report_listens_without_pending_command_and_acks_report() {
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![Some(report(
CHILD_REPORT_PRINTER_STATUS,
Tlv::encode(&[
TlvEntry::uint8(0x01, 1).unwrap(),
TlvEntry::uint8(0x02, 1).unwrap(),
TlvEntry::uint8(0x03, 0).unwrap(),
TlvEntry::uint8(0x04, 0).unwrap(),
TlvEntry::uint8(0x05, 0).unwrap(),
])
.unwrap(),
))]);
let recorder = connection.clone();
let mut printer = EmapiPrinter::new(connection).with_timeout(Duration::from_millis(20));
let report = printer.read_report().unwrap();
assert!(matches!(
report,
EmapiReport::PrinterStatus {
cover_status: Some(1),
..
}
));
assert_eq!(printer.reports().len(), 1);
let writes = recorder.writes();
assert_eq!(writes.len(), 1);
assert_eq!(
FrameCodec::decode(&writes[0]).unwrap().command_type,
TYPE_RESPONSE
);
assert_eq!(
FrameCodec::decode(&writes[0]).unwrap().parent,
PARENT_REPORT
);
assert_eq!(
FrameCodec::decode(&writes[0]).unwrap().child,
CHILD_REPORT_PRINTER_STATUS
);
}
#[test]
fn test_read_report_buffers_non_report_until_later_command_wait() {
let connection = psdk_fruit_emapi::connection::ScriptedEmapiConnection::new(vec![
Some(ack(PARENT_PRINTER, CHILD_TRANSFER_DATA)),
Some(report(CHILD_REPORT_PRINT_RESULT, [0x00])),
]);
let recorder = connection.clone();
let mut session = EmapiSession::new(connection)
.with_timeout(Duration::from_millis(20))
.with_max_retries(0)
.unwrap();
let report = session.read_report().unwrap();
assert!(matches!(report, EmapiReport::PrintResult { result: 0, .. }));
session
.send_and_wait(
EmapiCommand::new(TYPE_REQUEST, PARENT_PRINTER, CHILD_TRANSFER_DATA, []),
|command| command.is_ack_for(PARENT_PRINTER, CHILD_TRANSFER_DATA),
)
.unwrap();
assert_eq!(recorder.reads(), 2);
}