use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RejectReason {
Insertion,
Magnetic,
RemainingBillInHead,
Multiplying,
Conveying,
Identification1,
Verification,
Optic,
Inhibit,
Capacity,
Operation,
Length,
Uv,
Barcode,
Unknown(u8),
}
impl RejectReason {
fn from_byte(b: u8) -> Self {
match b {
0x60 => Self::Insertion,
0x61 => Self::Magnetic,
0x62 => Self::RemainingBillInHead,
0x63 => Self::Multiplying,
0x64 => Self::Conveying,
0x65 => Self::Identification1,
0x66 => Self::Verification,
0x67 => Self::Optic,
0x68 => Self::Inhibit,
0x69 => Self::Capacity,
0x6A => Self::Operation,
0x6C => Self::Length,
0x6D => Self::Uv,
0x92 => Self::Barcode,
other => Self::Unknown(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FailureCode {
StackMotorFailure,
TransportMotorSpeed,
TransportMotorFailure,
AligningMotorFailure,
InitialCassetteStatus,
OpticCanal,
MagneticCanal,
CapacitanceCanal,
Unknown(u8),
}
impl FailureCode {
fn from_byte(b: u8) -> Self {
match b {
0x41 => Self::StackMotorFailure,
0x42 => Self::TransportMotorSpeed,
0x43 => Self::TransportMotorFailure,
0x44 => Self::AligningMotorFailure,
0x45 => Self::InitialCassetteStatus,
0x46 => Self::OpticCanal,
0x47 => Self::MagneticCanal,
0x48 => Self::CapacitanceCanal,
other => Self::Unknown(other),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceState {
PowerUp,
PowerUpBillInValidator,
PowerUpBillInStacker,
Initializing,
Idling,
Accepting,
Stacking,
Returning,
UnitDisabled,
Holding,
Busy,
Rejecting(RejectReason),
CassetteFull,
CassetteOutOfPosition,
ValidatorJammed,
CassetteJammed,
Cheated,
Paused,
Failure(FailureCode),
EscrowPosition {
bill_type: u8,
},
BillStacked {
bill_type: u8,
},
BillReturned {
bill_type: u8,
},
}
impl DeviceState {
pub fn from_poll_data(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(Error::InvalidFrame("empty POLL response"));
}
let status = data[0];
let sub = data.get(1).copied().unwrap_or(0);
match status {
0x10 => Ok(Self::PowerUp),
0x11 => Ok(Self::PowerUpBillInValidator),
0x12 => Ok(Self::PowerUpBillInStacker),
0x13 => Ok(Self::Initializing),
0x14 => Ok(Self::Idling),
0x15 => Ok(Self::Accepting),
0x17 => Ok(Self::Stacking),
0x18 => Ok(Self::Returning),
0x19 => Ok(Self::UnitDisabled),
0x1A => Ok(Self::Holding),
0x1B => Ok(Self::Busy),
0x1C => Ok(Self::Rejecting(RejectReason::from_byte(sub))),
0x41 => Ok(Self::CassetteFull),
0x42 => Ok(Self::CassetteOutOfPosition),
0x43 => Ok(Self::ValidatorJammed),
0x44 => Ok(Self::CassetteJammed),
0x45 => Ok(Self::Cheated),
0x46 => Ok(Self::Paused),
0x47 => Ok(Self::Failure(FailureCode::from_byte(sub))),
0x80 => Ok(Self::EscrowPosition {
bill_type: sub.saturating_sub(1),
}),
0x81 => Ok(Self::BillStacked {
bill_type: sub.saturating_sub(1),
}),
0x82 => Ok(Self::BillReturned {
bill_type: sub.saturating_sub(1),
}),
other => Err(Error::UnknownStatus(other)),
}
}
pub fn is_escrow(&self) -> bool {
matches!(self, Self::EscrowPosition { .. })
}
pub fn is_error(&self) -> bool {
matches!(
self,
Self::CassetteFull
| Self::CassetteOutOfPosition
| Self::ValidatorJammed
| Self::CassetteJammed
| Self::Cheated
| Self::Paused
| Self::Failure(_)
)
}
pub fn is_ready(&self) -> bool {
matches!(self, Self::Idling)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_idling() {
let state = DeviceState::from_poll_data(&[0x14]).unwrap();
assert_eq!(state, DeviceState::Idling);
assert!(state.is_ready());
}
#[test]
fn parse_escrow_bill_type_5() {
let state = DeviceState::from_poll_data(&[0x80, 0x06]).unwrap();
assert_eq!(state, DeviceState::EscrowPosition { bill_type: 5 });
assert!(state.is_escrow());
}
#[test]
fn parse_rejection_with_subreason() {
let state = DeviceState::from_poll_data(&[0x1C, 0x60]).unwrap();
assert_eq!(state, DeviceState::Rejecting(RejectReason::Insertion));
}
#[test]
fn parse_failure_with_subcode() {
let state = DeviceState::from_poll_data(&[0x47, 0x43]).unwrap();
assert_eq!(
state,
DeviceState::Failure(FailureCode::TransportMotorFailure)
);
}
#[test]
fn parse_bill_stacked() {
let state = DeviceState::from_poll_data(&[0x81, 0x01]).unwrap();
assert_eq!(state, DeviceState::BillStacked { bill_type: 0 });
}
#[test]
fn unknown_status_is_error() {
assert!(matches!(
DeviceState::from_poll_data(&[0x20]),
Err(Error::UnknownStatus(0x20))
));
}
}