2018-02-09 11:24:27 +00:00
/*
* Copyright ( c ) 2017 , Arm Limited and affiliates .
* SPDX - License - Identifier : Apache - 2.0
*
* Licensed under the Apache License , Version 2.0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
# include <time.h>
# include <stdlib.h>
# include "mbed_wait_api.h"
# include "AT_CellularSMS.h"
# include "CellularUtil.h"
# include "CellularLog.h"
using namespace mbed_cellular_util ;
using namespace mbed ;
using namespace std ;
# define CTRL_Z "\x1a"
# define ESC "\x1b"
const uint8_t SMS_STATUS_SIZE = 12 + 1 ;
const uint8_t FIRST_OCTET_DELIVER_SUBMIT = 17 ;
const uint8_t TP_VALIDITY_PERIOD_24_HOURS = 167 ;
const uint8_t TP_PROTOCOL_IDENTIFIER = 0 ;
const uint8_t SMS_DATA_CODING_SCHEME = 0 ;
const uint8_t SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE = 134 ;
const uint8_t SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE = 153 ;
# define NVAM '?' // Not Valid ascii, ISO-8859-1 mark
// mapping table from 7-bit GSM to ascii (ISO-8859-1)
static const int gsm_to_ascii [ ] = {
64 , // 0
163 , // 1
36 , // 2
165 , // 3
232 , // 4
233 , // 5
249 , // 6
236 , // 7
242 , // 8
199 , // 9
10 , // 10
216 , // 11
248 , // 12
13 , // 13
197 , // 14
229 , // 15
NVAM , // 16
95 , // 17
NVAM , // 18
NVAM , // 19
NVAM , // 20
NVAM , // 21
NVAM , // 22
NVAM , // 23
NVAM , // 24
NVAM , // 25
NVAM , // 26
27 , // 27
198 , // 28
230 , // 29
223 , // 30
201 , // 31
32 , // 32
33 , // 33
34 , // 34
35 , // 35
164 , // 36
37 , // 37
38 , // 38
39 , // 39
40 , // 40
41 , // 41
42 , // 42
43 , // 43
44 , // 44
45 , // 45
46 , // 46
47 , // 47
48 , // 48
49 , // 49
50 , // 50
51 , // 51
52 , // 52
53 , // 53
54 , // 54
55 , // 55
56 , // 56
57 , // 57
58 , // 58
59 , // 59
60 , // 60
61 , // 61
62 , // 62
63 , // 63
161 , // 64
65 , // 65
66 , // 66
67 , // 67
68 , // 68
69 , // 69
70 , // 70
71 , // 71
72 , // 72
73 , // 73
74 , // 74
75 , // 75
76 , // 76
77 , // 77
78 , // 78
79 , // 79
80 , // 80
81 , // 81
82 , // 82
83 , // 83
84 , // 84
85 , // 85
86 , // 86
87 , // 87
88 , // 88
89 , // 89
90 , // 90
196 , // 91
214 , // 92
209 , // 93
220 , // 94
167 , // 95
191 , // 96
97 , // 97
98 , // 98
99 , // 99
100 , // 100
101 , // 101
102 , // 102
103 , // 103
104 , // 104
105 , // 105
106 , // 106
107 , // 107
108 , // 108
109 , // 109
110 , // 110
111 , // 111
112 , // 112
113 , // 113
114 , // 114
115 , // 115
116 , // 116
117 , // 117
118 , // 118
119 , // 119
120 , // 120
121 , // 121
122 , // 122
228 , // 123
246 , // 124
241 , // 125
252 , // 126
224 // 127
} ;
const int GSM_TO_ASCII_TABLE_SIZE = sizeof ( gsm_to_ascii ) / sizeof ( gsm_to_ascii [ 0 ] ) ;
AT_CellularSMS : : AT_CellularSMS ( ATHandler & at ) : AT_CellularBase ( at ) , _cb ( 0 ) , _mode ( CellularSMSMmodeText ) ,
_use_8bit_encoding ( false ) , _sim_wait_time ( 0 ) , _sms_message_ref_number ( 1 ) , _sms_info ( NULL )
{
/* URCs, handled out of band */
2018-02-15 08:16:25 +00:00
_at . set_urc_handler ( " +CMTI: " , callback ( this , & AT_CellularSMS : : cmti_urc ) ) ;
_at . set_urc_handler ( " +CMT: " , callback ( this , & AT_CellularSMS : : cmt_urc ) ) ;
2018-02-09 11:24:27 +00:00
}
AT_CellularSMS : : ~ AT_CellularSMS ( )
{
}
void AT_CellularSMS : : cmt_urc ( )
{
2018-02-19 10:44:24 +00:00
tr_debug ( " CMT_URC called " ) ;
2018-02-09 11:24:27 +00:00
//+CMT: <oa>,[<alpha>],<scts>[,<tooa>,<fo>,<pid>,<dcs>,<sca>,<tosca>,<length>]<CR><LF><data>
_at . consume_to_stop_tag ( ) ;
// call user defined callback function
if ( _cb ) {
_cb ( ) ;
} else {
2018-02-19 10:44:24 +00:00
tr_warn ( " cmt_urc, no user defined callback for receiving sms! " ) ;
2018-02-09 11:24:27 +00:00
}
}
void AT_CellularSMS : : cmti_urc ( )
{
//+CMTI: <mem>,<index>,
2018-02-19 10:44:24 +00:00
tr_debug ( " CMTI_URC called " ) ;
2018-02-09 11:24:27 +00:00
// call user defined callback function
if ( _cb ) {
_cb ( ) ;
} else {
2018-02-19 10:44:24 +00:00
tr_warn ( " cmti_urc, no user defined callback for receiving sms! " ) ;
2018-02-09 11:24:27 +00:00
}
}
nsapi_error_t AT_CellularSMS : : set_cnmi ( )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CNMI=2,1 " ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
nsapi_error_t AT_CellularSMS : : set_cmgf ( int msg_format )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CMGF= " ) ;
_at . write_int ( msg_format ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
nsapi_error_t AT_CellularSMS : : set_csmp ( int fo , int vp , int pid , int dcs )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CSMP= " ) ;
_at . write_int ( fo ) ;
_at . write_int ( vp ) ;
_at . write_int ( pid ) ;
_at . write_int ( dcs ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
nsapi_error_t AT_CellularSMS : : set_csdh ( int show_header )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CSDH= " ) ;
_at . write_int ( show_header ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
nsapi_error_t AT_CellularSMS : : initialize ( CellularSMSMmode mode )
{
_at . lock ( ) ;
set_cnmi ( ) ; //set new SMS indication
set_cmgf ( mode ) ; //set message format/PDU
if ( mode = = CellularSMSMmodeText ) {
set_csmp ( FIRST_OCTET_DELIVER_SUBMIT , TP_VALIDITY_PERIOD_24_HOURS , TP_PROTOCOL_IDENTIFIER ,
SMS_DATA_CODING_SCHEME ) ; //set Set Text Mode Parameters(default values for SMS-SUBMIT and RECEIVE)
set_csdh ( 1 ) ; //set header extra info as it's needed
}
_mode = mode ;
return _at . unlock_return_error ( ) ;
}
void AT_CellularSMS : : set_extra_sim_wait_time ( int sim_wait_time )
{
_sim_wait_time = sim_wait_time ;
}
char * AT_CellularSMS : : create_pdu ( const char * phone_number , const char * message , uint8_t message_length , uint8_t msg_parts ,
2018-02-21 08:37:27 +00:00
uint8_t msg_part_number , uint8_t & header_size )
2018-02-09 11:24:27 +00:00
{
int totalPDULength = 0 ;
int number_len = strlen ( phone_number ) ;
totalPDULength + = number_len ;
if ( number_len & 0x01 ) { // if phone number length is not even length we must pad it and so +1
totalPDULength + = 1 ;
}
totalPDULength + = 16 ; // all other than phone number and message length
if ( msg_parts > 1 ) { // add more space for UDH
totalPDULength + = 12 ;
}
// there might be need for padding so some more space
totalPDULength + = 2 ;
// message 7-bit padded and it will be converted to hex so it will take twice as much space
totalPDULength + = ( message_length - ( message_length / 8 ) ) * 2 ;
char * pdu = ( char * ) calloc ( totalPDULength , sizeof ( char ) ) ;
if ( ! pdu ) {
return NULL ;
}
int x = 0 ;
// See more how to create PDU from 3GPP specification 23040
// first two define that we use service center number which is set with +CSCA
pdu [ x + + ] = ' 0 ' ;
pdu [ x + + ] = ' 0 ' ;
// First Octet of the TPDU. 41 means SMS SUBMIT, no validity period, no status report, use User Data Header.
// 01 means SMS SUBMIT, no validity period, no status report, NO User Data Header.
if ( msg_parts > 1 ) { // concatenated, must use UDH
pdu [ x + + ] = ' 4 ' ;
} else {
pdu [ x + + ] = ' 0 ' ;
}
pdu [ x + + ] = ' 1 ' ;
// assign a message reference automatically. We have defined TP-RD bit as 0 so duplicates are not rejected.
pdu [ x + + ] = ' 0 ' ;
pdu [ x + + ] = ' 0 ' ;
// [6] and [7] Length of the Destination Phone Number
int_to_hex_str ( number_len , pdu + x ) ;
x + = 2 ;
// Type of the Destination Phone Number
pdu [ x + + ] = ' 8 ' ;
pdu [ x + + ] = ' 1 ' ;
// phone number as reverse nibble encoded
int i = 0 ;
for ( ; i < number_len ; i + = 2 ) {
if ( i + 1 = = number_len ) {
pdu [ x + + ] = ' f ' ;
} else {
pdu [ x + + ] = phone_number [ i + 1 ] ;
}
pdu [ x + + ] = phone_number [ i ] ;
}
// Protocol Identifier
pdu [ x + + ] = ' 0 ' ;
pdu [ x + + ] = ' 0 ' ;
// Data Coding Scheme, GSM 7-bit default alphabet = '00', 8-bit '04'
pdu [ x + + ] = ' 0 ' ;
if ( _use_8bit_encoding ) {
pdu [ x + + ] = ' 4 ' ;
} else {
pdu [ x + + ] = ' 0 ' ;
}
// possible to use 16 bit identifier, can't be defined yet from outside
bool use_16_bit_identifier = false ;
uint8_t udhlen = 0 ;
// Length can be update after we have created PDU, store position for later use.
int lengthPos = x ;
x + = 2 ;
int paddingBits = 0 ;
if ( msg_parts > 1 ) { // concatenated, must use UDH
// user data header length in chars
pdu [ x + + ] = ' 0 ' ;
if ( use_16_bit_identifier ) {
udhlen = 7 ; // udh length in chars (6) + udhl length in chars
pdu [ x + + ] = ' 6 ' ;
} else {
udhlen = 6 ; // udh length in chars (5) + udhl length in chars
pdu [ x + + ] = ' 5 ' ;
}
// Information element identifier
pdu [ x + + ] = ' 0 ' ;
if ( use_16_bit_identifier ) {
pdu [ x + + ] = ' 8 ' ;
} else {
pdu [ x + + ] = ' 0 ' ;
}
// Information element data length
pdu [ x + + ] = ' 0 ' ;
if ( use_16_bit_identifier ) {
pdu [ x + + ] = ' 4 ' ;
} else {
pdu [ x + + ] = ' 3 ' ;
}
// A reference number (must be the same for all parts of the same larger messages)
int_to_hex_str ( _sms_message_ref_number & 0xFF , pdu + x ) ;
x + = 2 ;
if ( use_16_bit_identifier ) {
int_to_hex_str ( ( _sms_message_ref_number > > 16 ) & 0xFF , pdu + x ) ;
x + = 2 ;
}
// How many parts does this message have?
int_to_hex_str ( msg_parts , pdu + x ) ;
x + = 2 ;
// this is a part number
int_to_hex_str ( msg_part_number , pdu + x ) ;
x + = 2 ;
// if there is padding bits then udhlen is octet bigger as we need to keep septet boundary
paddingBits = ( udhlen * 8 ) % 7 ;
if ( paddingBits ) {
paddingBits = 7 - paddingBits ;
udhlen + = 1 ;
}
}
if ( _use_8bit_encoding ) {
char_str_to_hex_str ( message , message_length , pdu + x ) ;
} else {
// we might need to send zero length sms
if ( message_length ) {
if ( pack_7_bit_gsm_and_hex ( message , message_length , pdu + x , paddingBits ) = = 0 ) {
free ( pdu ) ;
return NULL ;
}
}
}
// now we know the correct length of the UDL (User Data Length)
int_to_hex_str ( message_length + udhlen , pdu + lengthPos ) ;
2018-02-21 08:37:27 +00:00
header_size = x ;
2018-02-09 11:24:27 +00:00
return pdu ;
}
nsapi_size_or_error_t AT_CellularSMS : : send_sms ( const char * phone_number , const char * message , int msg_len )
{
int single_sms_max_length = _use_8bit_encoding ? SMS_MAX_SIZE_8BIT_SINGLE_SMS_SIZE :
SMS_MAX_SIZE_GSM7_SINGLE_SMS_SIZE ;
if ( ( _mode = = CellularSMSMmodeText & & msg_len > single_sms_max_length ) | | ! phone_number ) {
return NSAPI_ERROR_PARAMETER ;
}
_at . lock ( ) ;
int write_size = 0 ;
int remove_plus_sign = ( phone_number [ 0 ] = = ' + ' ) ? 1 : 0 ;
wait_ms ( _sim_wait_time ) ;
if ( _mode = = CellularSMSMmodeText ) {
_at . cmd_start ( " AT+CMGS= " ) ;
_at . write_string ( phone_number + remove_plus_sign ) ;
_at . cmd_stop ( ) ;
wait_ms ( _sim_wait_time ) ;
_at . resp_start ( " > " , true ) ;
if ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) {
write_size = _at . write_bytes ( ( uint8_t * ) message , msg_len ) ;
if ( write_size < msg_len ) {
// sending can be cancelled by giving <ESC> character (IRA 27).
_at . cmd_start ( ESC ) ;
_at . cmd_stop ( ) ;
_at . unlock ( ) ;
return write_size ;
}
// <ctrl-Z> (IRA 26) must be used to indicate the ending of the message body.
_at . cmd_start ( CTRL_Z ) ;
_at . cmd_stop ( ) ;
2018-02-15 08:16:25 +00:00
_at . resp_start ( " +CMGS: " ) ;
2018-02-09 11:24:27 +00:00
_at . resp_stop ( ) ;
}
} else {
// supports uncompressed 8 bit data and GSM 7 bit default alphabet data. Current implementation uses only
// GSM 7 bit default but support is done for 8 bit data.
int sms_count ;
int concatenated_sms_length = _use_8bit_encoding ? SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE :
SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE ;
if ( msg_len < = single_sms_max_length ) {
// single message
sms_count = 1 ;
} else {
// concatenated message
sms_count = msg_len / concatenated_sms_length ;
if ( msg_len % concatenated_sms_length ! = 0 ) {
sms_count + + ;
}
}
int remaining_len = msg_len ;
int pdu_len ;
2018-02-21 08:37:27 +00:00
int msg_write_len = 0 ;
uint8_t header_len ;
2018-02-09 11:24:27 +00:00
char * pdu_str ;
2018-02-21 08:37:27 +00:00
for ( int i = 0 ; i < sms_count ; i + + ) {
2018-02-09 11:24:27 +00:00
2018-02-21 08:37:27 +00:00
header_len = 0 ;
2018-02-09 11:24:27 +00:00
if ( sms_count = = 1 ) {
pdu_len = msg_len ;
} else {
pdu_len = remaining_len > concatenated_sms_length ? concatenated_sms_length : remaining_len ;
}
pdu_str = create_pdu ( phone_number + remove_plus_sign , message + i * concatenated_sms_length , pdu_len ,
2018-02-21 08:37:27 +00:00
sms_count , i + 1 , header_len ) ;
2018-02-09 11:24:27 +00:00
if ( ! pdu_str ) {
_at . unlock ( ) ;
return NSAPI_ERROR_NO_MEMORY ;
}
pdu_len = strlen ( pdu_str ) ;
// specification says that service center number should not be included so we subtract -2 from pdu_len as we use '00' for automatic service center number
_at . cmd_start ( " AT+CMGS= " ) ;
_at . write_int ( ( pdu_len - 2 ) / 2 ) ;
_at . cmd_stop ( ) ;
wait_ms ( _sim_wait_time ) ;
_at . resp_start ( " > " , true ) ;
if ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) {
write_size = _at . write_bytes ( ( uint8_t * ) pdu_str , pdu_len ) ;
if ( write_size < pdu_len ) {
2018-02-21 08:37:27 +00:00
// calculate exact size of what we have send
if ( write_size < = header_len ) {
// managed only to write header or some of it so actual msg write size in this iteration is 0
write_size = 0 ;
} else {
write_size = ( write_size - header_len ) / 2 ; // as hex encoded so divide by two
}
msg_write_len + = write_size ;
2018-02-09 11:24:27 +00:00
// sending can be cancelled by giving <ESC> character (IRA 27).
_at . cmd_start ( ESC ) ;
_at . cmd_stop ( ) ;
_at . unlock ( ) ;
free ( pdu_str ) ;
2018-02-21 08:37:27 +00:00
return msg_write_len ;
2018-02-09 11:24:27 +00:00
}
// <ctrl-Z> (IRA 26) must be used to indicate the ending of the message body.
_at . cmd_start ( CTRL_Z ) ;
_at . cmd_stop ( ) ;
2018-02-15 08:16:25 +00:00
_at . resp_start ( " +CMGS: " ) ;
2018-02-09 11:24:27 +00:00
_at . resp_stop ( ) ;
}
free ( pdu_str ) ;
remaining_len - = concatenated_sms_length ;
if ( _at . get_last_error ( ) ! = NSAPI_ERROR_OK ) {
return _at . unlock_return_error ( ) ;
}
}
}
_sms_message_ref_number + + ;
nsapi_error_t ret = _at . get_last_error ( ) ;
_at . unlock ( ) ;
2018-02-21 08:37:27 +00:00
return ( ret = = NSAPI_ERROR_OK ) ? msg_len : ret ;
2018-02-09 11:24:27 +00:00
}
void AT_CellularSMS : : set_sms_callback ( Callback < void ( ) > func )
{
_cb = func ;
}
nsapi_error_t AT_CellularSMS : : set_cpms ( const char * memr , const char * memw , const char * mems )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CPMS= " ) ;
_at . write_string ( memr ) ;
_at . write_string ( memw ) ;
_at . write_string ( mems ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
nsapi_error_t AT_CellularSMS : : set_csca ( const char * sca , int type )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CSCA= " ) ;
_at . write_string ( sca ) ;
_at . write_int ( type ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
nsapi_size_or_error_t AT_CellularSMS : : set_cscs ( const char * chr_set )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CSCS= " ) ;
_at . write_string ( chr_set ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
nsapi_error_t AT_CellularSMS : : delete_sms ( sms_info_t * sms )
{
_at . lock ( ) ;
for ( int i = 0 ; i < sms - > parts ; i + + ) {
_at . cmd_start ( " AT+CMGD= " ) ;
_at . write_int ( sms - > msg_index [ i ] ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
}
return _at . unlock_return_error ( ) ;
}
// we need this as for example concatenated sms can get different sms reference numbers
// if for example last part take much more time to arrive. This situation happened while testing.
// What this means that after this we can't read another sms because we always read the oldest sms
// that was corrupted. So we need to have delete all messages.
nsapi_error_t AT_CellularSMS : : delete_all_messages ( )
{
_at . lock ( ) ;
_at . cmd_start ( " AT+CMGD=1,4 " ) ;
_at . cmd_stop ( ) ;
_at . resp_start ( ) ;
_at . resp_stop ( ) ;
return _at . unlock_return_error ( ) ;
}
// read msg in text mode
nsapi_size_or_error_t AT_CellularSMS : : read_sms_from_index ( int msg_index , char * buf , uint16_t len , char * phone_num ,
char * time_stamp )
{
/*
* + CMGR : < stat > , < oa > , < alpha > , < scts > [ , < tooa > , < fo > , < pid > , < dcs > , < sca > , < tosca > , < length > ] < CR > < LF > < data > < CR > < LF > OK < CR > < LF >
*/
wait_ms ( _sim_wait_time ) ;
_at . cmd_start ( " AT+CMGR= " ) ;
_at . write_int ( msg_index ) ;
_at . cmd_stop ( ) ;
// TODO: NOTE: If the selected <mem1> can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs),
// the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter.
// for now we support sms reading of received messages
int buf_len = 0 ;
if ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) {
char status [ SMS_STATUS_SIZE ] ;
// first we read msg status and with that we can decide how the rest of message format
2018-02-15 08:16:25 +00:00
_at . resp_start ( " +CMGR: " ) ;
2018-02-09 11:24:27 +00:00
if ( _at . info_resp ( ) ) {
_at . read_string ( status , SMS_STATUS_SIZE ) ;
uint16_t status_len = strlen ( status ) ;
if ( ( ( status_len = = sizeof ( " REC READ " ) - 1 ) & & memcmp ( " REC READ " , status , status_len ) = = 0 )
| | ( ( status_len = = sizeof ( " REC UNREAD " ) - 1 ) & & memcmp ( " REC UNREAD " , status , status_len ) = = 0 ) ) {
// Received message
if ( phone_num ) {
_at . read_string ( phone_num , SMS_MAX_PHONE_NUMBER_SIZE ) ;
}
else {
_at . skip_param ( ) ; // <oa>,<alpha>
}
_at . skip_param ( ) ; // <alpha>
if ( time_stamp ) {
_at . read_string ( time_stamp , SMS_MAX_TIME_STAMP_SIZE ) ;
}
( void ) _at . consume_to_stop_tag ( ) ; // consume until <CR><LF>
if ( buf ) {
_at . read_string ( buf , len ) ;
buf_len = strlen ( buf ) ;
}
}
}
_at . resp_stop ( ) ;
}
return ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) ? buf_len : _at . get_last_error ( ) ;
}
// read msg in PDU mode
nsapi_size_or_error_t AT_CellularSMS : : read_sms ( sms_info_t * sms , char * buf , char * phone_num , char * time_stamp )
{
// +CMGR: <stat>,[<alpha>],<length><CR><LF><pdu>
int index = - 1 ;
if ( sms - > parts = = sms - > parts_added ) {
char * pdu ; // we need a temp buffer as payload is hexencoded ---> can't use buf as it might be enough for message but not hexenconded pdu.
int status = - 1 ;
int msg_len = 0 ;
index = 0 ;
int pduSize = 0 ;
for ( int i = 0 ; i < sms - > parts ; i + + ) {
wait_ms ( _sim_wait_time ) ;
_at . cmd_start ( " AT+CMGR= " ) ;
_at . write_int ( sms - > msg_index [ i ] ) ;
_at . cmd_stop ( ) ;
2018-02-15 08:16:25 +00:00
_at . resp_start ( " +CMGR: " ) ;
2018-02-09 11:24:27 +00:00
if ( _at . info_resp ( ) ) {
status = _at . read_int ( ) ;
_at . skip_param ( ) ; // <alpha>
if ( ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) & & ( status = = 0 | | status = = 1 ) ) {
msg_len = _at . read_int ( ) ;
if ( msg_len > 0 ) {
pduSize = msg_len * 2 + 20 ; // *2 as it's hex encoded and +20 as service center number is not included in size given by CMGR
pdu = ( char * ) calloc ( pduSize , sizeof ( char ) ) ;
if ( ! pdu ) {
_at . resp_stop ( ) ;
return NSAPI_ERROR_NO_MEMORY ;
}
_at . read_string ( pdu , pduSize , true ) ;
if ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) {
msg_len = get_data_from_pdu ( pdu , NULL , NULL , phone_num , buf + index ) ;
if ( msg_len > = 0 ) { // we need to allow zero length messages
index + = msg_len ;
} else {
free ( pdu ) ;
_at . resp_stop ( ) ;
return - 1 ;
}
}
free ( pdu ) ;
}
}
}
_at . resp_stop ( ) ;
}
if ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) {
if ( time_stamp ) {
strcpy ( time_stamp , sms - > date ) ;
}
buf [ index ] = ' \0 ' ;
}
}
else {
2018-02-19 10:44:24 +00:00
tr_warn ( " NOT all concatenated parts were received... " ) ;
2018-02-09 11:24:27 +00:00
index = SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ ;
}
return index ;
}
nsapi_size_or_error_t AT_CellularSMS : : get_sms ( char * buf , uint16_t len , char * phone_num , uint16_t phone_len ,
char * time_stamp , uint16_t time_len , int * buf_size )
{
// validate buffer sizes already here to avoid any necessary function calls and locking of _at
if ( ( phone_num & & phone_len < SMS_MAX_PHONE_NUMBER_SIZE ) | | ( time_stamp & & time_len < SMS_MAX_TIME_STAMP_SIZE ) | |
buf = = NULL ) {
return NSAPI_ERROR_PARAMETER ;
}
_at . lock ( ) ;
nsapi_size_or_error_t err = list_messages ( ) ;
if ( err = = NSAPI_ERROR_OK ) {
// we return the oldest sms and delete it after successful read
sms_info_t * info = get_oldest_sms_index ( ) ;
if ( info ) {
if ( info - > msg_size + 1 > len ) { // +1 for '\0'
2018-02-19 10:44:24 +00:00
tr_warn ( " Given buf too small, len is: %d but is must be: %d " , len , info - > msg_size ) ;
2018-02-09 11:24:27 +00:00
if ( buf_size ) {
* buf_size = info - > msg_size ;
}
free_linked_list ( ) ;
_at . unlock ( ) ;
return NSAPI_ERROR_PARAMETER ;
}
if ( _mode = = CellularSMSMmodePDU ) {
err = read_sms ( info , buf , phone_num , time_stamp ) ;
} else {
err = read_sms_from_index ( info - > msg_index [ 0 ] , buf , len , phone_num , time_stamp ) ;
}
if ( err > 0 ) {
int delerr = delete_sms ( info ) ;
if ( delerr ) {
err = delerr ;
}
}
} else {
// No messages were found, return -1
err = - 1 ;
}
}
free_linked_list ( ) ;
_at . unlock ( ) ;
// update error only when there really was an error, otherwise we return the length
if ( _at . get_last_error ( ) ) {
err = _at . get_last_error ( ) ;
}
return err ;
}
nsapi_size_or_error_t AT_CellularSMS : : get_data_from_pdu ( const char * pdu , sms_info_t * info , int * part_number ,
char * phone_number , char * msg )
{
int index = 0 ;
int tmp ;
bool userDataHeader ;
int oaLength ;
int dataScheme ;
nsapi_size_or_error_t err = NSAPI_ERROR_OK ;
// read Length of the SMSC information
oaLength = hex_str_to_int ( pdu , 2 ) ;
index + = 2 ; // length we just read
index + = oaLength * 2 ; // skip service center number
// read first the lower part of first octet as there is message type
index + + ;
tmp = hex_str_to_int ( pdu + index , 1 ) ;
//wait_ms(200);
if ( ( tmp & 0x03 ) = = 0 ) { // SMS-DELIVER type, last two bits should be zero
// UDH present? Check from first octets higher part
tmp = hex_str_to_int ( pdu + ( - - index ) , 1 ) ;
userDataHeader = ( ( tmp & 0x04 ) = = 0 ) ? false : true ;
index + = 2 ; // we just read the high bits of first octet so move +2
// originating address length
oaLength = hex_str_to_int ( pdu + index , 2 ) ;
index + = 2 ; // add index over address length
index + = 2 ; // skip number type
if ( phone_number ) {
// phone number as reverse nibble encoded
int a = 0 ;
for ( ; a < oaLength ; a + = 2 ) {
if ( a + 1 = = oaLength ) {
phone_number [ a ] = pdu [ index + a + 1 ] ;
} else {
phone_number [ a ] = pdu [ index + a + 1 ] ;
phone_number [ a + 1 ] = pdu [ index + a ] ;
}
}
phone_number [ oaLength ] = ' \0 ' ;
}
index + = oaLength ;
if ( oaLength & 0x01 ) { // if phone number length is odd then it has padded F so skip that
index + + ;
}
index + = 2 ; // skip TP-Protocol identifier
dataScheme = hex_str_to_int ( pdu + index , 2 ) ;
index + = 2 ; // skip TP-Data-Coding-Scheme
// next one is date, it's length is 7 octets according to 3GPP TS 23.040
// create time string
if ( info ) {
int i = 0 ;
// year
info - > date [ i + + ] = pdu [ index + 1 ] ;
info - > date [ i + + ] = pdu [ index ] ;
index + = 2 ;
info - > date [ i + + ] = ' / ' ;
// month
info - > date [ i + + ] = pdu [ index + 1 ] ;
info - > date [ i + + ] = pdu [ index ] ;
index + = 2 ;
info - > date [ i + + ] = ' / ' ;
// Day
info - > date [ i + + ] = pdu [ index + 1 ] ;
info - > date [ i + + ] = pdu [ index ] ;
index + = 2 ;
info - > date [ i + + ] = ' , ' ;
// Hour
info - > date [ i + + ] = pdu [ index + 1 ] ;
info - > date [ i + + ] = pdu [ index ] ;
index + = 2 ;
info - > date [ i + + ] = ' : ' ;
// Minute
info - > date [ i + + ] = pdu [ index + 1 ] ;
info - > date [ i + + ] = pdu [ index ] ;
index + = 2 ;
info - > date [ i + + ] = ' : ' ;
// Second
info - > date [ i + + ] = pdu [ index + 1 ] ;
info - > date [ i + + ] = pdu [ index ] ;
index + = 2 ;
// timezone related to GMT. pdu[index+1] most significant bit indicates the sign related to gmt
tmp = hex_str_to_int ( pdu + index + 1 , 1 ) ;
if ( tmp & 0x08 ) {
info - > date [ i + + ] = ' - ' ;
} else {
info - > date [ i + + ] = ' + ' ;
}
// pdu[index+1 & 0x07 is the most significant bits of the timezone
// pdu [index] is the least significant bits
info - > date [ i + + ] = ' 0 ' + ( tmp & 0x07 ) ;
info - > date [ i + + ] = pdu [ index ] ;
info - > date [ i ] = ' \0 ' ;
index + = 2 ;
} else {
index + = 14 ;
}
2018-02-22 08:55:13 +00:00
int udl = hex_str_to_int ( pdu + index , 2 ) ;
2018-02-09 11:24:27 +00:00
index + = 2 ;
2018-02-22 08:55:13 +00:00
2018-02-09 11:24:27 +00:00
int paddingBits = 0 ;
int partnro = 1 ;
if ( userDataHeader ) {
// we need to read User Defined Header to know what part number this message is.
2018-02-22 08:55:13 +00:00
index + = read_udh_from_pdu ( pdu + index , info , partnro , paddingBits ) ;
2018-02-09 11:24:27 +00:00
}
if ( part_number ) {
* part_number = partnro ;
}
if ( msg ) {
// we are reading the message
2018-02-22 08:55:13 +00:00
err = read_pdu_payload ( pdu + index , udl , dataScheme , msg , paddingBits ) ;
2018-02-09 11:24:27 +00:00
}
else {
if ( dataScheme = = 0x00 ) {
// when listing messages we need to calculated length. Other way would be unpacking the whole message.
err = strlen ( pdu + index ) > > 1 ;
err * = 8 ;
err / = 7 ;
} else if ( dataScheme = = 0x04 ) {
err = strlen ( pdu + index ) > > 1 ;
} else {
return NSAPI_ERROR_UNSUPPORTED ;
}
}
return err ;
}
else {
// message was not DELIVER so discard it
return NSAPI_ERROR_UNSUPPORTED ;
}
}
2018-02-22 08:55:13 +00:00
// read params from User Defined Header
int AT_CellularSMS : : read_udh_from_pdu ( const char * pdu , sms_info_t * info , int & part_number , int & padding_bits ) {
2018-02-09 11:24:27 +00:00
int index = 0 ;
int udhLength = hex_str_to_int ( pdu , 2 ) ;
index + = 2 ;
// if there is padding bits then udhlen is octet bigger as we need to keep septet boundary
padding_bits = ( ( udhLength + 1 ) * 8 ) % 7 ; // +1 is for udhLength itself
2018-02-22 08:55:13 +00:00
2018-02-09 11:24:27 +00:00
if ( padding_bits ) {
padding_bits = 7 - padding_bits ;
} else {
padding_bits = 0 ;
}
int tmp = hex_str_to_int ( pdu + index , 2 ) ;
index + = 4 ;
if ( tmp = = 0 ) { // 8-bit reference number
if ( info ) {
info - > msg_ref_number = ( uint16_t ) hex_str_to_int ( pdu + index , 2 ) ;
}
index + = 2 ;
} else { // 16-bit reference number
if ( info ) {
info - > msg_ref_number = ( uint16_t ) hex_str_to_int ( pdu + index + 2 , 2 ) ;
tmp = hex_str_to_int ( pdu + index , 2 ) ;
info - > msg_ref_number | = ( tmp < < 8 ) ;
}
index + = 4 ;
}
if ( info ) {
2018-02-22 08:55:13 +00:00
info - > parts = hex_str_to_int ( pdu + index , 2 ) ;
2018-02-09 11:24:27 +00:00
}
index + = 2 ;
part_number = hex_str_to_int ( pdu + index , 2 ) ;
index + = 2 ;
return ( udhLength * 2 + 2 ) ; // udh in hex and udhl
}
2018-02-22 08:55:13 +00:00
nsapi_size_or_error_t AT_CellularSMS : : read_pdu_payload ( const char * pdu , int msg_len , int scheme , char * msg , int padding_bits )
2018-02-09 11:24:27 +00:00
{
if ( scheme = = 0x00 ) {
// 7 bit gsm encoding, must do the conversions from hex to 7-bit encoding and to ascii
2018-02-22 08:55:13 +00:00
return unpack_7_bit_gsm_to_str ( pdu , strlen ( pdu ) / 2 , msg , padding_bits , msg_len ) ;
2018-02-09 11:24:27 +00:00
} else if ( scheme = = 0x04 ) {
// 8bit scheme so just convert hexstring to charstring
return hex_str_to_char_str ( pdu , strlen ( pdu ) , msg ) ;
} else {
2018-02-19 10:44:24 +00:00
tr_error ( " Received unsupported data coding scheme: 0x%02x " , scheme ) ;
2018-02-09 11:24:27 +00:00
return NSAPI_ERROR_UNSUPPORTED ;
}
}
void AT_CellularSMS : : free_linked_list ( )
{
sms_info_t * info = _sms_info ;
sms_info_t * old ;
while ( info ) {
old = info ;
info = info - > next_info ;
delete old ;
}
_sms_info = NULL ;
}
void AT_CellularSMS : : add_info ( sms_info_t * info , int index , int part_number ) {
// check for same message reference id. If found, update it and delete the given info.
// if NOT found then add to the end of the list.
if ( ! _sms_info ) {
info - > msg_index [ part_number - 1 ] = index ; // part numbering starts from 1 so -1 to put to right index
_sms_info = info ;
return ;
}
sms_info_t * current = _sms_info ;
sms_info_t * prev ;
bool found_msg = false ;
while ( current ) {
prev = current ;
// sms messages can have same reference number so additional checks are needed.
// TODO: should we include phone number also?
if ( current - > msg_ref_number = = info - > msg_ref_number & & current - > parts > current - > parts_added & &
info - > parts > info - > parts_added ) {
// multipart sms, update msg size and index
current - > msg_size + = info - > msg_size ;
current - > msg_index [ part_number - 1 ] = index ; // part numbering starts from 1 so -1 to put to right index
current - > parts_added + + ;
// update oldest part as date
if ( compare_time_strings ( info - > date , current - > date ) = = - 1 ) {
strcpy ( current - > date , info - > date ) ;
}
found_msg = true ;
break ;
}
current = current - > next_info ;
}
if ( found_msg ) {
// info was added to existing item in linked list, must be deleted
delete info ;
} else {
// message not found, add to linked list
info - > msg_index [ part_number - 1 ] = index ;
prev - > next_info = info ;
}
}
// reads all the messages to the linked list AT_CellularSMS::_sms_info
nsapi_error_t AT_CellularSMS : : list_messages ( )
{
// TODO: NOTE: If the selected <mem1> can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs),
// the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter.
// for now we assume that only SMS-DELIVER messages are read.
if ( _mode = = CellularSMSMmodePDU ) {
_at . cmd_start ( " AT+CMGL=4 " ) ;
} else {
_at . cmd_start ( " AT+CMGL= \" ALL \" " ) ;
}
_at . cmd_stop ( ) ;
sms_info_t * info = NULL ;
// init for 1 so that in text mode we will add to the correct place without any additional logic in addInfo() in text mode
int part_number = 1 ;
int index = 0 ;
int length = 0 ;
char * pdu = NULL ;
2018-02-15 08:16:25 +00:00
_at . resp_start ( " +CMGL: " ) ;
2018-02-09 11:24:27 +00:00
while ( _at . info_resp ( ) ) {
info = new sms_info_t ( ) ;
if ( ! info ) {
_at . resp_stop ( ) ;
return NSAPI_ERROR_NO_MEMORY ;
}
if ( _mode = = CellularSMSMmodePDU ) {
//+CMGL: <index>,<stat>,[<alpha>],<length><CR><LF><pdu>[<CR><LF>
// +CMGL:<index>,<stat>,[<alpha>],<length><CR><LF><pdu>
//[...]]
index = _at . read_int ( ) ;
_at . skip_param ( 2 ) ; // <stat>,[<alpha>]
length = _at . read_int ( ) ;
length = length * 2 + 20 ; // *2 as it's hex encoded and +20 as service center number is not included in size given by CMGL
pdu = ( char * ) calloc ( length , sizeof ( char ) ) ;
if ( ! pdu ) {
delete info ;
_at . resp_stop ( ) ;
return NSAPI_ERROR_NO_MEMORY ;
}
_at . read_string ( pdu , length , true ) ;
if ( _at . get_last_error ( ) = = NSAPI_ERROR_OK ) {
info - > msg_size = get_data_from_pdu ( pdu , info , & part_number ) ;
}
} else {
// +CMGL: <index>,<stat>,<oa/da>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[<CR><LF>
// +CMGL: <index>,<stat>,<da/oa>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[...]]
index = _at . read_int ( ) ;
( void ) _at . consume_to_stop_tag ( ) ; // consume until <CR><LF>
( void ) _at . consume_to_stop_tag ( ) ; // consume until <CR><LF>
}
if ( index > 0 ) {
add_info ( info , index , part_number ) ;
} else {
delete info ;
info = NULL ;
}
free ( pdu ) ;
pdu = NULL ;
}
_at . resp_stop ( ) ;
return _at . get_last_error ( ) ;
}
AT_CellularSMS : : sms_info_t * AT_CellularSMS : : get_oldest_sms_index ( )
{
/*
* Different scenarios when finding the oldest concatenated sms
*
* 1. Find first parts first and it was received first
* 2. Find first parts first and it was NOT received first - > older timestamp might exist in some other part
* 3. Find other than first part first and it was received first
* 4. Find other than first part first and it was NOT received first - > older timestamp might exist in some other part
*
* So must take all message to a linked list and loop that for the oldest
*/
// if text mode we need to read sms with +CMGR because time stamp is optional while looping with +CMGL
sms_info_t * retVal = NULL ;
sms_info_t * current = _sms_info ;
nsapi_size_or_error_t err = 0 ;
while ( current ) {
if ( _mode = = CellularSMSMmodeText ) {
wait_ms ( _sim_wait_time ) ;
err = read_sms_from_index ( current - > msg_index [ 0 ] , NULL , 0 , NULL , current - > date ) ;
if ( err ! = 0 ) {
return NULL ;
}
}
if ( retVal = = NULL ) {
retVal = current ;
} else if ( compare_time_strings ( current - > date , retVal - > date ) = = - 1 ) {
// found older sms, update return value to oldest
retVal = current ;
}
current = current - > next_info ;
}
return retVal ;
}
// if time_string_1 is greater (more fresh date) then return 1, same 0, smaller -1. Error -2
int AT_CellularSMS : : compare_time_strings ( const char * time_string_1 , const char * time_string_2 )
{
time_t t1 ;
time_t t2 ;
bool success = create_time ( time_string_1 , & t1 ) & & create_time ( time_string_2 , & t2 ) ;
int retVal = - 2 ;
if ( success ) {
double diff = difftime ( t1 , t2 ) ;
if ( diff > 0 ) {
retVal = 1 ;
} else if ( diff = = 0 ) {
retVal = 0 ;
} else {
retVal = - 1 ;
}
}
return retVal ;
}
bool AT_CellularSMS : : create_time ( const char * time_string , time_t * time )
{
const int kNumberOfElements = 8 ;
tm time_struct = { 0 } ;
int gmt = 0 ;
char sign ;
bool retVal = false ;
if ( sscanf ( time_string , " %d/%d/%d,%d:%d:%d%c%d " , & time_struct . tm_year , & time_struct . tm_mon , & time_struct . tm_mday ,
& time_struct . tm_hour , & time_struct . tm_min , & time_struct . tm_sec , & sign , & gmt ) = = kNumberOfElements ) {
* time = mktime ( & time_struct ) ;
// add timezone as seconds. gmt is in quarter of hours.
int x = 60 * 60 * gmt * 0.25 ;
if ( sign = = ' + ' ) {
* time + = x ;
} else {
* time - = x ;
}
retVal = true ;
}
return retVal ;
}
uint16_t AT_CellularSMS : : pack_7_bit_gsm_and_hex ( const char * str , uint16_t len , char * buf ,
int number_of_padding_bit )
{
uint16_t strCnt = 0 ;
uint16_t i = 0 ;
uint8_t shift ;
char tmp ;
// convert to 7bit gsm first
char * gsm_str = ( char * ) malloc ( len ) ;
if ( ! gsm_str ) {
return 0 ;
}
for ( uint16_t y = 0 ; y < len ; y + + ) {
for ( int x = 0 ; x < GSM_TO_ASCII_TABLE_SIZE ; x + + ) {
if ( gsm_to_ascii [ x ] = = str [ y ] ) {
gsm_str [ y ] = x ;
}
}
}
// then packing and converting to hex
if ( number_of_padding_bit ) {
tmp = gsm_str [ strCnt ] < < number_of_padding_bit ;
strCnt + + ;
char_str_to_hex_str ( & tmp , 1 , & buf [ i * 2 ] ) ;
i + + ;
}
while ( strCnt < len ) {
if ( number_of_padding_bit ) {
shift = ( i + number_of_padding_bit - 2 ) % 7 ;
} else {
shift = i % 7 ;
}
if ( strCnt + 1 = = len ) {
tmp = ( gsm_str [ strCnt ] > > shift ) ;
} else {
tmp = ( gsm_str [ strCnt ] > > shift ) | ( gsm_str [ strCnt + 1 ] < < ( 7 - shift ) ) ;
}
char_str_to_hex_str ( & tmp , 1 , buf + ( i * 2 ) ) ;
if ( shift = = 6 ) {
strCnt + + ;
}
strCnt + + ;
i + + ;
}
free ( gsm_str ) ;
return i ;
}
uint16_t AT_CellularSMS : : unpack_7_bit_gsm_to_str ( const char * str , int len , char * buf , int padding_bits ,
2018-02-22 08:55:13 +00:00
int msg_len )
2018-02-09 11:24:27 +00:00
{
int strCount = 0 ;
uint16_t decodedCount = 0 ;
uint8_t shift ;
char tmp ;
char tmp1 ;
if ( padding_bits ) {
hex_str_to_char_str ( str , 2 , & tmp ) ;
buf [ decodedCount ] = gsm_to_ascii [ ( tmp > > padding_bits ) & 0x7F ] ;
strCount + + ;
decodedCount + + ;
}
while ( strCount < len ) {
shift = ( strCount - padding_bits ) % 7 ;
hex_str_to_char_str ( str + strCount * 2 , 2 , & tmp ) ;
if ( shift = = 0 ) {
buf [ decodedCount ] = gsm_to_ascii [ tmp & 0x7F ] ;
} else if ( shift = = 6 ) {
hex_str_to_char_str ( str + ( strCount - 1 ) * 2 , 2 , & tmp1 ) ;
buf [ decodedCount ] = gsm_to_ascii [ ( ( ( tmp1 > > 2 ) ) | ( tmp < < 6 ) ) & 0x7F ] ;
2018-02-22 08:55:13 +00:00
if ( decodedCount + 1 < msg_len ) {
2018-02-09 11:24:27 +00:00
hex_str_to_char_str ( str + strCount * 2 , 2 , & tmp ) ;
decodedCount + + ;
buf [ decodedCount ] = gsm_to_ascii [ ( tmp > > 1 ) & 0x7F ] ;
}
} else {
hex_str_to_char_str ( str + ( strCount - 1 ) * 2 , 2 , & tmp1 ) ;
buf [ decodedCount ] = gsm_to_ascii [ ( ( ( tmp1 > > ( 8 - shift ) ) ) | ( ( tmp < < shift ) ) ) & 0x7F ] ;
}
strCount + + ;
decodedCount + + ;
}
return decodedCount ;
}