huawei_modem/
lib.rs

1//! The `huawei-modem` library provides a set of utilities for interfacing with USB 3G/HSDPA/UMTS
2//! modems (particularly Huawei models, like the E220 and E3531) that use the Hayes/AT command set.
3//!
4//! At present, the library's main consumer is
5//! [sms-irc](https://git.theta.eu.org/sms-irc.git/about/). In particular, it may be helpful to
6//! look at [modem.rs](https://git.theta.eu.org/sms-irc.git/tree/src/modem.rs) inside that project
7//! to get a feel for how to use this library, as well as looking inside the `examples/`
8//! subdirectory to see some simple SMS sending/receiving examples.
9
10#[macro_use] extern crate log;
11#[macro_use] extern crate failure_derive;
12#[macro_use] extern crate nom;
13#[macro_use] extern crate derive_is_enum_variant;
14#[macro_use] extern crate num_derive;
15
16use std::fs::{File, OpenOptions};
17use tokio_file_unix::File as FileNb;
18use crate::codec::AtCodec;
19use crate::at::{AtResponse, AtCommand};
20use futures::{Future, Poll};
21use futures::sync::{oneshot, mpsc};
22use crate::future::{ModemRequest, ModemResponse, HuaweiModemFuture};
23use tokio_core::reactor::Handle;
24use tokio_codec::Decoder;
25pub use crate::errors::HuaweiResult;
26
27/// Bog-standard boxed future alias.
28pub type HuaweiFuture<T> = Box<dyn Future<Item = T, Error = errors::HuaweiError>>;
29
30macro_rules! check_offset {
31    ($b:ident, $offset:ident, $reason:expr) => {
32        if $b.get($offset).is_none() {
33            return Err(HuaweiError::InvalidPdu(concat!("Offset check failed for: ", $reason)));
34        }
35    }
36}
37
38pub mod error_codes;
39pub mod errors;
40pub mod gsm_encoding;
41pub mod at;
42pub mod pdu;
43mod parse;
44pub mod codec;
45pub mod cmd;
46mod util;
47mod future;
48
49use std::path::Path;
50use crate::errors::HuaweiError;
51
52/// Future representing a response from the modem.
53pub struct ModemResponseFuture {
54    rx: Result<oneshot::Receiver<ModemResponse>, ()>
55}
56impl Future for ModemResponseFuture {
57    type Item = ModemResponse;
58    type Error = HuaweiError;
59
60    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
61        match self.rx {
62            Ok(ref mut rx) => Ok(rx.poll()?),
63            Err(_) => Err(HuaweiError::FutureDied)
64        }
65    }
66}
67/// A connection to an AT/Huawei-style modem.
68pub struct HuaweiModem {
69    tx: mpsc::UnboundedSender<ModemRequest>,
70    urc: Option<mpsc::UnboundedReceiver<AtResponse>>
71}
72impl HuaweiModem {
73    /// Start talking to the modem at a specified file path.
74    pub fn new_from_path<P: AsRef<Path>>(path: P, h: &Handle) -> HuaweiResult<Self> {
75        let file = OpenOptions::new()
76            .read(true)
77            .write(true)
78            .open(path)?;
79        Self::new_from_file(file, h)
80    }
81    /// Start talking to the modem represented by a given file handle.
82    ///
83    /// The file handle provided must support non-blocking IO for this method to work.
84    pub fn new_from_file(f: File, h: &Handle) -> HuaweiResult<Self> {
85        let ev = FileNb::new_nb(f)?.into_io(h)?;
86        let framed = AtCodec.framed(ev);
87        let (tx, rx) = mpsc::unbounded();
88        let (urctx, urcrx) = mpsc::unbounded();
89        let fut = HuaweiModemFuture::new(framed, rx, urctx);
90        h.spawn(fut.map_err(|e| {
91            error!("HuaweiModemFuture failed: {}", e);
92            error!("Backtrace: {}", e.backtrace());
93            ()
94        }));
95        Ok(Self { tx, urc: Some(urcrx) })
96    }
97    /// Retrieve the URC (Unsolicited Result Code) receiver from the modem (it can only be taken
98    /// once).
99    ///
100    /// This gives you an `UnboundedReceiver` that provides you with a stream of `AtResponse`s that
101    /// are *unsolicited*, i.e. they are proactive notifications from the modem of something
102    /// happening. On some modems, you may well receive a steady stream of random updates.
103    ///
104    /// This can be useful when you configure your modem for message notification on delivery (see
105    /// `cmd::sms::set_new_message_indications`), in which case you'll want to check for `CNMI`
106    /// URCs through this receiver and use that to poll for new messages.
107    pub fn take_urc_rx(&mut self) -> Option<mpsc::UnboundedReceiver<AtResponse>> {
108        self.urc.take()
109    }
110    /// Send a raw AT command to the modem.
111    pub fn send_raw(&mut self, cmd: AtCommand) -> ModemResponseFuture {
112        let (tx, rx) = oneshot::channel();
113        let expected = cmd.expected();
114        let req = ModemRequest {
115            command: cmd,
116            notif: tx,
117            expected
118        };
119        if let Err(_) = self.tx.unbounded_send(req) {
120            ModemResponseFuture { rx: Err(()) }
121        }
122        else {
123            ModemResponseFuture { rx: Ok(rx) }
124        }
125    }
126}
127