elatec_twn4_simple/
lib.rs

1#![no_std]
2
3///
4/// Basic implementation of a Simple Protocol client for [Elatec
5/// TWN4](https://www.elatec-rfid.com/en/products/rfid-readerwriter-with-antenna/multi-frequency/twn4-multitech/)
6/// family devices, based upon [embedded-hal](https://github.com/japaric/embedded-hal).
7
8#[macro_use]
9extern crate bitflags;
10extern crate byteorder;
11#[macro_use(block)]
12extern crate nb;
13extern crate embedded_hal as hal;
14
15use commands::SimpleCmd;
16use core::marker::PhantomData;
17use core::time::Duration;
18use hal::serial;
19
20mod hex;
21
22/// Run modes for the reader
23pub mod mode {
24    /// The reader is active and ready to take commands
25    pub struct Run;
26    /// The reader is in a low-power state and can be awoken by LPCD, incoming data, or timeout
27    pub struct Sleep;
28    /// (Unimplemented) The reader is stopped, and can be awoken by incoming data.
29    pub struct Stop;
30}
31
32#[derive(Debug)]
33/// Elatec Multitech3-based RFID card reader
34pub struct Multitech3<RX, TX, MODE>
35where
36    RX: serial::Read<u8>,
37    TX: serial::Write<u8>,
38{
39    /// RX serial pin
40    rx: RX,
41    /// TX serial pin
42    tx: TX,
43    #[doc(hidden)]
44    _mode: PhantomData<MODE>,
45}
46
47/// Create a new instance of the reader accessed via the provided pins.
48pub fn new<RX, TX>(rx: RX, tx: TX) -> Multitech3<RX, TX, mode::Run>
49where
50    RX: serial::Read<u8>,
51    TX: serial::Write<u8>,
52{
53    Multitech3::<RX, TX, mode::Run> {
54        rx,
55        tx,
56        _mode: PhantomData,
57    }
58}
59
60impl<RX, TX, MODE> Multitech3<RX, TX, MODE>
61where
62    RX: serial::Read<u8>,
63    TX: serial::Write<u8>,
64{
65    /// Execute a blocking read of a single byte from the serial port
66    fn read_byte(&mut self) -> Result<u8, Error> {
67        match block!(self.rx.read()) {
68            Ok(c) => Ok(c),
69            Err(_e) => Err(Error::Read),
70        }
71    }
72
73    /// Execute a blocking read of a ASCII hex-encoded byte (i.e. two bytes) from the serial port
74    fn read_hex_byte(&mut self) -> Result<u8, Error> {
75        match hex::hex_byte_to_byte(self.read_byte()?, self.read_byte()?) {
76            Ok(b) => Ok(b),
77            Err(e) => Err(Error::Hex(e)),
78        }
79    }
80
81    /// Read and return the status of the last operation
82    fn read_err(&mut self) -> Result<ReaderError, Error> {
83        Ok(ReaderError::from(self.read_hex_byte()?))
84    }
85
86    /// Read the status of the last operation and save the rest of the line in `buf`
87    fn read_resp(&mut self, buf: &mut [u8]) -> Result<ReaderError, Error> {
88        let err = self.read_err()?;
89        match err {
90            ReaderError::None(_) => {
91                let mut i = 0;
92                loop {
93                    if i > buf.len() {
94                        return Err(Error::BufferFull);
95                    }
96
97                    let c = match block!(self.rx.read()) {
98                        Ok(c) => c,
99                        Err(_e) => return Err(Error::Read),
100                    };
101                    if c == '\r' as u8 {
102                        break;
103                    }
104                    buf[i] = c;
105                    i += 1;
106                }
107                Ok(ReaderError::None(i))
108            }
109            _ => Err(Error::Reader(err)),
110        }
111    }
112}
113
114impl<RX, TX> Multitech3<RX, TX, mode::Sleep>
115where
116    RX: serial::Read<u8>,
117    TX: serial::Write<u8>,
118{
119    /// Read the results of the sleep operation and return a running reader object
120    pub fn into_running(
121        mut self,
122    ) -> Result<(Multitech3<RX, TX, mode::Run>, WakeReason), (Self, Error)> {
123        let mut resp_buf = [0u8; 2];
124        match self.read_resp(&mut resp_buf) {
125            Ok(resp) => match resp {
126                ReaderError::None(_) => {
127                    let reason_code = match hex::hex_byte_to_byte(resp_buf[0], resp_buf[1]) {
128                        Ok(c) => c,
129                        Err(e) => return Err((self, Error::Hex(e))),
130                    };
131
132                    Ok((
133                        Multitech3::<RX, TX, mode::Run> {
134                            rx: self.rx,
135                            tx: self.tx,
136                            _mode: PhantomData,
137                        },
138                        WakeReason::from(reason_code),
139                    ))
140                }
141                _ => Err((self, Error::Reader(resp))),
142            },
143            Err(e) => Err((self, e)),
144        }
145    }
146}
147
148impl<RX, TX> Multitech3<RX, TX, mode::Run>
149where
150    RX: serial::Read<u8>,
151    TX: serial::Write<u8>,
152{
153    /// Write the commands to the serial port
154    fn issue_cmd<C: SimpleCmd>(&mut self, buf: &mut [u8], cmd: &C) -> Result<(), Error> {
155        let sz = cmd.get_cmd_hex(buf)?;
156        self.write_buf(&buf[..sz])?;
157        self.write_buf("\r".as_bytes())?;
158        Ok(())
159    }
160
161    /// Write the entire contents of `buf` to the serial port
162    fn write_buf(&mut self, buf: &[u8]) -> Result<(), Error> {
163        for c in buf.iter() {
164            match block!(self.tx.write(*c)) {
165                Ok(_) => {}
166                Err(_) => return Err(Error::Write),
167            }
168        }
169        Ok(())
170    }
171
172    /// Reset the reader; does not return a status
173    pub fn reset(&mut self) -> Result<(), Error> {
174        let cmd = commands::Reset;
175        self.issue_cmd(&mut [0u8; commands::Reset::CMD_LEN], &cmd)
176    }
177
178    /// Put the reader to sleep; will wake on low-power card detect or timeout
179    pub fn sleep(mut self, dur: Duration) -> Result<Multitech3<RX, TX, mode::Sleep>, Error> {
180        let sleep_cmd = commands::Sleep {
181            period: dur,
182            flags: commands::SleepFlags::WAKEUP_BY_TIMEOUT_MSK
183                | commands::SleepFlags::WAKEUP_BY_LPCD_MSK,
184        };
185        match self.issue_cmd(&mut [0u8; commands::Sleep::CMD_LEN], &sleep_cmd) {
186            Ok(_) => Ok(Multitech3::<RX, TX, mode::Sleep> {
187                rx: self.rx,
188                tx: self.tx,
189                _mode: PhantomData,
190            }),
191            Err(e) => Err(e),
192        }
193    }
194
195    /// Return the number of ticks the reader has been powered on
196    pub fn get_sys_ticks(&mut self) -> Result<u32, Error> {
197        const RESP_LEN: usize = 8;
198        let mut resp_buf = [0u8; RESP_LEN];
199        let cmd = commands::GetSysTicks;
200        match self.issue_cmd(&mut [0u8; commands::GetSysTicks::CMD_LEN], &cmd) {
201            Ok(_) => {
202                let resp = self.read_resp(&mut resp_buf)?;
203                match resp {
204                    ReaderError::None(n) => cmd.parse_response(&mut resp_buf[..n]),
205                    _ => Err(Error::Reader(resp)),
206                }
207            }
208            Err(e) => Err(e),
209        }
210    }
211
212    /// Return the reader version string in `buf`
213    pub fn get_version_string(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
214        let cmd = commands::GetVersionString {
215            max_resp_len: core::cmp::min(0xFF as usize, buf.len()) as u16,
216        };
217        match self.issue_cmd(&mut [0u8; commands::GetVersionString::CMD_LEN], &cmd) {
218            Ok(()) => {
219                let resp = self.read_resp(buf)?;
220                match resp {
221                    ReaderError::None(n) => cmd.parse_response(&mut buf[..n]),
222                    _ => Err(Error::Reader(resp)),
223                }
224            }
225            Err(e) => Err(e),
226        }
227    }
228
229    /// Execute a tag read operation and return the tag type and ID in `buf`
230    ///
231    /// This does no parsing of the tag information except to strip out TLV-esqe data sent during
232    /// transmission. The data is returned in the form:
233    /// ```
234    /// [type: u8] [id_bit_cnt: u8] [tag_id: u8|...]
235    /// ```
236    pub fn search_tag(&mut self, buf: &mut [u8]) -> Result<Option<usize>, Error> {
237        let cmd = commands::SearchTag;
238        match self.issue_cmd(&mut [0u8; commands::SearchTag::CMD_LEN], &cmd) {
239            Ok(()) => {
240                // because the card data might include b"\r", we cannot use read_resp and must
241                // instead read byte-by-byte, at which point we don't need to use parse_response,
242                // since we can just unpack it directly as we read the bytes.
243
244                let rdr_resp = ReaderError::from(self.read_hex_byte()?);
245                match rdr_resp {
246                    ReaderError::None(_) => {}
247                    _ => return Err(Error::Reader(rdr_resp)),
248                };
249
250                let result = self.read_hex_byte()?;
251                if result != 1u8 {
252                    return Ok(None);
253                }
254
255                if buf.len() < 2 {
256                    return Err(Error::BufferTooSmall(2));
257                }
258
259                buf[0] = self.read_hex_byte()?; // tag type
260                let bit_count = self.read_hex_byte()?; // id bit count
261
262                if bit_count == 0 {
263                    return Ok(None);
264                } else {
265                    buf[1] = bit_count;
266                }
267
268                let id_bytes = self.read_hex_byte()? as usize;
269
270                if buf.len() < id_bytes + 2 {
271                    return Err(Error::BufferTooSmall(id_bytes + 2));
272                }
273
274                let mut i = 0;
275                loop {
276                    if i == id_bytes {
277                        break;
278                    }
279                    buf[i + 2] = self.read_hex_byte()?;
280                    i += 1;
281                }
282
283                Ok(Some(id_bytes + 2))
284            }
285            Err(e) => Err(e),
286        }
287    }
288}
289
290#[derive(Debug)]
291/// Exceptions occurring during reader operations
292pub enum Error {
293    /// The reader issued a response that could not be processed as expected
294    BadResponse(usize),
295    /// The provided buffer was filled but more data awaits
296    BufferFull,
297    /// The supplied buffer is too small - the inner value is the required size
298    BufferTooSmall(usize),
299    /// A read of the serial port failed
300    ///
301    /// TODO make this properly bubble up <RX as hal::serial::Read>::Error somehow
302    Read,
303    /// A write to the serial port failed
304    Write,
305    /// The reader is still asleep and no bytes were waiting
306    StillAsleep,
307    /// An unspecified error occurred
308    Other,
309    /// The requested function is unimplemented
310    Unimplemented,
311    /// Communication with the reader succeeded, but the reader returned an error
312    Reader(ReaderError),
313    /// An attempt to manipulate hex bytes failed
314    Hex(hex::Error),
315}
316
317#[derive(Debug)]
318/// Error responses returned by the reader
319pub enum ReaderError {
320    /// ERR_NONE; the inner value contains the number of subsequent bytes read
321    None(usize),
322    /// ERR_UNKNOWN_FUNCTION
323    UnknownFunction,
324    /// ERR_MISSING_PARAMETER
325    MissingParameter,
326    /// ERR_UNUSED_PARAMETERS
327    UnusedParameters,
328    /// ERR_INVALID_FUNCTION
329    InvalidFunction,
330    /// ERR_PARSER
331    Parser,
332    /// Unknown/unrecognized; the inner value contains the (hex-decoded) error value
333    Unknown(u8),
334}
335
336impl From<u8> for ReaderError {
337    /// Convert a hex-decoded byte response into a ReaderError
338    fn from(code: u8) -> Self {
339        match code {
340            0 => ReaderError::None(0),
341            1 => ReaderError::UnknownFunction,
342            2 => ReaderError::MissingParameter,
343            3 => ReaderError::UnusedParameters,
344            4 => ReaderError::InvalidFunction,
345            5 => ReaderError::Parser,
346            _ => ReaderError::Unknown(code),
347        }
348    }
349}
350
351impl From<hex::Error> for Error {
352    /// Turn a hex conversion error into an Error
353    fn from(e: hex::Error) -> Self {
354        Error::Hex(e)
355    }
356}
357
358impl From<nb::Error<Error>> for Error {
359    /// Convert an `nb::Error` into an Error
360    fn from(e: nb::Error<Error>) -> Error {
361        match e {
362            nb::Error::Other(e) => e,
363            _ => Error::Other,
364        }
365    }
366}
367
368#[derive(Debug)]
369/// Reasons the reader has awoken from sleep
370pub enum WakeReason {
371    /// An unrecognized reason was returned during sleep
372    Unknown,
373    /// The USB input channel received at least one byte.
374    USB,
375    /// The input channel of COM1 received at least one byte.
376    COM1,
377    /// The input channel of COM2 received at least one byte.
378    COM2,
379    /// Sleep time ran out.
380    Timeout,
381    /// The presence of a transponder card was detected. (Supported by TWN4 MultiTech Nano only)
382    LPCD,
383}
384
385impl From<u8> for WakeReason {
386    /// Convert a hex-decoded sleep wake-up reason code into a WakeReason
387    fn from(n: u8) -> Self {
388        match n {
389            1 => WakeReason::USB,
390            2 => WakeReason::COM1,
391            3 => WakeReason::COM2,
392            4 => WakeReason::Timeout,
393            5 => WakeReason::LPCD,
394            _ => WakeReason::Unknown,
395        }
396    }
397}
398
399pub struct TagInfo<'i> {
400    pub tag_type: u8,
401    pub id_bit_count: u8,
402    pub id: &'i [u8],
403}
404
405mod commands {
406    use super::hex;
407    use super::Error;
408    use byteorder::{ByteOrder, LittleEndian};
409    use core::time::Duration;
410
411    fn copy_all_bytes(dest: &mut [u8], src: &[u8]) {
412        dest[..src.len()].copy_from_slice(&src[..]);
413    }
414
415    fn check_bufsz(l: usize, b: &[u8]) -> Result<(), Error> {
416        if b.len() < l {
417            Err(Error::BufferTooSmall(l))
418        } else {
419            Ok(())
420        }
421    }
422
423    /// Simple protocol commands
424    pub trait SimpleCmd {
425        /// The maximum length of a simple command in hex-encoded bytes
426        const CMD_LEN: usize;
427        /// The type of value returned in the parsed command response
428        type Response;
429
430        /// Retrieve hex-encoded command bytes to be sent to the reader into `buf`
431        fn get_cmd_hex(&self, buf: &mut [u8]) -> Result<usize, Error>;
432        /// Parse the hex-encoded response (excl. response code) in `buf`
433        fn parse_response(&self, _buf: &mut [u8]) -> Result<Self::Response, Error> {
434            Err(Error::Unimplemented)
435        }
436    }
437
438    /// Reset the firmware (including any running App)
439    pub struct Reset;
440
441    impl SimpleCmd for Reset {
442        const CMD_LEN: usize = 2;
443        type Response = ();
444
445        fn get_cmd_hex(&self, buf: &mut [u8]) -> Result<usize, Error> {
446            check_bufsz(Reset::CMD_LEN, buf)?;
447            copy_all_bytes(buf, "0001".as_bytes());
448            Ok(2)
449        }
450    }
451
452    bitflags! {
453        /// Sleep mode flags used in the sleep command
454        pub struct SleepFlags: u32 {
455            const WAKEUP_BY_USB_MSK = 0x1;
456            const WAKEUP_BY_COM1_MSK = 0x2;
457            const WAKEUP_BY_COM2_MSK = 0x4;
458            const WAKEUP_BY_TIMEOUT_MSK = 0x10;
459            const WAKEUP_BY_LPCD_MSK = 0x20;
460            const SLEEPMODE_SLEEP = 0x0000;
461            const SLEEPMODE_STOP = 0x0100;
462        }
463    }
464
465    /// The device enters the sleep state for a specified time.
466    ///
467    /// During sleep state, the device reduces the current consumption to a value, which depends on the mode of sleep.
468    pub struct Sleep {
469        pub period: Duration,
470        pub flags: SleepFlags,
471    }
472
473    impl SimpleCmd for Sleep {
474        const CMD_LEN: usize = 20;
475        type Response = ();
476
477        fn get_cmd_hex(&self, buf: &mut [u8]) -> Result<usize, Error> {
478            check_bufsz(Sleep::CMD_LEN, buf)?;
479
480            copy_all_bytes(buf, "0007".as_bytes());
481            let mut u32_buf = [0u8; 4];
482            LittleEndian::write_u32(
483                &mut u32_buf,
484                self.period.as_secs() as u32 * 1000 + self.period.subsec_millis(),
485            );
486            hex::bytes_to_hex(&u32_buf, &mut buf[4..12])?;
487            LittleEndian::write_u32(&mut u32_buf, self.flags.bits());
488            hex::bytes_to_hex(&u32_buf, &mut buf[12..20])?;
489            Ok(Self::CMD_LEN)
490        }
491    }
492
493    /// Retrieve number of system ticks, specified in multiple of 1 milliseconds, since startup of the firmware.
494    pub struct GetSysTicks;
495
496    impl SimpleCmd for GetSysTicks {
497        const CMD_LEN: usize = 4;
498        type Response = u32;
499
500        fn get_cmd_hex(&self, buf: &mut [u8]) -> Result<usize, Error> {
501            check_bufsz(GetSysTicks::CMD_LEN, buf)?;
502
503            copy_all_bytes(buf, "0003".as_bytes());
504            Ok(GetSysTicks::CMD_LEN)
505        }
506
507        fn parse_response(&self, buf: &mut [u8]) -> Result<u32, Error> {
508            if buf.len() != 8 {
509                return Err(Error::BadResponse(buf.len()));
510            }
511
512            let mut result_buf = [0u8; 4];
513            hex::hex_to_bytes(&buf, &mut result_buf)?;
514            Ok(LittleEndian::read_u32(&result_buf))
515        }
516    }
517
518    /// Retrieve version information.
519    pub struct GetVersionString {
520        pub max_resp_len: u16,
521    }
522
523    impl SimpleCmd for GetVersionString {
524        const CMD_LEN: usize = 6;
525        type Response = usize;
526
527        fn get_cmd_hex(&self, buf: &mut [u8]) -> Result<usize, Error> {
528            check_bufsz(GetVersionString::CMD_LEN, buf)?;
529            copy_all_bytes(buf, "0004".as_bytes());
530            hex::bytes_to_hex(&[0xFFu8], &mut buf[4..])?;
531            Ok(GetVersionString::CMD_LEN)
532        }
533
534        fn parse_response(&self, buf: &mut [u8]) -> Result<usize, Error> {
535            const MAX_RESP_LEN: usize = 0xFF;
536            let mut resp_len = [0u8];
537            hex::hex_to_bytes(&[buf[0], buf[1]], &mut resp_len)?;
538            let resp_len = resp_len[0] as usize;
539
540            if resp_len * 2 != buf.len() - 2 || resp_len > MAX_RESP_LEN {
541                return Err(Error::BadResponse(resp_len));
542            }
543
544            let mut resp_buf = [0u8; MAX_RESP_LEN];
545            hex::hex_to_bytes(&buf[2..], &mut resp_buf)?;
546            copy_all_bytes(buf, &resp_buf[..resp_len]);
547            Ok(resp_len)
548        }
549    }
550
551    /// Use this function to search a transponder in the reading range of TWN4.
552    ///
553    /// TWN4 is searching for all types of transponders, which have been specified via function
554    /// SetTagTypes (unimplemented in this library). If a transponder has been found, tag type,
555    /// length of ID and ID data itself are returned.
556    pub struct SearchTag;
557
558    impl SimpleCmd for SearchTag {
559        const CMD_LEN: usize = 6;
560        type Response = Option<usize>;
561
562        fn get_cmd_hex(&self, buf: &mut [u8]) -> Result<usize, Error> {
563            check_bufsz(SearchTag::CMD_LEN, buf)?;
564            copy_all_bytes(buf, "0500".as_bytes());
565            hex::bytes_to_hex(&[0xFFu8], &mut buf[4..])?;
566            Ok(SearchTag::CMD_LEN)
567        }
568    }
569}