Skip to main content

psdk_fruit_emapi/
session.rs

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}