use bitfield::bitfield;
use crate::{
bool_enum, impl_message_ops, impl_omnibus_command_ops,
len::{FLASH_DATA_PACKET, OMNIBUS_COMMAND},
std::fmt,
FlashDownloadMessage, MessageOps, MessageType, StandardDenomination,
};
bitfield! {
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct OperationalMode(u8);
u8;
pub orientation_control, set_orientation_control: 3, 2;
pub escrow_mode, set_escrow_mode: 4;
pub document_stack, set_document_stack: 5;
pub document_return, set_document_return: 6;
}
impl From<OperationalMode> for u8 {
fn from(o: OperationalMode) -> Self {
o.0
}
}
impl From<&OperationalMode> for u8 {
fn from(o: &OperationalMode) -> Self {
o.0
}
}
impl From<u8> for OperationalMode {
fn from(b: u8) -> Self {
Self(b & 0b111_1111)
}
}
impl fmt::Display for OperationalMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
write!(
f,
r#""orientation_control": {}, "#,
OrientationControl::from(self.orientation_control())
)?;
write!(
f,
r#""escrow_mode": {}, "#,
EscrowMode::from(self.escrow_mode())
)?;
write!(
f,
r#""document_stack": {}, "#,
DocumentStack::from(self.document_stack())
)?;
write!(
f,
r#""document_return": {}"#,
DocumentReturn::from(self.document_return())
)?;
write!(f, "}}")
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum OrientationControl {
OneWay = 0b00,
TwoWay = 0b01,
#[default]
FourWay = 0b10,
}
impl From<u8> for OrientationControl {
fn from(b: u8) -> Self {
match b & bitmask::ORIENTATION {
0b00 => Self::OneWay,
0b01 => Self::TwoWay,
0b10 | 0b11 => Self::FourWay,
_ => Self::FourWay,
}
}
}
impl From<OrientationControl> for u8 {
fn from(val: OrientationControl) -> Self {
val as u8
}
}
impl From<&OrientationControl> for u8 {
fn from(val: &OrientationControl) -> Self {
(*val).into()
}
}
impl From<&OrientationControl> for &'static str {
fn from(val: &OrientationControl) -> Self {
match val {
OrientationControl::OneWay => "one way",
OrientationControl::TwoWay => "two way",
OrientationControl::FourWay => "four way",
}
}
}
impl fmt::Display for OrientationControl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#""{}""#, <&str>::from(self))
}
}
impl From<OrientationControl> for &'static str {
fn from(val: OrientationControl) -> Self {
(&val).into()
}
}
bool_enum!(
EscrowMode,
r"
Determines how documents are handled after validation
Unset:
**Deprecated/Obsolete**: Escrow is disabled
Set:
Escrow mode is enabled
"
);
bool_enum!(
DocumentStack,
r"
If a document is in escrow, stack it in the cash box
This command is mutually exclusive with [DocumentReturn](DocumentReturn)
Unset:
No-op
Set:
Stack a document in the cash box. Only valid if escrow mode enabled
"
);
bool_enum!(
DocumentReturn,
r"
If a document is in escrow, return it to the consumer
This command is mutually exclusive with [DocumentStack](DocumentStack)
Unset:
No-op
Set:
Return a document to the consumer. Only valid if escrow mode enabled
"
);
bitfield! {
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Configuration(u8);
u8;
pub no_push, set_no_push: 0;
pub barcode, set_barcode: 1;
pub power_up, set_power_up: 3, 2;
pub extended_note, set_extended_note: 4;
pub extended_coupon, set_extended_coupon: 5;
}
impl From<Configuration> for u8 {
fn from(c: Configuration) -> Self {
c.0
}
}
impl From<&Configuration> for u8 {
fn from(c: &Configuration) -> Self {
c.0
}
}
impl From<u8> for Configuration {
fn from(b: u8) -> Self {
Self(b & 0b11_1111)
}
}
impl fmt::Display for Configuration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
write!(f, r#""no_push": {}, "#, NoPush::from(self.no_push()))?;
write!(f, r#""barcode": {}, "#, Barcode::from(self.barcode()))?;
write!(f, r#""power_up": {}, "#, PowerUp::from(self.power_up()))?;
write!(
f,
r#""extended_note": {}, "#,
ExtendedNoteReporting::from(self.extended_note())
)?;
write!(
f,
r#""extended_coupon": {}"#,
ExtendedCouponReporting::from(self.extended_coupon())
)?;
write!(f, "}}")
}
}
bool_enum!(
NoPush,
r"
There are times when the device is unable to give credit for a note due to a
problem in transporting the document, and the document cannot be returned
to the customer. In these cases, this bit determines how such documents should
be handled.
Unset:
Recommended: Push non-credit notes into the stacker, and continue operating.
Set:
Do not push non-credit notes. Stall the device with the document still in the path. A
manager/technician level intervention is required.
"
);
bool_enum!(
Barcode,
r"
A barcode voucher is a document with a unique bar-coded identity number
encoded into it. These identity numbers are referenced against an external
database by the host to determine the validity and value of the voucher.
Notes: Barcode vouchers must be inserted “face up” with CFSC devices.
Unset:
Barcode vouchers are disabled.
Set:
Barcode vouchers are enabled.
"
);
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum PowerUp {
#[default]
A = 0b00,
B = 0b01,
C = 0b10,
Reserved = 0b11,
}
impl From<u8> for PowerUp {
fn from(b: u8) -> Self {
match b & bitmask::POWER_UP {
0b00 => Self::A,
0b01 => Self::B,
0b10 => Self::B,
_ => Self::Reserved,
}
}
}
impl From<&PowerUp> for &'static str {
fn from(val: &PowerUp) -> Self {
match val {
PowerUp::A => "a",
PowerUp::B => "b",
PowerUp::C => "c",
PowerUp::Reserved => "reserved",
}
}
}
impl From<PowerUp> for &'static str {
fn from(val: PowerUp) -> Self {
(&val).into()
}
}
impl fmt::Display for PowerUp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, r#""{}""#, <&str>::from(self))
}
}
bool_enum!(
ExtendedNoteReporting,
r"
Whether to use extended note reporting for bank note values.
Unset:
Use non-extended note reporting. Notes are reported as the generic Denom1 through 7.
Set:
Use extended note reporting. Notes are reported to the host via the Extended Omnibus.
- Note Reply packets. See Section 7.5.2 (Subtype 0x02)
Extended Note Specification Message for details.
- Notes are enabled / inhibited individually via the Set Note
Inhibits command. See (Subtype 0x03) Set Extended Note Inhibits for details.
- This bit is also associated with enabling / disabling the device.
See Section 4.9 Disabling the Device and Inhibiting Notes.
"
);
bool_enum!(
ExtendedCouponReporting,
r"
Handling for MEI/CPI coupon vouchers.
Unset:
Recommended: No special handling of generic coupons. MEI™ Generic Coupons (if
supported) are reported the same as a bank note of the same value.
Free vend coupons are not supported.
Set:
Enable detailed reporting of MEI Generic Coupons. The host
receives details on the type and identification of generic coupons fed
into the device. See the (Subtype 0x04) Set Escrow Timeout /
Extended Coupon response (7.5.4) for more details about the device’s
message when a coupon is detected.
"
);
pub mod index {
use crate::index::DATA;
pub const DENOMINATION: usize = DATA;
pub const OPERATIONAL_MODE: usize = DATA + 1;
pub const CONFIGURATION: usize = DATA + 2;
}
mod bitmask {
pub const ORIENTATION: u8 = 0b11;
pub const POWER_UP: u8 = 0b11;
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct OmnibusCommand {
buf: [u8; OMNIBUS_COMMAND],
}
impl OmnibusCommand {
pub fn new() -> Self {
let mut command = Self {
buf: [0u8; OMNIBUS_COMMAND],
};
command.init();
command.set_message_type(MessageType::OmnibusCommand);
command.set_escrow_mode(EscrowMode::Set);
command
}
}
pub trait OmnibusCommandOps: MessageOps {
fn denomination(&self) -> StandardDenomination {
self.buf()[index::DENOMINATION].into()
}
fn set_denomination(&mut self, denomination: StandardDenomination) {
self.buf_mut()[index::DENOMINATION] = denomination.into();
}
fn operational_mode(&self) -> OperationalMode {
self.buf()[index::OPERATIONAL_MODE].into()
}
fn set_operational_mode(&mut self, op: OperationalMode) {
self.buf_mut()[index::OPERATIONAL_MODE] = op.into();
}
fn orientation_control(&self) -> OrientationControl {
self.operational_mode().orientation_control().into()
}
fn set_orientation_control(&mut self, orientation: OrientationControl) {
let mut op = self.operational_mode();
op.set_orientation_control(orientation as u8);
self.set_operational_mode(op);
}
fn escrow_mode(&self) -> EscrowMode {
self.operational_mode().escrow_mode().into()
}
fn set_escrow_mode(&mut self, escrow_mode: EscrowMode) {
let mut op = self.operational_mode();
op.set_escrow_mode(escrow_mode.into());
self.set_operational_mode(op);
}
fn document_stack(&self) -> DocumentStack {
self.operational_mode().document_stack().into()
}
fn set_document_stack(&mut self, document_stack: DocumentStack) {
let mut op = self.operational_mode();
op.set_document_stack(document_stack.into());
self.set_operational_mode(op);
}
fn document_return(&self) -> DocumentReturn {
self.operational_mode().document_return().into()
}
fn set_document_return(&mut self, document_return: DocumentReturn) {
let mut op = self.operational_mode();
op.set_document_return(document_return.into());
self.set_operational_mode(op);
}
fn configuration(&self) -> Configuration {
self.buf()[index::CONFIGURATION].into()
}
fn set_configuration(&mut self, cfg: Configuration) {
self.buf_mut()[index::CONFIGURATION] = cfg.into();
}
fn no_push(&self) -> NoPush {
self.configuration().no_push().into()
}
fn set_no_push(&mut self, no_push: NoPush) {
let mut cfg = self.configuration();
cfg.set_no_push(no_push.into());
self.set_configuration(cfg);
}
fn barcode(&self) -> Barcode {
self.configuration().barcode().into()
}
fn set_barcode(&mut self, barcode: Barcode) {
let mut cfg = self.configuration();
cfg.set_barcode(barcode.into());
self.set_configuration(cfg);
}
fn power_up(&self) -> PowerUp {
self.configuration().power_up().into()
}
fn set_power_up(&mut self, power_up: PowerUp) {
let mut cfg = self.configuration();
cfg.set_power_up(power_up as u8);
self.set_configuration(cfg);
}
fn extended_note(&self) -> ExtendedNoteReporting {
self.configuration().extended_note().into()
}
fn set_extended_note(&mut self, extended_note: ExtendedNoteReporting) {
let mut cfg = self.configuration();
cfg.set_extended_note(extended_note.into());
self.set_configuration(cfg);
}
fn extended_coupon(&self) -> ExtendedCouponReporting {
self.configuration().extended_coupon().into()
}
fn set_extended_coupon(&mut self, extended_coupon: ExtendedCouponReporting) {
let mut cfg = self.configuration();
cfg.set_extended_coupon(extended_coupon.into());
self.set_configuration(cfg);
}
}
impl_message_ops!(OmnibusCommand);
impl_omnibus_command_ops!(OmnibusCommand);
impl fmt::Display for OmnibusCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{{")?;
write!(f, r#""acknak": {}, "#, self.acknak())?;
write!(f, r#""device_type": {}, "#, self.device_type())?;
write!(f, r#""message_type": {}, "#, self.message_type())?;
write!(f, r#""denomination": {}, "#, self.denomination())?;
write!(f, r#""operational_mode": {}, "#, self.operational_mode())?;
write!(f, r#""configuration": {}"#, self.configuration())?;
write!(f, "}}")
}
}
impl FlashDownloadMessage<FLASH_DATA_PACKET> for OmnibusCommand {
fn is_initial_poll(&self) -> bool {
true
}
fn packet_number(&self) -> u16 {
0xffff
}
fn set_packet_number(&mut self, _n: u16) {}
fn increment_packet_number(&mut self) -> u16 {
0xffff
}
fn data(&self) -> [u8; FLASH_DATA_PACKET] {
[0u8; FLASH_DATA_PACKET]
}
fn data_ref(&self) -> &[u8] {
self.buf()
}
fn set_data(&mut self, _data: &[u8]) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Result;
#[test]
#[rustfmt::skip]
fn test_omnibus_command_from_buf() -> Result<()> {
let msg_bytes = [
0x02, 0x08, 0x10,
0x7f, 0x00, 0x00,
0x03, 0x67,
];
let mut msg = OmnibusCommand::new();
msg.from_buf(msg_bytes.as_ref())?;
assert_eq!(msg.message_type(), MessageType::OmnibusCommand);
assert_eq!(msg.denomination(), StandardDenomination::all());
assert_eq!(msg.operational_mode(), OperationalMode::from(0));
assert_eq!(msg.configuration(), Configuration::from(0));
Ok(())
}
}