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!(¶m, "+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}