Skip to main content

huawei_modem/cmd/
sms.rs

1//! Receiving and managing SMS messages.
2//!
3//! Modems typically support *PDU mode* SMS commands - which operate using SMS PDUs (Protocol Data
4//! Units) - and *text mode* ones. PDUs are what you want, if your library can handle them (this one can; see the `pdu`
5//! module), since they lets you handle things like concatenated messages and emoji.
6//!
7//! Due to issues with properly parsing text-mode SMS responses without breaking, this library does
8//! *not* let you list messages in text mode, making it rather a pain to use.
9//!
10//! However, text mode *is* ostensibly useful if all you want to do is send simple 7-bit ASCII
11//! messages, and you don't care about receiving.
12//!
13//! **NB:** You *MUST* configure the modem for PDU mode by calling `send_sms_textmode` with `false`
14//! as argument before sending PDU-mode commands. Failure to do so will result in some fun times.
15use crate::{HuaweiModem};
16use crate::at::*;
17use crate::errors::*;
18use futures::Future;
19use crate::pdu::{HexData, Pdu, AddressType, DeliverPdu};
20use std::convert::TryFrom;
21use crate::util::HuaweiFromPrimitive;
22
23/// The storage status of an SMS message (returned in `AT+CMGL`).
24#[repr(u8)]
25#[derive(Fail, Debug, FromPrimitive, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
26pub enum MessageStatus {
27    /// Received and unread.
28    #[fail(display = "Unread")]
29    ReceivedUnread = 0,
30    /// Received and read.
31    #[fail(display = "Read")]
32    ReceivedRead = 1,
33    /// Outgoing and unsent.
34    #[fail(display = "Unsent")]
35    StoredUnsent = 2,
36    /// Outgoing and sent.
37    #[fail(display = "Sent")]
38    StoredSent = 3,
39    /// Any kind (used for `list_sms_pdu` only).
40    #[fail(display = "All messages")]
41    All = 4
42}
43/// Controls whether to notify the TE about new messages (from `AT+CNMI`).
44#[repr(u8)]
45#[derive(Debug, FromPrimitive, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
46pub enum NewMessageNotification {
47    /// Buffer new message indications in the ME, overwriting old indications if necessary.
48    BufferInMe = 0,
49    /// Send SMS-DELIVER indications to the TE, discarding them if they cannot be sent
50    /// (for example, when in online data mode)
51    SendDirectlyOrDiscard = 1,
52    /// Send SMS-DELIVER indications to the TE, buffering them and sending them later if they
53    /// cannot be sent.
54    ///
55    /// If you're aiming to use new message notification, this is probably what you want.
56    SendDirectlyOrBuffer = 2
57}
58/// Controls how new messages are saved, and how indications are sent to the TE (from `AT+CNMI`).
59#[repr(u8)]
60#[derive(Debug, FromPrimitive, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
61pub enum NewMessageStorage {
62    /// Do not route any SMS-DELIVER indications to the TE.
63    RouteNothing = 0,
64    /// Store SMS-DELIVER indications on the MT, and send a `+CMTI: <mem>,<index>` URC to
65    /// the TE.
66    ///
67    /// If you're aiming to use new message notification, this is probably what you want (and is
68    /// the only thing that has really been tested apart from `RouteNothing`).
69    StoreAndNotify = 1,
70    /// Directly forward the SMS-DELIVER indication to the TE, sending a `+CMT:
71    /// [<reserved>],<length><CR><LF><pdu>` URC to the TE.
72    SendDirectly = 2,
73    /// Store SMS-DELIVER indications on the MT, but don't notify the TE.
74    StoreAndDiscardNotification = 3
75}
76/// Controls which messages to delete (in `AT+CMGD`).
77#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
78pub enum DeletionOptions {
79    /// Delete the message stored at the index specified.
80    Indexed(u32),
81    /// Delete all read messages, keeping unread, sent, and unsent ones.
82    DeleteRead,
83    /// Delete all read and sent messages, keeping unread and unsent ones.
84    DeleteReadAndSent,
85    /// Delete all read, sent, and unsent messages, keeping unread ones.
86    DeleteReadAndOutgoing,
87    /// Delete all messages.
88    DeleteAll
89}
90/// An SMS message, received from a listing.
91#[derive(Clone, Debug)]
92pub struct SmsMessage {
93    /// The message status (read, unread, etc.)
94    pub status: MessageStatus,
95    /// The message's index in the modem's memory (useful if you want to delete it later).
96    pub index: u32,
97    /// The message's raw SMS PDU.
98    ///
99    /// Most implementations won't want to care about this, unless you want to store it somewhere
100    /// verbatim for whatever reason and parse it again later.
101    pub raw_pdu: Vec<u8>,
102    /// The decoded SMS PDU (Protocol Data Unit).
103    ///
104    /// This contains everything you probably want to get out of the message, like the sender and
105    /// the message text. Have a look at the documentation on `DeliverPdu` to figure out how to get
106    /// stuff out of it!
107    pub pdu: DeliverPdu
108}
109/// Controls whether to send new message indications to the TE when new messages arrive. Useful to
110/// avoid polling. (`AT+CNMI`)
111///
112/// Basically, playing with this setting means you can get the modem to send you a URC (c.f.
113/// `HuaweiModem::take_urc_rx`) when you get new messages, allowing you to intelligently list
114/// messages only when they're delivered instead of polling all the damn time and suffering delays.
115///
116/// Looking at the docs on the two `enums` provided as arguments will give you an idea as to which
117/// settings you'd want to use for this. 
118/// 
119/// Also note that this **may not necessarily be supported** by all modems! (in which case you'll have
120/// to fall back on polling).
121pub fn set_new_message_indications(modem: &mut HuaweiModem, mode: NewMessageNotification, mt: NewMessageStorage) -> impl Future<Item = (), Error = HuaweiError> {
122    modem.send_raw(AtCommand::Equals {
123        param: "+CNMI".into(),
124        value: AtValue::Array(vec![
125            AtValue::Integer(mode as u32),
126            AtValue::Integer(mt as u32)
127        ])
128    }).and_then(|pkt| {
129        pkt.assert_ok()?;
130        Ok(())
131    })
132}
133/// Set the address of the SMS Service Center (`AT+CSCA`).
134///
135/// You may need to configure this with the value provided by your network provider before being
136/// able to send SMSes.
137pub fn set_smsc_addr(modem: &mut HuaweiModem, sca: String, tosca: Option<AddressType>) -> impl Future<Item = (), Error = HuaweiError> {
138    let mut arr = vec![AtValue::String(sca)];
139    if let Some(t) = tosca {
140        let t: u8 = t.into();
141        arr.push(AtValue::Integer(t as u32));
142    }
143    modem.send_raw(AtCommand::Equals {
144        param: "+CSCA".into(),
145        value: AtValue::Array(arr)
146    }).and_then(|pkt| {
147        pkt.assert_ok()?;
148        Ok(())
149    })
150}
151/// Delete a message from the modem's message store (`AT+CMGD`).
152pub fn del_sms_pdu(modem: &mut HuaweiModem, del: DeletionOptions) -> impl Future<Item = (), Error = HuaweiError> {
153    use self::DeletionOptions::*;
154
155    let (index, delflag) = match del {
156        Indexed(i) => (i, 0),
157        DeleteRead => (0, 1),
158        DeleteReadAndSent => (0, 2),
159        DeleteReadAndOutgoing => (0, 3),
160        DeleteAll => (0, 4)
161    };
162    modem.send_raw(AtCommand::Equals {
163        param: "+CMGD".into(),
164        value: AtValue::Array(vec![AtValue::Integer(index), AtValue::Integer(delflag)])
165    }).and_then(|pkt| {
166        pkt.assert_ok()?;
167        Ok(())
168    })
169}
170/// List SMSes from the modem's message store, in PDU mode (`AT+CMGL`).
171///
172/// The modem must be configured properly for PDU mode first. See the module-level documentation for
173/// more information.
174pub fn list_sms_pdu(modem: &mut HuaweiModem, status: MessageStatus) -> impl Future<Item = Vec<SmsMessage>, Error = HuaweiError> {
175    modem.send_raw(AtCommand::Equals {
176        param: "+CMGL".into(),
177        value: AtValue::Integer(status as u32)
178    }).and_then(|pkt| {
179        pkt.assert_ok()?;
180        let mut cur = None;
181        let mut ret = vec![];
182        for resp in pkt.responses {
183            match resp {
184                AtResponse::InformationResponse { param, response } => {
185                    assert_eq!(&param, "+CMGL");
186                    let list = response.get_array()?;
187                    let index = list.get(0)
188                        .ok_or(HuaweiError::TypeMismatch)?
189                        .get_integer()?;
190                    let stat = list.get(1)
191                        .ok_or(HuaweiError::TypeMismatch)?
192                        .get_integer()?;
193                    let stat = MessageStatus::from_integer(*stat)?;
194                    cur = Some((*index, stat));
195                },
196                AtResponse::Unknown(ref st) => {
197                    if st.trim().len() > 0 {
198                        let cur = cur.take().ok_or(HuaweiError::TypeMismatch)?;
199                        let hex = HexData::decode(st.trim())?;
200                        let pdu = DeliverPdu::try_from(&hex as &[u8])?;
201                        ret.push(SmsMessage {
202                            index: cur.0,
203                            status: cur.1,
204                            raw_pdu: hex.into(),
205                            pdu
206                        })
207                    }
208                },
209                _ => {}
210            }
211        }
212        Ok(ret)
213    })
214}
215/// Set whether the modem will use text mode or not (`AT+CMGF`).
216pub fn set_sms_textmode(modem: &mut HuaweiModem, text: bool) -> impl Future<Item = (), Error = HuaweiError> {
217    modem.send_raw(AtCommand::Equals {
218        param: "+CMGF".into(),
219        value: AtValue::Integer(if text { 1 } else { 0 })
220    }).and_then(|pkt| {
221        pkt.assert_ok()?;
222        Ok(())
223    })
224}
225/// Send a message to a phone number, in text mode (`AT+CMGS`).
226///
227/// Using text mode is recommended against for all but the most simple of cases; see the module-level
228/// documentation for more.
229pub fn send_sms_textmode(modem: &mut HuaweiModem, to: String, msg: String) -> impl Future<Item = u32, Error = HuaweiError> {
230    let text = format!("AT+CMGS=\"{}\"\n{}\x1A", to, msg);
231    modem.send_raw(AtCommand::Text { text, expected: vec!["+CMGS".into()] })
232        .and_then(|pkt| {
233           let rpl = pkt.extract_named_response("+CMGS")?
234               .get_integer()?;
235           Ok(*rpl)
236        })
237}
238/// Send a message to a phone number, in PDU mode (`AT+CMGS`).
239///
240/// See the `Pdu` documentation for information on how PDUs are made.
241pub fn send_sms_pdu(modem: &mut HuaweiModem, pdu: &Pdu) -> impl Future<Item = u32, Error = HuaweiError> {
242    let (data, len) = pdu.as_bytes();
243    let text = format!("AT+CMGS={}\n{}\x1A", len, HexData(&data));
244    modem.send_raw(AtCommand::Text { text, expected: vec!["+CMGS".into()] })
245        .and_then(|pkt| {
246           let rpl = pkt.extract_named_response("+CMGS")?
247               .get_integer()?;
248           Ok(*rpl)
249        })
250}