use alloc::string::String;
use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
use crate::{
status::{DeviceState, DeviceStateFlags},
std::{self, fmt},
Currency,
};
pub const ENV_BAU_DEVICE: &str = "SERIAL_PATH_BAU";
pub const ENV_CDU_DEVICE: &str = "SERIAL_PATH_CPU";
pub const DEFAULT_BAU_DEV_PATH: &str = "/dev/bau";
pub const DEFAULT_CDU_DEV_PATH: &str = "/dev/cdu";
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum HardwareComponent {
CDU,
EPP,
SIU,
RPU,
MCR,
BAU,
BA2,
BCS,
CAM,
UPS,
}
impl HardwareComponent {
pub const fn default() -> Self {
Self::BAU
}
}
impl From<HardwareComponent> for &'static str {
fn from(h: HardwareComponent) -> Self {
match h {
HardwareComponent::CDU => "CDU",
HardwareComponent::EPP => "EPP",
HardwareComponent::SIU => "SIU",
HardwareComponent::RPU => "RPU",
HardwareComponent::MCR => "MCR",
HardwareComponent::BAU => "BAU",
HardwareComponent::BA2 => "BA2",
HardwareComponent::BCS => "BCS",
HardwareComponent::CAM => "CAM",
HardwareComponent::UPS => "UPS",
}
}
}
impl From<&HardwareComponent> for &'static str {
fn from(h: &HardwareComponent) -> Self {
(*h).into()
}
}
impl fmt::Display for HardwareComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", <&'static str>::from(self))
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Deserialize)]
#[serde(field_identifier, rename_all = "UPPERCASE")]
pub enum HardwareState {
OK,
Missing,
Warning,
Error,
}
impl HardwareState {
pub const fn default() -> Self {
Self::OK
}
}
impl Serialize for HardwareState {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match *self {
Self::OK => serializer.serialize_unit_variant("HardwareState", 0, "OK"),
Self::Missing => serializer.serialize_unit_variant("HardwareState", 1, "MISSING"),
Self::Warning => serializer.serialize_unit_variant("HardwareState", 2, "WARNING"),
Self::Error => serializer.serialize_unit_variant("HardwareState", 3, "ERROR"),
}
}
}
impl From<DeviceState> for HardwareState {
fn from(dev_state: DeviceState) -> Self {
Self::from(DeviceStateFlags::from(dev_state))
}
}
impl From<DeviceStateFlags> for HardwareState {
fn from(dev_state: DeviceStateFlags) -> Self {
match dev_state {
DeviceStateFlags::Disconnected | DeviceStateFlags::CashBoxRemoved => Self::Warning,
DeviceStateFlags::PowerUp
| DeviceStateFlags::Initialize
| DeviceStateFlags::Download
| DeviceStateFlags::Idle
| DeviceStateFlags::HostDisabled
| DeviceStateFlags::BusyCalculation
| DeviceStateFlags::Escrowed
| DeviceStateFlags::Accepting
| DeviceStateFlags::Stacking
| DeviceStateFlags::Returning
| DeviceStateFlags::Paused
| DeviceStateFlags::Calibration
| DeviceStateFlags::Dispensing
| DeviceStateFlags::FloatingDown
| DeviceStateFlags::IdleInEscrowSession
| DeviceStateFlags::HostDisabledInEscrowSession
| DeviceStateFlags::PatternRecovering => Self::OK,
DeviceStateFlags::Cheated
| DeviceStateFlags::StackerFull
| DeviceStateFlags::TransportOpened
| DeviceStateFlags::EscrowStorageFull
| DeviceStateFlags::UnknownDocumentsDetected => Self::Warning,
DeviceStateFlags::Jammed
| DeviceStateFlags::Failure
| DeviceStateFlags::Stalled
| DeviceStateFlags::DisabledAndJammed
| DeviceStateFlags::Disabled => Self::Error,
_ => Self::Error,
}
}
}
impl From<HardwareState> for &'static str {
fn from(h: HardwareState) -> Self {
match h {
HardwareState::OK => "OK",
HardwareState::Missing => "MISSING",
HardwareState::Warning => "WARNING",
HardwareState::Error => "ERROR",
}
}
}
impl From<&HardwareState> for &'static str {
fn from(h: &HardwareState) -> Self {
(*h).into()
}
}
impl fmt::Display for HardwareState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", <&str>::from(self))
}
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Deserialize)]
pub struct BillAcceptorStatusDetails {
cashbox_removed: Option<bool>,
firmware_version: Option<String>,
currency: Option<Currency>,
jammed: Option<bool>,
}
impl BillAcceptorStatusDetails {
pub const fn new(
cashbox_removed: Option<bool>,
firmware_version: Option<String>,
currency: Option<Currency>,
jammed: Option<bool>,
) -> Self {
Self {
cashbox_removed,
firmware_version,
currency,
jammed,
}
}
pub const fn default() -> Self {
Self {
cashbox_removed: None,
firmware_version: None,
currency: None,
jammed: None,
}
}
pub fn with_cashbox_removed(mut self, cashbox_removed: bool) -> Self {
self.cashbox_removed = Some(cashbox_removed);
self
}
pub fn with_firmware_version(mut self, firmware_version: &str) -> Self {
self.firmware_version = Some(firmware_version.into());
self
}
pub fn with_currency(mut self, currency: Currency) -> Self {
self.currency = Some(currency);
self
}
pub fn with_jammed(mut self, jammed: bool) -> Self {
self.jammed = Some(jammed);
self
}
pub fn cashbox_removed(&self) -> bool {
self.cashbox_removed.unwrap_or_default()
}
pub fn set_cashbox_removed(&mut self, removed: bool) {
self.cashbox_removed = Some(removed);
}
pub fn unset_cashbox_removed(&mut self) {
self.cashbox_removed = None;
}
pub fn firmware_version(&self) -> &str {
if let Some(ret) = self.firmware_version.as_ref() {
ret
} else {
""
}
}
pub fn set_firmware_version<S>(&mut self, version: S)
where
S: Into<String>,
{
self.firmware_version = Some(version.into());
}
pub fn unset_firmware_version(&mut self) {
self.firmware_version = None
}
pub fn currency(&self) -> Currency {
if let Some(ret) = self.currency {
ret
} else {
Currency::USD
}
}
pub fn set_currency(&mut self, currency: Currency) {
self.currency = Some(currency);
}
pub fn unset_currency(&mut self) {
self.currency = None;
}
pub fn jammed(&self) -> bool {
self.jammed.unwrap_or_default()
}
pub fn set_jammed(&mut self, jammed: bool) {
self.jammed = Some(jammed);
}
pub fn unset_jammed(&mut self) {
self.jammed = None;
}
}
impl fmt::Display for BillAcceptorStatusDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut has_field = false;
write!(f, "{{")?;
if let Some(ret) = self.cashbox_removed {
write!(f, r#""cashbox_removed":{ret}"#)?;
has_field = true;
}
if let Some(ret) = self.firmware_version.as_ref() {
if has_field {
write!(f, ",")?;
}
write!(f, r#""firmware_version":"{ret}""#)?;
has_field = true;
}
if let Some(ret) = self.currency {
if has_field {
write!(f, ",")?;
}
write!(f, r#""currency":{ret}"#)?;
has_field = true;
}
if let Some(ret) = self.jammed {
if has_field {
write!(f, ",")?;
}
write!(f, r#""jammed":{ret}"#)?;
}
write!(f, "}}")
}
}
impl Serialize for BillAcceptorStatusDetails {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut bau_status = serializer.serialize_struct("BillAcceptorStatusDetails", 4)?;
bau_status.serialize_field("cashbox_removed", &self.cashbox_removed)?;
bau_status.serialize_field("firmware_version", &self.firmware_version)?;
bau_status.serialize_field("currency", &self.currency)?;
bau_status.serialize_field("jammed", &self.jammed)?;
bau_status.end()
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub enum HardwareStatusDetails {
NULL,
BAU,
}
impl Default for HardwareStatusDetails {
fn default() -> Self {
Self::NULL
}
}
impl From<HardwareStatusDetails> for &'static str {
fn from(h: HardwareStatusDetails) -> Self {
match h {
HardwareStatusDetails::NULL => "NULL",
HardwareStatusDetails::BAU => "BAU",
}
}
}
impl From<&HardwareStatusDetails> for &'static str {
fn from(h: &HardwareStatusDetails) -> &'static str {
(*h).into()
}
}
impl fmt::Display for HardwareStatusDetails {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", <&'static str>::from(self))
}
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct HardwareStatus {
component: HardwareComponent,
state: HardwareState,
description: String,
details: BillAcceptorStatusDetails,
}
impl HardwareStatus {
pub const fn new(
component: HardwareComponent,
state: HardwareState,
description: String,
details: BillAcceptorStatusDetails,
) -> Self {
Self {
component,
state,
description,
details,
}
}
pub const fn default() -> Self {
Self {
component: HardwareComponent::default(),
state: HardwareState::default(),
description: String::new(),
details: BillAcceptorStatusDetails::default(),
}
}
pub fn component(&self) -> HardwareComponent {
self.component
}
pub fn set_component(&mut self, component: HardwareComponent) {
self.component = component;
}
pub fn state(&self) -> HardwareState {
self.state
}
pub fn set_state(&mut self, state: HardwareState) {
self.state = state;
}
pub fn set_priority_state(&mut self, proposed: HardwareState) {
if (proposed as u32) > (self.state as u32) {
self.state = proposed;
}
}
pub fn description(&self) -> &str {
&self.description
}
pub fn set_description<S>(&mut self, description: S)
where
S: Into<String>,
{
self.description = description.into();
}
pub fn details(&self) -> &BillAcceptorStatusDetails {
&self.details
}
pub fn details_mut(&mut self) -> &mut BillAcceptorStatusDetails {
&mut self.details
}
pub fn set_details(&mut self, details: BillAcceptorStatusDetails) {
self.details = details;
}
}
impl fmt::Display for HardwareStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let component = self.component();
let state = self.state();
let description = self.description();
let details = self.details();
write!(f, "{{")?;
write!(f, r#""component":"{component}","#)?;
write!(f, r#""state":{state},"#)?;
write!(f, r#""description":"{description}","#)?;
write!(f, r#""details":{details}"#)?;
write!(f, "}}")
}
}
#[cfg(feature = "std")]
pub fn get_device_path(env_key: &str, default_path: &str) -> String {
std::env::var(env_key).unwrap_or(default_path.into())
}
#[cfg(not(feature = "std"))]
pub fn get_device_path(_env_key: &str, default_path: &str) -> String {
default_path.into()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{self, Result};
#[test]
fn test_hardware_component_serde() -> Result<()> {
assert_eq!(serde_json::to_string(&HardwareComponent::CDU)?, r#""CDU""#);
assert_eq!(serde_json::to_string(&HardwareComponent::EPP)?, r#""EPP""#);
assert_eq!(serde_json::to_string(&HardwareComponent::SIU)?, r#""SIU""#);
assert_eq!(serde_json::to_string(&HardwareComponent::RPU)?, r#""RPU""#);
assert_eq!(serde_json::to_string(&HardwareComponent::MCR)?, r#""MCR""#);
assert_eq!(serde_json::to_string(&HardwareComponent::BAU)?, r#""BAU""#);
assert_eq!(serde_json::to_string(&HardwareComponent::BA2)?, r#""BA2""#);
assert_eq!(serde_json::to_string(&HardwareComponent::BCS)?, r#""BCS""#);
assert_eq!(serde_json::to_string(&HardwareComponent::CAM)?, r#""CAM""#);
assert_eq!(serde_json::to_string(&HardwareComponent::UPS)?, r#""UPS""#);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""CDU""#)?,
HardwareComponent::CDU
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""EPP""#)?,
HardwareComponent::EPP
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""SIU""#)?,
HardwareComponent::SIU
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""RPU""#)?,
HardwareComponent::RPU
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""MCR""#)?,
HardwareComponent::MCR
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""BAU""#)?,
HardwareComponent::BAU
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""BA2""#)?,
HardwareComponent::BA2
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""BCS""#)?,
HardwareComponent::BCS
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""CAM""#)?,
HardwareComponent::CAM
);
assert_eq!(
serde_json::from_str::<HardwareComponent>(r#""UPS""#)?,
HardwareComponent::UPS
);
Ok(())
}
#[test]
fn test_hardware_state_serde() -> Result<()> {
assert_eq!(serde_json::to_string(&HardwareState::OK)?, r#""OK""#);
assert_eq!(
serde_json::to_string(&HardwareState::Missing)?,
r#""MISSING""#
);
assert_eq!(
serde_json::to_string(&HardwareState::Warning)?,
r#""WARNING""#
);
assert_eq!(serde_json::to_string(&HardwareState::Error)?, r#""ERROR""#);
Ok(())
}
#[test]
fn test_bill_acceptor_status_details_serde() -> Result<()> {
let bau_status_filled = BillAcceptorStatusDetails {
cashbox_removed: Some(true),
firmware_version: Some("version-1.0".into()),
currency: Some(Currency::USD),
jammed: Some(false),
};
let expected = r#"{"cashbox_removed":true,"firmware_version":"version-1.0","currency":"USD","jammed":false}"#;
assert_eq!(serde_json::to_string(&bau_status_filled)?, expected);
let des_filled: BillAcceptorStatusDetails = serde_json::from_str(expected)?;
assert_eq!(des_filled, bau_status_filled);
let bau_status_sparse = BillAcceptorStatusDetails {
cashbox_removed: None,
firmware_version: Some("version-1.0".into()),
currency: Some(Currency::USD),
jammed: None,
};
let expected = r#"{"cashbox_removed":null,"firmware_version":"version-1.0","currency":"USD","jammed":null}"#;
assert_eq!(serde_json::to_string(&bau_status_sparse)?, expected);
let sparse_json = r#"{"firmware_version":"version-1.0","currency":"USD"}"#;
let des_sparse: BillAcceptorStatusDetails = serde_json::from_str(sparse_json)?;
assert_eq!(des_sparse, bau_status_sparse);
Ok(())
}
}