mbed-os/features/cellular/framework/AT/AT_CellularSMS.cpp

1303 lines
40 KiB
C++

/*
* 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)
{
}
AT_CellularSMS::~AT_CellularSMS()
{
}
void AT_CellularSMS::cmt_urc()
{
tr_debug("CMT_URC called");
//+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 {
tr_warn("cmt_urc, no user defined callback for receiving sms!");
}
}
void AT_CellularSMS::cmti_urc()
{
//+CMTI: <mem>,<index>,
tr_debug("CMTI_URC called");
// call user defined callback function
if (_cb) {
_cb();
} else {
tr_warn("cmti_urc, no user defined callback for receiving sms!");
}
}
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)
{
if (_at.set_urc_handler("+CMTI:", callback(this, &AT_CellularSMS::cmti_urc)) ||
_at.set_urc_handler("+CMT:", callback(this, &AT_CellularSMS::cmt_urc))) {
return NSAPI_ERROR_NO_MEMORY;
}
_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,
uint8_t msg_part_number, uint8_t& header_size)
{
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);
header_size = x;
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();
_at.resp_start("+CMGS:");
_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;
int msg_write_len = 0;
uint8_t header_len;
char *pdu_str;
for (int i = 0; i < sms_count; i++) {
header_len = 0;
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,
sms_count, i+1, header_len);
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) {
// 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;
// sending can be cancelled by giving <ESC> character (IRA 27).
_at.cmd_start(ESC);
_at.cmd_stop();
_at.unlock();
free(pdu_str);
return msg_write_len;
}
// <ctrl-Z> (IRA 26) must be used to indicate the ending of the message body.
_at.cmd_start(CTRL_Z);
_at.cmd_stop();
_at.resp_start("+CMGS:");
_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();
return (ret == NSAPI_ERROR_OK) ? msg_len : ret;
}
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
_at.resp_start("+CMGR:");
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();
_at.resp_start("+CMGR:");
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 {
tr_warn("NOT all concatenated parts were received...");
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'
tr_warn("Given buf too small, len is: %d but is must be: %d", len, info->msg_size);
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;
}
int udl = hex_str_to_int(pdu+index, 2);
index +=2;
int paddingBits = 0;
int partnro = 1;
if (userDataHeader) {
// we need to read User Defined Header to know what part number this message is.
index += read_udh_from_pdu(pdu+index, info, partnro, paddingBits);
}
if (part_number) {
*part_number = partnro;
}
if (msg) {
// we are reading the message
err = read_pdu_payload(pdu+index, udl, dataScheme, msg, paddingBits);
}
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;
}
}
// 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) {
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
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) {
info->parts = hex_str_to_int(pdu+index, 2);
}
index +=2;
part_number = hex_str_to_int(pdu+index, 2);
index +=2;
return (udhLength*2 + 2); // udh in hex and udhl
}
nsapi_size_or_error_t AT_CellularSMS::read_pdu_payload(const char* pdu, int msg_len, int scheme, char *msg, int padding_bits)
{
if (scheme == 0x00) {
// 7 bit gsm encoding, must do the conversions from hex to 7-bit encoding and to ascii
return unpack_7_bit_gsm_to_str(pdu, strlen(pdu)/2, msg, padding_bits, msg_len);
} else if (scheme == 0x04) {
// 8bit scheme so just convert hexstring to charstring
return hex_str_to_char_str(pdu, strlen(pdu), msg);
} else {
tr_error("Received unsupported data coding scheme: 0x%02x", scheme);
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;
_at.resp_start("+CMGL:");
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,
int msg_len)
{
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];
if (decodedCount+1 < msg_len) {
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;
}