1use std::{
2 collections::VecDeque,
3 time::{Duration, Instant},
4};
5
6use crate::{
7 command::EmapiCommand,
8 connection::EmapiConnection,
9 constants::{
10 CHILD_REPORT_BLUETOOTH_CONNECTION, CHILD_REPORT_FLOW_CONTROL, CHILD_REPORT_PRINTER_STATUS,
11 CHILD_REPORT_PRINT_RESULT, CHILD_REPORT_UPGRADE_STATUS, CHILD_WIFI_CONFIG_STATUS,
12 PARENT_REPORT, PARENT_WIFI, TYPE_PASSTHROUGH_REQUEST, TYPE_PASSTHROUGH_RESPONSE, TYPE_REQUEST,
13 TYPE_RESPONSE,
14 },
15 error::{EmapiError, Result},
16 frame_codec::FrameCodec,
17 frame_parser::FrameParser,
18 models::EmapiReport,
19 tlv::Tlv,
20};
21
22pub struct EmapiSession<C> {
23 connection: C,
24 timeout: Duration,
25 max_retries: usize,
26 parser: FrameParser,
27 pending_commands: VecDeque<EmapiCommand>,
28 reports: Vec<EmapiReport>,
29 flow_busy: bool,
30}
31
32impl<C> EmapiSession<C>
33where
34 C: EmapiConnection,
35{
36 pub fn new(connection: C) -> Self {
37 Self {
38 connection,
39 timeout: Duration::from_secs(2),
40 max_retries: 3,
41 parser: FrameParser::new(),
42 pending_commands: VecDeque::new(),
43 reports: Vec::new(),
44 flow_busy: false,
45 }
46 }
47
48 pub fn with_timeout(mut self, timeout: Duration) -> Self {
49 self.timeout = timeout;
50 self
51 }
52
53 pub fn set_timeout(&mut self, timeout: Duration) {
54 self.timeout = timeout;
55 }
56
57 pub fn with_max_retries(mut self, max_retries: usize) -> Result<Self> {
58 self.max_retries = max_retries;
59 Ok(self)
60 }
61
62 pub fn set_max_retries(&mut self, max_retries: usize) {
63 self.max_retries = max_retries;
64 }
65
66 pub fn reports(&self) -> &[EmapiReport] {
67 &self.reports
68 }
69
70 pub fn read_report(&mut self) -> Result<EmapiReport> {
71 let start = Instant::now();
72 let mut pending = VecDeque::new();
73 while let Some(command) = self.pending_commands.pop_front() {
74 let report_index = self.reports.len();
75 if self.handle_report(command.clone())? {
76 pending.extend(self.pending_commands.drain(..));
77 self.pending_commands = pending;
78 return Ok(self.reports[report_index].clone());
79 }
80 pending.push_back(command);
81 }
82 self.pending_commands = pending;
83
84 loop {
85 let command = self.read_next_command_from_connection(start)?;
86 let report_index = self.reports.len();
87 if self.handle_report(command.clone())? {
88 return Ok(self.reports[report_index].clone());
89 }
90 self.pending_commands.push_back(command);
91 }
92 }
93
94 pub fn into_inner(self) -> C {
95 self.connection
96 }
97
98 pub fn send_and_wait<F>(&mut self, command: EmapiCommand, expected: F) -> Result<EmapiCommand>
99 where
100 F: Fn(&EmapiCommand) -> bool,
101 {
102 let mut last_error = None;
103 for _ in 0..=self.max_retries {
104 self.wait_until_flow_idle()?;
105 self.connection.write(&FrameCodec::encode(&command)?)?;
106 match self.read_until_expected(&expected) {
107 Ok(response) => return Ok(response),
108 Err(EmapiError::Timeout { message }) => {
109 last_error = Some(message);
110 }
111 Err(EmapiError::Protocol { message }) => {
112 last_error = Some(message);
113 }
114 Err(error) => return Err(error),
115 }
116 }
117
118 Err(EmapiError::Protocol {
119 message: format!(
120 "no expected ACK after {} attempt(s): {}",
121 self.max_retries + 1,
122 last_error.unwrap_or_else(|| "unknown error".to_string())
123 ),
124 })
125 }
126
127 fn read_until_expected<F>(&mut self, expected: F) -> Result<EmapiCommand>
128 where
129 F: Fn(&EmapiCommand) -> bool,
130 {
131 let start = Instant::now();
132 let mut last_unexpected = None;
133 loop {
134 let response = match self.read_next_command(start) {
135 Ok(response) => response,
136 Err(EmapiError::Timeout { .. }) if last_unexpected.is_some() => {
137 return Err(EmapiError::Protocol {
138 message: last_unexpected.unwrap(),
139 });
140 }
141 Err(error) => return Err(error),
142 };
143
144 if response.is_error() {
145 let error_kind = if response.is_passthrough_error() {
146 "passthrough"
147 } else {
148 "protocol"
149 };
150 return Err(EmapiError::Protocol {
151 message: format!("received {error_kind} error response: {response:?}"),
152 });
153 }
154 if self.handle_report(response.clone())? {
155 continue;
156 }
157 if expected(&response) {
158 return Ok(response);
159 }
160 last_unexpected = Some(format!("unexpected response: {response:?}"));
161 }
162 }
163
164 fn read_next_command(&mut self, start: Instant) -> Result<EmapiCommand> {
165 if let Some(command) = self.pending_commands.pop_front() {
166 return Ok(command);
167 }
168
169 self.read_next_command_from_connection(start)
170 }
171
172 fn read_next_command_from_connection(&mut self, start: Instant) -> Result<EmapiCommand> {
173 loop {
174 let remaining = self.remaining_timeout(start)?;
175 let data = self.connection.read(remaining)?;
176 let mut commands = self.parser.add(&data)?;
177 if !commands.is_empty() {
178 let command = commands.remove(0);
179 self.pending_commands.extend(commands);
180 return Ok(command);
181 }
182 }
183 }
184
185 fn wait_until_flow_idle(&mut self) -> Result<()> {
186 if !self.flow_busy {
187 return Ok(());
188 }
189
190 let start = Instant::now();
191 while self.flow_busy {
192 let command = self.read_next_command(start)?;
193 if !self.handle_report(command.clone())? {
194 self.pending_commands.push_front(command);
195 return Ok(());
196 }
197 }
198 Ok(())
199 }
200
201 fn remaining_timeout(&self, start: Instant) -> Result<Duration> {
202 self
203 .timeout
204 .checked_sub(start.elapsed())
205 .filter(|remaining| !remaining.is_zero())
206 .ok_or_else(|| EmapiError::Timeout {
207 message: format!(
208 "timeout waiting for expected response after {:?}",
209 self.timeout
210 ),
211 })
212 }
213
214 fn handle_report(&mut self, command: EmapiCommand) -> Result<bool> {
215 if is_normal_report(&command) {
216 self.write_ack(TYPE_RESPONSE, command.parent, command.child)?;
217 let report = parse_normal_report(command)?;
218 self.emit_report(report);
219 return Ok(true);
220 }
221 if is_wifi_passthrough_report(&command) {
222 self.write_ack(TYPE_PASSTHROUGH_RESPONSE, command.parent, command.child)?;
223 let report = parse_wifi_passthrough_report(command)?;
224 self.emit_report(report);
225 return Ok(true);
226 }
227 Ok(false)
228 }
229
230 fn write_ack(&mut self, command_type: u8, parent: u8, child: u8) -> Result<()> {
231 self
232 .connection
233 .write(&FrameCodec::encode(&EmapiCommand::new(
234 command_type,
235 parent,
236 child,
237 [],
238 ))?)
239 }
240
241 fn emit_report(&mut self, report: EmapiReport) {
242 if let EmapiReport::FlowControl { state, .. } = &report {
243 self.flow_busy = *state == 1;
244 }
245 self.reports.push(report);
246 }
247}
248
249fn is_normal_report(command: &EmapiCommand) -> bool {
250 command.command_type == TYPE_REQUEST && command.parent == PARENT_REPORT
251}
252
253fn is_wifi_passthrough_report(command: &EmapiCommand) -> bool {
254 command.command_type == TYPE_PASSTHROUGH_REQUEST
255 && command.parent == PARENT_WIFI
256 && command.child == CHILD_WIFI_CONFIG_STATUS
257}
258
259fn parse_normal_report(command: EmapiCommand) -> Result<EmapiReport> {
260 match command.child {
261 CHILD_REPORT_PRINT_RESULT => {
262 let result = payload_byte(&command.payload)?;
263 Ok(EmapiReport::PrintResult { command, result })
264 }
265 CHILD_REPORT_PRINTER_STATUS => {
266 let tlv = Tlv::decode(&command.payload)?;
267 Ok(EmapiReport::PrinterStatus {
268 paper_status: tlv.uint8(0x01)?,
269 cover_status: tlv.uint8(0x02)?,
270 battery_state: tlv.uint8(0x03)?,
271 overheat: tlv.uint8(0x04)?,
272 nfc_paper_recognition: tlv.uint8(0x05)?,
273 command,
274 })
275 }
276 CHILD_REPORT_FLOW_CONTROL => {
277 let state = payload_byte(&command.payload)?;
278 Ok(EmapiReport::FlowControl { command, state })
279 }
280 CHILD_REPORT_UPGRADE_STATUS => {
281 let status = payload_byte(&command.payload)?;
282 Ok(EmapiReport::UpgradeStatus { command, status })
283 }
284 CHILD_REPORT_BLUETOOTH_CONNECTION => {
285 let state = payload_byte(&command.payload)?;
286 Ok(EmapiReport::BluetoothConnection { command, state })
287 }
288 _ => Ok(EmapiReport::Unknown { command }),
289 }
290}
291
292fn parse_wifi_passthrough_report(command: EmapiCommand) -> Result<EmapiReport> {
293 let tlv = Tlv::decode(&command.payload)?;
294 Ok(EmapiReport::WifiConfigStatus {
295 ssid: tlv.string(0x01)?,
296 state: tlv.uint8(0x02)?,
297 command,
298 })
299}
300
301fn payload_byte(payload: &[u8]) -> Result<u8> {
302 payload
303 .first()
304 .copied()
305 .ok_or_else(|| EmapiError::Protocol {
306 message: "missing report payload byte".to_string(),
307 })
308}