ebds/extended_note_inhibits/
command.rs

1use crate::std;
2
3use crate::{
4    error::{Error, Result},
5    impl_default, impl_extended_ops, impl_message_ops, impl_omnibus_extended_command,
6    len::SET_EXTENDED_NOTE_INHIBITS_BASE,
7    std::fmt,
8    ExtendedCommand, ExtendedCommandOps, ExtendedNoteReporting, MessageOps, MessageType,
9    OmnibusCommandOps,
10};
11
12/// CFSC device extended note enable byte length, see section 7.5.3
13pub const CFSC_ENABLE_LEN: usize = 8;
14/// SC device extended note enable byte length, see section 7.5.3
15pub const SC_ENABLE_LEN: usize = 19;
16
17mod bitmask {
18    pub const ENABLE_NOTE: u8 = 0b111_1111;
19}
20
21pub mod index {
22    pub const ENABLE_NOTE: usize = 7;
23}
24
25bitfield! {
26    /// Represents enabled notes in the extended note table.
27    #[derive(Clone, Copy, Debug, Default, PartialEq)]
28    pub struct EnableNote(u16);
29    u16;
30    pub note1, set_note1: 0;
31    pub note2, set_note2: 1;
32    pub note3, set_note3: 2;
33    pub note4, set_note4: 3;
34    pub note5, set_note5: 4;
35    pub note6, set_note6: 5;
36    pub note7, set_note7: 6;
37    pub note_index, set_note_index: 15, 7;
38}
39
40impl EnableNote {
41    pub const LEN: usize = 7;
42
43    /// Creates an [EnableNote] with no bits set.
44    pub const fn none() -> Self {
45        Self(0)
46    }
47
48    /// Creates an [EnableNote] with all bits set.
49    pub const fn all() -> Self {
50        Self(bitmask::ENABLE_NOTE as u16)
51    }
52
53    /// Get the length of the [EnableNote] bitfield.
54    pub const fn len() -> usize {
55        Self::LEN
56    }
57
58    /// Sets an index to enable.
59    ///
60    /// Valid range is [1, 7] (inclusive).
61    pub fn set_index(&mut self, index: usize) -> Result<()> {
62        match index {
63            1 => self.set_note1(true),
64            2 => self.set_note2(true),
65            3 => self.set_note3(true),
66            4 => self.set_note4(true),
67            5 => self.set_note5(true),
68            6 => self.set_note6(true),
69            7 => self.set_note7(true),
70            _ => return Err(Error::failure("invalid enable index")),
71        }
72        Ok(())
73    }
74}
75
76impl From<&[bool]> for EnableNote {
77    fn from(b: &[bool]) -> Self {
78        let mut inner = 0u16;
79        // only allow a max of
80        let end = std::cmp::min(b.len(), Self::len());
81        for (i, &set) in b[..end].iter().enumerate() {
82            let bit = if set { 1 } else { 0 };
83            inner |= bit << i;
84        }
85        Self(inner)
86    }
87}
88
89impl<const N: usize> From<&[bool; N]> for EnableNote {
90    fn from(b: &[bool; N]) -> Self {
91        b.as_ref().into()
92    }
93}
94
95impl<const N: usize> From<[bool; N]> for EnableNote {
96    fn from(b: [bool; N]) -> Self {
97        (&b).into()
98    }
99}
100
101impl From<u8> for EnableNote {
102    fn from(b: u8) -> Self {
103        Self((b & bitmask::ENABLE_NOTE) as u16)
104    }
105}
106
107impl From<&EnableNote> for u8 {
108    fn from(e: &EnableNote) -> u8 {
109        (e.0 & 0xff) as u8
110    }
111}
112
113impl From<EnableNote> for u8 {
114    fn from(e: EnableNote) -> u8 {
115        (&e).into()
116    }
117}
118
119impl fmt::Display for EnableNote {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        let idx = self.note_index();
122
123        write!(f, "{{")?;
124        write!(f, r#""note_{idx}": {}, "#, self.note1())?;
125        write!(f, r#""note_{}": {}, "#, idx.saturating_add(1), self.note2())?;
126        write!(f, r#""note_{}": {}, "#, idx.saturating_add(2), self.note3())?;
127        write!(f, r#""note_{}": {}, "#, idx.saturating_add(3), self.note4())?;
128        write!(f, r#""note_{}": {}, "#, idx.saturating_add(4), self.note5())?;
129        write!(f, r#""note_{}": {}, "#, idx.saturating_add(5), self.note6())?;
130        write!(f, r#""note_{}": {}"#, idx.saturating_add(6), self.note7())?;
131        write!(f, "}}")
132    }
133}
134
135/// Set Extended Note Inhibits - Request (Subtype 0x03)
136///
137/// This command is used to control the acceptance of bank notes on a note type basis. It is only used when
138/// the device is running in extended note mode (section 4.2.2).
139///
140/// The generic parameter is the sum of
141/// [SET_EXTENDED_NOTE_INHIBITS_BASE](crate::len::SET_EXTENDED_NOTE_INHIBITS_BASE),
142/// and the number of enable note bytes (either [CFSC_ENABLE_LEN] or [SC_ENABLE_LEN]).
143///
144/// The Set Extended Note Inhibits is formatted as follows:
145///
146/// | Name  | STX  | LEN  | CTRL | Subtype | Data 0 | Data 1 | Data 2 | Enable 1 | ...    | Enable N | ETX    | CHK    |
147/// |:------|:----:|:----:|:----:|:-------:|:------:|:------:|:------:|:--------:|:------:|:--------:|:------:|:------:|
148/// | Byte  | 0    | 1    | 2    | 3       | 4      | 5      | 6      | 7        | ...    | LL - 3   | LL - 2 | LL - 1 |
149/// | Value | 0x02 | LL   | 0x7n | 0x03    | nn     | nn     | nn     | nn       | nn     | nn       | 0x03   | zz     |
150///
151/// | **CFSC** |
152/// |:--------:|
153///
154/// Supports up to 50 denomination types, therefore the command requires 8 extended data bytes.
155///
156/// This will make the message length 0x11:
157///
158/// | Byte     | Bit 6   | Bit 5   | Bit 4   | Bit 3   | Bit 2   | Bit 1   | Bit 0   |
159/// |:---------|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|:-------:|
160/// | Enable 1 | Note 7  | Note 6  | Note 5  | Note 4  | Note 3  | Note 2  | Note 1  |
161/// | Enable 2 | Note 14 | Note 13 | Note 12 | Note 11 | Note 10 | Note 9  | Note 8  |
162/// | Enable 3 | Note 21 | Note 20 | Note 19 | Note 18 | Note 17 | Note 16 | Note 15 |
163/// | Enable 4 | Note 28 | Note 27 | Note 26 | Note 25 | Note 24 | Note 23 | Note 22 |
164/// | Enable 5 | Note 35 | Note 34 | Note 33 | Note 32 | Note 31 | Note 30 | Note 29 |
165/// | Enable 6 | Note 42 | Note 41 | Note 40 | Note 39 | Note 38 | Note 37 | Note 36 |
166/// | Enable 7 | Note 49 | Note 48 | Note 47 | Note 46 | Note 45 | Note 44 | Note 43 |
167/// | Enable 8 | -       | -       | -       | -       | -       | -       | Note 50 |
168///
169/// | **SC Adv** | **SCR** |
170/// |:----------:|:-------:|
171///
172/// Supports up to 128 denomination types, therefore the command requires 19 extended
173/// data bytes.
174///
175/// This will make the message length 0x1C:
176///
177/// | Byte      | Bit 6    | Bit 5    | Bit 4    | Bit 3    | Bit 2    | Bit 1    | Bit 0    |
178/// |:----------|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|:--------:|
179/// | Enable 1  | Note 7   | Note 6   | Note 5   | Note 4   | Note 3   | Note 2   | Note 1   |
180/// | Enable 2  | Note 14  | Note 13  | Note 12  | Note 11  | Note 10  | Note 9   | Note 8   |
181/// | Enable 3  | Note 21  | Note 20  | Note 19  | Note 18  | Note 17  | Note 16  | Note 15  |
182/// | Enable 4  | Note 28  | Note 27  | Note 26  | Note 25  | Note 24  | Note 23  | Note 22  |
183/// | Enable 5  | Note 35  | Note 34  | Note 33  | Note 32  | Note 31  | Note 30  | Note 29  |
184/// | Enable 6  | Note 42  | Note 41  | Note 40  | Note 39  | Note 38  | Note 37  | Note 36  |
185/// | Enable 7  | Note 49  | Note 48  | Note 47  | Note 46  | Note 45  | Note 44  | Note 43  |
186/// | Enable 8  | Note 56  | Note 55  | Note 54  | Note 53  | Note 52  | Note 51  | Note 50  |
187/// | Enable 9  | Note 63  | Note 62  | Note 61  | Note 60  | Note 59  | Note 58  | Note 57  |
188/// | Enable 10 | Note 70  | Note 69  | Note 68  | Note 67  | Note 66  | Note 65  | Note 64  |
189/// | Enable 11 | Note 77  | Note 76  | Note 75  | Note 74  | Note 73  | Note 72  | Note 71  |
190/// | Enable 12 | Note 84  | Note 83  | Note 82  | Note 81  | Note 80  | Note 79  | Note 78  |
191/// | Enable 13 | Note 91  | Note 90  | Note 89  | Note 88  | Note 87  | Note 86  | Note 85  |
192/// | Enable 14 | Note 98  | Note 97  | Note 98  | Note 97  | Note 96  | Note 95  | Note 94  |
193/// | Enable 15 | Note 105 | Note 104 | Note 103 | Note 102 | Note 101 | Note 100 | Note 99  |
194/// | Enable 16 | Note 112 | Note 111 | Note 110 | Note 109 | Note 108 | Note 107 | Note 106 |
195/// | Enable 17 | Note 119 | Note 118 | Note 117 | Note 116 | Note 115 | Note 114 | Note 113 |
196/// | Enable 18 | Note 126 | Note 125 | Note 124 | Note 123 | Note 122 | Note 121 | Note 120 |
197/// | Enable 19 | -        | -        | -        | -        | -        | Note 128 | Note 127 |
198///
199/// If the bit equals 1 then the note is enabled.
200#[derive(Clone, Copy, Debug, PartialEq)]
201pub struct SetExtendedNoteInhibits<const M: usize, const N: usize> {
202    buf: [u8; M],
203}
204
205impl<const M: usize, const N: usize> SetExtendedNoteInhibits<M, N> {
206    /// The length of enable note bytes (N - [SET_EXTENDED_NOTE_INHIBITS_BASE]).
207    pub const ENABLE_NOTE_LEN: usize = N;
208
209    /// Creates a new [SetExtendedNoteInhibits] message.
210    pub fn new() -> Self {
211        assert!(
212            M == SET_EXTENDED_NOTE_INHIBITS_BASE + CFSC_ENABLE_LEN
213                || M == SET_EXTENDED_NOTE_INHIBITS_BASE + SC_ENABLE_LEN
214        );
215
216        let mut message = Self { buf: [0u8; M] };
217
218        message.init();
219        message.set_message_type(MessageType::Extended);
220        message.set_extended_note(ExtendedNoteReporting::Set);
221        message.set_extended_command(ExtendedCommand::SetExtendedNoteInhibits);
222
223        message
224    }
225
226    /// Get the table of enabled note bytes.
227    pub fn enabled_notes(&self) -> [EnableNote; N] {
228        let mut ret = [EnableNote::none(); N];
229
230        for (&note, set_note) in self.buf
231            [index::ENABLE_NOTE..index::ENABLE_NOTE + Self::ENABLE_NOTE_LEN]
232            .iter()
233            .zip(ret.iter_mut())
234        {
235            *set_note = EnableNote::from(note);
236        }
237
238        ret
239    }
240
241    /// Sets the enable note bytes
242    ///
243    /// Example: `notes[0]` sets `Enable 1`, `notes[1]` sets `Enable 2` etc.
244    ///
245    /// Note: maximum of [ENABLE_NOTE_LEN](Self::ENABLE_NOTE_LEN) [EnableNote]s can be set, any extra supplied are ignored.
246    pub fn set_enabled_notes(&mut self, notes: &[EnableNote]) {
247        let max_len = std::cmp::min(notes.len(), Self::ENABLE_NOTE_LEN);
248
249        for (i, note) in notes[..max_len].iter().enumerate() {
250            self.buf[index::ENABLE_NOTE + i] = note.into();
251        }
252    }
253}
254
255impl<const M: usize, const N: usize> fmt::Display for SetExtendedNoteInhibits<M, N> {
256    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257        write!(f, "{{")?;
258        write!(f, r#""message_type": {}, "#, self.message_type())?;
259        write!(f, r#""extended_command": {}, "#, self.extended_command())?;
260        write!(f, r#""denomination": {}, "#, self.denomination())?;
261        write!(f, r#""operational_mode": {}, "#, self.operational_mode())?;
262        write!(f, r#""configuration": {}, "#, self.configuration())?;
263        write!(f, r#""enabled_notes": ["#)?;
264
265        let mut notes = self.enabled_notes();
266        for (i, note) in notes.iter_mut().enumerate() {
267            if i != 0 {
268                write!(f, ", ")?;
269            }
270            note.set_note_index(((i * 7) + 1) as u16);
271            write!(f, "{note}")?;
272        }
273
274        write!(f, "]}}")
275    }
276}
277
278pub const CFSC_ENABLE_FULL_LEN: usize = SET_EXTENDED_NOTE_INHIBITS_BASE + CFSC_ENABLE_LEN;
279pub const SC_ENABLE_FULL_LEN: usize = SET_EXTENDED_NOTE_INHIBITS_BASE + SC_ENABLE_LEN;
280
281pub type SetExtendedNoteInhibitsCFSC =
282    SetExtendedNoteInhibits<CFSC_ENABLE_FULL_LEN, CFSC_ENABLE_LEN>;
283pub type SetExtendedNoteInhibitsSC = SetExtendedNoteInhibits<SC_ENABLE_FULL_LEN, SC_ENABLE_LEN>;
284
285impl_default!(SetExtendedNoteInhibits, M, N);
286impl_message_ops!(SetExtendedNoteInhibits, M, N);
287impl_extended_ops!(SetExtendedNoteInhibits, M, N);
288impl_omnibus_extended_command!(SetExtendedNoteInhibits, M, N);
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293    use crate::Result;
294
295    #[test]
296    #[rustfmt::skip]
297    fn test_query_set_extended_note_inhibits_from_bytes() -> Result<()> {
298
299        // CFSC note table
300        let msg_bytes = [
301            // STX | LEN | Message type | Subtype
302            0x02, 0x11, 0x70, 0x03,
303            // Data
304            0x00, 0x00, 0x00,
305            // Enable
306            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
307            // ETX | Checksum
308            0x03, 0x63,
309        ];
310
311        let mut msg = SetExtendedNoteInhibitsCFSC::new();
312        msg.from_buf(msg_bytes.as_ref())?;
313
314        assert_eq!(msg.message_type(), MessageType::Extended);
315        assert_eq!(msg.extended_command(), ExtendedCommand::SetExtendedNoteInhibits);
316
317        let exp_enabled = [
318            EnableNote::from(1), EnableNote::none(), EnableNote::none(), EnableNote::none(),
319            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
320        ];
321
322        assert_eq!(msg.enabled_notes(), exp_enabled);
323
324        // SC note table
325        let msg_bytes = [
326            // STX | LEN | Message type | Subtype
327            0x02, 0x1c, 0x70, 0x03,
328            // Data
329            0x00, 0x00, 0x00,
330            // Enable
331            0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
332            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
333            0x00, 0x00, 0x00,
334            // ETX | Checksum
335            0x03, 0x6e,
336        ];
337
338        let mut msg = SetExtendedNoteInhibitsSC::new();
339        msg.from_buf(msg_bytes.as_ref())?;
340
341        assert_eq!(msg.message_type(), MessageType::Extended);
342        assert_eq!(msg.extended_command(), ExtendedCommand::SetExtendedNoteInhibits);
343
344        let exp_enabled = [
345            EnableNote::from(1), EnableNote::none(), EnableNote::none(), EnableNote::none(),
346            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
347            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
348            EnableNote::none(), EnableNote::none(), EnableNote::none(),  EnableNote::none(),
349            EnableNote::none(), EnableNote::none(), EnableNote::none(),
350        ];
351
352        assert_eq!(msg.enabled_notes(), exp_enabled);
353
354        Ok(())
355    }
356
357    #[test]
358    fn test_display() -> Result<()> {
359        let enabled = SetExtendedNoteInhibitsCFSC::new();
360        let enabled_disp = r#"{"message_type": "Extended", "extended_command": "SetExtendedNoteInhibits", "denomination": "None", "operational_mode": {"orientation_control": "one way", "escrow_mode": "unset", "document_stack": "unset", "document_return": "unset"}, "configuration": {"no_push": "unset", "barcode": "unset", "power_up": "a", "extended_note": "set", "extended_coupon": "unset"}, "enabled_notes": [{"note_1": false, "note_2": false, "note_3": false, "note_4": false, "note_5": false, "note_6": false, "note_7": false}, {"note_8": false, "note_9": false, "note_10": false, "note_11": false, "note_12": false, "note_13": false, "note_14": false}, {"note_15": false, "note_16": false, "note_17": false, "note_18": false, "note_19": false, "note_20": false, "note_21": false}, {"note_22": false, "note_23": false, "note_24": false, "note_25": false, "note_26": false, "note_27": false, "note_28": false}, {"note_29": false, "note_30": false, "note_31": false, "note_32": false, "note_33": false, "note_34": false, "note_35": false}, {"note_36": false, "note_37": false, "note_38": false, "note_39": false, "note_40": false, "note_41": false, "note_42": false}, {"note_43": false, "note_44": false, "note_45": false, "note_46": false, "note_47": false, "note_48": false, "note_49": false}, {"note_50": false, "note_51": false, "note_52": false, "note_53": false, "note_54": false, "note_55": false, "note_56": false}]}"#;
361
362        assert_eq!(format!("{enabled}").as_str(), enabled_disp);
363
364        Ok(())
365    }
366}