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

1283 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 <stdio.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 char 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_read_resp();
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_read_resp();
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_read_resp();
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_read_resp();
return _at.unlock_return_error();
}
nsapi_error_t AT_CellularSMS::initialize(CellularSMSMmode mode)
{
if (NSAPI_ERROR_OK != _at.set_urc_handler("+CMTI:", callback(this, &AT_CellularSMS::cmti_urc)) ||
NSAPI_ERROR_OK != _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 = new char[totalPDULength];
memset(pdu, 0, totalPDULength);
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';
}
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';
udhlen = 6; // udh length in chars (5) + udhl length in chars
pdu[x++] = '5';
// Information element identifier
pdu[x++] = '0';
pdu[x++] = '0';
// Information element data length
pdu[x++] = '0';
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;
// 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) {
delete [] 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();
delete [] 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();
}
delete [] 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_read_resp();
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_read_resp();
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_read_resp();
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_read_resp();
}
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_read_resp();
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) {
int len = _at.read_string(time_stamp, SMS_MAX_TIME_STAMP_SIZE);
if (len < (SMS_MAX_TIME_STAMP_SIZE - 2)) {
time_stamp[len++] = ',';
_at.read_string(&time_stamp[len], SMS_MAX_TIME_STAMP_SIZE - len);
}
}
(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;
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;
int msg_len;
index = 0;
int pduSize;
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 = new char[pduSize];
memset(pdu, 0, pduSize);
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 {
delete [] pdu;
_at.resp_stop();
return -1;
}
}
delete [] 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 > len) {
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
int type = hex_str_to_int(pdu + index, 1);
index += 2; // add index over type
if (phone_number) {
// phone number as reverse nibble encoded
int a = 0, field_length = oaLength;
if (type == 9) {
//add the plus sign in case of international number (23.040 chapter address fields)
phone_number[a++] = '+';
field_length++;
}
for (; a < field_length; a += 2) {
if ((a + 1) == field_length) {
phone_number[a] = pdu[index + 1];
index++;
} else {
phone_number[a] = pdu[index + 1];
phone_number[a + 1] = pdu[index];
index += 2;
}
}
phone_number[field_length] = '\0';
} else {
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 = new char[length];
memset(pdu, 0, length);
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;
}
delete [] 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;
if (len == 0) {
return 0;
}
// convert to 7bit gsm first
char *gsm_str = new char[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++;
}
delete [] 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;
}